From: Timo Röhling Date: Sun, 28 Aug 2022 12:13:25 +0000 (+0200) Subject: New upstream version 1.5.0+ds X-Git-Url: https://git.proxmox.com/?p=libgit2.git;a=commitdiff_plain;h=ad5611d85b27cf20f342ff382f93db9312eed074 New upstream version 1.5.0+ds --- diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..d6e9cfceb --- /dev/null +++ b/.clang-format @@ -0,0 +1,92 @@ +# This file is an example configuration for clang-format 5.0. +# +# Note that this style definition should only be understood as a hint +# for writing new code. The rules are still work-in-progress and does +# not yet exactly match the style we have in the existing code. + +# C Language specifics +Language: Cpp + +# Use tabs whenever we need to fill whitespace that spans at least from one tab +# stop to the next one. +# +# These settings are mirrored in .editorconfig. Keep them in sync. +UseTab: ForIndentation +TabWidth: 8 +IndentWidth: 8 +ContinuationIndentWidth: 8 +ColumnLimit: 80 + +AlignAfterOpenBracket: AlwaysBreak +AlignEscapedNewlines: Left +AlignTrailingComments: false + +# Allow putting parameters onto the next line +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false + +# Don't allow short braced statements to be on a single line +# if (a) not if (a) return; +# return; +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AllowShortLambdasOnASingleLine: None + +# Pack as many parameters or arguments onto the same line as possible +# int myFunction(int aaaaaaaaaaaa, int bbbbbbbb, +# int cccc); +BinPackArguments: true +BinPackParameters: false + +BreakBeforeBraces: Linux +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: false +BreakStringLiterals: false + +# The number of spaces before trailing line comments (// - comments). +# This does not affect trailing block comments (/* - comments). +SpacesBeforeTrailingComments: 1 + +# Don't insert spaces in casts +# x = (int32) y; not x = ( int32 ) y; +SpacesInCStyleCastParentheses: false + +# Don't insert spaces inside container literals +# var arr = [1, 2, 3]; not var arr = [ 1, 2, 3 ]; +SpacesInContainerLiterals: false + +# Don't insert spaces after '(' or before ')' +# f(arg); not f( arg ); +SpacesInParentheses: false + +# Don't insert spaces after '[' or before ']' +# int a[5]; not int a[ 5 ]; +SpacesInSquareBrackets: false + +# Insert a space after '{' and before '}' in struct initializers +Cpp11BracedListStyle: false + +# A list of macros that should be interpreted as foreach loops instead of as +# function calls. +ForEachMacros: + - 'git_array_foreach' + - 'git_vector_foreach' + +# The maximum number of consecutive empty lines to keep. +MaxEmptyLinesToKeep: 1 + +# No empty line at the start of a block. +KeepEmptyLinesAtTheStartOfBlocks: false + +# Penalties +# This decides what order things should be done if a line is too long +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +SortIncludes: false diff --git a/.github/release.yml b/.github/release.yml index c3c8da290..79158f492 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -6,10 +6,13 @@ changelog: - title: Bug fixes labels: - bug + - title: Security fixes + labels: + - security - title: Code cleanups labels: - cleanup - - title: CI improvements + - title: Build and CI improvements labels: - build - title: Documentation improvements diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000..285c273b1 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,82 @@ +# Benchmark libgit2 against the git reference implementation. +name: Benchmark + +on: + workflow_dispatch: + schedule: + - cron: '15 4 * * *' + +jobs: + # Run our nightly builds. We build a matrix with the various build + # targets and their details. Then we build either in a docker container + # (Linux) or on the actual hosts (macOS, Windows). + build: + # Only run scheduled workflows on the main repository; prevents people + # from using build minutes on their forks. + if: github.repository == 'libgit2/libgit2' + + strategy: + matrix: + platform: + - name: "Linux (clang, OpenSSL)" + env: + CC: clang + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_CLI=ON -DCMAKE_BUILD_TYPE=Release + CMAKE_BUILD_OPTIONS: --config Release + id: linux + os: ubuntu-latest + setup-script: ubuntu + - name: "macOS" + os: macos-10.15 + env: + CC: clang + CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_CLI=ON -DCMAKE_BUILD_TYPE=Release + CMAKE_BUILD_OPTIONS: --config Release + PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig + id: macos + setup-script: osx + - name: "Windows (amd64, Visual Studio)" + os: windows-2019 + env: + ARCH: amd64 + CMAKE_GENERATOR: Visual Studio 16 2019 + CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON -DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_CLI=ON -DCMAKE_BUILD_TYPE=Release + CMAKE_BUILD_OPTIONS: --config Release + id: windows + setup-script: win32 + fail-fast: false + name: "Build ${{ matrix.platform.name }}" + env: ${{ matrix.platform.env }} + runs-on: ${{ matrix.platform.os }} + steps: + - name: Check out repository + uses: actions/checkout@v2 + with: + path: source + fetch-depth: 0 + - name: Set up benchmark environment + run: source/ci/setup-${{ matrix.platform.setup-script }}-benchmark.sh + shell: bash + if: matrix.platform.setup-script != '' + - name: Build + run: | + mkdir build && cd build + ../source/ci/build.sh + shell: bash + - name: Benchmark + run: | + if [[ "$(uname -s)" == MINGW* ]]; then + GIT2_CLI="$(cygpath -w $(pwd))\\build\\Release\\git2_cli" + else + GIT2_CLI="$(pwd)/build/git2_cli" + fi + + mkdir benchmark && cd benchmark + ../source/tests/benchmarks/benchmark.sh --baseline-cli "git" --cli "${GIT2_CLI}" --json benchmarks.json --zip benchmarks.zip + shell: bash + - name: Upload results + uses: actions/upload-artifact@v2 + with: + name: benchmark-${{ matrix.platform.id }} + path: benchmark + if: always() diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 38b4a044a..000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: "CodeQL" - -on: - workflow_dispatch: - schedule: - - cron: '21 3 * * 1' - -env: - docker-registry: docker.pkg.github.com - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - steps: - - name: Check out repository - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: 'cpp' - - - name: Build - run: | - mkdir build - cd build - cmake .. -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON - cmake --build . - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 74bab53f3..bcad84b8b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: env: - docker-registry: docker.pkg.github.com + docker-registry: ghcr.io docker-config-path: source/ci/docker jobs: @@ -69,7 +69,7 @@ jobs: if [ "${{ matrix.container.base }}" != "" ]; then BASE_ARG="--build-arg BASE=${{ matrix.container.base }}" fi - docker build -t ${{ env.docker-registry-container-sha }} ${BASE_ARG} -f ${{ env.dockerfile }} . + docker build -t ${{ env.docker-registry-container-sha }} --build-arg UID=$(id -u) --build-arg GID=$(id -g) ${BASE_ARG} -f ${{ env.dockerfile }} . docker tag ${{ env.docker-registry-container-sha }} ${{ env.docker-registry-container-latest }} docker push ${{ env.docker-registry-container-sha }} docker push ${{ env.docker-registry-container-latest }} @@ -85,6 +85,7 @@ jobs: matrix: platform: - name: "Linux (Xenial, GCC, OpenSSL)" + id: xenial-gcc-openssl container: name: xenial env: @@ -93,6 +94,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON os: ubuntu-latest - name: Linux (Xenial, GCC, mbedTLS) + id: xenial-gcc-mbedtls container: name: xenial env: @@ -101,6 +103,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON os: ubuntu-latest - name: "Linux (Xenial, Clang, OpenSSL)" + id: xenial-clang-openssl container: name: xenial env: @@ -109,6 +112,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON os: ubuntu-latest - name: "Linux (Xenial, Clang, mbedTLS)" + id: xenial-clang-mbedtls container: name: xenial env: @@ -117,6 +121,7 @@ jobs: CMAKE_GENERATOR: Ninja os: ubuntu-latest - name: "Linux (MemorySanitizer)" + id: memorysanitizer container: name: focal env: @@ -130,6 +135,7 @@ jobs: UBSAN_OPTIONS: print_stacktrace=1 os: ubuntu-latest - name: "Linux (UndefinedBehaviorSanitizer)" + id: ubsanitizer container: name: focal env: @@ -143,6 +149,7 @@ jobs: UBSAN_OPTIONS: print_stacktrace=1 os: ubuntu-latest - name: "Linux (ThreadSanitizer)" + id: threadsanitizer container: name: focal env: @@ -157,6 +164,7 @@ jobs: TSAN_OPTIONS: suppressions=/home/libgit2/source/script/thread-sanitizer.supp second_deadlock_stack=1 os: ubuntu-latest - name: "macOS" + id: macos os: macos-10.15 env: CC: clang @@ -166,6 +174,7 @@ jobs: SKIP_NEGOTIATE_TESTS: true setup-script: osx - name: "Windows (amd64, Visual Studio)" + id: windows-amd64-vs os: windows-2019 env: ARCH: amd64 @@ -174,6 +183,7 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - name: "Windows (x86, Visual Studio)" + id: windows-x86-vs os: windows-2019 env: ARCH: x86 @@ -182,6 +192,7 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - name: "Windows (amd64, mingw)" + id: windows-amd64-mingw os: windows-2019 setup-script: mingw env: @@ -193,6 +204,7 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - name: "Windows (x86, mingw)" + id: windows-x86-mingw os: windows-2019 setup-script: mingw env: @@ -214,7 +226,7 @@ jobs: path: source fetch-depth: 0 - name: Set up build environment - run: source/ci/setup-${{ matrix.platform.setup-script }}.sh + run: source/ci/setup-${{ matrix.platform.setup-script }}-build.sh shell: bash if: matrix.platform.setup-script != '' - name: Setup QEMU @@ -229,7 +241,11 @@ jobs: working-directory: ${{ env.docker-config-path }} if: matrix.platform.container.name != '' - name: Create container - run: docker build -t ${{ env.docker-registry-container-sha }} -f ${{ env.dockerfile }} . + run: | + if [ "${{ matrix.container.base }}" != "" ]; then + BASE_ARG="--build-arg BASE=${{ matrix.container.base }}" + fi + docker build -t ${{ env.docker-registry-container-sha }} --build-arg UID=$(id -u) --build-arg GID=$(id -g) ${BASE_ARG} -f ${{ env.dockerfile }} . working-directory: ${{ env.docker-config-path }} if: matrix.platform.container.name != '' && env.docker-container-exists != 'true' - name: Build and test @@ -237,10 +253,12 @@ jobs: export GITTEST_NEGOTIATE_PASSWORD="${{ secrets.GITTEST_NEGOTIATE_PASSWORD }}" if [ -n "${{ matrix.platform.container.name }}" ]; then + mkdir build docker run \ --rm \ - --user libgit2:libgit2 \ + --user "$(id -u):$(id -g)" \ -v "$(pwd)/source:/home/libgit2/source" \ + -v "$(pwd)/build:/home/libgit2/build" \ -w /home/libgit2 \ -e ASAN_SYMBOLIZER_PATH \ -e CC \ @@ -248,19 +266,40 @@ jobs: -e CMAKE_GENERATOR \ -e CMAKE_OPTIONS \ -e GITTEST_NEGOTIATE_PASSWORD \ + -e GITTEST_FLAKY_STAT \ -e PKG_CONFIG_PATH \ -e SKIP_NEGOTIATE_TESTS \ -e SKIP_SSH_TESTS \ -e TSAN_OPTIONS \ -e UBSAN_OPTIONS \ ${{ env.docker-registry-container-sha }} \ - /bin/bash -c "mkdir build && cd build && ../source/ci/build.sh && ../source/ci/test.sh" + /bin/bash -c "cd build && ../source/ci/build.sh && ../source/ci/test.sh" else - mkdir build && cd build + mkdir build + cd build ../source/ci/build.sh ../source/ci/test.sh fi shell: bash + - name: Upload test results + uses: actions/upload-artifact@v3 + if: success() || failure() + with: + name: test-results-${{ matrix.platform.id }} + path: build/results_*.xml + + test_results: + name: Test results + needs: [ build ] + runs-on: ubuntu-latest + steps: + - name: Download test results + uses: actions/download-artifact@v3 + - name: Generate test summary + uses: test-summary/action@v1 + with: + paths: 'test-results-*/*.xml' + # Generate documentation using docurium. We'll upload the documentation # as a build artifact so that it can be reviewed as part of a pull @@ -270,6 +309,7 @@ jobs: documentation: name: Generate documentation needs: [ containers ] + if: success() || failure() runs-on: ubuntu-latest steps: - name: Check out repository diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5513d5b43..856da28a3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,7 +7,7 @@ on: - cron: '15 1 * * *' env: - docker-registry: docker.pkg.github.com + docker-registry: ghcr.io docker-config-path: source/ci/docker jobs: @@ -15,6 +15,10 @@ jobs: # targets and their details. Then we build either in a docker container # (Linux) or on the actual hosts (macOS, Windows). build: + # Only run scheduled workflows on the main repository; prevents people + # from using build minutes on their forks. + if: github.repository == 'libgit2/libgit2' + strategy: matrix: platform: @@ -247,6 +251,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true SKIP_PROXY_TESTS: true + GITTEST_FLAKY_STAT: true os: ubuntu-latest - name: "Linux (arm64, Bionic, GCC, OpenSSL)" container: @@ -271,7 +276,7 @@ jobs: path: source fetch-depth: 0 - name: Set up build environment - run: source/ci/setup-${{ matrix.platform.setup-script }}.sh + run: source/ci/setup-${{ matrix.platform.setup-script }}-build.sh shell: bash if: matrix.platform.setup-script != '' - name: Setup QEMU @@ -305,6 +310,7 @@ jobs: -e CMAKE_GENERATOR \ -e CMAKE_OPTIONS \ -e GITTEST_NEGOTIATE_PASSWORD \ + -e GITTEST_FLAKY_STAT \ -e PKG_CONFIG_PATH \ -e SKIP_NEGOTIATE_TESTS \ -e SKIP_SSH_TESTS \ @@ -319,6 +325,10 @@ jobs: shell: bash coverity: + # Only run scheduled workflows on the main repository; prevents people + # from using build minutes on their forks. + if: github.repository == 'libgit2/libgit2' + name: Coverity runs-on: ubuntu-latest steps: @@ -338,3 +348,32 @@ jobs: run: source/ci/coverity.sh env: COVERITY_TOKEN: ${{ secrets.coverity_token }} + + codeql: + # Only run scheduled workflows on the main repository; prevents people + # from using build minutes on their forks. + if: github.repository == 'libgit2/libgit2' + + name: CodeQL + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: 'cpp' + + - name: Build + run: | + mkdir build + cd build + cmake .. -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON + cmake --build . + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ff6748e0..2b5a2842c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,12 @@ -# CMake build script for the libgit2 project +# libgit2: the cross-platform, linkable library implementation of git. # See `README.md` for build instructions. +# +# This top-level CMakeLists.txt sets up configuration options and +# determines which subprojects to build. cmake_minimum_required(VERSION 3.5.1) -project(libgit2 VERSION "1.4.3" LANGUAGES C) +project(libgit2 VERSION "1.5.0" LANGUAGES C) # Add find modules to the path set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") @@ -15,6 +18,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") # Optional subsystems option(BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON) option(BUILD_TESTS "Build Tests using the Clar suite" ON) +option(BUILD_CLI "Build the command-line interface" ON) option(BUILD_EXAMPLES "Build library usage example apps" OFF) option(BUILD_FUZZERS "Build the fuzz targets" OFF) @@ -25,7 +29,8 @@ option(USE_NSEC "Support nanosecond precision file mtimes and cti # Backend selection option(USE_SSH "Link with libssh2 to enable SSH support" OFF) 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_SHA1 "Enable SHA1. Can be set to CollisionDetection(ON)/HTTPS" ON) +option(USE_SHA256 "Enable SHA256. Can be set to HTTPS/Builtin" ON) option(USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF) set(USE_HTTP_PARSER "" CACHE STRING "Specifies the HTTP Parser implementation; either system or builtin.") set(REGEX_BACKEND "" CACHE STRING "Regular expression implementation. One of regcomp_l, pcre2, pcre, regcomp, or builtin.") @@ -127,6 +132,14 @@ if(BUILD_FUZZERS) endif() +# Export for people who use us as a dependency + +if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) + set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) +endif() + + # Summary feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") diff --git a/COPYING b/COPYING index ccfb7dbf8..28226696d 100644 --- a/COPYING +++ b/COPYING @@ -1144,3 +1144,43 @@ worldwide. This software is distributed without any warranty. See . +---------------------------------------------------------------------- + +The built-in SHA256 support (src/hash/rfc6234) is taken from RFC 6234 +under the following license: + +Copyright (c) 2011 IETF Trust and the persons identified as +authors of the code. 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. + +- Neither the name of Internet Society, IETF or IETF Trust, nor + the names of specific contributors, may be used to endorse or + promote products derived from this software without specific + prior written permission. + +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. + diff --git a/README.md b/README.md index c73b50701..a9deaa01d 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,8 @@ Here are the bindings to libgit2 that are currently available: * parrot-libgit2 * Perl * Git-Raw +* Pharo Smalltalk + * libgit2-pharo-bindings * PHP * php-git * Python @@ -405,6 +407,8 @@ Here are the bindings to libgit2 that are currently available: * git2-rs * Swift * SwiftGit2 +* Tcl + * lg2 * Vala * libgit2.vapi diff --git a/ci/build.sh b/ci/build.sh index 5a51f925a..21a45af5f 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -59,7 +59,7 @@ echo "########################################################################## echo "## Configuring build environment" echo "##############################################################################" -echo cmake -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON -G \"${CMAKE_GENERATOR}\" ${CMAKE_OPTIONS} -S \"${SOURCE_DIR}\" +echo "${CMAKE}" -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON -G \"${CMAKE_GENERATOR}\" ${CMAKE_OPTIONS} -S \"${SOURCE_DIR}\" env PATH="${BUILD_PATH}" "${CMAKE}" -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON -G "${CMAKE_GENERATOR}" ${CMAKE_OPTIONS} -S "${SOURCE_DIR}" echo "" @@ -69,10 +69,11 @@ echo "########################################################################## # Determine parallelism; newer cmake supports `--build --parallel` but # we cannot yet rely on that. -if [ "${CMAKE_GENERATOR}" = "Unix Makefiles" -a "${CORES}" != "" ]; then +if [ "${CMAKE_GENERATOR}" = "Unix Makefiles" -a "${CORES}" != "" -a "${CMAKE_BUILD_OPTIONS}" = "" ]; then BUILDER=(make -j ${CORES}) else - BUILDER=("${CMAKE}" --build .) + BUILDER=("${CMAKE}" --build . ${CMAKE_BUILD_OPTIONS}) fi +echo "${BUILDER[@]}" env PATH="${BUILD_PATH}" "${BUILDER[@]}" diff --git a/ci/docker/bionic b/ci/docker/bionic index 51af5c01c..f1b69edef 100644 --- a/ci/docker/bionic +++ b/ci/docker/bionic @@ -28,17 +28,22 @@ RUN apt-get update && \ 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 && \ + curl --location --silent --show-error https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/mbedtls-2.16.2.tar.gz | \ + tar -xz && \ + cd mbedtls-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 + rm -rf mbedtls-mbedtls-2.16.2 FROM mbedtls AS adduser -RUN useradd --shell /bin/bash libgit2 --create-home +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 FROM adduser AS configure RUN mkdir /var/run/sshd diff --git a/ci/docker/centos7 b/ci/docker/centos7 index 8105f1442..28ed65081 100644 --- a/ci/docker/centos7 +++ b/ci/docker/centos7 @@ -48,7 +48,12 @@ RUN cd /tmp && \ rm -rf cmake-3.21.1 FROM cmake AS adduser -RUN useradd --shell /bin/bash libgit2 --create-home +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 FROM adduser AS configure ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig diff --git a/ci/docker/centos8 b/ci/docker/centos8 index cca088103..81f0c3c76 100644 --- a/ci/docker/centos8 +++ b/ci/docker/centos8 @@ -1,6 +1,10 @@ ARG BASE=centos:8 -FROM ${BASE} AS yum +FROM ${BASE} AS stream +RUN dnf -y --disablerepo '*' --enablerepo=extras swap centos-linux-repos centos-stream-repos && \ + dnf -y distro-sync + +FROM stream AS yum RUN yum install -y \ which \ bzip2 \ @@ -40,7 +44,12 @@ RUN cd /tmp && \ rm -rf valgrind-3.15.0 FROM valgrind AS adduser -RUN useradd --shell /bin/bash libgit2 --create-home +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 FROM adduser AS configure ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig diff --git a/ci/docker/focal b/ci/docker/focal index 37d7d6356..b3a402cb0 100644 --- a/ci/docker/focal +++ b/ci/docker/focal @@ -32,9 +32,9 @@ RUN apt-get update && \ FROM apt AS mbedtls RUN cd /tmp && \ - curl --location --silent --show-error https://tls.mbed.org/download/mbedtls-2.16.2-apache.tgz | \ + curl --location --silent --show-error https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/mbedtls-2.16.2.tar.gz | \ tar -xz && \ - cd mbedtls-2.16.2 && \ + cd mbedtls-mbedtls-2.16.2 && \ scripts/config.pl unset MBEDTLS_AESNI_C && \ scripts/config.pl set MBEDTLS_MD4_C 1 && \ mkdir build build-msan && \ @@ -45,7 +45,7 @@ RUN cd /tmp && \ CC=clang-10 CFLAGS="-fPIC" cmake -G Ninja -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=ON -DUSE_STATIC_MBEDTLS_LIBRARY=OFF -DCMAKE_BUILD_TYPE=MemSanDbg -DCMAKE_INSTALL_PREFIX=/usr/local/msan .. && \ ninja install && \ cd .. && \ - rm -rf mbedtls-2.16.2 + rm -rf mbedtls-mbedtls-2.16.2 FROM mbedtls AS libssh2 RUN cd /tmp && \ @@ -73,7 +73,13 @@ RUN cd /tmp && \ rm -rf valgrind-3.15.0 FROM valgrind AS adduser -RUN useradd --shell /bin/bash libgit2 --create-home +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 + FROM adduser AS configure RUN mkdir /var/run/sshd diff --git a/ci/docker/xenial b/ci/docker/xenial index c19fe421d..f5fa5a315 100644 --- a/ci/docker/xenial +++ b/ci/docker/xenial @@ -30,14 +30,14 @@ RUN apt-get update && \ 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 && \ + curl --location --silent --show-error https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/mbedtls-2.16.2.tar.gz | \ + tar -xz && \ + cd mbedtls-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 + rm -rf mbedtls-mbedtls-2.16.2 FROM mbedtls AS libssh2 RUN cd /tmp && \ @@ -60,7 +60,13 @@ RUN cd /tmp && \ rm -rf valgrind-3.15.0 FROM valgrind AS adduser -RUN useradd --shell /bin/bash libgit2 --create-home +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 + FROM adduser AS configure RUN mkdir /var/run/sshd diff --git a/ci/getcontainer.sh b/ci/getcontainer.sh index 07ef7b8ea..81d0c1d92 100755 --- a/ci/getcontainer.sh +++ b/ci/getcontainer.sh @@ -37,9 +37,13 @@ DOCKER_REGISTRY_CONTAINER_SHA="${DOCKER_REGISTRY_CONTAINER}:${DOCKER_SHA}" echo "docker-registry-container-sha=${DOCKER_REGISTRY_CONTAINER_SHA}" >> $GITHUB_ENV echo "docker-registry-container-latest=${DOCKER_REGISTRY_CONTAINER}:latest" >> $GITHUB_ENV +echo "::: logging in to ${DOCKER_REGISTRY} as ${GITHUB_ACTOR}" + exists="true" docker login https://${DOCKER_REGISTRY} -u ${GITHUB_ACTOR} -p ${GITHUB_TOKEN} || exists="false" +echo "::: pulling ${DOCKER_REGISTRY_CONTAINER_SHA}" + if [ "${exists}" != "false" ]; then docker pull ${DOCKER_REGISTRY_CONTAINER_SHA} || exists="false" fi diff --git a/ci/setup-mingw-build.sh b/ci/setup-mingw-build.sh new file mode 100755 index 000000000..3d72b24eb --- /dev/null +++ b/ci/setup-mingw-build.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -ex + +echo "##############################################################################" +echo "## Downloading mingw" +echo "##############################################################################" + +BUILD_TEMP=${BUILD_TEMP:=$TEMP} +BUILD_TEMP=$(cygpath $BUILD_TEMP) + +case "$ARCH" in + amd64) + MINGW_URI="https://github.com/libgit2/ci-dependencies/releases/download/2021-05-04/mingw-x86_64-8.1.0-release-win32-sjlj-rt_v6-rev0.zip";; + x86) + MINGW_URI="https://github.com/libgit2/ci-dependencies/releases/download/2021-05-04/mingw-i686-8.1.0-release-win32-sjlj-rt_v6-rev0.zip";; +esac + +if [ -z "$MINGW_URI" ]; then + echo "No URL" + exit 1 +fi + +mkdir -p "$BUILD_TEMP" + +curl -s -L "$MINGW_URI" -o "$BUILD_TEMP"/mingw-"$ARCH".zip +unzip -q "$BUILD_TEMP"/mingw-"$ARCH".zip -d "$BUILD_TEMP" diff --git a/ci/setup-mingw.sh b/ci/setup-mingw.sh deleted file mode 100755 index f5a882d2b..000000000 --- a/ci/setup-mingw.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -e - -echo "##############################################################################" -echo "## Downloading mingw" -echo "##############################################################################" - -BUILD_TEMP=${BUILD_TEMP:=$TEMP} -BUILD_TEMP=$(cygpath $BUILD_TEMP) - -case "$ARCH" in - amd64) - MINGW_URI="https://github.com/libgit2/ci-dependencies/releases/download/2021-05-04/mingw-x86_64-8.1.0-release-win32-sjlj-rt_v6-rev0.zip";; - x86) - MINGW_URI="https://github.com/libgit2/ci-dependencies/releases/download/2021-05-04/mingw-i686-8.1.0-release-win32-sjlj-rt_v6-rev0.zip";; -esac - -if [ -z "$MINGW_URI" ]; then - echo "No URL" - exit 1 -fi - -mkdir -p "$BUILD_TEMP" - -curl -s -L "$MINGW_URI" -o "$BUILD_TEMP"/mingw-"$ARCH".zip -unzip -q "$BUILD_TEMP"/mingw-"$ARCH".zip -d "$BUILD_TEMP" diff --git a/ci/setup-osx-benchmark.sh b/ci/setup-osx-benchmark.sh new file mode 100755 index 000000000..80d87682b --- /dev/null +++ b/ci/setup-osx-benchmark.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +brew update +brew install hyperfine diff --git a/ci/setup-osx-build.sh b/ci/setup-osx-build.sh new file mode 100755 index 000000000..0b95e7629 --- /dev/null +++ b/ci/setup-osx-build.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -ex + +brew update +brew install pkgconfig zlib curl openssl libssh2 ninja + +ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib diff --git a/ci/setup-osx.sh b/ci/setup-osx.sh deleted file mode 100755 index 2e630eedb..000000000 --- a/ci/setup-osx.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -x - -brew update -brew install pkgconfig zlib curl openssl libssh2 ninja - -ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib diff --git a/ci/setup-ubuntu-benchmark.sh b/ci/setup-ubuntu-benchmark.sh new file mode 100755 index 000000000..561a18fd9 --- /dev/null +++ b/ci/setup-ubuntu-benchmark.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +set -ex + +sudo apt-get update +sudo apt-get install -y --no-install-recommends \ + cargo \ + cmake \ + gcc \ + git \ + krb5-user \ + libkrb5-dev \ + libssl-dev \ + libz-dev \ + make \ + ninja-build \ + pkgconf + +wget https://github.com/sharkdp/hyperfine/releases/download/v1.12.0/hyperfine_1.12.0_amd64.deb +sudo dpkg -i hyperfine_1.12.0_amd64.deb diff --git a/ci/setup-win32-benchmark.sh b/ci/setup-win32-benchmark.sh new file mode 100755 index 000000000..0eac2f666 --- /dev/null +++ b/ci/setup-win32-benchmark.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -ex + +choco install hyperfine zip + +CHOCO_PATH=$(mktemp -d) +curl -L https://github.com/ethomson/PurgeStandbyList/releases/download/v1.0/purgestandbylist.1.0.0.nupkg -o "${CHOCO_PATH}/purgestandbylist.1.0.0.nupkg" +choco install purgestandbylist -s $(cygpath -w "${CHOCO_PATH}") diff --git a/ci/test.sh b/ci/test.sh index a94839778..0815522a9 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -159,10 +159,18 @@ fi if [ -z "$SKIP_OFFLINE_TESTS" ]; then echo "" echo "##############################################################################" - echo "## Running (offline) tests" + echo "## Running core tests" echo "##############################################################################" + echo "" + echo "Running libgit2 integration (offline) tests" + echo "" run_test offline + + echo "" + echo "Running utility tests" + echo "" + run_test util fi if [ -n "$RUN_INVASIVE_TESTS" ]; then @@ -186,7 +194,7 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then echo "" echo "##############################################################################" - echo "## Running (online) tests" + echo "## Running networking (online) tests" echo "##############################################################################" export GITTEST_REMOTE_REDIRECT_INITIAL="http://localhost:9000/initial-redirect/libgit2/TestGitRepository" @@ -198,9 +206,9 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then # Run the online tests that immutably change global state separately # to avoid polluting the test environment. echo "" - echo "##############################################################################" - echo "## Running (online_customcert) tests" - echo "##############################################################################" + echo "Running custom certificate (online_customcert) tests" + echo "" + run_test online_customcert fi diff --git a/cmake/AddClarTest.cmake b/cmake/AddClarTest.cmake new file mode 100644 index 000000000..743941638 --- /dev/null +++ b/cmake/AddClarTest.cmake @@ -0,0 +1,7 @@ +function(ADD_CLAR_TEST project name) + if(NOT USE_LEAK_CHECKER STREQUAL "OFF") + add_test(${name} "${PROJECT_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${PROJECT_BINARY_DIR}/${project}" ${ARGN}) + else() + add_test(${name} "${PROJECT_BINARY_DIR}/${project}" ${ARGN}) + endif() +endfunction(ADD_CLAR_TEST) diff --git a/cmake/FindPCRE.cmake b/cmake/FindPCRE.cmake index 3a7cfad91..02e7edce1 100644 --- a/cmake/FindPCRE.cmake +++ b/cmake/FindPCRE.cmake @@ -16,19 +16,18 @@ # PCRE_FOUND - True if pcre found. # Look for the header file. -find_path(PCRE_INCLUDE_DIR NAMES pcreposix.h) +find_path(PCRE_INCLUDE_DIR NAMES pcre.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) +find_package_handle_standard_args(PCRE DEFAULT_MSG PCRE_LIBRARY PCRE_INCLUDE_DIR) # Copy the results to the output variables. if(PCRE_FOUND) - set(PCRE_LIBRARIES ${PCRE_LIBRARY} ${PCRE_POSIX_LIBRARY}) + set(PCRE_LIBRARIES ${PCRE_LIBRARY}) set(PCRE_INCLUDE_DIRS ${PCRE_INCLUDE_DIR}) else(PCRE_FOUND) set(PCRE_LIBRARIES) diff --git a/cmake/FindPCRE2.cmake b/cmake/FindPCRE2.cmake index d4b8e6761..41c165b65 100644 --- a/cmake/FindPCRE2.cmake +++ b/cmake/FindPCRE2.cmake @@ -16,7 +16,7 @@ # PCRE2_FOUND - True if pcre found. # Look for the header file. -find_path(PCRE2_INCLUDE_DIR NAMES pcre2posix.h) +find_path(PCRE2_INCLUDE_DIR NAMES pcre2.h) # Look for the library. find_library(PCRE2_LIBRARY NAMES pcre2-8) diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index 79319502e..20221bf9f 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -64,7 +64,7 @@ if(USE_HTTPS) if(NOT CERT_LOCATION) message(STATUS "Auto-detecting default certificates location") - if(CMAKE_SYSTEM_NAME MATCHES Darwin) + if(EXISTS "/usr/local/opt/openssl/bin/openssl") # Check for an Homebrew installation set(OPENSSL_CMD "/usr/local/opt/openssl/bin/openssl") else() diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake index bedd8c4e3..faf9e2ea3 100644 --- a/cmake/SelectHashes.cmake +++ b/cmake/SelectHashes.cmake @@ -4,6 +4,9 @@ include(SanitizeBool) # USE_SHA1=CollisionDetection(ON)/HTTPS/Generic/OFF sanitizebool(USE_SHA1) +sanitizebool(USE_SHA256) + +# sha1 if(USE_SHA1 STREQUAL ON) SET(USE_SHA1 "CollisionDetection") @@ -21,32 +24,77 @@ endif() if(USE_SHA1 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\") elseif(USE_SHA1 STREQUAL "OpenSSL") - # OPENSSL_FOUND should already be set, we're checking USE_HTTPS - set(GIT_SHA1_OPENSSL 1) +elseif(USE_SHA1 STREQUAL "OpenSSL-Dynamic") + set(GIT_SHA1_OPENSSL 1) + set(GIT_SHA1_OPENSSL_DYNAMIC 1) + list(APPEND LIBGIT2_SYSTEM_LIBS dl) +elseif(USE_SHA1 STREQUAL "CommonCrypto") + set(GIT_SHA1_COMMON_CRYPTO 1) +elseif(USE_SHA1 STREQUAL "mbedTLS") + set(GIT_SHA1_MBEDTLS 1) +elseif(USE_SHA1 STREQUAL "Win32") + set(GIT_SHA1_WIN32 1) +else() + message(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}") +endif() + +# sha256 + +if(USE_SHA256 STREQUAL ON AND USE_HTTPS) + SET(USE_SHA256 "HTTPS") +elseif(USE_SHA256 STREQUAL ON) + SET(USE_SHA256 "Builtin") +endif() + +if(USE_SHA256 STREQUAL "HTTPS") + if(USE_HTTPS STREQUAL "SecureTransport") + set(USE_SHA256 "CommonCrypto") + elseif(USE_HTTPS STREQUAL "WinHTTP") + set(USE_SHA256 "Win32") + elseif(USE_HTTPS) + set(USE_SHA256 ${USE_HTTPS}) + endif() +endif() + +if(USE_SHA256 STREQUAL "Builtin") + set(GIT_SHA256_BUILTIN 1) +elseif(USE_SHA256 STREQUAL "OpenSSL") + set(GIT_SHA256_OPENSSL 1) +elseif(USE_SHA256 STREQUAL "OpenSSL-Dynamic") + set(GIT_SHA256_OPENSSL 1) + set(GIT_SHA256_OPENSSL_DYNAMIC 1) + list(APPEND LIBGIT2_SYSTEM_LIBS dl) +elseif(USE_SHA256 STREQUAL "CommonCrypto") + set(GIT_SHA256_COMMON_CRYPTO 1) +elseif(USE_SHA256 STREQUAL "mbedTLS") + set(GIT_SHA256_MBEDTLS 1) +elseif(USE_SHA256 STREQUAL "Win32") + set(GIT_SHA256_WIN32 1) +else() + message(FATAL_ERROR "Asked for unknown SHA256 backend: ${USE_SHA256}") +endif() + +# add library requirements +if(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL") if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") list(APPEND LIBGIT2_PC_LIBS "-lssl") else() list(APPEND LIBGIT2_PC_REQUIRES "openssl") endif() -elseif(USE_SHA1 STREQUAL "CommonCrypto") - set(GIT_SHA1_COMMON_CRYPTO 1) -elseif(USE_SHA1 STREQUAL "mbedTLS") - set(GIT_SHA1_MBEDTLS 1) +endif() + +if(USE_SHA1 STREQUAL "mbedTLS" OR USE_SHA256 STREQUAL "mbedTLS") list(APPEND LIBGIT2_SYSTEM_INCLUDES ${MBEDTLS_INCLUDE_DIR}) list(APPEND LIBGIT2_SYSTEM_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(USE_SHA1 STREQUAL "Win32") - set(GIT_SHA1_WIN32 1) -elseif(NOT (USE_SHA1 STREQUAL "Generic")) - message(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}") endif() -add_feature_info(SHA ON "using ${USE_SHA1}") +# notify feature enablement + +add_feature_info(SHA1 ON "using ${USE_SHA1}") +add_feature_info(SHA256 ON "using ${USE_SHA256}") diff --git a/docs/changelog.md b/docs/changelog.md index 32a67d2c7..989800a11 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,39 +1,79 @@ -v1.4.3 ------- - -🔒 This is a security release to provide compatibility with git's changes to address [CVE 2022-24765](https://github.blog/2022-04-12-git-security-vulnerability-announced/). - -**libgit2 is not directly affected** by this vulnerability, because libgit2 does not directly invoke any executable. But we are providing these changes as a security release for any users that use libgit2 for repository discovery and then _also_ use git on that repository. In this release, we will now validate that the user opening the repository is the same user that owns the on-disk repository. This is to match git's behavior. - -In addition, we are providing several correctness fixes where invalid input can lead to a crash. These may prevent possible denial of service attacks. At this time there are not known exploits to these issues. - -Full list of changes: - -* Validate repository directory ownership (v1.4) by @ethomson in https://github.com/libgit2/libgit2/pull/6267 -* midx: Fix an undefined behavior (left-shift signed overflow) by @lhchavez in https://github.com/libgit2/libgit2/pull/6260 -* fetch: support OID refspec without dst by @ethomson in https://github.com/libgit2/libgit2/pull/6251 -* Fix crash when regenerating a patch with unquoted spaces in filename by @jorio in https://github.com/libgit2/libgit2/pull/6244 - -All users of the v1.4 release line are recommended to upgrade. - -**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.4.2...v1.4.3 - -v1.4.2 ------- +v1.5 +---- -This is a bugfix release with the following changes: +This is release v1.5.0, "Stubentiger". This release adds the basis for an experimental CLI, continues preparing for SHA256 support, adds a benchmarking utility, and has numerous new features and bugfixes. +## What's Changed +### New features +* The beginnings of a git-compatible CLI for testing and benchmarking by @ethomson in https://github.com/libgit2/libgit2/pull/6133 +* Add `clone` support to the CLI @ethomson in https://github.com/libgit2/libgit2/pull/6274 +* A benchmarking suite to compare libgit2 functionality against git by @ethomson in https://github.com/libgit2/libgit2/pull/6235 +* SHA256: add a SHA256 implementation backend by @ethomson in https://github.com/libgit2/libgit2/pull/6144 +* SHA256: support dynamically loaded openssl by @ethomson in https://github.com/libgit2/libgit2/pull/6258 +* Transport: introduce `git_transport_smart_remote_connect_options` by @lhchavez in https://github.com/libgit2/libgit2/pull/6278 +### Bug fixes +* Free parent and ref in lg2_commit before returning. by @apnadkarni in https://github.com/libgit2/libgit2/pull/6219 +* xdiff: use xdl_free not free by @ethomson in https://github.com/libgit2/libgit2/pull/6223 * remote: do store the update_tips callback error value by @carlosmn in https://github.com/libgit2/libgit2/pull/6226 * win32: `find_system_dirs` does not return `GIT_ENOTFOUND` by @ethomson in https://github.com/libgit2/libgit2/pull/6228 +* Some minor fixes for issues discovered by coverity by @ethomson in https://github.com/libgit2/libgit2/pull/6238 +* Fix a string concatenation bug when validating extensions by @bierbaum in https://github.com/libgit2/libgit2/pull/6246 +* fetch: support OID refspec without dst by @ethomson in https://github.com/libgit2/libgit2/pull/6251 +* Fix crash when regenerating a patch with unquoted spaces in filename by @jorio in https://github.com/libgit2/libgit2/pull/6244 +* midx: Fix an undefined behavior (left-shift signed overflow) by @lhchavez in https://github.com/libgit2/libgit2/pull/6260 +* Validate repository directory ownership by @ethomson in https://github.com/libgit2/libgit2/pull/6266 +* midx: fix large offset table check. by @ccstolley in https://github.com/libgit2/libgit2/pull/6309 +* midx: do not verify the checksum on load by @carlosmn in https://github.com/libgit2/libgit2/pull/6291 +* revparse: Remove error-prone, redundant test by @dongcarl in https://github.com/libgit2/libgit2/pull/6299 +* refs: fix missing error message by @zawata in https://github.com/libgit2/libgit2/pull/6305 +* CLI: progress updates by @ethomson in https://github.com/libgit2/libgit2/pull/6319 +* A couple of simplications around mwindow by @carlosmn in https://github.com/libgit2/libgit2/pull/6288 +* config: update config entry iteration lifecycle by @ethomson in https://github.com/libgit2/libgit2/pull/6320 +* repo: allow administrator to own the configuration by @ethomson in https://github.com/libgit2/libgit2/pull/6321 +* filter: Fix Segfault by @zawata in https://github.com/libgit2/libgit2/pull/6303 +* ntlmclient: LibreSSL 3.5 removed HMAC_CTX_cleanup by @vishwin in https://github.com/libgit2/libgit2/pull/6340 +* Fix internal git_sysdir_find* function usage within public git_config_find* functions by @kcsaul in https://github.com/libgit2/libgit2/pull/6335 +* fix interactive rebase detect. by @i-tengfei in https://github.com/libgit2/libgit2/pull/6334 +* cmake: drop posix dependency from pcre* detection by @jpalus in https://github.com/libgit2/libgit2/pull/6333 +* Fix erroneously lax configuration ownership checks by @ethomson in https://github.com/libgit2/libgit2/pull/6341 +* pack: don't pretend we support pack files v3 by @ethomson in https://github.com/libgit2/libgit2/pull/6347 +* Fix creation of branches and tags with invalid names by @lya001 in https://github.com/libgit2/libgit2/pull/6348 +### Security fixes +* Fixes for CVE 2022-29187 by @ethomson in https://github.com/libgit2/libgit2/pull/6349 +* zlib: update bundled zlib to v1.2.12 by @ethomson in https://github.com/libgit2/libgit2/pull/6350 +### Code cleanups +* sha256: refactoring in preparation for sha256 by @ethomson in https://github.com/libgit2/libgit2/pull/6265 +* remote: Delete a now-inexistent API declaration by @lhchavez in https://github.com/libgit2/libgit2/pull/6276 +* Fix missing include by @cschlack in https://github.com/libgit2/libgit2/pull/6277 +### Build and CI improvements +* meta: show build status for v1.3 and v1.4 branches by @ethomson in https://github.com/libgit2/libgit2/pull/6216 +* cmake: Fix package name for system http-parser by @mgorny in https://github.com/libgit2/libgit2/pull/6217 +* meta: update version number to v1.5.0-alpha by @ethomson in https://github.com/libgit2/libgit2/pull/6220 +* cmake: export libraries needed to compile against libgit2 by @ethomson in https://github.com/libgit2/libgit2/pull/6239 +* clone: update bitbucket tests by @ethomson in https://github.com/libgit2/libgit2/pull/6252 +* diff: don't stat empty file on arm32 (flaky test) by @ethomson in https://github.com/libgit2/libgit2/pull/6259 +* tests: support flaky stat by @ethomson in https://github.com/libgit2/libgit2/pull/6262 +* Include test results data in CI by @ethomson in https://github.com/libgit2/libgit2/pull/6306 +* Add a .clang-format with our style by @ethomson in https://github.com/libgit2/libgit2/pull/6023 +* CI: limits actions scheduled workflows to the main repo by @ethomson in https://github.com/libgit2/libgit2/pull/6342 +* ci: update dockerfiles for mbedTLS new url by @ethomson in https://github.com/libgit2/libgit2/pull/6343 +### Documentation improvements +* Add Pharo to language bindings by @theseion in https://github.com/libgit2/libgit2/pull/6310 +* Add link to Tcl bindings for libgit2 by @apnadkarni in https://github.com/libgit2/libgit2/pull/6318 +* fix couple of typos by @SkinnyMind in https://github.com/libgit2/libgit2/pull/6287 +* update documentation for default status options by @ethomson in https://github.com/libgit2/libgit2/pull/6322 -v1.4.1 ------- - -This is a bugfix release with the following changes: - -* xdiff: use xdl_free not free by @ethomson -* cmake: Fix package name for system http-parser by @mgorny -* Free parent and ref in lg2_commit before returning by @apnadkarni +## New Contributors +* @bierbaum made their first contribution in https://github.com/libgit2/libgit2/pull/6246 +* @dongcarl made their first contribution in https://github.com/libgit2/libgit2/pull/6299 +* @SkinnyMind made their first contribution in https://github.com/libgit2/libgit2/pull/6287 +* @zawata made their first contribution in https://github.com/libgit2/libgit2/pull/6305 +* @vishwin made their first contribution in https://github.com/libgit2/libgit2/pull/6340 +* @i-tengfei made their first contribution in https://github.com/libgit2/libgit2/pull/6334 +* @jpalus made their first contribution in https://github.com/libgit2/libgit2/pull/6333 +* @lya001 made their first contribution in https://github.com/libgit2/libgit2/pull/6348 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.4.0...v1.5.0 v1.4 ---- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 235e72ada..8e38c7d4e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,5 @@ +# examples: code usage examples of libgit2 + file(GLOB SRC_EXAMPLES *.c *.h) add_executable(lg2 ${SRC_EXAMPLES}) @@ -10,7 +12,7 @@ target_include_directories(lg2 PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_ target_include_directories(lg2 SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) if(WIN32 OR ANDROID) - target_link_libraries(lg2 git2) + target_link_libraries(lg2 libgit2package) else() - target_link_libraries(lg2 git2 pthread) + target_link_libraries(lg2 libgit2package pthread) endif() diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt index eaa490fd9..a2c19ed40 100644 --- a/fuzzers/CMakeLists.txt +++ b/fuzzers/CMakeLists.txt @@ -1,3 +1,5 @@ +# fuzzers: libFuzzer and standalone fuzzing utilities + if(BUILD_FUZZERS AND NOT USE_STANDALONE_FUZZERS) set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer-no-link") add_c_flag(-fsanitize=fuzzer) diff --git a/fuzzers/packfile_fuzzer.c b/fuzzers/packfile_fuzzer.c index 6002fa1de..23ecaf80f 100644 --- a/fuzzers/packfile_fuzzer.c +++ b/fuzzers/packfile_fuzzer.c @@ -94,7 +94,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) fprintf(stderr, "Failed to compute the SHA1 hash\n"); abort(); } - if (git_indexer_append(indexer, &oid, sizeof(oid), &stats) < 0) { + if (git_indexer_append(indexer, &oid.id, GIT_OID_RAWSZ, &stats) < 0) { goto cleanup; } } diff --git a/include/git2/branch.h b/include/git2/branch.h index 36a393d10..27c6fa157 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -129,8 +129,8 @@ GIT_EXTERN(void) git_branch_iterator_free(git_branch_iterator *iter); * 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()`. + * be valid anymore, and should be freed immediately by the user using + * `git_reference_free()`. * * @param out New reference object for the updated name. * diff --git a/include/git2/common.h b/include/git2/common.h index b8c4ad531..c3e3e7b4e 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -121,6 +121,17 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_libgit2_version(int *major, int *minor, int *rev); +/** + * Return the prerelease state of the libgit2 library currently being + * used. For nightly builds during active development, this will be + * "alpha". Releases may have a "beta" or release candidate ("rc1", + * "rc2", etc) prerelease. For a final release, this function returns + * NULL. + * + * @return the name of the prerelease state or NULL + */ +GIT_EXTERN(const char *) git_libgit2_prerelease(void); + /** * Combinations of these values describe the features with which libgit2 * was compiled diff --git a/include/git2/config.h b/include/git2/config.h index a5486a4c9..cfab0c757 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -122,7 +122,7 @@ typedef struct { * global configuration file. * * This method will not guess the path to the xdg compatible - * config file (.config/git/config). + * config file (`.config/git/config`). * * @param out Pointer to a user-allocated git_buf in which to store the path * @return 0 if a global configuration file has been found. Its path will be stored in `out`. @@ -149,8 +149,8 @@ GIT_EXTERN(int) git_config_find_xdg(git_buf *out); /** * Locate the path to the system configuration file * - * If /etc/gitconfig doesn't exist, it will look for - * %PROGRAMFILES%\Git\etc\gitconfig. + * If `/etc/gitconfig` doesn't exist, it will look for + * `%PROGRAMFILES%\Git\etc\gitconfig`. * * @param out Pointer to a user-allocated git_buf in which to store the path * @return 0 if a system configuration file has been @@ -161,7 +161,7 @@ GIT_EXTERN(int) git_config_find_system(git_buf *out); /** * Locate the path to the configuration file in ProgramData * - * Look for the file in %PROGRAMDATA%\Git\config used by portable git. + * Look for the file in `%PROGRAMDATA%\Git\config` used by portable git. * * @param out Pointer to a user-allocated git_buf in which to store the path * @return 0 if a ProgramData configuration file has been @@ -449,8 +449,8 @@ GIT_EXTERN(int) git_config_multivar_iterator_new(git_config_iterator **out, cons /** * Return the current entry and advance the iterator * - * The pointers returned by this function are valid until the iterator - * is freed. + * The pointers returned by this function are valid until the next call + * to `git_config_next` or until the iterator is freed. * * @param entry pointer to store the entry * @param iter the iterator diff --git a/include/git2/deprecated.h b/include/git2/deprecated.h index f73d7da61..c32abeeb7 100644 --- a/include/git2/deprecated.h +++ b/include/git2/deprecated.h @@ -436,6 +436,8 @@ GIT_EXTERN(int) git_diff_format_email_options_init( #define GITERR_WORKTREE GIT_ERROR_WORKTREE #define GITERR_SHA1 GIT_ERROR_SHA1 +#define GIT_ERROR_SHA1 GIT_ERROR_SHA + /** * Return the last `git_error` object that was generated for the * current thread. This is an alias of `git_error_last` and is diff --git a/include/git2/errors.h b/include/git2/errors.h index 16712f988..a61964bbb 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -107,7 +107,7 @@ typedef enum { GIT_ERROR_FILESYSTEM, GIT_ERROR_PATCH, GIT_ERROR_WORKTREE, - GIT_ERROR_SHA1, + GIT_ERROR_SHA, GIT_ERROR_HTTP, GIT_ERROR_INTERNAL } git_error_t; @@ -131,7 +131,8 @@ GIT_EXTERN(const git_error *) git_error_last(void); GIT_EXTERN(void) git_error_clear(void); /** - * Set the error message string for this thread. + * Set the error message string for this thread, using `printf`-style + * formatting. * * This function is public so that custom ODB backends and the like can * relay an error message through libgit2. Most regular users of libgit2 @@ -144,7 +145,20 @@ 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 + * @param fmt The `printf`-style format string; subsequent arguments must + * be the arguments for the format string. + */ +GIT_EXTERN(void) git_error_set(int error_class, const char *fmt, ...) + GIT_FORMAT_PRINTF(2, 3); + +/** + * Set the error message string for this thread. This function is like + * `git_error_set` but takes a static string instead of a `printf`-style + * format. + * + * @param error_class One of the `git_error_t` enum above describing the + * general subsystem that is responsible for the error. + * @param string The error message to keep * @return 0 on success or -1 on failure */ GIT_EXTERN(int) git_error_set_str(int error_class, const char *string); diff --git a/include/git2/merge.h b/include/git2/merge.h index e90941a49..fcce5594d 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -603,7 +603,7 @@ GIT_EXTERN(int) git_merge_commits( * completes, resolve any conflicts and prepare a commit. * * For compatibility with git, the repository is put into a merging - * state. Once the commit is done (or if the uses wishes to abort), + * state. Once the commit is done (or if the user wishes to abort), * you should clear this state by calling * `git_repository_state_cleanup()`. * diff --git a/include/git2/status.h b/include/git2/status.h index d8f9663b4..bb28e875b 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -227,13 +227,16 @@ typedef struct { /** * The `show` value is one of the `git_status_show_t` constants that - * control which files to scan and in what order. + * control which files to scan and in what order. The default is + * `GIT_STATUS_SHOW_INDEX_AND_WORKDIR`. */ git_status_show_t show; /** * The `flags` value is an OR'ed combination of the - * `git_status_opt_t` values above. + * `git_status_opt_t` values above. The default is + * `GIT_STATUS_OPT_DEFAULTS`, which matches git's default + * behavior. */ unsigned int flags; diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h index dd243ca55..0eae9234d 100644 --- a/include/git2/sys/remote.h +++ b/include/git2/sys/remote.h @@ -8,6 +8,8 @@ #ifndef INCLUDE_sys_git_remote_h #define INCLUDE_sys_git_remote_h +#include "git2/remote.h" + /** * @file git2/sys/remote.h * @brief Low-level remote functionality for custom transports @@ -26,6 +28,19 @@ typedef enum { GIT_REMOTE_CAPABILITY_REACHABLE_OID = (1 << 1), } git_remote_capability_t; +/** + * Disposes libgit2-initialized fields from a git_remote_connect_options. + * This should only be used for git_remote_connect_options returned by + * git_transport_remote_connect_options. + * + * Note that this does not free the `git_remote_connect_options` itself, just + * the memory pointed to by it. + * + * @param opts The `git_remote_connect_options` struct to dispose. + */ +GIT_EXTERN(void) git_remote_connect_options_dispose( + git_remote_connect_options *opts); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index f0c2a3eab..06ae7079f 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -9,10 +9,11 @@ #define INCLUDE_sys_git_transport_h #include "git2/net.h" +#include "git2/proxy.h" +#include "git2/remote.h" +#include "git2/strarray.h" #include "git2/transport.h" #include "git2/types.h" -#include "git2/strarray.h" -#include "git2/proxy.h" /** * @file git2/sys/transport.h @@ -261,14 +262,17 @@ GIT_EXTERN(int) git_transport_smart_certificate_check(git_transport *transport, 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 + * Get a copy of the remote connect options * - * The url is copied and must be freed by the caller. + * All data is copied and must be freed by the caller by calling + * `git_remote_connect_options_dispose`. * * @param out options struct to fill * @param transport the transport to extract the data from. */ -GIT_EXTERN(int) git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport); +GIT_EXTERN(int) git_transport_remote_connect_options( + git_remote_connect_options *out, + git_transport *transport); /* *** End of base transport interface *** diff --git a/include/git2/version.h b/include/git2/version.h index 980f70b69..d591904fd 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,12 +7,33 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "1.4.3" -#define LIBGIT2_VER_MAJOR 1 -#define LIBGIT2_VER_MINOR 4 -#define LIBGIT2_VER_REVISION 3 -#define LIBGIT2_VER_PATCH 0 +/** + * The version string for libgit2. This string follows semantic + * versioning (v2) guidelines. + */ +#define LIBGIT2_VERSION "1.5.0" + +/** The major version number for this version of libgit2. */ +#define LIBGIT2_VER_MAJOR 1 + +/** The minor version number for this version of libgit2. */ +#define LIBGIT2_VER_MINOR 5 + +/** The revision ("teeny") version number for this version of libgit2. */ +#define LIBGIT2_VER_REVISION 0 + +/** The Windows DLL patch number for this version of libgit2. */ +#define LIBGIT2_VER_PATCH 0 + +/** + * The prerelease string for this version of libgit2. For development + * (nightly) builds, this will be "alpha". For prereleases, this will be + * a prerelease name like "beta" or "rc1". For final releases, this will + * be `NULL`. + */ +#define LIBGIT2_VER_PRERELEASE NULL -#define LIBGIT2_SOVERSION "1.4" +/** The library ABI soversion for this version of libgit2. */ +#define LIBGIT2_SOVERSION "1.5" #endif diff --git a/package.json b/package.json index 5caa47aea..8676fe513 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "1.4.3", + "version": "1.5.0", "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/src/CMakeLists.txt b/src/CMakeLists.txt index e7b54d036..d16cfe538 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,12 +1,22 @@ -add_library(git2internal OBJECT) -set_target_properties(git2internal PROPERTIES C_STANDARD 90) -set_target_properties(git2internal PROPERTIES C_EXTENSIONS OFF) +# The main libgit2 source tree: this CMakeLists.txt identifies platform +# support and includes the subprojects that make up core libgit2 support. +# +# Optional build configuration settings +# if(DEPRECATE_HARD) add_definitions(-DGIT_DEPRECATE_HARD) endif() +if(USE_LEAK_CHECKER STREQUAL "valgrind") + add_definitions(-DVALGRIND) +endif() + +# +# Optional debugging functionality +# + if(DEBUG_POOL) set(GIT_DEBUG_POOL 1) endif() @@ -22,29 +32,32 @@ if(DEBUG_STRICT_OPEN) endif() add_feature_info(debugopen GIT_DEBUG_STRICT_OPEN "path validation in open") +# +# Optional feature enablement +# -include(PkgBuildConfig) -include(SanitizeBool) +include(SelectGSSAPI) +include(SelectHTTPSBackend) +include(SelectHashes) +include(SelectHTTPParser) +include(SelectRegex) +include(SelectSSH) +include(SelectWinHTTP) +include(SelectZlib) -# 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. -set(LIBGIT2_PC_REQUIRES "") -# This will be set later if we use the system's http-parser library or -# use iconv (OSX) and will be written to the Libs.private field in the -# pc file. -set(LIBGIT2_PC_LIBS "") +# +# Platform support +# -set(LIBGIT2_INCLUDES - "${CMAKE_CURRENT_BINARY_DIR}" - "${PROJECT_SOURCE_DIR}/src" - "${PROJECT_SOURCE_DIR}/include") +# futimes/futimens if(HAVE_FUTIMENS) set(GIT_USE_FUTIMENS 1) endif () add_feature_info(futimens GIT_USE_FUTIMENS "futimens support") +# qsort + check_prototype_definition(qsort_r "void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))" "" "stdlib.h" GIT_QSORT_R_BSD) @@ -55,69 +68,85 @@ check_prototype_definition(qsort_r check_function_exists(qsort_s GIT_QSORT_S) +# random / entropy data + check_function_exists(getentropy GIT_RAND_GETENTROPY) +check_function_exists(getloadavg GIT_RAND_GETLOADAVG) -# Find required dependencies +# determine architecture of the machine -if(WIN32) - list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) -elseif(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") - list(APPEND LIBGIT2_SYSTEM_LIBS socket nsl) - list(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") -elseif(CMAKE_SYSTEM_NAME MATCHES "Haiku") - list(APPEND LIBGIT2_SYSTEM_LIBS network) - list(APPEND LIBGIT2_PC_LIBS "-lnetwork") +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(GIT_ARCH_64 1) +elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(GIT_ARCH_32 1) +elseif(CMAKE_SIZEOF_VOID_P) + message(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") +else() + message(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") +endif() + +# nanosecond mtime/ctime support + +if(USE_NSEC) + set(GIT_USE_NSEC 1) endif() +# high-resolution stat support + +if(HAVE_STRUCT_STAT_ST_MTIM) + set(GIT_USE_STAT_MTIM 1) +elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) + set(GIT_USE_STAT_MTIMESPEC 1) +elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) + set(GIT_USE_STAT_MTIME_NSEC 1) +endif() + +# realtime support + check_library_exists(rt clock_gettime "time.h" NEED_LIBRT) if(NEED_LIBRT) list(APPEND LIBGIT2_SYSTEM_LIBS rt) list(APPEND LIBGIT2_PC_LIBS "-lrt") endif() -if(USE_THREADS) - list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) - list(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) +# platform libraries + +if(WIN32) + list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) endif() -add_feature_info(threadsafe USE_THREADS "threadsafe support") +if(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + list(APPEND LIBGIT2_SYSTEM_LIBS socket nsl) + list(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") +endif() -if(WIN32 AND EMBED_SSH_PATH) - file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") - list(SORT SRC_SSH) - target_sources(git2internal PRIVATE ${SRC_SSH}) +if(CMAKE_SYSTEM_NAME MATCHES "Haiku") + list(APPEND LIBGIT2_SYSTEM_LIBS gnu network) + list(APPEND LIBGIT2_PC_LIBS "-lgnu -lnetwork") +endif() - 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) +if(AMIGA) + add_definitions(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) endif() -include(SelectHTTPSBackend) -include(SelectHashes) -include(SelectHTTPParser) -include(SelectRegex) -include(SelectSSH) -include(SelectWinHTTP) -include(SelectZlib) +# threads +if(USE_THREADS) + if(NOT WIN32) + find_package(Threads REQUIRED) + list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) + list(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) + endif() -if(USE_SHA1 STREQUAL "CollisionDetection") - file(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) -elseif(USE_SHA1 STREQUAL "OpenSSL") - file(GLOB SRC_SHA1 hash/sha1/openssl.*) -elseif(USE_SHA1 STREQUAL "CommonCrypto") - file(GLOB SRC_SHA1 hash/sha1/common_crypto.*) -elseif(USE_SHA1 STREQUAL "mbedTLS") - file(GLOB SRC_SHA1 hash/sha1/mbedtls.*) -elseif(USE_SHA1 STREQUAL "Win32") - file(GLOB SRC_SHA1 hash/sha1/win32.*) -elseif(USE_SHA1 STREQUAL "Generic") - file(GLOB SRC_SHA1 hash/sha1/generic.*) + set(GIT_THREADS 1) endif() -list(APPEND SRC_SHA1 "hash/sha1.h") -target_sources(git2internal PRIVATE ${SRC_SHA1}) +add_feature_info(threadsafe USE_THREADS "threadsafe support") + +# +# Optional bundled features +# -# Optional external dependency: ntlmclient +# ntlmclient if(USE_NTLMCLIENT) set(GIT_NTLM 1) add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") @@ -126,11 +155,10 @@ if(USE_NTLMCLIENT) endif() add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") -# Optional external dependency: GSSAPI +# +# Optional external dependencies -include(SelectGSSAPI) - -# Optional external dependency: iconv +# iconv if(USE_ICONV) find_package(Iconv) endif() @@ -142,166 +170,28 @@ if(ICONV_FOUND) endif() add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") +# +# Configure support +# -if(USE_THREADS) - if(NOT WIN32) - find_package(Threads REQUIRED) - endif() - - set(GIT_THREADS 1) -endif() - -if(USE_NSEC) - set(GIT_USE_NSEC 1) -endif() - -if(HAVE_STRUCT_STAT_ST_MTIM) - set(GIT_USE_STAT_MTIM 1) -elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) - set(GIT_USE_STAT_MTIMESPEC 1) -elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) - set(GIT_USE_STAT_MTIME_NSEC 1) -endif() - -target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) - -# Collect sourcefiles -file(GLOB SRC_H - "${PROJECT_SOURCE_DIR}/include/git2.h" - "${PROJECT_SOURCE_DIR}/include/git2/*.h" - "${PROJECT_SOURCE_DIR}/include/git2/sys/*.h") -list(SORT SRC_H) -target_sources(git2internal PRIVATE ${SRC_H}) - -# On Windows use specific platform sources -if(WIN32 AND NOT CYGWIN) - set(WIN_RC "win32/git2.rc") - - file(GLOB SRC_OS win32/*.c win32/*.h) - list(SORT SRC_OS) - target_sources(git2internal PRIVATE ${SRC_OS}) -elseif(AMIGA) - target_compile_definitions(git2internal PRIVATE NO_ADDRINFO NO_READDIR_R NO_MMAP) -else() - file(GLOB SRC_OS unix/*.c unix/*.h) - list(SORT SRC_OS) - target_sources(git2internal PRIVATE ${SRC_OS}) -endif() - -if(USE_LEAK_CHECKER STREQUAL "valgrind") - target_compile_definitions(git2internal PRIVATE VALGRIND) -endif() +configure_file(features.h.in git2/sys/features.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) -target_sources(git2internal PRIVATE ${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) -endif() +# +# Include child projects +# -# 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/xemit.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xmerge.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS -WX-) -else() - set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") - set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") - set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") - set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") - set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") -endif() +add_subdirectory(libgit2) +add_subdirectory(util) -# Determine architecture of the machine -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(GIT_ARCH_64 1) -elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(GIT_ARCH_32 1) -elseif(CMAKE_SIZEOF_VOID_P) - message(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") -else() - message(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") +if(BUILD_CLI) + add_subdirectory(cli) endif() -configure_file(features.h.in git2/sys/features.h) - -ide_split_sources(git2internal) -list(APPEND LIBGIT2_OBJECTS $ ${LIBGIT2_DEPENDENCY_OBJECTS}) - -target_include_directories(git2internal PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_include_directories(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) - +# re-export these to the root so that peer projects (tests, fuzzers, +# examples) can use them set(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) set(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) set(LIBGIT2_DEPENDENCY_INCLUDES ${LIBGIT2_DEPENDENCY_INCLUDES} PARENT_SCOPE) set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) set(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE) set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) - -if(XCODE_VERSION) - # This is required for Xcode to actually link the libgit2 library - # when using only object libraries. - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/dummy.c "") - list(APPEND LIBGIT2_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/dummy.c) -endif() - -# Compile and link libgit2 -add_library(git2 ${WIN_RC} ${LIBGIT2_OBJECTS}) -target_link_libraries(git2 ${LIBGIT2_SYSTEM_LIBS}) - -set_target_properties(git2 PROPERTIES C_STANDARD 90) -set_target_properties(git2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set_target_properties(git2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set_target_properties(git2 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - -# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) -# Win64+MSVC+static libs = linker error -if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) - set_target_properties(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") -endif() - -ide_split_sources(git2) - -if(SONAME) - set_target_properties(git2 PROPERTIES VERSION ${libgit2_VERSION}) - set_target_properties(git2 PROPERTIES SOVERSION "${libgit2_VERSION_MAJOR}.${libgit2_VERSION_MINOR}") - if(LIBGIT2_FILENAME) - target_compile_definitions(git2 PRIVATE LIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") - set_target_properties(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) - elseif(DEFINED LIBGIT2_PREFIX) - set_target_properties(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}") - endif() -endif() - -pkg_build_config(NAME libgit2 - VERSION ${libgit2_VERSION} - DESCRIPTION "The git library, take 2" - LIBS_SELF git2 - PRIVATE_LIBS ${LIBGIT2_PC_LIBS} - REQUIRES ${LIBGIT2_PC_REQUIRES} -) - -if(MSVC_IDE) - # Precompiled headers - set_target_properties(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") -endif() - -# Install -install(TARGETS git2 - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/git2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES ${PROJECT_SOURCE_DIR}/include/git2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/README.md b/src/README.md new file mode 100644 index 000000000..10b86c1dc --- /dev/null +++ b/src/README.md @@ -0,0 +1,12 @@ +# libgit2 sources + +This is the source that makes up the core of libgit2 and its related +projects. + +* `cli` + A git-compatible command-line interface that uses libgit2. +* `libgit2` + This is the libgit2 project, a cross-platform, linkable library + implementation of Git that you can use in your application. +* `util` + A shared utility library for these projects. diff --git a/src/alloc.c b/src/alloc.c deleted file mode 100644 index 2820d84a2..000000000 --- a/src/alloc.c +++ /dev/null @@ -1,56 +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 "alloc.h" -#include "runtime.h" - -#include "allocators/failalloc.h" -#include "allocators/stdalloc.h" -#include "allocators/win32_leakcheck.h" - -/* Fail any allocation until git_libgit2_init is called. */ -git_allocator git__allocator = { - git_failalloc_malloc, - git_failalloc_calloc, - git_failalloc_strdup, - git_failalloc_strndup, - git_failalloc_substrdup, - git_failalloc_realloc, - git_failalloc_reallocarray, - git_failalloc_mallocarray, - git_failalloc_free -}; - -static int setup_default_allocator(void) -{ -#if defined(GIT_WIN32_LEAKCHECK) - return git_win32_leakcheck_init_allocator(&git__allocator); -#else - return git_stdalloc_init_allocator(&git__allocator); -#endif -} - -int git_allocator_global_init(void) -{ - /* - * We don't want to overwrite any allocator which has been set - * before the init function is called. - */ - if (git__allocator.gmalloc != git_failalloc_malloc) - return 0; - - return setup_default_allocator(); -} - -int git_allocator_setup(git_allocator *allocator) -{ - if (!allocator) - return setup_default_allocator(); - - memcpy(&git__allocator, allocator, sizeof(*allocator)); - return 0; -} diff --git a/src/alloc.h b/src/alloc.h deleted file mode 100644 index 04fb7e101..000000000 --- a/src/alloc.h +++ /dev/null @@ -1,40 +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_alloc_h__ -#define INCLUDE_alloc_h__ - -#include "git2/sys/alloc.h" - -extern git_allocator git__allocator; - -#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__) -#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__) -#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__) -#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__) -#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__) -#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__) -#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__) -#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__) -#define git__free git__allocator.gfree - -/** - * This function is being called by our global setup routines to - * initialize the standard allocator. - */ -int git_allocator_global_init(void); - -/** - * Switch out libgit2's global memory allocator - * - * @param allocator The new allocator that should be used. All function pointers - * of it need to be set correctly. - * @return An error code or 0. - */ -int git_allocator_setup(git_allocator *allocator); - -#endif diff --git a/src/allocators/failalloc.c b/src/allocators/failalloc.c deleted file mode 100644 index 5257d1dec..000000000 --- a/src/allocators/failalloc.c +++ /dev/null @@ -1,92 +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 "failalloc.h" - -void *git_failalloc_malloc(size_t len, const char *file, int line) -{ - GIT_UNUSED(len); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_strdup(const char *str, const char *file, int line) -{ - GIT_UNUSED(str); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line) -{ - GIT_UNUSED(str); - GIT_UNUSED(n); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line) -{ - GIT_UNUSED(start); - GIT_UNUSED(n); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line) -{ - GIT_UNUSED(ptr); - GIT_UNUSED(size); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(ptr); - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void git_failalloc_free(void *ptr) -{ - GIT_UNUSED(ptr); -} diff --git a/src/allocators/failalloc.h b/src/allocators/failalloc.h deleted file mode 100644 index 6115e51e7..000000000 --- a/src/allocators/failalloc.h +++ /dev/null @@ -1,23 +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_allocators_failalloc_h__ -#define INCLUDE_allocators_failalloc_h__ - -#include "common.h" - -extern void *git_failalloc_malloc(size_t len, const char *file, int line); -extern void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line); -extern char *git_failalloc_strdup(const char *str, const char *file, int line); -extern char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line); -extern char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line); -extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line); -extern void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line); -extern void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line); -extern void git_failalloc_free(void *ptr); - -#endif diff --git a/src/allocators/stdalloc.c b/src/allocators/stdalloc.c deleted file mode 100644 index 2b36d9f3d..000000000 --- a/src/allocators/stdalloc.c +++ /dev/null @@ -1,150 +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 "stdalloc.h" - -static void *stdalloc__malloc(size_t len, const char *file, int line) -{ - void *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!len) - return NULL; -#endif - - ptr = malloc(len); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!elsize || !nelem) - return NULL; -#endif - - ptr = calloc(nelem, elsize); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static char *stdalloc__strdup(const char *str, const char *file, int line) -{ - char *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - - ptr = strdup(str); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static char *stdalloc__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 = stdalloc__malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *stdalloc__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 = stdalloc__malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!size) - return NULL; -#endif - - new_ptr = realloc(ptr, size); - - if (!new_ptr) - git_error_set_oom(); - - return new_ptr; -} - -static void *stdalloc__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 stdalloc__realloc(ptr, newsize, file, line); -} - -static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return stdalloc__reallocarray(NULL, nelem, elsize, file, line); -} - -static void stdalloc__free(void *ptr) -{ - free(ptr); -} - -int git_stdalloc_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = stdalloc__malloc; - allocator->gcalloc = stdalloc__calloc; - allocator->gstrdup = stdalloc__strdup; - allocator->gstrndup = stdalloc__strndup; - allocator->gsubstrdup = stdalloc__substrdup; - allocator->grealloc = stdalloc__realloc; - allocator->greallocarray = stdalloc__reallocarray; - allocator->gmallocarray = stdalloc__mallocarray; - allocator->gfree = stdalloc__free; - return 0; -} diff --git a/src/allocators/stdalloc.h b/src/allocators/stdalloc.h deleted file mode 100644 index fa23fe6e3..000000000 --- a/src/allocators/stdalloc.h +++ /dev/null @@ -1,17 +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_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_leakcheck.c b/src/allocators/win32_leakcheck.c deleted file mode 100644 index fe06a14af..000000000 --- a/src/allocators/win32_leakcheck.c +++ /dev/null @@ -1,118 +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 "win32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) - -#include "win32/w32_leakcheck.h" - -static void *leakcheck_malloc(size_t len, const char *file, int line) -{ - void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static void *leakcheck_calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *leakcheck_strdup(const char *str, const char *file, int line) -{ - char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *leakcheck_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 = leakcheck_malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *leakcheck_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 = leakcheck_malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!new_ptr) git_error_set_oom(); - return new_ptr; -} - -static void *leakcheck_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 leakcheck_realloc(ptr, newsize, file, line); -} - -static void *leakcheck_mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return leakcheck_reallocarray(NULL, nelem, elsize, file, line); -} - -static void leakcheck_free(void *ptr) -{ - free(ptr); -} - -int git_win32_leakcheck_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = leakcheck_malloc; - allocator->gcalloc = leakcheck_calloc; - allocator->gstrdup = leakcheck_strdup; - allocator->gstrndup = leakcheck_strndup; - allocator->gsubstrdup = leakcheck_substrdup; - allocator->grealloc = leakcheck_realloc; - allocator->greallocarray = leakcheck_reallocarray; - allocator->gmallocarray = leakcheck_mallocarray; - allocator->gfree = leakcheck_free; - return 0; -} - -#else - -int git_win32_leakcheck_init_allocator(git_allocator *allocator) -{ - GIT_UNUSED(allocator); - git_error_set(GIT_EINVALID, "leakcheck memory allocator not available"); - return -1; -} - -#endif diff --git a/src/allocators/win32_leakcheck.h b/src/allocators/win32_leakcheck.h deleted file mode 100644 index 089690f90..000000000 --- a/src/allocators/win32_leakcheck.h +++ /dev/null @@ -1,17 +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_allocators_win32_leakcheck_h -#define INCLUDE_allocators_win32_leakcheck_h - -#include "common.h" - -#include "alloc.h" - -int git_win32_leakcheck_init_allocator(git_allocator *allocator); - -#endif diff --git a/src/annotated_commit.c b/src/annotated_commit.c deleted file mode 100644 index e48947679..000000000 --- a/src/annotated_commit.c +++ /dev/null @@ -1,240 +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 "annotated_commit.h" - -#include "refs.h" -#include "cache.h" - -#include "git2/commit.h" -#include "git2/refs.h" -#include "git2/repository.h" -#include "git2/annotated_commit.h" -#include "git2/revparse.h" -#include "git2/tree.h" -#include "git2/index.h" - -static int annotated_commit_init( - git_annotated_commit **out, - git_commit *commit, - const char *description) -{ - git_annotated_commit *annotated_commit; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(commit); - - *out = NULL; - - annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); - GIT_ERROR_CHECK_ALLOC(annotated_commit); - - annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; - - if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0) - goto done; - - git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); - annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; - - if (!description) - description = annotated_commit->id_str; - - annotated_commit->description = git__strdup(description); - GIT_ERROR_CHECK_ALLOC(annotated_commit->description); - -done: - if (!error) - *out = annotated_commit; - - return error; -} - -static int annotated_commit_init_from_id( - git_annotated_commit **out, - git_repository *repo, - const git_oid *id, - const char *description) -{ - git_commit *commit = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(id); - - *out = NULL; - - if ((error = git_commit_lookup(&commit, repo, id)) < 0) - goto done; - - error = annotated_commit_init(out, commit, description); - -done: - git_commit_free(commit); - return error; -} - -int git_annotated_commit_lookup( - git_annotated_commit **out, - git_repository *repo, - const git_oid *id) -{ - return annotated_commit_init_from_id(out, repo, id, NULL); -} - -int git_annotated_commit_from_commit( - git_annotated_commit **out, - git_commit *commit) -{ - return annotated_commit_init(out, commit, NULL); -} - -int git_annotated_commit_from_revspec( - git_annotated_commit **out, - git_repository *repo, - const char *revspec) -{ - git_object *obj, *commit; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(revspec); - - if ((error = git_revparse_single(&obj, repo, revspec)) < 0) - return error; - - if ((error = git_object_peel(&commit, obj, GIT_OBJECT_COMMIT))) { - git_object_free(obj); - return error; - } - - error = annotated_commit_init(out, (git_commit *)commit, revspec); - - git_object_free(obj); - git_object_free(commit); - - return error; -} - -int git_annotated_commit_from_ref( - git_annotated_commit **out, - git_repository *repo, - const git_reference *ref) -{ - git_object *peeled; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ref); - - *out = NULL; - - if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_COMMIT)) < 0) - return error; - - error = annotated_commit_init_from_id(out, - repo, - git_object_id(peeled), - git_reference_name(ref)); - - if (!error) { - (*out)->ref_name = git__strdup(git_reference_name(ref)); - GIT_ERROR_CHECK_ALLOC((*out)->ref_name); - } - - git_object_free(peeled); - return error; -} - -int git_annotated_commit_from_head( - git_annotated_commit **out, - git_repository *repo) -{ - git_reference *head; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) - return -1; - - error = git_annotated_commit_from_ref(out, repo, head); - - git_reference_free(head); - return error; -} - -int git_annotated_commit_from_fetchhead( - git_annotated_commit **out, - git_repository *repo, - const char *branch_name, - const char *remote_url, - const git_oid *id) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(branch_name); - GIT_ASSERT_ARG(remote_url); - GIT_ASSERT_ARG(id); - - if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0) - return -1; - - (*out)->ref_name = git__strdup(branch_name); - GIT_ERROR_CHECK_ALLOC((*out)->ref_name); - - (*out)->remote_url = git__strdup(remote_url); - GIT_ERROR_CHECK_ALLOC((*out)->remote_url); - - return 0; -} - - -const git_oid *git_annotated_commit_id( - const git_annotated_commit *annotated_commit) -{ - GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); - return git_commit_id(annotated_commit->commit); -} - -const char *git_annotated_commit_ref( - const git_annotated_commit *annotated_commit) -{ - GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); - return annotated_commit->ref_name; -} - -void git_annotated_commit_free(git_annotated_commit *annotated_commit) -{ - if (annotated_commit == NULL) - return; - - switch (annotated_commit->type) { - case GIT_ANNOTATED_COMMIT_REAL: - git_commit_free(annotated_commit->commit); - git_tree_free(annotated_commit->tree); - git__free((char *)annotated_commit->description); - git__free((char *)annotated_commit->ref_name); - git__free((char *)annotated_commit->remote_url); - break; - case GIT_ANNOTATED_COMMIT_VIRTUAL: - git_index_free(annotated_commit->index); - git_array_clear(annotated_commit->parents); - break; - default: - abort(); - } - - git__free(annotated_commit); -} diff --git a/src/annotated_commit.h b/src/annotated_commit.h deleted file mode 100644 index 444a2ed10..000000000 --- a/src/annotated_commit.h +++ /dev/null @@ -1,52 +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_annotated_commit_h__ -#define INCLUDE_annotated_commit_h__ - -#include "common.h" - -#include "oidarray.h" - -#include "git2/oid.h" - -typedef enum { - GIT_ANNOTATED_COMMIT_REAL = 1, - GIT_ANNOTATED_COMMIT_VIRTUAL = 2 -} git_annotated_commit_t; - -/** - * Internal structure for merge inputs. An annotated commit is generally - * "real" and backed by an actual commit in the repository, but merge will - * internally create "virtual" commits that are in-memory intermediate - * commits backed by an index. - */ -struct git_annotated_commit { - git_annotated_commit_t type; - - /* real commit */ - git_commit *commit; - git_tree *tree; - - /* virtual commit structure */ - git_index *index; - git_array_oid_t parents; - - /* how this commit was looked up */ - const char *description; - - const char *ref_name; - const char *remote_url; - - char id_str[GIT_OID_HEXSZ+1]; -}; - -extern int git_annotated_commit_from_head(git_annotated_commit **out, - git_repository *repo); -extern int git_annotated_commit_from_commit(git_annotated_commit **out, - git_commit *commit); - -#endif diff --git a/src/apply.c b/src/apply.c deleted file mode 100644 index 18304da4d..000000000 --- a/src/apply.c +++ /dev/null @@ -1,898 +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 "git2/apply.h" -#include "git2/patch.h" -#include "git2/filter.h" -#include "git2/blob.h" -#include "git2/index.h" -#include "git2/checkout.h" -#include "git2/repository.h" -#include "array.h" -#include "patch.h" -#include "futils.h" -#include "delta.h" -#include "zstream.h" -#include "reader.h" -#include "index.h" -#include "apply.h" - -typedef struct { - /* The lines that we allocate ourself are allocated out of the pool. - * (Lines may have been allocated out of the diff.) - */ - git_pool pool; - 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, - size_t in_len, - size_t in_offset) -{ - out->content = in; - out->content_len = in_len; - out->content_offset = in_offset; -} - -#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT } - -static int patch_image_init_fromstr( - patch_image *out, const char *in, size_t in_len) -{ - git_diff_line *line; - const char *start, *end; - - memset(out, 0x0, sizeof(patch_image)); - - if (git_pool_init(&out->pool, sizeof(git_diff_line)) < 0) - return -1; - - if (!in_len) - return 0; - - for (start = in; start < in + in_len; start = end) { - end = memchr(start, '\n', in_len - (start - in)); - - if (end == NULL) - end = in + in_len; - - else if (end < in + in_len) - end++; - - line = git_pool_mallocz(&out->pool, 1); - GIT_ERROR_CHECK_ALLOC(line); - - if (git_vector_insert(&out->lines, line) < 0) - return -1; - - patch_line_init(line, start, (end - start), (start - in)); - } - - return 0; -} - -static void patch_image_free(patch_image *image) -{ - if (image == NULL) - return; - - git_pool_clear(&image->pool); - git_vector_free(&image->lines); -} - -static bool match_hunk( - patch_image *image, - patch_image *preimage, - size_t linenum) -{ - bool match = 0; - size_t i; - - /* Ensure this hunk is within the image boundaries. */ - if (git_vector_length(&preimage->lines) + linenum > - git_vector_length(&image->lines)) - return 0; - - match = 1; - - /* Check exact match. */ - for (i = 0; i < git_vector_length(&preimage->lines); i++) { - git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); - git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); - - if (preimage_line->content_len != image_line->content_len || - memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { - match = 0; - break; - } - } - - return match; -} - -static bool find_hunk_linenum( - size_t *out, - patch_image *image, - patch_image *preimage, - size_t linenum) -{ - size_t max = git_vector_length(&image->lines); - bool match; - - if (linenum > max) - linenum = max; - - match = match_hunk(image, preimage, linenum); - - *out = linenum; - return match; -} - -static int update_hunk( - patch_image *image, - size_t linenum, - patch_image *preimage, - patch_image *postimage) -{ - size_t postlen = git_vector_length(&postimage->lines); - size_t prelen = git_vector_length(&preimage->lines); - size_t i; - int error = 0; - - if (postlen > prelen) - error = git_vector_insert_null( - &image->lines, linenum, (postlen - prelen)); - else if (prelen > postlen) - error = git_vector_remove_range( - &image->lines, linenum, (prelen - postlen)); - - if (error) { - git_error_set_oom(); - return -1; - } - - for (i = 0; i < git_vector_length(&postimage->lines); i++) { - image->lines.contents[linenum + i] = - git_vector_get(&postimage->lines, i); - } - - return 0; -} - -typedef struct { - git_apply_options opts; - size_t skipped_new_lines; - size_t skipped_old_lines; -} apply_hunks_ctx; - -static int apply_hunk( - patch_image *image, - git_patch *patch, - git_patch_hunk *hunk, - apply_hunks_ctx *ctx) -{ - patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; - size_t line_num, i; - int error = 0; - - if (ctx->opts.hunk_cb) { - error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload); - - if (error) { - if (error > 0) { - ctx->skipped_new_lines += hunk->hunk.new_lines; - ctx->skipped_old_lines += hunk->hunk.old_lines; - error = 0; - } - - goto done; - } - } - - 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), *prev; - - if (!line) { - error = apply_err("preimage does not contain line %"PRIuZ, linenum); - 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; - } - } - - if (hunk->hunk.new_start) { - line_num = hunk->hunk.new_start - - ctx->skipped_new_lines + - ctx->skipped_old_lines - - 1; - } else { - line_num = 0; - } - - if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { - error = apply_err("hunk at line %d did not apply", - hunk->hunk.new_start); - goto done; - } - - error = update_hunk(image, line_num, &preimage, &postimage); - -done: - patch_image_free(&preimage); - patch_image_free(&postimage); - - return error; -} - -static int apply_hunks( - git_str *out, - const char *source, - size_t source_len, - git_patch *patch, - apply_hunks_ctx *ctx) -{ - git_patch_hunk *hunk; - git_diff_line *line; - patch_image image; - size_t i; - int error = 0; - - if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) - goto done; - - git_array_foreach(patch->hunks, i, hunk) { - if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0) - goto done; - } - - git_vector_foreach(&image.lines, i, line) - git_str_put(out, line->content, line->content_len); - -done: - patch_image_free(&image); - - return error; -} - -static int apply_binary_delta( - git_str *out, - const char *source, - size_t source_len, - git_diff_binary_file *binary_file) -{ - git_str inflated = GIT_STR_INIT; - int error = 0; - - /* no diff means identical contents */ - if (binary_file->datalen == 0) - return git_str_put(out, source, source_len); - - error = git_zstream_inflatebuf(&inflated, - binary_file->data, binary_file->datalen); - - if (!error && inflated.size != binary_file->inflatedlen) { - error = apply_err("inflated delta does not match expected length"); - git_str_dispose(out); - } - - if (error < 0) - goto done; - - if (binary_file->type == GIT_DIFF_BINARY_DELTA) { - void *data; - size_t data_len; - - error = git_delta_apply(&data, &data_len, (void *)source, source_len, - (void *)inflated.ptr, inflated.size); - - out->ptr = data; - out->size = data_len; - out->asize = data_len; - } - else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { - git_str_swap(out, &inflated); - } - else { - error = apply_err("unknown binary delta type"); - goto done; - } - -done: - git_str_dispose(&inflated); - return error; -} - -static int apply_binary( - git_str *out, - const char *source, - size_t source_len, - git_patch *patch) -{ - git_str reverse = GIT_STR_INIT; - int error = 0; - - if (!patch->binary.contains_data) { - error = apply_err("patch does not contain binary data"); - goto done; - } - - if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen) - goto done; - - /* first, apply the new_file delta to the given source */ - if ((error = apply_binary_delta(out, source, source_len, - &patch->binary.new_file)) < 0) - goto done; - - /* second, apply the old_file delta to sanity check the result */ - if ((error = apply_binary_delta(&reverse, out->ptr, out->size, - &patch->binary.old_file)) < 0) - goto done; - - /* Verify that the resulting file with the reverse patch applied matches the source file */ - if (source_len != reverse.size || - (source_len && memcmp(source, reverse.ptr, source_len) != 0)) { - error = apply_err("binary patch did not apply cleanly"); - goto done; - } - -done: - if (error < 0) - git_str_dispose(out); - - git_str_dispose(&reverse); - return error; -} - -int git_apply__patch( - git_str *contents_out, - char **filename_out, - unsigned int *mode_out, - const char *source, - size_t source_len, - git_patch *patch, - const git_apply_options *given_opts) -{ - apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT }; - char *filename = NULL; - unsigned int mode = 0; - int error = 0; - - GIT_ASSERT_ARG(contents_out); - GIT_ASSERT_ARG(filename_out); - GIT_ASSERT_ARG(mode_out); - GIT_ASSERT_ARG(source || !source_len); - GIT_ASSERT_ARG(patch); - - if (given_opts) - memcpy(&ctx.opts, given_opts, sizeof(git_apply_options)); - - *filename_out = NULL; - *mode_out = 0; - - if (patch->delta->status != GIT_DELTA_DELETED) { - const git_diff_file *newfile = &patch->delta->new_file; - - filename = git__strdup(newfile->path); - mode = newfile->mode ? - newfile->mode : GIT_FILEMODE_BLOB; - } - - if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) - error = apply_binary(contents_out, source, source_len, patch); - else if (patch->hunks.size) - error = apply_hunks(contents_out, source, source_len, patch, &ctx); - else - error = git_str_put(contents_out, source, source_len); - - if (error) - goto done; - - if (patch->delta->status == GIT_DELTA_DELETED && - git_str_len(contents_out) > 0) { - error = apply_err("removal patch leaves file contents"); - goto done; - } - - *filename_out = filename; - *mode_out = mode; - -done: - if (error < 0) - git__free(filename); - - return error; -} - -static int apply_one( - git_repository *repo, - git_reader *preimage_reader, - git_index *preimage, - git_reader *postimage_reader, - git_index *postimage, - git_diff *diff, - git_strmap *removed_paths, - size_t i, - const git_apply_options *opts) -{ - git_patch *patch = NULL; - git_str pre_contents = GIT_STR_INIT, post_contents = GIT_STR_INIT; - const git_diff_delta *delta; - char *filename = NULL; - unsigned int mode; - git_oid pre_id, post_id; - git_filemode_t pre_filemode; - git_index_entry pre_entry, post_entry; - bool skip_preimage = false; - int error; - - if ((error = git_patch_from_diff(&patch, diff, i)) < 0) - goto done; - - delta = git_patch_get_delta(patch); - - if (opts->delta_cb) { - error = opts->delta_cb(delta, opts->payload); - - if (error) { - if (error > 0) - error = 0; - - goto done; - } - } - - /* - * Ensure that the file has not been deleted or renamed if we're - * applying a modification delta. - */ - if (delta->status != GIT_DELTA_RENAMED && - delta->status != GIT_DELTA_ADDED) { - 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; - } - } - - /* - * We may be applying a second delta to an already seen file. If so, - * use the already modified data in the postimage instead of the - * content from the index or working directory. (Don't do this in - * the case of a rename, which must be specified before additional - * deltas since we apply deltas to the target filename.) - */ - if (delta->status != GIT_DELTA_RENAMED) { - if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, - postimage_reader, delta->old_file.path)) == 0) { - skip_preimage = true; - } else if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } else { - goto done; - } - } - - if (!skip_preimage && delta->status != GIT_DELTA_ADDED) { - error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, - preimage_reader, delta->old_file.path); - - /* ENOTFOUND means the preimage was not found; apply failed. */ - if (error == GIT_ENOTFOUND) - error = GIT_EAPPLYFAIL; - - /* When applying to BOTH, the index did not match the workdir. */ - if (error == GIT_READER_MISMATCH) - error = apply_err("%s: does not match index", delta->old_file.path); - - if (error < 0) - goto done; - - /* - * We need to populate the preimage data structure with the - * contents that we are using as the preimage for this file. - * This allows us to apply patches to files that have been - * modified in the working directory. During checkout, - * we will use this expected preimage as the baseline, and - * limit checkout to only the paths affected by patch - * application. (Without this, we would fail to write the - * postimage contents to any file that had been modified - * from HEAD on-disk, even if the patch application succeeded.) - * Use the contents from the delta where available - some - * fields may not be available, like the old file mode (eg in - * an exact rename situation) so trust the patch parsing to - * validate and use the preimage data in that case. - */ - if (preimage) { - memset(&pre_entry, 0, sizeof(git_index_entry)); - pre_entry.path = delta->old_file.path; - pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode; - git_oid_cpy(&pre_entry.id, &pre_id); - - if ((error = git_index_add(preimage, &pre_entry)) < 0) - goto done; - } - } - - 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_from_buffer(&post_id, repo, - post_contents.ptr, post_contents.size)) < 0) - goto done; - - memset(&post_entry, 0, sizeof(git_index_entry)); - post_entry.path = filename; - post_entry.mode = mode; - git_oid_cpy(&post_entry.id, &post_id); - - if ((error = git_index_add(postimage, &post_entry)) < 0) - goto done; - } - - if (delta->status == GIT_DELTA_RENAMED || - delta->status == GIT_DELTA_DELETED) - 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) - git_strmap_delete(removed_paths, delta->new_file.path); - -done: - git_str_dispose(&pre_contents); - git_str_dispose(&post_contents); - git__free(filename); - git_patch_free(patch); - - return error; -} - -static int apply_deltas( - git_repository *repo, - git_reader *pre_reader, - git_index *preimage, - git_reader *post_reader, - git_index *postimage, - git_diff *diff, - const git_apply_options *opts) -{ - git_strmap *removed_paths; - size_t i; - int error = 0; - - if (git_strmap_new(&removed_paths) < 0) - return -1; - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0) - goto done; - } - -done: - git_strmap_free(removed_paths); - return error; -} - -int git_apply_to_tree( - git_index **out, - git_repository *repo, - git_tree *preimage, - git_diff *diff, - const git_apply_options *given_opts) -{ - git_index *postimage = NULL; - git_reader *pre_reader = NULL, *post_reader = NULL; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - const git_diff_delta *delta; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(preimage); - GIT_ASSERT_ARG(diff); - - *out = NULL; - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_apply_options)); - - if ((error = git_reader_for_tree(&pre_reader, preimage)) < 0) - goto done; - - /* - * put the current tree into the postimage as-is - the diff will - * replace any entries contained therein - */ - if ((error = git_index_new(&postimage)) < 0 || - (error = git_index_read_tree(postimage, preimage)) < 0 || - (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) - goto done; - - /* - * Remove the old paths from the index before applying diffs - - * we need to do a full pass to remove them before adding deltas, - * in order to handle rename situations. - */ - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - 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) - goto done; - - *out = postimage; - -done: - if (error < 0) - git_index_free(postimage); - - git_reader_free(pre_reader); - git_reader_free(post_reader); - - return error; -} - -static int git_apply__to_workdir( - git_repository *repo, - git_diff *diff, - git_index *preimage, - git_index *postimage, - git_apply_location_t location, - git_apply_options *opts) -{ - git_vector paths = GIT_VECTOR_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - const git_diff_delta *delta; - size_t i; - int error; - - GIT_UNUSED(opts); - - /* - * Limit checkout to the paths affected by the diff; this ensures - * that other modifications in the working directory are unaffected. - */ - if ((error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL)) < 0) - goto done; - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - if ((error = git_vector_insert(&paths, (void *)delta->old_file.path)) < 0) - goto done; - - if (strcmp(delta->old_file.path, delta->new_file.path) && - (error = git_vector_insert(&paths, (void *)delta->new_file.path)) < 0) - goto done; - } - - checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; - checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; - - if (location == GIT_APPLY_LOCATION_WORKDIR) - checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; - - checkout_opts.paths.strings = (char **)paths.contents; - checkout_opts.paths.count = paths.length; - - checkout_opts.baseline_index = preimage; - - error = git_checkout_index(repo, postimage, &checkout_opts); - -done: - git_vector_free(&paths); - return error; -} - -static int git_apply__to_index( - git_repository *repo, - git_diff *diff, - git_index *preimage, - git_index *postimage, - git_apply_options *opts) -{ - git_index *index = NULL; - const git_diff_delta *delta; - const git_index_entry *entry; - size_t i; - int error; - - GIT_UNUSED(preimage); - GIT_UNUSED(opts); - - if ((error = git_repository_index(&index, repo)) < 0) - goto done; - - /* Remove deleted (or renamed) paths from the index. */ - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - if (delta->status == GIT_DELTA_DELETED || - delta->status == GIT_DELTA_RENAMED) { - if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) - goto done; - } - } - - /* Then add the changes back to the index. */ - for (i = 0; i < git_index_entrycount(postimage); i++) { - entry = git_index_get_byindex(postimage, i); - - if ((error = git_index_add(index, entry)) < 0) - goto done; - } - -done: - git_index_free(index); - return error; -} - -int git_apply_options_init(git_apply_options *opts, unsigned int version) -{ - GIT_ASSERT_ARG(opts); - - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT); - return 0; -} - -/* - * Handle the three application options ("locations"): - * - * GIT_APPLY_LOCATION_WORKDIR: the default, emulates `git apply`. - * Applies the diff only to the workdir items and ignores the index - * entirely. - * - * GIT_APPLY_LOCATION_INDEX: emulates `git apply --cached`. - * Applies the diff only to the index items and ignores the workdir - * completely. - * - * GIT_APPLY_LOCATION_BOTH: emulates `git apply --index`. - * Applies the diff to both the index items and the working directory - * items. - */ - -int git_apply( - git_repository *repo, - git_diff *diff, - git_apply_location_t location, - const git_apply_options *given_opts) -{ - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - git_index *index = NULL, *preimage = NULL, *postimage = NULL; - git_reader *pre_reader = NULL, *post_reader = NULL; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - int error = GIT_EINVALID; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(diff); - - GIT_ERROR_CHECK_VERSION( - given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options"); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_apply_options)); - - /* - * by default, we apply a patch directly to the working directory; - * in `--cached` or `--index` mode, we apply to the contents already - * in the index. - */ - switch (location) { - case GIT_APPLY_LOCATION_BOTH: - error = git_reader_for_workdir(&pre_reader, repo, true); - break; - case GIT_APPLY_LOCATION_INDEX: - error = git_reader_for_index(&pre_reader, repo, NULL); - break; - case GIT_APPLY_LOCATION_WORKDIR: - error = git_reader_for_workdir(&pre_reader, repo, false); - break; - default: - GIT_ASSERT(false); - } - - if (error < 0) - goto done; - - /* - * Build the preimage and postimage (differences). Note that - * this is not the complete preimage or postimage, it only - * contains the files affected by the patch. We want to avoid - * having the full repo index, so we will limit our checkout - * to only write these files that were affected by the diff. - */ - if ((error = git_index_new(&preimage)) < 0 || - (error = git_index_new(&postimage)) < 0 || - (error = git_reader_for_index(&post_reader, repo, postimage)) < 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); - break; - case GIT_APPLY_LOCATION_INDEX: - error = git_apply__to_index(repo, diff, preimage, postimage, &opts); - break; - case GIT_APPLY_LOCATION_WORKDIR: - error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); - break; - default: - GIT_ASSERT(false); - } - - if (error < 0) - goto done; - - error = git_indexwriter_commit(&indexwriter); - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(postimage); - git_index_free(preimage); - git_index_free(index); - git_reader_free(pre_reader); - git_reader_free(post_reader); - - return error; -} diff --git a/src/apply.h b/src/apply.h deleted file mode 100644 index e990a7107..000000000 --- a/src/apply.h +++ /dev/null @@ -1,25 +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_apply_h__ -#define INCLUDE_apply_h__ - -#include "common.h" - -#include "git2/patch.h" -#include "git2/apply.h" -#include "str.h" - -extern int git_apply__patch( - git_str *out, - char **filename, - unsigned int *mode, - const char *source, - size_t source_len, - git_patch *patch, - const git_apply_options *opts); - -#endif diff --git a/src/array.h b/src/array.h deleted file mode 100644 index e97688b36..000000000 --- a/src/array.h +++ /dev/null @@ -1,124 +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_array_h__ -#define INCLUDE_array_h__ - -#include "common.h" - -/* - * Use this to declare a typesafe resizable array of items, a la: - * - * git_array_t(int) my_ints = GIT_ARRAY_INIT; - * ... - * int *i = git_array_alloc(my_ints); - * GIT_ERROR_CHECK_ALLOC(i); - * ... - * git_array_clear(my_ints); - * - * You may also want to do things like: - * - * typedef git_array_t(my_struct) my_struct_array_t; - */ -#define git_array_t(type) struct { type *ptr; size_t size, asize; } - -#define GIT_ARRAY_INIT { NULL, 0, 0 } - -#define git_array_init(a) \ - do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) - -#define git_array_init_to_size(a, desired) \ - do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) - -#define git_array_clear(a) \ - do { git__free((a).ptr); git_array_init(a); } while (0) - -#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) - - -typedef git_array_t(char) git_array_generic_t; - -/* use a generic array for growth, return 0 on success */ -GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) -{ - volatile git_array_generic_t *a = _a; - size_t new_size; - char *new_array; - - if (a->size < 8) { - new_size = 8; - } else { - if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) - goto on_oom; - new_size /= 2; - } - - if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) - goto on_oom; - - a->ptr = new_array; - a->asize = new_size; - return 0; - -on_oom: - git_array_clear(*a); - return -1; -} - -#define git_array_alloc(a) \ - (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ - &(a).ptr[(a).size++] : (void *)NULL) - -#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) - -#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL) - -#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL) - -#define git_array_size(a) (a).size - -#define git_array_valid_index(a, i) ((i) < (a).size) - -#define git_array_foreach(a, i, element) \ - for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) - -GIT_INLINE(int) git_array__search( - size_t *out, - void *array_ptr, - size_t item_size, - size_t array_len, - int (*compare)(const void *, const void *), - const void *key) -{ - size_t lim; - unsigned char *part, *array = array_ptr, *base = array_ptr; - int cmp = -1; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1) * item_size; - cmp = (*compare)(key, part); - - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1 * item_size; - lim--; - } /* else take left partition */ - } - - if (out) - *out = (base - array) / item_size; - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -#define git_array_search(out, a, cmp, key) \ - git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ - (cmp), (key)) - -#endif diff --git a/src/assert_safe.h b/src/assert_safe.h deleted file mode 100644 index 8c261100f..000000000 --- a/src/assert_safe.h +++ /dev/null @@ -1,58 +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_assert_safe_h__ -#define INCLUDE_assert_safe_h__ - -/* - * In a debug build, we'll assert(3) for aide in debugging. In release - * builds, we will provide macros that will set an error message that - * indicate a failure and return. Note that memory leaks can occur in - * a release-mode assertion failure -- it is impractical to provide - * safe clean up routines in these very extreme failures, but care - * should be taken to not leak very large objects. - */ - -#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0 -# include - -# define GIT_ASSERT(expr) assert(expr) -# define GIT_ASSERT_ARG(expr) assert(expr) - -# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr) -# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr) -#else - -/** Internal consistency check to stop the function. */ -# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1) - -/** - * Assert that a consumer-provided argument is valid, setting an - * actionable error message and returning -1 if it is not. - */ -# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1) - -/** Internal consistency check to return the `fail` param on failure. */ -# define GIT_ASSERT_WITH_RETVAL(expr, fail) \ - GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail) - -/** - * Assert that a consumer-provided argument is valid, setting an - * actionable error message and returning the `fail` param if not. - */ -# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \ - GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail) - -# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \ - if (!(expr)) { \ - git_error_set(code, "%s: '%s'", msg, #expr); \ - return fail; \ - } \ - } while(0) - -#endif /* GIT_ASSERT_HARD */ - -#endif diff --git a/src/attr.c b/src/attr.c deleted file mode 100644 index 1623b1d45..000000000 --- a/src/attr.c +++ /dev/null @@ -1,700 +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 "attr.h" - -#include "repository.h" -#include "sysdir.h" -#include "config.h" -#include "attr_file.h" -#include "ignore.h" -#include "git2/oid.h" -#include - -const char *git_attr__true = "[internal]__TRUE__"; -const char *git_attr__false = "[internal]__FALSE__"; -const char *git_attr__unset = "[internal]__UNSET__"; - -git_attr_value_t git_attr_value(const char *attr) -{ - if (attr == NULL || attr == git_attr__unset) - return GIT_ATTR_VALUE_UNSPECIFIED; - - if (attr == git_attr__true) - return GIT_ATTR_VALUE_TRUE; - - if (attr == git_attr__false) - return GIT_ATTR_VALUE_FALSE; - - return GIT_ATTR_VALUE_STRING; -} - -static int collect_attr_files( - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *path, - git_vector *files); - -static void release_attr_files(git_vector *files); - -int git_attr_get_ext( - const char **value, - git_repository *repo, - git_attr_options *opts, - const char *pathname, - const char *name) -{ - int error; - git_attr_path path; - git_vector files = GIT_VECTOR_INIT; - size_t i, j; - git_attr_file *file; - git_attr_name attr; - git_attr_rule *rule; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - GIT_ASSERT_ARG(value); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); - - *value = NULL; - - if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) - return -1; - - if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0) - goto cleanup; - - memset(&attr, 0, sizeof(attr)); - attr.name = name; - attr.name_hash = git_attr_file__name_hash(name); - - git_vector_foreach(&files, i, file) { - - git_attr_file__foreach_matching_rule(file, &path, j, rule) { - size_t pos; - - if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) { - *value = ((git_attr_assignment *)git_vector_get( - &rule->assigns, pos))->value; - goto cleanup; - } - } - } - -cleanup: - release_attr_files(&files); - git_attr_path__free(&path); - - return error; -} - -int git_attr_get( - const char **value, - git_repository *repo, - uint32_t flags, - const char *pathname, - const char *name) -{ - git_attr_options opts = GIT_ATTR_OPTIONS_INIT; - - opts.flags = flags; - - return git_attr_get_ext(value, repo, &opts, pathname, name); -} - - -typedef struct { - git_attr_name name; - git_attr_assignment *found; -} attr_get_many_info; - -int git_attr_get_many_with_session( - const char **values, - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *pathname, - size_t num_attr, - const char **names) -{ - int error; - git_attr_path path; - git_vector files = GIT_VECTOR_INIT; - size_t i, j, k; - git_attr_file *file; - git_attr_rule *rule; - attr_get_many_info *info = NULL; - size_t num_found = 0; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - if (!num_attr) - return 0; - - GIT_ASSERT_ARG(values); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(pathname); - GIT_ASSERT_ARG(names); - GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); - - if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) - return -1; - - if ((error = collect_attr_files(repo, attr_session, opts, pathname, &files)) < 0) - goto cleanup; - - info = git__calloc(num_attr, sizeof(attr_get_many_info)); - GIT_ERROR_CHECK_ALLOC(info); - - git_vector_foreach(&files, i, file) { - - git_attr_file__foreach_matching_rule(file, &path, j, rule) { - - for (k = 0; k < num_attr; k++) { - size_t pos; - - if (info[k].found != NULL) /* already found assignment */ - continue; - - if (!info[k].name.name) { - info[k].name.name = names[k]; - info[k].name.name_hash = git_attr_file__name_hash(names[k]); - } - - if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) { - info[k].found = (git_attr_assignment *) - git_vector_get(&rule->assigns, pos); - values[k] = info[k].found->value; - - if (++num_found == num_attr) - goto cleanup; - } - } - } - } - - for (k = 0; k < num_attr; k++) { - if (!info[k].found) - values[k] = NULL; - } - -cleanup: - release_attr_files(&files); - git_attr_path__free(&path); - git__free(info); - - return error; -} - -int git_attr_get_many( - const char **values, - git_repository *repo, - uint32_t flags, - const char *pathname, - size_t num_attr, - const char **names) -{ - git_attr_options opts = GIT_ATTR_OPTIONS_INIT; - - opts.flags = flags; - - return git_attr_get_many_with_session( - values, repo, NULL, &opts, pathname, num_attr, names); -} - -int git_attr_get_many_ext( - const char **values, - git_repository *repo, - git_attr_options *opts, - const char *pathname, - size_t num_attr, - const char **names) -{ - return git_attr_get_many_with_session( - values, repo, NULL, opts, pathname, num_attr, names); -} - -int git_attr_foreach( - git_repository *repo, - uint32_t flags, - const char *pathname, - int (*callback)(const char *name, const char *value, void *payload), - void *payload) -{ - git_attr_options opts = GIT_ATTR_OPTIONS_INIT; - - opts.flags = flags; - - return git_attr_foreach_ext(repo, &opts, pathname, callback, payload); -} - -int git_attr_foreach_ext( - git_repository *repo, - git_attr_options *opts, - const char *pathname, - int (*callback)(const char *name, const char *value, void *payload), - void *payload) -{ - int error; - git_attr_path path; - git_vector files = GIT_VECTOR_INIT; - size_t i, j, k; - git_attr_file *file; - git_attr_rule *rule; - git_attr_assignment *assign; - git_strmap *seen = NULL; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(callback); - GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); - - if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) - return -1; - - if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 || - (error = git_strmap_new(&seen)) < 0) - goto cleanup; - - git_vector_foreach(&files, i, file) { - - git_attr_file__foreach_matching_rule(file, &path, j, rule) { - - git_vector_foreach(&rule->assigns, k, assign) { - /* skip if higher priority assignment was already seen */ - if (git_strmap_exists(seen, assign->name)) - continue; - - if ((error = git_strmap_set(seen, assign->name, assign)) < 0) - goto cleanup; - - error = callback(assign->name, assign->value, payload); - if (error) { - git_error_set_after_callback(error); - goto cleanup; - } - } - } - } - -cleanup: - git_strmap_free(seen); - release_attr_files(&files); - git_attr_path__free(&path); - - return error; -} - -static int preload_attr_source( - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source) -{ - int error; - git_attr_file *preload = NULL; - - if (!source) - return 0; - - error = git_attr_cache__get(&preload, repo, attr_session, source, - git_attr_file__parse_buffer, true); - - if (!error) - git_attr_file__free(preload); - - return error; -} - -GIT_INLINE(int) preload_attr_file( - git_repository *repo, - git_attr_session *attr_session, - const char *base, - const char *filename) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; - - if (!filename) - return 0; - - source.base = base; - source.filename = filename; - - return preload_attr_source(repo, attr_session, &source); -} - -static int system_attr_file( - git_str *out, - git_attr_session *attr_session) -{ - int error; - - if (!attr_session) { - error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM); - - if (error == GIT_ENOTFOUND) - git_error_clear(); - - return error; - } - - if (!attr_session->init_sysdir) { - error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM); - - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error) - return error; - - attr_session->init_sysdir = 1; - } - - if (attr_session->sysdir.size == 0) - return GIT_ENOTFOUND; - - /* We can safely provide a git_str with no allocation (asize == 0) to - * a consumer. This allows them to treat this as a regular `git_str`, - * but their call to `git_str_dispose` will not attempt to free it. - */ - git_str_attach_notowned( - out, attr_session->sysdir.ptr, attr_session->sysdir.size); - return 0; -} - -static int attr_setup( - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts) -{ - git_str system = GIT_STR_INIT, info = GIT_STR_INIT; - git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE, NULL }; - git_attr_file_source head_source = { GIT_ATTR_FILE_SOURCE_HEAD, NULL, GIT_ATTR_FILE, NULL }; - git_attr_file_source commit_source = { GIT_ATTR_FILE_SOURCE_COMMIT, NULL, GIT_ATTR_FILE, NULL }; - git_index *idx = NULL; - const char *workdir; - int error = 0; - - if (attr_session && attr_session->init_setup) - return 0; - - 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. - */ - - if ((error = system_attr_file(&system, attr_session)) < 0 || - (error = preload_attr_file(repo, attr_session, NULL, system.ptr)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - - error = 0; - } - - if ((error = preload_attr_file(repo, attr_session, NULL, - git_repository_attr_cache(repo)->cfg_attr_file)) < 0) - goto out; - - if ((error = git_repository__item_path(&info, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = preload_attr_file(repo, attr_session, info.ptr, GIT_ATTR_FILE_INREPO)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - - error = 0; - } - - if ((workdir = git_repository_workdir(repo)) != NULL && - (error = preload_attr_file(repo, attr_session, workdir, GIT_ATTR_FILE)) < 0) - goto out; - - if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = preload_attr_source(repo, attr_session, &index_source)) < 0) - goto out; - - if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) && - (error = preload_attr_source(repo, attr_session, &head_source)) < 0) - goto out; - - if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0)) { -#ifndef GIT_DEPRECATE_HARD - if (opts->commit_id) - commit_source.commit_id = opts->commit_id; - else -#endif - commit_source.commit_id = &opts->attr_commit_id; - - if ((error = preload_attr_source(repo, attr_session, &commit_source)) < 0) - goto out; - } - - if (attr_session) - attr_session->init_setup = 1; - -out: - git_str_dispose(&system); - git_str_dispose(&info); - - return error; -} - -int git_attr_add_macro( - git_repository *repo, - const char *name, - const char *values) -{ - int error; - git_attr_rule *macro = NULL; - git_pool *pool; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_attr_cache__init(repo)) < 0) - return error; - - macro = git__calloc(1, sizeof(git_attr_rule)); - GIT_ERROR_CHECK_ALLOC(macro); - - pool = &git_repository_attr_cache(repo)->pool; - - macro->match.pattern = git_pool_strdup(pool, name); - GIT_ERROR_CHECK_ALLOC(macro->match.pattern); - - macro->match.length = strlen(macro->match.pattern); - macro->match.flags = GIT_ATTR_FNMATCH_MACRO; - - error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); - - if (!error) - error = git_attr_cache__insert_macro(repo, macro); - - if (error < 0) - git_attr_rule__free(macro); - - return error; -} - -typedef struct { - git_repository *repo; - git_attr_session *attr_session; - git_attr_options *opts; - const char *workdir; - git_index *index; - git_vector *files; -} attr_walk_up_info; - -static int attr_decide_sources( - uint32_t flags, - bool has_wd, - bool has_index, - git_attr_file_source_t *srcs) -{ - int count = 0; - - switch (flags & 0x03) { - case GIT_ATTR_CHECK_FILE_THEN_INDEX: - if (has_wd) - srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; - if (has_index) - srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; - break; - case GIT_ATTR_CHECK_INDEX_THEN_FILE: - if (has_index) - srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; - if (has_wd) - srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; - break; - case GIT_ATTR_CHECK_INDEX_ONLY: - if (has_index) - srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; - break; - } - - if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) - srcs[count++] = GIT_ATTR_FILE_SOURCE_HEAD; - - if ((flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0) - srcs[count++] = GIT_ATTR_FILE_SOURCE_COMMIT; - - return count; -} - -static int push_attr_source( - git_repository *repo, - git_attr_session *attr_session, - git_vector *list, - git_attr_file_source *source, - bool allow_macros) -{ - int error = 0; - git_attr_file *file = NULL; - - error = git_attr_cache__get(&file, repo, attr_session, - source, - git_attr_file__parse_buffer, - allow_macros); - - if (error < 0) - return error; - - if (file != NULL) { - if ((error = git_vector_insert(list, file)) < 0) - git_attr_file__free(file); - } - - return error; -} - -GIT_INLINE(int) push_attr_file( - git_repository *repo, - git_attr_session *attr_session, - git_vector *list, - const char *base, - const char *filename) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; - return push_attr_source(repo, attr_session, list, &source, true); -} - -static int push_one_attr(void *ref, const char *path) -{ - attr_walk_up_info *info = (attr_walk_up_info *)ref; - git_attr_file_source_t src[GIT_ATTR_FILE_NUM_SOURCES]; - int error = 0, n_src, i; - bool allow_macros; - - n_src = attr_decide_sources(info->opts ? info->opts->flags : 0, - info->workdir != NULL, - info->index != NULL, - src); - - allow_macros = info->workdir ? !strcmp(info->workdir, path) : false; - - for (i = 0; !error && i < n_src; ++i) { - git_attr_file_source source = { src[i], path, GIT_ATTR_FILE }; - - if (src[i] == GIT_ATTR_FILE_SOURCE_COMMIT && info->opts) { -#ifndef GIT_DEPRECATE_HARD - if (info->opts->commit_id) - source.commit_id = info->opts->commit_id; - else -#endif - source.commit_id = &info->opts->attr_commit_id; - } - - error = push_attr_source(info->repo, info->attr_session, info->files, - &source, allow_macros); - } - - return error; -} - -static void release_attr_files(git_vector *files) -{ - size_t i; - git_attr_file *file; - - git_vector_foreach(files, i, file) { - git_attr_file__free(file); - files->contents[i] = NULL; - } - git_vector_free(files); -} - -static int collect_attr_files( - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *path, - git_vector *files) -{ - int error = 0; - git_str dir = GIT_STR_INIT, attrfile = GIT_STR_INIT; - const char *workdir = git_repository_workdir(repo); - attr_walk_up_info info = { NULL }; - - GIT_ASSERT(!git_fs_path_is_absolute(path)); - - if ((error = attr_setup(repo, attr_session, opts)) < 0) - return error; - - /* Resolve path in a non-bare repo */ - if (workdir != NULL) { - if (!(error = git_repository_workdir_path(&dir, repo, path))) - error = git_fs_path_find_dir(&dir); - } - else { - error = git_fs_path_dirname_r(&dir, path); - } - - if (error < 0) - goto cleanup; - - /* in precedence order highest to lowest: - * - $GIT_DIR/info/attributes - * - path components with .gitattributes - * - config core.attributesfile - * - $GIT_PREFIX/etc/gitattributes - */ - - if ((error = git_repository__item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = push_attr_file(repo, attr_session, files, attrfile.ptr, GIT_ATTR_FILE_INREPO)) < 0) { - if (error != GIT_ENOTFOUND) - goto cleanup; - } - - info.repo = repo; - info.attr_session = attr_session; - info.opts = opts; - info.workdir = workdir; - if (git_repository_index__weakptr(&info.index, repo) < 0) - git_error_clear(); /* no error even if there is no index */ - info.files = files; - - if (!strcmp(dir.ptr, ".")) - error = push_one_attr(&info, ""); - else - error = git_fs_path_walk_up(&dir, workdir, push_one_attr, &info); - - if (error < 0) - goto cleanup; - - if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { - error = push_attr_file(repo, attr_session, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); - if (error < 0) - goto cleanup; - } - - if (!opts || (opts->flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { - error = system_attr_file(&dir, attr_session); - - if (!error) - error = push_attr_file(repo, attr_session, files, NULL, dir.ptr); - else if (error == GIT_ENOTFOUND) - error = 0; - } - - cleanup: - if (error < 0) - release_attr_files(files); - git_str_dispose(&attrfile); - git_str_dispose(&dir); - - return error; -} diff --git a/src/attr.h b/src/attr.h deleted file mode 100644 index 977565205..000000000 --- a/src/attr.h +++ /dev/null @@ -1,15 +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_attr_h__ -#define INCLUDE_attr_h__ - -#include "common.h" - -#include "attr_file.h" -#include "attrcache.h" - -#endif diff --git a/src/attr_file.c b/src/attr_file.c deleted file mode 100644 index 0eb881a9b..000000000 --- a/src/attr_file.c +++ /dev/null @@ -1,1027 +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 "attr_file.h" - -#include "repository.h" -#include "filebuf.h" -#include "attrcache.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) -{ - bool unlock = !git_mutex_lock(&file->lock); - git_attr_file__clear_rules(file, false); - git_pool_clear(&file->pool); - if (unlock) - git_mutex_unlock(&file->lock); - git_mutex_free(&file->lock); - - git__memzero(file, sizeof(*file)); - git__free(file); -} - -int git_attr_file__new( - git_attr_file **out, - git_attr_file_entry *entry, - git_attr_file_source *source) -{ - git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file)); - GIT_ERROR_CHECK_ALLOC(attrs); - - if (git_mutex_init(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize lock"); - goto on_error; - } - - if (git_pool_init(&attrs->pool, 1) < 0) - goto on_error; - - GIT_REFCOUNT_INC(attrs); - attrs->entry = entry; - memcpy(&attrs->source, source, sizeof(git_attr_file_source)); - *out = attrs; - return 0; - -on_error: - git__free(attrs); - return -1; -} - -int git_attr_file__clear_rules(git_attr_file *file, bool need_lock) -{ - unsigned int i; - git_attr_rule *rule; - - if (need_lock && git_mutex_lock(&file->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); - return -1; - } - - git_vector_foreach(&file->rules, i, rule) - git_attr_rule__free(rule); - git_vector_free(&file->rules); - - if (need_lock) - git_mutex_unlock(&file->lock); - - return 0; -} - -void git_attr_file__free(git_attr_file *file) -{ - if (!file) - return; - GIT_REFCOUNT_DEC(file, attr_file_free); -} - -static int attr_file_oid_from_index( - git_oid *oid, git_repository *repo, const char *path) -{ - int error; - git_index *idx; - size_t pos; - const git_index_entry *entry; - - if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0) - return error; - - if (!(entry = git_index_get_byindex(idx, pos))) - return GIT_ENOTFOUND; - - *oid = entry->id; - return 0; -} - -int git_attr_file__load( - git_attr_file **out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_entry *entry, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros) -{ - int error = 0; - git_commit *commit = NULL; - git_tree *tree = NULL; - git_tree_entry *tree_entry = NULL; - git_blob *blob = NULL; - git_str content = GIT_STR_INIT; - const char *content_str; - git_attr_file *file; - struct stat st; - bool nonexistent = false; - int bom_offset; - git_str_bom_t bom; - git_oid id; - git_object_size_t blobsize; - - *out = NULL; - - switch (source->type) { - case GIT_ATTR_FILE_SOURCE_MEMORY: - /* in-memory attribute file doesn't need data */ - break; - case GIT_ATTR_FILE_SOURCE_INDEX: { - if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 || - (error = git_blob_lookup(&blob, repo, &id)) < 0) - return error; - - /* 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); - git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize); - break; - } - case GIT_ATTR_FILE_SOURCE_FILE: { - int fd = -1; - - /* For open or read errors, pretend that we got ENOTFOUND. */ - /* TODO: issue warning when warning API is available */ - - if (p_stat(entry->fullpath, &st) < 0 || - S_ISDIR(st.st_mode) || - (fd = git_futils_open_ro(entry->fullpath)) < 0 || - (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) - nonexistent = true; - - if (fd >= 0) - p_close(fd); - - break; - } - case GIT_ATTR_FILE_SOURCE_HEAD: - case GIT_ATTR_FILE_SOURCE_COMMIT: { - if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) { - if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0 || - (error = git_commit_tree(&tree, commit)) < 0) - goto cleanup; - } else { - if ((error = git_repository_head_tree(&tree, repo)) < 0) - goto cleanup; - } - - if ((error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0) { - /* - * If the attributes file does not exist, we can - * cache an empty file for this commit to prevent - * needless future lookups. - */ - if (error == GIT_ENOTFOUND) { - error = 0; - break; - } - - goto cleanup; - } - - if ((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_str_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->type); - return -1; - } - - if ((error = git_attr_file__new(&file, entry, source)) < 0) - goto cleanup; - - /* advance over a UTF8 BOM */ - content_str = git_str_cstr(&content); - bom_offset = git_str_detect_bom(&bom, &content); - - if (bom == GIT_STR_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, content_str, allow_macros)) < 0) { - git_attr_file__free(file); - goto cleanup; - } - - /* write cache breakers */ - if (nonexistent) - file->nonexistent = 1; - else if (source->type == GIT_ATTR_FILE_SOURCE_INDEX) - git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); - else if (source->type == GIT_ATTR_FILE_SOURCE_HEAD) - git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); - else if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) - git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); - else if (source->type == GIT_ATTR_FILE_SOURCE_FILE) - git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); - /* else always cacheable */ - - *out = file; - -cleanup: - git_blob_free(blob); - git_tree_entry_free(tree_entry); - git_tree_free(tree); - git_commit_free(commit); - git_str_dispose(&content); - - return error; -} - -int git_attr_file__out_of_date( - git_repository *repo, - git_attr_session *attr_session, - git_attr_file *file, - git_attr_file_source *source) -{ - if (!file) - return 1; - - /* we are never out of date if we just created this data in the same - * attr_session; otherwise, nonexistent files must be invalidated - */ - if (attr_session && attr_session->key == file->session_key) - return 0; - else if (file->nonexistent) - return 1; - - switch (file->source.type) { - case GIT_ATTR_FILE_SOURCE_MEMORY: - return 0; - - case GIT_ATTR_FILE_SOURCE_FILE: - return git_futils_filestamp_check( - &file->cache_data.stamp, file->entry->fullpath); - - case GIT_ATTR_FILE_SOURCE_INDEX: { - int error; - git_oid id; - - if ((error = attr_file_oid_from_index( - &id, repo, file->entry->path)) < 0) - return error; - - return (git_oid__cmp(&file->cache_data.oid, &id) != 0); - } - - case GIT_ATTR_FILE_SOURCE_HEAD: { - git_tree *tree = NULL; - int error = git_repository_head_tree(&tree, repo); - - if (error < 0) - return error; - - error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); - - git_tree_free(tree); - return error; - } - - case GIT_ATTR_FILE_SOURCE_COMMIT: { - git_commit *commit = NULL; - git_tree *tree = NULL; - int error; - - if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0) - return error; - - error = git_commit_tree(&tree, commit); - git_commit_free(commit); - - if (error < 0) - return error; - - error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); - - git_tree_free(tree); - return error; - } - - default: - git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source.type); - return -1; - } -} - -static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); -static void git_attr_rule__clear(git_attr_rule *rule); -static bool parse_optimized_patterns( - git_attr_fnmatch *spec, - git_pool *pool, - const char *pattern); - -int git_attr_file__parse_buffer( - git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) -{ - 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_fs_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) { - git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); - return -1; - } - - while (!error && *scan) { - /* 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; - - /* 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 (error != GIT_ENOTFOUND) - goto out; - error = 0; - continue; - } - - 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); - - return error; -} - -uint32_t git_attr_file__name_hash(const char *name) -{ - uint32_t h = 5381; - int c; - - GIT_ASSERT_ARG(name); - - while ((c = (int)*name++) != 0) - h = ((h << 5) + h) + c; - return h; -} - -int git_attr_file__lookup_one( - git_attr_file *file, - git_attr_path *path, - const char *attr, - const char **value) -{ - size_t i; - git_attr_name name; - git_attr_rule *rule; - - *value = NULL; - - name.name = attr; - name.name_hash = git_attr_file__name_hash(attr); - - git_attr_file__foreach_matching_rule(file, path, i, rule) { - size_t pos; - - if (!git_vector_bsearch(&pos, &rule->assigns, &name)) { - *value = ((git_attr_assignment *) - git_vector_get(&rule->assigns, pos))->value; - break; - } - } - - return 0; -} - -int git_attr_file__load_standalone(git_attr_file **out, const char *path) -{ - git_str content = GIT_STR_INIT; - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; - git_attr_file *file = NULL; - int error; - - if ((error = git_futils_readbuffer(&content, path)) < 0) - goto out; - - /* - * 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_attr_file__new(&file, NULL, &source)) < 0 || - (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 || - (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0) - goto out; - - *out = file; -out: - if (error < 0) - git_attr_file__free(file); - git_str_dispose(&content); - - return error; -} - -bool git_attr_fnmatch__match( - git_attr_fnmatch *match, - git_attr_path *path) -{ - const char *relpath = path->path; - const char *filename; - int flags = 0; - - /* - * If the rule was generated in a subdirectory, we must only - * use it for paths inside that directory. We can thus return - * a non-match if the prefixes don't match. - */ - if (match->containing_dir) { - if (match->flags & GIT_ATTR_FNMATCH_ICASE) { - if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length)) - return 0; - } else { - if (git__prefixcmp(path->path, match->containing_dir)) - return 0; - } - - relpath += match->containing_dir_length; - } - - if (match->flags & GIT_ATTR_FNMATCH_ICASE) - flags |= WM_CASEFOLD; - - if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { - filename = relpath; - flags |= WM_PATHNAME; - } else { - filename = path->basename; - } - - if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { - bool samename; - - /* - * for attribute checks or checks at the root of this match's - * containing_dir (or root of the repository if no containing_dir), - * do not match. - */ - if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || - path->basename == relpath) - return false; - - /* fail match if this is a file with same name as ignored folder */ - samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? - !strcasecmp(match->pattern, relpath) : - !strcmp(match->pattern, relpath); - - if (samename) - return false; - - return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); - } - - return (wildmatch(match->pattern, filename, flags) == WM_MATCH); -} - -bool git_attr_rule__match( - git_attr_rule *rule, - git_attr_path *path) -{ - bool matched = git_attr_fnmatch__match(&rule->match, path); - - if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) - matched = !matched; - - return matched; -} - -git_attr_assignment *git_attr_rule__lookup_assignment( - git_attr_rule *rule, const char *name) -{ - size_t pos; - git_attr_name key; - key.name = name; - key.name_hash = git_attr_file__name_hash(name); - - if (git_vector_bsearch(&pos, &rule->assigns, &key)) - return NULL; - - return git_vector_get(&rule->assigns, pos); -} - -int git_attr_path__init( - git_attr_path *info, - const char *path, - const char *base, - git_dir_flag dir_flag) -{ - ssize_t root; - - /* build full path as best we can */ - git_str_init(&info->full, 0); - - if (git_fs_path_join_unrooted(&info->full, path, base, &root) < 0) - return -1; - - info->path = info->full.ptr + root; - - /* remove trailing slashes */ - while (info->full.size > 0) { - if (info->full.ptr[info->full.size - 1] != '/') - break; - info->full.size--; - } - info->full.ptr[info->full.size] = '\0'; - - /* skip leading slashes in path */ - while (*info->path == '/') - info->path++; - - /* find trailing basename component */ - info->basename = strrchr(info->path, '/'); - if (info->basename) - info->basename++; - if (!info->basename || !*info->basename) - info->basename = info->path; - - switch (dir_flag) - { - case GIT_DIR_FLAG_FALSE: - info->is_dir = 0; - break; - - case GIT_DIR_FLAG_TRUE: - info->is_dir = 1; - break; - - case GIT_DIR_FLAG_UNKNOWN: - default: - info->is_dir = (int)git_fs_path_isdir(info->full.ptr); - break; - } - - return 0; -} - -void git_attr_path__free(git_attr_path *info) -{ - git_str_dispose(&info->full); - info->path = NULL; - info->basename = NULL; -} - -/* - * From gitattributes(5): - * - * Patterns have the following format: - * - * - A blank line matches no files, so it can serve as a separator for - * readability. - * - * - A line starting with # serves as a comment. - * - * - An optional prefix ! which negates the pattern; any matching file - * excluded by a previous pattern will become included again. If a negated - * pattern matches, this will override lower precedence patterns sources. - * - * - If the pattern ends with a slash, it is removed for the purpose of the - * following description, but it would only find a match with a directory. In - * other words, foo/ will match a directory foo and paths underneath it, but - * will not match a regular file or a symbolic link foo (this is consistent - * with the way how pathspec works in general in git). - * - * - If the pattern does not contain a slash /, git treats it as a shell glob - * pattern and checks for a match against the pathname without leading - * directories. - * - * - Otherwise, git treats the pattern as a shell glob suitable for consumption - * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will - * not match a / in the pathname. For example, "Documentation/\*.html" matches - * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading - * slash matches the beginning of the pathname; for example, "/\*.c" matches - * "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 - * another error code there was an actual problem. - */ -int git_attr_fnmatch__parse( - git_attr_fnmatch *spec, - git_pool *pool, - const char *context, - const char **base) -{ - const char *pattern, *scan; - int slash_count, allow_space; - bool escaped; - - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(base && *base); - - if (parse_optimized_patterns(spec, pool, *base)) - return 0; - - spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); - allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); - - pattern = *base; - - 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; - } - - if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { - if (strncmp(pattern, "[attr]", 6) == 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; - pattern += 6; - } - /* else a character range like [a-e]* which is accepted */ - } - - if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; - pattern++; - } - - slash_count = 0; - escaped = false; - /* Scan until a non-escaped whitespace. */ - for (scan = pattern; *scan != '\0'; ++scan) { - char c = *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 (slash_count == 1 && pattern == scan) - pattern++; - } 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; - - if ((spec->length = scan - pattern) == 0) - return GIT_ENOTFOUND; - - /* - * Remove one trailing \r in case this is a CRLF delimited - * file, in the case of Icon\r\r\n, we still leave the first - * \r there to match against. - */ - if (pattern[spec->length - 1] == '\r') - if (--spec->length == 0) - return GIT_ENOTFOUND; - - /* Remove trailing spaces. */ - spec->length -= trailing_space_length(pattern, spec->length); - - if (spec->length == 0) - return GIT_ENOTFOUND; - - if (pattern[spec->length - 1] == '/') { - spec->length--; - spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; - if (--slash_count <= 0) - spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; - } - - if (context) { - char *slash = strrchr(context, '/'); - size_t len; - if (slash) { - /* include the slash for easier matching */ - len = slash - context + 1; - spec->containing_dir = git_pool_strndup(pool, context, len); - spec->containing_dir_length = len; - } - } - - spec->pattern = git_pool_strndup(pool, pattern, spec->length); - - if (!spec->pattern) { - *base = git__next_line(pattern); - return -1; - } else { - /* strip '\' that might have been used for internal whitespace */ - spec->length = unescape_spaces(spec->pattern); - } - - return 0; -} - -static bool parse_optimized_patterns( - git_attr_fnmatch *spec, - git_pool *pool, - const char *pattern) -{ - if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) { - spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL; - spec->pattern = git_pool_strndup(pool, pattern, 1); - spec->length = 1; - - return true; - } - - return false; -} - -static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) -{ - const git_attr_name *a = a_raw; - const git_attr_name *b = b_raw; - - if (b->name_hash < a->name_hash) - return 1; - else if (b->name_hash > a->name_hash) - return -1; - else - return strcmp(b->name, a->name); -} - -static void git_attr_assignment__free(git_attr_assignment *assign) -{ - /* name and value are stored in a git_pool associated with the - * git_attr_file, so they do not need to be freed here - */ - assign->name = NULL; - assign->value = NULL; - git__free(assign); -} - -static int merge_assignments(void **old_raw, void *new_raw) -{ - git_attr_assignment **old = (git_attr_assignment **)old_raw; - git_attr_assignment *new = (git_attr_assignment *)new_raw; - - GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); - *old = new; - return GIT_EEXISTS; -} - -int git_attr_assignment__parse( - git_repository *repo, - git_pool *pool, - git_vector *assigns, - const char **base) -{ - int error; - const char *scan = *base; - git_attr_assignment *assign = NULL; - - GIT_ASSERT_ARG(assigns && !assigns->length); - - git_vector_set_cmp(assigns, sort_by_hash_and_name); - - while (*scan && *scan != '\n') { - const char *name_start, *value_start; - - /* skip leading blanks */ - while (git__isspace(*scan) && *scan != '\n') scan++; - - /* allocate assign if needed */ - if (!assign) { - assign = git__calloc(1, sizeof(git_attr_assignment)); - GIT_ERROR_CHECK_ALLOC(assign); - GIT_REFCOUNT_INC(assign); - } - - assign->name_hash = 5381; - assign->value = git_attr__true; - - /* look for magic name prefixes */ - if (*scan == '-') { - assign->value = git_attr__false; - scan++; - } else if (*scan == '!') { - assign->value = git_attr__unset; /* explicit unspecified state */ - scan++; - } else if (*scan == '#') /* comment rest of line */ - break; - - /* find the name */ - name_start = scan; - while (*scan && !git__isspace(*scan) && *scan != '=') { - assign->name_hash = - ((assign->name_hash << 5) + assign->name_hash) + *scan; - scan++; - } - if (scan == name_start) { - /* must have found lone prefix (" - ") or leading = ("=foo") - * or end of buffer -- advance until whitespace and continue - */ - while (*scan && !git__isspace(*scan)) scan++; - continue; - } - - /* allocate permanent storage for name */ - assign->name = git_pool_strndup(pool, name_start, scan - name_start); - GIT_ERROR_CHECK_ALLOC(assign->name); - - /* if there is an equals sign, find the value */ - if (*scan == '=') { - for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); - - /* if we found a value, allocate permanent storage for it */ - if (scan > value_start) { - assign->value = git_pool_strndup(pool, value_start, scan - value_start); - GIT_ERROR_CHECK_ALLOC(assign->value); - } - } - - /* expand macros (if given a repo with a macro cache) */ - if (repo != NULL && assign->value == git_attr__true) { - git_attr_rule *macro = - git_attr_cache__lookup_macro(repo, assign->name); - - if (macro != NULL) { - unsigned int i; - git_attr_assignment *massign; - - git_vector_foreach(¯o->assigns, i, massign) { - GIT_REFCOUNT_INC(massign); - - error = git_vector_insert_sorted( - assigns, massign, &merge_assignments); - if (error < 0 && error != GIT_EEXISTS) { - git_attr_assignment__free(assign); - return error; - } - } - } - } - - /* insert allocated assign into vector */ - error = git_vector_insert_sorted(assigns, assign, &merge_assignments); - if (error < 0 && error != GIT_EEXISTS) - return error; - - /* clear assign since it is now "owned" by the vector */ - assign = NULL; - } - - if (assign != NULL) - git_attr_assignment__free(assign); - - *base = git__next_line(scan); - - return (assigns->length == 0) ? GIT_ENOTFOUND : 0; -} - -static void git_attr_rule__clear(git_attr_rule *rule) -{ - unsigned int i; - git_attr_assignment *assign; - - if (!rule) - return; - - if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { - git_vector_foreach(&rule->assigns, i, assign) - GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); - git_vector_free(&rule->assigns); - } - - /* match.pattern is stored in a git_pool, so no need to free */ - rule->match.pattern = NULL; - rule->match.length = 0; -} - -void git_attr_rule__free(git_attr_rule *rule) -{ - git_attr_rule__clear(rule); - git__free(rule); -} - -int git_attr_session__init(git_attr_session *session, git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - memset(session, 0, sizeof(*session)); - session->key = git_atomic32_inc(&repo->attr_session_key); - - return 0; -} - -void git_attr_session__free(git_attr_session *session) -{ - if (!session) - return; - - git_str_dispose(&session->sysdir); - git_str_dispose(&session->tmp); - - memset(session, 0, sizeof(git_attr_session)); -} diff --git a/src/attr_file.h b/src/attr_file.h deleted file mode 100644 index 08630d1a6..000000000 --- a/src/attr_file.h +++ /dev/null @@ -1,241 +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_attr_file_h__ -#define INCLUDE_attr_file_h__ - -#include "common.h" - -#include "git2/oid.h" -#include "git2/attr.h" -#include "vector.h" -#include "pool.h" -#include "str.h" -#include "futils.h" - -#define GIT_ATTR_FILE ".gitattributes" -#define GIT_ATTR_FILE_INREPO "attributes" -#define GIT_ATTR_FILE_SYSTEM "gitattributes" -#define GIT_ATTR_FILE_XDG "attributes" - -#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) -#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) -#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) -#define GIT_ATTR_FNMATCH_MACRO (1U << 3) -#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) -#define GIT_ATTR_FNMATCH_HASWILD (1U << 5) -#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) -#define GIT_ATTR_FNMATCH_ICASE (1U << 7) -#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__INCOMING \ - (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) - -typedef enum { - GIT_ATTR_FILE_SOURCE_MEMORY = 0, - GIT_ATTR_FILE_SOURCE_FILE = 1, - GIT_ATTR_FILE_SOURCE_INDEX = 2, - GIT_ATTR_FILE_SOURCE_HEAD = 3, - GIT_ATTR_FILE_SOURCE_COMMIT = 4, - - GIT_ATTR_FILE_NUM_SOURCES = 5 -} git_attr_file_source_t; - -typedef struct { - /* The source location for the attribute file. */ - git_attr_file_source_t type; - - /* - * The filename of the attribute file to read (relative to the - * given base path). - */ - const char *base; - const char *filename; - - /* - * The commit ID when the given source type is a commit (or NULL - * for the repository's HEAD commit.) - */ - git_oid *commit_id; -} git_attr_file_source; - -extern const char *git_attr__true; -extern const char *git_attr__false; -extern const char *git_attr__unset; - -typedef struct { - char *pattern; - size_t length; - char *containing_dir; - size_t containing_dir_length; - unsigned int flags; -} git_attr_fnmatch; - -typedef struct { - git_attr_fnmatch match; - git_vector assigns; /* vector of */ -} git_attr_rule; - -typedef struct { - git_refcount unused; - const char *name; - uint32_t name_hash; -} git_attr_name; - -typedef struct { - git_refcount rc; /* for macros */ - char *name; - uint32_t name_hash; - const char *value; -} git_attr_assignment; - -typedef struct git_attr_file_entry git_attr_file_entry; - -typedef struct { - git_refcount rc; - git_mutex lock; - git_attr_file_entry *entry; - git_attr_file_source source; - git_vector rules; /* vector of or */ - git_pool pool; - unsigned int nonexistent:1; - int session_key; - union { - git_oid oid; - git_futils_filestamp stamp; - } cache_data; -} git_attr_file; - -struct git_attr_file_entry { - git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES]; - const char *path; /* points into fullpath */ - char fullpath[GIT_FLEX_ARRAY]; -}; - -typedef struct { - git_str full; - char *path; - char *basename; - int is_dir; -} git_attr_path; - -/* A git_attr_session can provide an "instance" of reading, to prevent cache - * invalidation during a single operation instance (like checkout). - */ - -typedef struct { - int key; - unsigned int init_setup:1, - init_sysdir:1; - git_str sysdir; - git_str tmp; -} git_attr_session; - -extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo); -extern void git_attr_session__free(git_attr_session *session); - -extern int git_attr_get_many_with_session( - const char **values_out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *path, - size_t num_attr, - const char **names); - -typedef int (*git_attr_file_parser)( - git_repository *repo, - git_attr_file *file, - const char *data, - bool allow_macros); - -/* - * git_attr_file API - */ - -int git_attr_file__new( - git_attr_file **out, - git_attr_file_entry *entry, - git_attr_file_source *source); - -void git_attr_file__free(git_attr_file *file); - -int git_attr_file__load( - git_attr_file **out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_entry *ce, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros); - -int git_attr_file__load_standalone( - git_attr_file **out, const char *path); - -int git_attr_file__out_of_date( - git_repository *repo, git_attr_session *session, git_attr_file *file, git_attr_file_source *source); - -int git_attr_file__parse_buffer( - 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); - -int git_attr_file__lookup_one( - git_attr_file *file, - git_attr_path *path, - const char *attr, - const char **value); - -/* loop over rules in file from bottom to top */ -#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ - git_vector_rforeach(&(file)->rules, (iter), (rule)) \ - if (git_attr_rule__match((rule), (path))) - -uint32_t git_attr_file__name_hash(const char *name); - - -/* - * other utilities - */ - -extern int git_attr_fnmatch__parse( - git_attr_fnmatch *spec, - git_pool *pool, - const char *source, - const char **base); - -extern bool git_attr_fnmatch__match( - git_attr_fnmatch *rule, - git_attr_path *path); - -extern void git_attr_rule__free(git_attr_rule *rule); - -extern bool git_attr_rule__match( - git_attr_rule *rule, - git_attr_path *path); - -extern git_attr_assignment *git_attr_rule__lookup_assignment( - git_attr_rule *rule, const char *name); - -typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag; - -extern int git_attr_path__init( - git_attr_path *out, - const char *path, - const char *base, - git_dir_flag is_dir); -extern void git_attr_path__free(git_attr_path *info); - -extern int git_attr_assignment__parse( - git_repository *repo, /* needed to expand macros */ - git_pool *pool, - git_vector *assigns, - const char **scan); - -#endif diff --git a/src/attrcache.c b/src/attrcache.c deleted file mode 100644 index b16d95c3c..000000000 --- a/src/attrcache.c +++ /dev/null @@ -1,478 +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 "attrcache.h" - -#include "repository.h" -#include "attr_file.h" -#include "config.h" -#include "sysdir.h" -#include "ignore.h" -#include "path.h" - -GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache) -{ - GIT_UNUSED(cache); /* avoid warning if threading is off */ - - if (git_mutex_lock(&cache->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to get attr cache lock"); - return -1; - } - return 0; -} - -GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache) -{ - GIT_UNUSED(cache); /* avoid warning if threading is off */ - git_mutex_unlock(&cache->lock); -} - -GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry( - git_attr_cache *cache, const char *path) -{ - return git_strmap_get(cache->files, path); -} - -int git_attr_cache__alloc_file_entry( - git_attr_file_entry **out, - git_repository *repo, - const char *base, - const char *path, - git_pool *pool) -{ - git_str fullpath_str = GIT_STR_INIT; - size_t baselen = 0, pathlen = strlen(path); - size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1; - git_attr_file_entry *ce; - - if (base != NULL && git_fs_path_root(path) < 0) { - baselen = strlen(base); - cachesize += baselen; - - if (baselen && base[baselen - 1] != '/') - cachesize++; - } - - ce = git_pool_mallocz(pool, cachesize); - GIT_ERROR_CHECK_ALLOC(ce); - - if (baselen) { - memcpy(ce->fullpath, base, baselen); - - if (base[baselen - 1] != '/') - ce->fullpath[baselen++] = '/'; - } - memcpy(&ce->fullpath[baselen], path, pathlen); - - fullpath_str.ptr = ce->fullpath; - fullpath_str.size = pathlen + baselen; - - if (git_path_validate_str_length(repo, &fullpath_str) < 0) - return -1; - - ce->path = &ce->fullpath[baselen]; - *out = ce; - - return 0; -} - -/* call with attrcache locked */ -static int attr_cache_make_entry( - git_attr_file_entry **out, git_repository *repo, const char *path) -{ - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry = NULL; - int error; - - if ((error = git_attr_cache__alloc_file_entry(&entry, repo, - git_repository_workdir(repo), path, &cache->pool)) < 0) - return error; - - if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0) - return error; - - *out = entry; - return error; -} - -/* insert entry or replace existing if we raced with another thread */ -static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file) -{ - git_attr_file_entry *entry; - git_attr_file *old; - - if (attr_cache_lock(cache) < 0) - return -1; - - entry = attr_cache_lookup_entry(cache, file->entry->path); - - GIT_REFCOUNT_OWN(file, entry); - GIT_REFCOUNT_INC(file); - - /* - * Replace the existing value if another thread has - * created it in the meantime. - */ - old = git_atomic_swap(entry->file[file->source.type], file); - - if (old) { - GIT_REFCOUNT_OWN(old, NULL); - git_attr_file__free(old); - } - - attr_cache_unlock(cache); - return 0; -} - -static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) -{ - int error = 0; - git_attr_file_entry *entry; - git_attr_file *oldfile = NULL; - - if (!file) - return 0; - - if ((error = attr_cache_lock(cache)) < 0) - return error; - - if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL) - oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); - - attr_cache_unlock(cache); - - if (oldfile == file) { - GIT_REFCOUNT_OWN(file, NULL); - git_attr_file__free(file); - } - - return error; -} - -/* Look up cache entry and file. - * - If entry is not present, create it while the cache is locked. - * - If file is present, increment refcount before returning it, so the - * cache can be unlocked and it won't go away. - */ -static int attr_cache_lookup( - git_attr_file **out_file, - git_attr_file_entry **out_entry, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source) -{ - int error = 0; - git_str path = GIT_STR_INIT; - const char *wd = git_repository_workdir(repo); - const char *filename; - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry = NULL; - git_attr_file *file = NULL; - - /* join base and path as needed */ - if (source->base != NULL && git_fs_path_root(source->filename) < 0) { - git_str *p = attr_session ? &attr_session->tmp : &path; - - if (git_str_joinpath(p, source->base, source->filename) < 0 || - git_path_validate_str_length(repo, p) < 0) - return -1; - - filename = p->ptr; - } else { - filename = source->filename; - } - - if (wd && !git__prefixcmp(filename, wd)) - filename += strlen(wd); - - /* check cache for existing entry */ - if ((error = attr_cache_lock(cache)) < 0) - goto cleanup; - - entry = attr_cache_lookup_entry(cache, filename); - - if (!entry) { - error = attr_cache_make_entry(&entry, repo, filename); - } else if (entry->file[source->type] != NULL) { - file = entry->file[source->type]; - GIT_REFCOUNT_INC(file); - } - - attr_cache_unlock(cache); - -cleanup: - *out_file = file; - *out_entry = entry; - - git_str_dispose(&path); - return error; -} - -int git_attr_cache__get( - git_attr_file **out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros) -{ - int error = 0; - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry = NULL; - git_attr_file *file = NULL, *updated = NULL; - - if ((error = attr_cache_lookup(&file, &entry, repo, attr_session, source)) < 0) - return error; - - /* 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, source)) > 0) - 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) { - if ((error = attr_cache_upsert(cache, updated)) < 0) { - git_attr_file__free(updated); - } else { - git_attr_file__free(file); /* offset incref from lookup */ - file = updated; - } - } - - /* if file could not be loaded */ - if (error < 0) { - /* remove existing entry */ - if (file) { - attr_cache_remove(cache, file); - git_attr_file__free(file); /* offset incref from lookup */ - file = NULL; - } - /* no error if file simply doesn't exist */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - } - - *out = file; - return error; -} - -bool git_attr_cache__is_cached( - git_repository *repo, - git_attr_file_source_t source_type, - const char *filename) -{ - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry; - git_strmap *files; - - if (!cache || !(files = cache->files)) - return false; - - if ((entry = git_strmap_get(files, filename)) == NULL) - return false; - - return entry && (entry->file[source_type] != NULL); -} - - -static int attr_cache__lookup_path( - char **out, git_config *cfg, const char *key, const char *fallback) -{ - git_str buf = GIT_STR_INIT; - int error; - git_config_entry *entry = NULL; - - *out = NULL; - - if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0) - return error; - - if (entry) { - const char *cfgval = entry->value; - - /* expand leading ~/ as needed */ - if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') { - if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2]))) - *out = git_str_detach(&buf); - } else if (cfgval) { - *out = git__strdup(cfgval); - } - } - else if (!git_sysdir_find_xdg_file(&buf, fallback)) { - *out = git_str_detach(&buf); - } - - git_config_entry_free(entry); - git_str_dispose(&buf); - - return error; -} - -static void attr_cache__free(git_attr_cache *cache) -{ - bool unlock; - - if (!cache) - return; - - unlock = (attr_cache_lock(cache) == 0); - - if (cache->files != NULL) { - git_attr_file_entry *entry; - git_attr_file *file; - int i; - - git_strmap_foreach_value(cache->files, entry, { - for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) { - if ((file = git_atomic_swap(entry->file[i], NULL)) != NULL) { - GIT_REFCOUNT_OWN(file, NULL); - git_attr_file__free(file); - } - } - }); - git_strmap_free(cache->files); - } - - if (cache->macros != NULL) { - git_attr_rule *rule; - - git_strmap_foreach_value(cache->macros, rule, { - git_attr_rule__free(rule); - }); - git_strmap_free(cache->macros); - } - - git_pool_clear(&cache->pool); - - git__free(cache->cfg_attr_file); - cache->cfg_attr_file = NULL; - - git__free(cache->cfg_excl_file); - cache->cfg_excl_file = NULL; - - if (unlock) - attr_cache_unlock(cache); - git_mutex_free(&cache->lock); - - git__free(cache); -} - -int git_attr_cache__init(git_repository *repo) -{ - int ret = 0; - git_attr_cache *cache = git_repository_attr_cache(repo); - git_config *cfg = NULL; - - if (cache) - return 0; - - cache = git__calloc(1, sizeof(git_attr_cache)); - GIT_ERROR_CHECK_ALLOC(cache); - - /* set up lock */ - if (git_mutex_init(&cache->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache"); - git__free(cache); - return -1; - } - - if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) - goto cancel; - - /* cache config settings for attributes and ignores */ - ret = attr_cache__lookup_path( - &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); - if (ret < 0) - goto cancel; - - ret = attr_cache__lookup_path( - &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG); - if (ret < 0) - goto cancel; - - /* allocate hashtable for attribute and ignore file contents, - * hashtable for attribute macros, and string pool - */ - if ((ret = git_strmap_new(&cache->files)) < 0 || - (ret = git_strmap_new(&cache->macros)) < 0 || - (ret = git_pool_init(&cache->pool, 1)) < 0) - goto cancel; - - if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL) - goto cancel; /* raced with another thread, free this but no error */ - - git_config_free(cfg); - - /* insert default macros */ - return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf"); - -cancel: - attr_cache__free(cache); - git_config_free(cfg); - return ret; -} - -int git_attr_cache_flush(git_repository *repo) -{ - git_attr_cache *cache; - - /* this could be done less expensively, but for now, we'll just free - * the entire attrcache and let the next use reinitialize it... - */ - if (repo && (cache = git_atomic_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_attr_rule *preexisting; - bool locked = false; - int error = 0; - - /* - * 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; - } - - 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; - - return git_strmap_get(macros, name); -} diff --git a/src/attrcache.h b/src/attrcache.h deleted file mode 100644 index b13e0e8f0..000000000 --- a/src/attrcache.h +++ /dev/null @@ -1,56 +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_attrcache_h__ -#define INCLUDE_attrcache_h__ - -#include "common.h" - -#include "attr_file.h" -#include "strmap.h" - -#define GIT_ATTR_CONFIG "core.attributesfile" -#define GIT_IGNORE_CONFIG "core.excludesfile" - -typedef struct { - char *cfg_attr_file; /* cached value of core.attributesfile */ - char *cfg_excl_file; /* cached value of core.excludesfile */ - git_strmap *files; /* hash path to git_attr_cache_entry records */ - git_strmap *macros; /* hash name to vector */ - git_mutex lock; - git_pool pool; -} git_attr_cache; - -extern int git_attr_cache__init(git_repository *repo); - -/* get file - loading and reload as needed */ -extern int git_attr_cache__get( - git_attr_file **file, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros); - -extern bool git_attr_cache__is_cached( - git_repository *repo, - git_attr_file_source_t source_type, - const char *filename); - -extern int git_attr_cache__alloc_file_entry( - git_attr_file_entry **out, - git_repository *repo, - const char *base, - const char *path, - git_pool *pool); - -extern int git_attr_cache__insert_macro( - git_repository *repo, git_attr_rule *macro); - -extern git_attr_rule *git_attr_cache__lookup_macro( - git_repository *repo, const char *name); - -#endif diff --git a/src/bitvec.h b/src/bitvec.h deleted file mode 100644 index 544832d95..000000000 --- a/src/bitvec.h +++ /dev/null @@ -1,75 +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_bitvec_h__ -#define INCLUDE_bitvec_h__ - -#include "common.h" - -/* - * This is a silly little fixed length bit vector type that will store - * vectors of 64 bits or less directly in the structure and allocate - * memory for vectors longer than 64 bits. You can use the two versions - * transparently through the API and avoid heap allocation completely when - * using a short bit vector as a result. - */ -typedef struct { - size_t length; - union { - uint64_t *words; - uint64_t bits; - } u; -} git_bitvec; - -GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) -{ - memset(bv, 0x0, sizeof(*bv)); - - if (capacity >= 64) { - bv->length = (capacity / 64) + 1; - bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); - if (!bv->u.words) - return -1; - } - - return 0; -} - -#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) -#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) - -GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) -{ - uint64_t *word = GIT_BITVEC_WORD(bv, bit); - uint64_t mask = GIT_BITVEC_MASK(bit); - - if (on) - *word |= mask; - else - *word &= ~mask; -} - -GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) -{ - uint64_t *word = GIT_BITVEC_WORD(bv, bit); - return (*word & GIT_BITVEC_MASK(bit)) != 0; -} - -GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) -{ - if (!bv->length) - bv->u.bits = 0; - else - memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); -} - -GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) -{ - if (bv->length) - git__free(bv->u.words); -} - -#endif diff --git a/src/blame.c b/src/blame.c deleted file mode 100644 index a6ab43efd..000000000 --- a/src/blame.c +++ /dev/null @@ -1,557 +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 "blame.h" - -#include "git2/commit.h" -#include "git2/revparse.h" -#include "git2/revwalk.h" -#include "git2/tree.h" -#include "git2/diff.h" -#include "git2/blob.h" -#include "git2/signature.h" -#include "git2/mailmap.h" -#include "util.h" -#include "repository.h" -#include "blame_git.h" - - -static int hunk_byfinalline_search_cmp(const void *key, const void *entry) -{ - git_blame_hunk *hunk = (git_blame_hunk*)entry; - - size_t lineno = *(size_t*)key; - size_t lines_in_hunk = hunk->lines_in_hunk; - size_t final_start_line_number = hunk->final_start_line_number; - - if (lineno < final_start_line_number) - return -1; - if (lineno >= final_start_line_number + lines_in_hunk) - return 1; - return 0; -} - -static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); } -static int hunk_cmp(const void *_a, const void *_b) -{ - git_blame_hunk *a = (git_blame_hunk*)_a, - *b = (git_blame_hunk*)_b; - - if (a->final_start_line_number > b->final_start_line_number) - return 1; - else if (a->final_start_line_number < b->final_start_line_number) - return -1; - else - return 0; -} - -static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line) -{ - return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1); -} - -static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) -{ - return line <= hunk->final_start_line_number; -} - -static git_blame_hunk *new_hunk( - size_t start, - size_t lines, - size_t orig_start, - const char *path) -{ - git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk)); - if (!hunk) return NULL; - - hunk->lines_in_hunk = lines; - hunk->final_start_line_number = start; - hunk->orig_start_line_number = orig_start; - hunk->orig_path = path ? git__strdup(path) : NULL; - - return hunk; -} - -static void free_hunk(git_blame_hunk *hunk) -{ - git__free((void*)hunk->orig_path); - git_signature_free(hunk->final_signature); - git_signature_free(hunk->orig_signature); - git__free(hunk); -} - -static git_blame_hunk *dup_hunk(git_blame_hunk *hunk) -{ - git_blame_hunk *newhunk = new_hunk( - hunk->final_start_line_number, - hunk->lines_in_hunk, - hunk->orig_start_line_number, - hunk->orig_path); - - if (!newhunk) - return NULL; - - git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id); - git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id); - newhunk->boundary = hunk->boundary; - - if (git_signature_dup(&newhunk->final_signature, hunk->final_signature) < 0 || - git_signature_dup(&newhunk->orig_signature, hunk->orig_signature) < 0) { - free_hunk(newhunk); - return NULL; - } - - return newhunk; -} - -/* Starting with the hunk that includes start_line, shift all following hunks' - * final_start_line by shift_by lines */ -static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by) -{ - size_t i; - - if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) { - for (; i < v->length; i++) { - git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; - hunk->final_start_line_number += shift_by; - } - } -} - -git_blame *git_blame__alloc( - git_repository *repo, - git_blame_options opts, - const char *path) -{ - git_blame *gbr = git__calloc(1, sizeof(git_blame)); - if (!gbr) - return NULL; - - gbr->repository = repo; - gbr->options = opts; - - if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 || - git_vector_init(&gbr->paths, 8, paths_cmp) < 0 || - (gbr->path = git__strdup(path)) == NULL || - git_vector_insert(&gbr->paths, git__strdup(path)) < 0) - { - git_blame_free(gbr); - return NULL; - } - - if (opts.flags & GIT_BLAME_USE_MAILMAP && - git_mailmap_from_repository(&gbr->mailmap, repo) < 0) { - git_blame_free(gbr); - return NULL; - } - - return gbr; -} - -void git_blame_free(git_blame *blame) -{ - size_t i; - git_blame_hunk *hunk; - - if (!blame) return; - - git_vector_foreach(&blame->hunks, i, hunk) - free_hunk(hunk); - git_vector_free(&blame->hunks); - - git_vector_free_deep(&blame->paths); - - git_array_clear(blame->line_index); - - git_mailmap_free(blame->mailmap); - - git__free(blame->path); - git_blob_free(blame->final_blob); - git__free(blame); -} - -uint32_t git_blame_get_hunk_count(git_blame *blame) -{ - GIT_ASSERT_ARG(blame); - return (uint32_t)blame->hunks.length; -} - -const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index) -{ - GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); - return (git_blame_hunk*)git_vector_get(&blame->hunks, index); -} - -const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno) -{ - size_t i, new_lineno = lineno; - - GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); - - if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) { - return git_blame_get_hunk_byindex(blame, (uint32_t)i); - } - - return NULL; -} - -static int normalize_options( - git_blame_options *out, - const git_blame_options *in, - git_repository *repo) -{ - git_blame_options dummy = GIT_BLAME_OPTIONS_INIT; - if (!in) in = &dummy; - - memcpy(out, in, sizeof(git_blame_options)); - - /* No newest_commit => HEAD */ - if (git_oid_is_zero(&out->newest_commit)) { - if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) { - return -1; - } - } - - /* min_line 0 really means 1 */ - if (!out->min_line) out->min_line = 1; - /* max_line 0 really means N, but we don't know N yet */ - - /* Fix up option implications */ - if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) - out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; - if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) - out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; - if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) - out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE; - - return 0; -} - -static git_blame_hunk *split_hunk_in_vector( - git_vector *vec, - git_blame_hunk *hunk, - size_t rel_line, - bool return_new) -{ - size_t new_line_count; - git_blame_hunk *nh; - - /* Don't split if already at a boundary */ - if (rel_line <= 0 || - rel_line >= hunk->lines_in_hunk) - { - return hunk; - } - - new_line_count = hunk->lines_in_hunk - rel_line; - nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count, - hunk->orig_start_line_number + rel_line, hunk->orig_path); - - if (!nh) - return NULL; - - git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id); - git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id); - - /* Adjust hunk that was split */ - hunk->lines_in_hunk -= new_line_count; - git_vector_insert_sorted(vec, nh, NULL); - { - git_blame_hunk *ret = return_new ? nh : hunk; - return ret; - } -} - -/* - * Construct a list of char indices for where lines begin - * Adapted from core git: - * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789 - */ -static int index_blob_lines(git_blame *blame) -{ - const char *buf = blame->final_buf; - size_t len = blame->final_buf_size; - int num = 0, incomplete = 0, bol = 1; - size_t *i; - - if (len && buf[len-1] != '\n') - incomplete++; /* incomplete line at the end */ - while (len--) { - if (bol) { - i = git_array_alloc(blame->line_index); - GIT_ERROR_CHECK_ALLOC(i); - *i = buf - blame->final_buf; - bol = 0; - } - if (*buf++ == '\n') { - num++; - bol = 1; - } - } - i = git_array_alloc(blame->line_index); - GIT_ERROR_CHECK_ALLOC(i); - *i = buf - blame->final_buf; - blame->num_lines = num + incomplete; - return blame->num_lines; -} - -static git_blame_hunk *hunk_from_entry(git_blame__entry *e, git_blame *blame) -{ - git_blame_hunk *h = new_hunk( - e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path); - - if (!h) - return NULL; - - git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit)); - git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit)); - git_commit_author_with_mailmap( - &h->final_signature, e->suspect->commit, blame->mailmap); - git_signature_dup(&h->orig_signature, h->final_signature); - h->boundary = e->is_boundary ? 1 : 0; - return h; -} - -static int load_blob(git_blame *blame) -{ - int error; - - if (blame->final_blob) return 0; - - error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit); - if (error < 0) - goto cleanup; - error = git_object_lookup_bypath((git_object**)&blame->final_blob, - (git_object*)blame->final, blame->path, GIT_OBJECT_BLOB); - -cleanup: - return error; -} - -static int blame_internal(git_blame *blame) -{ - int error; - git_blame__entry *ent = NULL; - git_blame__origin *o; - - 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 = (size_t)git_blob_rawsize(blame->final_blob); - - ent = git__calloc(1, sizeof(git_blame__entry)); - GIT_ERROR_CHECK_ALLOC(ent); - - ent->num_lines = index_blob_lines(blame); - ent->lno = blame->options.min_line - 1; - ent->num_lines = ent->num_lines - blame->options.min_line + 1; - if (blame->options.max_line > 0) - ent->num_lines = blame->options.max_line - blame->options.min_line + 1; - ent->s_lno = ent->lno; - ent->suspect = o; - - blame->ent = ent; - - error = git_blame__like_git(blame, blame->options.flags); - -cleanup: - for (ent = blame->ent; ent; ) { - git_blame__entry *e = ent->next; - git_blame_hunk *h = hunk_from_entry(ent, blame); - - git_vector_insert(&blame->hunks, h); - - git_blame__free_entry(ent); - ent = e; - } - - return error; -} - -/******************************************************************************* - * File blaming - ******************************************************************************/ - -int git_blame_file( - git_blame **out, - git_repository *repo, - const char *path, - git_blame_options *options) -{ - int error = -1; - git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT; - git_blame *blame = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(path); - - if ((error = normalize_options(&normOptions, options, repo)) < 0) - goto on_error; - - blame = git_blame__alloc(repo, normOptions, path); - GIT_ERROR_CHECK_ALLOC(blame); - - if ((error = load_blob(blame)) < 0) - goto on_error; - - if ((error = blame_internal(blame)) < 0) - goto on_error; - - *out = blame; - return 0; - -on_error: - git_blame_free(blame); - return error; -} - -/******************************************************************************* - * Buffer blaming - *******************************************************************************/ - -static bool hunk_is_bufferblame(git_blame_hunk *hunk) -{ - return hunk && git_oid_is_zero(&hunk->final_commit_id); -} - -static int buffer_hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - void *payload) -{ - git_blame *blame = (git_blame*)payload; - uint32_t wedge_line; - - GIT_UNUSED(delta); - - wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; - blame->current_diff_line = wedge_line; - - blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line); - if (!blame->current_hunk) { - /* Line added at the end of the file */ - blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path); - GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - - git_vector_insert(&blame->hunks, blame->current_hunk); - } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){ - /* If this hunk doesn't start between existing hunks, split a hunk up so it does */ - blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk, - wedge_line - blame->current_hunk->orig_start_line_number, true); - GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - } - - return 0; -} - -static int ptrs_equal_cmp(const void *a, const void *b) { return ab ? 1 : 0; } -static int buffer_line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - git_blame *blame = (git_blame*)payload; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - GIT_UNUSED(line); - - if (line->origin == GIT_DIFF_LINE_ADDITION) { - if (hunk_is_bufferblame(blame->current_hunk) && - hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) { - /* Append to the current buffer-blame hunk */ - blame->current_hunk->lines_in_hunk++; - shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1); - } else { - /* Create a new buffer-blame hunk with this line */ - shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); - blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path); - GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - - git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); - } - blame->current_diff_line++; - } - - if (line->origin == GIT_DIFF_LINE_DELETION) { - /* Trim the line from the current hunk; remove it if it's now empty */ - size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1; - - if (--(blame->current_hunk->lines_in_hunk) == 0) { - size_t i; - shift_base--; - if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) { - git_vector_remove(&blame->hunks, i); - free_hunk(blame->current_hunk); - blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i); - } - } - shift_hunks_by(&blame->hunks, shift_base, -1); - } - return 0; -} - -int git_blame_buffer( - git_blame **out, - git_blame *reference, - const char *buffer, - size_t buffer_len) -{ - git_blame *blame; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - size_t i; - git_blame_hunk *hunk; - - diffopts.context_lines = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(reference); - GIT_ASSERT_ARG(buffer && buffer_len); - - blame = git_blame__alloc(reference->repository, reference->options, reference->path); - GIT_ERROR_CHECK_ALLOC(blame); - - /* Duplicate all of the hunk structures in the reference blame */ - git_vector_foreach(&reference->hunks, i, hunk) { - git_blame_hunk *h = dup_hunk(hunk); - GIT_ERROR_CHECK_ALLOC(h); - - git_vector_insert(&blame->hunks, h); - } - - /* Diff to the reference blob */ - git_diff_blob_to_buffer(reference->final_blob, blame->path, - buffer, buffer_len, blame->path, &diffopts, - NULL, NULL, buffer_hunk_cb, buffer_line_cb, blame); - - *out = blame; - return 0; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_blame_init_options(git_blame_options *opts, unsigned int version) -{ - return git_blame_options_init(opts, version); -} -#endif diff --git a/src/blame.h b/src/blame.h deleted file mode 100644 index 4141e2bb5..000000000 --- a/src/blame.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef INCLUDE_blame_h__ -#define INCLUDE_blame_h__ - -#include "common.h" - -#include "git2/blame.h" -#include "vector.h" -#include "diff.h" -#include "array.h" -#include "git2/oid.h" - -/* - * One blob in a commit that is being suspected - */ -typedef struct git_blame__origin { - int refcnt; - struct git_blame__origin *previous; - git_commit *commit; - git_blob *blob; - char path[GIT_FLEX_ARRAY]; -} git_blame__origin; - -/* - * Each group of lines is described by a git_blame__entry; it can be split - * as we pass blame to the parents. They form a linked list in the - * scoreboard structure, sorted by the target line number. - */ -typedef struct git_blame__entry { - struct git_blame__entry *prev; - struct git_blame__entry *next; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - size_t lno; - - /* how many lines this group has */ - size_t num_lines; - - /* the commit that introduced this group into the final image */ - git_blame__origin *suspect; - - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - bool guilty; - - /* true if the entry has been scanned for copies in the current parent - */ - bool scanned; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - size_t s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over. - */ - unsigned score; - - /* Whether this entry has been tracked to a boundary commit. - */ - bool is_boundary; -} git_blame__entry; - -struct git_blame { - char *path; - git_repository *repository; - git_mailmap *mailmap; - git_blame_options options; - - git_vector hunks; - git_vector paths; - - git_blob *final_blob; - git_array_t(size_t) line_index; - - size_t current_diff_line; - git_blame_hunk *current_hunk; - - /* Scoreboard fields */ - git_commit *final; - git_blame__entry *ent; - int num_lines; - const char *final_buf; - size_t final_buf_size; -}; - -git_blame *git_blame__alloc( - git_repository *repo, - git_blame_options opts, - const char *path); - -#endif diff --git a/src/blame_git.c b/src/blame_git.c deleted file mode 100644 index 2504b338a..000000000 --- a/src/blame_git.c +++ /dev/null @@ -1,685 +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 "blame_git.h" - -#include "commit.h" -#include "blob.h" -#include "xdiff/xinclude.h" -#include "diff_xdiff.h" - -/* - * Origin is refcounted and usually we keep the blob contents to be - * reused. - */ -static git_blame__origin *origin_incref(git_blame__origin *o) -{ - if (o) - o->refcnt++; - return o; -} - -static void origin_decref(git_blame__origin *o) -{ - if (o && --o->refcnt <= 0) { - if (o->previous) - origin_decref(o->previous); - git_blob_free(o->blob); - git_commit_free(o->commit); - git__free(o); - } -} - -/* Given a commit and a path in it, create a new origin structure. */ -static int make_origin(git_blame__origin **out, git_commit *commit, const char *path) -{ - git_blame__origin *o; - git_object *blob; - size_t path_len = strlen(path), alloc_len; - int error = 0; - - if ((error = git_object_lookup_bypath(&blob, (git_object*)commit, - path, GIT_OBJECT_BLOB)) < 0) - return error; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - o = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(o); - - o->commit = commit; - o->blob = (git_blob *) blob; - o->refcnt = 1; - strcpy(o->path, path); - - *out = o; - - return 0; -} - -/* Locate an existing origin or create a new one. */ -int git_blame__get_origin( - git_blame__origin **out, - git_blame *blame, - git_commit *commit, - const char *path) -{ - git_blame__entry *e; - - for (e = blame->ent; e; e = e->next) { - if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) { - *out = origin_incref(e->suspect); - } - } - return make_origin(out, commit, path); -} - -typedef struct blame_chunk_cb_data { - git_blame *blame; - git_blame__origin *target; - git_blame__origin *parent; - long tlno; - long plno; -}blame_chunk_cb_data; - -static bool same_suspect(git_blame__origin *a, git_blame__origin *b) -{ - if (a == b) - return true; - if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit))) - return false; - return 0 == strcmp(a->path, b->path); -} - -/* find the line number of the last line the target is suspected for */ -static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target) -{ - git_blame__entry *e; - size_t last_in_target = 0; - bool found = false; - - *out = 0; - - for (e=blame->ent; e; e=e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (last_in_target < e->s_lno + e->num_lines) { - found = true; - last_in_target = e->s_lno + e->num_lines; - } - } - - *out = last_in_target; - return found; -} - -/* - * It is known that lines between tlno to same came from parent, and e - * has an overlap with that range. it also is known that parent's - * line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> (entirely within) - * <------------> (extends past) - * <------------> (starts before) - * <------------------> (entirely encloses) - * - * Split e into potentially three parts; before this chunk, the chunk - * to be blamed for the parent, and after that portion. - */ -static void split_overlap(git_blame__entry *split, git_blame__entry *e, - size_t tlno, size_t plno, size_t same, git_blame__origin *parent) -{ - size_t chunk_end_lno; - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on the parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } else { - chunk_end_lno = e->lno + e->num_lines; - } - split[1].num_lines = chunk_end_lno - split[1].lno; - - /* - * if it turns out there is nothing to blame the parent for, forget about - * the splitting. !split[1].suspect signals this. - */ - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -/* - * Link in a new blame entry to the scoreboard. Entries that cover the same - * line range have been removed from the scoreboard previously. - */ -static void add_blame_entry(git_blame *blame, git_blame__entry *e) -{ - git_blame__entry *ent, *prev = NULL; - - origin_incref(e->suspect); - - for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - e->prev = prev; - if (prev) { - e->next = prev->next; - prev->next = e; - } else { - e->next = blame->ent; - blame->ent = e; - } - if (e->next) - e->next->prev = e; -} - -/* - * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that is already on the linked list of the scoreboard. - * The origin of dst loses a refcnt while the origin of src gains one. - */ -static void dup_entry(git_blame__entry *dst, git_blame__entry *src) -{ - git_blame__entry *p, *n; - - p = dst->prev; - n = dst->next; - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->prev = p; - dst->next = n; - dst->score = 0; -} - -/* - * 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 int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) -{ - git_blame__entry *new_entry; - - if (split[0].suspect && split[2].suspect) { - /* The first part (reuse storage for the existing entry e */ - dup_entry(e, &split[0]); - - /* 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) { - /* - * The parent covers the entire area; reuse storage for e and replace it - * with the parent - */ - dup_entry(e, &split[1]); - } else if (split[0].suspect) { - /* 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; -} - -/* - * After splitting the blame, the origins used by the on-stack blame_entry - * should lose one refcnt each. - */ -static void decref_split(git_blame__entry *split) -{ - int i; - for (i=0; i<3; i++) - origin_decref(split[i].suspect); -} - -/* - * Helper for blame_chunk(). blame_entry e is known to overlap with the patch - * hunk; split it and pass blame to the parent. - */ -static int blame_overlap( - git_blame *blame, - git_blame__entry *e, - size_t tlno, - size_t plno, - size_t same, - git_blame__origin *parent) -{ - git_blame__entry split[3] = {{0}}; - - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - if (split_blame(blame, split, e) < 0) - return -1; - decref_split(split); - - return 0; -} - -/* - * Process one hunk from the patch between the current suspect for blame_entry - * e and its parent. Find and split the overlap, and pass blame to the - * overlapping part to the parent. - */ -static int blame_chunk( - git_blame *blame, - size_t tlno, - size_t plno, - size_t same, - git_blame__origin *target, - git_blame__origin *parent) -{ - git_blame__entry *e; - - for (e = blame->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) { - if (blame_overlap(blame, e, tlno, plno, same, parent) < 0) - return -1; - } - } - - return 0; -} - -static int my_emit( - long start_a, long count_a, - long start_b, long count_b, - void *cb_data) -{ - blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data; - - 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; - - return 0; -} - -static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx) -{ - const int blk = 1024; - long trimmed = 0, recovered = 0; - char *ap = a->ptr + a->size; - char *bp = b->ptr + b->size; - long smaller = (long)((a->size < b->size) ? a->size : b->size); - - if (ctx) - return; - - while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) { - trimmed += blk; - ap -= blk; - bp -= blk; - } - - while (recovered < trimmed) - if (ap[recovered++] == '\n') - break; - a->size -= trimmed - recovered; - b->size -= trimmed - recovered; -} - -static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data, git_blame_options *options) -{ - xdemitconf_t xecfg = {0}; - xdemitcb_t ecb = {0}; - xpparam_t xpp = {0}; - - if (options->flags & GIT_BLAME_IGNORE_WHITESPACE) - xpp.flags |= XDF_IGNORE_WHITESPACE; - - xecfg.hunk_func = my_emit; - ecb.priv = cb_data; - - trim_common_tail(&file_a, &file_b, 0); - - if (file_a.size > GIT_XDIFF_MAX_SIZE || - file_b.size > GIT_XDIFF_MAX_SIZE) { - git_error_set(GIT_ERROR_INVALID, "file too large to blame"); - return -1; - } - - return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb); -} - -static void fill_origin_blob(git_blame__origin *o, mmfile_t *file) -{ - memset(file, 0, sizeof(*file)); - if (o->blob) { - file->ptr = (char*)git_blob_rawcontent(o->blob); - file->size = (long)git_blob_rawsize(o->blob); - } -} - -static int pass_blame_to_parent( - git_blame *blame, - git_blame__origin *target, - git_blame__origin *parent) -{ - size_t last_in_target; - mmfile_t file_p, file_o; - blame_chunk_cb_data d = { blame, target, parent, 0, 0 }; - - if (!find_last_in_target(&last_in_target, blame, target)) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - fill_origin_blob(target, &file_o); - - if (diff_hunks(file_p, file_o, &d, &blame->options) < 0) - return -1; - - /* The reset (i.e. anything after tlno) are the same as the parent */ - if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0) - return -1; - - return 0; -} - -static int paths_on_dup(void **old, void *new) -{ - GIT_UNUSED(old); - git__free(new); - return -1; -} - -static git_blame__origin *find_origin( - git_blame *blame, - git_commit *parent, - git_blame__origin *origin) -{ - git_blame__origin *porigin = NULL; - git_diff *difflist = NULL; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_tree *otree=NULL, *ptree=NULL; - - /* Get the trees from this commit and its parent */ - if (0 != git_commit_tree(&otree, origin->commit) || - 0 != git_commit_tree(&ptree, parent)) - goto cleanup; - - /* Configure the diff */ - diffopts.context_lines = 0; - diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK; - - /* Check to see if files we're interested have changed */ - diffopts.pathspec.count = blame->paths.length; - diffopts.pathspec.strings = (char**)blame->paths.contents; - if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) - goto cleanup; - - if (!git_diff_num_deltas(difflist)) { - /* No changes; copy data */ - git_blame__get_origin(&porigin, blame, parent, origin->path); - } else { - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - int i; - - /* Generate a full diff between the two trees */ - git_diff_free(difflist); - diffopts.pathspec.count = 0; - if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) - goto cleanup; - - /* Let diff find renames */ - findopts.flags = GIT_DIFF_FIND_RENAMES; - if (0 != git_diff_find_similar(difflist, &findopts)) - goto cleanup; - - /* Find one that matches */ - for (i=0; i<(int)git_diff_num_deltas(difflist); i++) { - const git_diff_delta *delta = git_diff_get_delta(difflist, i); - - if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path)) - { - git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path), - paths_on_dup); - make_origin(&porigin, parent, delta->old_file.path); - } - } - } - -cleanup: - git_diff_free(difflist); - git_tree_free(otree); - git_tree_free(ptree); - return porigin; -} - -/* - * The blobs of origin and porigin exactly match, so everything origin is - * suspected for can be blamed on the parent. - */ -static int pass_whole_blame(git_blame *blame, - git_blame__origin *origin, git_blame__origin *porigin) -{ - git_blame__entry *e; - - if (!porigin->blob && - git_object_lookup((git_object**)&porigin->blob, blame->repository, - git_blob_id(origin->blob), GIT_OBJECT_BLOB) < 0) - return -1; - for (e=blame->ent; e; e=e->next) { - if (!same_suspect(e->suspect, origin)) - continue; - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } - - return 0; -} - -static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt) -{ - git_commit *commit = origin->commit; - int i, num_parents; - git_blame__origin *sg_buf[16]; - git_blame__origin *porigin, **sg_origin = sg_buf; - int ret, error = 0; - - num_parents = git_commit_parentcount(commit); - if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit)) - /* Stop at oldest specified commit */ - num_parents = 0; - else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1) - /* Limit search to the first parent */ - num_parents = 1; - - if (!num_parents) { - git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit)); - goto finish; - } else if (num_parents < (int)ARRAY_SIZE(sg_buf)) - memset(sg_buf, 0, sizeof(sg_buf)); - else { - sg_origin = git__calloc(num_parents, sizeof(*sg_origin)); - GIT_ERROR_CHECK_ALLOC(sg_origin); - } - - for (i=0; icommit, i)) < 0) - goto finish; - porigin = find_origin(blame, p, origin); - - if (!porigin) { - /* - * We only have to decrement the parent's - * reference count when no porigin has - * been created, as otherwise the commit - * is assigned to the created object. - */ - git_commit_free(p); - continue; - } - if (porigin->blob && origin->blob && - !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) { - error = pass_whole_blame(blame, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; jblob), git_blob_id(porigin->blob))) { - same = 1; - break; - } - if (!same) - sg_origin[i] = porigin; - else - origin_decref(porigin); - } - - /* Standard blame */ - for (i=0; iprevious) { - origin_incref(porigin); - origin->previous = porigin; - } - - if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) { - if (ret < 0) - error = -1; - - goto finish; - } - } - - /* TODO: optionally find moves in parents' files */ - - /* TODO: optionally find copies in parents' files */ - -finish: - for (i=0; i pair), - * merge them together. - */ -static void coalesce(git_blame *blame) -{ - git_blame__entry *ent, *next; - - for (ent=blame->ent; ent && (next = ent->next); ent = next) { - if (same_suspect(ent->suspect, next->suspect) && - ent->guilty == next->guilty && - ent->s_lno + ent->num_lines == next->s_lno) - { - ent->num_lines += next->num_lines; - ent->next = next->next; - if (ent->next) - ent->next->prev = ent; - origin_decref(next->suspect); - git__free(next); - ent->score = 0; - next = ent; /* again */ - } - } -} - -int git_blame__like_git(git_blame *blame, uint32_t opt) -{ - int error = 0; - - while (true) { - git_blame__entry *ent; - git_blame__origin *suspect = NULL; - - /* Find a suspect to break down */ - for (ent = blame->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - break; - - /* We'll use this suspect later in the loop, so hold on to it for now. */ - origin_incref(suspect); - - if ((error = pass_blame(blame, suspect, opt)) < 0) - break; - - /* Take responsibility for the remaining entries */ - for (ent = blame->ent; ent; ent = ent->next) { - if (same_suspect(ent->suspect, suspect)) { - ent->guilty = true; - ent->is_boundary = !git_oid_cmp( - git_commit_id(suspect->commit), - &blame->options.oldest_commit); - } - } - origin_decref(suspect); - } - - if (!error) - coalesce(blame); - - return error; -} - -void git_blame__free_entry(git_blame__entry *ent) -{ - if (!ent) return; - origin_decref(ent->suspect); - git__free(ent); -} diff --git a/src/blame_git.h b/src/blame_git.h deleted file mode 100644 index 48b85a20d..000000000 --- a/src/blame_git.h +++ /dev/null @@ -1,22 +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_blame_git__ -#define INCLUDE_blame_git__ - -#include "common.h" - -#include "blame.h" - -int git_blame__get_origin( - git_blame__origin **out, - git_blame *sb, - git_commit *commit, - const char *path); -void git_blame__free_entry(git_blame__entry *ent); -int git_blame__like_git(git_blame *sb, uint32_t flags); - -#endif diff --git a/src/blob.c b/src/blob.c deleted file mode 100644 index 19ce8b3b5..000000000 --- a/src/blob.c +++ /dev/null @@ -1,528 +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 "blob.h" - -#include "git2/common.h" -#include "git2/object.h" -#include "git2/repository.h" -#include "git2/odb_backend.h" - -#include "buf.h" -#include "filebuf.h" -#include "filter.h" - -const void *git_blob_rawcontent(const git_blob *blob) -{ - GIT_ASSERT_ARG_WITH_RETVAL(blob, NULL); - - if (blob->raw) - return blob->data.raw.data; - else - return git_odb_object_data(blob->data.odb); -} - -git_object_size_t git_blob_rawsize(const git_blob *blob) -{ - GIT_ASSERT_ARG(blob); - - if (blob->raw) - return blob->data.raw.size; - else - return (git_object_size_t)git_odb_object_size(blob->data.odb); -} - -int git_blob__getbuf(git_str *buffer, git_blob *blob) -{ - git_object_size_t size = git_blob_rawsize(blob); - - GIT_ERROR_CHECK_BLOBSIZE(size); - return git_str_set(buffer, git_blob_rawcontent(blob), (size_t)size); -} - -void git_blob__free(void *_blob) -{ - git_blob *blob = (git_blob *) _blob; - if (!blob->raw) - git_odb_object_free(blob->data.odb); - git__free(blob); -} - -int git_blob__parse_raw(void *_blob, const char *data, size_t size) -{ - git_blob *blob = (git_blob *) _blob; - - GIT_ASSERT_ARG(blob); - - blob->raw = 1; - blob->data.raw.data = data; - blob->data.raw.size = size; - return 0; -} - -int git_blob__parse(void *_blob, git_odb_object *odb_obj) -{ - git_blob *blob = (git_blob *) _blob; - - GIT_ASSERT_ARG(blob); - - git_cached_obj_incref((git_cached_obj *)odb_obj); - blob->raw = 0; - blob->data.odb = odb_obj; - return 0; -} - -int git_blob_create_from_buffer( - git_oid *id, git_repository *repo, const void *buffer, size_t len) -{ - int error; - git_odb *odb; - git_odb_stream *stream; - - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(repo); - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJECT_BLOB)) < 0) - return error; - - if ((error = git_odb_stream_write(stream, buffer, len)) == 0) - error = git_odb_stream_finalize_write(id, stream); - - git_odb_stream_free(stream); - return error; -} - -static int write_file_stream( - 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_object_size_t written = 0; - - if ((error = git_odb_open_wstream( - &stream, odb, file_size, GIT_OBJECT_BLOB)) < 0) - return error; - - if ((fd = git_futils_open_ro(path)) < 0) { - git_odb_stream_free(stream); - return -1; - } - - while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - error = git_odb_stream_write(stream, buffer, read_len); - written += read_len; - } - - p_close(fd); - - if (written != file_size || read_len < 0) { - git_error_set(GIT_ERROR_OS, "failed to read file into stream"); - error = -1; - } - - if (!error) - error = git_odb_stream_finalize_write(id, stream); - - git_odb_stream_free(stream); - return error; -} - -static int write_file_filtered( - git_oid *id, - git_object_size_t *size, - git_odb *odb, - const char *full_path, - git_filter_list *fl, - git_repository* repo) -{ - int error; - git_str tgt = GIT_STR_INIT; - - error = git_filter_list__apply_to_file(&tgt, fl, repo, full_path); - - /* Write the file to disk if it was properly filtered */ - if (!error) { - *size = tgt.size; - - error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB); - } - - git_str_dispose(&tgt); - return error; -} - -static int write_symlink( - git_oid *id, git_odb *odb, const char *path, size_t link_size) -{ - char *link_data; - ssize_t read_len; - int error; - - link_data = git__malloc(link_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(path, link_data, link_size); - if (read_len != (ssize_t)link_size) { - git_error_set(GIT_ERROR_OS, "failed to create blob: cannot read symlink '%s'", path); - git__free(link_data); - return -1; - } - - error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB); - git__free(link_data); - return error; -} - -int git_blob__create_from_paths( - git_oid *id, - struct stat *out_st, - git_repository *repo, - const char *content_path, - const char *hint_path, - mode_t hint_mode, - bool try_load_filters) -{ - int error; - struct stat st; - git_odb *odb = NULL; - git_object_size_t size; - mode_t mode; - git_str path = GIT_STR_INIT; - - GIT_ASSERT_ARG(hint_path || !try_load_filters); - - if (!content_path) { - if (git_repository_workdir_path(&path, repo, hint_path) < 0) - return -1; - - content_path = path.ptr; - } - - if ((error = git_fs_path_lstat(content_path, &st)) < 0 || - (error = git_repository_odb(&odb, repo)) < 0) - goto done; - - if (S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_ODB, "cannot create blob from '%s': it is a directory", content_path); - error = GIT_EDIRECTORY; - goto done; - } - - if (out_st) - memcpy(out_st, &st, sizeof(st)); - - size = st.st_size; - mode = hint_mode ? hint_mode : st.st_mode; - - if (S_ISLNK(mode)) { - error = write_symlink(id, odb, content_path, (size_t)size); - } else { - git_filter_list *fl = NULL; - - if (try_load_filters) - /* Load the filters for writing this file to the ODB */ - error = git_filter_list_load( - &fl, repo, NULL, hint_path, - GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); - - if (error < 0) - /* well, that didn't work */; - else if (fl == NULL) - /* No filters need to be applied to the document: we can stream - * directly from disk */ - error = write_file_stream(id, odb, content_path, size); - else { - /* We need to apply one or more filters */ - error = write_file_filtered(id, &size, odb, content_path, fl, repo); - - git_filter_list_free(fl); - } - - /* - * TODO: eventually support streaming filtered files, for files - * which are bigger than a given threshold. This is not a priority - * because applying a filter in streaming mode changes the final - * size of the blob, and without knowing its final size, the blob - * cannot be written in stream mode to the ODB. - * - * The plan is to do streaming writes to a tempfile on disk and then - * opening streaming that file to the ODB, using - * `write_file_stream`. - * - * CAREFULLY DESIGNED APIS YO - */ - } - -done: - git_odb_free(odb); - git_str_dispose(&path); - - return error; -} - -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_from_disk( - git_oid *id, git_repository *repo, const char *path) -{ - int error; - git_str full_path = GIT_STR_INIT; - const char *workdir, *hintpath = NULL; - - if ((error = git_fs_path_prettify(&full_path, path, NULL)) < 0) { - git_str_dispose(&full_path); - return error; - } - - workdir = git_repository_workdir(repo); - - if (workdir && !git__prefixcmp(full_path.ptr, workdir)) - hintpath = full_path.ptr + strlen(workdir); - - error = git_blob__create_from_paths( - id, NULL, repo, git_str_cstr(&full_path), hintpath, 0, !!hintpath); - - git_str_dispose(&full_path); - return error; -} - -typedef struct { - git_writestream parent; - git_filebuf fbuf; - git_repository *repo; - char *hintpath; -} blob_writestream; - -static int blob_writestream_close(git_writestream *_stream) -{ - blob_writestream *stream = (blob_writestream *) _stream; - - git_filebuf_cleanup(&stream->fbuf); - return 0; -} - -static void blob_writestream_free(git_writestream *_stream) -{ - blob_writestream *stream = (blob_writestream *) _stream; - - git_filebuf_cleanup(&stream->fbuf); - git__free(stream->hintpath); - git__free(stream); -} - -static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len) -{ - blob_writestream *stream = (blob_writestream *) _stream; - - return git_filebuf_write(&stream->fbuf, buffer, len); -} - -int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath) -{ - int error; - git_str path = GIT_STR_INIT; - blob_writestream *stream; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - stream = git__calloc(1, sizeof(blob_writestream)); - GIT_ERROR_CHECK_ALLOC(stream); - - if (hintpath) { - stream->hintpath = git__strdup(hintpath); - GIT_ERROR_CHECK_ALLOC(stream->hintpath); - } - - stream->repo = repo; - stream->parent.write = blob_writestream_write; - stream->parent.close = blob_writestream_close; - stream->parent.free = blob_writestream_free; - - if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 - || (error = git_str_joinpath(&path, path.ptr, "streamed")) < 0) - goto cleanup; - - if ((error = git_filebuf_open_withsize(&stream->fbuf, git_str_cstr(&path), GIT_FILEBUF_TEMPORARY, - 0666, 2 * 1024 * 1024)) < 0) - goto cleanup; - - *out = (git_writestream *) stream; - -cleanup: - if (error < 0) - blob_writestream_free((git_writestream *) stream); - - git_str_dispose(&path); - return error; -} - -int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream) -{ - int error; - blob_writestream *stream = (blob_writestream *) _stream; - - /* - * We can make this more officient by avoiding writing to - * disk, but for now let's re-use the helper functions we - * have. - */ - if ((error = git_filebuf_flush(&stream->fbuf)) < 0) - goto cleanup; - - error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock, - stream->hintpath, 0, !!stream->hintpath); - -cleanup: - blob_writestream_free(_stream); - return error; - -} - -int git_blob_is_binary(const git_blob *blob) -{ - git_str content = GIT_STR_INIT; - git_object_size_t size; - - GIT_ASSERT_ARG(blob); - - size = git_blob_rawsize(blob); - - git_str_attach_notowned(&content, git_blob_rawcontent(blob), - (size_t)min(size, GIT_FILTER_BYTES_TO_CHECK_NUL)); - return git_str_is_binary(&content); -} - -int git_blob_data_is_binary(const char *str, size_t len) -{ - git_str content = GIT_STR_INIT; - - git_str_attach_notowned(&content, str, len); - - return git_str_is_binary(&content); -} - -int git_blob_filter_options_init( - git_blob_filter_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, - git_blob_filter_options, GIT_BLOB_FILTER_OPTIONS_INIT); - return 0; -} - -int git_blob_filter( - git_buf *out, - git_blob *blob, - const char *path, - git_blob_filter_options *given_opts) -{ - git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; - git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; - git_filter_list *fl = NULL; - int error = 0; - - GIT_ASSERT_ARG(blob); - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(out); - - 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) - filter_opts.flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES; - - if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD) != 0) - filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD; - - if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { - filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_COMMIT; - -#ifndef GIT_DEPRECATE_HARD - if (opts.commit_id) - git_oid_cpy(&filter_opts.attr_commit_id, opts.commit_id); - else -#endif - git_oid_cpy(&filter_opts.attr_commit_id, &opts.attr_commit_id); - } - - if (!(error = git_filter_list_load_ext( - &fl, git_blob_owner(blob), blob, path, - GIT_FILTER_TO_WORKTREE, &filter_opts))) { - - error = git_filter_list_apply_to_blob(out, fl, blob); - - git_filter_list_free(fl); - } - - return error; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD -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); -} -#endif diff --git a/src/blob.h b/src/blob.h deleted file mode 100644 index 9a5dda225..000000000 --- a/src/blob.h +++ /dev/null @@ -1,52 +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_blob_h__ -#define INCLUDE_blob_h__ - -#include "common.h" - -#include "git2/blob.h" -#include "repository.h" -#include "odb.h" -#include "futils.h" - -struct git_blob { - git_object object; - - union { - git_odb_object *odb; - struct { - const char *data; - git_object_size_t size; - } raw; - } data; - unsigned int raw:1; -}; - -#define GIT_ERROR_CHECK_BLOBSIZE(n) \ - do { \ - if (!git__is_sizet(n)) { \ - git_error_set(GIT_ERROR_NOMEMORY, "blob contents too large to fit in memory"); \ - return -1; \ - } \ - } while(0) - -void git_blob__free(void *blob); -int git_blob__parse(void *blob, git_odb_object *obj); -int git_blob__parse_raw(void *blob, const char *data, size_t size); -int git_blob__getbuf(git_str *buffer, git_blob *blob); - -extern int git_blob__create_from_paths( - git_oid *out_oid, - struct stat *out_st, - git_repository *repo, - const char *full_path, - const char *hint_path, - mode_t hint_mode, - bool apply_filters); - -#endif diff --git a/src/branch.c b/src/branch.c deleted file mode 100644 index 2e29af99d..000000000 --- a/src/branch.c +++ /dev/null @@ -1,818 +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 "branch.h" - -#include "buf.h" -#include "commit.h" -#include "tag.h" -#include "config.h" -#include "refspec.h" -#include "refs.h" -#include "remote.h" -#include "annotated_commit.h" -#include "worktree.h" - -#include "git2/branch.h" - -static int retrieve_branch_reference( - git_reference **branch_reference_out, - git_repository *repo, - const char *branch_name, - bool is_remote) -{ - git_reference *branch = NULL; - int error = 0; - char *prefix; - git_str ref_name = GIT_STR_INIT; - - prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; - - if ((error = git_str_joinpath(&ref_name, prefix, branch_name)) < 0) - /* OOM */; - else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) - git_error_set( - GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'", - is_remote ? "remote-tracking" : "local", branch_name); - - *branch_reference_out = branch; /* will be NULL on error */ - - git_str_dispose(&ref_name); - return error; -} - -static int not_a_local_branch(const char *reference_name) -{ - git_error_set( - GIT_ERROR_INVALID, - "reference '%s' is not a local branch.", reference_name); - return -1; -} - -static int create_branch( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_commit *commit, - const char *from, - int force) -{ - int is_unmovable_head = 0; - git_reference *branch = NULL; - git_str canonical_branch_name = GIT_STR_INIT, - log_message = GIT_STR_INIT; - int error = -1; - int bare = git_repository_is_bare(repository); - - GIT_ASSERT_ARG(branch_name); - GIT_ASSERT_ARG(commit); - GIT_ASSERT_ARG(ref_out); - GIT_ASSERT_ARG(git_commit_owner(commit) == repository); - - if (!git__strcmp(branch_name, "HEAD")) { - git_error_set(GIT_ERROR_REFERENCE, "'HEAD' is not a valid branch name"); - error = -1; - goto cleanup; - } - - if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { - error = git_branch_is_head(branch); - git_reference_free(branch); - branch = NULL; - - if (error < 0) - goto cleanup; - - is_unmovable_head = error; - } - - if (is_unmovable_head && force) { - git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is " - "the current HEAD of the repository.", branch_name); - error = -1; - goto cleanup; - } - - if (git_str_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) - goto cleanup; - - if (git_str_printf(&log_message, "branch: Created from %s", from) < 0) - goto cleanup; - - error = git_reference_create(&branch, repository, - git_str_cstr(&canonical_branch_name), git_commit_id(commit), force, - git_str_cstr(&log_message)); - - if (!error) - *ref_out = branch; - -cleanup: - git_str_dispose(&canonical_branch_name); - git_str_dispose(&log_message); - return error; -} - -int git_branch_create( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_commit *commit, - int force) -{ - char commit_id[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(commit_id, GIT_OID_HEXSZ + 1, git_commit_id(commit)); - return create_branch(ref_out, repository, branch_name, commit, commit_id, force); -} - -int git_branch_create_from_annotated( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_annotated_commit *commit, - int force) -{ - return create_branch(ref_out, - repository, branch_name, commit->commit, commit->description, force); -} - -static int branch_is_checked_out(git_repository *worktree, void *payload) -{ - git_reference *branch = (git_reference *) payload; - git_reference *head = NULL; - int error; - - if (git_repository_is_bare(worktree)) - return 0; - - if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - goto out; - } - - if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC) - goto out; - - error = !git__strcmp(head->target.symbolic, branch->name); - -out: - git_reference_free(head); - return error; -} - -int git_branch_is_checked_out(const git_reference *branch) -{ - GIT_ASSERT_ARG(branch); - - if (!git_reference_is_branch(branch)) - return 0; - return git_repository_foreach_worktree(git_reference_owner(branch), - branch_is_checked_out, (void *)branch) == 1; -} - -int git_branch_delete(git_reference *branch) -{ - int is_head; - git_str config_section = GIT_STR_INIT; - int error = -1; - - GIT_ASSERT_ARG(branch); - - if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) { - git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.", - git_reference_name(branch)); - return GIT_ENOTFOUND; - } - - if ((is_head = git_branch_is_head(branch)) < 0) - return is_head; - - if (is_head) { - git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is " - "the current HEAD of the repository.", git_reference_name(branch)); - return -1; - } - - if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) { - git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is " - "the current HEAD of a linked repository.", git_reference_name(branch)); - return -1; - } - - if (git_str_join(&config_section, '.', "branch", - git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto on_error; - - if (git_config_rename_section( - git_reference_owner(branch), git_str_cstr(&config_section), NULL) < 0) - goto on_error; - - error = git_reference_delete(branch); - -on_error: - git_str_dispose(&config_section); - return error; -} - -typedef struct { - git_reference_iterator *iter; - unsigned int flags; -} branch_iter; - -int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter) -{ - branch_iter *iter = (branch_iter *) _iter; - git_reference *ref; - int error; - - while ((error = git_reference_next(&ref, iter->iter)) == 0) { - if ((iter->flags & GIT_BRANCH_LOCAL) && - !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) { - *out = ref; - *out_type = GIT_BRANCH_LOCAL; - - return 0; - } else if ((iter->flags & GIT_BRANCH_REMOTE) && - !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { - *out = ref; - *out_type = GIT_BRANCH_REMOTE; - - return 0; - } else { - git_reference_free(ref); - } - } - - return error; -} - -int git_branch_iterator_new( - git_branch_iterator **out, - git_repository *repo, - git_branch_t list_flags) -{ - branch_iter *iter; - - iter = git__calloc(1, sizeof(branch_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->flags = list_flags; - - if (git_reference_iterator_new(&iter->iter, repo) < 0) { - git__free(iter); - return -1; - } - - *out = (git_branch_iterator *) iter; - - return 0; -} - -void git_branch_iterator_free(git_branch_iterator *_iter) -{ - branch_iter *iter = (branch_iter *) _iter; - - if (iter == NULL) - return; - - git_reference_iterator_free(iter->iter); - git__free(iter); -} - -int git_branch_move( - git_reference **out, - git_reference *branch, - const char *new_branch_name, - int force) -{ - git_str new_reference_name = GIT_STR_INIT, - old_config_section = GIT_STR_INIT, - new_config_section = GIT_STR_INIT, - log_message = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(branch); - GIT_ASSERT_ARG(new_branch_name); - - if (!git_reference_is_branch(branch)) - return not_a_local_branch(git_reference_name(branch)); - - if ((error = git_str_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) - goto done; - - if ((error = git_str_printf(&log_message, "branch: renamed %s to %s", - git_reference_name(branch), git_str_cstr(&new_reference_name))) < 0) - goto done; - - /* first update ref then config so failure won't trash config */ - - error = git_reference_rename( - out, branch, git_str_cstr(&new_reference_name), force, - git_str_cstr(&log_message)); - if (error < 0) - goto done; - - git_str_join(&old_config_section, '.', "branch", - git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)); - git_str_join(&new_config_section, '.', "branch", new_branch_name); - - error = git_config_rename_section( - git_reference_owner(branch), - git_str_cstr(&old_config_section), - git_str_cstr(&new_config_section)); - -done: - git_str_dispose(&new_reference_name); - git_str_dispose(&old_config_section); - git_str_dispose(&new_config_section); - git_str_dispose(&log_message); - - return error; -} - -int git_branch_lookup( - git_reference **ref_out, - git_repository *repo, - const char *branch_name, - git_branch_t branch_type) -{ - int error = -1; - - GIT_ASSERT_ARG(ref_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(branch_name); - - 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: - GIT_ASSERT(false); - } - return error; -} - -int git_branch_name( - const char **out, - const git_reference *ref) -{ - const char *branch_name; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - - branch_name = ref->name; - - if (git_reference_is_branch(ref)) { - branch_name += strlen(GIT_REFS_HEADS_DIR); - } else if (git_reference_is_remote(ref)) { - branch_name += strlen(GIT_REFS_REMOTES_DIR); - } else { - git_error_set(GIT_ERROR_INVALID, - "reference '%s' is neither a local nor a remote branch.", ref->name); - return -1; - } - *out = branch_name; - return 0; -} - -static int retrieve_upstream_configuration( - git_str *out, - const git_config *config, - const char *canonical_branch_name, - const char *format) -{ - git_str buf = GIT_STR_INIT; - int error; - - if (git_str_printf(&buf, format, - canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0) - return -1; - - error = git_config__get_string_buf(out, config, git_str_cstr(&buf)); - git_str_dispose(&buf); - return error; -} - -int git_branch_upstream_name( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_name, repo, refname); -} - -int git_branch__upstream_name( - git_str *out, - git_repository *repo, - const char *refname) -{ - git_str remote_name = GIT_STR_INIT; - git_str merge_name = GIT_STR_INIT; - git_str buf = GIT_STR_INIT; - int error = -1; - git_remote *remote = NULL; - const git_refspec *refspec; - git_config *config; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if (!git_reference__is_branch(refname)) - return not_a_local_branch(refname); - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - - if ((error = retrieve_upstream_configuration( - &remote_name, config, refname, "branch.%s.remote")) < 0) - goto cleanup; - - if ((error = retrieve_upstream_configuration( - &merge_name, config, refname, "branch.%s.merge")) < 0) - goto cleanup; - - if (git_str_len(&remote_name) == 0 || git_str_len(&merge_name) == 0) { - git_error_set(GIT_ERROR_REFERENCE, - "branch '%s' does not have an upstream", refname); - error = GIT_ENOTFOUND; - goto cleanup; - } - - if (strcmp(".", git_str_cstr(&remote_name)) != 0) { - if ((error = git_remote_lookup(&remote, repo, git_str_cstr(&remote_name))) < 0) - goto cleanup; - - refspec = git_remote__matching_refspec(remote, git_str_cstr(&merge_name)); - if (!refspec) { - error = GIT_ENOTFOUND; - goto cleanup; - } - - if (git_refspec__transform(&buf, refspec, git_str_cstr(&merge_name)) < 0) - goto cleanup; - } else - if (git_str_set(&buf, git_str_cstr(&merge_name), git_str_len(&merge_name)) < 0) - goto cleanup; - - git_str_swap(out, &buf); - -cleanup: - git_config_free(config); - git_remote_free(remote); - git_str_dispose(&remote_name); - git_str_dispose(&merge_name); - git_str_dispose(&buf); - return error; -} - -static int git_branch_upstream_with_format( - git_str *out, - git_repository *repo, - const char *refname, - const char *format, - const char *format_name) -{ - git_config *cfg; - int error; - - if (!git_reference__is_branch(refname)) - return not_a_local_branch(refname); - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || - (error = retrieve_upstream_configuration(out, cfg, refname, format)) < 0) - return error; - - if (git_str_len(out) == 0) { - git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream %s", refname, format_name); - error = GIT_ENOTFOUND; - } - - return error; -} - -int git_branch_upstream_remote( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_remote, repo, refname); -} - -int git_branch__upstream_remote( - git_str *out, - git_repository *repo, - const char *refname) -{ - return git_branch_upstream_with_format(out, repo, refname, "branch.%s.remote", "remote"); -} - -int git_branch_upstream_merge( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_merge, repo, refname); -} - -int git_branch__upstream_merge( - git_str *out, - git_repository *repo, - const char *refname) -{ - return git_branch_upstream_with_format(out, repo, refname, "branch.%s.merge", "merge"); -} - -int git_branch_remote_name( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__remote_name, repo, refname); -} - -int git_branch__remote_name( - git_str *out, - git_repository *repo, - const char *refname) -{ - git_strarray remote_list = {0}; - size_t i; - git_remote *remote; - const git_refspec *fetchspec; - int error = 0; - char *remote_name = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - /* Verify that this is a remote branch */ - if (!git_reference__is_remote(refname)) { - git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.", - refname); - error = GIT_ERROR; - goto cleanup; - } - - /* Get the remotes */ - if ((error = git_remote_list(&remote_list, repo)) < 0) - goto cleanup; - - /* Find matching remotes */ - for (i = 0; i < remote_list.count; i++) { - if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0) - continue; - - fetchspec = git_remote__matching_dst_refspec(remote, refname); - if (fetchspec) { - /* If we have not already set out yet, then set - * it to the matching remote name. Otherwise - * multiple remotes match this reference, and it - * is ambiguous. */ - if (!remote_name) { - remote_name = remote_list.strings[i]; - } else { - git_remote_free(remote); - - git_error_set(GIT_ERROR_REFERENCE, - "reference '%s' is ambiguous", refname); - error = GIT_EAMBIGUOUS; - goto cleanup; - } - } - - git_remote_free(remote); - } - - if (remote_name) { - git_str_clear(out); - error = git_str_puts(out, remote_name); - } else { - git_error_set(GIT_ERROR_REFERENCE, - "could not determine remote for '%s'", refname); - error = GIT_ENOTFOUND; - } - -cleanup: - if (error < 0) - git_str_dispose(out); - - git_strarray_dispose(&remote_list); - return error; -} - -int git_branch_upstream( - git_reference **tracking_out, - const git_reference *branch) -{ - int error; - git_str tracking_name = GIT_STR_INIT; - - if ((error = git_branch__upstream_name(&tracking_name, - git_reference_owner(branch), git_reference_name(branch))) < 0) - return error; - - error = git_reference_lookup( - tracking_out, - git_reference_owner(branch), - git_str_cstr(&tracking_name)); - - git_str_dispose(&tracking_name); - return error; -} - -static int unset_upstream(git_config *config, const char *shortname) -{ - git_str buf = GIT_STR_INIT; - - if (git_str_printf(&buf, "branch.%s.remote", shortname) < 0) - return -1; - - if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) - goto on_error; - - git_str_clear(&buf); - if (git_str_printf(&buf, "branch.%s.merge", shortname) < 0) - goto on_error; - - if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) - goto on_error; - - git_str_dispose(&buf); - return 0; - -on_error: - git_str_dispose(&buf); - return -1; -} - -int git_branch_set_upstream(git_reference *branch, const char *branch_name) -{ - git_str key = GIT_STR_INIT, remote_name = GIT_STR_INIT, merge_refspec = GIT_STR_INIT; - git_reference *upstream; - git_repository *repo; - git_remote *remote = NULL; - git_config *config; - const char *refname, *shortname; - int local, error; - const git_refspec *fetchspec; - - 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 = refname + strlen(GIT_REFS_HEADS_DIR); - - /* 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 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, branch_name, GIT_BRANCH_REMOTE) == 0) - local = 0; - else { - git_error_set(GIT_ERROR_REFERENCE, - "cannot set upstream for branch '%s'", shortname); - return GIT_ENOTFOUND; - } - - /* - * 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_str_puts(&remote_name, "."); - else - error = git_branch__remote_name(&remote_name, repo, git_reference_name(upstream)); - - if (error < 0) - goto on_error; - - /* Update the upstream branch config with the new name */ - if (git_str_printf(&key, "branch.%s.remote", shortname) < 0) - goto on_error; - - if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&remote_name)) < 0) - goto on_error; - - if (local) { - /* A local branch uses the upstream refname directly */ - if (git_str_puts(&merge_refspec, git_reference_name(upstream)) < 0) - goto on_error; - } else { - /* We transform the upstream branch name according to the remote's refspecs */ - if (git_remote_lookup(&remote, repo, git_str_cstr(&remote_name)) < 0) - goto on_error; - - fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); - 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_str_clear(&key); - if (git_str_printf(&key, "branch.%s.merge", shortname) < 0) - goto on_error; - - if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&merge_refspec)) < 0) - goto on_error; - - git_reference_free(upstream); - git_str_dispose(&key); - git_str_dispose(&remote_name); - git_str_dispose(&merge_refspec); - - return 0; - -on_error: - git_reference_free(upstream); - git_str_dispose(&key); - git_str_dispose(&remote_name); - git_str_dispose(&merge_refspec); - git_remote_free(remote); - - return -1; -} - -int git_branch_is_head( - const git_reference *branch) -{ - git_reference *head; - bool is_same = false; - int error; - - GIT_ASSERT_ARG(branch); - - if (!git_reference_is_branch(branch)) - return false; - - error = git_repository_head(&head, git_reference_owner(branch)); - - if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) - return false; - - if (error < 0) - return -1; - - is_same = strcmp( - git_reference_name(branch), - git_reference_name(head)) == 0; - - git_reference_free(head); - - return is_same; -} - -int git_branch_name_is_valid(int *valid, const char *name) -{ - git_str ref_name = GIT_STR_INIT; - int error = 0; - - GIT_ASSERT(valid); - - *valid = 0; - - /* - * Discourage branch name starting with dash, - * https://github.com/git/git/commit/6348624010888b - * and discourage HEAD as branch name, - * https://github.com/git/git/commit/a625b092cc5994 - */ - if (!name || name[0] == '-' || !git__strcmp(name, "HEAD")) - goto done; - - if ((error = git_str_puts(&ref_name, GIT_REFS_HEADS_DIR)) < 0 || - (error = git_str_puts(&ref_name, name)) < 0) - goto done; - - error = git_reference_name_is_valid(valid, ref_name.ptr); - -done: - git_str_dispose(&ref_name); - return error; -} diff --git a/src/branch.h b/src/branch.h deleted file mode 100644 index b4db42a01..000000000 --- a/src/branch.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_branch_h__ -#define INCLUDE_branch_h__ - -#include "common.h" - -#include "str.h" - -int git_branch__remote_name( - git_str *out, - git_repository *repo, - const char *refname); -int git_branch__upstream_remote( - git_str *out, - git_repository *repo, - const char *refname); -int git_branch__upstream_merge( - git_str *out, - git_repository *repo, - const char *refname); -int git_branch__upstream_name( - git_str *tracking_name, - git_repository *repo, - const char *canonical_branch_name); - -#endif diff --git a/src/buf.c b/src/buf.c deleted file mode 100644 index 652f5dd52..000000000 --- a/src/buf.c +++ /dev/null @@ -1,126 +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 "buf.h" -#include "common.h" - -int git_buf_sanitize(git_buf *buf) -{ - GIT_ASSERT_ARG(buf); - - if (buf->reserved > 0) - buf->ptr[0] = '\0'; - else - buf->ptr = git_str__initstr; - - buf->size = 0; - return 0; -} - -int git_buf_tostr(git_str *out, git_buf *buf) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(buf); - - if (git_buf_sanitize(buf) < 0) - return -1; - - out->ptr = buf->ptr; - out->asize = buf->reserved; - out->size = buf->size; - - buf->ptr = git_str__initstr; - buf->reserved = 0; - buf->size = 0; - - return 0; -} - -int git_buf_fromstr(git_buf *out, git_str *str) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(str); - - out->ptr = str->ptr; - out->reserved = str->asize; - out->size = str->size; - - str->ptr = git_str__initstr; - str->asize = 0; - str->size = 0; - - return 0; -} - -void git_buf_dispose(git_buf *buf) -{ - if (!buf) - return; - - if (buf->ptr != git_str__initstr) - git__free(buf->ptr); - - buf->ptr = git_str__initstr; - buf->reserved = 0; - buf->size = 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_buf_grow(git_buf *buffer, size_t target_size) -{ - char *newptr; - - if (buffer->reserved >= target_size) - return 0; - - if (buffer->ptr == git_str__initstr) - newptr = git__malloc(target_size); - else - newptr = git__realloc(buffer->ptr, target_size); - - if (!newptr) - return -1; - - buffer->ptr = newptr; - buffer->reserved = target_size; - return 0; -} - -int git_buf_set(git_buf *buffer, const void *data, size_t datalen) -{ - size_t alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, datalen, 1); - - if (git_buf_grow(buffer, alloclen) < 0) - return -1; - - memmove(buffer->ptr, data, datalen); - buffer->size = datalen; - buffer->ptr[buffer->size] = '\0'; - - return 0; -} - -int git_buf_is_binary(const git_buf *buf) -{ - git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); - return git_str_is_binary(&str); -} - -int git_buf_contains_nul(const git_buf *buf) -{ - git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); - return git_str_contains_nul(&str); -} - -void git_buf_free(git_buf *buffer) -{ - git_buf_dispose(buffer); -} - -#endif diff --git a/src/buf.h b/src/buf.h deleted file mode 100644 index 4bc7f2709..000000000 --- a/src/buf.h +++ /dev/null @@ -1,50 +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_buf_h__ -#define INCLUDE_buf_h__ - -#include "git2/buffer.h" -#include "common.h" - -/* - * Adapts a private API that takes a `git_str` into a public API that - * takes a `git_buf`. - */ - -#define GIT_BUF_WRAP_PRIVATE(buf, fn, ...) \ - { \ - git_str str = GIT_STR_INIT; \ - int error; \ - if ((error = git_buf_tostr(&str, buf)) == 0 && \ - (error = fn(&str, __VA_ARGS__)) == 0) \ - error = git_buf_fromstr(buf, &str); \ - git_str_dispose(&str); \ - return error; \ -} - -/** - * "Sanitizes" a buffer from user input. This simply ensures that the - * `git_buf` has nice defaults if the user didn't set the members to - * anything, so that if we return early we don't leave it populated - * with nonsense. - */ -extern int git_buf_sanitize(git_buf *from_user); - -/** - * Populate a `git_str` from a `git_buf` for passing to libgit2 internal - * functions. Sanitizes the given `git_buf` before proceeding. The - * `git_buf` will no longer point to this memory. - */ -extern int git_buf_tostr(git_str *out, git_buf *buf); - -/** - * Populate a `git_buf` from a `git_str` for returning to a user. - * The `git_str` will no longer point to this memory. - */ -extern int git_buf_fromstr(git_buf *out, git_str *str); - -#endif diff --git a/src/cache.c b/src/cache.c deleted file mode 100644 index 2f68e357c..000000000 --- a/src/cache.c +++ /dev/null @@ -1,253 +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 "cache.h" - -#include "repository.h" -#include "commit.h" -#include "thread.h" -#include "util.h" -#include "odb.h" -#include "object.h" -#include "git2/oid.h" - -bool git_cache__enabled = true; -ssize_t git_cache__max_storage = (256 * 1024 * 1024); -git_atomic_ssize git_cache__current_storage = {0}; - -static size_t git_cache__max_object_size[8] = { - 0, /* GIT_OBJECT__EXT1 */ - 4096, /* GIT_OBJECT_COMMIT */ - 4096, /* GIT_OBJECT_TREE */ - 0, /* GIT_OBJECT_BLOB */ - 4096, /* GIT_OBJECT_TAG */ - 0, /* GIT_OBJECT__EXT2 */ - 0, /* GIT_OBJECT_OFS_DELTA */ - 0 /* GIT_OBJECT_REF_DELTA */ -}; - -int git_cache_set_max_object_size(git_object_t type, size_t size) -{ - if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) { - git_error_set(GIT_ERROR_INVALID, "type out of range"); - return -1; - } - - git_cache__max_object_size[type] = size; - return 0; -} - -int git_cache_init(git_cache *cache) -{ - memset(cache, 0, sizeof(*cache)); - - 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; -} - -/* called with lock */ -static void clear_cache(git_cache *cache) -{ - git_cached_obj *evict = NULL; - - if (git_cache_size(cache) == 0) - return; - - git_oidmap_foreach_value(cache->map, evict, { - git_cached_obj_decref(evict); - }); - - git_oidmap_clear(cache->map); - git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory); - cache->used_memory = 0; -} - -void git_cache_clear(git_cache *cache) -{ - if (git_rwlock_wrlock(&cache->lock) < 0) - return; - - clear_cache(cache); - - git_rwlock_wrunlock(&cache->lock); -} - -void git_cache_dispose(git_cache *cache) -{ - git_cache_clear(cache); - git_oidmap_free(cache->map); - git_rwlock_free(&cache->lock); - git__memzero(cache, sizeof(*cache)); -} - -/* Called with lock */ -static void cache_evict_entries(git_cache *cache) -{ - 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) { - git_cached_obj *evict; - const git_oid *key; - - if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER) - break; - - evict_count--; - evicted_memory += evict->size; - git_oidmap_delete(cache->map, key); - git_cached_obj_decref(evict); - } - - cache->used_memory -= evicted_memory; - git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory); -} - -static bool cache_should_store(git_object_t object_type, size_t object_size) -{ - size_t max_size = git_cache__max_object_size[object_type]; - return git_cache__enabled && object_size < max_size; -} - -static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) -{ - git_cached_obj *entry; - - if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) - return NULL; - - if ((entry = git_oidmap_get(cache->map, oid)) != NULL) { - if (flags && entry->flags != flags) { - entry = NULL; - } else { - git_cached_obj_incref(entry); - } - } - - git_rwlock_rdunlock(&cache->lock); - - return entry; -} - -static void *cache_store(git_cache *cache, git_cached_obj *entry) -{ - git_cached_obj *stored_entry; - - git_cached_obj_incref(entry); - - if (!git_cache__enabled && cache->used_memory > 0) { - git_cache_clear(cache); - return entry; - } - - if (!cache_should_store(entry->type, entry->size)) - return entry; - - if (git_rwlock_wrlock(&cache->lock) < 0) - return entry; - - /* soften the load on the cache */ - if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage) - cache_evict_entries(cache); - - /* not found */ - 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); - } - } - /* found */ - else { - 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) { - 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 */ - } - } - - git_rwlock_wrunlock(&cache->lock); - return entry; -} - -void *git_cache_store_raw(git_cache *cache, git_odb_object *entry) -{ - entry->cached.flags = GIT_CACHE_STORE_RAW; - return cache_store(cache, (git_cached_obj *)entry); -} - -void *git_cache_store_parsed(git_cache *cache, git_object *entry) -{ - entry->cached.flags = GIT_CACHE_STORE_PARSED; - return cache_store(cache, (git_cached_obj *)entry); -} - -git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid) -{ - return cache_get(cache, oid, GIT_CACHE_STORE_RAW); -} - -git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid) -{ - return cache_get(cache, oid, GIT_CACHE_STORE_PARSED); -} - -void *git_cache_get_any(git_cache *cache, const git_oid *oid) -{ - return cache_get(cache, oid, GIT_CACHE_STORE_ANY); -} - -void git_cached_obj_decref(void *_obj) -{ - git_cached_obj *obj = _obj; - - if (git_atomic32_dec(&obj->refcount) == 0) { - switch (obj->flags) { - case GIT_CACHE_STORE_RAW: - git_odb_object__free(_obj); - break; - - case GIT_CACHE_STORE_PARSED: - git_object__free(_obj); - break; - - default: - git__free(_obj); - break; - } - } -} diff --git a/src/cache.h b/src/cache.h deleted file mode 100644 index 42c4fa80d..000000000 --- a/src/cache.h +++ /dev/null @@ -1,69 +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_cache_h__ -#define INCLUDE_cache_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.h" - -#include "thread.h" -#include "oidmap.h" - -enum { - GIT_CACHE_STORE_ANY = 0, - GIT_CACHE_STORE_RAW = 1, - GIT_CACHE_STORE_PARSED = 2 -}; - -typedef struct { - git_oid oid; - int16_t type; /* git_object_t value */ - uint16_t flags; /* GIT_CACHE_STORE value */ - size_t size; - git_atomic32 refcount; -} git_cached_obj; - -typedef struct { - git_oidmap *map; - git_rwlock lock; - ssize_t used_memory; -} git_cache; - -extern bool git_cache__enabled; -extern ssize_t git_cache__max_storage; -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_dispose(git_cache *cache); -void git_cache_clear(git_cache *cache); - -void *git_cache_store_raw(git_cache *cache, git_odb_object *entry); -void *git_cache_store_parsed(git_cache *cache, git_object *entry); - -git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid); -git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid); -void *git_cache_get_any(git_cache *cache, const git_oid *oid); - -GIT_INLINE(size_t) git_cache_size(git_cache *cache) -{ - return (size_t)git_oidmap_size(cache->map); -} - -GIT_INLINE(void) git_cached_obj_incref(void *_obj) -{ - git_cached_obj *obj = _obj; - git_atomic32_inc(&obj->refcount); -} - -void git_cached_obj_decref(void *_obj); - -#endif diff --git a/src/cc-compat.h b/src/cc-compat.h deleted file mode 100644 index a0971e86c..000000000 --- a/src/cc-compat.h +++ /dev/null @@ -1,106 +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_cc_compat_h__ -#define INCLUDE_cc_compat_h__ - -#include - -/* - * See if our compiler is known to support flexible array members. - */ -#ifndef GIT_FLEX_ARRAY -# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_FLEX_ARRAY /* empty */ -# elif defined(__GNUC__) -# if (__GNUC__ >= 3) -# define GIT_FLEX_ARRAY /* empty */ -# else -# define GIT_FLEX_ARRAY 0 /* older GNU extension */ -# endif -# endif - -/* Default to safer but a bit wasteful traditional style */ -# ifndef GIT_FLEX_ARRAY -# define GIT_FLEX_ARRAY 1 -# endif -#endif - -#if defined(__GNUC__) -# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size))) -#elif defined(_MSC_VER) -# define GIT_ALIGN(x,size) __declspec(align(size)) x -#else -# define GIT_ALIGN(x,size) x -#endif - -#if defined(__GNUC__) -# define GIT_UNUSED(x) \ - do { \ - __typeof__(x) _unused __attribute__((unused)); \ - _unused = (x); \ - } while (0) -#else -# define GIT_UNUSED(x) ((void)(x)) -#endif - -/* Define the printf format specifier 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" -# define PRIxZ "I64x" -# define PRIXZ "I64X" -# define PRIdZ "I64d" -# else -# define PRIuZ "Iu" -# define PRIxZ "Ix" -# define PRIXZ "IX" -# define PRIdZ "Id" -# endif - -#else -# define PRIuZ "zu" -# define PRIxZ "zx" -# define PRIXZ "zX" -# define PRIdZ "zd" -#endif - -/* Microsoft Visual C/C++ */ -#if defined(_MSC_VER) -/* disable "deprecated function" warnings */ -# pragma warning ( disable : 4996 ) -/* disable "conditional expression is constant" level 4 warnings */ -# pragma warning ( disable : 4127 ) -#endif - -#if defined (_MSC_VER) - typedef unsigned char bool; -# ifndef true -# define true 1 -# endif -# ifndef false -# define false 0 -# endif -#else -# include -#endif - -#ifndef va_copy -# ifdef __va_copy -# define va_copy(dst, src) __va_copy(dst, src) -# else -# define va_copy(dst, src) ((dst) = (src)) -# endif -#endif - -#endif diff --git a/src/checkout.c b/src/checkout.c deleted file mode 100644 index 6a4643196..000000000 --- a/src/checkout.c +++ /dev/null @@ -1,2813 +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 "checkout.h" - -#include "git2/repository.h" -#include "git2/refs.h" -#include "git2/tree.h" -#include "git2/blob.h" -#include "git2/config.h" -#include "git2/diff.h" -#include "git2/submodule.h" -#include "git2/sys/index.h" -#include "git2/sys/filter.h" -#include "git2/merge.h" - -#include "refs.h" -#include "repository.h" -#include "index.h" -#include "filter.h" -#include "blob.h" -#include "diff.h" -#include "diff_generate.h" -#include "pathspec.h" -#include "diff_xdiff.h" -#include "fs_path.h" -#include "attr.h" -#include "pool.h" -#include "strmap.h" -#include "path.h" - -/* See docs/checkout-internals.md for more information */ - -enum { - CHECKOUT_ACTION__NONE = 0, - CHECKOUT_ACTION__REMOVE = 1, - CHECKOUT_ACTION__UPDATE_BLOB = 2, - CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, - CHECKOUT_ACTION__CONFLICT = 8, - CHECKOUT_ACTION__REMOVE_CONFLICT = 16, - CHECKOUT_ACTION__UPDATE_CONFLICT = 32, - CHECKOUT_ACTION__MAX = 32, - CHECKOUT_ACTION__REMOVE_AND_UPDATE = - (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE) -}; - -typedef struct { - git_repository *repo; - git_iterator *target; - git_diff *diff; - git_checkout_options opts; - bool opts_free_baseline; - char *pfx; - git_index *index; - git_pool pool; - git_vector removes; - git_vector remove_conflicts; - git_vector update_conflicts; - git_vector *update_reuc; - git_vector *update_names; - git_str target_path; - size_t target_len; - git_str tmp; - unsigned int strategy; - int can_symlink; - int respect_filemode; - bool reload_submodules; - size_t total_steps; - size_t completed_steps; - git_checkout_perfdata perfdata; - git_strmap *mkdir_map; - git_attr_session attr_session; -} checkout_data; - -typedef struct { - const git_index_entry *ancestor; - const git_index_entry *ours; - const git_index_entry *theirs; - - unsigned int name_collision:1, - directoryfile:1, - one_to_two:1, - binary:1, - submodule:1; -} checkout_conflictdata; - -static int checkout_notify( - checkout_data *data, - git_checkout_notify_t why, - const git_diff_delta *delta, - const git_index_entry *wditem) -{ - git_diff_file wdfile; - const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL; - const char *path = NULL; - - if (!data->opts.notify_cb || - (why & data->opts.notify_flags) == 0) - return 0; - - if (wditem) { - memset(&wdfile, 0, sizeof(wdfile)); - - git_oid_cpy(&wdfile.id, &wditem->id); - wdfile.path = wditem->path; - wdfile.size = wditem->file_size; - wdfile.flags = GIT_DIFF_FLAG_VALID_ID; - wdfile.mode = wditem->mode; - - workdir = &wdfile; - - path = wditem->path; - } - - if (delta) { - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_MODIFIED: - case GIT_DELTA_TYPECHANGE: - default: - baseline = &delta->old_file; - target = &delta->new_file; - break; - case GIT_DELTA_ADDED: - case GIT_DELTA_IGNORED: - case GIT_DELTA_UNTRACKED: - case GIT_DELTA_UNREADABLE: - target = &delta->new_file; - break; - case GIT_DELTA_DELETED: - baseline = &delta->old_file; - break; - } - - path = delta->old_file.path; - } - - { - int error = data->opts.notify_cb( - why, path, baseline, target, workdir, data->opts.notify_payload); - - return git_error_set_after_callback_function( - error, "git_checkout notification"); - } -} - -GIT_INLINE(bool) is_workdir_base_or_new( - const git_oid *workdir_id, - const git_diff_file *baseitem, - const git_diff_file *newitem) -{ - return (git_oid__cmp(&baseitem->id, workdir_id) == 0 || - git_oid__cmp(&newitem->id, workdir_id) == 0); -} - -GIT_INLINE(bool) is_filemode_changed(git_filemode_t a, git_filemode_t b, int respect_filemode) -{ - /* If core.filemode = false, ignore links in the repository and executable bit changes */ - if (!respect_filemode) { - if (a == S_IFLNK) - a = GIT_FILEMODE_BLOB; - if (b == S_IFLNK) - b = GIT_FILEMODE_BLOB; - - a &= ~0111; - b &= ~0111; - } - - return (a != b); -} - -static bool checkout_is_workdir_modified( - checkout_data *data, - const git_diff_file *baseitem, - const git_diff_file *newitem, - const git_index_entry *wditem) -{ - git_oid oid; - const git_index_entry *ie; - - /* handle "modified" submodule */ - if (wditem->mode == GIT_FILEMODE_COMMIT) { - git_submodule *sm; - unsigned int sm_status = 0; - const git_oid *sm_oid = NULL; - bool rval = false; - - if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0) { - git_error_clear(); - return true; - } - - if (git_submodule_status(&sm_status, data->repo, wditem->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED) < 0 || - GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) - rval = true; - else if ((sm_oid = git_submodule_wd_id(sm)) == NULL) - rval = false; - else - rval = (git_oid__cmp(&baseitem->id, sm_oid) != 0); - - git_submodule_free(sm); - return rval; - } - - /* - * Look at the cache to decide if the workdir is modified: if the - * cache contents match the workdir contents, then we do not need - * to examine the working directory directly, instead we can - * examine the cache to see if _it_ has been modified. This allows - * us to avoid touching the disk. - */ - ie = git_index_get_bypath(data->index, wditem->path, 0); - - if (ie != NULL && - !git_index_entry_newer_than_index(ie, data->index) && - git_index_time_eq(&wditem->mtime, &ie->mtime) && - wditem->file_size == ie->file_size && - !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { - - /* The workdir is modified iff the index entry is modified */ - return !is_workdir_base_or_new(&ie->id, baseitem, newitem) || - is_filemode_changed(baseitem->mode, ie->mode, data->respect_filemode); - } - - /* depending on where base is coming from, we may or may not know - * the actual size of the data, so we can't rely on this shortcut. - */ - if (baseitem->size && wditem->file_size != baseitem->size) - return true; - - /* if the workdir item is a directory, it cannot be a modified file */ - if (S_ISDIR(wditem->mode)) - return false; - - if (is_filemode_changed(baseitem->mode, wditem->mode, data->respect_filemode)) - return true; - - if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) - return false; - - /* Allow the checkout if the workdir is not modified *or* if the checkout - * target's contents are already in the working directory. - */ - return !is_workdir_base_or_new(&oid, baseitem, newitem); -} - -#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ - ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO) - -static int checkout_action_common( - int *action, - checkout_data *data, - const git_diff_delta *delta, - const git_index_entry *wd) -{ - git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) - *action = (*action & ~CHECKOUT_ACTION__REMOVE); - - if ((*action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) { - if (S_ISGITLINK(delta->new_file.mode)) - *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB) | - CHECKOUT_ACTION__UPDATE_SUBMODULE; - - /* to "update" a symlink, we must remove the old one first */ - if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) - *action |= CHECKOUT_ACTION__REMOVE; - - /* if the file is on disk and doesn't match our mode, force update */ - if (wd && - GIT_PERMS_IS_EXEC(wd->mode) != GIT_PERMS_IS_EXEC(delta->new_file.mode)) - *action |= CHECKOUT_ACTION__REMOVE; - - notify = GIT_CHECKOUT_NOTIFY_UPDATED; - } - - if ((*action & CHECKOUT_ACTION__CONFLICT) != 0) - notify = GIT_CHECKOUT_NOTIFY_CONFLICT; - - return checkout_notify(data, notify, delta, wd); -} - -static int checkout_action_no_wd( - int *action, - checkout_data *data, - const git_diff_delta *delta) -{ - int error = 0; - - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: /* case 12 */ - error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL); - if (error) - return error; - *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ - *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT); - break; - case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ - if (delta->new_file.mode == GIT_FILEMODE_TREE) - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_DELETED: /* case 8 or 25 */ - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, NULL); -} - -static int checkout_target_fullpath( - git_str **out, checkout_data *data, const char *path) -{ - git_str_truncate(&data->target_path, data->target_len); - - if (path && git_str_puts(&data->target_path, path) < 0) - return -1; - - if (git_path_validate_str_length(data->repo, &data->target_path) < 0) - return -1; - - *out = &data->target_path; - - return 0; -} - -static bool wd_item_is_removable( - checkout_data *data, const git_index_entry *wd) -{ - git_str *full; - - if (wd->mode != GIT_FILEMODE_TREE) - return true; - - if (checkout_target_fullpath(&full, data, wd->path) < 0) - return false; - - return !full || !git_fs_path_contains(full, DOT_GIT); -} - -static int checkout_queue_remove(checkout_data *data, const char *path) -{ - char *copy = git_pool_strdup(&data->pool, path); - GIT_ERROR_CHECK_ALLOC(copy); - return git_vector_insert(&data->removes, copy); -} - -/* note that this advances the iterator over the wd item */ -static int checkout_action_wd_only( - checkout_data *data, - git_iterator *workdir, - const git_index_entry **wditem, - git_vector *pathspec) -{ - int error = 0; - bool remove = false; - git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - const git_index_entry *wd = *wditem; - - if (!git_pathspec__match( - pathspec, wd->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) { - - if (wd->mode == GIT_FILEMODE_TREE) - return git_iterator_advance_into(wditem, workdir); - else - return git_iterator_advance(wditem, workdir); - } - - /* check if item is tracked in the index but not in the checkout diff */ - if (data->index != NULL) { - size_t pos; - - error = git_index__find_pos( - &pos, data->index, wd->path, 0, GIT_INDEX_STAGE_ANY); - - if (wd->mode != GIT_FILEMODE_TREE) { - if (!error) { /* found by git_index__find_pos call */ - notify = GIT_CHECKOUT_NOTIFY_DIRTY; - remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); - } else if (error != GIT_ENOTFOUND) - return error; - else - error = 0; /* git_index__find_pos does not set error msg */ - } else { - /* for tree entries, we have to see if there are any index - * entries that are contained inside that tree - */ - const git_index_entry *e = git_index_get_byindex(data->index, pos); - - if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) - return git_iterator_advance_into(wditem, workdir); - } - } - - if (notify != GIT_CHECKOUT_NOTIFY_NONE) { - /* if we found something in the index, notify and advance */ - if ((error = checkout_notify(data, notify, NULL, wd)) != 0) - return error; - - if (remove && wd_item_is_removable(data, wd)) - error = checkout_queue_remove(data, wd->path); - - if (!error) - error = git_iterator_advance(wditem, workdir); - } else { - /* untracked or ignored - can't know which until we advance through */ - bool over = false, removable = wd_item_is_removable(data, wd); - git_iterator_status_t untracked_state; - - /* copy the entry for issuing notification callback later */ - git_index_entry saved_wd = *wd; - git_str_sets(&data->tmp, wd->path); - saved_wd.path = data->tmp.ptr; - - error = git_iterator_advance_over( - wditem, &untracked_state, workdir); - if (error == GIT_ITEROVER) - over = true; - else if (error < 0) - return error; - - if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) { - notify = GIT_CHECKOUT_NOTIFY_IGNORED; - remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); - } else { - notify = GIT_CHECKOUT_NOTIFY_UNTRACKED; - remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0); - } - - if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0) - return error; - - if (remove && removable) - error = checkout_queue_remove(data, saved_wd.path); - - if (!error && over) /* restore ITEROVER if needed */ - error = GIT_ITEROVER; - } - - return error; -} - -static bool submodule_is_config_only( - checkout_data *data, - const char *path) -{ - git_submodule *sm = NULL; - unsigned int sm_loc = 0; - bool rval = false; - - if (git_submodule_lookup(&sm, data->repo, path) < 0) - return true; - - if (git_submodule_location(&sm_loc, sm) < 0 || - sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG) - rval = true; - - git_submodule_free(sm); - - return rval; -} - -static bool checkout_is_empty_dir(checkout_data *data, const char *path) -{ - git_str *fullpath; - - if (checkout_target_fullpath(&fullpath, data, path) < 0) - return false; - - return git_fs_path_is_empty_dir(fullpath->ptr); -} - -static int checkout_action_with_wd( - int *action, - checkout_data *data, - const git_diff_delta *delta, - git_iterator *workdir, - const git_index_entry *wd) -{ - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ - if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) { - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); - *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE); - } - break; - case GIT_DELTA_ADDED: /* case 3, 4 or 6 */ - if (git_iterator_current_is_ignored(workdir)) - *action = CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, UPDATE_BLOB); - else - *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); - break; - case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */ - if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); - else - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); - break; - case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ - if (wd->mode != GIT_FILEMODE_COMMIT && - checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) - *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); - else - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ - if (delta->old_file.mode == GIT_FILEMODE_TREE) { - if (wd->mode == GIT_FILEMODE_TREE) - /* either deleting items in old tree will delete the wd dir, - * or we'll get a conflict when we attempt blob update... - */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - else if (wd->mode == GIT_FILEMODE_COMMIT) { - /* workdir is possibly a "phantom" submodule - treat as a - * tree if the only submodule info came from the config - */ - if (submodule_is_config_only(data, wd->path)) - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - else - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - } else - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); - } - else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - else - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); - - /* don't update if the typechange is to a tree */ - if (delta->new_file.mode == GIT_FILEMODE_TREE) - *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, wd); -} - -static int checkout_action_with_wd_blocker( - int *action, - checkout_data *data, - const git_diff_delta *delta, - const git_index_entry *wd) -{ - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - /* should show delta as dirty / deleted */ - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); - break; - case GIT_DELTA_ADDED: - case GIT_DELTA_MODIFIED: - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - case GIT_DELTA_DELETED: - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); - break; - case GIT_DELTA_TYPECHANGE: - /* not 100% certain about this... */ - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, wd); -} - -static int checkout_action_with_wd_dir( - int *action, - checkout_data *data, - const git_diff_delta *delta, - git_iterator *workdir, - const git_index_entry *wd) -{ - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */ - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL)); - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); - break; - case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ - case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ - if (delta->old_file.mode == GIT_FILEMODE_COMMIT) - /* expected submodule (and maybe found one) */; - else if (delta->new_file.mode != GIT_FILEMODE_TREE) - *action = git_iterator_current_is_ignored(workdir) ? - CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, REMOVE_AND_UPDATE) : - CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */ - if (delta->old_file.mode != GIT_FILEMODE_TREE) - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); - break; - case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */ - if (delta->old_file.mode == GIT_FILEMODE_TREE) { - /* For typechange from dir, remove dir and add blob, but it is - * not safe to remove dir if it contains modified files. - * However, safely removing child files will remove the parent - * directory if is it left empty, so we can defer removing the - * dir and it will succeed if no children are left. - */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - } - else if (delta->new_file.mode != GIT_FILEMODE_TREE) - /* For typechange to dir, dir is already created so no action */ - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, wd); -} - -static int checkout_action_with_wd_dir_empty( - int *action, - checkout_data *data, - const git_diff_delta *delta) -{ - int error = checkout_action_no_wd(action, data, delta); - - /* We can always safely remove an empty directory. */ - if (error == 0 && *action != CHECKOUT_ACTION__NONE) - *action |= CHECKOUT_ACTION__REMOVE; - - return error; -} - -static int checkout_action( - int *action, - checkout_data *data, - git_diff_delta *delta, - git_iterator *workdir, - const git_index_entry **wditem, - git_vector *pathspec) -{ - int cmp = -1, error; - int (*strcomp)(const char *, const char *) = data->diff->strcomp; - int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp; - int (*advance)(const git_index_entry **, git_iterator *) = NULL; - - /* move workdir iterator to follow along with deltas */ - - while (1) { - const git_index_entry *wd = *wditem; - - if (!wd) - return checkout_action_no_wd(action, data, delta); - - cmp = strcomp(wd->path, delta->old_file.path); - - /* 1. wd before delta ("a/a" before "a/b") - * 2. wd prefixes delta & should expand ("a/" before "a/b") - * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c") - * 4. wd equals delta ("a/b" and "a/b") - * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b") - * 6. wd after delta ("a/c" after "a/b") - */ - - if (cmp < 0) { - cmp = pfxcomp(delta->old_file.path, wd->path); - - if (cmp == 0) { - if (wd->mode == GIT_FILEMODE_TREE) { - /* case 2 - entry prefixed by workdir tree */ - error = git_iterator_advance_into(wditem, workdir); - if (error < 0 && error != GIT_ITEROVER) - goto done; - continue; - } - - /* case 3 maybe - wd contains non-dir where dir expected */ - if (delta->old_file.path[strlen(wd->path)] == '/') { - error = checkout_action_with_wd_blocker( - action, data, delta, wd); - advance = git_iterator_advance; - goto done; - } - } - - /* case 1 - handle wd item (if it matches pathspec) */ - error = checkout_action_wd_only(data, workdir, wditem, pathspec); - if (error && error != GIT_ITEROVER) - goto done; - continue; - } - - if (cmp == 0) { - /* case 4 */ - error = checkout_action_with_wd(action, data, delta, workdir, wd); - advance = git_iterator_advance; - goto done; - } - - cmp = pfxcomp(wd->path, delta->old_file.path); - - if (cmp == 0) { /* case 5 */ - if (wd->path[strlen(delta->old_file.path)] != '/') - return checkout_action_no_wd(action, data, delta); - - if (delta->status == GIT_DELTA_TYPECHANGE) { - if (delta->old_file.mode == GIT_FILEMODE_TREE) { - error = checkout_action_with_wd(action, data, delta, workdir, wd); - advance = git_iterator_advance_into; - goto done; - } - - if (delta->new_file.mode == GIT_FILEMODE_TREE || - delta->new_file.mode == GIT_FILEMODE_COMMIT || - delta->old_file.mode == GIT_FILEMODE_COMMIT) - { - error = checkout_action_with_wd(action, data, delta, workdir, wd); - advance = git_iterator_advance; - goto done; - } - } - - return checkout_is_empty_dir(data, wd->path) ? - checkout_action_with_wd_dir_empty(action, data, delta) : - checkout_action_with_wd_dir(action, data, delta, workdir, wd); - } - - /* case 6 - wd is after delta */ - return checkout_action_no_wd(action, data, delta); - } - -done: - if (!error && advance != NULL && - (error = advance(wditem, workdir)) < 0) { - *wditem = NULL; - if (error == GIT_ITEROVER) - error = 0; - } - - return error; -} - -static int checkout_remaining_wd_items( - checkout_data *data, - git_iterator *workdir, - const git_index_entry *wd, - git_vector *spec) -{ - int error = 0; - - while (wd && !error) - error = checkout_action_wd_only(data, workdir, &wd, spec); - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -GIT_INLINE(int) checkout_idxentry_cmp( - const git_index_entry *a, - const git_index_entry *b) -{ - if (!a && !b) - return 0; - else if (!a && b) - return -1; - else if(a && !b) - return 1; - else - return strcmp(a->path, b->path); -} - -static int checkout_conflictdata_cmp(const void *a, const void *b) -{ - const checkout_conflictdata *ca = a; - const checkout_conflictdata *cb = b; - int diff; - - if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 && - (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0) - diff = checkout_idxentry_cmp(ca->theirs, cb->theirs); - - return diff; -} - -static int checkout_conflictdata_empty( - const git_vector *conflicts, size_t idx, void *payload) -{ - checkout_conflictdata *conflict; - - GIT_UNUSED(payload); - - if ((conflict = git_vector_get(conflicts, idx)) == NULL) - return -1; - - if (conflict->ancestor || conflict->ours || conflict->theirs) - return 0; - - git__free(conflict); - return 1; -} - -GIT_INLINE(bool) conflict_pathspec_match( - checkout_data *data, - git_iterator *workdir, - git_vector *pathspec, - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs) -{ - /* if the pathspec matches ours *or* theirs, proceed */ - if (ours && git_pathspec__match(pathspec, ours->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) - return true; - - if (theirs && git_pathspec__match(pathspec, theirs->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) - return true; - - if (ancestor && git_pathspec__match(pathspec, ancestor->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) - return true; - - return false; -} - -GIT_INLINE(int) checkout_conflict_detect_submodule(checkout_conflictdata *conflict) -{ - conflict->submodule = ((conflict->ancestor && S_ISGITLINK(conflict->ancestor->mode)) || - (conflict->ours && S_ISGITLINK(conflict->ours->mode)) || - (conflict->theirs && S_ISGITLINK(conflict->theirs->mode))); - return 0; -} - -GIT_INLINE(int) checkout_conflict_detect_binary(git_repository *repo, checkout_conflictdata *conflict) -{ - git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL; - int error = 0; - - if (conflict->submodule) - return 0; - - if (conflict->ancestor) { - if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor->id)) < 0) - goto done; - - conflict->binary = git_blob_is_binary(ancestor_blob); - } - - if (!conflict->binary && conflict->ours) { - if ((error = git_blob_lookup(&our_blob, repo, &conflict->ours->id)) < 0) - goto done; - - conflict->binary = git_blob_is_binary(our_blob); - } - - if (!conflict->binary && conflict->theirs) { - if ((error = git_blob_lookup(&their_blob, repo, &conflict->theirs->id)) < 0) - goto done; - - conflict->binary = git_blob_is_binary(their_blob); - } - -done: - git_blob_free(ancestor_blob); - git_blob_free(our_blob); - git_blob_free(their_blob); - - return error; -} - -static int checkout_conflict_append_update( - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs, - void *payload) -{ - checkout_data *data = payload; - checkout_conflictdata *conflict; - int error; - - conflict = git__calloc(1, sizeof(checkout_conflictdata)); - GIT_ERROR_CHECK_ALLOC(conflict); - - conflict->ancestor = ancestor; - conflict->ours = ours; - conflict->theirs = theirs; - - if ((error = checkout_conflict_detect_submodule(conflict)) < 0 || - (error = checkout_conflict_detect_binary(data->repo, conflict)) < 0) - { - git__free(conflict); - return error; - } - - if (git_vector_insert(&data->update_conflicts, conflict)) - return -1; - - return 0; -} - -static int checkout_conflicts_foreach( - checkout_data *data, - git_index *index, - git_iterator *workdir, - git_vector *pathspec, - int (*cb)(const git_index_entry *, const git_index_entry *, const git_index_entry *, void *), - void *payload) -{ - git_index_conflict_iterator *iterator = NULL; - const git_index_entry *ancestor, *ours, *theirs; - int error = 0; - - if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0) - goto done; - - /* Collect the conflicts */ - while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) { - if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs)) - continue; - - if ((error = cb(ancestor, ours, theirs, payload)) < 0) - goto done; - } - - if (error == GIT_ITEROVER) - error = 0; - -done: - git_index_conflict_iterator_free(iterator); - - return error; -} - -static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) -{ - git_index *index; - - /* Only write conflicts from sources that have them: indexes. */ - if ((index = git_iterator_index(data->target)) == NULL) - return 0; - - data->update_conflicts._cmp = checkout_conflictdata_cmp; - - if (checkout_conflicts_foreach(data, index, workdir, pathspec, checkout_conflict_append_update, data) < 0) - return -1; - - /* Collect the REUC and NAME entries */ - data->update_reuc = &index->reuc; - data->update_names = &index->names; - - return 0; -} - -GIT_INLINE(int) checkout_conflicts_cmp_entry( - const char *path, - const git_index_entry *entry) -{ - return strcmp((const char *)path, entry->path); -} - -static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) -{ - const char *path = p; - const checkout_conflictdata *conflict = c; - - if (!conflict->ancestor) - return 1; - - return checkout_conflicts_cmp_entry(path, conflict->ancestor); -} - -static checkout_conflictdata *checkout_conflicts_search_ancestor( - checkout_data *data, - const char *path) -{ - size_t pos; - - if (git_vector_bsearch2(&pos, &data->update_conflicts, checkout_conflicts_cmp_ancestor, path) < 0) - return NULL; - - return git_vector_get(&data->update_conflicts, pos); -} - -static checkout_conflictdata *checkout_conflicts_search_branch( - checkout_data *data, - const char *path) -{ - checkout_conflictdata *conflict; - size_t i; - - git_vector_foreach(&data->update_conflicts, i, conflict) { - int cmp = -1; - - if (conflict->ancestor) - break; - - if (conflict->ours) - cmp = checkout_conflicts_cmp_entry(path, conflict->ours); - else if (conflict->theirs) - cmp = checkout_conflicts_cmp_entry(path, conflict->theirs); - - if (cmp == 0) - return conflict; - } - - return NULL; -} - -static int checkout_conflicts_load_byname_entry( - checkout_conflictdata **ancestor_out, - checkout_conflictdata **ours_out, - checkout_conflictdata **theirs_out, - checkout_data *data, - const git_index_name_entry *name_entry) -{ - checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; - int error = 0; - - *ancestor_out = NULL; - *ours_out = NULL; - *theirs_out = NULL; - - if (!name_entry->ancestor) { - git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ancestor"); - error = -1; - goto done; - } - - if (!name_entry->ours && !name_entry->theirs) { - git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ours or theirs"); - error = -1; - goto done; - } - - if ((ancestor = checkout_conflicts_search_ancestor(data, - name_entry->ancestor)) == NULL) { - git_error_set(GIT_ERROR_INDEX, - "a NAME entry referenced ancestor entry '%s' which does not exist in the main index", - name_entry->ancestor); - error = -1; - goto done; - } - - if (name_entry->ours) { - if (strcmp(name_entry->ancestor, name_entry->ours) == 0) - ours = ancestor; - else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL || - ours->ours == NULL) { - git_error_set(GIT_ERROR_INDEX, - "a NAME entry referenced our entry '%s' which does not exist in the main index", - name_entry->ours); - error = -1; - goto done; - } - } - - if (name_entry->theirs) { - if (strcmp(name_entry->ancestor, name_entry->theirs) == 0) - theirs = ancestor; - else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) - theirs = ours; - else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL || - theirs->theirs == NULL) { - git_error_set(GIT_ERROR_INDEX, - "a NAME entry referenced their entry '%s' which does not exist in the main index", - name_entry->theirs); - error = -1; - goto done; - } - } - - *ancestor_out = ancestor; - *ours_out = ours; - *theirs_out = theirs; - -done: - return error; -} - -static int checkout_conflicts_coalesce_renames( - checkout_data *data) -{ - git_index *index; - const git_index_name_entry *name_entry; - checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; - size_t i, names; - int error = 0; - - if ((index = git_iterator_index(data->target)) == NULL) - return 0; - - /* Juggle entries based on renames */ - names = git_index_name_entrycount(index); - - for (i = 0; i < names; i++) { - name_entry = git_index_name_get_byindex(index, i); - - if ((error = checkout_conflicts_load_byname_entry( - &ancestor_conflict, &our_conflict, &their_conflict, - data, name_entry)) < 0) - goto done; - - if (our_conflict && our_conflict != ancestor_conflict) { - ancestor_conflict->ours = our_conflict->ours; - our_conflict->ours = NULL; - - if (our_conflict->theirs) - our_conflict->name_collision = 1; - - if (our_conflict->name_collision) - ancestor_conflict->name_collision = 1; - } - - if (their_conflict && their_conflict != ancestor_conflict) { - ancestor_conflict->theirs = their_conflict->theirs; - their_conflict->theirs = NULL; - - if (their_conflict->ours) - their_conflict->name_collision = 1; - - if (their_conflict->name_collision) - ancestor_conflict->name_collision = 1; - } - - if (our_conflict && our_conflict != ancestor_conflict && - their_conflict && their_conflict != ancestor_conflict) - ancestor_conflict->one_to_two = 1; - } - - git_vector_remove_matching( - &data->update_conflicts, checkout_conflictdata_empty, NULL); - -done: - return error; -} - -static int checkout_conflicts_mark_directoryfile( - checkout_data *data) -{ - git_index *index; - checkout_conflictdata *conflict; - const git_index_entry *entry; - size_t i, j, len; - const char *path; - int prefixed, error = 0; - - if ((index = git_iterator_index(data->target)) == NULL) - return 0; - - len = git_index_entrycount(index); - - /* Find d/f conflicts */ - git_vector_foreach(&data->update_conflicts, i, conflict) { - if ((conflict->ours && conflict->theirs) || - (!conflict->ours && !conflict->theirs)) - continue; - - path = conflict->ours ? - conflict->ours->path : conflict->theirs->path; - - if ((error = git_index_find(&j, index, path)) < 0) { - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_INDEX, - "index inconsistency, could not find entry for expected conflict '%s'", path); - - goto done; - } - - for (; j < len; j++) { - if ((entry = git_index_get_byindex(index, j)) == NULL) { - git_error_set(GIT_ERROR_INDEX, - "index inconsistency, truncated index while loading expected conflict '%s'", path); - error = -1; - goto done; - } - - prefixed = git_fs_path_equal_or_prefixed(path, entry->path, NULL); - - if (prefixed == GIT_FS_PATH_EQUAL) - continue; - - if (prefixed == GIT_FS_PATH_PREFIX) - conflict->directoryfile = 1; - - break; - } - } - -done: - return error; -} - -static int checkout_get_update_conflicts( - checkout_data *data, - git_iterator *workdir, - git_vector *pathspec) -{ - int error = 0; - - if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED) - return 0; - - if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 || - (error = checkout_conflicts_coalesce_renames(data)) < 0 || - (error = checkout_conflicts_mark_directoryfile(data)) < 0) - goto done; - -done: - return error; -} - -static int checkout_conflict_append_remove( - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs, - void *payload) -{ - checkout_data *data = payload; - const char *name; - - GIT_ASSERT_ARG(ancestor || ours || theirs); - - if (ancestor) - name = git__strdup(ancestor->path); - else if (ours) - name = git__strdup(ours->path); - else if (theirs) - name = git__strdup(theirs->path); - else - abort(); - - GIT_ERROR_CHECK_ALLOC(name); - - return git_vector_insert(&data->remove_conflicts, (char *)name); -} - -static int checkout_get_remove_conflicts( - checkout_data *data, - git_iterator *workdir, - git_vector *pathspec) -{ - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) - return 0; - - return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data); -} - -static int checkout_verify_paths( - git_repository *repo, - int action, - git_diff_delta *delta) -{ - unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; - - if (action & CHECKOUT_ACTION__REMOVE) { - if (!git_path_is_valid(repo, delta->old_file.path, delta->old_file.mode, flags)) { - git_error_set(GIT_ERROR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path); - return -1; - } - } - - if (action & ~CHECKOUT_ACTION__REMOVE) { - if (!git_path_is_valid(repo, delta->new_file.path, delta->new_file.mode, flags)) { - git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path); - return -1; - } - } - - return 0; -} - -static int checkout_get_actions( - uint32_t **actions_ptr, - size_t **counts_ptr, - checkout_data *data, - git_iterator *workdir) -{ - int error = 0, act; - const git_index_entry *wditem; - git_vector pathspec = GIT_VECTOR_INIT, *deltas; - git_pool pathpool; - git_diff_delta *delta; - size_t i, *counts = NULL; - uint32_t *actions = NULL; - - if (git_pool_init(&pathpool, 1) < 0) - return -1; - - if (data->opts.paths.count > 0 && - git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) - return -1; - - if ((error = git_iterator_current(&wditem, workdir)) < 0 && - error != GIT_ITEROVER) - goto fail; - - deltas = &data->diff->deltas; - - *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t)); - *actions_ptr = actions = git__calloc( - deltas->length ? deltas->length : 1, sizeof(uint32_t)); - if (!counts || !actions) { - error = -1; - goto fail; - } - - git_vector_foreach(deltas, i, delta) { - if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0) - error = checkout_verify_paths(data->repo, act, delta); - - if (error != 0) - goto fail; - - actions[i] = act; - - if (act & CHECKOUT_ACTION__REMOVE) - counts[CHECKOUT_ACTION__REMOVE]++; - if (act & CHECKOUT_ACTION__UPDATE_BLOB) - counts[CHECKOUT_ACTION__UPDATE_BLOB]++; - if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE) - counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++; - if (act & CHECKOUT_ACTION__CONFLICT) - counts[CHECKOUT_ACTION__CONFLICT]++; - } - - error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec); - if (error) - goto fail; - - counts[CHECKOUT_ACTION__REMOVE] += data->removes.length; - - if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && - (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0) { - git_error_set(GIT_ERROR_CHECKOUT, "%"PRIuZ" %s checkout", - counts[CHECKOUT_ACTION__CONFLICT], - counts[CHECKOUT_ACTION__CONFLICT] == 1 ? - "conflict prevents" : "conflicts prevent"); - error = GIT_ECONFLICT; - goto fail; - } - - - if ((error = checkout_get_remove_conflicts(data, workdir, &pathspec)) < 0 || - (error = checkout_get_update_conflicts(data, workdir, &pathspec)) < 0) - goto fail; - - counts[CHECKOUT_ACTION__REMOVE_CONFLICT] = git_vector_length(&data->remove_conflicts); - counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->update_conflicts); - - git_pathspec__vfree(&pathspec); - git_pool_clear(&pathpool); - - return 0; - -fail: - *counts_ptr = NULL; - git__free(counts); - *actions_ptr = NULL; - git__free(actions); - - git_pathspec__vfree(&pathspec); - git_pool_clear(&pathpool); - - return error; -} - -static bool should_remove_existing(checkout_data *data) -{ - int ignorecase; - - if (git_repository__configmap_lookup(&ignorecase, data->repo, GIT_CONFIGMAP_IGNORECASE) < 0) { - ignorecase = 0; - } - - return (ignorecase && - (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0); -} - -#define MKDIR_NORMAL \ - GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR -#define MKDIR_REMOVE_EXISTING \ - MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS - -static int checkout_mkdir( - checkout_data *data, - const char *path, - const char *base, - mode_t mode, - unsigned int flags) -{ - struct git_futils_mkdir_options mkdir_opts = {0}; - int error; - - mkdir_opts.dir_map = data->mkdir_map; - mkdir_opts.pool = &data->pool; - - error = git_futils_mkdir_relative( - path, base, mode, flags, &mkdir_opts); - - data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; - data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls; - data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls; - - return error; -} - -static int mkpath2file( - checkout_data *data, const char *path, unsigned int mode) -{ - struct stat st; - bool remove_existing = should_remove_existing(data); - unsigned int flags = - (remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) | - GIT_MKDIR_SKIP_LAST; - int error; - - if ((error = checkout_mkdir( - data, path, data->opts.target_directory, mode, flags)) < 0) - return error; - - if (remove_existing) { - data->perfdata.stat_calls++; - - if (p_lstat(path, &st) == 0) { - - /* Some file, symlink or folder already exists at this name. - * We would have removed it in remove_the_old unless we're on - * a case inensitive filesystem (or the user has asked us not - * to). Remove the similarly named file to write the new. - */ - error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES); - } else if (errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); - return GIT_EEXISTS; - } else { - git_error_clear(); - } - } - - return error; -} - -struct checkout_stream { - git_writestream base; - const char *path; - int fd; - int open; -}; - -static int checkout_stream_write( - git_writestream *s, const char *buffer, size_t len) -{ - struct checkout_stream *stream = (struct checkout_stream *)s; - int ret; - - if ((ret = p_write(stream->fd, buffer, len)) < 0) - git_error_set(GIT_ERROR_OS, "could not write to '%s'", stream->path); - - return ret; -} - -static int checkout_stream_close(git_writestream *s) -{ - struct checkout_stream *stream = (struct checkout_stream *)s; - - GIT_ASSERT_ARG(stream); - GIT_ASSERT_ARG(stream->open); - - stream->open = 0; - return p_close(stream->fd); -} - -static void checkout_stream_free(git_writestream *s) -{ - GIT_UNUSED(s); -} - -static int blob_content_to_file( - checkout_data *data, - struct stat *st, - git_blob *blob, - const char *path, - const char *hint_path, - mode_t entry_filemode) -{ - int flags = data->opts.file_open_flags; - mode_t file_mode = data->opts.file_mode ? - data->opts.file_mode : entry_filemode; - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - struct checkout_stream writer; - mode_t mode; - git_filter_list *fl = NULL; - int fd; - int error = 0; - - GIT_ASSERT(hint_path != NULL); - - if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) - return error; - - if (flags <= 0) - flags = O_CREAT | O_TRUNC | O_WRONLY; - if (!(mode = file_mode)) - mode = GIT_FILEMODE_BLOB; - - if ((fd = p_open(path, flags, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); - return fd; - } - - filter_session.attr_session = &data->attr_session; - filter_session.temp_buf = &data->tmp; - - if (!data->opts.disable_filters && - (error = git_filter_list__load( - &fl, data->repo, blob, hint_path, - GIT_FILTER_TO_WORKTREE, &filter_session))) { - p_close(fd); - return error; - } - - /* setup the writer */ - memset(&writer, 0, sizeof(struct checkout_stream)); - writer.base.write = checkout_stream_write; - writer.base.close = checkout_stream_close; - writer.base.free = checkout_stream_free; - writer.path = path; - writer.fd = fd; - writer.open = 1; - - error = git_filter_list_stream_blob(fl, blob, &writer.base); - - GIT_ASSERT(writer.open == 0); - - git_filter_list_free(fl); - - if (error < 0) - return error; - - if (st) { - data->perfdata.stat_calls++; - - if ((error = p_stat(path, st)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); - return error; - } - - st->st_mode = entry_filemode; - } - - return 0; -} - -static int blob_content_to_link( - checkout_data *data, - struct stat *st, - git_blob *blob, - const char *path) -{ - git_str linktarget = GIT_STR_INIT; - int error; - - if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) - return error; - - if ((error = git_blob__getbuf(&linktarget, blob)) < 0) - return error; - - if (data->can_symlink) { - if ((error = p_symlink(git_str_cstr(&linktarget), path)) < 0) - git_error_set(GIT_ERROR_OS, "could not create symlink %s", path); - } else { - error = git_futils_fake_symlink(git_str_cstr(&linktarget), path); - } - - if (!error) { - data->perfdata.stat_calls++; - - if ((error = p_lstat(path, st)) < 0) - git_error_set(GIT_ERROR_CHECKOUT, "could not stat symlink %s", path); - - st->st_mode = GIT_FILEMODE_LINK; - } - - git_str_dispose(&linktarget); - - return error; -} - -static int checkout_update_index( - checkout_data *data, - const git_diff_file *file, - struct stat *st) -{ - git_index_entry entry; - - if (!data->index) - return 0; - - memset(&entry, 0, sizeof(entry)); - entry.path = (char *)file->path; /* cast to prevent warning */ - git_index_entry__init_from_stat(&entry, st, true); - git_oid_cpy(&entry.id, &file->id); - - return git_index_add(data->index, &entry); -} - -static int checkout_submodule_update_index( - checkout_data *data, - const git_diff_file *file) -{ - git_str *fullpath; - struct stat st; - - /* update the index unless prevented */ - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) - return 0; - - if (checkout_target_fullpath(&fullpath, data, file->path) < 0) - return -1; - - data->perfdata.stat_calls++; - if (p_stat(fullpath->ptr, &st) < 0) { - git_error_set( - GIT_ERROR_CHECKOUT, "could not stat submodule %s\n", file->path); - return GIT_ENOTFOUND; - } - - st.st_mode = GIT_FILEMODE_COMMIT; - - return checkout_update_index(data, file, &st); -} - -static int checkout_submodule( - checkout_data *data, - const git_diff_file *file) -{ - bool remove_existing = should_remove_existing(data); - int error = 0; - - /* Until submodules are supported, UPDATE_ONLY means do nothing here */ - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) - return 0; - - if ((error = checkout_mkdir( - data, - file->path, data->opts.target_directory, data->opts.dir_mode, - remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) - return error; - - if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) { - /* I've observed repos with submodules in the tree that do not - * have a .gitmodules - core Git just makes an empty directory - */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return checkout_submodule_update_index(data, file); - } - - return error; - } - - /* TODO: Support checkout_strategy options. Two circumstances: - * 1 - submodule already checked out, but we need to move the HEAD - * to the new OID, or - * 2 - submodule not checked out and we should recursively check it out - * - * Checkout will not execute a pull on the submodule, but a clone - * command should probably be able to. Do we need a submodule callback? - */ - - return checkout_submodule_update_index(data, file); -} - -static void report_progress( - checkout_data *data, - const char *path) -{ - if (data->opts.progress_cb) - data->opts.progress_cb( - path, data->completed_steps, data->total_steps, - data->opts.progress_payload); -} - -static int checkout_safe_for_update_only( - checkout_data *data, const char *path, mode_t expected_mode) -{ - struct stat st; - - data->perfdata.stat_calls++; - - if (p_lstat(path, &st) < 0) { - /* if doesn't exist, then no error and no update */ - if (errno == ENOENT || errno == ENOTDIR) - return 0; - - /* otherwise, stat error and no update */ - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); - return -1; - } - - /* only safe for update if this is the same type of file */ - if ((st.st_mode & ~0777) == (expected_mode & ~0777)) - return 1; - - return 0; -} - -static int checkout_write_content( - checkout_data *data, - const git_oid *oid, - const char *full_path, - const char *hint_path, - unsigned int mode, - struct stat *st) -{ - int error = 0; - git_blob *blob; - - if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) - return error; - - if (S_ISLNK(mode)) - error = blob_content_to_link(data, st, blob, full_path); - else - error = blob_content_to_file(data, st, blob, full_path, hint_path, mode); - - git_blob_free(blob); - - /* if we try to create the blob and an existing directory blocks it from - * being written, then there must have been a typechange conflict in a - * parent directory - suppress the error and try to continue. - */ - if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 && - (error == GIT_ENOTFOUND || error == GIT_EEXISTS)) - { - git_error_clear(); - error = 0; - } - - return error; -} - -static int checkout_blob( - checkout_data *data, - const git_diff_file *file) -{ - git_str *fullpath; - struct stat st; - int error = 0; - - if (checkout_target_fullpath(&fullpath, data, file->path) < 0) - return -1; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = checkout_safe_for_update_only( - data, fullpath->ptr, file->mode); - - if (rval <= 0) - return rval; - } - - error = checkout_write_content( - data, &file->id, fullpath->ptr, file->path, file->mode, &st); - - /* update the index unless prevented */ - if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) - error = checkout_update_index(data, file, &st); - - /* update the submodule data if this was a new .gitmodules file */ - if (!error && strcmp(file->path, ".gitmodules") == 0) - data->reload_submodules = true; - - return error; -} - -static int checkout_remove_the_old( - unsigned int *actions, - checkout_data *data) -{ - int error = 0; - git_diff_delta *delta; - const char *str; - size_t i; - git_str *fullpath; - uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; - - if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) - flg |= GIT_RMDIR_SKIP_NONEMPTY; - - if (checkout_target_fullpath(&fullpath, data, NULL) < 0) - return -1; - - git_vector_foreach(&data->diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__REMOVE) { - error = git_futils_rmdir_r( - delta->old_file.path, fullpath->ptr, flg); - - if (error < 0) - return error; - - data->completed_steps++; - report_progress(data, delta->old_file.path); - - if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && - (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && - data->index != NULL) - { - (void)git_index_remove(data->index, delta->old_file.path, 0); - } - } - } - - git_vector_foreach(&data->removes, i, str) { - error = git_futils_rmdir_r(str, fullpath->ptr, flg); - if (error < 0) - return error; - - data->completed_steps++; - report_progress(data, str); - - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && - data->index != NULL) - { - if (str[strlen(str) - 1] == '/') - (void)git_index_remove_directory(data->index, str, 0); - else - (void)git_index_remove(data->index, str, 0); - } - } - - return 0; -} - -static int checkout_create_the_new( - unsigned int *actions, - checkout_data *data) -{ - int error = 0; - git_diff_delta *delta; - size_t i; - - 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); - } - } - - 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); - } - } - - return 0; -} - -static int checkout_create_submodules( - unsigned int *actions, - checkout_data *data) -{ - git_diff_delta *delta; - size_t i; - - git_vector_foreach(&data->diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { - int error = checkout_submodule(data, &delta->new_file); - if (error < 0) - return error; - - data->completed_steps++; - report_progress(data, delta->new_file.path); - } - } - - return 0; -} - -static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) -{ - int error = 0; - git_reference *ref = NULL; - git_object *head; - - if (!(error = git_repository_head(&ref, repo)) && - !(error = git_reference_peel(&head, ref, GIT_OBJECT_TREE))) - *out = (git_tree *)head; - - git_reference_free(ref); - - return error; -} - - -static int conflict_entry_name( - git_str *out, - const char *side_name, - const char *filename) -{ - if (git_str_puts(out, side_name) < 0 || - git_str_putc(out, ':') < 0 || - git_str_puts(out, filename) < 0) - return -1; - - return 0; -} - -static int checkout_path_suffixed(git_str *path, const char *suffix) -{ - size_t path_len; - int i = 0, error = 0; - - if ((error = git_str_putc(path, '~')) < 0 || (error = git_str_puts(path, suffix)) < 0) - return -1; - - path_len = git_str_len(path); - - while (git_fs_path_exists(git_str_cstr(path)) && i < INT_MAX) { - git_str_truncate(path, path_len); - - if ((error = git_str_putc(path, '_')) < 0 || - (error = git_str_printf(path, "%d", i)) < 0) - return error; - - i++; - } - - if (i == INT_MAX) { - git_str_truncate(path, path_len); - - git_error_set(GIT_ERROR_CHECKOUT, "could not write '%s': working directory file exists", path->ptr); - return GIT_EEXISTS; - } - - return 0; -} - -static int checkout_write_entry( - checkout_data *data, - checkout_conflictdata *conflict, - const git_index_entry *side) -{ - const char *hint_path = NULL, *suffix; - git_str *fullpath; - struct stat st; - int error; - - GIT_ASSERT(side == conflict->ours || side == conflict->theirs); - - if (checkout_target_fullpath(&fullpath, data, side->path) < 0) - return -1; - - if ((conflict->name_collision || conflict->directoryfile) && - (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && - (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { - - if (side == conflict->ours) - suffix = data->opts.our_label ? data->opts.our_label : - "ours"; - else - suffix = data->opts.their_label ? data->opts.their_label : - "theirs"; - - if (checkout_path_suffixed(fullpath, suffix) < 0) - return -1; - } - - hint_path = side->path; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0) - return error; - - if (!S_ISGITLINK(side->mode)) - return checkout_write_content(data, - &side->id, fullpath->ptr, hint_path, side->mode, &st); - - return 0; -} - -static int checkout_write_entries( - checkout_data *data, - checkout_conflictdata *conflict) -{ - int error = 0; - - if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0) - error = checkout_write_entry(data, conflict, conflict->theirs); - - return error; -} - -static int checkout_merge_path( - git_str *out, - checkout_data *data, - checkout_conflictdata *conflict, - git_merge_file_result *result) -{ - const char *our_label_raw, *their_label_raw, *suffix; - int error = 0; - - if ((error = git_str_joinpath(out, data->opts.target_directory, result->path)) < 0 || - (error = git_path_validate_str_length(data->repo, out)) < 0) - return error; - - /* Most conflicts simply use the filename in the index */ - if (!conflict->name_collision) - return 0; - - /* Rename 2->1 conflicts need the branch name appended */ - our_label_raw = data->opts.our_label ? data->opts.our_label : "ours"; - their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs"; - suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw; - - if ((error = checkout_path_suffixed(out, suffix)) < 0) - return error; - - return 0; -} - -static int checkout_write_merge( - checkout_data *data, - checkout_conflictdata *conflict) -{ - git_str our_label = GIT_STR_INIT, their_label = GIT_STR_INIT, - path_suffixed = GIT_STR_INIT, path_workdir = GIT_STR_INIT, - in_data = GIT_STR_INIT, out_data = GIT_STR_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - git_filebuf output = GIT_FILEBUF_INIT; - git_filter_list *fl = NULL; - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - int error = 0; - - if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) - opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; - - if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3) - opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; - - opts.ancestor_label = data->opts.ancestor_label ? - data->opts.ancestor_label : "ancestor"; - opts.our_label = data->opts.our_label ? - data->opts.our_label : "ours"; - opts.their_label = data->opts.their_label ? - data->opts.their_label : "theirs"; - - /* If all the paths are identical, decorate the diff3 file with the branch - * names. Otherwise, append branch_name:path. - */ - if (conflict->ours && conflict->theirs && - strcmp(conflict->ours->path, conflict->theirs->path) != 0) { - - if ((error = conflict_entry_name( - &our_label, opts.our_label, conflict->ours->path)) < 0 || - (error = conflict_entry_name( - &their_label, opts.their_label, conflict->theirs->path)) < 0) - goto done; - - opts.our_label = git_str_cstr(&our_label); - opts.their_label = git_str_cstr(&their_label); - } - - if ((error = git_merge_file_from_index(&result, data->repo, - conflict->ancestor, conflict->ours, conflict->theirs, &opts)) < 0) - goto done; - - if (result.path == NULL || result.mode == 0) { - git_error_set(GIT_ERROR_CHECKOUT, "could not merge contents of file"); - error = GIT_ECONFLICT; - goto done; - } - - if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) - goto done; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = checkout_safe_for_update_only(data, git_str_cstr(&path_workdir), result.mode)) <= 0) - goto done; - - if (!data->opts.disable_filters) { - in_data.ptr = (char *)result.ptr; - in_data.size = result.len; - - filter_session.attr_session = &data->attr_session; - filter_session.temp_buf = &data->tmp; - - if ((error = git_filter_list__load( - &fl, data->repo, NULL, result.path, - GIT_FILTER_TO_WORKTREE, &filter_session)) < 0 || - (error = git_filter_list__convert_buf(&out_data, fl, &in_data)) < 0) - goto done; - } else { - out_data.ptr = (char *)result.ptr; - out_data.size = result.len; - } - - if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 || - (error = git_filebuf_open(&output, git_str_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || - (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 || - (error = git_filebuf_commit(&output)) < 0) - goto done; - -done: - git_filter_list_free(fl); - - git_str_dispose(&out_data); - git_str_dispose(&our_label); - git_str_dispose(&their_label); - - git_merge_file_result_free(&result); - git_str_dispose(&path_workdir); - git_str_dispose(&path_suffixed); - - return error; -} - -static int checkout_conflict_add( - checkout_data *data, - const git_index_entry *conflict) -{ - int error = git_index_remove(data->index, conflict->path, 0); - - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - return error; - - return git_index_add(data->index, conflict); -} - -static int checkout_conflict_update_index( - checkout_data *data, - checkout_conflictdata *conflict) -{ - int error = 0; - - if (conflict->ancestor) - error = checkout_conflict_add(data, conflict->ancestor); - - if (!error && conflict->ours) - error = checkout_conflict_add(data, conflict->ours); - - if (!error && conflict->theirs) - error = checkout_conflict_add(data, conflict->theirs); - - return error; -} - -static int checkout_create_conflicts(checkout_data *data) -{ - checkout_conflictdata *conflict; - size_t i; - int error = 0; - - git_vector_foreach(&data->update_conflicts, i, conflict) { - - /* Both deleted: nothing to do */ - if (conflict->ours == NULL && conflict->theirs == NULL) - error = 0; - - else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && - conflict->ours) - error = checkout_write_entry(data, conflict, conflict->ours); - else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && - conflict->theirs) - error = checkout_write_entry(data, conflict, conflict->theirs); - - /* Ignore the other side of name collisions. */ - else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && - !conflict->ours && conflict->name_collision) - error = 0; - else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && - !conflict->theirs && conflict->name_collision) - error = 0; - - /* For modify/delete, name collisions and d/f conflicts, write - * the file (potentially with the name mangled. - */ - else if (conflict->ours != NULL && conflict->theirs == NULL) - error = checkout_write_entry(data, conflict, conflict->ours); - else if (conflict->ours == NULL && conflict->theirs != NULL) - error = checkout_write_entry(data, conflict, conflict->theirs); - - /* Add/add conflicts and rename 1->2 conflicts, write the - * ours/theirs sides (potentially name mangled). - */ - else if (conflict->one_to_two) - error = checkout_write_entries(data, conflict); - - /* If all sides are links, write the ours side */ - else if (S_ISLNK(conflict->ours->mode) && - S_ISLNK(conflict->theirs->mode)) - error = checkout_write_entry(data, conflict, conflict->ours); - /* Link/file conflicts, write the file side */ - else if (S_ISLNK(conflict->ours->mode)) - error = checkout_write_entry(data, conflict, conflict->theirs); - else if (S_ISLNK(conflict->theirs->mode)) - error = checkout_write_entry(data, conflict, conflict->ours); - - /* If any side is a gitlink, do nothing. */ - else if (conflict->submodule) - error = 0; - - /* If any side is binary, write the ours side */ - else if (conflict->binary) - error = checkout_write_entry(data, conflict, conflict->ours); - - else if (!error) - error = checkout_write_merge(data, conflict); - - /* Update the index extensions (REUC and NAME) if we're checking - * out a different index. (Otherwise just leave them there.) - */ - if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) - error = checkout_conflict_update_index(data, conflict); - - if (error) - break; - - data->completed_steps++; - report_progress(data, - conflict->ours ? conflict->ours->path : - (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); - } - - return error; -} - -static int checkout_remove_conflicts(checkout_data *data) -{ - const char *conflict; - size_t i; - - git_vector_foreach(&data->remove_conflicts, i, conflict) { - if (git_index_conflict_remove(data->index, conflict) < 0) - return -1; - - data->completed_steps++; - } - - return 0; -} - -static int checkout_extensions_update_index(checkout_data *data) -{ - const git_index_reuc_entry *reuc_entry; - const git_index_name_entry *name_entry; - size_t i; - int error = 0; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) - return 0; - - if (data->update_reuc) { - git_vector_foreach(data->update_reuc, i, reuc_entry) { - if ((error = git_index_reuc_add(data->index, reuc_entry->path, - reuc_entry->mode[0], &reuc_entry->oid[0], - reuc_entry->mode[1], &reuc_entry->oid[1], - reuc_entry->mode[2], &reuc_entry->oid[2])) < 0) - goto done; - } - } - - if (data->update_names) { - git_vector_foreach(data->update_names, i, name_entry) { - if ((error = git_index_name_add(data->index, name_entry->ancestor, - name_entry->ours, name_entry->theirs)) < 0) - goto done; - } - } - -done: - return error; -} - -static void checkout_data_clear(checkout_data *data) -{ - if (data->opts_free_baseline) { - git_tree_free(data->opts.baseline); - data->opts.baseline = NULL; - } - - git_vector_free(&data->removes); - git_pool_clear(&data->pool); - - git_vector_free_deep(&data->remove_conflicts); - git_vector_free_deep(&data->update_conflicts); - - git__free(data->pfx); - data->pfx = NULL; - - git_str_dispose(&data->target_path); - git_str_dispose(&data->tmp); - - git_index_free(data->index); - data->index = NULL; - - git_strmap_free(data->mkdir_map); - data->mkdir_map = NULL; - - git_attr_session__free(&data->attr_session); -} - -static int validate_target_directory(checkout_data *data) -{ - int error; - - if ((error = git_path_validate_length(data->repo, data->opts.target_directory)) < 0) - return error; - - if (git_fs_path_isdir(data->opts.target_directory)) - return 0; - - error = checkout_mkdir(data, data->opts.target_directory, NULL, - GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR); - - return error; -} - -static int checkout_data_init( - checkout_data *data, - git_iterator *target, - const git_checkout_options *proposed) -{ - int error = 0; - git_repository *repo = git_iterator_owner(target); - - memset(data, 0, sizeof(*data)); - - if (!repo) { - git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout nothing"); - return -1; - } - - if ((!proposed || !proposed->target_directory) && - (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) - return error; - - data->repo = repo; - data->target = target; - - GIT_ERROR_CHECK_VERSION( - proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); - - if (!proposed) - GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTIONS_VERSION); - else - memmove(&data->opts, proposed, sizeof(git_checkout_options)); - - if (!data->opts.target_directory) - data->opts.target_directory = git_repository_workdir(repo); - else if ((error = validate_target_directory(data)) < 0) - goto cleanup; - - if ((error = git_repository_index(&data->index, data->repo)) < 0) - goto cleanup; - - /* refresh config and index content unless NO_REFRESH is given */ - if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { - git_config *cfg; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - goto cleanup; - - /* Reload the repository index (unless we're checking out the - * index; then it has the changes we're trying to check out - * and those should not be overwritten.) - */ - if (data->index != git_iterator_index(target)) { - if (data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) { - /* When forcing, we can blindly re-read the index */ - if ((error = git_index_read(data->index, false)) < 0) - goto cleanup; - } else { - /* - * When not being forced, we need to check for unresolved - * conflicts and unsaved changes in the index before - * proceeding. - */ - if (git_index_has_conflicts(data->index)) { - error = GIT_ECONFLICT; - git_error_set(GIT_ERROR_CHECKOUT, - "unresolved conflicts exist in the index"); - goto cleanup; - } - - if ((error = git_index_read_safely(data->index)) < 0) - goto cleanup; - } - - /* clean conflict data in the current index */ - git_index_name_clear(data->index); - git_index_reuc_clear(data->index); - } - } - - /* if you are forcing, allow all safe updates, plus recreate missing */ - if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; - - /* if the repository does not actually have an index file, then this - * is an initial checkout (perhaps from clone), so we allow safe updates - */ - if (!data->index->on_disk && - (data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING; - - data->strategy = data->opts.checkout_strategy; - - /* opts->disable_filters is false by default */ - - if (!data->opts.dir_mode) - data->opts.dir_mode = GIT_DIR_MODE; - - if (!data->opts.file_open_flags) - data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; - - data->pfx = git_pathspec_prefix(&data->opts.paths); - - if ((error = git_repository__configmap_lookup( - &data->can_symlink, repo, GIT_CONFIGMAP_SYMLINKS)) < 0) - goto cleanup; - - if ((error = git_repository__configmap_lookup( - &data->respect_filemode, repo, GIT_CONFIGMAP_FILEMODE)) < 0) - goto cleanup; - - if (!data->opts.baseline && !data->opts.baseline_index) { - data->opts_free_baseline = true; - error = 0; - - /* if we don't have an index, this is an initial checkout and - * should be against an empty baseline - */ - if (data->index->on_disk) - error = checkout_lookup_head_tree(&data->opts.baseline, repo); - - if (error == GIT_EUNBORNBRANCH) { - error = 0; - git_error_clear(); - } - - if (error < 0) - goto cleanup; - } - - if ((data->opts.checkout_strategy & - (GIT_CHECKOUT_CONFLICT_STYLE_MERGE | GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)) == 0) { - git_config_entry *conflict_style = NULL; - git_config *cfg = NULL; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || - (error = git_config_get_entry(&conflict_style, cfg, "merge.conflictstyle")) < 0 || - error == GIT_ENOTFOUND) - ; - else if (error) - goto cleanup; - else if (strcmp(conflict_style->value, "merge") == 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE; - else if (strcmp(conflict_style->value, "diff3") == 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; - else if (strcmp(conflict_style->value, "zdiff3") == 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3; - else { - git_error_set(GIT_ERROR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'", - conflict_style->value); - error = -1; - git_config_entry_free(conflict_style); - goto cleanup; - } - git_config_entry_free(conflict_style); - } - - if ((error = git_pool_init(&data->pool, 1)) < 0 || - (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_str_puts(&data->target_path, data->opts.target_directory)) < 0 || - (error = git_fs_path_to_dir(&data->target_path)) < 0 || - (error = git_strmap_new(&data->mkdir_map)) < 0) - goto cleanup; - - data->target_len = git_str_len(&data->target_path); - - git_attr_session__init(&data->attr_session, data->repo); - -cleanup: - if (error < 0) - checkout_data_clear(data); - - return error; -} - -#define CHECKOUT_INDEX_DONT_WRITE_MASK \ - (GIT_CHECKOUT_DONT_UPDATE_INDEX | GIT_CHECKOUT_DONT_WRITE_INDEX) - -GIT_INLINE(void) setup_pathspecs( - git_iterator_options *iter_opts, - const git_checkout_options *checkout_opts) -{ - if (checkout_opts && - (checkout_opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) { - iter_opts->pathlist.count = checkout_opts->paths.count; - iter_opts->pathlist.strings = checkout_opts->paths.strings; - } -} - -int git_checkout_iterator( - git_iterator *target, - git_index *index, - const git_checkout_options *opts) -{ - int error = 0; - git_iterator *baseline = NULL, *workdir = NULL; - git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT, - workdir_opts = GIT_ITERATOR_OPTIONS_INIT; - checkout_data data = {0}; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - uint32_t *actions = NULL; - size_t *counts = NULL; - - /* initialize structures and options */ - error = checkout_data_init(&data, target, opts); - if (error < 0) - return error; - - diff_opts.flags = - GIT_DIFF_INCLUDE_UNMODIFIED | - GIT_DIFF_INCLUDE_UNREADABLE | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ - GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_TYPECHANGE | - GIT_DIFF_INCLUDE_TYPECHANGE_TREES | - GIT_DIFF_SKIP_BINARY_CHECK | - GIT_DIFF_INCLUDE_CASECHANGE; - if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) - diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; - if (data.opts.paths.count > 0) - diff_opts.pathspec = data.opts.paths; - - /* set up iterators */ - - workdir_opts.flags = git_iterator_ignore_case(target) ? - GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; - workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND; - workdir_opts.start = data.pfx; - workdir_opts.end = data.pfx; - - setup_pathspecs(&workdir_opts, opts); - - if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 || - (error = git_iterator_for_workdir_ext( - &workdir, data.repo, data.opts.target_directory, index, NULL, - &workdir_opts)) < 0) - goto cleanup; - - baseline_opts.flags = git_iterator_ignore_case(target) ? - GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; - baseline_opts.start = data.pfx; - baseline_opts.end = data.pfx; - - setup_pathspecs(&baseline_opts, opts); - - if (data.opts.baseline_index) { - if ((error = git_iterator_for_index( - &baseline, git_index_owner(data.opts.baseline_index), - data.opts.baseline_index, &baseline_opts)) < 0) - goto cleanup; - } else { - if ((error = git_iterator_for_tree( - &baseline, data.opts.baseline, &baseline_opts)) < 0) - goto cleanup; - } - - /* Should not have case insensitivity mismatch */ - GIT_ASSERT(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline)); - - /* Generate baseline-to-target diff which will include an entry for - * every possible update that might need to be made. - */ - if ((error = git_diff__from_iterators( - &data.diff, data.repo, baseline, target, &diff_opts)) < 0) - goto cleanup; - - /* Loop through diff (and working directory iterator) building a list of - * actions to be taken, plus look for conflicts and send notifications, - * then loop through conflicts. - */ - if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0) - goto cleanup; - - if (data.strategy & GIT_CHECKOUT_DRY_RUN) - goto cleanup; - - data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + - counts[CHECKOUT_ACTION__REMOVE_CONFLICT] + - counts[CHECKOUT_ACTION__UPDATE_BLOB] + - counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + - counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; - - report_progress(&data, NULL); /* establish 0 baseline */ - - /* To deal with some order dependencies, perform remaining checkout - * in three passes: removes, then update blobs, then update submodules. - */ - if (counts[CHECKOUT_ACTION__REMOVE] > 0 && - (error = checkout_remove_the_old(actions, &data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__REMOVE_CONFLICT] > 0 && - (error = checkout_remove_conflicts(&data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && - (error = checkout_create_the_new(actions, &data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && - (error = checkout_create_submodules(actions, &data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && - (error = checkout_create_conflicts(&data)) < 0) - goto cleanup; - - if (data.index != git_iterator_index(target) && - (error = checkout_extensions_update_index(&data)) < 0) - goto cleanup; - - GIT_ASSERT(data.completed_steps == data.total_steps); - - if (data.opts.perfdata_cb) - data.opts.perfdata_cb(&data.perfdata, data.opts.perfdata_payload); - -cleanup: - if (!error && data.index != NULL && - (data.strategy & CHECKOUT_INDEX_DONT_WRITE_MASK) == 0) - error = git_index_write(data.index); - - git_diff_free(data.diff); - git_iterator_free(workdir); - git_iterator_free(baseline); - git__free(actions); - git__free(counts); - checkout_data_clear(&data); - - return error; -} - -int git_checkout_index( - git_repository *repo, - git_index *index, - const git_checkout_options *opts) -{ - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error, owned = 0; - git_iterator *index_i; - - if (!index && !repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "must provide either repository or index to checkout"); - return -1; - } - - if (index && repo && - git_index_owner(index) && - git_index_owner(index) != repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "index to checkout does not match repository"); - return -1; - } else if(index && repo && !git_index_owner(index)) { - GIT_REFCOUNT_OWN(index, repo); - owned = 1; - } - - if (!repo) - repo = git_index_owner(index); - - if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) - return error; - GIT_REFCOUNT_INC(index); - - setup_pathspecs(&iter_opts, opts); - - if (!(error = git_iterator_for_index(&index_i, repo, index, &iter_opts))) - error = git_checkout_iterator(index_i, index, opts); - - if (owned) - GIT_REFCOUNT_OWN(index, NULL); - - git_iterator_free(index_i); - git_index_free(index); - - return error; -} - -int git_checkout_tree( - git_repository *repo, - const git_object *treeish, - const git_checkout_options *opts) -{ - int error; - git_index *index; - git_tree *tree = NULL; - git_iterator *tree_i = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - if (!treeish && !repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "must provide either repository or tree to checkout"); - return -1; - } - if (treeish && repo && git_object_owner(treeish) != repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "object to checkout does not match repository"); - return -1; - } - - if (!repo) - repo = git_object_owner(treeish); - - if (treeish) { - if (git_object_peel((git_object **)&tree, treeish, GIT_OBJECT_TREE) < 0) { - git_error_set( - GIT_ERROR_CHECKOUT, "provided object cannot be peeled to a tree"); - return -1; - } - } - else { - if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) { - if (error != GIT_EUNBORNBRANCH) - git_error_set( - GIT_ERROR_CHECKOUT, - "HEAD could not be peeled to a tree and no treeish given"); - return error; - } - } - - if ((error = git_repository_index(&index, repo)) < 0) - return error; - - setup_pathspecs(&iter_opts, opts); - - if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts))) - error = git_checkout_iterator(tree_i, index, opts); - - git_iterator_free(tree_i); - git_index_free(index); - git_tree_free(tree); - - return error; -} - -int git_checkout_head( - git_repository *repo, - const git_checkout_options *opts) -{ - GIT_ASSERT_ARG(repo); - - return git_checkout_tree(repo, NULL, opts); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_checkout_init_options(git_checkout_options *opts, unsigned int version) -{ - return git_checkout_options_init(opts, version); -} -#endif diff --git a/src/checkout.h b/src/checkout.h deleted file mode 100644 index 517fbf3b1..000000000 --- a/src/checkout.h +++ /dev/null @@ -1,27 +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_checkout_h__ -#define INCLUDE_checkout_h__ - -#include "common.h" - -#include "git2/checkout.h" -#include "iterator.h" - -#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12) - -/** - * Update the working directory to match the target iterator. The - * expected baseline value can be passed in via the checkout options - * or else will default to the HEAD commit. - */ -extern int git_checkout_iterator( - git_iterator *target, - git_index *index, - const git_checkout_options *opts); - -#endif diff --git a/src/cherrypick.c b/src/cherrypick.c deleted file mode 100644 index 9ec4962b9..000000000 --- a/src/cherrypick.c +++ /dev/null @@ -1,242 +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 "common.h" - -#include "repository.h" -#include "filebuf.h" -#include "merge.h" -#include "vector.h" -#include "index.h" - -#include "git2/types.h" -#include "git2/merge.h" -#include "git2/cherrypick.h" -#include "git2/commit.h" -#include "git2/sys/commit.h" - -#define GIT_CHERRYPICK_FILE_MODE 0666 - -static int write_cherrypick_head( - git_repository *repo, - const char *commit_oidstr) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 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); - - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int write_merge_msg( - git_repository *repo, - const char *commit_msg) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 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; - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int cherrypick_normalize_opts( - git_repository *repo, - git_cherrypick_options *opts, - const git_cherrypick_options *given, - const char *their_label) -{ - int error = 0; - unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; - - GIT_UNUSED(repo); - - if (given != NULL) - memcpy(opts, given, sizeof(git_cherrypick_options)); - else { - git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT; - memcpy(opts, &default_opts, sizeof(git_cherrypick_options)); - } - - if (!opts->checkout_opts.checkout_strategy) - opts->checkout_opts.checkout_strategy = default_checkout_strategy; - - if (!opts->checkout_opts.our_label) - opts->checkout_opts.our_label = "HEAD"; - - if (!opts->checkout_opts.their_label) - opts->checkout_opts.their_label = their_label; - - return error; -} - -static int cherrypick_state_cleanup(git_repository *repo) -{ - const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -static int cherrypick_seterr(git_commit *commit, const char *fmt) -{ - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - git_error_set(GIT_ERROR_CHERRYPICK, fmt, - git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); - - return -1; -} - -int git_cherrypick_commit( - git_index **out, - git_repository *repo, - git_commit *cherrypick_commit, - git_commit *our_commit, - unsigned int mainline, - const git_merge_options *merge_opts) -{ - git_commit *parent_commit = NULL; - git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL; - int parent = 0, error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cherrypick_commit); - GIT_ASSERT_ARG(our_commit); - - if (git_commit_parentcount(cherrypick_commit) > 1) { - if (!mainline) - return cherrypick_seterr(cherrypick_commit, - "mainline branch is not specified but %s is a merge commit"); - - parent = mainline; - } else { - if (mainline) - return cherrypick_seterr(cherrypick_commit, - "mainline branch specified but %s is not a merge commit"); - - parent = git_commit_parentcount(cherrypick_commit); - } - - if (parent && - ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) - goto done; - - if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 || - (error = git_commit_tree(&our_tree, our_commit)) < 0) - goto done; - - error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts); - -done: - git_tree_free(parent_tree); - git_tree_free(our_tree); - git_tree_free(cherrypick_tree); - git_commit_free(parent_commit); - - return error; -} - -int git_cherrypick( - git_repository *repo, - git_commit *commit, - const git_cherrypick_options *given_opts) -{ - git_cherrypick_options opts; - git_reference *our_ref = NULL; - git_commit *our_commit = NULL; - char commit_oidstr[GIT_OID_HEXSZ + 1]; - const char *commit_msg, *commit_summary; - git_str their_label = GIT_STR_INIT; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(commit); - - GIT_ERROR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options"); - - if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0) - return error; - - if ((commit_msg = git_commit_message(commit)) == NULL || - (commit_summary = git_commit_summary(commit)) == NULL) { - error = -1; - goto on_error; - } - - git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit)); - - if ((error = write_merge_msg(repo, commit_msg)) < 0 || - (error = git_str_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 || - (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || - (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || - (error = write_cherrypick_head(repo, commit_oidstr)) < 0 || - (error = git_repository_head(&our_ref, repo)) < 0 || - (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || - (error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || - (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || - (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto on_error; - - goto done; - -on_error: - cherrypick_state_cleanup(repo); - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_commit_free(our_commit); - git_reference_free(our_ref); - git_str_dispose(&their_label); - - return error; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_cherrypick_init_options( - git_cherrypick_options *opts, unsigned int version) -{ - return git_cherrypick_options_init(opts, version); -} -#endif diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt new file mode 100644 index 000000000..4f347e93f --- /dev/null +++ b/src/cli/CMakeLists.txt @@ -0,0 +1,53 @@ +set(CLI_INCLUDES + "${libgit2_BINARY_DIR}/src" + "${libgit2_SOURCE_DIR}/src/util" + "${libgit2_SOURCE_DIR}/src/cli" + "${libgit2_SOURCE_DIR}/include") + +if(WIN32 AND NOT CYGWIN) + file(GLOB CLI_SRC_OS win32/*.c) + list(SORT CLI_SRC_OS) +else() + file(GLOB CLI_SRC_OS unix/*.c) + list(SORT CLI_SRC_OS) +endif() + +file(GLOB CLI_SRC_C *.c *.h) +list(SORT CLI_SRC_C) + +# +# The CLI currently needs to be statically linked against libgit2 because +# the utility library uses libgit2's thread-local error buffers. TODO: +# remove this dependency and allow us to dynamically link against libgit2. +# + +if(BUILD_CLI STREQUAL "dynamic") + set(CLI_LIBGIT2_LIBRARY libgit2package) +else() + set(CLI_LIBGIT2_OBJECTS $) +endif() + +# +# Compile and link the CLI +# + +add_executable(git2_cli ${CLI_SRC_C} ${CLI_SRC_OS} ${CLI_OBJECTS} + $ + ${CLI_LIBGIT2_OBJECTS} + ${LIBGIT2_DEPENDENCY_OBJECTS}) +target_link_libraries(git2_cli ${CLI_LIBGIT2_LIBRARY} ${LIBGIT2_SYSTEM_LIBS}) + +set_target_properties(git2_cli PROPERTIES C_STANDARD 90) +set_target_properties(git2_cli PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) + +ide_split_sources(git2_cli) + +target_include_directories(git2_cli PRIVATE ${CLI_INCLUDES}) + +if(MSVC_IDE) + # Precompiled headers + set_target_properties(git2_cli PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") +endif() + +install(TARGETS git2_cli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/cli/README.md b/src/cli/README.md new file mode 100644 index 000000000..3087c39c4 --- /dev/null +++ b/src/cli/README.md @@ -0,0 +1,26 @@ +# cli + +A git-compatible command-line interface that uses libgit2. + +## Adding commands + +1. Individual commands have a `main`-like top-level entrypoint. For example: + + ```c + int cmd_help(int argc, char **argv) + ``` + + Although this is the same signature as `main`, commands are not built as + individual standalone executables, they'll be linked into the main cli. + (Though there may be an option for command executables to be built as + standalone executables in the future.) + +2. Commands are prototyped in `cmd.h` and added to `main.c`'s list of + commands (`cli_cmds[]`). Commands should be specified with their name, + entrypoint and a brief description that can be printed in `git help`. + This is done because commands are linked into the main cli. + +3. Commands should accept a `--help` option that displays their help + information. This will be shown when a user runs ` --help` and + when a user runs `help `. + diff --git a/src/cli/cli.h b/src/cli/cli.h new file mode 100644 index 000000000..7dede6785 --- /dev/null +++ b/src/cli/cli.h @@ -0,0 +1,20 @@ +/* + * 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 CLI_cli_h__ +#define CLI_cli_h__ + +#define PROGRAM_NAME "git2" + +#include "git2_util.h" + +#include "error.h" +#include "opt.h" +#include "opt_usage.h" +#include "sighandler.h" + +#endif /* CLI_cli_h__ */ diff --git a/src/cli/cmd.c b/src/cli/cmd.c new file mode 100644 index 000000000..2a7e71cdb --- /dev/null +++ b/src/cli/cmd.c @@ -0,0 +1,21 @@ +/* + * 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 "cli.h" +#include "cmd.h" + +const cli_cmd_spec *cli_cmd_spec_byname(const char *name) +{ + const cli_cmd_spec *cmd; + + for (cmd = cli_cmds; cmd->name; cmd++) { + if (!strcmp(cmd->name, name)) + return cmd; + } + + return NULL; +} diff --git a/src/cli/cmd.h b/src/cli/cmd.h new file mode 100644 index 000000000..8b1a1b38f --- /dev/null +++ b/src/cli/cmd.h @@ -0,0 +1,33 @@ +/* + * 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 CLI_cmd_h__ +#define CLI_cmd_h__ + +/* Command definitions */ +typedef struct { + const char *name; + int (*fn)(int argc, char **argv); + const char *desc; +} cli_cmd_spec; + +/* Options that are common to all commands (eg --help, --git-dir) */ +extern const cli_opt_spec cli_common_opts[]; + +/* All the commands supported by the CLI */ +extern const cli_cmd_spec cli_cmds[]; + +/* Find a command by name */ +extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); + +/* Commands */ +extern int cmd_cat_file(int argc, char **argv); +extern int cmd_clone(int argc, char **argv); +extern int cmd_hash_object(int argc, char **argv); +extern int cmd_help(int argc, char **argv); + +#endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_cat_file.c b/src/cli/cmd_cat_file.c new file mode 100644 index 000000000..fb53a722b --- /dev/null +++ b/src/cli/cmd_cat_file.c @@ -0,0 +1,204 @@ +/* + * 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 +#include "cli.h" +#include "cmd.h" + +#define COMMAND_NAME "cat-file" + +typedef enum { + DISPLAY_CONTENT = 0, + DISPLAY_EXISTS, + DISPLAY_PRETTY, + DISPLAY_SIZE, + DISPLAY_TYPE +} display_t; + +static int show_help; +static int display = DISPLAY_CONTENT; +static char *type_name, *object_spec; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, + "display help about the " COMMAND_NAME " command" }, + + { CLI_OPT_TYPE_SWITCH, NULL, 't', &display, DISPLAY_TYPE, + CLI_OPT_USAGE_REQUIRED, NULL, "display the type of the object" }, + { CLI_OPT_TYPE_SWITCH, NULL, 's', &display, DISPLAY_SIZE, + CLI_OPT_USAGE_CHOICE, NULL, "display the size of the object" }, + { CLI_OPT_TYPE_SWITCH, NULL, 'e', &display, DISPLAY_EXISTS, + CLI_OPT_USAGE_CHOICE, NULL, "displays nothing unless the object is corrupt" }, + { CLI_OPT_TYPE_SWITCH, NULL, 'p', &display, DISPLAY_PRETTY, + CLI_OPT_USAGE_CHOICE, NULL, "pretty-print the object" }, + { CLI_OPT_TYPE_ARG, "type", 0, &type_name, 0, + CLI_OPT_USAGE_CHOICE, "type", "the type of object to display" }, + { CLI_OPT_TYPE_ARG, "object", 0, &object_spec, 0, + CLI_OPT_USAGE_REQUIRED, "object", "the object to display" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Display the content for the given object in the repository.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int print_odb(git_object *object, display_t display) +{ + git_odb *odb = NULL; + git_odb_object *odb_object = NULL; + const unsigned char *content; + git_object_size_t size; + int ret = 0; + + /* + * Our parsed blobs retain the raw content; all other objects are + * parsed into a working representation. To get the raw content, + * we need to do an ODB lookup. (Thankfully, this should be cached + * in-memory from our last call.) + */ + if (git_object_type(object) == GIT_OBJECT_BLOB) { + content = git_blob_rawcontent((git_blob *)object); + size = git_blob_rawsize((git_blob *)object); + } else { + if (git_repository_odb(&odb, git_object_owner(object)) < 0 || + git_odb_read(&odb_object, odb, git_object_id(object)) < 0) { + ret = cli_error_git(); + goto done; + } + + content = git_odb_object_data(odb_object); + size = git_odb_object_size(odb_object); + } + + switch (display) { + case DISPLAY_SIZE: + if (printf("%" PRIu64 "\n", size) < 0) + ret = cli_error_os(); + break; + case DISPLAY_CONTENT: + if (p_write(fileno(stdout), content, (size_t)size) < 0) + ret = cli_error_os(); + break; + default: + GIT_ASSERT(0); + } + +done: + git_odb_object_free(odb_object); + git_odb_free(odb); + return ret; +} + +static int print_type(git_object *object) +{ + if (printf("%s\n", git_object_type2string(git_object_type(object))) < 0) + return cli_error_os(); + + return 0; +} + +static int print_pretty(git_object *object) +{ + const git_tree_entry *entry; + size_t i, count; + + /* + * Only trees are stored in an unreadable format and benefit from + * pretty-printing. + */ + if (git_object_type(object) != GIT_OBJECT_TREE) + return print_odb(object, DISPLAY_CONTENT); + + for (i = 0, count = git_tree_entrycount((git_tree *)object); i < count; i++) { + entry = git_tree_entry_byindex((git_tree *)object, i); + + if (printf("%06o %s %s\t%s\n", + git_tree_entry_filemode_raw(entry), + git_object_type2string(git_tree_entry_type(entry)), + git_oid_tostr_s(git_tree_entry_id(entry)), + git_tree_entry_name(entry)) < 0) + return cli_error_os(); + } + + return 0; +} + +int cmd_cat_file(int argc, char **argv) +{ + git_repository *repo = NULL; + git_object *object = NULL; + git_object_t type; + cli_opt invalid_opt; + int giterr, ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (show_help) { + print_help(); + return 0; + } + + if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) + return cli_error_git(); + + if ((giterr = git_revparse_single(&object, repo, object_spec)) < 0) { + if (display == DISPLAY_EXISTS && giterr == GIT_ENOTFOUND) + ret = 1; + else + ret = cli_error_git(); + + goto done; + } + + if (type_name) { + git_object *peeled; + + if ((type = git_object_string2type(type_name)) == GIT_OBJECT_INVALID) { + ret = cli_error_usage("invalid object type '%s'", type_name); + goto done; + } + + if (git_object_peel(&peeled, object, type) < 0) { + ret = cli_error_git(); + goto done; + } + + git_object_free(object); + object = peeled; + } + + switch (display) { + case DISPLAY_EXISTS: + ret = 0; + break; + case DISPLAY_TYPE: + ret = print_type(object); + break; + case DISPLAY_PRETTY: + ret = print_pretty(object); + break; + default: + ret = print_odb(object, display); + break; + } + +done: + git_object_free(object); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/cmd_clone.c b/src/cli/cmd_clone.c new file mode 100644 index 000000000..a382b5875 --- /dev/null +++ b/src/cli/cmd_clone.c @@ -0,0 +1,176 @@ +/* + * 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 +#include +#include "cli.h" +#include "cmd.h" +#include "error.h" +#include "sighandler.h" +#include "progress.h" + +#include "fs_path.h" +#include "futils.h" + +#define COMMAND_NAME "clone" + +static char *branch, *remote_path, *local_path; +static int show_help, quiet, checkout = 1, bare; +static bool local_path_exists; +static cli_progress progress = CLI_PROGRESS_INIT; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, + "display help about the " COMMAND_NAME " command" }, + + { CLI_OPT_TYPE_SWITCH, "quiet", 'q', &quiet, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display the type of the object" }, + { CLI_OPT_TYPE_SWITCH, "no-checkout", 'n', &checkout, 0, + CLI_OPT_USAGE_DEFAULT, NULL, "don't checkout HEAD" }, + { CLI_OPT_TYPE_SWITCH, "bare", 0, &bare, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "don't create a working directory" }, + { CLI_OPT_TYPE_VALUE, "branch", 'b', &branch, 0, + CLI_OPT_USAGE_DEFAULT, "name", "branch to check out" }, + { CLI_OPT_TYPE_LITERAL }, + { CLI_OPT_TYPE_ARG, "repository", 0, &remote_path, 0, + CLI_OPT_USAGE_REQUIRED, "repository", "repository path" }, + { CLI_OPT_TYPE_ARG, "directory", 0, &local_path, 0, + CLI_OPT_USAGE_DEFAULT, "directory", "directory to clone into" }, + { 0 } +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Clone a repository into a new directory.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static char *compute_local_path(const char *orig_path) +{ + const char *slash; + char *local_path; + + if ((slash = strrchr(orig_path, '/')) == NULL && + (slash = strrchr(orig_path, '\\')) == NULL) + local_path = git__strdup(orig_path); + else + local_path = git__strdup(slash + 1); + + return local_path; +} + +static bool validate_local_path(const char *path) +{ + if (!git_fs_path_exists(path)) + return false; + + if (!git_fs_path_isdir(path) || !git_fs_path_is_empty_dir(path)) { + fprintf(stderr, "fatal: destination path '%s' already exists and is not an empty directory.\n", + path); + exit(128); + } + + return true; +} + +static void cleanup(void) +{ + int rmdir_flags = GIT_RMDIR_REMOVE_FILES; + + cli_progress_abort(&progress); + + if (local_path_exists) + rmdir_flags |= GIT_RMDIR_SKIP_ROOT; + + if (!git_fs_path_isdir(local_path)) + return; + + git_futils_rmdir_r(local_path, NULL, rmdir_flags); +} + +static void interrupt_cleanup(void) +{ + cleanup(); + exit(130); +} + +int cmd_clone(int argc, char **argv) +{ + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_repository *repo = NULL; + cli_opt invalid_opt; + char *computed_path = NULL; + int ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (show_help) { + print_help(); + return 0; + } + + if (!remote_path) { + ret = cli_error_usage("you must specify a repository to clone"); + goto done; + } + + if (bare) + clone_opts.bare = 1; + + if (branch) + clone_opts.checkout_branch = branch; + + if (!checkout) + clone_opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; + + if (!local_path) + local_path = computed_path = compute_local_path(remote_path); + + local_path_exists = validate_local_path(local_path); + + cli_sighandler_set_interrupt(interrupt_cleanup); + + if (!local_path_exists && + git_futils_mkdir(local_path, 0777, 0) < 0) { + ret = cli_error_git(); + goto done; + } + + if (!quiet) { + clone_opts.fetch_opts.callbacks.sideband_progress = cli_progress_fetch_sideband; + clone_opts.fetch_opts.callbacks.transfer_progress = cli_progress_fetch_transfer; + clone_opts.fetch_opts.callbacks.payload = &progress; + + clone_opts.checkout_opts.progress_cb = cli_progress_checkout; + clone_opts.checkout_opts.progress_payload = &progress; + + printf("Cloning into '%s'...\n", local_path); + } + + if (git_clone(&repo, remote_path, local_path, &clone_opts) < 0) { + cleanup(); + ret = cli_error_git(); + goto done; + } + + cli_progress_finish(&progress); + +done: + cli_progress_dispose(&progress); + git__free(computed_path); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/cmd_hash_object.c b/src/cli/cmd_hash_object.c new file mode 100644 index 000000000..5cfe9146a --- /dev/null +++ b/src/cli/cmd_hash_object.c @@ -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. + */ + +#include +#include "cli.h" +#include "cmd.h" + +#include "futils.h" + +#define COMMAND_NAME "hash-object" + +static int show_help; +static char *type_name; +static int write_object, read_stdin, literally; +static char **filenames; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, + "display help about the " COMMAND_NAME " command" }, + + { CLI_OPT_TYPE_VALUE, NULL, 't', &type_name, 0, + CLI_OPT_USAGE_DEFAULT, "type", "the type of object to hash (default: \"blob\")" }, + { CLI_OPT_TYPE_SWITCH, NULL, 'w', &write_object, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "write the object to the object database" }, + { CLI_OPT_TYPE_SWITCH, "literally", 0, &literally, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "do not validate the object contents" }, + { CLI_OPT_TYPE_SWITCH, "stdin", 0, &read_stdin, 1, + CLI_OPT_USAGE_REQUIRED, NULL, "read content from stdin" }, + { CLI_OPT_TYPE_ARGS, "file", 0, &filenames, 0, + CLI_OPT_USAGE_CHOICE, "file", "the file (or files) to read and hash" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Compute the object ID for a given file and optionally write that file\nto the object database.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int hash_buf(git_odb *odb, git_str *buf, git_object_t type) +{ + git_oid oid; + + if (!literally) { + int valid = 0; + + if (git_object_rawcontent_is_valid(&valid, buf->ptr, buf->size, type) < 0 || !valid) + return cli_error_git(); + } + + if (write_object) { + if (git_odb_write(&oid, odb, buf->ptr, buf->size, type) < 0) + return cli_error_git(); + } else { + if (git_odb_hash(&oid, buf->ptr, buf->size, type) < 0) + return cli_error_git(); + } + + if (printf("%s\n", git_oid_tostr_s(&oid)) < 0) + return cli_error_os(); + + return 0; +} + +int cmd_hash_object(int argc, char **argv) +{ + git_repository *repo = NULL; + git_odb *odb = NULL; + git_str buf = GIT_STR_INIT; + cli_opt invalid_opt; + git_object_t type = GIT_OBJECT_BLOB; + char **filename; + int ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (show_help) { + print_help(); + return 0; + } + + if (type_name && (type = git_object_string2type(type_name)) == GIT_OBJECT_INVALID) + return cli_error_usage("invalid object type '%s'", type_name); + + if (write_object && + (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0 || + git_repository_odb(&odb, repo) < 0)) { + ret = cli_error_git(); + goto done; + } + + /* + * TODO: we're reading blobs, we shouldn't pull them all into main + * memory, we should just stream them into the odb instead. + * (Or create a `git_odb_writefile` API.) + */ + if (read_stdin) { + if (git_futils_readbuffer_fd_full(&buf, fileno(stdin)) < 0) { + ret = cli_error_git(); + goto done; + } + + if ((ret = hash_buf(odb, &buf, type)) != 0) + goto done; + } else { + for (filename = filenames; *filename; filename++) { + if (git_futils_readbuffer(&buf, *filename) < 0) { + ret = cli_error_git(); + goto done; + } + + if ((ret = hash_buf(odb, &buf, type)) != 0) + goto done; + } + } + +done: + git_str_dispose(&buf); + git_odb_free(odb); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c new file mode 100644 index 000000000..7ee982242 --- /dev/null +++ b/src/cli/cmd_help.c @@ -0,0 +1,86 @@ +/* + * 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 +#include +#include "cli.h" +#include "cmd.h" + +#define COMMAND_NAME "help" + +static char *command; +static int show_help; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN, NULL, "display help about the help command" }, + { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, + CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" }, + { 0 }, +}; + +static int print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Display help information about %s. If a command is specified, help\n", PROGRAM_NAME); + printf("about that command will be shown. Otherwise, general information about\n"); + printf("%s will be shown, including the commands available.\n", PROGRAM_NAME); + + return 0; +} + +static int print_commands(void) +{ + const cli_cmd_spec *cmd; + + cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts); + printf("\n"); + + printf("These are the %s commands available:\n\n", PROGRAM_NAME); + + for (cmd = cli_cmds; cmd->name; cmd++) + printf(" %-11s %s\n", cmd->name, cmd->desc); + + printf("\nSee '%s help ' for more information on a specific command.\n", PROGRAM_NAME); + + return 0; +} + +int cmd_help(int argc, char **argv) +{ + char *fake_args[2]; + const cli_cmd_spec *cmd; + cli_opt invalid_opt; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + /* Show the meta-help */ + if (show_help) + return print_help(); + + /* We were not asked to show help for a specific command. */ + if (!command) + return print_commands(); + + /* + * If we were asked for help for a command (eg, `help `), + * delegate back to that command's `--help` option. This lets + * commands own their help. Emulate the command-line arguments + * that would invoke ` --help` and invoke that command. + */ + fake_args[0] = command; + fake_args[1] = "--help"; + + if ((cmd = cli_cmd_spec_byname(command)) == NULL) + return cli_error("'%s' is not a %s command. See '%s help'.", + command, PROGRAM_NAME, PROGRAM_NAME); + + return cmd->fn(2, fake_args); +} diff --git a/src/cli/error.h b/src/cli/error.h new file mode 100644 index 000000000..cce7a54c0 --- /dev/null +++ b/src/cli/error.h @@ -0,0 +1,51 @@ +/* + * 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 CLI_error_h__ +#define CLI_error_h__ + +#include "cli.h" +#include + +#define CLI_EXIT_OK 0 +#define CLI_EXIT_ERROR 1 +#define CLI_EXIT_OS 128 +#define CLI_EXIT_GIT 128 +#define CLI_EXIT_USAGE 129 + +#define cli_error__print(fmt) do { \ + va_list ap; \ + va_start(ap, fmt); \ + fprintf(stderr, "%s: ", PROGRAM_NAME); \ + vfprintf(stderr, fmt, ap); \ + fprintf(stderr, "\n"); \ + va_end(ap); \ + } while(0) + +GIT_INLINE(int) cli_error(const char *fmt, ...) +{ + cli_error__print(fmt); + return CLI_EXIT_ERROR; +} + +GIT_INLINE(int) cli_error_usage(const char *fmt, ...) +{ + cli_error__print(fmt); + return CLI_EXIT_USAGE; +} + +GIT_INLINE(int) cli_error_git(void) +{ + const git_error *err = git_error_last(); + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, + err ? err->message : "unknown error"); + return CLI_EXIT_GIT; +} + +#define cli_error_os() (perror(PROGRAM_NAME), CLI_EXIT_OS) + +#endif /* CLI_error_h__ */ diff --git a/src/cli/main.c b/src/cli/main.c new file mode 100644 index 000000000..cbfc50eec --- /dev/null +++ b/src/cli/main.c @@ -0,0 +1,106 @@ +/* + * 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 +#include +#include "cli.h" +#include "cmd.h" + +static int show_help = 0; +static int show_version = 0; +static char *command = NULL; +static char **args = NULL; + +const cli_opt_spec cli_common_opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display help information" }, + { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display the version" }, + { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, + CLI_OPT_USAGE_REQUIRED, "command", "the command to run" }, + { CLI_OPT_TYPE_ARGS, "args", 0, &args, 0, + CLI_OPT_USAGE_DEFAULT, "args", "arguments for the command" }, + { 0 } +}; + +const cli_cmd_spec cli_cmds[] = { + { "cat-file", cmd_cat_file, "Display an object in the repository" }, + { "clone", cmd_clone, "Clone a repository into a new directory" }, + { "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" }, + { "help", cmd_help, "Display help information" }, + { NULL } +}; + +int main(int argc, char **argv) +{ + const cli_cmd_spec *cmd; + cli_opt_parser optparser; + cli_opt opt; + char *help_args[3] = { NULL }; + int help_args_len; + int args_len = 0; + int ret = 0; + + if (git_libgit2_init() < 0) { + cli_error("failed to initialize libgit2"); + exit(CLI_EXIT_GIT); + } + + cli_opt_parser_init(&optparser, cli_common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU); + + /* Parse the top-level (common) options and command information */ + while (cli_opt_parser_next(&opt, &optparser)) { + if (!opt.spec) { + cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, cli_common_opts); + ret = CLI_EXIT_USAGE; + goto done; + } + + /* + * When we see a command, stop parsing and capture the + * remaining arguments as args for the command itself. + */ + if (command) { + args = &argv[optparser.idx]; + args_len = (int)(argc - optparser.idx); + break; + } + } + + if (show_version) { + printf("%s version %s\n", PROGRAM_NAME, LIBGIT2_VERSION); + goto done; + } + + /* + * If `--help ` is specified, delegate to that command's + * `--help` option. If no command is specified, run the `help` + * command. Do this by updating the args to emulate that behavior. + */ + if (!command || show_help) { + help_args[0] = command ? (char *)command : "help"; + help_args[1] = command ? "--help" : NULL; + help_args_len = command ? 2 : 1; + + command = help_args[0]; + args = help_args; + args_len = help_args_len; + } + + if ((cmd = cli_cmd_spec_byname(command)) == NULL) { + ret = cli_error("'%s' is not a %s command. See '%s help'.", + command, PROGRAM_NAME, PROGRAM_NAME); + goto done; + } + + ret = cmd->fn(args_len, args); + +done: + git_libgit2_shutdown(); + return ret; +} diff --git a/src/cli/opt.c b/src/cli/opt.c new file mode 100644 index 000000000..72df5877f --- /dev/null +++ b/src/cli/opt.c @@ -0,0 +1,669 @@ +/* + * Copyright (c), Edward Thomson + * All rights reserved. + * + * This file is part of adopt, distributed under the MIT license. + * For full terms and conditions, see the included LICENSE file. + * + * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT. + * + * This file was produced by using the `rename.pl` script included with + * adopt. The command-line specified was: + * + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage + */ + +#include +#include +#include +#include +#include + +#include "cli.h" +#include "opt.h" + +#ifdef _WIN32 +# include +#else +# include +# include +#endif + +#ifdef _MSC_VER +# define alloca _alloca +#endif + +#define spec_is_option_type(x) \ + ((x)->type == CLI_OPT_TYPE_BOOL || \ + (x)->type == CLI_OPT_TYPE_SWITCH || \ + (x)->type == CLI_OPT_TYPE_VALUE) + +GIT_INLINE(const cli_opt_spec *) spec_for_long( + int *is_negated, + int *has_value, + const char **value, + const cli_opt_parser *parser, + const char *arg) +{ + const cli_opt_spec *spec; + char *eql; + size_t eql_pos; + + eql = strchr(arg, '='); + eql_pos = (eql = strchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg); + + for (spec = parser->specs; spec->type; ++spec) { + /* Handle -- (everything after this is literal) */ + if (spec->type == CLI_OPT_TYPE_LITERAL && arg[0] == '\0') + return spec; + + /* Handle --no-option arguments for bool types */ + if (spec->type == CLI_OPT_TYPE_BOOL && + strncmp(arg, "no-", 3) == 0 && + strcmp(arg + 3, spec->name) == 0) { + *is_negated = 1; + return spec; + } + + /* Handle the typical --option arguments */ + if (spec_is_option_type(spec) && + spec->name && + strcmp(arg, spec->name) == 0) + return spec; + + /* Handle --option=value arguments */ + if (spec->type == CLI_OPT_TYPE_VALUE && + eql && + strncmp(arg, spec->name, eql_pos) == 0 && + spec->name[eql_pos] == '\0') { + *has_value = 1; + *value = arg[eql_pos + 1] ? &arg[eql_pos + 1] : NULL; + return spec; + } + } + + return NULL; +} + +GIT_INLINE(const cli_opt_spec *) spec_for_short( + const char **value, + const cli_opt_parser *parser, + const char *arg) +{ + const cli_opt_spec *spec; + + for (spec = parser->specs; spec->type; ++spec) { + /* Handle -svalue short options with a value */ + if (spec->type == CLI_OPT_TYPE_VALUE && + arg[0] == spec->alias && + arg[1] != '\0') { + *value = &arg[1]; + return spec; + } + + /* Handle typical -s short options */ + if (arg[0] == spec->alias) { + *value = NULL; + return spec; + } + } + + return NULL; +} + +GIT_INLINE(const cli_opt_spec *) spec_for_arg(cli_opt_parser *parser) +{ + const cli_opt_spec *spec; + size_t args = 0; + + for (spec = parser->specs; spec->type; ++spec) { + if (spec->type == CLI_OPT_TYPE_ARG) { + if (args == parser->arg_idx) { + parser->arg_idx++; + return spec; + } + + args++; + } + + if (spec->type == CLI_OPT_TYPE_ARGS && args == parser->arg_idx) + return spec; + } + + return NULL; +} + +GIT_INLINE(int) spec_is_choice(const cli_opt_spec *spec) +{ + return ((spec + 1)->type && + ((spec + 1)->usage & CLI_OPT_USAGE_CHOICE)); +} + +/* + * If we have a choice with switches and bare arguments, and we see + * the switch, then we no longer expect the bare argument. + */ +GIT_INLINE(void) consume_choices(const cli_opt_spec *spec, cli_opt_parser *parser) +{ + /* back up to the beginning of the choices */ + while (spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE)) + --spec; + + if (!spec_is_choice(spec)) + return; + + do { + if (spec->type == CLI_OPT_TYPE_ARG) + parser->arg_idx++; + ++spec; + } while(spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE)); +} + +static cli_opt_status_t parse_long(cli_opt *opt, cli_opt_parser *parser) +{ + const cli_opt_spec *spec; + char *arg = parser->args[parser->idx++]; + const char *value = NULL; + int is_negated = 0, has_value = 0; + + opt->arg = arg; + + if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2])) == NULL) { + opt->spec = NULL; + opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION; + goto done; + } + + opt->spec = spec; + + /* Future options parsed as literal */ + if (spec->type == CLI_OPT_TYPE_LITERAL) + parser->in_literal = 1; + + /* --bool or --no-bool */ + else if (spec->type == CLI_OPT_TYPE_BOOL && spec->value) + *((int *)spec->value) = !is_negated; + + /* --accumulate */ + else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value) + *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1; + + /* --switch */ + else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value) + *((int *)spec->value) = spec->switch_value; + + /* Parse values as "--foo=bar" or "--foo bar" */ + else if (spec->type == CLI_OPT_TYPE_VALUE) { + if (has_value) + opt->value = (char *)value; + else if ((parser->idx + 1) <= parser->args_len) + opt->value = parser->args[parser->idx++]; + + if (spec->value) + *((char **)spec->value) = opt->value; + } + + /* Required argument was not provided */ + if (spec->type == CLI_OPT_TYPE_VALUE && + !opt->value && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + opt->status = CLI_OPT_STATUS_MISSING_VALUE; + else + opt->status = CLI_OPT_STATUS_OK; + + consume_choices(opt->spec, parser); + +done: + return opt->status; +} + +static cli_opt_status_t parse_short(cli_opt *opt, cli_opt_parser *parser) +{ + const cli_opt_spec *spec; + char *arg = parser->args[parser->idx++]; + const char *value; + + opt->arg = arg; + + if ((spec = spec_for_short(&value, parser, &arg[1 + parser->in_short])) == NULL) { + opt->spec = NULL; + opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION; + goto done; + } + + opt->spec = spec; + + if (spec->type == CLI_OPT_TYPE_BOOL && spec->value) + *((int *)spec->value) = 1; + + else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value) + *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1; + + else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value) + *((int *)spec->value) = spec->switch_value; + + /* Parse values as "-ifoo" or "-i foo" */ + else if (spec->type == CLI_OPT_TYPE_VALUE) { + if (value) + opt->value = (char *)value; + else if ((parser->idx + 1) <= parser->args_len) + opt->value = parser->args[parser->idx++]; + + if (spec->value) + *((char **)spec->value) = opt->value; + } + + /* + * Handle compressed short arguments, like "-fbcd"; see if there's + * another character after the one we processed. If not, advance + * the parser index. + */ + if (spec->type != CLI_OPT_TYPE_VALUE && arg[2 + parser->in_short] != '\0') { + parser->in_short++; + parser->idx--; + } else { + parser->in_short = 0; + } + + /* Required argument was not provided */ + if (spec->type == CLI_OPT_TYPE_VALUE && !opt->value) + opt->status = CLI_OPT_STATUS_MISSING_VALUE; + else + opt->status = CLI_OPT_STATUS_OK; + + consume_choices(opt->spec, parser); + +done: + return opt->status; +} + +static cli_opt_status_t parse_arg(cli_opt *opt, cli_opt_parser *parser) +{ + const cli_opt_spec *spec = spec_for_arg(parser); + + opt->spec = spec; + opt->arg = parser->args[parser->idx]; + + if (!spec) { + parser->idx++; + opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION; + } else if (spec->type == CLI_OPT_TYPE_ARGS) { + if (spec->value) + *((char ***)spec->value) = &parser->args[parser->idx]; + + /* + * We have started a list of arguments; the remainder of + * given arguments need not be examined. + */ + parser->in_args = (parser->args_len - parser->idx); + parser->idx = parser->args_len; + opt->args_len = parser->in_args; + opt->status = CLI_OPT_STATUS_OK; + } else { + if (spec->value) + *((char **)spec->value) = parser->args[parser->idx]; + + parser->idx++; + opt->status = CLI_OPT_STATUS_OK; + } + + return opt->status; +} + +static int support_gnu_style(unsigned int flags) +{ + if ((flags & CLI_OPT_PARSE_FORCE_GNU) != 0) + return 1; + + if ((flags & CLI_OPT_PARSE_GNU) == 0) + return 0; + + /* TODO: Windows */ +#if defined(_WIN32) && defined(UNICODE) + if (_wgetenv(L"POSIXLY_CORRECT") != NULL) + return 0; +#else + if (getenv("POSIXLY_CORRECT") != NULL) + return 0; +#endif + + return 1; +} + +void cli_opt_parser_init( + cli_opt_parser *parser, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags) +{ + assert(parser); + + memset(parser, 0x0, sizeof(cli_opt_parser)); + + parser->specs = specs; + parser->args = args; + parser->args_len = args_len; + parser->flags = flags; + + parser->needs_sort = support_gnu_style(flags); +} + +GIT_INLINE(const cli_opt_spec *) spec_for_sort( + int *needs_value, + const cli_opt_parser *parser, + const char *arg) +{ + int is_negated, has_value = 0; + const char *value; + const cli_opt_spec *spec = NULL; + size_t idx = 0; + + *needs_value = 0; + + if (strncmp(arg, "--", 2) == 0) { + spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2]); + *needs_value = !has_value; + } + + else if (strncmp(arg, "-", 1) == 0) { + spec = spec_for_short(&value, parser, &arg[1]); + + /* + * Advance through compressed short arguments to see if + * the last one has a value, eg "-xvffilename". + */ + while (spec && !value && arg[1 + ++idx] != '\0') + spec = spec_for_short(&value, parser, &arg[1 + idx]); + + *needs_value = (value == NULL); + } + + return spec; +} + +/* + * Some parsers allow for handling arguments like "file1 --help file2"; + * this is done by re-sorting the arguments in-place; emulate that. + */ +static int sort_gnu_style(cli_opt_parser *parser) +{ + size_t i, j, insert_idx = parser->idx, offset; + const cli_opt_spec *spec; + char *option, *value; + int needs_value, changed = 0; + + parser->needs_sort = 0; + + for (i = parser->idx; i < parser->args_len; i++) { + spec = spec_for_sort(&needs_value, parser, parser->args[i]); + + /* Not a "-" or "--" prefixed option. No change. */ + if (!spec) + continue; + + /* A "--" alone means remaining args are literal. */ + if (spec->type == CLI_OPT_TYPE_LITERAL) + break; + + option = parser->args[i]; + + /* + * If the argument is a value type and doesn't already + * have a value (eg "--foo=bar" or "-fbar") then we need + * to copy the next argument as its value. + */ + if (spec->type == CLI_OPT_TYPE_VALUE && needs_value) { + /* + * A required value is not provided; set parser + * index to this value so that we fail on it. + */ + if (i + 1 >= parser->args_len) { + parser->idx = i; + return 1; + } + + value = parser->args[i + 1]; + offset = 1; + } else { + value = NULL; + offset = 0; + } + + /* Caller error if args[0] is an option. */ + if (i == 0) + return 0; + + /* Shift args up one (or two) and insert the option */ + for (j = i; j > insert_idx; j--) + parser->args[j + offset] = parser->args[j - 1]; + + parser->args[insert_idx] = option; + + if (value) + parser->args[insert_idx + 1] = value; + + insert_idx += (1 + offset); + i += offset; + + changed = 1; + } + + return changed; +} + +cli_opt_status_t cli_opt_parser_next(cli_opt *opt, cli_opt_parser *parser) +{ + assert(opt && parser); + + memset(opt, 0x0, sizeof(cli_opt)); + + if (parser->idx >= parser->args_len) { + opt->args_len = parser->in_args; + return CLI_OPT_STATUS_DONE; + } + + /* Handle options in long form, those beginning with "--" */ + if (strncmp(parser->args[parser->idx], "--", 2) == 0 && + !parser->in_short && + !parser->in_literal) + return parse_long(opt, parser); + + /* Handle options in short form, those beginning with "-" */ + else if (parser->in_short || + (strncmp(parser->args[parser->idx], "-", 1) == 0 && + !parser->in_literal)) + return parse_short(opt, parser); + + /* + * We've reached the first "bare" argument. In POSIX mode, all + * remaining items on the command line are arguments. In GNU + * mode, there may be long or short options after this. Sort any + * options up to this position then re-parse the current position. + */ + if (parser->needs_sort && sort_gnu_style(parser)) + return cli_opt_parser_next(opt, parser); + + return parse_arg(opt, parser); +} + +GIT_INLINE(int) spec_included(const cli_opt_spec **specs, const cli_opt_spec *spec) +{ + const cli_opt_spec **i; + + for (i = specs; *i; ++i) { + if (spec == *i) + return 1; + } + + return 0; +} + +static cli_opt_status_t validate_required( + cli_opt *opt, + const cli_opt_spec specs[], + const cli_opt_spec **given_specs) +{ + const cli_opt_spec *spec, *required; + int given; + + /* + * Iterate over the possible specs to identify requirements and + * ensure that those have been given on the command-line. + * Note that we can have required *choices*, where one in a + * list of choices must be specified. + */ + for (spec = specs, required = NULL, given = 0; spec->type; ++spec) { + if (!required && (spec->usage & CLI_OPT_USAGE_REQUIRED)) { + required = spec; + given = 0; + } else if (!required) { + continue; + } + + if (!given) + given = spec_included(given_specs, spec); + + /* + * Validate the requirement unless we're in a required + * choice. In that case, keep the required state and + * validate at the end of the choice list. + */ + if (!spec_is_choice(spec)) { + if (!given) { + opt->spec = required; + opt->status = CLI_OPT_STATUS_MISSING_ARGUMENT; + break; + } + + required = NULL; + given = 0; + } + } + + return opt->status; +} + +cli_opt_status_t cli_opt_parse( + cli_opt *opt, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags) +{ + cli_opt_parser parser; + const cli_opt_spec **given_specs; + size_t given_idx = 0; + + cli_opt_parser_init(&parser, specs, args, args_len, flags); + + given_specs = alloca(sizeof(const cli_opt_spec *) * (args_len + 1)); + + while (cli_opt_parser_next(opt, &parser)) { + if (opt->status != CLI_OPT_STATUS_OK && + opt->status != CLI_OPT_STATUS_DONE) + return opt->status; + + if ((opt->spec->usage & CLI_OPT_USAGE_STOP_PARSING)) + return (opt->status = CLI_OPT_STATUS_DONE); + + given_specs[given_idx++] = opt->spec; + } + + given_specs[given_idx] = NULL; + + return validate_required(opt, specs, given_specs); +} + +static int spec_name_fprint(FILE *file, const cli_opt_spec *spec) +{ + int error; + + if (spec->type == CLI_OPT_TYPE_ARG) + error = fprintf(file, "%s", spec->value_name); + else if (spec->type == CLI_OPT_TYPE_ARGS) + error = fprintf(file, "%s", spec->value_name); + else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + error = fprintf(file, "-%c", spec->alias); + else + error = fprintf(file, "--%s", spec->name); + + return error; +} + +int cli_opt_status_fprint( + FILE *file, + const char *command, + const cli_opt *opt) +{ + const cli_opt_spec *choice; + int error; + + if (command && (error = fprintf(file, "%s: ", command)) < 0) + return error; + + switch (opt->status) { + case CLI_OPT_STATUS_DONE: + error = fprintf(file, "finished processing arguments (no error)\n"); + break; + case CLI_OPT_STATUS_OK: + error = fprintf(file, "no error\n"); + break; + case CLI_OPT_STATUS_UNKNOWN_OPTION: + error = fprintf(file, "unknown option: %s\n", opt->arg); + break; + case CLI_OPT_STATUS_MISSING_VALUE: + if ((error = fprintf(file, "argument '")) < 0 || + (error = spec_name_fprint(file, opt->spec)) < 0 || + (error = fprintf(file, "' requires a value.\n")) < 0) + break; + break; + case CLI_OPT_STATUS_MISSING_ARGUMENT: + if (spec_is_choice(opt->spec)) { + int is_choice = 1; + + if (spec_is_choice((opt->spec)+1)) + error = fprintf(file, "one of"); + else + error = fprintf(file, "either"); + + if (error < 0) + break; + + for (choice = opt->spec; is_choice; ++choice) { + is_choice = spec_is_choice(choice); + + if (!is_choice) + error = fprintf(file, " or"); + else if (choice != opt->spec) + error = fprintf(file, ","); + + if ((error < 0) || + (error = fprintf(file, " '")) < 0 || + (error = spec_name_fprint(file, choice)) < 0 || + (error = fprintf(file, "'")) < 0) + break; + + if (!spec_is_choice(choice)) + break; + } + + if ((error < 0) || + (error = fprintf(file, " is required.\n")) < 0) + break; + } else { + if ((error = fprintf(file, "argument '")) < 0 || + (error = spec_name_fprint(file, opt->spec)) < 0 || + (error = fprintf(file, "' is required.\n")) < 0) + break; + } + + break; + default: + error = fprintf(file, "unknown status: %d\n", opt->status); + break; + } + + return error; +} + diff --git a/src/cli/opt.h b/src/cli/opt.h new file mode 100644 index 000000000..6c1d4603e --- /dev/null +++ b/src/cli/opt.h @@ -0,0 +1,349 @@ +/* + * Copyright (c), Edward Thomson + * All rights reserved. + * + * This file is part of adopt, distributed under the MIT license. + * For full terms and conditions, see the included LICENSE file. + * + * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT. + * + * This file was produced by using the `rename.pl` script included with + * adopt. The command-line specified was: + * + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage + */ + +#ifndef CLI_opt_h__ +#define CLI_opt_h__ + +#include +#include + +/** + * The type of argument to be parsed. + */ +typedef enum { + CLI_OPT_TYPE_NONE = 0, + + /** + * An option that, when specified, sets a given value to true. + * This is useful for options like "--debug". A negation + * option (beginning with "no-") is implicitly specified; for + * example "--no-debug". The `value` pointer in the returned + * option will be set to `1` when this is specified, and set to + * `0` when the negation "no-" option is specified. + */ + CLI_OPT_TYPE_BOOL, + + /** + * An option that, when specified, sets the given `value` pointer + * to the specified `switch_value`. This is useful for booleans + * where you do not want the implicit negation that comes with an + * `CLI_OPT_TYPE_BOOL`, or for switches that multiplex a value, like + * setting a mode. For example, `--read` may set the `value` to + * `MODE_READ` and `--write` may set the `value` to `MODE_WRITE`. + */ + CLI_OPT_TYPE_SWITCH, + + /** + * An option that, when specified, increments the given + * `value` by the given `switch_value`. This can be specified + * multiple times to continue to increment the `value`. + * (For example, "-vvv" to set verbosity to 3.) + */ + CLI_OPT_TYPE_ACCUMULATOR, + + /** + * An option that takes a value, for example `-n value`, + * `-nvalue`, `--name value` or `--name=value`. + */ + CLI_OPT_TYPE_VALUE, + + /** + * A bare "--" that indicates that arguments following this are + * literal. This allows callers to specify things that might + * otherwise look like options, for example to operate on a file + * named "-rf" then you can invoke "program -- -rf" to treat + * "-rf" as an argument not an option. + */ + CLI_OPT_TYPE_LITERAL, + + /** + * A single argument, not an option. When options are exhausted, + * arguments will be matches in the order that they're specified + * in the spec list. For example, if two `CLI_OPT_TYPE_ARGS` are + * specified, `input_file` and `output_file`, then the first bare + * argument on the command line will be `input_file` and the + * second will be `output_file`. + */ + CLI_OPT_TYPE_ARG, + + /** + * A collection of arguments. This is useful when you want to take + * a list of arguments, for example, multiple paths. When specified, + * the value will be set to the first argument in the list. + */ + CLI_OPT_TYPE_ARGS, +} cli_opt_type_t; + +/** + * Additional information about an option, including parsing + * restrictions and usage information to be displayed to the end-user. + */ +typedef enum { + /** Defaults for the argument. */ + CLI_OPT_USAGE_DEFAULT = 0, + + /** This argument is required. */ + CLI_OPT_USAGE_REQUIRED = (1u << 0), + + /** + * This is a multiple choice argument, combined with the previous + * argument. For example, when the previous argument is `-f` and + * this optional is applied to an argument of type `-b` then one + * of `-f` or `-b` may be specified. + */ + CLI_OPT_USAGE_CHOICE = (1u << 1), + + /** + * This argument short-circuits the remainder of parsing. + * Useful for arguments like `--help`. + */ + CLI_OPT_USAGE_STOP_PARSING = (1u << 2), + + /** The argument's value is optional ("-n" or "-n foo") */ + CLI_OPT_USAGE_VALUE_OPTIONAL = (1u << 3), + + /** This argument should not be displayed in usage. */ + CLI_OPT_USAGE_HIDDEN = (1u << 4), + + /** In usage, show the long format instead of the abbreviated format. */ + CLI_OPT_USAGE_SHOW_LONG = (1u << 5), +} cli_opt_usage_t; + +typedef enum { + /** Default parsing behavior. */ + CLI_OPT_PARSE_DEFAULT = 0, + + /** + * Parse with GNU `getopt_long` style behavior, where options can + * be intermixed with arguments at any position (for example, + * "file1 --help file2".) Like `getopt_long`, this can mutate the + * arguments given. + */ + CLI_OPT_PARSE_GNU = (1u << 0), + + /** + * Force GNU `getopt_long` style behavior; the `POSIXLY_CORRECT` + * environment variable is ignored. + */ + CLI_OPT_PARSE_FORCE_GNU = (1u << 1), +} cli_opt_flag_t; + +/** Specification for an available option. */ +typedef struct cli_opt_spec { + /** Type of option expected. */ + cli_opt_type_t type; + + /** Name of the long option. */ + const char *name; + + /** The alias is the short (one-character) option alias. */ + const char alias; + + /** + * If this spec is of type `CLI_OPT_TYPE_BOOL`, this is a pointer + * to an `int` that will be set to `1` if the option is specified. + * + * If this spec is of type `CLI_OPT_TYPE_SWITCH`, this is a pointer + * to an `int` that will be set to the opt's `switch_value` (below) + * when this option is specified. + * + * If this spec is of type `CLI_OPT_TYPE_ACCUMULATOR`, this is a + * pointer to an `int` that will be incremented by the opt's + * `switch_value` (below). If no `switch_value` is provided then + * the value will be incremented by 1. + * + * If this spec is of type `CLI_OPT_TYPE_VALUE`, + * `CLI_OPT_TYPE_VALUE_OPTIONAL`, or `CLI_OPT_TYPE_ARG`, this is + * a pointer to a `char *` that will be set to the value + * specified on the command line. + * + * If this spec is of type `CLI_OPT_TYPE_ARGS`, this is a pointer + * to a `char **` that will be set to the remaining values + * specified on the command line. + */ + void *value; + + /** + * If this spec is of type `CLI_OPT_TYPE_SWITCH`, this is the value + * to set in the option's `value` pointer when it is specified. If + * this spec is of type `CLI_OPT_TYPE_ACCUMULATOR`, this is the value + * to increment in the option's `value` pointer when it is + * specified. This is ignored for other opt types. + */ + int switch_value; + + /** + * Optional usage flags that change parsing behavior and how + * usage information is shown to the end-user. + */ + uint32_t usage; + + /** + * The name of the value, provided when creating usage information. + * This is required only for the functions that display usage + * information and only when a spec is of type `CLI_OPT_TYPE_VALUE, + * `CLI_OPT_TYPE_ARG` or `CLI_OPT_TYPE_ARGS``. + */ + const char *value_name; + + /** + * Optional short description of the option to display to the + * end-user. This is only used when creating usage information. + */ + const char *help; +} cli_opt_spec; + +/** Return value for `cli_opt_parser_next`. */ +typedef enum { + /** Parsing is complete; there are no more arguments. */ + CLI_OPT_STATUS_DONE = 0, + + /** + * This argument was parsed correctly; the `opt` structure is + * populated and the value pointer has been set. + */ + CLI_OPT_STATUS_OK = 1, + + /** + * The argument could not be parsed correctly, it does not match + * any of the specifications provided. + */ + CLI_OPT_STATUS_UNKNOWN_OPTION = 2, + + /** + * The argument matched a spec of type `CLI_OPT_VALUE`, but no value + * was provided. + */ + CLI_OPT_STATUS_MISSING_VALUE = 3, + + /** A required argument was not provided. */ + CLI_OPT_STATUS_MISSING_ARGUMENT = 4, +} cli_opt_status_t; + +/** An option provided on the command-line. */ +typedef struct cli_opt { + /** The status of parsing the most recent argument. */ + cli_opt_status_t status; + + /** + * The specification that was provided on the command-line, or + * `NULL` if the argument did not match an `cli_opt_spec`. + */ + const cli_opt_spec *spec; + + /** + * The argument as it was specified on the command-line, including + * dashes, eg, `-f` or `--foo`. + */ + char *arg; + + /** + * If the spec is of type `CLI_OPT_VALUE` or `CLI_OPT_VALUE_OPTIONAL`, + * this is the value provided to the argument. + */ + char *value; + + /** + * If the argument is of type `CLI_OPT_ARGS`, this is the number of + * arguments remaining. This value is persisted even when parsing + * is complete and `status` == `CLI_OPT_STATUS_DONE`. + */ + size_t args_len; +} cli_opt; + +/* The internal parser state. Callers should not modify this structure. */ +typedef struct cli_opt_parser { + const cli_opt_spec *specs; + char **args; + size_t args_len; + unsigned int flags; + + /* Parser state */ + size_t idx; + size_t arg_idx; + size_t in_args; + size_t in_short; + int needs_sort : 1, + in_literal : 1; +} cli_opt_parser; + +/** + * Parses all the command-line arguments and updates all the options using + * the pointers provided. Parsing stops on any invalid argument and + * information about the failure will be provided in the opt argument. + * + * This is the simplest way to parse options; it handles the initialization + * (`parser_init`) and looping (`parser_next`). + * + * @param opt The The `cli_opt` information that failed parsing + * @param specs A NULL-terminated array of `cli_opt_spec`s that can be parsed + * @param args The arguments that will be parsed + * @param args_len The length of arguments to be parsed + * @param flags The `cli_opt_flag_t flags for parsing + */ +cli_opt_status_t cli_opt_parse( + cli_opt *opt, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags); + +/** + * Initializes a parser that parses the given arguments according to the + * given specifications. + * + * @param parser The `cli_opt_parser` that will be initialized + * @param specs A NULL-terminated array of `cli_opt_spec`s that can be parsed + * @param args The arguments that will be parsed + * @param args_len The length of arguments to be parsed + * @param flags The `cli_opt_flag_t flags for parsing + */ +void cli_opt_parser_init( + cli_opt_parser *parser, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags); + +/** + * Parses the next command-line argument and places the information about + * the argument into the given `opt` data. + * + * @param opt The `cli_opt` information parsed from the argument + * @param parser An `cli_opt_parser` that has been initialized with + * `cli_opt_parser_init` + * @return true if the caller should continue iterating, or 0 if there are + * no arguments left to process. + */ +cli_opt_status_t cli_opt_parser_next( + cli_opt *opt, + cli_opt_parser *parser); + +/** + * Prints the status after parsing the most recent argument. This is + * useful for printing an error message when an unknown argument was + * specified, or when an argument was specified without a value. + * + * @param file The file to print information to + * @param command The name of the command to use when printing (optional) + * @param opt The option that failed to parse + * @return 0 on success, -1 on failure + */ +int cli_opt_status_fprint( + FILE *file, + const char *command, + const cli_opt *opt); + +#endif /* CLI_opt_h__ */ diff --git a/src/cli/opt_usage.c b/src/cli/opt_usage.c new file mode 100644 index 000000000..478b41631 --- /dev/null +++ b/src/cli/opt_usage.c @@ -0,0 +1,194 @@ +/* + * 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 "cli.h" +#include "str.h" + +static int print_spec_name(git_str *out, const cli_opt_spec *spec) +{ + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c <%s>", spec->alias, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c [<%s>]", spec->alias, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + return git_str_printf(out, "--%s[=<%s>]", spec->name, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE) + return git_str_printf(out, "--%s=<%s>", spec->name, spec->value_name); + if (spec->type == CLI_OPT_TYPE_ARG) + return git_str_printf(out, "<%s>", spec->value_name); + if (spec->type == CLI_OPT_TYPE_ARGS) + return git_str_printf(out, "<%s>...", spec->value_name); + if (spec->type == CLI_OPT_TYPE_LITERAL) + return git_str_printf(out, "--"); + if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c", spec->alias); + if (spec->name) + return git_str_printf(out, "--%s", spec->name); + + GIT_ASSERT(0); +} + +/* + * This is similar to adopt's function, but modified to understand + * that we have a command ("git") and a "subcommand" ("checkout"). + * It also understands a terminal's line length and wrap appropriately, + * using a `git_str` for storage. + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const char *subcommand, + const cli_opt_spec specs[]) +{ + git_str usage = GIT_BUF_INIT, opt = GIT_BUF_INIT; + const cli_opt_spec *spec; + size_t i, prefixlen, linelen; + bool choice = false, next_choice = false, optional = false; + int error; + + /* TODO: query actual console width. */ + int console_width = 80; + + if ((error = git_str_printf(&usage, "usage: %s", command)) < 0) + goto done; + + if (subcommand && + (error = git_str_printf(&usage, " %s", subcommand)) < 0) + goto done; + + linelen = git_str_len(&usage); + prefixlen = linelen + 1; + + for (spec = specs; spec->type; ++spec) { + if (!choice) + optional = !(spec->usage & CLI_OPT_USAGE_REQUIRED); + + next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); + + if (spec->usage & CLI_OPT_USAGE_HIDDEN) + continue; + + if (choice) + git_str_putc(&opt, '|'); + else + git_str_clear(&opt); + + if (optional && !choice) + git_str_putc(&opt, '['); + if (!optional && !choice && next_choice) + git_str_putc(&opt, '('); + + if ((error = print_spec_name(&opt, spec)) < 0) + goto done; + + if (!optional && choice && !next_choice) + git_str_putc(&opt, ')'); + else if (optional && !next_choice) + git_str_putc(&opt, ']'); + + if ((choice = next_choice)) + continue; + + if (git_str_oom(&opt)) { + error = -1; + goto done; + } + + if (linelen > prefixlen && + console_width > 0 && + linelen + git_str_len(&opt) + 1 > (size_t)console_width) { + git_str_putc(&usage, '\n'); + + for (i = 0; i < prefixlen; i++) + git_str_putc(&usage, ' '); + + linelen = prefixlen; + } else { + git_str_putc(&usage, ' '); + linelen += git_str_len(&opt) + 1; + } + + git_str_puts(&usage, git_str_cstr(&opt)); + + if (git_str_oom(&usage)) { + error = -1; + goto done; + } + } + + error = fprintf(file, "%s\n", git_str_cstr(&usage)); + +done: + error = (error < 0) ? -1 : 0; + + git_str_dispose(&usage); + git_str_dispose(&opt); + return error; +} + +int cli_opt_usage_error( + const char *subcommand, + const cli_opt_spec specs[], + const cli_opt *invalid_opt) +{ + cli_opt_status_fprint(stderr, PROGRAM_NAME, invalid_opt); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, subcommand, specs); + return CLI_EXIT_USAGE; +} + +int cli_opt_help_fprint( + FILE *file, + const cli_opt_spec specs[]) +{ + git_str help = GIT_BUF_INIT; + const cli_opt_spec *spec; + int error = 0; + + /* Display required arguments first */ + for (spec = specs; spec->type; ++spec) { + if (! (spec->usage & CLI_OPT_USAGE_REQUIRED) || + (spec->usage & CLI_OPT_USAGE_HIDDEN)) + continue; + + git_str_printf(&help, " "); + + if ((error = print_spec_name(&help, spec)) < 0) + goto done; + + git_str_printf(&help, ": %s\n", spec->help); + } + + /* Display the remaining arguments */ + for (spec = specs; spec->type; ++spec) { + if ((spec->usage & CLI_OPT_USAGE_REQUIRED) || + (spec->usage & CLI_OPT_USAGE_HIDDEN)) + continue; + + git_str_printf(&help, " "); + + if ((error = print_spec_name(&help, spec)) < 0) + goto done; + + git_str_printf(&help, ": %s\n", spec->help); + + } + + if (git_str_oom(&help) || + p_write(fileno(file), help.ptr, help.size) < 0) + error = -1; + +done: + error = (error < 0) ? -1 : 0; + + git_str_dispose(&help); + return error; +} + diff --git a/src/cli/opt_usage.h b/src/cli/opt_usage.h new file mode 100644 index 000000000..c752494e1 --- /dev/null +++ b/src/cli/opt_usage.h @@ -0,0 +1,35 @@ +/* + * 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 CLI_opt_usage_h__ +#define CLI_opt_usage_h__ + +/** + * Prints usage information to the given file handle. + * + * @param file The file to print information to + * @param command The name of the command to use when printing + * @param subcommand The name of the subcommand (eg "checkout") to use when printing, or NULL to skip + * @param specs The specifications allowed by the command + * @return 0 on success, -1 on failure + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const char *subcommand, + const cli_opt_spec specs[]); + +int cli_opt_usage_error( + const char *subcommand, + const cli_opt_spec specs[], + const cli_opt *invalid_opt); + +int cli_opt_help_fprint( + FILE *file, + const cli_opt_spec specs[]); + +#endif /* CLI_opt_usage_h__ */ diff --git a/src/cli/progress.c b/src/cli/progress.c new file mode 100644 index 000000000..ba52655e7 --- /dev/null +++ b/src/cli/progress.c @@ -0,0 +1,345 @@ +/* + * 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 +#include +#include + +#include "progress.h" +#include "error.h" + +/* + * Show updates to the percentage and number of objects received + * separately from the throughput to give an accurate progress while + * avoiding too much noise on the screen. + */ +#define PROGRESS_UPDATE_TIME 0.10 +#define THROUGHPUT_UPDATE_TIME 1.00 + +#define is_nl(c) ((c) == '\r' || (c) == '\n') + +#define return_os_error(msg) do { \ + git_error_set(GIT_ERROR_OS, "%s", msg); return -1; } while(0) + +GIT_INLINE(size_t) no_nl_len(const char *str, size_t len) +{ + size_t i = 0; + + while (i < len && !is_nl(str[i])) + i++; + + return i; +} + +GIT_INLINE(size_t) nl_len(bool *has_nl, const char *str, size_t len) +{ + size_t i = no_nl_len(str, len); + + *has_nl = false; + + while (i < len && is_nl(str[i])) { + *has_nl = true; + i++; + } + + return i; +} + +static int progress_write(cli_progress *progress, bool force, git_str *line) +{ + bool has_nl; + size_t no_nl = no_nl_len(line->ptr, line->size); + size_t nl = nl_len(&has_nl, line->ptr + no_nl, line->size - no_nl); + double now = git__timer(); + size_t i; + + /* Avoid spamming the console with progress updates */ + if (!force && line->ptr[line->size - 1] != '\n' && progress->last_update) { + if (now - progress->last_update < PROGRESS_UPDATE_TIME) { + git_str_clear(&progress->deferred); + git_str_put(&progress->deferred, line->ptr, line->size); + return git_str_oom(&progress->deferred) ? -1 : 0; + } + } + + /* + * If there's something on this line already (eg, a progress line + * with only a trailing `\r` that we'll print over) then we need + * to really print over it in case we're writing a shorter line. + */ + if (printf("%.*s", (int)no_nl, line->ptr) < 0) + return_os_error("could not print status"); + + if (progress->onscreen.size) { + for (i = no_nl; i < progress->onscreen.size; i++) { + if (printf(" ") < 0) + return_os_error("could not print status"); + } + } + + if (printf("%.*s", (int)nl, line->ptr + no_nl) < 0 || + fflush(stdout) != 0) + return_os_error("could not print status"); + + git_str_clear(&progress->onscreen); + + if (line->ptr[line->size - 1] == '\n') { + progress->last_update = 0; + } else { + git_str_put(&progress->onscreen, line->ptr, line->size); + progress->last_update = now; + } + + git_str_clear(&progress->deferred); + return git_str_oom(&progress->onscreen) ? -1 : 0; +} + +static int progress_printf(cli_progress *progress, bool force, const char *fmt, ...) + GIT_FORMAT_PRINTF(3, 4); + +int progress_printf(cli_progress *progress, bool force, const char *fmt, ...) +{ + git_str buf = GIT_BUF_INIT; + va_list ap; + int error; + + va_start(ap, fmt); + error = git_str_vprintf(&buf, fmt, ap); + va_end(ap); + + if (error < 0) + return error; + + error = progress_write(progress, force, &buf); + + git_str_dispose(&buf); + return error; +} + +static int progress_complete(cli_progress *progress) +{ + if (progress->deferred.size) + progress_write(progress, true, &progress->deferred); + + if (progress->onscreen.size) + if (printf("\n") < 0) + return_os_error("could not print status"); + + git_str_clear(&progress->deferred); + git_str_clear(&progress->onscreen); + progress->last_update = 0; + progress->action_start = 0; + progress->action_finish = 0; + + return 0; +} + +GIT_INLINE(int) percent(size_t completed, size_t total) +{ + if (total == 0) + return (completed == 0) ? 100 : 0; + + return (int)(((double)completed / (double)total) * 100); +} + +int cli_progress_fetch_sideband(const char *str, int len, void *payload) +{ + cli_progress *progress = (cli_progress *)payload; + size_t remain; + + if (len <= 0) + return 0; + + /* Accumulate the sideband data, then print it line-at-a-time. */ + if (git_str_put(&progress->sideband, str, len) < 0) + return -1; + + str = progress->sideband.ptr; + remain = progress->sideband.size; + + while (remain) { + bool has_nl; + size_t line_len = nl_len(&has_nl, str, remain); + + if (!has_nl) + break; + + if (line_len < INT_MAX) { + int error = progress_printf(progress, true, + "remote: %.*s", (int)line_len, str); + + if (error < 0) + return error; + } + + str += line_len; + remain -= line_len; + } + + git_str_consume_bytes(&progress->sideband, (progress->sideband.size - remain)); + + return 0; +} + +static int fetch_receiving( + cli_progress *progress, + const git_indexer_progress *stats) +{ + char *recv_units[] = { "B", "KiB", "MiB", "GiB", "TiB", NULL }; + char *rate_units[] = { "B/s", "KiB/s", "MiB/s", "GiB/s", "TiB/s", NULL }; + + double now, recv_len, rate, elapsed; + size_t recv_unit_idx = 0, rate_unit_idx = 0; + bool done = (stats->received_objects == stats->total_objects); + + if (!progress->action_start) + progress->action_start = git__timer(); + + if (done && progress->action_finish) + now = progress->action_finish; + else if (done) + progress->action_finish = now = git__timer(); + else + now = git__timer(); + + if (progress->throughput_update && + now - progress->throughput_update < THROUGHPUT_UPDATE_TIME) { + elapsed = progress->throughput_update - + progress->action_start; + recv_len = progress->throughput_bytes; + } else { + elapsed = now - progress->action_start; + recv_len = (double)stats->received_bytes; + + progress->throughput_update = now; + progress->throughput_bytes = recv_len; + } + + rate = elapsed ? recv_len / elapsed : 0; + + while (recv_len > 1024 && recv_units[recv_unit_idx+1]) { + recv_len /= 1024; + recv_unit_idx++; + } + + while (rate > 1024 && rate_units[rate_unit_idx+1]) { + rate /= 1024; + rate_unit_idx++; + } + + return progress_printf(progress, false, + "Receiving objects: %3d%% (%d/%d), %.2f %s | %.2f %s%s\r", + percent(stats->received_objects, stats->total_objects), + stats->received_objects, + stats->total_objects, + recv_len, recv_units[recv_unit_idx], + rate, rate_units[rate_unit_idx], + done ? ", done." : ""); +} + +static int fetch_resolving( + cli_progress *progress, + const git_indexer_progress *stats) +{ + bool done = (stats->indexed_deltas == stats->total_deltas); + + return progress_printf(progress, false, + "Resolving deltas: %3d%% (%d/%d)%s\r", + percent(stats->indexed_deltas, stats->total_deltas), + stats->indexed_deltas, stats->total_deltas, + done ? ", done." : ""); +} + +int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload) +{ + cli_progress *progress = (cli_progress *)payload; + int error = 0; + + switch (progress->action) { + case CLI_PROGRESS_NONE: + progress->action = CLI_PROGRESS_RECEIVING; + /* fall through */ + + case CLI_PROGRESS_RECEIVING: + if ((error = fetch_receiving(progress, stats)) < 0) + break; + + /* + * Upgrade from receiving to resolving; do this after the + * final call to cli_progress_fetch_receiving (above) to + * ensure that we've printed a final "done" string after + * any sideband data. + */ + if (!stats->indexed_deltas) + break; + + progress_complete(progress); + progress->action = CLI_PROGRESS_RESOLVING; + /* fall through */ + + case CLI_PROGRESS_RESOLVING: + error = fetch_resolving(progress, stats); + break; + + default: + /* should not be reached */ + GIT_ASSERT(!"unexpected progress state"); + } + + return error; +} + +void cli_progress_checkout( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + cli_progress *progress = (cli_progress *)payload; + bool done = (completed_steps == total_steps); + + GIT_UNUSED(path); + + if (progress->action != CLI_PROGRESS_CHECKING_OUT) { + progress_complete(progress); + progress->action = CLI_PROGRESS_CHECKING_OUT; + } + + progress_printf(progress, false, + "Checking out files: %3d%% (%" PRIuZ "/%" PRIuZ ")%s\r", + percent(completed_steps, total_steps), + completed_steps, total_steps, + done ? ", done." : ""); +} + +int cli_progress_abort(cli_progress *progress) +{ + if (progress->onscreen.size > 0 && printf("\n") < 0) + return_os_error("could not print status"); + + return 0; +} + +int cli_progress_finish(cli_progress *progress) +{ + int error = progress->action ? progress_complete(progress) : 0; + + progress->action = 0; + return error; +} + +void cli_progress_dispose(cli_progress *progress) +{ + if (progress == NULL) + return; + + git_str_dispose(&progress->sideband); + git_str_dispose(&progress->onscreen); + git_str_dispose(&progress->deferred); + + memset(progress, 0, sizeof(cli_progress)); +} diff --git a/src/cli/progress.h b/src/cli/progress.h new file mode 100644 index 000000000..7a445ec29 --- /dev/null +++ b/src/cli/progress.h @@ -0,0 +1,117 @@ +/* + * 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 CLI_progress_h__ +#define CLI_progress_h__ + +#include +#include "str.h" + +/* + * A general purpose set of progress printing functions. An individual + * `cli_progress` object is capable of displaying progress for a single + * function, even if that function displays multiple pieces of progress + * (like `git_clone`). `cli_progress_finish` should be called after + * any function invocation to re-set state. + */ + +typedef enum { + CLI_PROGRESS_NONE, + CLI_PROGRESS_RECEIVING, + CLI_PROGRESS_RESOLVING, + CLI_PROGRESS_CHECKING_OUT +} cli_progress_t; + +typedef struct { + cli_progress_t action; + + /* Actions may time themselves (eg fetch) but are not required to */ + double action_start; + double action_finish; + + /* Last console update, avoid too frequent updates. */ + double last_update; + + /* Accumulators for partial output and deferred updates. */ + git_str sideband; + git_str onscreen; + git_str deferred; + + /* Last update about throughput */ + double throughput_update; + double throughput_bytes; +} cli_progress; + +#define CLI_PROGRESS_INIT { 0 } + +/** + * Prints sideband data from fetch to the console. Suitable for a + * `sideband_progress` callback for `git_fetch_options`. + * + * @param str The sideband string + * @param len The length of the sideband string + * @param payload A pointer to the cli_progress + * @return 0 on success, -1 on failure + */ +extern int cli_progress_fetch_sideband( + const char *str, + int len, + void *payload); + +/** + * Prints fetch transfer statistics to the console. Suitable for a + * `transfer_progress` callback for `git_fetch_options`. + * + * @param stats The indexer stats + * @param payload A pointer to the cli_progress + * @return 0 on success, -1 on failure + */ +extern int cli_progress_fetch_transfer( + const git_indexer_progress *stats, + void *payload); + +/** + * Prints checkout progress to the console. Suitable for a + * `progress_cb` callback for `git_checkout_options`. + * + * @param path The path being written + * @param completed_steps The completed checkout steps + * @param total_steps The total number of checkout steps + * @param payload A pointer to the cli_progress + */ +extern void cli_progress_checkout( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload); + +/** + * Stop displaying progress quickly; suitable for stopping an application + * quickly. Does not display any lines that were buffered, just gets the + * console back to a sensible place. + * + * @param progress The progress information + * @return 0 on success, -1 on failure + */ +extern int cli_progress_abort(cli_progress *progress); + +/** + * Finishes displaying progress; flushes any buffered output. + * + * @param progress The progress information + * @return 0 on success, -1 on failure + */ +extern int cli_progress_finish(cli_progress *progress); + +/** + * Disposes the progress information. + * + * @param progress The progress information + */ +extern void cli_progress_dispose(cli_progress *progress); + +#endif /* CLI_progress_h__ */ diff --git a/src/cli/sighandler.h b/src/cli/sighandler.h new file mode 100644 index 000000000..877223e02 --- /dev/null +++ b/src/cli/sighandler.h @@ -0,0 +1,20 @@ +/* + * 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 CLI_sighandler_h__ +#define CLI_sighandler_h__ + +/** + * Sets up a signal handler that will run when the process is interrupted + * (via SIGINT on POSIX or Control-C or Control-Break on Windows). + * + * @param handler The function to run on interrupt + * @return 0 on success, -1 on failure + */ +int cli_sighandler_set_interrupt(void (*handler)(void)); + +#endif /* CLI_sighandler_h__ */ diff --git a/src/cli/unix/sighandler.c b/src/cli/unix/sighandler.c new file mode 100644 index 000000000..6b4982d48 --- /dev/null +++ b/src/cli/unix/sighandler.c @@ -0,0 +1,36 @@ +/* + * 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 +#include +#include "git2_util.h" +#include "cli.h" + +static void (*interrupt_handler)(void) = NULL; + +static void interrupt_proxy(int signal) +{ + GIT_UNUSED(signal); + interrupt_handler(); +} + +int cli_sighandler_set_interrupt(void (*handler)(void)) +{ + void (*result)(int); + + if ((interrupt_handler = handler) != NULL) + result = signal(SIGINT, interrupt_proxy); + else + result = signal(SIGINT, SIG_DFL); + + if (result == SIG_ERR) { + git_error_set(GIT_ERROR_OS, "could not set signal handler"); + return -1; + } + + return 0; +} diff --git a/src/cli/win32/precompiled.c b/src/cli/win32/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/src/cli/win32/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/src/cli/win32/precompiled.h b/src/cli/win32/precompiled.h new file mode 100644 index 000000000..b0309b864 --- /dev/null +++ b/src/cli/win32/precompiled.h @@ -0,0 +1,3 @@ +#include + +#include "cli.h" diff --git a/src/cli/win32/sighandler.c b/src/cli/win32/sighandler.c new file mode 100644 index 000000000..cc0b64640 --- /dev/null +++ b/src/cli/win32/sighandler.c @@ -0,0 +1,37 @@ +/* + * 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_util.h" +#include + +#include "cli.h" + +static void (*interrupt_handler)(void) = NULL; + +static BOOL WINAPI interrupt_proxy(DWORD signal) +{ + GIT_UNUSED(signal); + interrupt_handler(); + return TRUE; +} + +int cli_sighandler_set_interrupt(void (*handler)(void)) +{ + BOOL result; + + if ((interrupt_handler = handler) != NULL) + result = SetConsoleCtrlHandler(interrupt_proxy, FALSE); + else + result = SetConsoleCtrlHandler(NULL, FALSE); + + if (!result) { + git_error_set(GIT_ERROR_OS, "could not set control control handler"); + return -1; + } + + return 0; +} diff --git a/src/clone.c b/src/clone.c deleted file mode 100644 index 1843875f8..000000000 --- a/src/clone.c +++ /dev/null @@ -1,655 +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 "clone.h" - -#include "git2/clone.h" -#include "git2/remote.h" -#include "git2/revparse.h" -#include "git2/branch.h" -#include "git2/config.h" -#include "git2/checkout.h" -#include "git2/commit.h" -#include "git2/tree.h" - -#include "remote.h" -#include "futils.h" -#include "refs.h" -#include "fs_path.h" -#include "repository.h" -#include "odb.h" - -static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link); - -static int create_branch( - git_reference **branch, - git_repository *repo, - const git_oid *target, - const char *name, - const char *log_message) -{ - git_commit *head_obj = NULL; - git_reference *branch_ref = NULL; - git_str refname = GIT_STR_INIT; - int error; - - /* Find the target commit */ - if ((error = git_commit_lookup(&head_obj, repo, target)) < 0) - return error; - - /* Create the new branch */ - if ((error = git_str_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0) - return error; - - error = git_reference_create(&branch_ref, repo, git_str_cstr(&refname), target, 0, log_message); - git_str_dispose(&refname); - git_commit_free(head_obj); - - if (!error) - *branch = branch_ref; - else - git_reference_free(branch_ref); - - return error; -} - -static int setup_tracking_config( - git_repository *repo, - const char *branch_name, - const char *remote_name, - const char *merge_target) -{ - git_config *cfg; - git_str remote_key = GIT_STR_INIT, merge_key = GIT_STR_INIT; - int error = -1; - - if (git_repository_config__weakptr(&cfg, repo) < 0) - return -1; - - if (git_str_printf(&remote_key, "branch.%s.remote", branch_name) < 0) - goto cleanup; - - if (git_str_printf(&merge_key, "branch.%s.merge", branch_name) < 0) - goto cleanup; - - if (git_config_set_string(cfg, git_str_cstr(&remote_key), remote_name) < 0) - goto cleanup; - - if (git_config_set_string(cfg, git_str_cstr(&merge_key), merge_target) < 0) - goto cleanup; - - error = 0; - -cleanup: - git_str_dispose(&remote_key); - git_str_dispose(&merge_key); - return error; -} - -static int create_tracking_branch( - git_reference **branch, - git_repository *repo, - const git_oid *target, - const char *branch_name, - const char *log_message) -{ - int error; - - if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0) - return error; - - return setup_tracking_config( - repo, - branch_name, - GIT_REMOTE_ORIGIN, - git_reference_name(*branch)); -} - -static int update_head_to_new_branch( - git_repository *repo, - const git_oid *target, - const char *name, - const char *reflog_message) -{ - git_reference *tracking_branch = NULL; - int error; - - if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) - name += strlen(GIT_REFS_HEADS_DIR); - - error = create_tracking_branch(&tracking_branch, repo, target, name, - reflog_message); - - if (!error) - error = git_repository_set_head( - repo, git_reference_name(tracking_branch)); - - git_reference_free(tracking_branch); - - /* if it already existed, then the user's refspec created it for us, ignore it' */ - if (error == GIT_EEXISTS) - error = 0; - - return error; -} - -static int update_head_to_default(git_repository *repo) -{ - git_str initialbranch = GIT_STR_INIT; - const char *branch_name; - int error = 0; - - if ((error = git_repository_initialbranch(&initialbranch, repo)) < 0) - goto done; - - if (git__prefixcmp(initialbranch.ptr, GIT_REFS_HEADS_DIR) != 0) { - git_error_set(GIT_ERROR_INVALID, "invalid initial branch '%s'", initialbranch.ptr); - error = -1; - goto done; - } - - branch_name = initialbranch.ptr + strlen(GIT_REFS_HEADS_DIR); - - error = setup_tracking_config(repo, branch_name, GIT_REMOTE_ORIGIN, - initialbranch.ptr); - -done: - git_str_dispose(&initialbranch); - return error; -} - -static int update_remote_head( - git_repository *repo, - git_remote *remote, - git_str *target, - const char *reflog_message) -{ - git_refspec *refspec; - git_reference *remote_head = NULL; - git_str remote_head_name = GIT_STR_INIT; - git_str remote_branch_name = GIT_STR_INIT; - int error; - - /* Determine the remote tracking ref name from the local branch */ - refspec = git_remote__matching_refspec(remote, git_str_cstr(target)); - - if (refspec == NULL) { - git_error_set(GIT_ERROR_NET, "the remote's default branch does not fit the refspec configuration"); - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - if ((error = git_refspec__transform( - &remote_branch_name, - refspec, - git_str_cstr(target))) < 0) - goto cleanup; - - if ((error = git_str_printf(&remote_head_name, - "%s%s/%s", - GIT_REFS_REMOTES_DIR, - git_remote_name(remote), - GIT_HEAD_FILE)) < 0) - goto cleanup; - - error = git_reference_symbolic_create( - &remote_head, - repo, - git_str_cstr(&remote_head_name), - git_str_cstr(&remote_branch_name), - true, - reflog_message); - -cleanup: - git_reference_free(remote_head); - git_str_dispose(&remote_branch_name); - git_str_dispose(&remote_head_name); - return error; -} - -static int update_head_to_remote( - git_repository *repo, - git_remote *remote, - const char *reflog_message) -{ - int error = 0; - size_t refs_len; - const git_remote_head *remote_head, **refs; - const git_oid *remote_head_id; - git_str branch = GIT_STR_INIT; - - if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) - return error; - - /* We cloned an empty repository or one with an unborn HEAD */ - if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE)) - return update_head_to_default(repo); - - /* We know we have HEAD, let's see where it points */ - remote_head = refs[0]; - GIT_ASSERT(remote_head); - - remote_head_id = &remote_head->oid; - - error = git_remote__default_branch(&branch, remote); - if (error == GIT_ENOTFOUND) { - error = git_repository_set_head_detached( - repo, remote_head_id); - goto cleanup; - } - - if ((error = update_remote_head(repo, remote, &branch, reflog_message)) < 0) - goto cleanup; - - error = update_head_to_new_branch( - repo, - remote_head_id, - git_str_cstr(&branch), - reflog_message); - -cleanup: - git_str_dispose(&branch); - - return error; -} - -static int update_head_to_branch( - git_repository *repo, - git_remote *remote, - const char *branch, - const char *reflog_message) -{ - int retcode; - git_str remote_branch_name = GIT_STR_INIT; - git_reference *remote_ref = NULL; - git_str default_branch = GIT_STR_INIT; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(branch); - - if ((retcode = git_str_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", - git_remote_name(remote), branch)) < 0 ) - goto cleanup; - - if ((retcode = git_reference_lookup(&remote_ref, repo, git_str_cstr(&remote_branch_name))) < 0) - goto cleanup; - - if ((retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch, - reflog_message)) < 0) - goto cleanup; - - if ((retcode = git_remote__default_branch(&default_branch, remote)) < 0) - goto cleanup; - - if (!git_remote__matching_refspec(remote, git_str_cstr(&default_branch))) - goto cleanup; - - retcode = update_remote_head(repo, remote, &default_branch, reflog_message); - -cleanup: - git_reference_free(remote_ref); - git_str_dispose(&remote_branch_name); - git_str_dispose(&default_branch); - return retcode; -} - -static int default_repository_create(git_repository **out, const char *path, int bare, void *payload) -{ - GIT_UNUSED(payload); - - return git_repository_init(out, path, bare); -} - -static int default_remote_create( - git_remote **out, - git_repository *repo, - const char *name, - const char *url, - void *payload) -{ - GIT_UNUSED(payload); - - return git_remote_create(out, repo, name, url); -} - -/* - * submodules? - */ - -static int create_and_configure_origin( - git_remote **out, - git_repository *repo, - const char *url, - const git_clone_options *options) -{ - int error; - git_remote *origin = NULL; - char buf[GIT_PATH_MAX]; - git_remote_create_cb remote_create = options->remote_cb; - void *payload = options->remote_cb_payload; - - /* If the path exists and is a dir, the url should be the absolute path */ - if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) { - if (p_realpath(url, buf) == NULL) - return -1; - - url = buf; - } - - if (!remote_create) { - remote_create = default_remote_create; - payload = NULL; - } - - if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0) - goto on_error; - - *out = origin; - return 0; - -on_error: - git_remote_free(origin); - return error; -} - -static bool should_checkout( - git_repository *repo, - bool is_bare, - const git_checkout_options *opts) -{ - if (is_bare) - return false; - - if (!opts) - return false; - - if (opts->checkout_strategy == GIT_CHECKOUT_NONE) - return false; - - return !git_repository_head_unborn(repo); -} - -static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) -{ - int error; - - if (branch) - error = update_head_to_branch(repo, remote, branch, reflog_message); - /* Point HEAD to the same ref as the remote's head */ - else - error = update_head_to_remote(repo, remote, reflog_message); - - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) - error = git_checkout_head(repo, co_opts); - - return error; -} - -static int clone_into(git_repository *repo, git_remote *_remote, const git_fetch_options *opts, const git_checkout_options *co_opts, const char *branch) -{ - int error; - git_str reflog_message = GIT_STR_INIT; - git_fetch_options fetch_opts; - git_remote *remote; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(_remote); - - if (!git_repository_is_empty(repo)) { - git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); - return -1; - } - - if ((error = git_remote_dup(&remote, _remote)) < 0) - return error; - - memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); - fetch_opts.update_fetchhead = 0; - fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - - if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_str_cstr(&reflog_message))) != 0) - goto cleanup; - - error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); - -cleanup: - git_remote_free(remote); - git_str_dispose(&reflog_message); - - return error; -} - -int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) -{ - git_str fromurl = GIT_STR_INIT; - const char *path = url_or_path; - bool is_url, is_local; - - if (local == GIT_CLONE_NO_LOCAL) - return 0; - - if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) { - if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) { - is_local = -1; - goto done; - } - - path = fromurl.ptr; - } - - is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && - git_fs_path_isdir(path); - -done: - git_str_dispose(&fromurl); - return is_local; -} - -static int git__clone( - git_repository **out, - const char *url, - const char *local_path, - const git_clone_options *_options, - int use_existing) -{ - int error = 0; - git_repository *repo = NULL; - git_remote *origin; - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; - git_repository_create_cb repository_cb; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(local_path); - - if (_options) - memcpy(&options, _options, sizeof(git_clone_options)); - - GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); - - /* Only clone to a new directory or an empty directory */ - if (git_fs_path_exists(local_path) && !use_existing && !git_fs_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; - } - - /* Only remove the root directory on failure if we create it */ - if (git_fs_path_exists(local_path)) - rmdir_flags |= GIT_RMDIR_SKIP_ROOT; - - if (options.repository_cb) - repository_cb = options.repository_cb; - else - repository_cb = default_repository_create; - - if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0) - return error; - - if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - int clone_local = git_clone__should_clone_local(url, options.local); - int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; - - if (clone_local == 1) - error = clone_local_into( - repo, origin, &options.fetch_opts, &options.checkout_opts, - options.checkout_branch, link); - else if (clone_local == 0) - error = clone_into( - repo, origin, &options.fetch_opts, &options.checkout_opts, - options.checkout_branch); - else - error = -1; - - git_remote_free(origin); - } - - if (error != 0) { - git_error_state last_error = {0}; - git_error_state_capture(&last_error, error); - - git_repository_free(repo); - repo = NULL; - - (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); - - git_error_state_restore(&last_error); - } - - *out = repo; - return error; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_clone_init_options(git_clone_options *opts, unsigned int version) -{ - return git_clone_options_init(opts, version); -} -#endif - -static bool can_link(const char *src, const char *dst, int link) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(src); - GIT_UNUSED(dst); - GIT_UNUSED(link); - return false; -#else - - struct stat st_src, st_dst; - - if (!link) - return false; - - if (p_stat(src, &st_src) < 0) - return false; - - if (p_stat(dst, &st_dst) < 0) - return false; - - return st_src.st_dev == st_dst.st_dev; -#endif -} - -static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link) -{ - int error, flags; - git_repository *src; - git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; - git_str reflog_message = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(remote); - - if (!git_repository_is_empty(repo)) { - git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); - return -1; - } - - /* - * Let's figure out what path we should use for the source - * repo, if it's not rooted, the path should be relative to - * the repository's worktree/gitdir. - */ - if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) - return error; - - /* Copy .git/objects/ from the source to the target */ - if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { - git_str_dispose(&src_path); - return error; - } - - if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || - git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { - error = -1; - goto cleanup; - } - - flags = 0; - if (can_link(git_repository_path(src), git_repository_path(repo), link)) - flags |= GIT_CPDIR_LINK_FILES; - - error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), - flags, GIT_OBJECT_DIR_MODE); - - /* - * can_link() doesn't catch all variations, so if we hit an - * error and did want to link, let's try again without trying - * to link. - */ - if (error < 0 && link) { - flags &= ~GIT_CPDIR_LINK_FILES; - error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), - flags, GIT_OBJECT_DIR_MODE); - } - - if (error < 0) - goto cleanup; - - git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - - if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_str_cstr(&reflog_message))) != 0) - goto cleanup; - - error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); - -cleanup: - git_str_dispose(&reflog_message); - git_str_dispose(&src_path); - git_str_dispose(&src_odb); - git_str_dispose(&dst_odb); - git_repository_free(src); - return error; -} diff --git a/src/clone.h b/src/clone.h deleted file mode 100644 index 7d73cabd5..000000000 --- a/src/clone.h +++ /dev/null @@ -1,20 +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_clone_h__ -#define INCLUDE_clone_h__ - -#include "common.h" - -#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 deleted file mode 100644 index b137463f3..000000000 --- a/src/commit.c +++ /dev/null @@ -1,1042 +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 "commit.h" - -#include "git2/common.h" -#include "git2/object.h" -#include "git2/repository.h" -#include "git2/signature.h" -#include "git2/mailmap.h" -#include "git2/sys/commit.h" - -#include "buf.h" -#include "odb.h" -#include "commit.h" -#include "signature.h" -#include "refs.h" -#include "object.h" -#include "array.h" -#include "oidarray.h" - -void git_commit__free(void *_commit) -{ - git_commit *commit = _commit; - - git_array_clear(commit->parent_ids); - - git_signature_free(commit->author); - git_signature_free(commit->committer); - - git__free(commit->raw_header); - git__free(commit->raw_message); - git__free(commit->message_encoding); - git__free(commit->summary); - git__free(commit->body); - - git__free(commit); -} - -static int git_commit__create_buffer_internal( - git_str *out, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - git_array_oid_t *parents) -{ - size_t i = 0; - const git_oid *parent; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(tree); - - git_oid__writebuf(out, "tree ", tree); - - for (i = 0; i < git_array_size(*parents); i++) { - parent = git_array_get(*parents, i); - git_oid__writebuf(out, "parent ", parent); - } - - git_signature__writebuf(out, "author ", author); - git_signature__writebuf(out, "committer ", committer); - - if (message_encoding != NULL) - git_str_printf(out, "encoding %s\n", message_encoding); - - git_str_putc(out, '\n'); - - if (git_str_puts(out, message) < 0) - goto on_error; - - return 0; - -on_error: - git_str_dispose(out); - return -1; -} - -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) -{ - size_t i; - int error; - git_oid *parent_cpy; - const git_oid *parent; - - if (validate && !git_object__is_valid(repo, tree, GIT_OBJECT_TREE)) - return -1; - - i = 0; - while ((parent = parent_cb(i, parent_payload)) != NULL) { - if (validate && !git_object__is_valid(repo, parent, GIT_OBJECT_COMMIT)) { - error = -1; - goto on_error; - } - - parent_cpy = git_array_alloc(*parents); - GIT_ERROR_CHECK_ALLOC(parent_cpy); - - git_oid_cpy(parent_cpy, parent); - i++; - } - - if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) { - git_error_set(GIT_ERROR_OBJECT, "failed to create commit: current tip is not the first parent"); - error = GIT_EMODIFIED; - goto on_error; - } - - return 0; - -on_error: - git_array_clear(*parents); - return error; -} - -static int git_commit__create_internal( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - git_commit_parent_callback parent_cb, - void *parent_payload, - bool validate) -{ - int error; - git_odb *odb; - git_reference *ref = NULL; - git_str buf = GIT_STR_INIT; - const git_oid *current_id = NULL; - git_array_oid_t parents = GIT_ARRAY_INIT; - - if (update_ref) { - error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); - if (error < 0 && error != GIT_ENOTFOUND) - return error; - } - git_error_clear(); - - if (ref) - current_id = git_reference_target(ref); - - if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0) - goto cleanup; - - error = git_commit__create_buffer_internal(&buf, author, committer, - message_encoding, message, tree, - &parents); - - if (error < 0) - goto cleanup; - - if (git_repository_odb__weakptr(&odb, repo) < 0) - goto cleanup; - - if (git_odb__freshen(odb, tree) < 0) - goto cleanup; - - if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0) - goto cleanup; - - - if (update_ref != NULL) { - error = git_reference__update_for_commit( - repo, ref, update_ref, id, "commit"); - goto cleanup; - } - -cleanup: - git_array_clear(parents); - git_reference_free(ref); - git_str_dispose(&buf); - return error; -} - -int git_commit_create_from_callback( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - git_commit_parent_callback parent_cb, - void *parent_payload) -{ - return git_commit__create_internal( - id, repo, update_ref, author, committer, message_encoding, message, - tree, parent_cb, parent_payload, true); -} - -typedef struct { - size_t total; - va_list args; -} commit_parent_varargs; - -static const git_oid *commit_parent_from_varargs(size_t curr, void *payload) -{ - commit_parent_varargs *data = payload; - const git_commit *commit; - if (curr >= data->total) - return NULL; - commit = va_arg(data->args, const git_commit *); - return commit ? git_commit_id(commit) : NULL; -} - -int git_commit_create_v( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - ...) -{ - int error = 0; - commit_parent_varargs data; - - GIT_ASSERT_ARG(tree); - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - - data.total = parent_count; - va_start(data.args, parent_count); - - error = git_commit__create_internal( - id, repo, update_ref, author, committer, - message_encoding, message, git_tree_id(tree), - commit_parent_from_varargs, &data, false); - - va_end(data.args); - return error; -} - -typedef struct { - size_t total; - const git_oid **parents; -} commit_parent_oids; - -static const git_oid *commit_parent_from_ids(size_t curr, void *payload) -{ - commit_parent_oids *data = payload; - return (curr < data->total) ? data->parents[curr] : NULL; -} - -int git_commit_create_from_ids( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - size_t parent_count, - const git_oid *parents[]) -{ - commit_parent_oids data = { parent_count, parents }; - - return git_commit__create_internal( - id, repo, update_ref, author, committer, - message_encoding, message, tree, - commit_parent_from_ids, &data, true); -} - -typedef struct { - size_t total; - const git_commit **parents; - git_repository *repo; -} commit_parent_data; - -static const git_oid *commit_parent_from_array(size_t curr, void *payload) -{ - commit_parent_data *data = payload; - const git_commit *commit; - if (curr >= data->total) - return NULL; - commit = data->parents[curr]; - if (git_commit_owner(commit) != data->repo) - return NULL; - return git_commit_id(commit); -} - -int git_commit_create( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]) -{ - commit_parent_data data = { parent_count, parents, repo }; - - GIT_ASSERT_ARG(tree); - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - - return git_commit__create_internal( - id, repo, update_ref, author, committer, - message_encoding, message, git_tree_id(tree), - commit_parent_from_array, &data, false); -} - -static const git_oid *commit_parent_for_amend(size_t curr, void *payload) -{ - const git_commit *commit_to_amend = payload; - if (curr >= git_array_size(commit_to_amend->parent_ids)) - return NULL; - return git_array_get(commit_to_amend->parent_ids, curr); -} - -int git_commit_amend( - git_oid *id, - const git_commit *commit_to_amend, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree) -{ - git_repository *repo; - git_oid tree_id; - git_reference *ref; - int error; - - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(commit_to_amend); - - repo = git_commit_owner(commit_to_amend); - - if (!author) - author = git_commit_author(commit_to_amend); - if (!committer) - committer = git_commit_committer(commit_to_amend); - if (!message_encoding) - message_encoding = git_commit_message_encoding(commit_to_amend); - if (!message) - message = git_commit_message(commit_to_amend); - - if (!tree) { - git_tree *old_tree; - GIT_ERROR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) ); - git_oid_cpy(&tree_id, git_tree_id(old_tree)); - git_tree_free(old_tree); - } else { - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - git_oid_cpy(&tree_id, git_tree_id(tree)); - } - - if (update_ref) { - if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0) - return error; - - if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) { - git_reference_free(ref); - git_error_set(GIT_ERROR_REFERENCE, "commit to amend is not the tip of the given branch"); - return -1; - } - } - - error = git_commit__create_internal( - id, repo, NULL, author, committer, message_encoding, message, - &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false); - - if (!error && update_ref) { - error = git_reference__update_for_commit( - repo, ref, NULL, id, "commit"); - git_reference_free(ref); - } - - return error; -} - -static int commit_parse(git_commit *commit, const char *data, size_t size, unsigned int flags) -{ - 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; - int error; - - GIT_ASSERT_ARG(commit); - GIT_ASSERT_ARG(data); - - buffer = buffer_start; - - /* Allocate for one, which will allow not to realloc 90% of the time */ - git_array_init_to_size(commit->parent_ids, 1); - GIT_ERROR_CHECK_ARRAY(commit->parent_ids); - - /* The tree is always the first field */ - 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! - */ - - while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { - git_oid *new_id = git_array_alloc(commit->parent_ids); - GIT_ERROR_CHECK_ALLOC(new_id); - - git_oid_cpy(new_id, &parent_id); - } - - if (!(flags & GIT_COMMIT_PARSE_QUICK)) { - commit->author = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(commit->author); - - if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < 0) - return error; - } - - /* Some tools create multiple author fields, ignore the extra ones */ - while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) { - if ((error = git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n')) < 0) - return error; - - git__free(dummy_sig.name); - git__free(dummy_sig.email); - } - - /* Always parse the committer; we need the commit time */ - commit->committer = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(commit->committer); - - if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < 0) - return error; - - if (flags & GIT_COMMIT_PARSE_QUICK) - return 0; - - /* Parse add'l header entries */ - while (buffer < buffer_end) { - const char *eoln = buffer; - if (buffer[-1] == '\n' && buffer[0] == '\n') - break; - - while (eoln < buffer_end && *eoln != '\n') - ++eoln; - - if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { - buffer += strlen("encoding "); - - commit->message_encoding = git__strndup(buffer, eoln - buffer); - GIT_ERROR_CHECK_ALLOC(commit->message_encoding); - } - - if (eoln < buffer_end && *eoln == '\n') - ++eoln; - buffer = eoln; - } - - header_len = buffer - buffer_start; - commit->raw_header = git__strndup(buffer_start, header_len); - GIT_ERROR_CHECK_ALLOC(commit->raw_header); - - /* point "buffer" to data after header, +1 for the final LF */ - buffer = buffer_start + header_len + 1; - - /* extract commit message */ - if (buffer <= buffer_end) - commit->raw_message = git__strndup(buffer, buffer_end - buffer); - else - commit->raw_message = git__strdup(""); - GIT_ERROR_CHECK_ALLOC(commit->raw_message); - - return 0; - -bad_buffer: - git_error_set(GIT_ERROR_OBJECT, "failed to parse bad commit object"); - return GIT_EINVALID; -} - -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_ext(_commit, odb_obj, 0); -} - -#define GIT_COMMIT_GETTER(_rvalue, _name, _return, _invalid) \ - _rvalue git_commit_##_name(const git_commit *commit) \ - {\ - GIT_ASSERT_ARG_WITH_RETVAL(commit, _invalid); \ - return _return; \ - } - -GIT_COMMIT_GETTER(const git_signature *, author, commit->author, NULL) -GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer, NULL) -GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message, NULL) -GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding, NULL) -GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header, NULL) -GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time, INT64_MIN) -GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset, -1) -GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids), 0) -GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id, NULL) - -const char *git_commit_message(const git_commit *commit) -{ - const char *message; - - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - message = commit->raw_message; - - /* trim leading newlines from raw message */ - while (*message && *message == '\n') - ++message; - - return message; -} - -const char *git_commit_summary(git_commit *commit) -{ - git_str summary = GIT_STR_INIT; - const char *msg, *space, *next; - bool space_contains_newline = false; - - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - if (!commit->summary) { - for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { - char next_character = msg[0]; - /* stop processing at the end of the first paragraph */ - if (next_character == '\n') { - if (!msg[1]) - break; - if (msg[1] == '\n') - break; - /* stop processing if next line contains only whitespace */ - next = msg + 1; - while (*next && git__isspace_nonlf(*next)) { - ++next; - } - if (!*next || *next == '\n') - break; - } - /* record the beginning of contiguous whitespace runs */ - if (git__isspace(next_character)) { - if(space == NULL) { - space = msg; - space_contains_newline = false; - } - space_contains_newline |= next_character == '\n'; - } - /* the next character is non-space */ - else { - /* process any recorded whitespace */ - if (space) { - if(space_contains_newline) - git_str_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */ - else - git_str_put(&summary, space, (msg - space)); /* otherwise copy it */ - space = NULL; - } - /* copy the next character */ - git_str_putc(&summary, next_character); - } - } - - commit->summary = git_str_detach(&summary); - if (!commit->summary) - commit->summary = git__strdup(""); - } - - return commit->summary; -} - -const char *git_commit_body(git_commit *commit) -{ - const char *msg, *end; - - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - if (!commit->body) { - /* search for end of summary */ - for (msg = git_commit_message(commit); *msg; ++msg) - if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) - break; - - /* trim leading and trailing whitespace */ - for (; *msg; ++msg) - if (!git__isspace(*msg)) - break; - for (end = msg + strlen(msg) - 1; msg <= end; --end) - if (!git__isspace(*end)) - break; - - if (*msg) - commit->body = git__strndup(msg, end - msg + 1); - } - - return commit->body; -} - -int git_commit_tree(git_tree **tree_out, const git_commit *commit) -{ - GIT_ASSERT_ARG(commit); - return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id); -} - -const git_oid *git_commit_parent_id( - const git_commit *commit, unsigned int n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - return git_array_get(commit->parent_ids, n); -} - -int git_commit_parent( - git_commit **parent, const git_commit *commit, unsigned int n) -{ - const git_oid *parent_id; - GIT_ASSERT_ARG(commit); - - parent_id = git_commit_parent_id(commit, n); - if (parent_id == NULL) { - git_error_set(GIT_ERROR_INVALID, "parent %u does not exist", n); - return GIT_ENOTFOUND; - } - - return git_commit_lookup(parent, commit->object.repo, parent_id); -} - -int git_commit_nth_gen_ancestor( - git_commit **ancestor, - const git_commit *commit, - unsigned int n) -{ - git_commit *current, *parent = NULL; - int error; - - GIT_ASSERT_ARG(ancestor); - GIT_ASSERT_ARG(commit); - - if (git_commit_dup(¤t, (git_commit *)commit) < 0) - return -1; - - if (n == 0) { - *ancestor = current; - return 0; - } - - while (n--) { - error = git_commit_parent(&parent, current, 0); - - git_commit_free(current); - - if (error < 0) - return error; - - current = parent; - } - - *ancestor = parent; - return 0; -} - -int git_commit_header_field( - git_buf *out, - const git_commit *commit, - const char *field) -{ - GIT_BUF_WRAP_PRIVATE(out, git_commit__header_field, commit, field); -} - -int git_commit__header_field( - git_str *out, - const git_commit *commit, - const char *field) -{ - const char *eol, *buf = commit->raw_header; - - git_str_clear(out); - - while ((eol = strchr(buf, '\n'))) { - /* We can skip continuations here */ - if (buf[0] == ' ') { - buf = eol + 1; - continue; - } - - /* Skip until we find the field we're after */ - if (git__prefixcmp(buf, field)) { - buf = eol + 1; - continue; - } - - buf += strlen(field); - /* Check that we're not matching a prefix but the field itself */ - if (buf[0] != ' ') { - buf = eol + 1; - continue; - } - - buf++; /* skip the SP */ - - git_str_put(out, buf, eol - buf); - if (git_str_oom(out)) - goto oom; - - /* If the next line starts with SP, it's multi-line, we must continue */ - while (eol[1] == ' ') { - git_str_putc(out, '\n'); - buf = eol + 2; - eol = strchr(buf, '\n'); - if (!eol) - goto malformed; - - git_str_put(out, buf, eol - buf); - } - - if (git_str_oom(out)) - goto oom; - - return 0; - } - - git_error_set(GIT_ERROR_OBJECT, "no such field '%s'", field); - return GIT_ENOTFOUND; - -malformed: - git_error_set(GIT_ERROR_OBJECT, "malformed header"); - return -1; -oom: - git_error_set_oom(); - return -1; -} - -int git_commit_extract_signature( - git_buf *signature_out, - git_buf *signed_data_out, - git_repository *repo, - git_oid *commit_id, - const char *field) -{ - git_str signature = GIT_STR_INIT, signed_data = GIT_STR_INIT; - int error; - - if ((error = git_buf_tostr(&signature, signature_out)) < 0 || - (error = git_buf_tostr(&signed_data, signed_data_out)) < 0 || - (error = git_commit__extract_signature(&signature, &signed_data, repo, commit_id, field)) < 0 || - (error = git_buf_fromstr(signature_out, &signature)) < 0 || - (error = git_buf_fromstr(signed_data_out, &signed_data)) < 0) - goto done; - -done: - git_str_dispose(&signature); - git_str_dispose(&signed_data); - return error; -} - -int git_commit__extract_signature( - git_str *signature, - git_str *signed_data, - git_repository *repo, - git_oid *commit_id, - const char *field) -{ - git_odb_object *obj; - git_odb *odb; - const char *buf; - const char *h, *eol; - int error; - - git_str_clear(signature); - git_str_clear(signed_data); - - if (!field) - field = "gpgsig"; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; - - if ((error = git_odb_read(&obj, odb, commit_id)) < 0) - return error; - - if (obj->cached.type != GIT_OBJECT_COMMIT) { - git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in the ODB"); - error = GIT_ENOTFOUND; - goto cleanup; - } - - buf = git_odb_object_data(obj); - - while ((h = strchr(buf, '\n')) && h[1] != '\0') { - h++; - if (git__prefixcmp(buf, field)) { - if (git_str_put(signed_data, buf, h - buf) < 0) - return -1; - - buf = h; - continue; - } - - h = buf; - h += strlen(field); - eol = strchr(h, '\n'); - if (h[0] != ' ') { - buf = h; - continue; - } - if (!eol) - goto malformed; - - h++; /* skip the SP */ - - git_str_put(signature, h, eol - h); - if (git_str_oom(signature)) - goto oom; - - /* If the next line starts with SP, it's multi-line, we must continue */ - while (eol[1] == ' ') { - git_str_putc(signature, '\n'); - h = eol + 2; - eol = strchr(h, '\n'); - if (!eol) - goto malformed; - - git_str_put(signature, h, eol - h); - } - - if (git_str_oom(signature)) - goto oom; - - error = git_str_puts(signed_data, eol+1); - git_odb_object_free(obj); - return error; - } - - git_error_set(GIT_ERROR_OBJECT, "this commit is not signed"); - error = GIT_ENOTFOUND; - goto cleanup; - -malformed: - git_error_set(GIT_ERROR_OBJECT, "malformed header"); - error = -1; - goto cleanup; -oom: - git_error_set_oom(); - error = -1; - goto cleanup; - -cleanup: - git_odb_object_free(obj); - git_str_clear(signature); - git_str_clear(signed_data); - return error; -} - -int git_commit_create_buffer( - git_buf *out, - git_repository *repo, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]) -{ - GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo, - author, committer, message_encoding, message, - tree, parent_count, parents); -} - -int git_commit__create_buffer( - git_str *out, - git_repository *repo, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]) -{ - int error; - commit_parent_data data = { parent_count, parents, repo }; - git_array_oid_t parents_arr = GIT_ARRAY_INIT; - const git_oid *tree_id; - - GIT_ASSERT_ARG(tree); - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - - tree_id = git_tree_id(tree); - - if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0) - return error; - - error = git_commit__create_buffer_internal( - out, author, committer, - message_encoding, message, tree_id, - &parents_arr); - - git_array_clear(parents_arr); - return error; -} - -/** - * Append to 'out' properly marking continuations when there's a newline in 'content' - */ -static int format_header_field(git_str *out, const char *field, const char *content) -{ - const char *lf; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(field); - GIT_ASSERT_ARG(content); - - git_str_puts(out, field); - git_str_putc(out, ' '); - - while ((lf = strchr(content, '\n')) != NULL) { - git_str_put(out, content, lf - content); - git_str_puts(out, "\n "); - content = lf + 1; - } - - git_str_puts(out, content); - git_str_putc(out, '\n'); - - return git_str_oom(out) ? -1 : 0; -} - -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, - const char *commit_content, - const char *signature, - const char *signature_field) -{ - git_odb *odb; - int error = 0; - const char *field; - const char *header_end; - git_str commit = GIT_STR_INIT; - git_commit *parsed; - git_array_oid_t parents = GIT_ARRAY_INIT; - - /* 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 (commit_parse(parsed, commit_content, strlen(commit_content), 0) < 0) { - error = -1; - 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"); - error = -1; - goto cleanup; - } - - /* The header ends after the first LF */ - header_end++; - git_str_put(&commit, commit_content, header_end - commit_content); - - if (signature != NULL) { - field = signature_field ? signature_field : "gpgsig"; - - if ((error = format_header_field(&commit, field, signature)) < 0) - goto cleanup; - } - - git_str_puts(&commit, header_end); - - if (git_str_oom(&commit)) - return -1; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - goto cleanup; - - if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - -cleanup: - git_commit__free(parsed); - git_str_dispose(&commit); - return error; -} - -int git_commit_committer_with_mailmap( - git_signature **out, const git_commit *commit, const git_mailmap *mailmap) -{ - return git_mailmap_resolve_signature(out, mailmap, commit->committer); -} - -int git_commit_author_with_mailmap( - git_signature **out, const git_commit *commit, const git_mailmap *mailmap) -{ - return git_mailmap_resolve_signature(out, mailmap, commit->author); -} diff --git a/src/commit.h b/src/commit.h deleted file mode 100644 index 7a2454e61..000000000 --- a/src/commit.h +++ /dev/null @@ -1,69 +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_commit_h__ -#define INCLUDE_commit_h__ - -#include "common.h" - -#include "git2/commit.h" -#include "tree.h" -#include "repository.h" -#include "array.h" - -#include - -struct git_commit { - git_object object; - - git_array_t(git_oid) parent_ids; - git_oid tree_id; - - git_signature *author; - git_signature *committer; - - char *message_encoding; - char *raw_message; - char *raw_header; - - char *summary; - char *body; -}; - -int git_commit__header_field( - git_str *out, - const git_commit *commit, - const char *field); - -int git_commit__extract_signature( - git_str *signature, - git_str *signed_data, - git_repository *repo, - git_oid *commit_id, - const char *field); - -int git_commit__create_buffer( - git_str *out, - git_repository *repo, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]); - -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_graph.c b/src/commit_graph.c deleted file mode 100644 index 70e866b92..000000000 --- a/src/commit_graph.c +++ /dev/null @@ -1,1224 +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 "commit_graph.h" - -#include "array.h" -#include "buf.h" -#include "filebuf.h" -#include "futils.h" -#include "hash.h" -#include "oidarray.h" -#include "oidmap.h" -#include "pack.h" -#include "repository.h" -#include "revwalk.h" - -#define GIT_COMMIT_GRAPH_MISSING_PARENT 0x70000000 -#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX 0x3FFFFFFF -#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_INFINITY 0xFFFFFFFF - -#define COMMIT_GRAPH_SIGNATURE 0x43475048 /* "CGPH" */ -#define COMMIT_GRAPH_VERSION 1 -#define COMMIT_GRAPH_OBJECT_ID_VERSION 1 -struct git_commit_graph_header { - uint32_t signature; - uint8_t version; - uint8_t object_id_version; - uint8_t chunks; - uint8_t base_graph_files; -}; - -#define COMMIT_GRAPH_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ -#define COMMIT_GRAPH_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ -#define COMMIT_GRAPH_COMMIT_DATA_ID 0x43444154 /* "CDAT" */ -#define COMMIT_GRAPH_EXTRA_EDGE_LIST_ID 0x45444745 /* "EDGE" */ -#define COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID 0x42494458 /* "BIDX" */ -#define COMMIT_GRAPH_BLOOM_FILTER_DATA_ID 0x42444154 /* "BDAT" */ - -struct git_commit_graph_chunk { - off64_t offset; - size_t length; -}; - -typedef git_array_t(size_t) parent_index_array_t; - -struct packed_commit { - size_t index; - git_oid sha1; - git_oid tree_oid; - uint32_t generation; - git_time_t commit_time; - git_array_oid_t parents; - parent_index_array_t parent_indices; -}; - -static void packed_commit_free(struct packed_commit *p) -{ - if (!p) - return; - - git_array_clear(p->parents); - git_array_clear(p->parent_indices); - git__free(p); -} - -static struct packed_commit *packed_commit_new(git_commit *commit) -{ - unsigned int i, parentcount = git_commit_parentcount(commit); - struct packed_commit *p = git__calloc(1, sizeof(struct packed_commit)); - if (!p) - goto cleanup; - - git_array_init_to_size(p->parents, parentcount); - if (parentcount && !p->parents.ptr) - goto cleanup; - - if (git_oid_cpy(&p->sha1, git_commit_id(commit)) < 0) - goto cleanup; - if (git_oid_cpy(&p->tree_oid, git_commit_tree_id(commit)) < 0) - goto cleanup; - p->commit_time = git_commit_time(commit); - - for (i = 0; i < parentcount; ++i) { - git_oid *parent_id = git_array_alloc(p->parents); - if (!parent_id) - goto cleanup; - if (git_oid_cpy(parent_id, git_commit_parent_id(commit, i)) < 0) - goto cleanup; - } - - return p; - -cleanup: - packed_commit_free(p); - return NULL; -} - -typedef int (*commit_graph_write_cb)(const char *buf, size_t size, void *cb_data); - -static int commit_graph_error(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "invalid commit-graph file - %s", message); - return -1; -} - -static int commit_graph_parse_oid_fanout( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_oid_fanout) -{ - uint32_t i, nr; - if (chunk_oid_fanout->offset == 0) - return commit_graph_error("missing OID Fanout chunk"); - if (chunk_oid_fanout->length == 0) - return commit_graph_error("empty OID Fanout chunk"); - if (chunk_oid_fanout->length != 256 * 4) - return commit_graph_error("OID Fanout chunk has wrong length"); - - file->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); - nr = 0; - for (i = 0; i < 256; ++i) { - uint32_t n = ntohl(file->oid_fanout[i]); - if (n < nr) - return commit_graph_error("index is non-monotonic"); - nr = n; - } - file->num_commits = nr; - return 0; -} - -static int commit_graph_parse_oid_lookup( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_oid_lookup) -{ - uint32_t i; - git_oid *oid, *prev_oid, zero_oid = {{0}}; - - if (chunk_oid_lookup->offset == 0) - return commit_graph_error("missing OID Lookup chunk"); - if (chunk_oid_lookup->length == 0) - return commit_graph_error("empty OID Lookup chunk"); - if (chunk_oid_lookup->length != file->num_commits * GIT_OID_RAWSZ) - return commit_graph_error("OID Lookup chunk has wrong length"); - - file->oid_lookup = oid = (git_oid *)(data + chunk_oid_lookup->offset); - prev_oid = &zero_oid; - for (i = 0; i < file->num_commits; ++i, ++oid) { - if (git_oid_cmp(prev_oid, oid) >= 0) - return commit_graph_error("OID Lookup index is non-monotonic"); - prev_oid = oid; - } - - return 0; -} - -static int commit_graph_parse_commit_data( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_commit_data) -{ - if (chunk_commit_data->offset == 0) - return commit_graph_error("missing Commit Data chunk"); - if (chunk_commit_data->length == 0) - return commit_graph_error("empty Commit Data chunk"); - if (chunk_commit_data->length != file->num_commits * (GIT_OID_RAWSZ + 16)) - return commit_graph_error("Commit Data chunk has wrong length"); - - file->commit_data = data + chunk_commit_data->offset; - - return 0; -} - -static int commit_graph_parse_extra_edge_list( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_extra_edge_list) -{ - if (chunk_extra_edge_list->length == 0) - return 0; - if (chunk_extra_edge_list->length % 4 != 0) - return commit_graph_error("malformed Extra Edge List chunk"); - - file->extra_edge_list = data + chunk_extra_edge_list->offset; - file->num_extra_edge_list = chunk_extra_edge_list->length / 4; - - return 0; -} - -int git_commit_graph_file_parse( - git_commit_graph_file *file, - const unsigned char *data, - size_t size) -{ - struct git_commit_graph_header *hdr; - const unsigned char *chunk_hdr; - struct git_commit_graph_chunk *last_chunk; - uint32_t i; - off64_t last_chunk_offset, chunk_offset, trailer_offset; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - int error; - struct git_commit_graph_chunk chunk_oid_fanout = {0}, chunk_oid_lookup = {0}, - chunk_commit_data = {0}, chunk_extra_edge_list = {0}, - chunk_unsupported = {0}; - - GIT_ASSERT_ARG(file); - - if (size < sizeof(struct git_commit_graph_header) + GIT_OID_RAWSZ) - return commit_graph_error("commit-graph is too short"); - - hdr = ((struct git_commit_graph_header *)data); - - if (hdr->signature != htonl(COMMIT_GRAPH_SIGNATURE) || hdr->version != COMMIT_GRAPH_VERSION - || hdr->object_id_version != COMMIT_GRAPH_OBJECT_ID_VERSION) { - return commit_graph_error("unsupported commit-graph version"); - } - if (hdr->chunks == 0) - return commit_graph_error("no chunks in commit-graph"); - - /* - * The very first chunk's offset should be after the header, all the chunk - * headers, and a special zero chunk. - */ - last_chunk_offset = sizeof(struct git_commit_graph_header) + (1 + hdr->chunks) * 12; - trailer_offset = size - GIT_OID_RAWSZ; - checksum_size = GIT_HASH_SHA1_SIZE; - - if (trailer_offset < last_chunk_offset) - return commit_graph_error("wrong commit-graph size"); - memcpy(file->checksum, (data + trailer_offset), checksum_size); - - if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) - return commit_graph_error("could not calculate signature"); - if (memcmp(checksum, file->checksum, checksum_size) != 0) - return commit_graph_error("index signature mismatch"); - - chunk_hdr = data + sizeof(struct git_commit_graph_header); - last_chunk = NULL; - for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { - chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32 - | ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))); - if (chunk_offset < last_chunk_offset) - return commit_graph_error("chunks are non-monotonic"); - if (chunk_offset >= trailer_offset) - return commit_graph_error("chunks extend beyond the trailer"); - if (last_chunk != NULL) - last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); - last_chunk_offset = chunk_offset; - - switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) { - case COMMIT_GRAPH_OID_FANOUT_ID: - chunk_oid_fanout.offset = last_chunk_offset; - last_chunk = &chunk_oid_fanout; - break; - - case COMMIT_GRAPH_OID_LOOKUP_ID: - chunk_oid_lookup.offset = last_chunk_offset; - last_chunk = &chunk_oid_lookup; - break; - - case COMMIT_GRAPH_COMMIT_DATA_ID: - chunk_commit_data.offset = last_chunk_offset; - last_chunk = &chunk_commit_data; - break; - - case COMMIT_GRAPH_EXTRA_EDGE_LIST_ID: - chunk_extra_edge_list.offset = last_chunk_offset; - last_chunk = &chunk_extra_edge_list; - break; - - case COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID: - case COMMIT_GRAPH_BLOOM_FILTER_DATA_ID: - chunk_unsupported.offset = last_chunk_offset; - last_chunk = &chunk_unsupported; - break; - - default: - return commit_graph_error("unrecognized chunk ID"); - } - } - last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); - - error = commit_graph_parse_oid_fanout(file, data, &chunk_oid_fanout); - if (error < 0) - return error; - error = commit_graph_parse_oid_lookup(file, data, &chunk_oid_lookup); - if (error < 0) - return error; - error = commit_graph_parse_commit_data(file, data, &chunk_commit_data); - if (error < 0) - return error; - error = commit_graph_parse_extra_edge_list(file, data, &chunk_extra_edge_list); - if (error < 0) - return error; - - return 0; -} - -int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file) -{ - git_commit_graph *cgraph = NULL; - int error = 0; - - GIT_ASSERT_ARG(cgraph_out); - GIT_ASSERT_ARG(objects_dir); - - cgraph = git__calloc(1, sizeof(git_commit_graph)); - GIT_ERROR_CHECK_ALLOC(cgraph); - - error = git_str_joinpath(&cgraph->filename, objects_dir, "info/commit-graph"); - if (error < 0) - goto error; - - if (open_file) { - error = git_commit_graph_file_open(&cgraph->file, git_str_cstr(&cgraph->filename)); - if (error < 0) - goto error; - cgraph->checked = 1; - } - - *cgraph_out = cgraph; - return 0; - -error: - git_commit_graph_free(cgraph); - return error; -} - -int git_commit_graph_open(git_commit_graph **cgraph_out, const char *objects_dir) -{ - return git_commit_graph_new(cgraph_out, objects_dir, true); -} - -int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path) -{ - git_commit_graph_file *file; - git_file fd = -1; - size_t cgraph_size; - struct stat st; - int error; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return fd; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "commit-graph file not found - '%s'", path); - return GIT_ENOTFOUND; - } - - if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); - return GIT_ENOTFOUND; - } - cgraph_size = (size_t)st.st_size; - - file = git__calloc(1, sizeof(git_commit_graph_file)); - GIT_ERROR_CHECK_ALLOC(file); - - error = git_futils_mmap_ro(&file->graph_map, fd, 0, cgraph_size); - p_close(fd); - if (error < 0) { - git_commit_graph_file_free(file); - return error; - } - - if ((error = git_commit_graph_file_parse(file, file->graph_map.data, cgraph_size)) < 0) { - git_commit_graph_file_free(file); - return error; - } - - *file_out = file; - return 0; -} - -int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph) -{ - if (!cgraph->checked) { - int error = 0; - git_commit_graph_file *result = NULL; - - /* We only check once, no matter the result. */ - cgraph->checked = 1; - - /* Best effort */ - error = git_commit_graph_file_open(&result, git_str_cstr(&cgraph->filename)); - - if (error < 0) - return error; - - cgraph->file = result; - } - if (!cgraph->file) - return GIT_ENOTFOUND; - - *file_out = cgraph->file; - return 0; -} - -void git_commit_graph_refresh(git_commit_graph *cgraph) -{ - if (!cgraph->checked) - return; - - if (cgraph->file - && git_commit_graph_file_needs_refresh(cgraph->file, git_str_cstr(&cgraph->filename))) { - /* We just free the commit graph. The next time it is requested, it will be - * re-loaded. */ - git_commit_graph_file_free(cgraph->file); - cgraph->file = NULL; - } - /* Force a lazy re-check next time it is needed. */ - cgraph->checked = 0; -} - -static int git_commit_graph_entry_get_byindex( - git_commit_graph_entry *e, - const git_commit_graph_file *file, - size_t pos) -{ - const unsigned char *commit_data; - - GIT_ASSERT_ARG(e); - GIT_ASSERT_ARG(file); - - if (pos >= file->num_commits) { - git_error_set(GIT_ERROR_INVALID, "commit index %zu does not exist", pos); - return GIT_ENOTFOUND; - } - - commit_data = file->commit_data + pos * (GIT_OID_RAWSZ + 4 * sizeof(uint32_t)); - git_oid_cpy(&e->tree_oid, (const git_oid *)commit_data); - e->parent_indices[0] = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ))); - e->parent_indices[1] = ntohl( - *((uint32_t *)(commit_data + GIT_OID_RAWSZ + sizeof(uint32_t)))); - e->parent_count = (e->parent_indices[0] != GIT_COMMIT_GRAPH_MISSING_PARENT) - + (e->parent_indices[1] != GIT_COMMIT_GRAPH_MISSING_PARENT); - e->generation = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 2 * sizeof(uint32_t)))); - e->commit_time = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 3 * sizeof(uint32_t)))); - - e->commit_time |= (e->generation & UINT64_C(0x3)) << UINT64_C(32); - e->generation >>= 2u; - if (e->parent_indices[1] & 0x80000000u) { - uint32_t extra_edge_list_pos = e->parent_indices[1] & 0x7fffffff; - - /* Make sure we're not being sent out of bounds */ - if (extra_edge_list_pos >= file->num_extra_edge_list) { - git_error_set(GIT_ERROR_INVALID, - "commit %u does not exist", - extra_edge_list_pos); - return GIT_ENOTFOUND; - } - - e->extra_parents_index = extra_edge_list_pos; - while (extra_edge_list_pos < file->num_extra_edge_list - && (ntohl(*( - (uint32_t *)(file->extra_edge_list - + extra_edge_list_pos * sizeof(uint32_t)))) - & 0x80000000u) - == 0) { - extra_edge_list_pos++; - e->parent_count++; - } - } - git_oid_cpy(&e->sha1, &file->oid_lookup[pos]); - return 0; -} - -bool git_commit_graph_file_needs_refresh(const git_commit_graph_file *file, const char *path) -{ - git_file fd = -1; - struct stat st; - ssize_t bytes_read; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return true; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - return true; - } - - if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size) - || (size_t)st.st_size != file->graph_map.len) { - p_close(fd); - return true; - } - - bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - checksum_size); - p_close(fd); - if (bytes_read != (ssize_t)checksum_size) - return true; - - return (memcmp(checksum, file->checksum, checksum_size) != 0); -} - -int git_commit_graph_entry_find( - git_commit_graph_entry *e, - const git_commit_graph_file *file, - const git_oid *short_oid, - size_t len) -{ - int pos, found = 0; - uint32_t hi, lo; - const git_oid *current = NULL; - - GIT_ASSERT_ARG(e); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(short_oid); - - hi = ntohl(file->oid_fanout[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(file->oid_fanout[(int)short_oid->id[0] - 1])); - - pos = git_pack__lookup_sha1(file->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = file->oid_lookup + pos; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = -1 - pos; - if (pos < (int)file->num_commits) { - current = file->oid_lookup + pos; - - if (!git_oid_ncmp(short_oid, current, len)) - found = 1; - } - } - - if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)file->num_commits) { - /* Check for ambiguousity */ - const git_oid *next = current + 1; - - if (!git_oid_ncmp(short_oid, next, len)) { - found = 2; - } - } - - if (!found) - return git_odb__error_notfound( - "failed to find offset for commit-graph index entry", short_oid, len); - if (found > 1) - return git_odb__error_ambiguous( - "found multiple offsets for commit-graph index entry"); - - return git_commit_graph_entry_get_byindex(e, file, pos); -} - -int git_commit_graph_entry_parent( - git_commit_graph_entry *parent, - const git_commit_graph_file *file, - const git_commit_graph_entry *entry, - size_t n) -{ - GIT_ASSERT_ARG(parent); - GIT_ASSERT_ARG(file); - - if (n >= entry->parent_count) { - git_error_set(GIT_ERROR_INVALID, "parent index %zu does not exist", n); - return GIT_ENOTFOUND; - } - - if (n == 0 || (n == 1 && entry->parent_count == 2)) - return git_commit_graph_entry_get_byindex(parent, file, entry->parent_indices[n]); - - return git_commit_graph_entry_get_byindex( - parent, - file, - ntohl( - *(uint32_t *)(file->extra_edge_list - + (entry->extra_parents_index + n - 1) - * sizeof(uint32_t))) - & 0x7fffffff); -} - -int git_commit_graph_file_close(git_commit_graph_file *file) -{ - GIT_ASSERT_ARG(file); - - if (file->graph_map.data) - git_futils_mmap_free(&file->graph_map); - - return 0; -} - -void git_commit_graph_free(git_commit_graph *cgraph) -{ - if (!cgraph) - return; - - git_str_dispose(&cgraph->filename); - git_commit_graph_file_free(cgraph->file); - git__free(cgraph); -} - -void git_commit_graph_file_free(git_commit_graph_file *file) -{ - if (!file) - return; - - git_commit_graph_file_close(file); - git__free(file); -} - -static int packed_commit__cmp(const void *a_, const void *b_) -{ - const struct packed_commit *a = a_; - const struct packed_commit *b = b_; - return git_oid_cmp(&a->sha1, &b->sha1); -} - -int git_commit_graph_writer_new(git_commit_graph_writer **out, const char *objects_info_dir) -{ - git_commit_graph_writer *w = git__calloc(1, sizeof(git_commit_graph_writer)); - GIT_ERROR_CHECK_ALLOC(w); - - if (git_str_sets(&w->objects_info_dir, objects_info_dir) < 0) { - git__free(w); - return -1; - } - - if (git_vector_init(&w->commits, 0, packed_commit__cmp) < 0) { - git_str_dispose(&w->objects_info_dir); - git__free(w); - return -1; - } - - *out = w; - return 0; -} - -void git_commit_graph_writer_free(git_commit_graph_writer *w) -{ - struct packed_commit *packed_commit; - size_t i; - - if (!w) - return; - - git_vector_foreach (&w->commits, i, packed_commit) - packed_commit_free(packed_commit); - git_vector_free(&w->commits); - git_str_dispose(&w->objects_info_dir); - git__free(w); -} - -struct object_entry_cb_state { - git_repository *repo; - git_odb *db; - git_vector *commits; -}; - -static int object_entry__cb(const git_oid *id, void *data) -{ - struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; - git_commit *commit = NULL; - struct packed_commit *packed_commit = NULL; - size_t header_len; - git_object_t header_type; - int error = 0; - - error = git_odb_read_header(&header_len, &header_type, state->db, id); - if (error < 0) - return error; - - if (header_type != GIT_OBJECT_COMMIT) - return 0; - - error = git_commit_lookup(&commit, state->repo, id); - if (error < 0) - return error; - - packed_commit = packed_commit_new(commit); - git_commit_free(commit); - GIT_ERROR_CHECK_ALLOC(packed_commit); - - error = git_vector_insert(state->commits, packed_commit); - if (error < 0) { - packed_commit_free(packed_commit); - return error; - } - - return 0; -} - -int git_commit_graph_writer_add_index_file( - git_commit_graph_writer *w, - git_repository *repo, - const char *idx_path) -{ - int error; - struct git_pack_file *p = NULL; - struct object_entry_cb_state state = {0}; - state.repo = repo; - state.commits = &w->commits; - - error = git_repository_odb(&state.db, repo); - if (error < 0) - goto cleanup; - - error = git_mwindow_get_pack(&p, idx_path); - if (error < 0) - goto cleanup; - - error = git_pack_foreach_entry(p, object_entry__cb, &state); - if (error < 0) - goto cleanup; - -cleanup: - if (p) - git_mwindow_put_pack(p); - git_odb_free(state.db); - return error; -} - -int git_commit_graph_writer_add_revwalk(git_commit_graph_writer *w, git_revwalk *walk) -{ - int error; - git_oid id; - git_repository *repo = git_revwalk_repository(walk); - git_commit *commit; - struct packed_commit *packed_commit; - - while ((git_revwalk_next(&id, walk)) == 0) { - error = git_commit_lookup(&commit, repo, &id); - if (error < 0) - return error; - - packed_commit = packed_commit_new(commit); - git_commit_free(commit); - GIT_ERROR_CHECK_ALLOC(packed_commit); - - error = git_vector_insert(&w->commits, packed_commit); - if (error < 0) { - packed_commit_free(packed_commit); - return error; - } - } - - return 0; -} - -enum generation_number_commit_state { - GENERATION_NUMBER_COMMIT_STATE_UNVISITED = 0, - GENERATION_NUMBER_COMMIT_STATE_ADDED = 1, - GENERATION_NUMBER_COMMIT_STATE_EXPANDED = 2, - GENERATION_NUMBER_COMMIT_STATE_VISITED = 3 -}; - -static int compute_generation_numbers(git_vector *commits) -{ - git_array_t(size_t) index_stack = GIT_ARRAY_INIT; - size_t i, j; - size_t *parent_idx; - enum generation_number_commit_state *commit_states = NULL; - struct packed_commit *child_packed_commit; - git_oidmap *packed_commit_map = NULL; - int error = 0; - - /* First populate the parent indices fields */ - error = git_oidmap_new(&packed_commit_map); - if (error < 0) - goto cleanup; - git_vector_foreach (commits, i, child_packed_commit) { - child_packed_commit->index = i; - error = git_oidmap_set( - packed_commit_map, &child_packed_commit->sha1, child_packed_commit); - if (error < 0) - goto cleanup; - } - - git_vector_foreach (commits, i, child_packed_commit) { - size_t parent_i, *parent_idx_ptr; - struct packed_commit *parent_packed_commit; - git_oid *parent_id; - git_array_init_to_size( - child_packed_commit->parent_indices, - git_array_size(child_packed_commit->parents)); - if (git_array_size(child_packed_commit->parents) - && !child_packed_commit->parent_indices.ptr) { - error = -1; - goto cleanup; - } - git_array_foreach (child_packed_commit->parents, parent_i, parent_id) { - parent_packed_commit = git_oidmap_get(packed_commit_map, parent_id); - if (!parent_packed_commit) { - git_error_set(GIT_ERROR_ODB, - "parent commit %s not found in commit graph", - git_oid_tostr_s(parent_id)); - error = GIT_ENOTFOUND; - goto cleanup; - } - parent_idx_ptr = git_array_alloc(child_packed_commit->parent_indices); - if (!parent_idx_ptr) { - error = -1; - goto cleanup; - } - *parent_idx_ptr = parent_packed_commit->index; - } - } - - /* - * We copy all the commits to the stack and then during visitation, - * each node can be added up to two times to the stack. - */ - git_array_init_to_size(index_stack, 3 * git_vector_length(commits)); - if (!index_stack.ptr) { - error = -1; - goto cleanup; - } - - commit_states = (enum generation_number_commit_state *)git__calloc( - git_vector_length(commits), sizeof(enum generation_number_commit_state)); - if (!commit_states) { - error = -1; - goto cleanup; - } - - /* - * Perform a Post-Order traversal so that all parent nodes are fully - * visited before the child node. - */ - git_vector_foreach (commits, i, child_packed_commit) - *(size_t *)git_array_alloc(index_stack) = i; - - while (git_array_size(index_stack)) { - size_t *index_ptr = git_array_pop(index_stack); - i = *index_ptr; - child_packed_commit = git_vector_get(commits, i); - - if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_VISITED) { - /* This commit has already been fully visited. */ - continue; - } - if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_EXPANDED) { - /* All of the commits parents have been visited. */ - child_packed_commit->generation = 0; - git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { - struct packed_commit *parent = git_vector_get(commits, *parent_idx); - if (child_packed_commit->generation < parent->generation) - child_packed_commit->generation = parent->generation; - } - if (child_packed_commit->generation - < GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) { - ++child_packed_commit->generation; - } - commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; - continue; - } - - /* - * This is the first time we see this commit. We need - * to visit all its parents before we can fully visit - * it. - */ - if (git_array_size(child_packed_commit->parent_indices) == 0) { - /* - * Special case: if the commit has no parents, there's - * no need to add it to the stack just to immediately - * remove it. - */ - commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; - child_packed_commit->generation = 1; - continue; - } - - /* - * Add this current commit again so that it is visited - * again once all its children have been visited. - */ - *(size_t *)git_array_alloc(index_stack) = i; - git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { - if (commit_states[*parent_idx] - != GENERATION_NUMBER_COMMIT_STATE_UNVISITED) { - /* This commit has already been considered. */ - continue; - } - - commit_states[*parent_idx] = GENERATION_NUMBER_COMMIT_STATE_ADDED; - *(size_t *)git_array_alloc(index_stack) = *parent_idx; - } - commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_EXPANDED; - } - -cleanup: - git_oidmap_free(packed_commit_map); - git__free(commit_states); - git_array_clear(index_stack); - - return error; -} - -static int write_offset(off64_t offset, commit_graph_write_cb write_cb, void *cb_data) -{ - int error; - uint32_t word; - - word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - - return 0; -} - -static int write_chunk_header( - int chunk_id, - off64_t offset, - commit_graph_write_cb write_cb, - void *cb_data) -{ - uint32_t word = htonl(chunk_id); - int error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - return write_offset(offset, write_cb, cb_data); -} - -static int commit_graph_write_buf(const char *buf, size_t size, void *data) -{ - git_str *b = (git_str *)data; - return git_str_put(b, buf, size); -} - -struct commit_graph_write_hash_context { - commit_graph_write_cb write_cb; - void *cb_data; - git_hash_ctx *ctx; -}; - -static int commit_graph_write_hash(const char *buf, size_t size, void *data) -{ - struct commit_graph_write_hash_context *ctx = data; - int error; - - error = git_hash_update(ctx->ctx, buf, size); - if (error < 0) - return error; - - return ctx->write_cb(buf, size, ctx->cb_data); -} - -static void packed_commit_free_dup(void *packed_commit) -{ - packed_commit_free(packed_commit); -} - -static int commit_graph_write( - git_commit_graph_writer *w, - commit_graph_write_cb write_cb, - void *cb_data) -{ - int error = 0; - size_t i; - struct packed_commit *packed_commit; - struct git_commit_graph_header hdr = {0}; - uint32_t oid_fanout_count; - uint32_t extra_edge_list_count; - uint32_t oid_fanout[256]; - off64_t offset; - git_str oid_lookup = GIT_STR_INIT, commit_data = GIT_STR_INIT, - extra_edge_list = GIT_STR_INIT; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - git_hash_ctx ctx; - struct commit_graph_write_hash_context hash_cb_data = {0}; - - hdr.signature = htonl(COMMIT_GRAPH_SIGNATURE); - hdr.version = COMMIT_GRAPH_VERSION; - hdr.object_id_version = COMMIT_GRAPH_OBJECT_ID_VERSION; - hdr.chunks = 0; - hdr.base_graph_files = 0; - hash_cb_data.write_cb = write_cb; - hash_cb_data.cb_data = cb_data; - hash_cb_data.ctx = &ctx; - - checksum_size = GIT_HASH_SHA1_SIZE; - error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); - if (error < 0) - return error; - cb_data = &hash_cb_data; - write_cb = commit_graph_write_hash; - - /* Sort the commits. */ - git_vector_sort(&w->commits); - git_vector_uniq(&w->commits, packed_commit_free_dup); - error = compute_generation_numbers(&w->commits); - if (error < 0) - goto cleanup; - - /* Fill the OID Fanout table. */ - oid_fanout_count = 0; - for (i = 0; i < 256; i++) { - while (oid_fanout_count < git_vector_length(&w->commits) && - (packed_commit = (struct packed_commit *)git_vector_get(&w->commits, oid_fanout_count)) && - packed_commit->sha1.id[0] <= i) - ++oid_fanout_count; - oid_fanout[i] = htonl(oid_fanout_count); - } - - /* Fill the OID Lookup table. */ - git_vector_foreach (&w->commits, i, packed_commit) { - error = git_str_put(&oid_lookup, - (const char *)&packed_commit->sha1, sizeof(git_oid)); - if (error < 0) - goto cleanup; - } - - /* Fill the Commit Data and Extra Edge List tables. */ - extra_edge_list_count = 0; - git_vector_foreach (&w->commits, i, packed_commit) { - uint64_t commit_time; - uint32_t generation; - uint32_t word; - size_t *packed_index; - unsigned int parentcount = (unsigned int)git_array_size(packed_commit->parents); - - error = git_str_put(&commit_data, - (const char *)&packed_commit->tree_oid, - sizeof(git_oid)); - if (error < 0) - goto cleanup; - - if (parentcount == 0) { - word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); - } else { - packed_index = git_array_get(packed_commit->parent_indices, 0); - word = htonl((uint32_t)*packed_index); - } - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - - if (parentcount < 2) { - word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); - } else if (parentcount == 2) { - packed_index = git_array_get(packed_commit->parent_indices, 1); - word = htonl((uint32_t)*packed_index); - } else { - word = htonl(0x80000000u | extra_edge_list_count); - } - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - - if (parentcount > 2) { - unsigned int parent_i; - for (parent_i = 1; parent_i < parentcount; ++parent_i) { - packed_index = git_array_get( - packed_commit->parent_indices, parent_i); - word = htonl((uint32_t)(*packed_index | (parent_i + 1 == parentcount ? 0x80000000u : 0))); - - error = git_str_put(&extra_edge_list, - (const char *)&word, - sizeof(word)); - if (error < 0) - goto cleanup; - } - extra_edge_list_count += parentcount - 1; - } - - generation = packed_commit->generation; - commit_time = (uint64_t)packed_commit->commit_time; - if (generation > GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) - generation = GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX; - word = ntohl((uint32_t)((generation << 2) | (((uint32_t)(commit_time >> 32)) & 0x3) )); - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - word = ntohl((uint32_t)(commit_time & 0xfffffffful)); - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - } - - /* Write the header. */ - hdr.chunks = 3; - if (git_str_len(&extra_edge_list) > 0) - hdr.chunks++; - error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); - if (error < 0) - goto cleanup; - - /* Write the chunk headers. */ - offset = sizeof(hdr) + (hdr.chunks + 1) * 12; - error = write_chunk_header(COMMIT_GRAPH_OID_FANOUT_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += sizeof(oid_fanout); - error = write_chunk_header(COMMIT_GRAPH_OID_LOOKUP_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&oid_lookup); - error = write_chunk_header(COMMIT_GRAPH_COMMIT_DATA_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&commit_data); - if (git_str_len(&extra_edge_list) > 0) { - error = write_chunk_header( - COMMIT_GRAPH_EXTRA_EDGE_LIST_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&extra_edge_list); - } - error = write_chunk_header(0, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - - /* Write all the chunks. */ - error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&commit_data), git_str_len(&commit_data), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&extra_edge_list), git_str_len(&extra_edge_list), cb_data); - if (error < 0) - goto cleanup; - - /* Finalize the checksum and write the trailer. */ - error = git_hash_final(checksum, &ctx); - if (error < 0) - goto cleanup; - error = write_cb((char *)checksum, checksum_size, cb_data); - if (error < 0) - goto cleanup; - -cleanup: - git_str_dispose(&oid_lookup); - git_str_dispose(&commit_data); - git_str_dispose(&extra_edge_list); - git_hash_ctx_cleanup(&ctx); - return error; -} - -static int commit_graph_write_filebuf(const char *buf, size_t size, void *data) -{ - git_filebuf *f = (git_filebuf *)data; - return git_filebuf_write(f, buf, size); -} - -int git_commit_graph_writer_options_init( - git_commit_graph_writer_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, - version, - git_commit_graph_writer_options, - GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT); - return 0; -} - -int git_commit_graph_writer_commit( - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) -{ - int error; - int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; - git_str commit_graph_path = GIT_STR_INIT; - git_filebuf output = GIT_FILEBUF_INIT; - - /* TODO: support options and fill in defaults. */ - GIT_UNUSED(opts); - - error = git_str_joinpath( - &commit_graph_path, git_str_cstr(&w->objects_info_dir), "commit-graph"); - if (error < 0) - return error; - - if (git_repository__fsync_gitdir) - filebuf_flags |= GIT_FILEBUF_FSYNC; - error = git_filebuf_open(&output, git_str_cstr(&commit_graph_path), filebuf_flags, 0644); - git_str_dispose(&commit_graph_path); - if (error < 0) - return error; - - error = commit_graph_write(w, commit_graph_write_filebuf, &output); - if (error < 0) { - git_filebuf_cleanup(&output); - return error; - } - - return git_filebuf_commit(&output); -} - -int git_commit_graph_writer_dump( - git_buf *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) -{ - GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w, opts); -} - -int git_commit_graph__writer_dump( - git_str *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) -{ - /* TODO: support options. */ - GIT_UNUSED(opts); - return commit_graph_write(w, commit_graph_write_buf, cgraph); -} diff --git a/src/commit_graph.h b/src/commit_graph.h deleted file mode 100644 index 45e125b9e..000000000 --- a/src/commit_graph.h +++ /dev/null @@ -1,169 +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_commit_graph_h__ -#define INCLUDE_commit_graph_h__ - -#include "common.h" - -#include "git2/types.h" -#include "git2/sys/commit_graph.h" - -#include "map.h" -#include "vector.h" -#include "oid.h" -#include "hash.h" - -/** - * A commit-graph file. - * - * This file contains metadata about commits, particularly the generation - * number for each one. This can help speed up graph operations without - * requiring a full graph traversal. - * - * Support for this feature was added in git 2.19. - */ -typedef struct git_commit_graph_file { - git_map graph_map; - - /* The OID Fanout table. */ - const uint32_t *oid_fanout; - /* The total number of commits in the graph. */ - uint32_t num_commits; - - /* The OID Lookup table. */ - git_oid *oid_lookup; - - /* - * The Commit Data table. Each entry contains the OID of the commit followed - * by two 8-byte fields in network byte order: - * - The indices of the first two parents (32 bits each). - * - The generation number (first 30 bits) and commit time in seconds since - * UNIX epoch (34 bits). - */ - const unsigned char *commit_data; - - /* - * The Extra Edge List table. Each 4-byte entry is a network byte order index - * of one of the i-th (i > 0) parents of commits in the `commit_data` table, - * when the commit has more than 2 parents. - */ - const unsigned char *extra_edge_list; - /* The number of entries in the Extra Edge List table. Each entry is 4 bytes wide. */ - size_t num_extra_edge_list; - - /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ - unsigned char checksum[GIT_HASH_SHA1_SIZE]; -} git_commit_graph_file; - -/** - * An entry in the commit-graph file. Provides a subset of the information that - * can be obtained from the commit header. - */ -typedef struct git_commit_graph_entry { - /* The generation number of the commit within the graph */ - size_t generation; - - /* Time in seconds from UNIX epoch. */ - git_time_t commit_time; - - /* The number of parents of the commit. */ - size_t parent_count; - - /* - * The indices of the parent commits within the Commit Data table. The value - * of `GIT_COMMIT_GRAPH_MISSING_PARENT` indicates that no parent is in that - * position. - */ - size_t parent_indices[2]; - - /* The index within the Extra Edge List of any parent after the first two. */ - size_t extra_parents_index; - - /* The SHA-1 hash of the root tree of the commit. */ - git_oid tree_oid; - - /* The SHA-1 hash of the requested commit. */ - git_oid sha1; -} git_commit_graph_entry; - -/* A wrapper for git_commit_graph_file to enable lazy loading in the ODB. */ -struct git_commit_graph { - /* The path to the commit-graph file. Something like ".git/objects/info/commit-graph". */ - git_str filename; - - /* The underlying commit-graph file. */ - git_commit_graph_file *file; - - /* Whether the commit-graph file was already checked for validity. */ - bool checked; -}; - -/** Create a new commit-graph, optionally opening the underlying file. */ -int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file); - -/** Open and validate a commit-graph file. */ -int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path); - -/* - * Attempt to get the git_commit_graph's commit-graph file. This object is - * still owned by the git_commit_graph. If the repository does not contain a commit graph, - * it will return GIT_ENOTFOUND. - * - * This function is not thread-safe. - */ -int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph); - -/* Marks the commit-graph file as needing a refresh. */ -void git_commit_graph_refresh(git_commit_graph *cgraph); - -/* - * A writer for `commit-graph` files. - */ -struct git_commit_graph_writer { - /* - * The path of the `objects/info` directory where the `commit-graph` will be - * stored. - */ - git_str objects_info_dir; - - /* The list of packed commits. */ - git_vector commits; -}; - -int git_commit_graph__writer_dump( - git_str *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts); - -/* - * Returns whether the git_commit_graph_file needs to be reloaded since the - * contents of the commit-graph file have changed on disk. - */ -bool git_commit_graph_file_needs_refresh( - const git_commit_graph_file *file, const char *path); - -int git_commit_graph_entry_find( - git_commit_graph_entry *e, - const git_commit_graph_file *file, - const git_oid *short_oid, - size_t len); -int git_commit_graph_entry_parent( - git_commit_graph_entry *parent, - const git_commit_graph_file *file, - const git_commit_graph_entry *entry, - size_t n); -int git_commit_graph_file_close(git_commit_graph_file *cgraph); -void git_commit_graph_file_free(git_commit_graph_file *cgraph); - -/* This is exposed for use in the fuzzers. */ -int git_commit_graph_file_parse( - git_commit_graph_file *file, - const unsigned char *data, - size_t size); - -#endif diff --git a/src/commit_list.c b/src/commit_list.c deleted file mode 100644 index 4585508bc..000000000 --- a/src/commit_list.c +++ /dev/null @@ -1,208 +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 "commit_list.h" - -#include "revwalk.h" -#include "pool.h" -#include "odb.h" -#include "commit.h" - -int git_commit_list_generation_cmp(const void *a, const void *b) -{ - uint32_t generation_a = ((git_commit_list_node *) a)->generation; - uint32_t generation_b = ((git_commit_list_node *) b)->generation; - - if (!generation_a || !generation_b) { - /* Fall back to comparing by timestamps if at least one commit lacks a generation. */ - return git_commit_list_time_cmp(a, b); - } - - if (generation_a < generation_b) - return 1; - if (generation_a > generation_b) - return -1; - - return 0; -} - -int git_commit_list_time_cmp(const void *a, const void *b) -{ - int64_t time_a = ((git_commit_list_node *) a)->time; - int64_t time_b = ((git_commit_list_node *) b)->time; - - if (time_a < time_b) - return 1; - if (time_a > time_b) - return -1; - - return 0; -} - -git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p) -{ - git_commit_list *new_list = git__malloc(sizeof(git_commit_list)); - if (new_list != NULL) { - new_list->item = item; - new_list->next = *list_p; - } - *list_p = new_list; - return new_list; -} - -git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p) -{ - git_commit_list **pp = list_p; - git_commit_list *p; - - while ((p = *pp) != NULL) { - if (git_commit_list_time_cmp(p->item, item) > 0) - break; - - pp = &p->next; - } - - return git_commit_list_insert(item, pp); -} - -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 git_commit_list_node **alloc_parents( - git_revwalk *walk, git_commit_list_node *commit, size_t n_parents) -{ - size_t bytes; - - if (n_parents <= PARENTS_PER_COMMIT) - return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node)); - - if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *))) - return NULL; - - return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes); -} - - -void git_commit_list_free(git_commit_list **list_p) -{ - git_commit_list *list = *list_p; - - if (list == NULL) - return; - - while (list) { - git_commit_list *temp = list; - list = temp->next; - git__free(temp); - } - - *list_p = NULL; -} - -git_commit_list_node *git_commit_list_pop(git_commit_list **stack) -{ - git_commit_list *top = *stack; - git_commit_list_node *item = top ? top->item : NULL; - - if (top) { - *stack = top->next; - git__free(top); - } - return item; -} - -static int commit_quick_parse( - git_revwalk *walk, - git_commit_list_node *node, - git_odb_object *obj) -{ - git_oid *parent_oid; - git_commit *commit; - size_t i; - - commit = git__calloc(1, sizeof(*commit)); - GIT_ERROR_CHECK_ALLOC(commit); - commit->object.repo = walk->repo; - - if (git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK) < 0) { - git__free(commit); - return -1; - } - - 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; - } - - node->generation = 0; - 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); - - git_array_foreach(commit->parent_ids, i, parent_oid) { - node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid); - } - - git_commit__free(commit); - - node->parsed = 1; - - return 0; -} - -int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit) -{ - git_odb_object *obj; - git_commit_graph_file *cgraph_file = NULL; - int error; - - if (commit->parsed) - return 0; - - /* Let's try to use the commit graph first. */ - git_odb__get_commit_graph_file(&cgraph_file, walk->odb); - if (cgraph_file) { - git_commit_graph_entry e; - - error = git_commit_graph_entry_find(&e, cgraph_file, &commit->oid, GIT_OID_RAWSZ); - if (error == 0 && git__is_uint16(e.parent_count)) { - size_t i; - commit->generation = (uint32_t)e.generation; - commit->time = e.commit_time; - commit->out_degree = (uint16_t)e.parent_count; - commit->parents = alloc_parents(walk, commit, commit->out_degree); - GIT_ERROR_CHECK_ALLOC(commit->parents); - - for (i = 0; i < commit->out_degree; ++i) { - git_commit_graph_entry parent; - error = git_commit_graph_entry_parent(&parent, cgraph_file, &e, i); - if (error < 0) - return error; - commit->parents[i] = git_revwalk__commit_lookup(walk, &parent.sha1); - } - commit->parsed = 1; - return 0; - } - } - - if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) - return error; - - if (obj->cached.type != GIT_OBJECT_COMMIT) { - git_error_set(GIT_ERROR_INVALID, "object is no commit object"); - error = -1; - } else - 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 deleted file mode 100644 index aad39f351..000000000 --- a/src/commit_list.h +++ /dev/null @@ -1,57 +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_commit_list_h__ -#define INCLUDE_commit_list_h__ - -#include "common.h" - -#include "git2/oid.h" - -#define PARENT1 (1 << 0) -#define PARENT2 (1 << 1) -#define RESULT (1 << 2) -#define STALE (1 << 3) -#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT) - -#define PARENTS_PER_COMMIT 2 -#define COMMIT_ALLOC \ - (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *)) - -#define FLAG_BITS 4 - -typedef struct git_commit_list_node { - git_oid oid; - int64_t time; - uint32_t generation; - unsigned int seen:1, - uninteresting:1, - topo_delay:1, - parsed:1, - added:1, - flags : FLAG_BITS; - - uint16_t in_degree; - uint16_t out_degree; - - struct git_commit_list_node **parents; -} git_commit_list_node; - -typedef struct git_commit_list { - git_commit_list_node *item; - struct git_commit_list *next; -} git_commit_list; - -git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk); -int git_commit_list_generation_cmp(const void *a, const void *b); -int git_commit_list_time_cmp(const void *a, const void *b); -void git_commit_list_free(git_commit_list **list_p); -git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p); -git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p); -int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit); -git_commit_list_node *git_commit_list_pop(git_commit_list **stack); - -#endif diff --git a/src/common.h b/src/common.h deleted file mode 100644 index 549bddb59..000000000 --- a/src/common.h +++ /dev/null @@ -1,214 +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_common_h__ -#define INCLUDE_common_h__ - -#ifndef LIBGIT2_NO_FEATURES_H -# include "git2/sys/features.h" -#endif - -#include "git2/common.h" -#include "cc-compat.h" - -/** Declare a function as always inlined. */ -#if defined(_MSC_VER) -# define GIT_INLINE(type) static __inline type -#elif defined(__GNUC__) -# define GIT_INLINE(type) static __inline__ type -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_INLINE(type) static inline type -#else -# define GIT_INLINE(type) static type -#endif - -/** Support for gcc/clang __has_builtin intrinsic */ -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - -/** - * Declare that a function's return value must be used. - * - * Used mostly to guard against potential silent bugs at runtime. This is - * recommended to be added to functions that: - * - * - Allocate / reallocate memory. This prevents memory leaks or errors where - * buffers are expected to have grown to a certain size, but could not be - * resized. - * - Acquire locks. When a lock cannot be acquired, that will almost certainly - * cause a data race / undefined behavior. - */ -#if defined(__GNUC__) -# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -# define GIT_WARN_UNUSED_RESULT -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef GIT_WIN32 - -# include -# include -# include -# include -# 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" -# ifdef GIT_THREADS -# include "win32/thread.h" -# endif - -#else - -# include -# include -# ifdef GIT_THREADS -# include -# include -# endif - -#define GIT_LIBGIT2_CALL -#define GIT_SYSTEM_CALL - -#ifdef GIT_USE_STAT_ATIMESPEC -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -#endif - -# include - -#endif - -#include "git2/types.h" -#include "git2/errors.h" -#include "errors.h" -#include "thread.h" -#include "integer.h" -#include "assert_safe.h" -#include "utf8.h" - -/* - * Include the declarations for deprecated functions; this ensures - * that they're decorated with the proper extern/visibility attributes. - */ -#include "git2/deprecated.h" - -#include "posix.h" - -#define DEFAULT_BUFSIZE 65536 -#define FILEIO_BUFSIZE DEFAULT_BUFSIZE -#define FILTERIO_BUFSIZE DEFAULT_BUFSIZE -#define NETIO_BUFSIZE DEFAULT_BUFSIZE - -/** - * Check a pointer allocation result, returning -1 if it failed. - */ -#define GIT_ERROR_CHECK_ALLOC(ptr) do { \ - if ((ptr) == NULL) { return -1; } \ - } while(0) - -/** - * Check a string buffer allocation result, returning -1 if it failed. - */ -#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \ - if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \ - } while(0) - -/** - * Check a return value and propagate result if non-zero. - */ -#define GIT_ERROR_CHECK_ERROR(code) \ - do { int _err = (code); if (_err) return _err; } while (0) - -/** - * Check a versioned structure for validity - */ -GIT_INLINE(int) git_error__check_version(const void *structure, unsigned int expected_max, const char *name) -{ - unsigned int actual; - - if (!structure) - return 0; - - actual = *(const unsigned int*)structure; - if (actual > 0 && actual <= expected_max) - return 0; - - git_error_set(GIT_ERROR_INVALID, "invalid version %d on %s", actual, name); - return -1; -} -#define GIT_ERROR_CHECK_VERSION(S,V,N) if (git_error__check_version(S,V,N) < 0) return -1 - -/** - * Initialize a structure with a version. - */ -GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version) -{ - memset(structure, 0, len); - *((int*)structure) = version; -} -#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V) - -#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \ - TYPE _tmpl = TPL; \ - GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \ - memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0) - - -/** Check for additive overflow, setting an error if would occur. */ -#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \ - (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0) - -/** Check for additive overflow, setting an error if would occur. */ -#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \ - (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0) - -/** Check for additive overflow, failing if it would occur. */ -#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } - -/** Check for multiplicative overflow, failing if it would occur. */ -#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ - if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } - -/* NOTE: other git_error functions are in the public errors.h header file */ - -/* Forward declare git_str */ -typedef struct git_str git_str; - -#include "util.h" - -#endif diff --git a/src/config.c b/src/config.c deleted file mode 100644 index 6bd59f2a5..000000000 --- a/src/config.c +++ /dev/null @@ -1,1570 +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 "config.h" - -#include "git2/config.h" -#include "git2/sys/config.h" - -#include "buf.h" -#include "config_backend.h" -#include "regexp.h" -#include "sysdir.h" -#include "transaction.h" -#include "vector.h" -#if GIT_WIN32 -# include -#endif - -#include - -void git_config_entry_free(git_config_entry *entry) -{ - if (!entry) - return; - - entry->free(entry); -} - -typedef struct { - git_refcount rc; - - git_config_backend *backend; - git_config_level_t level; -} backend_internal; - -static void backend_internal_free(backend_internal *internal) -{ - git_config_backend *backend; - - backend = internal->backend; - backend->free(backend); - git__free(internal); -} - -static void config_free(git_config *cfg) -{ - size_t i; - backend_internal *internal; - - for (i = 0; i < cfg->backends.length; ++i) { - internal = git_vector_get(&cfg->backends, i); - GIT_REFCOUNT_DEC(internal, backend_internal_free); - } - - git_vector_free(&cfg->backends); - - git__memzero(cfg, sizeof(*cfg)); - git__free(cfg); -} - -void git_config_free(git_config *cfg) -{ - if (cfg == NULL) - return; - - GIT_REFCOUNT_DEC(cfg, config_free); -} - -static int config_backend_cmp(const void *a, const void *b) -{ - const backend_internal *bk_a = (const backend_internal *)(a); - const backend_internal *bk_b = (const backend_internal *)(b); - - return bk_b->level - bk_a->level; -} - -int git_config_new(git_config **out) -{ - git_config *cfg; - - cfg = git__malloc(sizeof(git_config)); - GIT_ERROR_CHECK_ALLOC(cfg); - - memset(cfg, 0x0, sizeof(git_config)); - - if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { - git__free(cfg); - return -1; - } - - *out = cfg; - GIT_REFCOUNT_INC(cfg); - return 0; -} - -int git_config_add_file_ondisk( - git_config *cfg, - const char *path, - git_config_level_t level, - const git_repository *repo, - int force) -{ - git_config_backend *file = NULL; - struct stat st; - int res; - - GIT_ASSERT_ARG(cfg); - GIT_ASSERT_ARG(path); - - res = p_stat(path, &st); - if (res < 0 && errno != ENOENT && errno != ENOTDIR) { - git_error_set(GIT_ERROR_CONFIG, "failed to stat '%s'", path); - return -1; - } - - if (git_config_backend_from_file(&file, path) < 0) - return -1; - - if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { - /* - * free manually; the file is not owned by the config - * instance yet and will not be freed on cleanup - */ - file->free(file); - return res; - } - - return 0; -} - -int git_config_open_ondisk(git_config **out, const char *path) -{ - int error; - git_config *config; - - *out = NULL; - - if (git_config_new(&config) < 0) - return -1; - - if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)) < 0) - git_config_free(config); - else - *out = config; - - return error; -} - -int git_config_snapshot(git_config **out, git_config *in) -{ - int error = 0; - size_t i; - backend_internal *internal; - git_config *config; - - *out = NULL; - - if (git_config_new(&config) < 0) - return -1; - - git_vector_foreach(&in->backends, i, internal) { - git_config_backend *b; - - if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) - break; - - if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { - b->free(b); - break; - } - } - - if (error < 0) - git_config_free(config); - else - *out = config; - - return error; -} - -static int find_backend_by_level( - backend_internal **out, - const git_config *cfg, - git_config_level_t level) -{ - int pos = -1; - backend_internal *internal; - size_t i; - - /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend - * which has the highest level. As config backends are stored in a vector - * sorted by decreasing order of level, getting the backend at position 0 - * will do the job. - */ - if (level == GIT_CONFIG_HIGHEST_LEVEL) { - pos = 0; - } else { - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; - } - } - - if (pos == -1) { - git_error_set(GIT_ERROR_CONFIG, - "no configuration exists for the given level '%i'", (int)level); - return GIT_ENOTFOUND; - } - - *out = git_vector_get(&cfg->backends, pos); - - return 0; -} - -static int duplicate_level(void **old_raw, void *new_raw) -{ - backend_internal **old = (backend_internal **)old_raw; - - GIT_UNUSED(new_raw); - - git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); - return GIT_EEXISTS; -} - -static void try_remove_existing_backend( - git_config *cfg, - git_config_level_t level) -{ - int pos = -1; - backend_internal *internal; - size_t i; - - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; - } - - if (pos == -1) - return; - - internal = git_vector_get(&cfg->backends, pos); - - if (git_vector_remove(&cfg->backends, pos) < 0) - return; - - GIT_REFCOUNT_DEC(internal, backend_internal_free); -} - -static int git_config__add_internal( - git_config *cfg, - backend_internal *internal, - git_config_level_t level, - int force) -{ - int result; - - /* delete existing config backend for level if it exists */ - if (force) - try_remove_existing_backend(cfg, level); - - if ((result = git_vector_insert_sorted(&cfg->backends, - internal, &duplicate_level)) < 0) - return result; - - git_vector_sort(&cfg->backends); - internal->backend->cfg = cfg; - - GIT_REFCOUNT_INC(internal); - - return 0; -} - -int git_config_open_global(git_config **cfg_out, git_config *cfg) -{ - if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) - return 0; - - return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); -} - -int git_config_open_level( - git_config **cfg_out, - const git_config *cfg_parent, - git_config_level_t level) -{ - git_config *cfg; - backend_internal *internal; - int res; - - if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) - return res; - - if ((res = git_config_new(&cfg)) < 0) - return res; - - if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { - git_config_free(cfg); - return res; - } - - *cfg_out = cfg; - - return 0; -} - -int git_config_add_backend( - git_config *cfg, - git_config_backend *backend, - git_config_level_t level, - const git_repository *repo, - int force) -{ - backend_internal *internal; - int result; - - GIT_ASSERT_ARG(cfg); - GIT_ASSERT_ARG(backend); - - GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); - - if ((result = backend->open(backend, level, repo)) < 0) - return result; - - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); - - memset(internal, 0x0, sizeof(backend_internal)); - - internal->backend = backend; - internal->level = level; - - if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { - git__free(internal); - return result; - } - - return 0; -} - -/* - * Loop over all the variables - */ - -typedef struct { - git_config_iterator parent; - git_config_iterator *current; - const git_config *cfg; - git_regexp regex; - size_t i; -} all_iter; - -static int find_next_backend(size_t *out, const git_config *cfg, size_t i) -{ - backend_internal *internal; - - for (; i > 0; --i) { - internal = git_vector_get(&cfg->backends, i - 1); - if (!internal || !internal->backend) - continue; - - *out = i; - return 0; - } - - return -1; -} - -static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) -{ - all_iter *iter = (all_iter *) _iter; - backend_internal *internal; - git_config_backend *backend; - size_t i; - int error = 0; - - if (iter->current != NULL && - (error = iter->current->next(entry, iter->current)) == 0) { - return 0; - } - - if (error < 0 && error != GIT_ITEROVER) - return error; - - do { - if (find_next_backend(&i, iter->cfg, iter->i) < 0) - return GIT_ITEROVER; - - internal = git_vector_get(&iter->cfg->backends, i - 1); - backend = internal->backend; - iter->i = i - 1; - - if (iter->current) - iter->current->free(iter->current); - - iter->current = NULL; - error = backend->iterator(&iter->current, backend); - if (error == GIT_ENOTFOUND) - continue; - - if (error < 0) - return error; - - error = iter->current->next(entry, iter->current); - /* If this backend is empty, then keep going */ - if (error == GIT_ITEROVER) - continue; - - return error; - - } while(1); - - return GIT_ITEROVER; -} - -static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) -{ - int error; - all_iter *iter = (all_iter *) _iter; - - /* - * We use the "normal" function to grab the next one across - * backends and then apply the regex - */ - while ((error = all_iter_next(entry, _iter)) == 0) { - /* skip non-matching keys if regexp was provided */ - if (git_regexp_match(&iter->regex, (*entry)->name) != 0) - continue; - - /* and simply return if we like the entry's name */ - return 0; - } - - return error; -} - -static void all_iter_free(git_config_iterator *_iter) -{ - all_iter *iter = (all_iter *) _iter; - - if (iter->current) - iter->current->free(iter->current); - - git__free(iter); -} - -static void all_iter_glob_free(git_config_iterator *_iter) -{ - all_iter *iter = (all_iter *) _iter; - - git_regexp_dispose(&iter->regex); - all_iter_free(_iter); -} - -int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) -{ - all_iter *iter; - - iter = git__calloc(1, sizeof(all_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->parent.free = all_iter_free; - iter->parent.next = all_iter_next; - - iter->i = cfg->backends.length; - iter->cfg = cfg; - - *out = (git_config_iterator *) iter; - - return 0; -} - -int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) -{ - all_iter *iter; - int result; - - if (regexp == NULL) - return git_config_iterator_new(out, cfg); - - iter = git__calloc(1, sizeof(all_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - if ((result = git_regexp_compile(&iter->regex, regexp, 0)) < 0) { - git__free(iter); - return -1; - } - - iter->parent.next = all_iter_glob_next; - iter->parent.free = all_iter_glob_free; - iter->i = cfg->backends.length; - iter->cfg = cfg; - - *out = (git_config_iterator *) iter; - - return 0; -} - -int git_config_foreach( - const git_config *cfg, git_config_foreach_cb cb, void *payload) -{ - return git_config_foreach_match(cfg, NULL, cb, payload); -} - -int git_config_backend_foreach_match( - git_config_backend *backend, - const char *regexp, - git_config_foreach_cb cb, - void *payload) -{ - git_config_entry *entry; - git_config_iterator *iter; - git_regexp regex; - int error = 0; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(cb); - - if (regexp && git_regexp_compile(®ex, regexp, 0) < 0) - return -1; - - if ((error = backend->iterator(&iter, backend)) < 0) { - iter = NULL; - return -1; - } - - while (!(iter->next(&entry, iter) < 0)) { - /* skip non-matching keys if regexp was provided */ - if (regexp && git_regexp_match(®ex, entry->name) != 0) - continue; - - /* abort iterator on non-zero return value */ - if ((error = cb(entry, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (regexp != NULL) - git_regexp_dispose(®ex); - - iter->free(iter); - - return error; -} - -int git_config_foreach_match( - const git_config *cfg, - const char *regexp, - git_config_foreach_cb cb, - void *payload) -{ - int error; - git_config_iterator *iter; - git_config_entry *entry; - - if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) - return error; - - while (!(error = git_config_next(&entry, iter))) { - if ((error = cb(entry, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - git_config_iterator_free(iter); - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -/************** - * Setters - **************/ - -typedef enum { - BACKEND_USE_SET, - BACKEND_USE_DELETE -} backend_use; - -static const char *uses[] = { - "set", - "delete" -}; - -static int get_backend_for_use(git_config_backend **out, - git_config *cfg, const char *name, backend_use use) -{ - size_t i; - backend_internal *backend; - - *out = NULL; - - if (git_vector_length(&cfg->backends) == 0) { - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when no config backends exist", - uses[use], name); - return GIT_ENOTFOUND; - } - - git_vector_foreach(&cfg->backends, i, backend) { - if (!backend->backend->readonly) { - *out = backend->backend; - return 0; - } - } - - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when all config backends are readonly", - uses[use], name); - return GIT_ENOTFOUND; -} - -int git_config_delete_entry(git_config *cfg, const char *name) -{ - git_config_backend *backend; - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; - - return backend->del(backend, name); -} - -int git_config_set_int64(git_config *cfg, const char *name, int64_t value) -{ - char str_value[32]; /* All numbers should fit in here */ - p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); - return git_config_set_string(cfg, name, str_value); -} - -int git_config_set_int32(git_config *cfg, const char *name, int32_t value) -{ - return git_config_set_int64(cfg, name, (int64_t)value); -} - -int git_config_set_bool(git_config *cfg, const char *name, int value) -{ - return git_config_set_string(cfg, name, value ? "true" : "false"); -} - -int git_config_set_string(git_config *cfg, const char *name, const char *value) -{ - int error; - git_config_backend *backend; - - if (!value) { - git_error_set(GIT_ERROR_CONFIG, "the value to set cannot be NULL"); - return -1; - } - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; - - error = backend->set(backend, name, value); - - if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) - git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); - - return error; -} - -int git_config__update_entry( - git_config *config, - const char *key, - const char *value, - bool overwrite_existing, - bool only_if_existing) -{ - int error = 0; - git_config_entry *ce = NULL; - - if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0) - return error; - - if (!ce && only_if_existing) /* entry doesn't exist */ - return 0; - if (ce && !overwrite_existing) /* entry would be overwritten */ - return 0; - if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */ - return 0; - if (!value && (!ce || !ce->value)) /* asked to delete absent entry */ - return 0; - - if (!value) - error = git_config_delete_entry(config, key); - else - error = git_config_set_string(config, key, value); - - git_config_entry_free(ce); - return error; -} - -/*********** - * Getters - ***********/ - -static int config_error_notfound(const char *name) -{ - git_error_set(GIT_ERROR_CONFIG, "config value '%s' was not found", name); - return GIT_ENOTFOUND; -} - -enum { - GET_ALL_ERRORS = 0, - GET_NO_MISSING = 1, - GET_NO_ERRORS = 2 -}; - -static int get_entry( - git_config_entry **out, - const git_config *cfg, - const char *name, - bool normalize_name, - int want_errors) -{ - int res = GIT_ENOTFOUND; - const char *key = name; - char *normalized = NULL; - size_t i; - backend_internal *internal; - - *out = NULL; - - if (normalize_name) { - if ((res = git_config__normalize_name(name, &normalized)) < 0) - goto cleanup; - key = normalized; - } - - res = GIT_ENOTFOUND; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; - - res = internal->backend->get(internal->backend, key, out); - if (res != GIT_ENOTFOUND) - break; - } - - git__free(normalized); - -cleanup: - if (res == GIT_ENOTFOUND) - res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); - else if (res && (want_errors == GET_NO_ERRORS)) { - git_error_clear(); - res = 0; - } - - return res; -} - -int git_config_get_entry( - git_config_entry **out, const git_config *cfg, const char *name) -{ - return get_entry(out, cfg, name, true, GET_ALL_ERRORS); -} - -int git_config__lookup_entry( - git_config_entry **out, - const git_config *cfg, - const char *key, - bool no_errors) -{ - return get_entry( - out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); -} - -int git_config_get_mapped( - int *out, - const git_config *cfg, - const char *name, - const git_configmap *maps, - size_t map_n) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_lookup_map_value(out, maps, map_n, entry->value); - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_parse_int64(out, entry->value); - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_parse_int32(out, entry->value); - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_bool(int *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_parse_bool(out, entry->value); - git_config_entry_free(entry); - - return ret; -} - -static int is_readonly(const git_config *cfg) -{ - size_t i; - backend_internal *internal; - - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; - - if (!internal->backend->readonly) - return 0; - } - - return 1; -} - -static int git_config__parse_path(git_str *out, const char *value) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(value); - - if (value[0] == '~') { - if (value[1] != '\0' && value[1] != '/') { - git_error_set(GIT_ERROR_CONFIG, "retrieving a homedir by name is not supported"); - return -1; - } - - return git_sysdir_expand_global_file(out, value[1] ? &value[2] : NULL); - } - - return git_str_sets(out, value); -} - -int git_config_parse_path(git_buf *out, const char *value) -{ - GIT_BUF_WRAP_PRIVATE(out, git_config__parse_path, value); -} - -int git_config_get_path( - git_buf *out, - const git_config *cfg, - const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); -} - -int git_config__get_path( - git_str *out, - const git_config *cfg, - const char *name) -{ - git_config_entry *entry; - int error; - - if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return error; - - error = git_config__parse_path(out, entry->value); - git_config_entry_free(entry); - - return error; -} - -int git_config_get_string( - const char **out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if (!is_readonly(cfg)) { - git_error_set(GIT_ERROR_CONFIG, "get_string called on a live config object"); - return -1; - } - - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); - *out = !ret ? (entry->value ? entry->value : "") : NULL; - - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_string_buf( - git_buf *out, const git_config *cfg, const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); -} - -int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - const char *str; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); - - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); - str = !ret ? (entry->value ? entry->value : "") : NULL; - - if (str) - ret = git_str_puts(out, str); - - git_config_entry_free(entry); - - return ret; -} - -char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value) -{ - git_config_entry *entry; - char *ret; - - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); - ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL; - git_config_entry_free(entry); - - return ret; -} - -int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value) -{ - int val = fallback_value; - git_config_entry *entry; - - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); - - if (entry && git_config_parse_bool(&val, entry->value) < 0) - git_error_clear(); - - git_config_entry_free(entry); - return val; -} - -int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value) -{ - int32_t val = (int32_t)fallback_value; - git_config_entry *entry; - - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); - - if (entry && git_config_parse_int32(&val, entry->value) < 0) - git_error_clear(); - - git_config_entry_free(entry); - return (int)val; -} - -int git_config_get_multivar_foreach( - const git_config *cfg, const char *name, const char *regexp, - git_config_foreach_cb cb, void *payload) -{ - int err, found; - git_config_iterator *iter; - git_config_entry *entry; - - if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) - return err; - - found = 0; - while ((err = iter->next(&entry, iter)) == 0) { - found = 1; - - if ((err = cb(entry, payload)) != 0) { - git_error_set_after_callback(err); - break; - } - } - - iter->free(iter); - if (err == GIT_ITEROVER) - err = 0; - - if (found == 0 && err == 0) - err = config_error_notfound(name); - - return err; -} - -typedef struct { - git_config_iterator parent; - git_config_iterator *iter; - char *name; - git_regexp regex; - int have_regex; -} multivar_iter; - -static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) -{ - multivar_iter *iter = (multivar_iter *) _iter; - int error = 0; - - while ((error = iter->iter->next(entry, iter->iter)) == 0) { - if (git__strcmp(iter->name, (*entry)->name)) - continue; - - if (!iter->have_regex) - return 0; - - if (git_regexp_match(&iter->regex, (*entry)->value) == 0) - return 0; - } - - return error; -} - -static void multivar_iter_free(git_config_iterator *_iter) -{ - multivar_iter *iter = (multivar_iter *) _iter; - - iter->iter->free(iter->iter); - - git__free(iter->name); - if (iter->have_regex) - git_regexp_dispose(&iter->regex); - git__free(iter); -} - -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) -{ - multivar_iter *iter = NULL; - git_config_iterator *inner = NULL; - int error; - - if ((error = git_config_iterator_new(&inner, cfg)) < 0) - return error; - - iter = git__calloc(1, sizeof(multivar_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - if ((error = git_config__normalize_name(name, &iter->name)) < 0) - goto on_error; - - if (regexp != NULL) { - if ((error = git_regexp_compile(&iter->regex, regexp, 0)) < 0) - goto on_error; - - iter->have_regex = 1; - } - - iter->iter = inner; - iter->parent.free = multivar_iter_free; - iter->parent.next = multivar_iter_next; - - *out = (git_config_iterator *) iter; - - return 0; - -on_error: - - inner->free(inner); - git__free(iter); - return error; -} - -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) -{ - git_config_backend *backend; - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; - - return backend->set_multivar(backend, name, regexp, value); -} - -int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) -{ - git_config_backend *backend; - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; - - return backend->del_multivar(backend, name, regexp); -} - -int git_config_next(git_config_entry **entry, git_config_iterator *iter) -{ - return iter->next(entry, iter); -} - -void git_config_iterator_free(git_config_iterator *iter) -{ - if (iter == NULL) - return; - - iter->free(iter); -} - -int git_config_find_global(git_buf *path) -{ - GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_GLOBAL); -} - -int git_config__find_global(git_str *path) -{ - return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); -} - -int git_config_find_xdg(git_buf *path) -{ - GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_XDG); -} - -int git_config__find_xdg(git_str *path) -{ - return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG); -} - -int git_config_find_system(git_buf *path) -{ - GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_SYSTEM); -} - -int git_config__find_system(git_str *path) -{ - return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); -} - -int git_config_find_programdata(git_buf *path) -{ - git_str str = GIT_STR_INIT; - int error; - - if ((error = git_buf_tostr(&str, path)) == 0 && - (error = git_config__find_programdata(&str)) == 0) - error = git_buf_fromstr(path, &str); - - git_str_dispose(&str); - return error; -} - -int git_config__find_programdata(git_str *path) -{ - bool is_safe; - - if (git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA) < 0 || - git_fs_path_owner_is_system_or_current_user(&is_safe, path->ptr) < 0) - return -1; - - if (!is_safe) { - git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership"); - return -1; - } - - return 0; -} - -int git_config__global_location(git_str *buf) -{ - const git_str *paths; - const char *sep, *start; - - if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0) - return -1; - - /* no paths, so give up */ - if (!paths || !git_str_len(paths)) - return -1; - - /* find unescaped separator or end of string */ - for (sep = start = git_str_cstr(paths); *sep; ++sep) { - if (*sep == GIT_PATH_LIST_SEPARATOR && - (sep <= start || sep[-1] != '\\')) - break; - } - - if (git_str_set(buf, start, (size_t)(sep - start)) < 0) - return -1; - - return git_str_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL); -} - -int git_config_open_default(git_config **out) -{ - int error; - git_config *cfg = NULL; - git_str buf = GIT_STR_INIT; - - if ((error = git_config_new(&cfg)) < 0) - return error; - - if (!git_config__find_global(&buf) || - !git_config__global_location(&buf)) { - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); - } - - if (!error && !git_config__find_xdg(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_XDG, NULL, 0); - - if (!error && !git_config__find_system(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); - - if (!error && !git_config__find_programdata(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); - - git_str_dispose(&buf); - - if (error) { - git_config_free(cfg); - cfg = NULL; - } - - *out = cfg; - - return error; -} - -int git_config_lock(git_transaction **out, git_config *cfg) -{ - int error; - git_config_backend *backend; - backend_internal *internal; - - GIT_ASSERT_ARG(cfg); - - internal = git_vector_get(&cfg->backends, 0); - if (!internal || !internal->backend) { - git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); - return -1; - } - backend = internal->backend; - - if ((error = backend->lock(backend)) < 0) - return error; - - return git_transaction_config_new(out, cfg); -} - -int git_config_unlock(git_config *cfg, int commit) -{ - git_config_backend *backend; - backend_internal *internal; - - GIT_ASSERT_ARG(cfg); - - internal = git_vector_get(&cfg->backends, 0); - if (!internal || !internal->backend) { - git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); - return -1; - } - - backend = internal->backend; - - return backend->unlock(backend, commit); -} - -/*********** - * Parsers - ***********/ - -int git_config_lookup_map_value( - int *out, - const git_configmap *maps, - size_t map_n, - const char *value) -{ - size_t i; - - for (i = 0; i < map_n; ++i) { - const git_configmap *m = maps + i; - - switch (m->type) { - case GIT_CONFIGMAP_FALSE: - case GIT_CONFIGMAP_TRUE: { - int bool_val; - - if (git_config_parse_bool(&bool_val, value) == 0 && - bool_val == (int)m->type) { - *out = m->map_value; - return 0; - } - break; - } - - case GIT_CONFIGMAP_INT32: - if (git_config_parse_int32(out, value) == 0) - return 0; - break; - - case GIT_CONFIGMAP_STRING: - if (value && strcasecmp(value, m->str_match) == 0) { - *out = m->map_value; - return 0; - } - break; - } - } - - git_error_set(GIT_ERROR_CONFIG, "failed to map '%s'", value); - return -1; -} - -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_configmap *m = &maps[i]; - - if (m->map_value != enum_val) - continue; - - *type_out = m->type; - *str_out = m->str_match; - return 0; - } - - git_error_set(GIT_ERROR_CONFIG, "invalid enum value"); - return GIT_ENOTFOUND; -} - -int git_config_parse_bool(int *out, const char *value) -{ - if (git__parse_bool(out, value) == 0) - return 0; - - if (git_config_parse_int32(out, value) == 0) { - *out = !!(*out); - return 0; - } - - git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a boolean value", value); - return -1; -} - -int git_config_parse_int64(int64_t *out, const char *value) -{ - const char *num_end; - int64_t num; - - if (!value || git__strntol64(&num, value, strlen(value), &num_end, 0) < 0) - goto fail_parse; - - switch (*num_end) { - case 'g': - case 'G': - num *= 1024; - /* fallthrough */ - - case 'm': - case 'M': - num *= 1024; - /* fallthrough */ - - case 'k': - case 'K': - num *= 1024; - - /* check that that there are no more characters after the - * given modifier suffix */ - if (num_end[1] != '\0') - return -1; - - /* fallthrough */ - - case '\0': - *out = num; - return 0; - - default: - goto fail_parse; - } - -fail_parse: - git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as an integer", value ? value : "(null)"); - return -1; -} - -int git_config_parse_int32(int32_t *out, const char *value) -{ - int64_t tmp; - int32_t truncate; - - if (git_config_parse_int64(&tmp, value) < 0) - goto fail_parse; - - truncate = tmp & 0xFFFFFFFF; - if (truncate != tmp) - goto fail_parse; - - *out = truncate; - return 0; - -fail_parse: - git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a 32-bit integer", value ? value : "(null)"); - return -1; -} - -static int normalize_section(char *start, char *end) -{ - char *scan; - - if (start == end) - return GIT_EINVALIDSPEC; - - /* Validate and downcase range */ - for (scan = start; *scan; ++scan) { - if (end && scan >= end) - break; - if (isalnum(*scan)) - *scan = (char)git__tolower(*scan); - else if (*scan != '-' || scan == start) - return GIT_EINVALIDSPEC; - } - - if (scan == start) - return GIT_EINVALIDSPEC; - - return 0; -} - - -/* Take something the user gave us and make it nice for our hash function */ -int git_config__normalize_name(const char *in, char **out) -{ - char *name, *fdot, *ldot; - - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(out); - - name = git__strdup(in); - GIT_ERROR_CHECK_ALLOC(name); - - fdot = strchr(name, '.'); - ldot = strrchr(name, '.'); - - if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) - goto invalid; - - /* Validate and downcase up to first dot and after last dot */ - if (normalize_section(name, fdot) < 0 || - normalize_section(ldot + 1, NULL) < 0) - goto invalid; - - /* If there is a middle range, make sure it doesn't have newlines */ - while (fdot < ldot) - if (*fdot++ == '\n') - goto invalid; - - *out = name; - return 0; - -invalid: - git__free(name); - git_error_set(GIT_ERROR_CONFIG, "invalid config item name '%s'", in); - return GIT_EINVALIDSPEC; -} - -struct rename_data { - git_config *config; - git_str *name; - size_t old_len; -}; - -static int rename_config_entries_cb( - const git_config_entry *entry, - void *payload) -{ - int error = 0; - struct rename_data *data = (struct rename_data *)payload; - size_t base_len = git_str_len(data->name); - - if (base_len > 0 && - !(error = git_str_puts(data->name, entry->name + data->old_len))) - { - error = git_config_set_string( - data->config, git_str_cstr(data->name), entry->value); - - git_str_truncate(data->name, base_len); - } - - if (!error) - error = git_config_delete_entry(data->config, entry->name); - - return error; -} - -int git_config_rename_section( - git_repository *repo, - const char *old_section_name, - const char *new_section_name) -{ - git_config *config; - git_str pattern = GIT_STR_INIT, replace = GIT_STR_INIT; - int error = 0; - struct rename_data data; - - git_str_puts_escape_regex(&pattern, old_section_name); - - if ((error = git_str_puts(&pattern, "\\..+")) < 0) - goto cleanup; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - goto cleanup; - - data.config = config; - data.name = &replace; - data.old_len = strlen(old_section_name) + 1; - - if ((error = git_str_join(&replace, '.', new_section_name, "")) < 0) - goto cleanup; - - if (new_section_name != NULL && - (error = normalize_section(replace.ptr, strchr(replace.ptr, '.'))) < 0) - { - git_error_set( - GIT_ERROR_CONFIG, "invalid config section '%s'", new_section_name); - goto cleanup; - } - - error = git_config_foreach_match( - config, git_str_cstr(&pattern), rename_config_entries_cb, &data); - -cleanup: - git_str_dispose(&pattern); - git_str_dispose(&replace); - - return error; -} - -int git_config_init_backend(git_config_backend *backend, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT); - return 0; -} diff --git a/src/config.h b/src/config.h deleted file mode 100644 index 01b84b157..000000000 --- a/src/config.h +++ /dev/null @@ -1,110 +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_config_h__ -#define INCLUDE_config_h__ - -#include "common.h" - -#include "git2.h" -#include "git2/config.h" -#include "vector.h" -#include "repository.h" - -#define GIT_CONFIG_FILENAME_PROGRAMDATA "config" -#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" -#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig" -#define GIT_CONFIG_FILENAME_XDG "config" - -#define GIT_CONFIG_FILENAME_INREPO "config" -#define GIT_CONFIG_FILE_MODE 0666 - -struct git_config { - git_refcount rc; - git_vector backends; -}; - -extern int git_config__global_location(git_str *buf); - -extern int git_config__find_global(git_str *path); -extern int git_config__find_xdg(git_str *path); -extern int git_config__find_system(git_str *path); -extern int git_config__find_programdata(git_str *path); - -extern int git_config_rename_section( - git_repository *repo, - const char *old_section_name, /* eg "branch.dummy" */ - const char *new_section_name); /* NULL to drop the old section */ - -extern int git_config__normalize_name(const char *in, char **out); - -/* internal only: does not normalize key and sets out to NULL if not found */ -extern int git_config__lookup_entry( - git_config_entry **out, - const git_config *cfg, - const char *key, - bool no_errors); - -/* internal only: update and/or delete entry string with constraints */ -extern int git_config__update_entry( - git_config *cfg, - const char *key, - const char *value, - bool overwrite_existing, - bool only_if_existing); - -int git_config__get_path( - git_str *out, - const git_config *cfg, - const char *name); - -int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name); - -/* - * Lookup functions that cannot fail. These functions look up a config - * value and return a fallback value if the value is missing or if any - * failures occur while trying to access the value. - */ - -extern char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value); - -extern int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value); - -extern int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value); - -/* API for repository configmap-style lookups from config - not cached, but - * uses configmap value maps and fallbacks - */ -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_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 - * - * Unlocking will allow other writers to update the configuration - * file. Optionally, any changes performed since the lock will be - * applied to the configuration. - * - * @param cfg the configuration - * @param commit boolean which indicates whether to commit any changes - * done since locking - * @return 0 or an error code - */ -GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); - -#endif diff --git a/src/config_backend.h b/src/config_backend.h deleted file mode 100644 index dbb190514..000000000 --- a/src/config_backend.h +++ /dev/null @@ -1,96 +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_config_file_h__ -#define INCLUDE_config_file_h__ - -#include "common.h" - -#include "git2/sys/config.h" -#include "git2/config.h" - -/** - * Create a configuration file backend for ondisk files - * - * These are the normal `.gitconfig` files that Core Git - * processes. Note that you first have to add this file to a - * configuration object before you can query it for configuration - * variables. - * - * @param out the new backend - * @param path where the config file is located - */ -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 - * - * @param out the new backend - * @param cfg the configuration that is to be parsed - * @param len the length of the string pointed to by `cfg` - */ -extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len); - -GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) -{ - return cfg->open(cfg, level, repo); -} - -GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg) -{ - if (cfg) - cfg->free(cfg); -} - -GIT_INLINE(int) git_config_backend_get_string( - git_config_entry **out, git_config_backend *cfg, const char *name) -{ - return cfg->get(cfg, name, out); -} - -GIT_INLINE(int) git_config_backend_set_string( - git_config_backend *cfg, const char *name, const char *value) -{ - return cfg->set(cfg, name, value); -} - -GIT_INLINE(int) git_config_backend_delete( - git_config_backend *cfg, const char *name) -{ - return cfg->del(cfg, name); -} - -GIT_INLINE(int) git_config_backend_foreach( - git_config_backend *cfg, - int (*fn)(const git_config_entry *entry, void *data), - void *data) -{ - return git_config_backend_foreach_match(cfg, NULL, fn, data); -} - -GIT_INLINE(int) git_config_backend_lock(git_config_backend *cfg) -{ - return cfg->lock(cfg); -} - -GIT_INLINE(int) git_config_backend_unlock(git_config_backend *cfg, int success) -{ - return cfg->unlock(cfg, success); -} - -#endif diff --git a/src/config_cache.c b/src/config_cache.c deleted file mode 100644 index 4bb91f52b..000000000 --- a/src/config_cache.c +++ /dev/null @@ -1,142 +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 "common.h" - -#include "futils.h" -#include "repository.h" -#include "config.h" -#include "git2/config.h" -#include "vector.h" -#include "filter.h" - -struct map_data { - const char *name; - git_configmap *maps; - size_t map_count; - int default_value; -}; - -/* - * core.eol - * Sets the line ending type to use in the working directory for - * files that have the text property set. Alternatives are lf, crlf - * and native, which uses the platform's native line ending. The default - * value is native. See gitattributes(5) for more information on - * end-of-line conversion. - */ -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} -}; - -/* - * core.autocrlf - * Setting this variable to "true" is almost the same as setting - * the text attribute to "auto" on all files except that text files are - * not guaranteed to be normalized: files that contain CRLF in the - * repository will not be touched. Use this setting if you want to have - * CRLF line endings in your working directory even though the repository - * does not have normalized line endings. This variable can be set to input, - * in which case no output conversion is performed. - */ -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_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_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_configmap _configmap_int[] = { - {GIT_CONFIGMAP_INT32, NULL, 0}, -}; - -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", _configmap_int, 1, GIT_ABBREV_DEFAULT }, - {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_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 }, - {"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT }, -}; - -int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) -{ - int error = 0; - struct map_data *data = &_configmaps[(int)item]; - git_config_entry *entry; - - if ((error = git_config__lookup_entry(&entry, config, data->name, false)) < 0) - return error; - - if (!entry) - *out = data->default_value; - else if (data->maps) - error = git_config_lookup_map_value( - out, data->maps, data->map_count, entry->value); - else - error = git_config_parse_bool(out, entry->value); - - git_config_entry_free(entry); - return error; -} - -int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item) -{ - intptr_t value = (intptr_t)git_atomic_load(repo->configmap_cache[(int)item]); - - *out = (int)value; - - if (value == GIT_CONFIGMAP_NOT_CACHED) { - git_config *config; - intptr_t oldval = value; - int error; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0 || - (error = git_config__configmap_lookup(out, config, item)) < 0) - return error; - - value = *out; - git_atomic_compare_and_swap(&repo->configmap_cache[(int)item], (void *)oldval, (void *)value); - } - - return 0; -} - -void git_repository__configmap_lookup_cache_clear(git_repository *repo) -{ - int i; - - 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 deleted file mode 100644 index 66aae096d..000000000 --- a/src/config_entries.c +++ /dev/null @@ -1,237 +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 "config_entries.h" - -typedef struct config_entry_list { - struct config_entry_list *next; - struct config_entry_list *last; - git_config_entry *entry; -} config_entry_list; - -typedef struct { - git_config_entry *entry; - bool multivar; -} config_entry_map_head; - -typedef struct config_entries_iterator { - git_config_iterator parent; - git_config_entries *entries; - config_entry_list *head; -} config_entries_iterator; - -struct git_config_entries { - git_refcount rc; - git_strmap *map; - config_entry_list *list; -}; - -int git_config_entries_new(git_config_entries **out) -{ - git_config_entries *entries; - int error; - - entries = git__calloc(1, sizeof(git_config_entries)); - GIT_ERROR_CHECK_ALLOC(entries); - GIT_REFCOUNT_INC(entries); - - if ((error = git_strmap_new(&entries->map)) < 0) - git__free(entries); - else - *out = entries; - - 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; - config_entry_list *head; - int error; - - if ((error = git_config_entries_new(&result)) < 0) - goto out; - - for (head = entries->list; head; head = head->next) - if ((git_config_entries_dup_entry(result, head->entry)) < 0) - goto out; - - *out = result; - result = NULL; - -out: - git_config_entries_free(result); - return error; -} - -void git_config_entries_incref(git_config_entries *entries) -{ - GIT_REFCOUNT_INC(entries); -} - -static void config_entries_free(git_config_entries *entries) -{ - config_entry_list *list = NULL, *next; - config_entry_map_head *head; - - git_strmap_foreach_value(entries->map, head, - git__free((char *) head->entry->name); git__free(head) - ); - git_strmap_free(entries->map); - - list = entries->list; - while (list != NULL) { - next = list->next; - git__free((char *) list->entry->value); - git__free(list->entry); - git__free(list); - list = next; - } - - git__free(entries); -} - -void git_config_entries_free(git_config_entries *entries) -{ - if (entries) - GIT_REFCOUNT_DEC(entries, config_entries_free); -} - -int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) -{ - config_entry_list *list_head; - config_entry_map_head *map_head; - - if ((map_head = git_strmap_get(entries->map, entry->name)) != NULL) { - map_head->multivar = true; - /* - * 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. - */ - git__free((char *) entry->name); - entry->name = map_head->entry->name; - } else { - map_head = git__calloc(1, sizeof(*map_head)); - if ((git_strmap_set(entries->map, entry->name, map_head)) < 0) - return -1; - } - map_head->entry = entry; - - list_head = git__calloc(1, sizeof(config_entry_list)); - GIT_ERROR_CHECK_ALLOC(list_head); - list_head->entry = entry; - - if (entries->list) - entries->list->last->next = list_head; - else - entries->list = list_head; - entries->list->last = list_head; - - return 0; -} - -int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) -{ - config_entry_map_head *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_map_head *entry; - - if ((entry = git_strmap_get(entries->map, key)) == NULL) - return GIT_ENOTFOUND; - - if (entry->multivar) { - git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); - return -1; - } - - if (entry->entry->include_depth) { - git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included"); - return -1; - } - - *out = entry->entry; - - return 0; -} - -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); -} - -static int config_iterator_next( - git_config_entry **entry, - git_config_iterator *iter) -{ - config_entries_iterator *it = (config_entries_iterator *) iter; - - if (!it->head) - return GIT_ITEROVER; - - *entry = it->head->entry; - it->head = it->head->next; - - return 0; -} - -int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries) -{ - config_entries_iterator *it; - - it = git__calloc(1, sizeof(config_entries_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - it->parent.next = config_iterator_next; - it->parent.free = config_iterator_free; - it->head = entries->list; - it->entries = entries; - - git_config_entries_incref(entries); - *out = &it->parent; - - return 0; -} diff --git a/src/config_entries.h b/src/config_entries.h deleted file mode 100644 index 832379e74..000000000 --- a/src/config_entries.h +++ /dev/null @@ -1,24 +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 "common.h" - -#include "git2/sys/config.h" -#include "config.h" - -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 */ -int git_config_entries_append(git_config_entries *entries, git_config_entry *entry); -int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key); -int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key); -int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries); diff --git a/src/config_file.c b/src/config_file.c deleted file mode 100644 index 66fcb8ae2..000000000 --- a/src/config_file.c +++ /dev/null @@ -1,1194 +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 "config.h" - -#include "git2/config.h" -#include "git2/sys/config.h" - -#include "array.h" -#include "str.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" -#include "hash.h" - -/* Max depth for [include] directives */ -#define MAX_INCLUDE_DEPTH 10 - -typedef struct config_file { - git_futils_filestamp stamp; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - char *path; - git_array_t(struct config_file) includes; -} config_file; - -typedef struct { - git_config_backend parent; - git_mutex values_mutex; - git_config_entries *entries; - const git_repository *repo; - git_config_level_t level; - - git_array_t(git_config_parser) readers; - - bool locked; - git_filebuf locked_buf; - git_str locked_content; - - config_file file; -} config_file_backend; - -typedef struct { - const git_repository *repo; - config_file *file; - git_config_entries *entries; - git_config_level_t level; - unsigned int depth; -} config_file_parse_data; - -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); - -/** - * 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 int config_file_entries_take(git_config_entries **out, config_file_backend *b) -{ - int error; - - if ((error = git_mutex_lock(&b->values_mutex)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock config backend"); - return error; - } - - git_config_entries_incref(b->entries); - *out = b->entries; - - git_mutex_unlock(&b->values_mutex); - - return 0; -} - -static void config_file_clear(config_file *file) -{ - config_file *include; - uint32_t i; - - if (file == NULL) - return; - - git_array_foreach(file->includes, i, include) { - config_file_clear(include); - } - git_array_clear(file->includes); - - git__free(file->path); -} - -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; - - b->level = level; - b->repo = repo; - - if ((res = git_config_entries_new(&b->entries)) < 0) - return res; - - if (!git_fs_path_exists(b->file.path)) - return 0; - - /* - * 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_file_is_modified(int *modified, config_file *file) -{ - config_file *include; - git_str buf = GIT_STR_INIT; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - uint32_t i; - int error = 0; - - *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; - - if ((error = git_hash_buf(checksum, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) - goto out; - - if (memcmp(checksum, file->checksum, GIT_HASH_SHA1_SIZE) != 0) { - *modified = 1; - goto out; - } - -check_includes: - git_array_foreach(file->includes, i, include) { - if ((error = config_file_is_modified(modified, include)) < 0 || *modified) - goto out; - } - -out: - git_str_dispose(&buf); - - return error; -} - -static void config_file_clear_includes(config_file_backend *cfg) -{ - config_file *include; - uint32_t i; - - git_array_foreach(cfg->file.includes, i, include) - config_file_clear(include); - git_array_clear(cfg->file.includes); -} - -static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *old = NULL; - int error; - - if (b->parent.readonly) { - git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); - return -1; - } - - 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; - - config_file_clear_includes(b); - - 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; - - config_file_clear_includes(b); - - 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; - - entries = NULL; -out: - git_config_entries_free(entries); - - return (error == GIT_ENOTFOUND) ? 0 : error; -} - -static void config_file_free(git_config_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->entries); - git_mutex_free(&backend->values_mutex); - git__free(backend); -} - -static int config_file_iterator( - git_config_iterator **iter, - struct git_config_backend *backend) -{ - config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); - git_config_entries *dupped = NULL, *entries = NULL; - int error; - - 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_file_snapshot(git_config_backend **out, git_config_backend *backend) -{ - 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; - int error; - - if ((error = git_config__normalize_name(name, &key)) < 0) - return error; - - 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) { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - } else if ((!existing->value && !value) || - (existing->value && value && !strcmp(existing->value, value))) { - /* don't update if old and new values already match */ - error = 0; - goto out; - } - - /* No early returns due to sanity checks, let's write it out and refresh */ - if (value) { - esc_value = escape_value(value); - GIT_ERROR_CHECK_ALLOC(esc_value); - } - - if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0) - goto out; - -out: - git_config_entries_free(entries); - git__free(esc_value); - git__free(key); - return error; -} - -/* release the map containing the entry as an equivalent to freeing it */ -static void config_file_entry_free(git_config_entry *entry) -{ - git_config_entries *entries = (git_config_entries *) entry->payload; - git_config_entries_free(entries); -} - -/* - * Internal function that actually gets the value in string form - */ -static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) -{ - 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_file_refresh(cfg)) < 0)) - return error; - - 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 = config_file_entry_free; - entry->payload = entries; - *out = entry; - - return 0; -} - -static int config_file_set_multivar( - git_config_backend *cfg, const char *name, const char *regexp, const char *value) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_regexp preg; - int result; - char *key; - - GIT_ASSERT_ARG(regexp); - - if ((result = git_config__normalize_name(name, &key)) < 0) - return result; - - if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) - goto out; - - /* 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); - git_regexp_dispose(&preg); - - return result; -} - -static int config_file_delete(git_config_backend *cfg, const char *name) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; - char *key = NULL; - int error; - - if ((error = git_config__normalize_name(name, &key)) < 0) - goto out; - - if ((error = config_file_entries_take(&entries, b)) < 0) - goto out; - - /* Check whether we'd be modifying an included or multivar key */ - if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) { - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); - goto out; - } - - if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) - goto out; - -out: - git_config_entries_free(entries); - git__free(key); - return error; -} - -static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry = NULL; - git_regexp preg = GIT_REGEX_INIT; - char *key = NULL; - int result; - - if ((result = git_config__normalize_name(name, &key)) < 0) - goto out; - - 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) - git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); - goto out; - } - - if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) - goto out; - - if ((result = config_file_write(b, name, key, &preg, NULL)) < 0) - goto out; - -out: - git_config_entries_free(entries); - git__free(key); - git_regexp_dispose(&preg); - return result; -} - -static int config_file_lock(git_config_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) - return error; - - error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path); - if (error < 0 && error != GIT_ENOTFOUND) { - git_filebuf_cleanup(&cfg->locked_buf); - return error; - } - - cfg->locked = true; - return 0; - -} - -static int config_file_unlock(git_config_backend *_cfg, int success) -{ - config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); - int error = 0; - - if (success) { - git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size); - error = git_filebuf_commit(&cfg->locked_buf); - } - - git_filebuf_cleanup(&cfg->locked_buf); - git_str_dispose(&cfg->locked_content); - cfg->locked = false; - - return error; -} - -int git_config_backend_from_file(git_config_backend **out, const char *path) -{ - config_file_backend *backend; - - backend = git__calloc(1, sizeof(config_file_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - 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->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; - - return 0; -} - -static int included_path(git_str *out, const char *dir, const char *path) -{ - /* From the user's home */ - if (path[0] == '~' && path[1] == '/') - return git_sysdir_expand_global_file(out, &path[1]); - - return git_fs_path_join_unrooted(out, path, dir, NULL); -} - -/* Escape the values to write them to the file */ -static char *escape_value(const char *ptr) -{ - git_str buf; - size_t len; - const char *esc; - - GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL); - - len = strlen(ptr); - if (!len) - return git__calloc(1, sizeof(char)); - - if (git_str_init(&buf, len) < 0) - return NULL; - - while (*ptr != '\0') { - if ((esc = strchr(git_config_escaped, *ptr)) != NULL) { - git_str_putc(&buf, '\\'); - git_str_putc(&buf, git_config_escapes[esc - git_config_escaped]); - } else { - git_str_putc(&buf, *ptr); - } - ptr++; - } - - if (git_str_oom(&buf)) - return NULL; - - return git_str_detach(&buf); -} - -static int parse_include(config_file_parse_data *parse_data, const char *file) -{ - config_file *include; - git_str path = GIT_STR_INIT; - char *dir; - int result; - - if (!file) - return 0; - - if ((result = git_fs_path_dirname_r(&path, parse_data->file->path)) < 0) - return result; - - dir = git_str_detach(&path); - result = included_path(&path, dir, file); - git__free(dir); - - if (result < 0) - return result; - - 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_str_detach(&path); - - 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(); - result = 0; - } - - return result; -} - -static int do_match_gitdir( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *condition, - bool case_insensitive) -{ - git_str pattern = GIT_STR_INIT, gitdir = GIT_STR_INIT; - int error; - - if (condition[0] == '.' && git_fs_path_is_dirsep(condition[1])) { - git_fs_path_dirname_r(&pattern, cfg_file); - git_str_joinpath(&pattern, pattern.ptr, condition + 2); - } else if (condition[0] == '~' && git_fs_path_is_dirsep(condition[1])) - git_sysdir_expand_global_file(&pattern, condition + 1); - else if (!git_fs_path_is_absolute(condition)) - git_str_joinpath(&pattern, "**", condition); - else - git_str_sets(&pattern, condition); - - if (git_fs_path_is_dirsep(condition[strlen(condition) - 1])) - git_str_puts(&pattern, "**"); - - if (git_str_oom(&pattern)) { - error = -1; - goto out; - } - - if ((error = git_repository__item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) - goto out; - - if (git_fs_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) - git_str_truncate(&gitdir, gitdir.size - 1); - - *matches = wildmatch(pattern.ptr, gitdir.ptr, - WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; -out: - git_str_dispose(&pattern); - git_str_dispose(&gitdir); - return error; -} - -static int conditional_match_gitdir( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *value) -{ - return do_match_gitdir(matches, repo, cfg_file, value, false); -} - -static int conditional_match_gitdir_i( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *value) -{ - 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_str reference = GIT_STR_INIT, buf = GIT_STR_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_str_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 || - (error = git_futils_readbuffer(&reference, buf.ptr)) < 0) - goto out; - git_str_rtrim(&reference); - - if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) - goto out; - git_str_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); - - if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR))) - goto out; - git_str_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_str_sets(&buf, condition)) < 0) - goto out; - if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]) && - (error = git_str_puts(&buf, "**")) < 0) - goto out; - - *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH; -out: - git_str_dispose(&reference); - git_str_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 }, - { "onbranch:", conditional_match_onbranch } -}; - -static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file) -{ - char *condition; - size_t section_len, i; - int error = 0, matches; - - if (!parse_data->repo || !file) - return 0; - - section_len = strlen(section); - - /* - * We checked that the string starts with `includeIf.` and ends - * in `.path` to get here. Make sure it consists of more. - */ - if (section_len < CONST_STRLEN("includeIf.") + CONST_STRLEN(".path")) - return 0; - - condition = git__substrdup(section + CONST_STRLEN("includeIf."), - section_len - CONST_STRLEN("includeIf.") - CONST_STRLEN(".path")); - - GIT_ERROR_CHECK_ALLOC(condition); - - for (i = 0; i < ARRAY_SIZE(conditions); i++) { - if (git__prefixcmp(condition, conditions[i].prefix)) - continue; - - if ((error = conditions[i].matches(&matches, - parse_data->repo, - parse_data->file->path, - condition + strlen(conditions[i].prefix))) < 0) - break; - - if (matches) - error = parse_include(parse_data, file); - - break; - } - - git__free(condition); - return error; -} - -static int read_on_variable( - git_config_parser *reader, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *data) -{ - config_file_parse_data *parse_data = (config_file_parse_data *)data; - git_str buf = GIT_STR_INIT; - git_config_entry *entry; - const char *c; - int result = 0; - - GIT_UNUSED(reader); - GIT_UNUSED(line); - GIT_UNUSED(line_len); - - if (current_section) { - /* TODO: Once warnings lang, we should likely warn - * here. Git appears to warn in most cases if it sees - * un-namespaced config options. - */ - git_str_puts(&buf, current_section); - git_str_putc(&buf, '.'); - } - - for (c = var_name; *c; c++) - git_str_putc(&buf, git__tolower(*c)); - - if (git_str_oom(&buf)) - return -1; - - entry = git__calloc(1, sizeof(git_config_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - entry->name = git_str_detach(&buf); - entry->value = var_value ? git__strdup(var_value) : NULL; - entry->level = parse_data->level; - entry->include_depth = parse_data->depth; - - if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) - return result; - - result = 0; - - /* Add or append the new config option */ - if (!git__strcmp(entry->name, "include.path")) - result = parse_include(parse_data, entry->value); - else if (!git__prefixcmp(entry->name, "includeif.") && - !git__suffixcmp(entry->name, ".path")) - result = parse_conditional_include(parse_data, entry->name, entry->value); - - return result; -} - -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) -{ - config_file_parse_data parse_data; - git_config_parser reader; - int error; - - if (depth >= MAX_INCLUDE_DEPTH) { - git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached"); - return -1; - } - - /* Initialize the reading position */ - 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') { - error = 0; - goto out; - } - - parse_data.repo = repo; - 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_str contents = GIT_STR_INIT; - struct stat st; - int error; - - if (p_stat(file->path, &st) < 0) { - error = git_fs_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, GIT_HASH_ALGORITHM_SHA1)) < 0) - goto out; - - if ((error = config_file_read_buffer(entries, repo, file, level, depth, - contents.ptr, contents.size)) < 0) - goto out; - -out: - git_str_dispose(&contents); - return error; -} - -static int write_section(git_str *fbuf, const char *key) -{ - int result; - const char *dot; - git_str buf = GIT_STR_INIT; - - /* All of this just for [section "subsection"] */ - dot = strchr(key, '.'); - git_str_putc(&buf, '['); - if (dot == NULL) { - git_str_puts(&buf, key); - } else { - char *escaped; - git_str_put(&buf, key, dot - key); - escaped = escape_value(dot + 1); - GIT_ERROR_CHECK_ALLOC(escaped); - git_str_printf(&buf, " \"%s\"", escaped); - git__free(escaped); - } - git_str_puts(&buf, "]\n"); - - if (git_str_oom(&buf)) - return -1; - - result = git_str_put(fbuf, git_str_cstr(&buf), buf.size); - git_str_dispose(&buf); - - return result; -} - -static const char *quotes_for_value(const char *value) -{ - const char *ptr; - - if (value[0] == ' ' || value[0] == '\0') - return "\""; - - for (ptr = value; *ptr; ++ptr) { - if (*ptr == ';' || *ptr == '#') - return "\""; - } - - if (ptr[-1] == ' ') - return "\""; - - return ""; -} - -struct write_data { - git_str *buf; - git_str buffered_comment; - unsigned int in_section : 1, - preg_replaced : 1; - const char *orig_section; - const char *section; - const char *orig_name; - const char *name; - const git_regexp *preg; - const char *value; -}; - -static int write_line_to(git_str *buf, const char *line, size_t line_len) -{ - int result = git_str_put(buf, line, line_len); - - if (!result && line_len && line[line_len-1] != '\n') - result = git_str_printf(buf, "\n"); - - return result; -} - -static int write_line(struct write_data *write_data, const char *line, size_t line_len) -{ - return write_line_to(write_data->buf, line, line_len); -} - -static int write_value(struct write_data *write_data) -{ - const char *q; - int result; - - q = quotes_for_value(write_data->value); - result = git_str_printf(write_data->buf, - "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q); - - /* If we are updating a single name/value, we're done. Setting `value` - * to `NULL` will prevent us from trying to write it again later (in - * `write_on_section`) if we see the same section repeated. - */ - if (!write_data->preg) - write_data->value = NULL; - - return result; -} - -static int write_on_section( - git_config_parser *reader, - const char *current_section, - const char *line, - size_t line_len, - void *data) -{ - struct write_data *write_data = (struct write_data *)data; - int result = 0; - - GIT_UNUSED(reader); - - /* If we were previously in the correct section (but aren't anymore) - * and haven't written our value (for a simple name/value set, not - * a multivar), then append it to the end of the section before writing - * the new one. - */ - if (write_data->in_section && !write_data->preg && write_data->value) - result = write_value(write_data); - - write_data->in_section = strcmp(current_section, write_data->section) == 0; - - /* - * If there were comments just before this section, dump them as well. - */ - if (!result) { - result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); - git_str_clear(&write_data->buffered_comment); - } - - if (!result) - result = write_line(write_data, line, line_len); - - return result; -} - -static int write_on_variable( - git_config_parser *reader, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *data) -{ - struct write_data *write_data = (struct write_data *)data; - bool has_matched = false; - int error; - - GIT_UNUSED(reader); - GIT_UNUSED(current_section); - - /* - * If there were comments just before this variable, let's dump them as well. - */ - if ((error = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) - return error; - - git_str_clear(&write_data->buffered_comment); - - /* See if we are to update this name/value pair; first examine name */ - if (write_data->in_section && - strcasecmp(write_data->name, var_name) == 0) - has_matched = true; - - /* If we have a regex to match the value, see if it matches */ - if (has_matched && write_data->preg != NULL) - 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. - */ - if (!has_matched) - return write_line(write_data, line, line_len); - - write_data->preg_replaced = 1; - - /* If value is NULL, we are deleting this value; write nothing. */ - if (!write_data->value) - return 0; - - return write_value(write_data); -} - -static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data) -{ - struct write_data *write_data; - - GIT_UNUSED(reader); - - write_data = (struct write_data *)data; - return write_line_to(&write_data->buffered_comment, line, line_len); -} - -static int write_on_eof( - git_config_parser *reader, const char *current_section, void *data) -{ - struct write_data *write_data = (struct write_data *)data; - int result = 0; - - GIT_UNUSED(reader); - - /* - * If we've buffered comments when reaching EOF, make sure to dump them. - */ - if ((result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) - return result; - - /* If we are at the EOF and have not written our value (again, for a - * simple name/value set, not a multivar) then we have never seen the - * section in question and should create a new section and write the - * value. - */ - if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { - /* write the section header unless we're already in it */ - if (!current_section || strcmp(current_section, write_data->section)) - result = write_section(write_data->buf, write_data->orig_section); - - if (!result) - result = write_value(write_data); - } - - return result; -} - -/* - * This is pretty much the parsing, except we write out anything we don't have - */ -static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value) - -{ - char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot; - git_str buf = GIT_STR_INIT, contents = GIT_STR_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) { - error = git_str_puts(&contents, git_str_cstr(&cfg->locked_content) == NULL ? "" : git_str_cstr(&cfg->locked_content)); - } else { - 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 */ - error = git_futils_readbuffer(&contents, cfg->file.path); - } - if (error < 0 && error != GIT_ENOTFOUND) - goto done; - - if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0) - goto done; - - ldot = strrchr(key, '.'); - name = ldot + 1; - section = git__strndup(key, ldot - key); - GIT_ERROR_CHECK_ALLOC(section); - - ldot = strrchr(orig_key, '.'); - orig_name = ldot + 1; - orig_section = git__strndup(orig_key, ldot - orig_key); - GIT_ERROR_CHECK_ALLOC(orig_section); - - write_data.buf = &buf; - write_data.orig_section = orig_section; - write_data.section = section; - write_data.orig_name = orig_name; - write_data.name = name; - write_data.preg = preg; - write_data.value = value; - - 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; - /* Update our copy with the modified contents */ - git_str_dispose(&cfg->locked_content); - git_str_attach(&cfg->locked_content, git_str_detach(&buf), len); - } else { - git_filebuf_write(&file, git_str_cstr(&buf), git_str_len(&buf)); - - 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_str_dispose(&write_data.buffered_comment); - git_str_dispose(&buf); - git_str_dispose(&contents); - git_filebuf_cleanup(&file); - git_config_parser_dispose(&parser); - - return error; -} diff --git a/src/config_mem.c b/src/config_mem.c deleted file mode 100644 index 560229cf5..000000000 --- a/src/config_mem.c +++ /dev/null @@ -1,220 +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 "config.h" - -#include "config_backend.h" -#include "config_parse.h" -#include "config_entries.h" - -typedef struct { - git_config_backend parent; - git_config_entries *entries; - git_str cfg; -} config_memory_backend; - -typedef struct { - git_config_entries *entries; - git_config_level_t level; -} config_memory_parse_data; - -static int config_error_readonly(void) -{ - git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); - return -1; -} - -static int read_variable_cb( - git_config_parser *reader, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *payload) -{ - config_memory_parse_data *parse_data = (config_memory_parse_data *) payload; - git_str buf = GIT_STR_INIT; - git_config_entry *entry; - const char *c; - int result; - - GIT_UNUSED(reader); - GIT_UNUSED(line); - GIT_UNUSED(line_len); - - if (current_section) { - /* TODO: Once warnings land, we should likely warn - * here. Git appears to warn in most cases if it sees - * un-namespaced config options. - */ - git_str_puts(&buf, current_section); - git_str_putc(&buf, '.'); - } - - for (c = var_name; *c; c++) - git_str_putc(&buf, git__tolower(*c)); - - if (git_str_oom(&buf)) - return -1; - - entry = git__calloc(1, sizeof(git_config_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - entry->name = git_str_detach(&buf); - entry->value = var_value ? git__strdup(var_value) : NULL; - entry->level = parse_data->level; - entry->include_depth = 0; - - if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) - return result; - - return result; -} - -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; - int error; - - GIT_UNUSED(repo); - - 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; - - 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) -{ - config_memory_backend *memory_backend = (config_memory_backend *) backend; - return git_config_entries_get(out, memory_backend->entries, key); -} - -static int config_memory_iterator( - git_config_iterator **iter, - git_config_backend *backend) -{ - config_memory_backend *memory_backend = (config_memory_backend *) backend; - git_config_entries *entries; - int error; - - if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0) - goto out; - - if ((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; -} - -static int config_memory_set(git_config_backend *backend, const char *name, const char *value) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - GIT_UNUSED(value); - return config_error_readonly(); -} - -static int config_memory_set_multivar( - git_config_backend *backend, const char *name, const char *regexp, const char *value) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - GIT_UNUSED(value); - return config_error_readonly(); -} - -static int config_memory_delete(git_config_backend *backend, const char *name) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - return config_error_readonly(); -} - -static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - return config_error_readonly(); -} - -static int config_memory_lock(git_config_backend *backend) -{ - GIT_UNUSED(backend); - return config_error_readonly(); -} - -static int config_memory_unlock(git_config_backend *backend, int success) -{ - GIT_UNUSED(backend); - GIT_UNUSED(success); - return config_error_readonly(); -} - -static void config_memory_free(git_config_backend *_backend) -{ - config_memory_backend *backend = (config_memory_backend *)_backend; - - if (backend == NULL) - return; - - git_config_entries_free(backend->entries); - git_str_dispose(&backend->cfg); - git__free(backend); -} - -int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len) -{ - config_memory_backend *backend; - - backend = git__calloc(1, sizeof(config_memory_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - if (git_config_entries_new(&backend->entries) < 0) { - git__free(backend); - return -1; - } - - if (git_str_set(&backend->cfg, cfg, len) < 0) { - git_config_entries_free(backend->entries); - git__free(backend); - return -1; - } - - backend->parent.version = GIT_CONFIG_BACKEND_VERSION; - backend->parent.readonly = 1; - backend->parent.open = config_memory_open; - backend->parent.get = config_memory_get; - backend->parent.set = config_memory_set; - backend->parent.set_multivar = config_memory_set_multivar; - backend->parent.del = config_memory_delete; - backend->parent.del_multivar = config_memory_delete_multivar; - backend->parent.iterator = config_memory_iterator; - backend->parent.lock = config_memory_lock; - backend->parent.unlock = config_memory_unlock; - backend->parent.snapshot = git_config_backend_snapshot; - backend->parent.free = config_memory_free; - - *out = (git_config_backend *)backend; - - return 0; -} diff --git a/src/config_parse.c b/src/config_parse.c deleted file mode 100644 index 06931368e..000000000 --- a/src/config_parse.c +++ /dev/null @@ -1,580 +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 "config_parse.h" - -#include - -const char *git_config_escapes = "ntb\"\\"; -const char *git_config_escaped = "\n\t\b\"\\"; - -static void set_parse_error(git_config_parser *reader, int col, const char *error_str) -{ - 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); -} - - -GIT_INLINE(int) config_keychar(int c) -{ - return isalnum(c) || c == '-'; -} - -static int strip_comments(char *line, int in_quotes) -{ - int quote_count = in_quotes, backslash_count = 0; - char *ptr; - - for (ptr = line; *ptr; ++ptr) { - if (ptr[0] == '"' && ((ptr > line && ptr[-1] != '\\') || ptr == line)) - quote_count++; - - if ((ptr[0] == ';' || ptr[0] == '#') && - (quote_count % 2) == 0 && - (backslash_count % 2) == 0) { - ptr[0] = '\0'; - break; - } - - if (ptr[0] == '\\') - backslash_count++; - else - backslash_count = 0; - } - - /* skip any space at the end */ - while (ptr > line && git__isspace(ptr[-1])) { - ptr--; - } - ptr[0] = '\0'; - - return quote_count; -} - - -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; - const char *first_quote, *last_quote; - const char *line_start = line; - git_str buf = GIT_STR_INIT; - size_t quoted_len, alloc_len, base_name_len = strlen(base_name); - - /* Skip any additional whitespace before our section name */ - while (git__isspace(line[pos])) - pos++; - - /* 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"); - goto end_error; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - if (git_str_grow(&buf, alloc_len) < 0 || - git_str_printf(&buf, "%s.", base_name) < 0) - goto end_error; - - rpos = 0; - - line = first_quote; - c = line[++rpos]; - - /* - * At the end of each iteration, whatever is stored in c will be - * added to the string. In case of error, jump to out - */ - do { - - switch (c) { - case 0: - set_parse_error(reader, 0, "unexpected end-of-line in section header"); - goto end_error; - - case '"': - goto end_parse; - - case '\\': - c = line[++rpos]; - - if (c == 0) { - set_parse_error(reader, rpos, "unexpected end-of-line in section header"); - goto end_error; - } - - default: - break; - } - - git_str_putc(&buf, (char)c); - c = line[++rpos]; - } while (line + rpos < last_quote); - -end_parse: - if (git_str_oom(&buf)) - goto end_error; - - if (line[rpos] != '"' || line[rpos + 1] != ']') { - set_parse_error(reader, rpos, "unexpected text after closing quotes"); - git_str_dispose(&buf); - return -1; - } - - *section_name = git_str_detach(&buf); - return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */ - -end_error: - git_str_dispose(&buf); - - return -1; -} - -static int parse_section_header(git_config_parser *reader, char **section_out) -{ - char *name, *name_end; - int name_length, c, pos; - int result; - char *line; - size_t line_len; - - git_parse_advance_ws(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - if (line == NULL) - return -1; - - /* find the end of the variable's name */ - name_end = strrchr(line, ']'); - if (name_end == NULL) { - git__free(line); - set_parse_error(reader, 0, "missing ']' in section header"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); - name = git__malloc(line_len); - GIT_ERROR_CHECK_ALLOC(name); - - name_length = 0; - pos = 0; - - /* Make sure we were given a section header */ - c = line[pos++]; - GIT_ASSERT(c == '['); - - c = line[pos++]; - - do { - if (git__isspace(c)){ - name[name_length] = '\0'; - 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"); - goto fail_parse; - } - - name[name_length++] = (char)git__tolower(c); - - } while ((c = line[pos++]) != ']'); - - if (line[pos - 1] != ']') { - set_parse_error(reader, pos, "unexpected end of file"); - goto fail_parse; - } - - git__free(line); - - name[name_length] = 0; - *section_out = name; - - return pos; - -fail_parse: - git__free(line); - git__free(name); - return -1; -} - -static int skip_bom(git_parse_ctx *parser) -{ - git_str buf = GIT_STR_INIT_CONST(parser->content, parser->content_len); - git_str_bom_t bom; - int bom_offset = git_str_detect_bom(&bom, &buf); - - if (bom == GIT_STR_BOM_UTF8) - git_parse_advance_chars(parser, bom_offset); - - /* TODO: reference implementation is pretty stupid with BoM */ - - return 0; -} - -/* - (* basic types *) - digit = "0".."9" - integer = digit { digit } - alphabet = "a".."z" + "A" .. "Z" - - section_char = alphabet | "." | "-" - extension_char = (* any character except newline *) - any_char = (* any character *) - variable_char = "alphabet" | "-" - - - (* actual grammar *) - config = { section } - - section = header { definition } - - header = "[" section [subsection | subsection_ext] "]" - - subsection = "." section - subsection_ext = "\"" extension "\"" - - section = section_char { section_char } - extension = extension_char { extension_char } - - definition = variable_name ["=" variable_value] "\n" - - variable_name = variable_char { variable_char } - variable_value = string | boolean | integer - - string = quoted_string | plain_string - quoted_string = "\"" plain_string "\"" - plain_string = { any_char } - - boolean = boolean_true | boolean_false - boolean_true = "yes" | "1" | "true" | "on" - boolean_false = "no" | "0" | "false" | "off" -*/ - -/* '\"' -> '"' etc */ -static int unescape_line( - char **out, bool *is_multi, const char *ptr, int quote_count) -{ - char *str, *fixed, *esc; - size_t ptr_len = strlen(ptr), alloc_len; - - *is_multi = false; - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || - (str = git__malloc(alloc_len)) == NULL) { - return -1; - } - - fixed = str; - - while (*ptr != '\0') { - if (*ptr == '"') { - quote_count++; - } else if (*ptr != '\\') { - *fixed++ = *ptr; - } else { - /* backslash, check the next char */ - ptr++; - /* if we're at the end, it's a multiline, so keep the backslash */ - if (*ptr == '\0') { - *is_multi = true; - goto done; - } - if ((esc = strchr(git_config_escapes, *ptr)) != NULL) { - *fixed++ = git_config_escaped[esc - git_config_escapes]; - } else { - git__free(str); - git_error_set(GIT_ERROR_CONFIG, "invalid escape at %s", ptr); - return -1; - } - } - ptr++; - } - -done: - *fixed = '\0'; - *out = str; - - return 0; -} - -static int parse_multiline_variable(git_config_parser *reader, git_str *value, int in_quotes, size_t *line_len) -{ - int quote_count; - bool multiline = true; - - while (multiline) { - char *line = NULL, *proc_line = NULL; - int error; - - /* Check that the next line exists */ - git_parse_advance_line(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - GIT_ERROR_CHECK_ALLOC(line); - if (GIT_ADD_SIZET_OVERFLOW(line_len, *line_len, reader->ctx.line_len)) { - error = -1; - goto out; - } - - /* - * We've reached the end of the file, there is no continuation. - * (this is not an error). - */ - if (line[0] == '\0') { - error = 0; - goto out; - } - - /* If it was just a comment, pretend it didn't exist */ - quote_count = strip_comments(line, in_quotes); - if (line[0] == '\0') - goto next; - - if ((error = unescape_line(&proc_line, &multiline, - line, in_quotes)) < 0) - goto out; - - /* Add this line to the multiline var */ - if ((error = git_str_puts(value, proc_line)) < 0) - goto out; - -next: - git__free(line); - git__free(proc_line); - in_quotes = quote_count; - continue; - -out: - git__free(line); - git__free(proc_line); - return error; - } - - return 0; -} - -GIT_INLINE(bool) is_namechar(char c) -{ - return isalnum(c) || c == '-'; -} - -static int parse_name( - char **name, const char **value, git_config_parser *reader, const char *line) -{ - const char *name_end = line, *value_start; - - *name = NULL; - *value = NULL; - - while (*name_end && is_namechar(*name_end)) - name_end++; - - if (line == name_end) { - set_parse_error(reader, 0, "invalid configuration key"); - return -1; - } - - value_start = name_end; - - while (*value_start && git__isspace(*value_start)) - value_start++; - - if (*value_start == '=') { - *value = value_start + 1; - } else if (*value_start) { - set_parse_error(reader, 0, "invalid configuration key"); - return -1; - } - - if ((*name = git__strndup(line, name_end - line)) == NULL) - return -1; - - return 0; -} - -static int parse_variable(git_config_parser *reader, char **var_name, char **var_value, size_t *line_len) -{ - const char *value_start = NULL; - char *line = NULL, *name = NULL, *value = NULL; - int quote_count, error; - bool multiline; - - *var_name = NULL; - *var_value = NULL; - - git_parse_advance_ws(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - GIT_ERROR_CHECK_ALLOC(line); - - quote_count = strip_comments(line, 0); - - if ((error = parse_name(&name, &value_start, reader, line)) < 0) - goto out; - - /* - * Now, let's try to parse the value - */ - if (value_start != NULL) { - while (git__isspace(value_start[0])) - value_start++; - - if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0) - goto out; - - if (multiline) { - git_str multi_value = GIT_STR_INIT; - git_str_attach(&multi_value, value, 0); - value = NULL; - - if (parse_multiline_variable(reader, &multi_value, quote_count % 2, line_len) < 0 || - git_str_oom(&multi_value)) { - error = -1; - git_str_dispose(&multi_value); - goto out; - } - - value = git_str_detach(&multi_value); - } - } - - *var_name = name; - *var_value = value; - name = NULL; - value = NULL; - -out: - git__free(name); - git__free(value); - git__free(line); - 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 *payload) -{ - git_parse_ctx *ctx; - char *current_section = NULL, *var_name = NULL, *var_value = NULL; - int result = 0; - - ctx = &parser->ctx; - - skip_bom(ctx); - - for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) { - const char *line_start; - size_t line_len; - char c; - - restart: - line_start = ctx->line; - line_len = ctx->line_len; - - /* - * Get either first non-whitespace character or, if that does - * not exist, the first whitespace character. This is required - * to preserve whitespaces when writing back the file. - */ - if (git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 && - git_parse_peek(&c, ctx, 0) < 0) - continue; - - switch (c) { - case '[': /* section header, new section begins */ - git__free(current_section); - current_section = NULL; - - result = parse_section_header(parser, ¤t_section); - if (result < 0) - break; - - git_parse_advance_chars(ctx, result); - - if (on_section) - 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, - * run the next loop with the rest of the current line - * instead of moving forward. - */ - - if (!git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE)) - goto restart; - - break; - - case '\n': /* comment or whitespace-only */ - case '\r': - case ' ': - case '\t': - case ';': - case '#': - if (on_comment) { - result = on_comment(parser, line_start, line_len, payload); - } - break; - - default: /* assume variable declaration */ - if ((result = parse_variable(parser, &var_name, &var_value, &line_len)) == 0 && on_variable) { - result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload); - git__free(var_name); - git__free(var_value); - } - - break; - } - - if (result < 0) - goto out; - } - - if (on_eof) - result = on_eof(parser, current_section, payload); - -out: - git__free(current_section); - return result; -} diff --git a/src/config_parse.h b/src/config_parse.h deleted file mode 100644 index b791d3245..000000000 --- a/src/config_parse.h +++ /dev/null @@ -1,65 +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_config_parse_h__ -#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 { - 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 *payload); - -typedef int (*git_config_parser_variable_cb)( - git_config_parser *parser, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *payload); - -typedef int (*git_config_parser_comment_cb)( - git_config_parser *parser, - const char *line, - size_t line_len, - void *payload); - -typedef int (*git_config_parser_eof_cb)( - git_config_parser *parser, - const char *current_section, - 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, - 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 *payload); - -#endif diff --git a/src/config_snapshot.c b/src/config_snapshot.c deleted file mode 100644 index e295d2f7f..000000000 --- a/src/config_snapshot.c +++ /dev/null @@ -1,207 +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 "config_backend.h" - -#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 deleted file mode 100644 index 1e1f1e845..000000000 --- a/src/crlf.c +++ /dev/null @@ -1,426 +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 "common.h" - -#include "git2/attr.h" -#include "git2/blob.h" -#include "git2/index.h" -#include "git2/sys/filter.h" - -#include "buf.h" -#include "futils.h" -#include "hash.h" -#include "filter.h" -#include "repository.h" - -typedef enum { - GIT_CRLF_UNDEFINED, - GIT_CRLF_BINARY, - GIT_CRLF_TEXT, - GIT_CRLF_TEXT_INPUT, - GIT_CRLF_TEXT_CRLF, - GIT_CRLF_AUTO, - GIT_CRLF_AUTO_INPUT, - GIT_CRLF_AUTO_CRLF -} git_crlf_t; - -struct crlf_attrs { - int attr_action; /* the .gitattributes setting */ - int crlf_action; /* the core.autocrlf setting */ - - int auto_crlf; - int safe_crlf; - int core_eol; -}; - -struct crlf_filter { - git_filter f; -}; - -static git_crlf_t check_crlf(const char *value) -{ - if (GIT_ATTR_IS_TRUE(value)) - return GIT_CRLF_TEXT; - else if (GIT_ATTR_IS_FALSE(value)) - return GIT_CRLF_BINARY; - else if (GIT_ATTR_IS_UNSPECIFIED(value)) - ; - else if (strcmp(value, "input") == 0) - return GIT_CRLF_TEXT_INPUT; - else if (strcmp(value, "auto") == 0) - return GIT_CRLF_AUTO; - - return GIT_CRLF_UNDEFINED; -} - -static git_configmap_value check_eol(const char *value) -{ - if (GIT_ATTR_IS_UNSPECIFIED(value)) - ; - else if (strcmp(value, "lf") == 0) - return GIT_EOL_LF; - else if (strcmp(value, "crlf") == 0) - return GIT_EOL_CRLF; - - return GIT_EOL_UNSET; -} - -static int has_cr_in_index(const git_filter_source *src) -{ - git_repository *repo = git_filter_source_repo(src); - const char *path = git_filter_source_path(src); - git_index *index; - const git_index_entry *entry; - git_blob *blob; - const void *blobcontent; - git_object_size_t blobsize; - bool found_cr; - - if (!path) - return false; - - if (git_repository_index__weakptr(&index, repo) < 0) { - git_error_clear(); - return false; - } - - if (!(entry = git_index_get_bypath(index, path, 0)) && - !(entry = git_index_get_bypath(index, path, 1))) - return false; - - if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ - return true; - - if (git_blob_lookup(&blob, repo, &entry->id) < 0) - return false; - - blobcontent = git_blob_rawcontent(blob); - blobsize = git_blob_rawsize(blob); - if (!git__is_sizet(blobsize)) - blobsize = (size_t)-1; - - found_cr = (blobcontent != NULL && - blobsize > 0 && - memchr(blobcontent, '\r', (size_t)blobsize) != NULL); - - git_blob_free(blob); - return found_cr; -} - -static int text_eol_is_crlf(struct crlf_attrs *ca) -{ - if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE) - return 1; - else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT) - return 0; - - if (ca->core_eol == GIT_EOL_CRLF) - return 1; - if (ca->core_eol == GIT_EOL_UNSET && GIT_EOL_NATIVE == GIT_EOL_CRLF) - return 1; - - return 0; -} - -static git_configmap_value output_eol(struct crlf_attrs *ca) -{ - switch (ca->crlf_action) { - case GIT_CRLF_BINARY: - return GIT_EOL_UNSET; - case GIT_CRLF_TEXT_CRLF: - return GIT_EOL_CRLF; - case GIT_CRLF_TEXT_INPUT: - return GIT_EOL_LF; - case GIT_CRLF_UNDEFINED: - case GIT_CRLF_AUTO_CRLF: - return GIT_EOL_CRLF; - case GIT_CRLF_AUTO_INPUT: - return GIT_EOL_LF; - case GIT_CRLF_TEXT: - case GIT_CRLF_AUTO: - return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF; - } - - /* TODO: warn when available */ - return ca->core_eol; -} - -GIT_INLINE(int) check_safecrlf( - struct crlf_attrs *ca, - const git_filter_source *src, - git_str_text_stats *stats) -{ - const char *filename = git_filter_source_path(src); - - if (!ca->safe_crlf) - return 0; - - if (output_eol(ca) == GIT_EOL_LF) { - /* - * CRLFs would not be restored by checkout: - * check if we'd remove CRLFs - */ - if (stats->crlf) { - if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ - } else { - if (filename && *filename) - git_error_set( - GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'", - filename); - else - git_error_set( - GIT_ERROR_FILTER, "CRLF would be replaced by LF"); - - return -1; - } - } - } else if (output_eol(ca) == GIT_EOL_CRLF) { - /* - * CRLFs would be added by checkout: - * check if we have "naked" LFs - */ - if (stats->crlf != stats->lf) { - if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ - } else { - if (filename && *filename) - git_error_set( - GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'", - filename); - else - git_error_set( - GIT_ERROR_FILTER, "LF would be replaced by CRLF"); - - return -1; - } - } - } - - return 0; -} - -static int crlf_apply_to_odb( - struct crlf_attrs *ca, - git_str *to, - const git_str *from, - const git_filter_source *src) -{ - git_str_text_stats stats; - bool is_binary; - int error; - - /* Binary attribute? Empty file? Nothing to do */ - if (ca->crlf_action == GIT_CRLF_BINARY || from->size == 0) - return GIT_PASSTHROUGH; - - is_binary = git_str_gather_text_stats(&stats, from, false); - - /* Heuristics to see if we can skip the conversion. - * Straight from Core Git. - */ - if (ca->crlf_action == GIT_CRLF_AUTO || - ca->crlf_action == GIT_CRLF_AUTO_INPUT || - ca->crlf_action == GIT_CRLF_AUTO_CRLF) { - - if (is_binary) - return GIT_PASSTHROUGH; - - /* - * If the file in the index has any CR in it, do not convert. - * This is the new safer autocrlf handling. - */ - if (has_cr_in_index(src)) - return GIT_PASSTHROUGH; - } - - if ((error = check_safecrlf(ca, src, &stats)) < 0) - return error; - - /* If there are no CR characters to filter out, then just pass */ - if (!stats.crlf) - return GIT_PASSTHROUGH; - - /* Actually drop the carriage returns */ - return git_str_crlf_to_lf(to, from); -} - -static int crlf_apply_to_workdir( - struct crlf_attrs *ca, - git_str *to, - const git_str *from) -{ - git_str_text_stats stats; - bool is_binary; - - /* Empty file? Nothing to do. */ - if (git_str_len(from) == 0 || output_eol(ca) != GIT_EOL_CRLF) - return GIT_PASSTHROUGH; - - is_binary = git_str_gather_text_stats(&stats, from, false); - - /* If there are no LFs, or all LFs are part of a CRLF, nothing to do */ - if (stats.lf == 0 || stats.lf == stats.crlf) - return GIT_PASSTHROUGH; - - if (ca->crlf_action == GIT_CRLF_AUTO || - ca->crlf_action == GIT_CRLF_AUTO_INPUT || - ca->crlf_action == GIT_CRLF_AUTO_CRLF) { - - /* If we have any existing CR or CRLF line endings, do nothing */ - if (stats.cr > 0) - return GIT_PASSTHROUGH; - - /* Don't filter binary files */ - if (is_binary) - return GIT_PASSTHROUGH; - } - - return git_str_lf_to_crlf(to, from); -} - -static int convert_attrs( - struct crlf_attrs *ca, - const char **attr_values, - const git_filter_source *src) -{ - int error; - - memset(ca, 0, sizeof(struct crlf_attrs)); - - 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 */ - if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) && - ca->safe_crlf == GIT_SAFE_CRLF_FAIL) - ca->safe_crlf = GIT_SAFE_CRLF_WARN; - - if (attr_values) { - /* load the text attribute */ - ca->crlf_action = check_crlf(attr_values[2]); /* text */ - - if (ca->crlf_action == GIT_CRLF_UNDEFINED) - ca->crlf_action = check_crlf(attr_values[0]); /* crlf */ - - if (ca->crlf_action != GIT_CRLF_BINARY) { - /* load the eol attribute */ - int eol_attr = check_eol(attr_values[1]); - - if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_LF) - ca->crlf_action = GIT_CRLF_AUTO_INPUT; - else if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_CRLF) - ca->crlf_action = GIT_CRLF_AUTO_CRLF; - else if (eol_attr == GIT_EOL_LF) - ca->crlf_action = GIT_CRLF_TEXT_INPUT; - else if (eol_attr == GIT_EOL_CRLF) - ca->crlf_action = GIT_CRLF_TEXT_CRLF; - } - - ca->attr_action = ca->crlf_action; - } else { - ca->crlf_action = GIT_CRLF_UNDEFINED; - } - - if (ca->crlf_action == GIT_CRLF_TEXT) - ca->crlf_action = text_eol_is_crlf(ca) ? GIT_CRLF_TEXT_CRLF : GIT_CRLF_TEXT_INPUT; - if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_FALSE) - ca->crlf_action = GIT_CRLF_BINARY; - if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_TRUE) - ca->crlf_action = GIT_CRLF_AUTO_CRLF; - if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_INPUT) - ca->crlf_action = GIT_CRLF_AUTO_INPUT; - - return 0; -} - -static int crlf_check( - git_filter *self, - void **payload, /* points to NULL ptr on entry, may be set */ - const git_filter_source *src, - const char **attr_values) -{ - struct crlf_attrs ca; - - GIT_UNUSED(self); - - convert_attrs(&ca, attr_values, src); - - if (ca.crlf_action == GIT_CRLF_BINARY) - return GIT_PASSTHROUGH; - - *payload = git__malloc(sizeof(ca)); - GIT_ERROR_CHECK_ALLOC(*payload); - memcpy(*payload, &ca, sizeof(ca)); - - return 0; -} - -static int crlf_apply( - git_filter *self, - void **payload, /* may be read and/or set */ - git_str *to, - const git_str *from, - const git_filter_source *src) -{ - int error = 0; - - /* initialize payload in case `check` was bypassed */ - if (!*payload) { - if ((error = crlf_check(self, payload, src, NULL)) < 0) - return error; - } - - if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) - error = crlf_apply_to_workdir(*payload, to, from); - else - error = crlf_apply_to_odb(*payload, to, from, src); - - return error; -} - -static int crlf_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, crlf_apply, NULL, payload, src, next); -} - -static void crlf_cleanup( - git_filter *self, - void *payload) -{ - GIT_UNUSED(self); - git__free(payload); -} - -git_filter *git_crlf_filter_new(void) -{ - struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter)); - if (f == NULL) - return NULL; - - f->f.version = GIT_FILTER_VERSION; - f->f.attributes = "crlf eol text"; - f->f.initialize = NULL; - f->f.shutdown = git_filter_free; - f->f.check = crlf_check; - f->f.stream = crlf_stream; - f->f.cleanup = crlf_cleanup; - - return (git_filter *)f; -} diff --git a/src/date.c b/src/date.c deleted file mode 100644 index 0e5ffc96b..000000000 --- a/src/date.c +++ /dev/null @@ -1,898 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ - -#include "common.h" - -#ifndef GIT_WIN32 -#include -#endif - -#include "util.h" -#include "posix.h" -#include "date.h" - -#include -#include - -typedef enum { - DATE_NORMAL = 0, - DATE_RELATIVE, - DATE_SHORT, - DATE_LOCAL, - DATE_ISO8601, - DATE_RFC2822, - DATE_RAW -} date_mode; - -/* - * This is like mktime, but without normalization of tm_wday and tm_yday. - */ -static git_time_t tm_to_time_t(const struct tm *tm) -{ - static const int mdays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - int year = tm->tm_year - 70; - int month = tm->tm_mon; - int day = tm->tm_mday; - - if (year < 0 || year > 129) /* algo only works for 1970-2099 */ - return -1; - if (month < 0 || month > 11) /* array bounds */ - return -1; - if (month < 2 || (year + 2) % 4) - day--; - if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) - return -1; - return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + - tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; -} - -static const char *month_names[] = { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" -}; - -static const char *weekday_names[] = { - "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" -}; - - - -/* - * Check these. And note how it doesn't do the summer-time conversion. - * - * In my world, it's always summer, and things are probably a bit off - * in other ways too. - */ -static const struct { - const char *name; - int offset; - int dst; -} timezone_names[] = { - { "IDLW", -12, 0, }, /* International Date Line West */ - { "NT", -11, 0, }, /* Nome */ - { "CAT", -10, 0, }, /* Central Alaska */ - { "HST", -10, 0, }, /* Hawaii Standard */ - { "HDT", -10, 1, }, /* Hawaii Daylight */ - { "YST", -9, 0, }, /* Yukon Standard */ - { "YDT", -9, 1, }, /* Yukon Daylight */ - { "PST", -8, 0, }, /* Pacific Standard */ - { "PDT", -8, 1, }, /* Pacific Daylight */ - { "MST", -7, 0, }, /* Mountain Standard */ - { "MDT", -7, 1, }, /* Mountain Daylight */ - { "CST", -6, 0, }, /* Central Standard */ - { "CDT", -6, 1, }, /* Central Daylight */ - { "EST", -5, 0, }, /* Eastern Standard */ - { "EDT", -5, 1, }, /* Eastern Daylight */ - { "AST", -3, 0, }, /* Atlantic Standard */ - { "ADT", -3, 1, }, /* Atlantic Daylight */ - { "WAT", -1, 0, }, /* West Africa */ - - { "GMT", 0, 0, }, /* Greenwich Mean */ - { "UTC", 0, 0, }, /* Universal (Coordinated) */ - { "Z", 0, 0, }, /* Zulu, alias for UTC */ - - { "WET", 0, 0, }, /* Western European */ - { "BST", 0, 1, }, /* British Summer */ - { "CET", +1, 0, }, /* Central European */ - { "MET", +1, 0, }, /* Middle European */ - { "MEWT", +1, 0, }, /* Middle European Winter */ - { "MEST", +1, 1, }, /* Middle European Summer */ - { "CEST", +1, 1, }, /* Central European Summer */ - { "MESZ", +1, 1, }, /* Middle European Summer */ - { "FWT", +1, 0, }, /* French Winter */ - { "FST", +1, 1, }, /* French Summer */ - { "EET", +2, 0, }, /* Eastern Europe */ - { "EEST", +2, 1, }, /* Eastern European Daylight */ - { "WAST", +7, 0, }, /* West Australian Standard */ - { "WADT", +7, 1, }, /* West Australian Daylight */ - { "CCT", +8, 0, }, /* China Coast */ - { "JST", +9, 0, }, /* Japan Standard */ - { "EAST", +10, 0, }, /* Eastern Australian Standard */ - { "EADT", +10, 1, }, /* Eastern Australian Daylight */ - { "GST", +10, 0, }, /* Guam Standard */ - { "NZT", +12, 0, }, /* New Zealand */ - { "NZST", +12, 0, }, /* New Zealand Standard */ - { "NZDT", +12, 1, }, /* New Zealand Daylight */ - { "IDLE", +12, 0, }, /* International Date Line East */ -}; - -static size_t match_string(const char *date, const char *str) -{ - size_t i = 0; - - for (i = 0; *date; date++, str++, i++) { - if (*date == *str) - continue; - if (toupper(*date) == toupper(*str)) - continue; - if (!isalnum(*date)) - break; - return 0; - } - return i; -} - -static int skip_alpha(const char *date) -{ - int i = 0; - do { - i++; - } while (isalpha(date[i])); - return i; -} - -/* -* Parse month, weekday, or timezone name -*/ -static size_t match_alpha(const char *date, struct tm *tm, int *offset) -{ - unsigned int i; - - for (i = 0; i < 12; i++) { - size_t match = match_string(date, month_names[i]); - if (match >= 3) { - tm->tm_mon = i; - return match; - } - } - - for (i = 0; i < 7; i++) { - size_t match = match_string(date, weekday_names[i]); - if (match >= 3) { - tm->tm_wday = i; - return match; - } - } - - for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { - size_t match = match_string(date, timezone_names[i].name); - if (match >= 3 || match == strlen(timezone_names[i].name)) { - int off = timezone_names[i].offset; - - /* This is bogus, but we like summer */ - off += timezone_names[i].dst; - - /* Only use the tz name offset if we don't have anything better */ - if (*offset == -1) - *offset = 60*off; - - return match; - } - } - - if (match_string(date, "PM") == 2) { - tm->tm_hour = (tm->tm_hour % 12) + 12; - return 2; - } - - if (match_string(date, "AM") == 2) { - tm->tm_hour = (tm->tm_hour % 12) + 0; - return 2; - } - - /* BAD */ - return skip_alpha(date); -} - -static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) -{ - if (month > 0 && month < 13 && day > 0 && day < 32) { - struct tm check = *tm; - struct tm *r = (now_tm ? &check : tm); - git_time_t specified; - - r->tm_mon = month - 1; - r->tm_mday = day; - if (year == -1) { - if (!now_tm) - return 1; - r->tm_year = now_tm->tm_year; - } - else if (year >= 1970 && year < 2100) - r->tm_year = year - 1900; - else if (year > 70 && year < 100) - r->tm_year = year; - else if (year < 38) - r->tm_year = year + 100; - else - return 0; - if (!now_tm) - return 1; - - specified = tm_to_time_t(r); - - /* Be it commit time or author time, it does not make - * sense to specify timestamp way into the future. Make - * sure it is not later than ten days from now... - */ - if (now + 10*24*3600 < specified) - return 0; - tm->tm_mon = r->tm_mon; - tm->tm_mday = r->tm_mday; - if (year != -1) - tm->tm_year = r->tm_year; - return 1; - } - return 0; -} - -static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) -{ - time_t now; - struct tm now_tm; - struct tm *refuse_future; - long num2, num3; - - num2 = strtol(end+1, &end, 10); - num3 = -1; - if (*end == c && isdigit(end[1])) - num3 = strtol(end+1, &end, 10); - - /* Time? Date? */ - switch (c) { - case ':': - if (num3 < 0) - num3 = 0; - if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { - tm->tm_hour = num; - tm->tm_min = num2; - tm->tm_sec = num3; - break; - } - return 0; - - case '-': - case '/': - case '.': - now = time(NULL); - refuse_future = NULL; - if (p_gmtime_r(&now, &now_tm)) - refuse_future = &now_tm; - - if (num > 70) { - /* yyyy-mm-dd? */ - if (is_date(num, num2, num3, refuse_future, now, tm)) - break; - /* yyyy-dd-mm? */ - if (is_date(num, num3, num2, refuse_future, now, tm)) - break; - } - /* Our eastern European friends say dd.mm.yy[yy] - * is the norm there, so giving precedence to - * mm/dd/yy[yy] form only when separator is not '.' - */ - if (c != '.' && - is_date(num3, num, num2, refuse_future, now, tm)) - break; - /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ - if (is_date(num3, num2, num, refuse_future, now, tm)) - break; - /* Funny European mm.dd.yy */ - if (c == '.' && - is_date(num3, num, num2, refuse_future, now, tm)) - break; - return 0; - } - return end - date; -} - -/* - * Have we filled in any part of the time/date yet? - * We just do a binary 'and' to see if the sign bit - * is set in all the values. - */ -static int nodate(struct tm *tm) -{ - return (tm->tm_year & - tm->tm_mon & - tm->tm_mday & - tm->tm_hour & - tm->tm_min & - tm->tm_sec) < 0; -} - -/* - * We've seen a digit. Time? Year? Date? - */ -static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) -{ - size_t n; - char *end; - unsigned long num; - - num = strtoul(date, &end, 10); - - /* - * Seconds since 1970? We trigger on that for any numbers with - * more than 8 digits. This is because we don't want to rule out - * numbers like 20070606 as a YYYYMMDD date. - */ - if (num >= 100000000 && nodate(tm)) { - time_t time = num; - if (p_gmtime_r(&time, tm)) { - *tm_gmt = 1; - return end - date; - } - } - - /* - * Check for special formats: num[-.:/]num[same]num - */ - switch (*end) { - case ':': - case '.': - case '/': - case '-': - if (isdigit(end[1])) { - size_t match = match_multi_number(num, *end, date, end, tm); - if (match) - return match; - } - } - - /* - * None of the special formats? Try to guess what - * the number meant. We use the number of digits - * to make a more educated guess.. - */ - n = 0; - do { - n++; - } while (isdigit(date[n])); - - /* Four-digit year or a timezone? */ - if (n == 4) { - if (num <= 1400 && *offset == -1) { - unsigned int minutes = num % 100; - unsigned int hours = num / 100; - *offset = hours*60 + minutes; - } else if (num > 1900 && num < 2100) - tm->tm_year = num - 1900; - return n; - } - - /* - * Ignore lots of numerals. We took care of 4-digit years above. - * Days or months must be one or two digits. - */ - if (n > 2) - return n; - - /* - * NOTE! We will give precedence to day-of-month over month or - * year numbers in the 1-12 range. So 05 is always "mday 5", - * unless we already have a mday.. - * - * IOW, 01 Apr 05 parses as "April 1st, 2005". - */ - if (num > 0 && num < 32 && tm->tm_mday < 0) { - tm->tm_mday = num; - return n; - } - - /* Two-digit year? */ - if (n == 2 && tm->tm_year < 0) { - if (num < 10 && tm->tm_mday >= 0) { - tm->tm_year = num + 100; - return n; - } - if (num >= 70) { - tm->tm_year = num; - return n; - } - } - - if (num > 0 && num < 13 && tm->tm_mon < 0) - tm->tm_mon = num-1; - - return n; -} - -static size_t match_tz(const char *date, int *offp) -{ - char *end; - int hour = strtoul(date + 1, &end, 10); - size_t n = end - (date + 1); - int min = 0; - - if (n == 4) { - /* hhmm */ - min = hour % 100; - hour = hour / 100; - } else if (n != 2) { - min = 99; /* random stuff */ - } else if (*end == ':') { - /* hh:mm? */ - min = strtoul(end + 1, &end, 10); - if (end - (date + 1) != 5) - min = 99; /* random stuff */ - } /* otherwise we parsed "hh" */ - - /* - * Don't accept any random stuff. Even though some places have - * offset larger than 12 hours (e.g. Pacific/Kiritimati is at - * UTC+14), there is something wrong if hour part is much - * larger than that. We might also want to check that the - * minutes are divisible by 15 or something too. (Offset of - * Kathmandu, Nepal is UTC+5:45) - */ - if (min < 60 && hour < 24) { - int offset = hour * 60 + min; - if (*date == '-') - offset = -offset; - *offp = offset; - } - return end - date; -} - -/* - * Parse a string like "0 +0000" as ancient timestamp near epoch, but - * only when it appears not as part of any other string. - */ -static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset) -{ - char *end; - unsigned long stamp; - int ofs; - - if (*date < '0' || '9' <= *date) - return -1; - stamp = strtoul(date, &end, 10); - if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) - return -1; - date = end + 2; - ofs = strtol(date, &end, 10); - if ((*end != '\0' && (*end != '\n')) || end != date + 4) - return -1; - ofs = (ofs / 100) * 60 + (ofs % 100); - if (date[-1] == '-') - ofs = -ofs; - *timestamp = stamp; - *offset = ofs; - return 0; -} - -/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 - (i.e. English) day/month names, and it doesn't work correctly with %z. */ -static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset) -{ - struct tm tm; - int tm_gmt; - git_time_t dummy_timestamp; - int dummy_offset; - - if (!timestamp) - timestamp = &dummy_timestamp; - if (!offset) - offset = &dummy_offset; - - memset(&tm, 0, sizeof(tm)); - tm.tm_year = -1; - tm.tm_mon = -1; - tm.tm_mday = -1; - tm.tm_isdst = -1; - tm.tm_hour = -1; - tm.tm_min = -1; - tm.tm_sec = -1; - *offset = -1; - tm_gmt = 0; - - if (*date == '@' && - !match_object_header_date(date + 1, timestamp, offset)) - return 0; /* success */ - for (;;) { - size_t match = 0; - unsigned char c = *date; - - /* Stop at end of string or newline */ - if (!c || c == '\n') - break; - - if (isalpha(c)) - match = match_alpha(date, &tm, offset); - else if (isdigit(c)) - match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit(date[1])) - match = match_tz(date, offset); - - if (!match) { - /* BAD */ - match = 1; - } - - date += match; - } - - /* mktime uses local timezone */ - *timestamp = tm_to_time_t(&tm); - if (*offset == -1) - *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60; - - if (*timestamp == (git_time_t)-1) - return -1; - - if (!tm_gmt) - *timestamp -= *offset * 60; - return 0; /* success */ -} - - -/* - * Relative time update (eg "2 days ago"). If we haven't set the time - * yet, we need to set it from current time. - */ -static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec) -{ - time_t n; - - if (tm->tm_mday < 0) - tm->tm_mday = now->tm_mday; - if (tm->tm_mon < 0) - tm->tm_mon = now->tm_mon; - if (tm->tm_year < 0) { - tm->tm_year = now->tm_year; - if (tm->tm_mon > now->tm_mon) - tm->tm_year--; - } - - n = mktime(tm) - sec; - p_localtime_r(&n, tm); - return n; -} - -static void date_now(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - update_tm(tm, now, 0); -} - -static void date_yesterday(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - update_tm(tm, now, 24*60*60); -} - -static void date_time(struct tm *tm, struct tm *now, int hour) -{ - if (tm->tm_hour < hour) - date_yesterday(tm, now, NULL); - tm->tm_hour = hour; - tm->tm_min = 0; - tm->tm_sec = 0; -} - -static void date_midnight(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 0); -} - -static void date_noon(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 12); -} - -static void date_tea(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 17); -} - -static void date_pm(struct tm *tm, struct tm *now, int *num) -{ - int hour, n = *num; - *num = 0; - GIT_UNUSED(now); - - hour = tm->tm_hour; - if (n) { - hour = n; - tm->tm_min = 0; - tm->tm_sec = 0; - } - tm->tm_hour = (hour % 12) + 12; -} - -static void date_am(struct tm *tm, struct tm *now, int *num) -{ - int hour, n = *num; - *num = 0; - GIT_UNUSED(now); - - hour = tm->tm_hour; - if (n) { - hour = n; - tm->tm_min = 0; - tm->tm_sec = 0; - } - tm->tm_hour = (hour % 12); -} - -static void date_never(struct tm *tm, struct tm *now, int *num) -{ - time_t n = 0; - GIT_UNUSED(now); - GIT_UNUSED(num); - p_localtime_r(&n, tm); -} - -static const struct special { - const char *name; - void (*fn)(struct tm *, struct tm *, int *); -} special[] = { - { "yesterday", date_yesterday }, - { "noon", date_noon }, - { "midnight", date_midnight }, - { "tea", date_tea }, - { "PM", date_pm }, - { "AM", date_am }, - { "never", date_never }, - { "now", date_now }, - { NULL } -}; - -static const char *number_name[] = { - "zero", "one", "two", "three", "four", - "five", "six", "seven", "eight", "nine", "ten", -}; - -static const struct typelen { - const char *type; - int length; -} typelen[] = { - { "seconds", 1 }, - { "minutes", 60 }, - { "hours", 60*60 }, - { "days", 24*60*60 }, - { "weeks", 7*24*60*60 }, - { NULL } -}; - -static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) -{ - const struct typelen *tl; - const struct special *s; - const char *end = date; - int i; - - while (isalpha(*++end)) - /* scan to non-alpha */; - - for (i = 0; i < 12; i++) { - size_t match = match_string(date, month_names[i]); - if (match >= 3) { - tm->tm_mon = i; - *touched = 1; - return end; - } - } - - for (s = special; s->name; s++) { - size_t len = strlen(s->name); - if (match_string(date, s->name) == len) { - s->fn(tm, now, num); - *touched = 1; - return end; - } - } - - if (!*num) { - for (i = 1; i < 11; i++) { - size_t len = strlen(number_name[i]); - if (match_string(date, number_name[i]) == len) { - *num = i; - *touched = 1; - return end; - } - } - if (match_string(date, "last") == 4) { - *num = 1; - *touched = 1; - } - return end; - } - - tl = typelen; - while (tl->type) { - size_t len = strlen(tl->type); - if (match_string(date, tl->type) >= len-1) { - update_tm(tm, now, tl->length * (unsigned long)*num); - *num = 0; - *touched = 1; - return end; - } - tl++; - } - - for (i = 0; i < 7; i++) { - size_t match = match_string(date, weekday_names[i]); - if (match >= 3) { - int diff, n = *num -1; - *num = 0; - - diff = tm->tm_wday - i; - if (diff <= 0) - n++; - diff += 7*n; - - update_tm(tm, now, diff * 24 * 60 * 60); - *touched = 1; - return end; - } - } - - if (match_string(date, "months") >= 5) { - int n; - update_tm(tm, now, 0); /* fill in date fields if needed */ - n = tm->tm_mon - *num; - *num = 0; - while (n < 0) { - n += 12; - tm->tm_year--; - } - tm->tm_mon = n; - *touched = 1; - return end; - } - - if (match_string(date, "years") >= 4) { - update_tm(tm, now, 0); /* fill in date fields if needed */ - tm->tm_year -= *num; - *num = 0; - *touched = 1; - return end; - } - - return end; -} - -static const char *approxidate_digit(const char *date, struct tm *tm, int *num) -{ - char *end; - unsigned long number = strtoul(date, &end, 10); - - switch (*end) { - case ':': - case '.': - case '/': - case '-': - if (isdigit(end[1])) { - size_t match = match_multi_number(number, *end, date, end, tm); - if (match) - return date + match; - } - } - - /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ - if (date[0] != '0' || end - date <= 2) - *num = number; - return end; -} - -/* - * Do we have a pending number at the end, or when - * we see a new one? Let's assume it's a month day, - * as in "Dec 6, 1992" - */ -static void pending_number(struct tm *tm, int *num) -{ - int number = *num; - - if (number) { - *num = 0; - if (tm->tm_mday < 0 && number < 32) - tm->tm_mday = number; - else if (tm->tm_mon < 0 && number < 13) - tm->tm_mon = number-1; - else if (tm->tm_year < 0) { - if (number > 1969 && number < 2100) - tm->tm_year = number - 1900; - else if (number > 69 && number < 100) - tm->tm_year = number; - else if (number < 38) - tm->tm_year = 100 + number; - /* We mess up for number = 00 ? */ - } - } -} - -static git_time_t approxidate_str(const char *date, - time_t time_sec, - int *error_ret) -{ - int number = 0; - int touched = 0; - struct tm tm = {0}, now; - - p_localtime_r(&time_sec, &tm); - now = tm; - - tm.tm_year = -1; - tm.tm_mon = -1; - tm.tm_mday = -1; - - for (;;) { - unsigned char c = *date; - if (!c) - break; - date++; - if (isdigit(c)) { - pending_number(&tm, &number); - date = approxidate_digit(date-1, &tm, &number); - touched = 1; - continue; - } - if (isalpha(c)) - date = approxidate_alpha(date-1, &tm, &now, &number, &touched); - } - pending_number(&tm, &number); - if (!touched) - *error_ret = -1; - return update_tm(&tm, &now, 0); -} - -int git_date_parse(git_time_t *out, const char *date) -{ - time_t time_sec; - git_time_t timestamp; - int offset, error_ret=0; - - if (!parse_date_basic(date, ×tamp, &offset)) { - *out = timestamp; - return 0; - } - - if (time(&time_sec) == -1) - return -1; - - *out = approxidate_str(date, time_sec, &error_ret); - return error_ret; -} - -int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) -{ - time_t t; - struct tm gmt; - - GIT_ASSERT_ARG(out); - - t = (time_t) (time + offset * 60); - - if (p_gmtime_r(&t, &gmt) == NULL) - return -1; - - return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d", - weekday_names[gmt.tm_wday], - gmt.tm_mday, - month_names[gmt.tm_mon], - gmt.tm_year + 1900, - gmt.tm_hour, gmt.tm_min, gmt.tm_sec, - offset / 60, offset % 60); -} - diff --git a/src/date.h b/src/date.h deleted file mode 100644 index 7ebd3c30e..000000000 --- a/src/date.h +++ /dev/null @@ -1,33 +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_date_h__ -#define INCLUDE_date_h__ - -#include "util.h" -#include "str.h" - -/* - * Parse a string into a value as a git_time_t. - * - * Sample valid input: - * - "yesterday" - * - "July 17, 2003" - * - "2003-7-17 08:23" - */ -extern int git_date_parse(git_time_t *out, const char *date); - -/* - * Format a git_time as a RFC2822 string - * - * @param out buffer to store formatted date - * @param time the time to be formatted - * @param offset the timezone offset - * @return 0 if successful; -1 on error - */ -extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset); - -#endif diff --git a/src/delta.c b/src/delta.c deleted file mode 100644 index 2d2c5fa85..000000000 --- a/src/delta.c +++ /dev/null @@ -1,628 +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 "delta.h" - -/* maximum hash entry list for the same hash bucket */ -#define HASH_LIMIT 64 - -#define RABIN_SHIFT 23 -#define RABIN_WINDOW 16 - -static const unsigned int T[256] = { - 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344, - 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259, - 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85, - 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2, - 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a, - 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db, - 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753, - 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964, - 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8, - 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5, - 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81, - 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6, - 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e, - 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77, - 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff, - 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8, - 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc, - 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1, - 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d, - 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a, - 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2, - 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02, - 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a, - 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd, - 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61, - 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c, - 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08, - 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f, - 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7, - 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe, - 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76, - 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141, - 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65, - 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78, - 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4, - 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93, - 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b, - 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa, - 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872, - 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645, - 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99, - 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84, - 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811 -}; - -static const unsigned int U[256] = { - 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a, - 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48, - 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511, - 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d, - 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8, - 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe, - 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb, - 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937, - 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e, - 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c, - 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d, - 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1, - 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4, - 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa, - 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef, - 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263, - 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302, - 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000, - 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59, - 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5, - 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90, - 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7, - 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2, - 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e, - 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467, - 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765, - 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604, - 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88, - 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd, - 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3, - 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996, - 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a, - 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b, - 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609, - 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50, - 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc, - 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99, - 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf, - 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa, - 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176, - 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f, - 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d, - 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a -}; - -struct index_entry { - const unsigned char *ptr; - unsigned int val; - struct index_entry *next; -}; - -struct git_delta_index { - unsigned long memsize; - const void *src_buf; - size_t src_size; - unsigned int hash_mask; - struct index_entry *hash[GIT_FLEX_ARRAY]; -}; - -static int lookup_index_alloc( - void **out, unsigned long *out_len, size_t entries, size_t hash_count) -{ - size_t entries_len, hash_len, index_len; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&entries_len, entries, sizeof(struct index_entry)); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&hash_len, hash_count, sizeof(struct index_entry *)); - - GIT_ERROR_CHECK_ALLOC_ADD(&index_len, sizeof(struct git_delta_index), entries_len); - GIT_ERROR_CHECK_ALLOC_ADD(&index_len, index_len, hash_len); - - if (!git__is_ulong(index_len)) { - git_error_set(GIT_ERROR_NOMEMORY, "overly large delta"); - return -1; - } - - *out = git__malloc(index_len); - GIT_ERROR_CHECK_ALLOC(*out); - - *out_len = (unsigned long)index_len; - return 0; -} - -int git_delta_index_init( - git_delta_index **out, const void *buf, size_t bufsize) -{ - unsigned int i, hsize, hmask, entries, prev_val, *hash_count; - const unsigned char *data, *buffer = buf; - struct git_delta_index *index; - struct index_entry *entry, **hash; - void *mem; - unsigned long memsize; - - *out = NULL; - - if (!buf || !bufsize) - return 0; - - /* Determine index hash size. Note that indexing skips the - first byte to allow for optimizing the rabin polynomial - initialization in create_delta(). */ - entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW; - if (bufsize >= 0xffffffffUL) { - /* - * Current delta format can't encode offsets into - * reference buffer with more than 32 bits. - */ - entries = 0xfffffffeU / RABIN_WINDOW; - } - hsize = entries / 4; - for (i = 4; i < 31 && (1u << i) < hsize; i++); - hsize = 1 << i; - hmask = hsize - 1; - - if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0) - return -1; - - index = mem; - mem = index->hash; - hash = mem; - mem = hash + hsize; - entry = mem; - - index->memsize = memsize; - index->src_buf = buf; - index->src_size = bufsize; - index->hash_mask = hmask; - memset(hash, 0, hsize * sizeof(*hash)); - - /* allocate an array to count hash entries */ - hash_count = git__calloc(hsize, sizeof(*hash_count)); - if (!hash_count) { - git__free(index); - return -1; - } - - /* then populate the index */ - prev_val = ~0; - for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW; - data >= buffer; - data -= RABIN_WINDOW) { - unsigned int val = 0; - for (i = 1; i <= RABIN_WINDOW; i++) - val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT]; - if (val == prev_val) { - /* keep the lowest of consecutive identical blocks */ - entry[-1].ptr = data + RABIN_WINDOW; - } else { - prev_val = val; - i = val & hmask; - entry->ptr = data + RABIN_WINDOW; - entry->val = val; - entry->next = hash[i]; - hash[i] = entry++; - hash_count[i]++; - } - } - - /* - * Determine a limit on the number of entries in the same hash - * bucket. This guard us against patological data sets causing - * really bad hash distribution with most entries in the same hash - * bucket that would bring us to O(m*n) computing costs (m and n - * corresponding to reference and target buffer sizes). - * - * Make sure none of the hash buckets has more entries than - * we're willing to test. Otherwise we cull the entry list - * uniformly to still preserve a good repartition across - * the reference buffer. - */ - for (i = 0; i < hsize; i++) { - if (hash_count[i] < HASH_LIMIT) - continue; - - entry = hash[i]; - do { - struct index_entry *keep = entry; - int skip = hash_count[i] / HASH_LIMIT / 2; - do { - entry = entry->next; - } while(--skip && entry); - keep->next = entry; - } while (entry); - } - git__free(hash_count); - - *out = index; - return 0; -} - -void git_delta_index_free(git_delta_index *index) -{ - git__free(index); -} - -size_t git_delta_index_size(git_delta_index *index) -{ - GIT_ASSERT_ARG(index); - - return index->memsize; -} - -/* - * The maximum size for any opcode sequence, including the initial header - * plus rabin window plus biggest copy. - */ -#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7) - -int git_delta_create_from_index( - void **out, - size_t *out_len, - const struct git_delta_index *index, - const void *trg_buf, - size_t trg_size, - size_t max_size) -{ - unsigned int i, bufpos, bufsize, moff, msize, val; - int inscnt; - const unsigned char *ref_data, *ref_top, *data, *top; - unsigned char *buf; - - *out = NULL; - *out_len = 0; - - if (!trg_buf || !trg_size) - return 0; - - if (index->src_size > UINT_MAX || - trg_size > UINT_MAX || - max_size > (UINT_MAX - MAX_OP_SIZE - 1)) { - git_error_set(GIT_ERROR_INVALID, "buffer sizes too large for delta processing"); - return -1; - } - - bufpos = 0; - bufsize = 8192; - if (max_size && bufsize >= max_size) - bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); - buf = git__malloc(bufsize); - GIT_ERROR_CHECK_ALLOC(buf); - - /* store reference buffer size */ - i = (unsigned int)index->src_size; - while (i >= 0x80) { - buf[bufpos++] = i | 0x80; - i >>= 7; - } - buf[bufpos++] = i; - - /* store target buffer size */ - i = (unsigned int)trg_size; - while (i >= 0x80) { - buf[bufpos++] = i | 0x80; - i >>= 7; - } - buf[bufpos++] = i; - - ref_data = index->src_buf; - ref_top = ref_data + index->src_size; - data = trg_buf; - top = (const unsigned char *) trg_buf + trg_size; - - bufpos++; - val = 0; - for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) { - buf[bufpos++] = *data; - val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; - } - inscnt = i; - - moff = 0; - msize = 0; - while (data < top) { - if (msize < 4096) { - struct index_entry *entry; - val ^= U[data[-RABIN_WINDOW]]; - val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; - i = val & index->hash_mask; - for (entry = index->hash[i]; entry; entry = entry->next) { - const unsigned char *ref = entry->ptr; - const unsigned char *src = data; - unsigned int ref_size = (unsigned int)(ref_top - ref); - if (entry->val != val) - continue; - if (ref_size > (unsigned int)(top - src)) - ref_size = (unsigned int)(top - src); - if (ref_size <= msize) - break; - while (ref_size-- && *src++ == *ref) - ref++; - if (msize < (unsigned int)(ref - entry->ptr)) { - /* this is our best match so far */ - msize = (unsigned int)(ref - entry->ptr); - moff = (unsigned int)(entry->ptr - ref_data); - if (msize >= 4096) /* good enough */ - break; - } - } - } - - if (msize < 4) { - if (!inscnt) - bufpos++; - buf[bufpos++] = *data++; - inscnt++; - if (inscnt == 0x7f) { - buf[bufpos - inscnt - 1] = inscnt; - inscnt = 0; - } - msize = 0; - } else { - unsigned int left; - unsigned char *op; - - if (inscnt) { - while (moff && ref_data[moff-1] == data[-1]) { - /* we can match one byte back */ - msize++; - moff--; - data--; - bufpos--; - if (--inscnt) - continue; - bufpos--; /* remove count slot */ - inscnt--; /* make it -1 */ - break; - } - buf[bufpos - inscnt - 1] = inscnt; - inscnt = 0; - } - - /* A copy op is currently limited to 64KB (pack v2) */ - left = (msize < 0x10000) ? 0 : (msize - 0x10000); - msize -= left; - - op = buf + bufpos++; - i = 0x80; - - if (moff & 0x000000ff) - buf[bufpos++] = moff >> 0, i |= 0x01; - if (moff & 0x0000ff00) - buf[bufpos++] = moff >> 8, i |= 0x02; - if (moff & 0x00ff0000) - buf[bufpos++] = moff >> 16, i |= 0x04; - if (moff & 0xff000000) - buf[bufpos++] = moff >> 24, i |= 0x08; - - if (msize & 0x00ff) - buf[bufpos++] = msize >> 0, i |= 0x10; - if (msize & 0xff00) - buf[bufpos++] = msize >> 8, i |= 0x20; - - *op = i; - - data += msize; - moff += msize; - msize = left; - - if (msize < 4096) { - int j; - val = 0; - for (j = -RABIN_WINDOW; j < 0; j++) - val = ((val << 8) | data[j]) - ^ T[val >> RABIN_SHIFT]; - } - } - - if (bufpos >= bufsize - MAX_OP_SIZE) { - void *tmp = buf; - bufsize = bufsize * 3 / 2; - if (max_size && bufsize >= max_size) - bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); - if (max_size && bufpos > max_size) - break; - buf = git__realloc(buf, bufsize); - if (!buf) { - git__free(tmp); - return -1; - } - } - } - - if (inscnt) - buf[bufpos - inscnt - 1] = inscnt; - - if (max_size && bufpos > max_size) { - git_error_set(GIT_ERROR_NOMEMORY, "delta would be larger than maximum size"); - git__free(buf); - return GIT_EBUFS; - } - - *out_len = bufpos; - *out = buf; - return 0; -} - -/* -* Delta application was heavily cribbed from BinaryDelta.java in JGit, which -* itself was heavily cribbed from patch-delta.c in the -* GIT project. The original delta patching code was written by -* Nicolas Pitre . -*/ - -static int hdr_sz( - size_t *size, - const unsigned char **delta, - const unsigned char *end) -{ - const unsigned char *d = *delta; - size_t r = 0; - unsigned int c, shift = 0; - - do { - if (d == end) { - git_error_set(GIT_ERROR_INVALID, "truncated delta"); - return -1; - } - - c = *d++; - r |= (c & 0x7f) << shift; - shift += 7; - } while (c & 0x80); - *delta = d; - *size = r; - return 0; -} - -int git_delta_read_header( - size_t *base_out, - size_t *result_out, - const unsigned char *delta, - size_t delta_len) -{ - const unsigned char *delta_end = delta + delta_len; - if ((hdr_sz(base_out, &delta, delta_end) < 0) || - (hdr_sz(result_out, &delta, delta_end) < 0)) - return -1; - return 0; -} - -#define DELTA_HEADER_BUFFER_LEN 16 -int git_delta_read_header_fromstream( - size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) -{ - static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; - unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; - const unsigned char *delta, *delta_end; - size_t len; - ssize_t read; - - len = read = 0; - while (len < buffer_len) { - read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); - - if (read == 0) - break; - - if (read == GIT_EBUFS) - continue; - - len += read; - } - - delta = buffer; - delta_end = delta + len; - if ((hdr_sz(base_sz, &delta, delta_end) < 0) || - (hdr_sz(res_sz, &delta, delta_end) < 0)) - return -1; - - return 0; -} - -int git_delta_apply( - void **out, - size_t *out_len, - const unsigned char *base, - size_t base_len, - const unsigned char *delta, - size_t delta_len) -{ - const unsigned char *delta_end = delta + delta_len; - size_t base_sz, res_sz, alloc_sz; - unsigned char *res_dp; - - *out = NULL; - *out_len = 0; - - /* - * Check that the base size matches the data we were given; - * if not we would underflow while accessing data from the - * base object, resulting in data corruption or segfault. - */ - if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { - git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); - return -1; - } - - if (hdr_sz(&res_sz, &delta, delta_end) < 0) { - git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); - res_dp = git__malloc(alloc_sz); - GIT_ERROR_CHECK_ALLOC(res_dp); - - res_dp[res_sz] = '\0'; - *out = res_dp; - *out_len = res_sz; - - while (delta < delta_end) { - unsigned char cmd = *delta++; - if (cmd & 0x80) { - /* cmd is a copy instruction; copy from the base. */ - size_t off = 0, len = 0, end; - -#define ADD_DELTA(o, shift) { if (delta < delta_end) (o) |= ((unsigned) *delta++ << shift); else goto fail; } - if (cmd & 0x01) ADD_DELTA(off, 0UL); - if (cmd & 0x02) ADD_DELTA(off, 8UL); - if (cmd & 0x04) ADD_DELTA(off, 16UL); - if (cmd & 0x08) ADD_DELTA(off, 24UL); - - if (cmd & 0x10) ADD_DELTA(len, 0UL); - if (cmd & 0x20) ADD_DELTA(len, 8UL); - if (cmd & 0x40) ADD_DELTA(len, 16UL); - if (!len) len = 0x10000; -#undef ADD_DELTA - - if (GIT_ADD_SIZET_OVERFLOW(&end, off, len) || - base_len < end || res_sz < len) - goto fail; - - memcpy(res_dp, base + off, len); - res_dp += len; - res_sz -= len; - - } else if (cmd) { - /* - * cmd is a literal insert instruction; copy from - * the delta stream itself. - */ - if (delta_end - delta < cmd || res_sz < cmd) - goto fail; - memcpy(res_dp, delta, cmd); - delta += cmd; - res_dp += cmd; - res_sz -= cmd; - - } else { - /* cmd == 0 is reserved for future encodings. */ - goto fail; - } - } - - if (delta != delta_end || res_sz) - goto fail; - return 0; - -fail: - git__free(*out); - - *out = NULL; - *out_len = 0; - - git_error_set(GIT_ERROR_INVALID, "failed to apply delta"); - return -1; -} diff --git a/src/delta.h b/src/delta.h deleted file mode 100644 index f61987304..000000000 --- a/src/delta.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * diff-delta code taken from git.git. See diff-delta.c for details. - * - */ -#ifndef INCLUDE_git_delta_h__ -#define INCLUDE_git_delta_h__ - -#include "common.h" - -#include "pack.h" - -typedef struct git_delta_index git_delta_index; - -/* - * git_delta_index_init: compute index data from given buffer - * - * This returns a pointer to a struct delta_index that should be passed to - * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer - * is returned on failure. The given buffer must not be freed nor altered - * before free_delta_index() is called. The returned pointer must be freed - * using free_delta_index(). - */ -extern int git_delta_index_init( - git_delta_index **out, const void *buf, size_t bufsize); - -/* - * Free the index created by git_delta_index_init() - */ -extern void git_delta_index_free(git_delta_index *index); - -/* - * Returns memory usage of delta index. - */ -extern size_t git_delta_index_size(git_delta_index *index); - -/* - * create_delta: create a delta from given index for the given buffer - * - * This function may be called multiple times with different buffers using - * the same delta_index pointer. If max_delta_size is non-zero and the - * resulting delta is to be larger than max_delta_size then NULL is returned. - * On success, a non-NULL pointer to the buffer with the delta data is - * returned and *delta_size is updated with its size. The returned buffer - * must be freed by the caller. - */ -extern int git_delta_create_from_index( - void **out, - size_t *out_size, - const struct git_delta_index *index, - const void *buf, - size_t bufsize, - size_t max_delta_size); - -/* - * diff_delta: create a delta from source buffer to target buffer - * - * If max_delta_size is non-zero and the resulting delta is to be larger - * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL - * pointer to the buffer with the delta data is returned and *delta_size is - * updated with its size. The returned buffer must be freed by the caller. - */ -GIT_INLINE(int) git_delta( - void **out, size_t *out_len, - const void *src_buf, size_t src_bufsize, - const void *trg_buf, size_t trg_bufsize, - size_t max_delta_size) -{ - git_delta_index *index; - int error = 0; - - *out = NULL; - *out_len = 0; - - if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0) - return error; - - if (index) { - error = git_delta_create_from_index(out, out_len, - index, trg_buf, trg_bufsize, max_delta_size); - - git_delta_index_free(index); - } - - return error; -} - -/* the smallest possible delta size is 4 bytes */ -#define GIT_DELTA_SIZE_MIN 4 - -/** -* Apply a git binary delta to recover the original content. -* The caller is responsible for freeing the returned buffer. -* -* @param out the output buffer -* @param out_len the length of the output buffer -* @param base the base to copy from during copy instructions. -* @param base_len number of bytes available at base. -* @param delta the delta to execute copy/insert instructions from. -* @param delta_len total number of bytes in the delta. -* @return 0 on success or an error code -*/ -extern int git_delta_apply( - void **out, - size_t *out_len, - const unsigned char *base, - size_t base_len, - const unsigned char *delta, - size_t delta_len); - -/** -* Read the header of a git binary delta. -* -* @param base_out pointer to store the base size field. -* @param result_out pointer to store the result size field. -* @param delta the delta to execute copy/insert instructions from. -* @param delta_len total number of bytes in the delta. -* @return 0 on success or an error code -*/ -extern int git_delta_read_header( - size_t *base_out, - size_t *result_out, - const unsigned char *delta, - size_t delta_len); - -/** - * Read the header of a git binary delta - * - * This variant reads just enough from the packfile stream to read the - * delta header. - */ -extern int git_delta_read_header_fromstream( - size_t *base_out, - size_t *result_out, - git_packfile_stream *stream); - -#endif diff --git a/src/describe.c b/src/describe.c deleted file mode 100644 index 1033eac50..000000000 --- a/src/describe.c +++ /dev/null @@ -1,909 +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 "common.h" - -#include "git2/describe.h" -#include "git2/strarray.h" -#include "git2/diff.h" -#include "git2/status.h" - -#include "buf.h" -#include "commit.h" -#include "commit_list.h" -#include "oidmap.h" -#include "refs.h" -#include "repository.h" -#include "revwalk.h" -#include "tag.h" -#include "vector.h" -#include "wildmatch.h" - -/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ - -struct commit_name { - git_tag *tag; - unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ - unsigned name_checked:1; - git_oid sha1; - char *path; - - /* Khash workaround. They original key has to still be reachable */ - git_oid peeled; -}; - -static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) -{ - return git_oidmap_get(map, key); -} - -static struct commit_name *find_commit_name( - git_oidmap *names, - const git_oid *peeled) -{ - return (struct commit_name *)(oidmap_value_bykey(names, peeled)); -} - -static int replace_name( - git_tag **tag, - git_repository *repo, - struct commit_name *e, - unsigned int prio, - const git_oid *sha1) -{ - git_time_t e_time = 0, t_time = 0; - - if (!e || e->prio < prio) - return 1; - - if (e->prio == 2 && prio == 2) { - /* Multiple annotated tags point to the same commit. - * Select one to keep based upon their tagger date. - */ - git_tag *t = NULL; - - if (!e->tag) { - if (git_tag_lookup(&t, repo, &e->sha1) < 0) - return 1; - e->tag = t; - } - - if (git_tag_lookup(&t, repo, sha1) < 0) - return 0; - - *tag = t; - - if (e->tag->tagger) - e_time = e->tag->tagger->when.time; - - if (t->tagger) - t_time = t->tagger->when.time; - - if (e_time < t_time) - return 1; - } - - return 0; -} - -static int add_to_known_names( - git_repository *repo, - git_oidmap *names, - const char *path, - const git_oid *peeled, - unsigned int prio, - const git_oid *sha1) -{ - struct commit_name *e = find_commit_name(names, peeled); - bool found = (e != NULL); - - git_tag *tag = NULL; - if (replace_name(&tag, repo, e, prio, sha1)) { - if (!found) { - e = git__malloc(sizeof(struct commit_name)); - GIT_ERROR_CHECK_ALLOC(e); - - e->path = NULL; - e->tag = NULL; - } - - if (e->tag) - git_tag_free(e->tag); - e->tag = tag; - e->prio = prio; - e->name_checked = 0; - git_oid_cpy(&e->sha1, sha1); - git__free(e->path); - e->path = git__strdup(path); - git_oid_cpy(&e->peeled, peeled); - - if (!found && git_oidmap_set(names, &e->peeled, e) < 0) - return -1; - } - else - git_tag_free(tag); - - return 0; -} - -static int retrieve_peeled_tag_or_object_oid( - git_oid *peeled_out, - git_oid *ref_target_out, - git_repository *repo, - const char *refname) -{ - git_reference *ref; - git_object *peeled = NULL; - int error; - - if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0) - return error; - - if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0) - goto cleanup; - - git_oid_cpy(ref_target_out, git_reference_target(ref)); - git_oid_cpy(peeled_out, git_object_id(peeled)); - - if (git_oid_cmp(ref_target_out, peeled_out) != 0) - error = 1; /* The reference was pointing to a annotated tag */ - else - error = 0; /* Any other object */ - -cleanup: - git_reference_free(ref); - git_object_free(peeled); - return error; -} - -struct git_describe_result { - int dirty; - int exact_match; - int fallback_to_id; - git_oid commit_id; - git_repository *repo; - struct commit_name *name; - struct possible_tag *tag; -}; - -struct get_name_data -{ - git_describe_options *opts; - git_repository *repo; - git_oidmap *names; - git_describe_result *result; -}; - -static int commit_name_dup(struct commit_name **out, struct commit_name *in) -{ - struct commit_name *name; - - name = git__malloc(sizeof(struct commit_name)); - GIT_ERROR_CHECK_ALLOC(name); - - memcpy(name, in, sizeof(struct commit_name)); - name->tag = NULL; - name->path = NULL; - - if (in->tag && git_tag_dup(&name->tag, in->tag) < 0) - return -1; - - name->path = git__strdup(in->path); - GIT_ERROR_CHECK_ALLOC(name->path); - - *out = name; - return 0; -} - -static int get_name(const char *refname, void *payload) -{ - struct get_name_data *data; - bool is_tag, is_annotated, all; - git_oid peeled, sha1; - unsigned int prio; - int error = 0; - - data = (struct get_name_data *)payload; - is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR); - all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; - - /* Reject anything outside refs/tags/ unless --all */ - if (!all && !is_tag) - return 0; - - /* Accept only tags that match the pattern, if given */ - if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, - refname + strlen(GIT_REFS_TAGS_DIR), 0))) - return 0; - - /* Is it annotated? */ - if ((error = retrieve_peeled_tag_or_object_oid( - &peeled, &sha1, data->repo, refname)) < 0) - return error; - - is_annotated = error; - - /* - * By default, we only use annotated tags, but with --tags - * we fall back to lightweight ones (even without --tags, - * we still remember lightweight ones, only to give hints - * in an error message). --all allows any refs to be used. - */ - if (is_annotated) - prio = 2; - else if (is_tag) - prio = 1; - else - prio = 0; - - add_to_known_names(data->repo, data->names, - all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), - &peeled, prio, &sha1); - return 0; -} - -struct possible_tag { - struct commit_name *name; - int depth; - int found_order; - unsigned flag_within; -}; - -static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in) -{ - struct possible_tag *tag; - int error; - - tag = git__malloc(sizeof(struct possible_tag)); - GIT_ERROR_CHECK_ALLOC(tag); - - memcpy(tag, in, sizeof(struct possible_tag)); - tag->name = NULL; - - if ((error = commit_name_dup(&tag->name, in->name)) < 0) { - git__free(tag); - *out = NULL; - return error; - } - - *out = tag; - return 0; -} - -static int compare_pt(const void *a_, const void *b_) -{ - struct possible_tag *a = (struct possible_tag *)a_; - struct possible_tag *b = (struct possible_tag *)b_; - if (a->depth != b->depth) - return a->depth - b->depth; - if (a->found_order != b->found_order) - return a->found_order - b->found_order; - return 0; -} - -#define SEEN (1u << 0) - -static unsigned long finish_depth_computation( - git_pqueue *list, - git_revwalk *walk, - struct possible_tag *best) -{ - unsigned long seen_commits = 0; - int error, i; - - while (git_pqueue_size(list) > 0) { - git_commit_list_node *c = git_pqueue_pop(list); - seen_commits++; - if (c->flags & best->flag_within) { - size_t index = 0; - while (git_pqueue_size(list) > index) { - git_commit_list_node *i = git_pqueue_get(list, index); - if (!(i->flags & best->flag_within)) - break; - index++; - } - if (index > git_pqueue_size(list)) - break; - } else - best->depth++; - for (i = 0; i < c->out_degree; i++) { - git_commit_list_node *p = c->parents[i]; - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - if (!(p->flags & SEEN)) - if ((error = git_pqueue_insert(list, p)) < 0) - return error; - p->flags |= c->flags; - } - } - return seen_commits; -} - -static int display_name(git_str *buf, git_repository *repo, struct commit_name *n) -{ - if (n->prio == 2 && !n->tag) { - if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) { - git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path); - return -1; - } - } - - if (n->tag && !n->name_checked) { - if (!git_tag_name(n->tag)) { - git_error_set(GIT_ERROR_TAG, "annotated tag '%s' has no embedded name", n->path); - return -1; - } - - /* TODO: Cope with warnings - if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); - */ - - n->name_checked = 1; - } - - if (n->tag) - git_str_printf(buf, "%s", git_tag_name(n->tag)); - else - git_str_printf(buf, "%s", n->path); - - return 0; -} - -static int find_unique_abbrev_size( - int *out, - git_repository *repo, - const git_oid *oid_in, - unsigned int abbreviated_size) -{ - size_t size = abbreviated_size; - git_odb *odb; - git_oid dummy; - int error; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; - - while (size < GIT_OID_HEXSZ) { - if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) { - *out = (int) size; - return 0; - } - - /* If the error wasn't that it's not unique, then it's a proper error */ - if (error != GIT_EAMBIGUOUS) - return error; - - /* Try again with a larger size */ - size++; - } - - /* If we didn't find any shorter prefix, we have to do the whole thing */ - *out = GIT_OID_HEXSZ; - - return 0; -} - -static int show_suffix( - git_str *buf, - int depth, - git_repository *repo, - const git_oid *id, - unsigned int abbrev_size) -{ - int error, size = 0; - - char hex_oid[GIT_OID_HEXSZ]; - - if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0) - return error; - - git_oid_fmt(hex_oid, id); - - git_str_printf(buf, "-%d-g", depth); - - git_str_put(buf, hex_oid, size); - - return git_str_oom(buf) ? -1 : 0; -} - -#define MAX_CANDIDATES_TAGS FLAG_BITS - 1 - -static int describe_not_found(const git_oid *oid, const char *message_format) { - char oid_str[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid_str, sizeof(oid_str), oid); - - git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str); - return GIT_ENOTFOUND; -} - -static int describe( - struct get_name_data *data, - git_commit *commit) -{ - struct commit_name *n; - struct possible_tag *best; - bool all, tags; - git_revwalk *walk = NULL; - git_pqueue list; - git_commit_list_node *cmit, *gave_up_on = NULL; - git_vector all_matches = GIT_VECTOR_INIT; - unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; - unsigned long seen_commits = 0; /* TODO: Check long */ - unsigned int unannotated_cnt = 0; - int error; - - if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0) - return -1; - - if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0) - goto cleanup; - - all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; - tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS; - - git_oid_cpy(&data->result->commit_id, git_commit_id(commit)); - - n = find_commit_name(data->names, git_commit_id(commit)); - if (n && (tags || all || n->prio == 2)) { - /* - * Exact match to an existing ref. - */ - data->result->exact_match = 1; - if ((error = commit_name_dup(&data->result->name, n)) < 0) - goto cleanup; - - goto cleanup; - } - - if (!data->opts->max_candidates_tags) { - error = describe_not_found( - git_commit_id(commit), - "cannot describe - no tag exactly matches '%s'"); - - goto cleanup; - } - - if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0) - goto cleanup; - - if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL) - goto cleanup; - - if ((error = git_commit_list_parse(walk, cmit)) < 0) - goto cleanup; - - cmit->flags = SEEN; - - if ((error = git_pqueue_insert(&list, cmit)) < 0) - goto cleanup; - - while (git_pqueue_size(&list) > 0) - { - int i; - - git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); - seen_commits++; - - n = find_commit_name(data->names, &c->oid); - - if (n) { - if (!tags && !all && n->prio < 2) { - unannotated_cnt++; - } else if (match_cnt < data->opts->max_candidates_tags) { - struct possible_tag *t = git__malloc(sizeof(struct commit_name)); - GIT_ERROR_CHECK_ALLOC(t); - if ((error = git_vector_insert(&all_matches, t)) < 0) - goto cleanup; - - match_cnt++; - - t->name = n; - t->depth = seen_commits - 1; - t->flag_within = 1u << match_cnt; - t->found_order = match_cnt; - c->flags |= t->flag_within; - if (n->prio == 2) - annotated_cnt++; - } - else { - gave_up_on = c; - break; - } - } - - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = git_vector_get(&all_matches, cur_match); - if (!(c->flags & t->flag_within)) - t->depth++; - } - - if (annotated_cnt && (git_pqueue_size(&list) == 0)) { - /* - if (debug) { - char oid_str[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid_str, sizeof(oid_str), &c->oid); - - fprintf(stderr, "finished search at %s\n", oid_str); - } - */ - break; - } - for (i = 0; i < c->out_degree; i++) { - git_commit_list_node *p = c->parents[i]; - if ((error = git_commit_list_parse(walk, p)) < 0) - goto cleanup; - if (!(p->flags & SEEN)) - if ((error = git_pqueue_insert(&list, p)) < 0) - goto cleanup; - p->flags |= c->flags; - - if (data->opts->only_follow_first_parent) - break; - } - } - - if (!match_cnt) { - if (data->opts->show_commit_oid_as_fallback) { - data->result->fallback_to_id = 1; - git_oid_cpy(&data->result->commit_id, &cmit->oid); - - goto cleanup; - } - if (unannotated_cnt) { - error = describe_not_found(git_commit_id(commit), - "cannot describe - " - "no annotated tags can describe '%s'; " - "however, there were unannotated tags."); - goto cleanup; - } - else { - error = describe_not_found(git_commit_id(commit), - "cannot describe - " - "no tags can describe '%s'."); - goto cleanup; - } - } - - git_vector_sort(&all_matches); - - best = (struct possible_tag *)git_vector_get(&all_matches, 0); - - if (gave_up_on) { - if ((error = git_pqueue_insert(&list, gave_up_on)) < 0) - goto cleanup; - seen_commits--; - } - if ((error = finish_depth_computation( - &list, walk, best)) < 0) - goto cleanup; - - seen_commits += error; - if ((error = possible_tag_dup(&data->result->tag, best)) < 0) - goto cleanup; - - /* - { - static const char *prio_names[] = { - "head", "lightweight", "annotated", - }; - - char oid_str[GIT_OID_HEXSZ + 1]; - - if (debug) { - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match); - fprintf(stderr, " %-11s %8d %s\n", - prio_names[t->name->prio], - t->depth, t->name->path); - } - fprintf(stderr, "traversed %lu commits\n", seen_commits); - if (gave_up_on) { - git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid); - fprintf(stderr, - "more than %i tags found; listed %i most recent\n" - "gave up search at %s\n", - data->opts->max_candidates_tags, data->opts->max_candidates_tags, - oid_str); - } - } - } - */ - - git_oid_cpy(&data->result->commit_id, &cmit->oid); - -cleanup: - { - size_t i; - struct possible_tag *match; - git_vector_foreach(&all_matches, i, match) { - git__free(match); - } - } - git_vector_free(&all_matches); - git_pqueue_free(&list); - git_revwalk_free(walk); - return error; -} - -static int normalize_options( - git_describe_options *dst, - const git_describe_options *src) -{ - git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT; - if (!src) src = &default_options; - - *dst = *src; - - if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS) - dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS; - - return 0; -} - -int git_describe_commit( - git_describe_result **result, - git_object *committish, - git_describe_options *opts) -{ - struct get_name_data data; - struct commit_name *name; - git_commit *commit; - int error = -1; - git_describe_options normalized; - - GIT_ASSERT_ARG(result); - GIT_ASSERT_ARG(committish); - - data.result = git__calloc(1, sizeof(git_describe_result)); - GIT_ERROR_CHECK_ALLOC(data.result); - data.result->repo = git_object_owner(committish); - - data.repo = git_object_owner(committish); - - if ((error = normalize_options(&normalized, opts)) < 0) - return error; - - GIT_ERROR_CHECK_VERSION( - &normalized, - GIT_DESCRIBE_OPTIONS_VERSION, - "git_describe_options"); - data.opts = &normalized; - - if ((error = git_oidmap_new(&data.names)) < 0) - return error; - - /** TODO: contains to be implemented */ - - if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - - if ((error = git_reference_foreach_name( - git_object_owner(committish), - get_name, &data)) < 0) - goto cleanup; - - if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) { - git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " - "no reference found, cannot describe anything."); - error = -1; - goto cleanup; - } - - if ((error = describe(&data, commit)) < 0) - goto cleanup; - -cleanup: - git_commit_free(commit); - - git_oidmap_foreach_value(data.names, name, { - git_tag_free(name->tag); - git__free(name->path); - git__free(name); - }); - - git_oidmap_free(data.names); - - if (error < 0) - git_describe_result_free(data.result); - else - *result = data.result; - - return error; -} - -int git_describe_workdir( - git_describe_result **out, - git_repository *repo, - git_describe_options *opts) -{ - int error; - git_oid current_id; - git_status_list *status = NULL; - git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; - git_describe_result *result = NULL; - git_object *commit; - - if ((error = git_reference_name_to_id(¤t_id, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_object_lookup(&commit, repo, ¤t_id, GIT_OBJECT_COMMIT)) < 0) - return error; - - /* The first step is to perform a describe of HEAD, so we can leverage this */ - if ((error = git_describe_commit(&result, commit, opts)) < 0) - goto out; - - if ((error = git_status_list_new(&status, repo, &status_opts)) < 0) - goto out; - - - if (git_status_list_entrycount(status) > 0) - result->dirty = 1; - -out: - git_object_free(commit); - git_status_list_free(status); - - if (error < 0) - git_describe_result_free(result); - else - *out = result; - - return error; -} - -static int normalize_format_options( - git_describe_format_options *dst, - const git_describe_format_options *src) -{ - if (!src) { - git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); - return 0; - } - - memcpy(dst, src, sizeof(git_describe_format_options)); - return 0; -} - -static int git_describe__format( - git_str *out, - const git_describe_result *result, - const git_describe_format_options *given) -{ - int error; - git_repository *repo; - struct commit_name *name; - git_describe_format_options opts; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(result); - - GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options"); - normalize_format_options(&opts, given); - - if (opts.always_use_long_format && opts.abbreviated_size == 0) { - git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " - "'always_use_long_format' is incompatible with a zero" - "'abbreviated_size'"); - return -1; - } - - - repo = result->repo; - - /* If we did find an exact match, then it's the easier method */ - if (result->exact_match) { - name = result->name; - if ((error = display_name(out, repo, name)) < 0) - return error; - - if (opts.always_use_long_format) { - const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id; - if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0) - return error; - } - - if (result->dirty && opts.dirty_suffix) - git_str_puts(out, opts.dirty_suffix); - - return git_str_oom(out) ? -1 : 0; - } - - /* If we didn't find *any* tags, we fall back to the commit's id */ - if (result->fallback_to_id) { - char hex_oid[GIT_OID_HEXSZ + 1] = {0}; - int size = 0; - - if ((error = find_unique_abbrev_size( - &size, repo, &result->commit_id, opts.abbreviated_size)) < 0) - return -1; - - git_oid_fmt(hex_oid, &result->commit_id); - git_str_put(out, hex_oid, size); - - if (result->dirty && opts.dirty_suffix) - git_str_puts(out, opts.dirty_suffix); - - return git_str_oom(out) ? -1 : 0; - } - - /* Lastly, if we found a matching tag, we show that */ - name = result->tag->name; - - if ((error = display_name(out, repo, name)) < 0) - return error; - - if (opts.abbreviated_size) { - if ((error = show_suffix(out, result->tag->depth, repo, - &result->commit_id, opts.abbreviated_size)) < 0) - return error; - } - - if (result->dirty && opts.dirty_suffix) { - git_str_puts(out, opts.dirty_suffix); - } - - return git_str_oom(out) ? -1 : 0; -} - -int git_describe_format( - git_buf *out, - const git_describe_result *result, - const git_describe_format_options *given) -{ - GIT_BUF_WRAP_PRIVATE(out, git_describe__format, result, given); -} - -void git_describe_result_free(git_describe_result *result) -{ - if (result == NULL) - return; - - if (result->name) { - git_tag_free(result->name->tag); - git__free(result->name->path); - git__free(result->name); - } - - if (result->tag) { - git_tag_free(result->tag->name->tag); - git__free(result->tag->name->path); - git__free(result->tag->name); - git__free(result->tag); - } - - git__free(result); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_describe_init_options(git_describe_options *opts, unsigned int version) -{ - return git_describe_options_init(opts, version); -} -#endif - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) -{ - return git_describe_format_options_init(opts, version); -} -#endif diff --git a/src/diff.c b/src/diff.c deleted file mode 100644 index 9840d6050..000000000 --- a/src/diff.c +++ /dev/null @@ -1,389 +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 "diff.h" - -#include "common.h" -#include "buf.h" -#include "patch.h" -#include "email.h" -#include "commit.h" -#include "index.h" -#include "diff_generate.h" - -#include "git2/version.h" -#include "git2/email.h" - -struct patch_id_args { - git_hash_ctx ctx; - git_oid result; - int first_file; -}; - -GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) -{ - const char *str = delta->old_file.path; - - if (!str || - delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_RENAMED || - delta->status == GIT_DELTA_COPIED) - str = delta->new_file.path; - - return str; -} - -int git_diff_delta__cmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcmp(diff_delta__path(da), diff_delta__path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -int git_diff_delta__casecmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcasecmp(diff_delta__path(da), diff_delta__path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -int git_diff__entry_cmp(const void *a, const void *b) -{ - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - return strcmp(entry_a->path, entry_b->path); -} - -int git_diff__entry_icmp(const void *a, const void *b) -{ - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - return strcasecmp(entry_a->path, entry_b->path); -} - -void git_diff_free(git_diff *diff) -{ - if (!diff) - return; - - GIT_REFCOUNT_DEC(diff, diff->free_fn); -} - -void git_diff_addref(git_diff *diff) -{ - GIT_REFCOUNT_INC(diff); -} - -size_t git_diff_num_deltas(const git_diff *diff) -{ - GIT_ASSERT_ARG(diff); - return diff->deltas.length; -} - -size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type) -{ - size_t i, count = 0; - const git_diff_delta *delta; - - GIT_ASSERT_ARG(diff); - - git_vector_foreach(&diff->deltas, i, delta) { - count += (delta->status == type); - } - - return count; -} - -const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL); - return git_vector_get(&diff->deltas, idx); -} - -int git_diff_is_sorted_icase(const git_diff *diff) -{ - return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; -} - -int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff) -{ - GIT_ASSERT_ARG(out); - GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); - out->stat_calls = diff->perf.stat_calls; - out->oid_calculations = diff->perf.oid_calculations; - return 0; -} - -int git_diff_foreach( - git_diff *diff, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - int error = 0; - git_diff_delta *delta; - size_t idx; - - GIT_ASSERT_ARG(diff); - - git_vector_foreach(&diff->deltas, idx, delta) { - git_patch *patch; - - /* check flags against patch status */ - if (git_diff_delta__should_skip(&diff->opts, delta)) - continue; - - if ((error = git_patch_from_diff(&patch, diff, idx)) != 0) - break; - - error = git_patch__invoke_callbacks(patch, file_cb, binary_cb, - hunk_cb, data_cb, payload); - git_patch_free(patch); - - if (error) - break; - } - - return error; -} - -#ifndef GIT_DEPRECATE_HARD - -int git_diff_format_email( - git_buf *out, - git_diff *diff, - const git_diff_format_email_options *opts) -{ - git_email_create_options email_create_opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - git_str email = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author); - - GIT_ERROR_CHECK_VERSION(opts, - GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, - "git_format_email_options"); - - /* This is a `git_buf` special case; subsequent calls append. */ - email.ptr = out->ptr; - email.asize = out->reserved; - email.size = out->size; - - out->ptr = git_str__initstr; - out->reserved = 0; - out->size = 0; - - if ((opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) - email_create_opts.subject_prefix = ""; - - error = git_email__append_from_diff(&email, diff, opts->patch_no, - opts->total_patches, opts->id, opts->summary, opts->body, - opts->author, &email_create_opts); - - if (error < 0) - goto done; - - error = git_buf_fromstr(out, &email); - -done: - git_str_dispose(&email); - return error; -} - -int git_diff_commit_as_email( - git_buf *out, - git_repository *repo, - git_commit *commit, - size_t patch_no, - size_t total_patches, - uint32_t flags, - const git_diff_options *diff_opts) -{ - git_diff *diff = NULL; - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - const git_oid *commit_id; - const char *summary, *body; - const git_signature *author; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(commit); - - commit_id = git_commit_id(commit); - summary = git_commit_summary(commit); - body = git_commit_body(commit); - author = git_commit_author(commit); - - if ((flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) - opts.subject_prefix = ""; - - if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) - return error; - - error = git_email_create_from_diff(out, diff, patch_no, total_patches, commit_id, summary, body, author, &opts); - - git_diff_free(diff); - return error; -} - -int git_diff_init_options(git_diff_options *opts, unsigned int version) -{ - return git_diff_options_init(opts, version); -} - -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( - opts, version, git_diff_format_email_options, - GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT); - 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); -} - -#endif - -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_options_init( - git_diff_find_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT); - return 0; -} - -static int flush_hunk(git_oid *result, git_hash_ctx *ctx) -{ - git_oid hash; - unsigned short carry = 0; - int error, i; - - if ((error = git_hash_final(hash.id, ctx)) < 0 || - (error = git_hash_init(ctx)) < 0) - return error; - - for (i = 0; i < GIT_OID_RAWSZ; i++) { - carry += result->id[i] + hash.id[i]; - result->id[i] = (unsigned char)carry; - carry >>= 8; - } - - return 0; -} - -static void strip_spaces(git_str *buf) -{ - char *src = buf->ptr, *dst = buf->ptr; - char c; - size_t len = 0; - - while ((c = *src++) != '\0') { - if (!git__isspace(c)) { - *dst++ = c; - len++; - } - } - - git_str_truncate(buf, len); -} - -static int diff_patchid_print_callback_to_buf( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - struct patch_id_args *args = (struct patch_id_args *) payload; - git_str buf = GIT_STR_INIT; - int error = 0; - - if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL || - line->origin == GIT_DIFF_LINE_ADD_EOFNL || - line->origin == GIT_DIFF_LINE_DEL_EOFNL) - goto out; - - if ((error = git_diff_print_callback__to_buf(delta, hunk, - line, &buf)) < 0) - goto out; - - 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_str_dispose(&buf); - return error; -} - -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); - return 0; -} - -int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts) -{ - struct patch_id_args args; - int error; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options"); - - memset(&args, 0, sizeof(args)); - args.first_file = 1; - if ((error = git_hash_ctx_init(&args.ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) - goto out; - - if ((error = git_diff_print(diff, - GIT_DIFF_FORMAT_PATCH_ID, - diff_patchid_print_callback_to_buf, - &args)) < 0) - goto out; - - if ((error = (flush_hunk(&args.result, &args.ctx))) < 0) - goto out; - - git_oid_cpy(out, &args.result); - -out: - git_hash_ctx_cleanup(&args.ctx); - return error; -} diff --git a/src/diff.h b/src/diff.h deleted file mode 100644 index 2cc35e65b..000000000 --- a/src/diff.h +++ /dev/null @@ -1,67 +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_diff_h__ -#define INCLUDE_diff_h__ - -#include "common.h" - -#include "git2/diff.h" -#include "git2/patch.h" -#include "git2/sys/diff.h" -#include "git2/oid.h" - -#include "vector.h" -#include "iterator.h" -#include "repository.h" -#include "pool.h" -#include "odb.h" - -#define DIFF_OLD_PREFIX_DEFAULT "a/" -#define DIFF_NEW_PREFIX_DEFAULT "b/" - -typedef enum { - GIT_DIFF_TYPE_UNKNOWN = 0, - GIT_DIFF_TYPE_GENERATED = 1, - GIT_DIFF_TYPE_PARSED = 2 -} git_diff_origin_t; - -struct git_diff { - git_refcount rc; - git_repository *repo; - git_attr_session attrsession; - git_diff_origin_t type; - git_diff_options opts; - git_vector deltas; /* vector of git_diff_delta */ - git_pool pool; - git_iterator_t old_src; - git_iterator_t new_src; - git_diff_perfdata perf; - - int (*strcomp)(const char *, const char *); - int (*strncomp)(const char *, const char *, size_t); - int (*pfxcomp)(const char *str, const char *pfx); - int (*entrycomp)(const void *a, const void *b); - - int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx); - void (*free_fn)(git_diff *diff); -}; - -extern int git_diff_delta__format_file_header( - git_str *out, - const git_diff_delta *delta, - const char *oldpfx, - const char *newpfx, - 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); - -extern int git_diff__entry_cmp(const void *a, const void *b); -extern int git_diff__entry_icmp(const void *a, const void *b); - -#endif diff --git a/src/diff_driver.c b/src/diff_driver.c deleted file mode 100644 index 5f25fdb44..000000000 --- a/src/diff_driver.c +++ /dev/null @@ -1,522 +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 "diff_driver.h" - -#include "git2/attr.h" - -#include "common.h" -#include "diff.h" -#include "strmap.h" -#include "map.h" -#include "config.h" -#include "regexp.h" -#include "repository.h" - -typedef enum { - DIFF_DRIVER_AUTO = 0, - DIFF_DRIVER_BINARY = 1, - DIFF_DRIVER_TEXT = 2, - DIFF_DRIVER_PATTERNLIST = 3 -} git_diff_driver_t; - -typedef struct { - git_regexp re; - int flags; -} git_diff_driver_pattern; - -enum { - REG_NEGATE = (1 << 15) /* get out of the way of existing flags */ -}; - -/* data for finding function context for a given file type */ -struct git_diff_driver { - git_diff_driver_t type; - uint32_t binary_flags; - uint32_t other_flags; - git_array_t(git_diff_driver_pattern) fn_patterns; - git_regexp word_pattern; - char name[GIT_FLEX_ARRAY]; -}; - -#include "userdiff.h" - -struct git_diff_driver_registry { - git_strmap *drivers; -}; - -#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY) - -static git_diff_driver diff_driver_auto = { DIFF_DRIVER_AUTO, 0, 0 }; -static git_diff_driver diff_driver_binary = { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 }; -static git_diff_driver diff_driver_text = { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 }; - -git_diff_driver_registry *git_diff_driver_registry_new(void) -{ - git_diff_driver_registry *reg = - git__calloc(1, sizeof(git_diff_driver_registry)); - if (!reg) - return NULL; - - if (git_strmap_new(®->drivers) < 0) { - git_diff_driver_registry_free(reg); - return NULL; - } - - return reg; -} - -void git_diff_driver_registry_free(git_diff_driver_registry *reg) -{ - git_diff_driver *drv; - - if (!reg) - return; - - git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv)); - git_strmap_free(reg->drivers); - git__free(reg); -} - -static int diff_driver_add_patterns( - git_diff_driver *drv, const char *regex_str, int regex_flags) -{ - int error = 0; - const char *scan, *end; - git_diff_driver_pattern *pat = NULL; - git_str buf = GIT_STR_INIT; - - for (scan = regex_str; scan; scan = end) { - /* get pattern to fill in */ - if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) { - return -1; - } - - pat->flags = regex_flags; - if (*scan == '!') { - pat->flags |= REG_NEGATE; - ++scan; - } - - if ((end = strchr(scan, '\n')) != NULL) { - error = git_str_set(&buf, scan, end - scan); - end++; - } else { - error = git_str_sets(&buf, scan); - } - if (error < 0) - break; - - if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) { - /* - * TODO: issue a warning - */ - } - } - - if (error && pat != NULL) - (void)git_array_pop(drv->fn_patterns); /* release last item */ - git_str_dispose(&buf); - - /* We want to ignore bad patterns, so return success regardless */ - return 0; -} - -static int diff_driver_xfuncname(const git_config_entry *entry, void *payload) -{ - return diff_driver_add_patterns(payload, entry->value, 0); -} - -static int diff_driver_funcname(const git_config_entry *entry, void *payload) -{ - return diff_driver_add_patterns(payload, entry->value, 0); -} - -static git_diff_driver_registry *git_repository_driver_registry( - git_repository *repo) -{ - git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg; - if (reg) - return reg; - - newreg = git_diff_driver_registry_new(); - if (!newreg) { - git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry"); - return newreg; - } - reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg); - if (!reg) { - reg = newreg; - } else { - /* if we race, free losing allocation */ - git_diff_driver_registry_free(newreg); - } - return reg; -} - -static int diff_driver_alloc( - git_diff_driver **out, size_t *namelen_out, const char *name) -{ - git_diff_driver *driver; - size_t driverlen = sizeof(git_diff_driver), - namelen = strlen(name), - alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - driver = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(driver); - - memcpy(driver->name, name, namelen); - - *out = driver; - - if (namelen_out) - *namelen_out = namelen; - - return 0; -} - -static int git_diff_driver_builtin( - git_diff_driver **out, - git_diff_driver_registry *reg, - const char *driver_name) -{ - 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) { - if (!strcasecmp(driver_name, builtin_defs[idx].name)) { - ddef = &builtin_defs[idx]; - break; - } - } - if (!ddef) - goto done; - - if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0) - goto done; - - drv->type = DIFF_DRIVER_PATTERNLIST; - - if (ddef->fns && - (error = diff_driver_add_patterns( - drv, ddef->fns, ddef->flags)) < 0) - goto done; - - if (ddef->words && - (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0) - goto done; - - if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) - goto done; - -done: - if (error && drv) - git_diff_driver_free(drv); - else - *out = drv; - - return error; -} - -static int git_diff_driver_load( - git_diff_driver **out, git_repository *repo, const char *driver_name) -{ - int error = 0; - git_diff_driver_registry *reg; - git_diff_driver *drv; - size_t namelen; - git_config *cfg = NULL; - git_str name = GIT_STR_INIT; - git_config_entry *ce = NULL; - bool found_driver = false; - - if ((reg = git_repository_driver_registry(repo)) == NULL) - return -1; - - if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) { - *out = drv; - return 0; - } - - if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0) - goto done; - - drv->type = DIFF_DRIVER_AUTO; - - /* if you can't read config for repo, just use default driver */ - if (git_repository_config_snapshot(&cfg, repo) < 0) { - git_error_clear(); - goto done; - } - - if ((error = git_str_printf(&name, "diff.%s.binary", driver_name)) < 0) - goto done; - - switch (git_config__get_bool_force(cfg, name.ptr, -1)) { - case true: - /* if diff..binary is true, just return the binary driver */ - *out = &diff_driver_binary; - goto done; - case false: - /* if diff..binary is false, force binary checks off */ - /* but still may have custom function context patterns, etc. */ - drv->binary_flags = GIT_DIFF_FORCE_TEXT; - found_driver = true; - break; - default: - /* diff..binary unspecified or "auto", so just continue */ - break; - } - - /* TODO: warn if diff..command or diff..textconv are set */ - - git_str_truncate(&name, namelen + strlen("diff..")); - if ((error = git_str_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) - goto done; - git_error_clear(); /* no diff..xfuncname, so just continue */ - } - - git_str_truncate(&name, namelen + strlen("diff..")); - if ((error = git_str_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) - goto done; - git_error_clear(); /* no diff..funcname, so just continue */ - } - - /* if we found any patterns, set driver type to use correct callback */ - if (git_array_size(drv->fn_patterns) > 0) { - drv->type = DIFF_DRIVER_PATTERNLIST; - found_driver = true; - } - - git_str_truncate(&name, namelen + strlen("diff..")); - if ((error = git_str_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 = git_regexp_compile(&drv->word_pattern, ce->value, 0))) - found_driver = true; - else { - /* TODO: warn about bad regex instead of failure */ - goto done; - } - - /* TODO: look up diff..algorithm to turn on minimal / patience - * diff in drv->other_flags - */ - - /* if no driver config found at all, fall back on AUTO driver */ - if (!found_driver) - goto done; - - /* store driver in registry */ - if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) - goto done; - - *out = drv; - -done: - git_config_entry_free(ce); - git_str_dispose(&name); - git_config_free(cfg); - - if (!*out) { - int error2 = git_diff_driver_builtin(out, reg, driver_name); - if (!error) - error = error2; - } - - if (drv && drv != *out) - git_diff_driver_free(drv); - - return error; -} - -int git_diff_driver_lookup( - git_diff_driver **out, git_repository *repo, - git_attr_session *attrsession, const char *path) -{ - int error = 0; - const char *values[1], *attrs[] = { "diff" }; - - GIT_ASSERT_ARG(out); - *out = NULL; - - if (!repo || !path || !strlen(path)) - /* just use the auto value */; - else if ((error = git_attr_get_many_with_session(values, repo, - attrsession, 0, path, 1, attrs)) < 0) - /* return error below */; - - else if (GIT_ATTR_IS_UNSPECIFIED(values[0])) - /* just use the auto value */; - else if (GIT_ATTR_IS_FALSE(values[0])) - *out = &diff_driver_binary; - else if (GIT_ATTR_IS_TRUE(values[0])) - *out = &diff_driver_text; - - /* otherwise look for driver information in config and build driver */ - else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) { - if (error == GIT_ENOTFOUND) { - error = 0; - git_error_clear(); - } - } - - if (!*out) - *out = &diff_driver_auto; - - return error; -} - -void git_diff_driver_free(git_diff_driver *driver) -{ - git_diff_driver_pattern *pat; - - if (!driver) - return; - - while ((pat = git_array_pop(driver->fn_patterns)) != NULL) - git_regexp_dispose(&pat->re); - git_array_clear(driver->fn_patterns); - - git_regexp_dispose(&driver->word_pattern); - - git__free(driver); -} - -void git_diff_driver_update_options( - uint32_t *option_flags, git_diff_driver *driver) -{ - if ((*option_flags & FORCE_DIFFABLE) == 0) - *option_flags |= driver->binary_flags; - - *option_flags |= driver->other_flags; -} - -int git_diff_driver_content_is_binary( - git_diff_driver *driver, const char *content, size_t content_len) -{ - git_str search = GIT_STR_INIT; - - GIT_UNUSED(driver); - - git_str_attach_notowned(&search, content, - min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL)); - - /* TODO: provide encoding / binary detection callbacks that can - * be UTF-8 aware, etc. For now, instead of trying to be smart, - * let's just use the simple NUL-byte detection that core git uses. - */ - - /* previously was: if (git_str_is_binary(&search)) */ - if (git_str_contains_nul(&search)) - return 1; - - return 0; -} - -static int diff_context_line__simple( - git_diff_driver *driver, git_str *line) -{ - char firstch = line->ptr[0]; - GIT_UNUSED(driver); - return (git__isalpha(firstch) || firstch == '_' || firstch == '$'); -} - -static int diff_context_line__pattern_match( - git_diff_driver *driver, git_str *line) -{ - size_t i, maxi = git_array_size(driver->fn_patterns); - git_regmatch pmatch[2]; - - for (i = 0; i < maxi; ++i) { - git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i); - - 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].start >= 0) ? 1 : 0; - git_str_consume(line, git_str_cstr(line) + pmatch[i].start); - git_str_truncate(line, pmatch[i].end - pmatch[i].start); - git_str_rtrim(line); - - return true; - } - } - - return false; -} - -static long diff_context_find( - const char *line, - long line_len, - char *out, - long out_size, - void *payload) -{ - git_diff_find_context_payload *ctxt = payload; - - if (git_str_set(&ctxt->line, line, (size_t)line_len) < 0) - return -1; - git_str_rtrim(&ctxt->line); - - if (!ctxt->line.size) - return -1; - - if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line)) - return -1; - - if (out_size > (long)ctxt->line.size) - out_size = (long)ctxt->line.size; - memcpy(out, ctxt->line.ptr, (size_t)out_size); - - return out_size; -} - -void git_diff_find_context_init( - git_diff_find_context_fn *findfn_out, - git_diff_find_context_payload *payload_out, - git_diff_driver *driver) -{ - *findfn_out = driver ? diff_context_find : NULL; - - memset(payload_out, 0, sizeof(*payload_out)); - if (driver) { - payload_out->driver = driver; - payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ? - diff_context_line__pattern_match : diff_context_line__simple; - git_str_init(&payload_out->line, 0); - } -} - -void git_diff_find_context_clear(git_diff_find_context_payload *payload) -{ - if (payload) { - git_str_dispose(&payload->line); - payload->driver = NULL; - } -} diff --git a/src/diff_driver.h b/src/diff_driver.h deleted file mode 100644 index 03711e89e..000000000 --- a/src/diff_driver.h +++ /dev/null @@ -1,52 +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_diff_driver_h__ -#define INCLUDE_diff_driver_h__ - -#include "common.h" - -#include "attr_file.h" -#include "str.h" - -typedef struct git_diff_driver_registry git_diff_driver_registry; - -git_diff_driver_registry *git_diff_driver_registry_new(void); -void git_diff_driver_registry_free(git_diff_driver_registry *); - -typedef struct git_diff_driver git_diff_driver; - -int git_diff_driver_lookup(git_diff_driver **, git_repository *, - git_attr_session *attrsession, const char *); -void git_diff_driver_free(git_diff_driver *); - -/* diff option flags to force off and on for this driver */ -void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *); - -/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */ -int git_diff_driver_content_is_binary( - git_diff_driver *, const char *content, size_t content_len); - -typedef long (*git_diff_find_context_fn)( - const char *, long, char *, long, void *); - -typedef int (*git_diff_find_context_line)( - git_diff_driver *, git_str *); - -typedef struct { - git_diff_driver *driver; - git_diff_find_context_line match_line; - git_str line; -} git_diff_find_context_payload; - -void git_diff_find_context_init( - git_diff_find_context_fn *findfn_out, - git_diff_find_context_payload *payload_out, - git_diff_driver *driver); - -void git_diff_find_context_clear(git_diff_find_context_payload *); - -#endif diff --git a/src/diff_file.c b/src/diff_file.c deleted file mode 100644 index c7e9fbeee..000000000 --- a/src/diff_file.c +++ /dev/null @@ -1,481 +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 "diff_file.h" - -#include "git2/blob.h" -#include "git2/submodule.h" -#include "diff.h" -#include "diff_generate.h" -#include "odb.h" -#include "futils.h" -#include "filter.h" - -#define DIFF_MAX_FILESIZE 0x20000000 - -static bool diff_file_content_binary_by_size(git_diff_file_content *fc) -{ - /* if we have diff opts, check max_size vs file size */ - if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && - fc->opts_max_size > 0 && - fc->file->size > fc->opts_max_size) - fc->file->flags |= GIT_DIFF_FLAG_BINARY; - - return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0); -} - -static void diff_file_content_binary_by_content(git_diff_file_content *fc) -{ - if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) - return; - - switch (git_diff_driver_content_is_binary( - fc->driver, fc->map.data, fc->map.len)) { - case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break; - case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break; - default: break; - } -} - -static int diff_file_content_init_common( - git_diff_file_content *fc, const git_diff_options *opts) -{ - fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL; - - if (opts && opts->max_size >= 0) - fc->opts_max_size = opts->max_size ? - opts->max_size : DIFF_MAX_FILESIZE; - - if (fc->src == GIT_ITERATOR_EMPTY) - fc->src = GIT_ITERATOR_TREE; - - if (!fc->driver && - git_diff_driver_lookup(&fc->driver, fc->repo, - NULL, fc->file->path) < 0) - return -1; - - /* give driver a chance to modify options */ - git_diff_driver_update_options(&fc->opts_flags, fc->driver); - - /* make sure file is conceivable mmap-able */ - 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) { - fc->file->flags &= ~GIT_DIFF_FLAG_BINARY; - fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; - } - /* check if user is forcing binary diff the file */ - else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { - fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY; - fc->file->flags |= GIT_DIFF_FLAG_BINARY; - } - - diff_file_content_binary_by_size(fc); - - if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) { - fc->flags |= GIT_DIFF_FLAG__LOADED; - fc->map.len = 0; - fc->map.data = ""; - } - - if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) - diff_file_content_binary_by_content(fc); - - return 0; -} - -int git_diff_file_content__init_from_diff( - git_diff_file_content *fc, - git_diff *diff, - git_diff_delta *delta, - bool use_old) -{ - bool has_data = true; - - memset(fc, 0, sizeof(*fc)); - fc->repo = diff->repo; - fc->file = use_old ? &delta->old_file : &delta->new_file; - fc->src = use_old ? diff->old_src : diff->new_src; - - if (git_diff_driver_lookup(&fc->driver, fc->repo, - &diff->attrsession, fc->file->path) < 0) - return -1; - - switch (delta->status) { - case GIT_DELTA_ADDED: - has_data = !use_old; break; - case GIT_DELTA_DELETED: - has_data = use_old; break; - case GIT_DELTA_UNTRACKED: - has_data = !use_old && - (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; - break; - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_MODIFIED: - case GIT_DELTA_COPIED: - case GIT_DELTA_RENAMED: - break; - default: - has_data = false; - break; - } - - if (!has_data) - fc->flags |= GIT_DIFF_FLAG__NO_DATA; - - return diff_file_content_init_common(fc, &diff->opts); -} - -int git_diff_file_content__init_from_src( - git_diff_file_content *fc, - git_repository *repo, - const git_diff_options *opts, - const git_diff_file_content_src *src, - git_diff_file *as_file) -{ - memset(fc, 0, sizeof(*fc)); - fc->repo = repo; - fc->file = as_file; - - if (!src->blob && !src->buf) { - fc->flags |= GIT_DIFF_FLAG__NO_DATA; - } else { - fc->flags |= GIT_DIFF_FLAG__LOADED; - fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; - fc->file->mode = GIT_FILEMODE_BLOB; - - if (src->blob) { - git_blob_dup((git_blob **)&fc->blob, (git_blob *) src->blob); - fc->file->size = git_blob_rawsize(src->blob); - git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); - fc->file->id_abbrev = GIT_OID_HEXSZ; - - fc->map.len = (size_t)fc->file->size; - fc->map.data = (char *)git_blob_rawcontent(src->blob); - - fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; - } else { - int error; - if ((error = git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJECT_BLOB)) < 0) - return error; - fc->file->size = src->buflen; - fc->file->id_abbrev = GIT_OID_HEXSZ; - - fc->map.len = src->buflen; - fc->map.data = (char *)src->buf; - } - } - - return diff_file_content_init_common(fc, opts); -} - -static int diff_file_content_commit_to_str( - git_diff_file_content *fc, bool check_status) -{ - char oid[GIT_OID_HEXSZ+1]; - git_str content = GIT_STR_INIT; - const char *status = ""; - - if (check_status) { - int error = 0; - git_submodule *sm = NULL; - unsigned int sm_status = 0; - const git_oid *sm_head; - - if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) { - /* GIT_EEXISTS means a "submodule" that has not been git added */ - if (error == GIT_EEXISTS) { - git_error_clear(); - error = 0; - } - return error; - } - - if ((error = git_submodule_status(&sm_status, fc->repo, fc->file->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) { - git_submodule_free(sm); - return error; - } - - /* update OID if we didn't have it previously */ - if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 && - ((sm_head = git_submodule_wd_id(sm)) != NULL || - (sm_head = git_submodule_head_id(sm)) != NULL)) - { - git_oid_cpy(&fc->file->id, sm_head); - fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; - } - - if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) - status = "-dirty"; - - git_submodule_free(sm); - } - - git_oid_tostr(oid, sizeof(oid), &fc->file->id); - if (git_str_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) - return -1; - - fc->map.len = git_str_len(&content); - fc->map.data = git_str_detach(&content); - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - - return 0; -} - -static int diff_file_content_load_blob( - git_diff_file_content *fc, - git_diff_options *opts) -{ - int error = 0; - git_odb_object *odb_obj = NULL; - - if (git_oid_is_zero(&fc->file->id)) - return 0; - - if (fc->file->mode == GIT_FILEMODE_COMMIT) - return diff_file_content_commit_to_str(fc, false); - - /* if we don't know size, try to peek at object header first */ - if (!fc->file->size) { - if ((error = git_diff_file__resolve_zero_size( - fc->file, &odb_obj, fc->repo)) < 0) - return error; - } - - if ((opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && - diff_file_content_binary_by_size(fc)) - return 0; - - if (odb_obj != NULL) { - error = git_object__from_odb_object( - (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJECT_BLOB); - git_odb_object_free(odb_obj); - } else { - error = git_blob_lookup( - (git_blob **)&fc->blob, fc->repo, &fc->file->id); - } - - if (!error) { - fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; - fc->map.data = (void *)git_blob_rawcontent(fc->blob); - fc->map.len = (size_t)git_blob_rawsize(fc->blob); - } - - return error; -} - -static int diff_file_content_load_workdir_symlink_fake( - git_diff_file_content *fc, git_str *path) -{ - git_str target = GIT_STR_INIT; - int error; - - if ((error = git_futils_readbuffer(&target, path->ptr)) < 0) - return error; - - fc->map.len = git_str_len(&target); - fc->map.data = git_str_detach(&target); - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - - git_str_dispose(&target); - return error; -} - -static int diff_file_content_load_workdir_symlink( - git_diff_file_content *fc, git_str *path) -{ - ssize_t alloc_len, read_len; - int symlink_supported, error; - - if ((error = git_repository__configmap_lookup( - &symlink_supported, fc->repo, GIT_CONFIGMAP_SYMLINKS)) < 0) - return -1; - - if (!symlink_supported) - return diff_file_content_load_workdir_symlink_fake(fc, path); - - /* link path on disk could be UTF-16, so prepare a buffer that is - * big enough to handle some UTF-8 data expansion - */ - alloc_len = (ssize_t)(fc->file->size * 2) + 1; - - fc->map.data = git__calloc(alloc_len, sizeof(char)); - GIT_ERROR_CHECK_ALLOC(fc->map.data); - - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - - read_len = p_readlink(git_str_cstr(path), fc->map.data, alloc_len); - if (read_len < 0) { - git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", fc->file->path); - return -1; - } - - fc->map.len = read_len; - return 0; -} - -static int diff_file_content_load_workdir_file( - git_diff_file_content *fc, - git_str *path, - git_diff_options *diff_opts) -{ - int error = 0; - git_filter_list *fl = NULL; - git_file fd = git_futils_open_ro(git_str_cstr(path)); - git_str raw = GIT_STR_INIT; - git_object_size_t new_file_size = 0; - - if (fd < 0) - return fd; - - error = git_futils_filesize(&new_file_size, fd); - - if (error < 0) - goto cleanup; - - if (!(fc->file->flags & GIT_DIFF_FLAG_VALID_SIZE)) { - fc->file->size = new_file_size; - fc->file->flags |= GIT_DIFF_FLAG_VALID_SIZE; - } else if (fc->file->size != new_file_size) { - git_error_set(GIT_ERROR_FILESYSTEM, "file changed before we could read it"); - error = -1; - goto cleanup; - } - - if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && - diff_file_content_binary_by_size(fc)) - goto cleanup; - - if ((error = git_filter_list_load( - &fl, fc->repo, NULL, fc->file->path, - GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)) < 0) - goto cleanup; - - /* if there are no filters, try to mmap the file */ - if (fl == NULL) { - if (!(error = git_futils_mmap_ro( - &fc->map, fd, 0, (size_t)fc->file->size))) { - fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; - goto cleanup; - } - - /* if mmap failed, fall through to try readbuffer below */ - git_error_clear(); - } - - if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { - git_str out = GIT_STR_INIT; - - error = git_filter_list__convert_buf(&out, fl, &raw); - - if (!error) { - fc->map.len = out.size; - fc->map.data = out.ptr; - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - } - } - -cleanup: - git_filter_list_free(fl); - p_close(fd); - - return error; -} - -static int diff_file_content_load_workdir( - git_diff_file_content *fc, - git_diff_options *diff_opts) -{ - int error = 0; - git_str path = GIT_STR_INIT; - - if (fc->file->mode == GIT_FILEMODE_COMMIT) - return diff_file_content_commit_to_str(fc, true); - - if (fc->file->mode == GIT_FILEMODE_TREE) - return 0; - - if (git_repository_workdir_path(&path, fc->repo, fc->file->path) < 0) - return -1; - - if (S_ISLNK(fc->file->mode)) - error = diff_file_content_load_workdir_symlink(fc, &path); - else - error = diff_file_content_load_workdir_file(fc, &path, diff_opts); - - /* once data is loaded, update OID if we didn't have it previously */ - if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) { - error = git_odb_hash( - &fc->file->id, fc->map.data, fc->map.len, GIT_OBJECT_BLOB); - fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; - } - - git_str_dispose(&path); - return error; -} - -int git_diff_file_content__load( - git_diff_file_content *fc, - git_diff_options *diff_opts) -{ - int error = 0; - - if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) - return 0; - - if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0 && - (diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0) - return 0; - - 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); - if (error) - return error; - - fc->flags |= GIT_DIFF_FLAG__LOADED; - - diff_file_content_binary_by_content(fc); - - return 0; -} - -void git_diff_file_content__unload(git_diff_file_content *fc) -{ - if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) - return; - - if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { - git__free(fc->map.data); - fc->map.data = ""; - fc->map.len = 0; - fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; - } - else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { - git_futils_mmap_free(&fc->map); - fc->map.data = ""; - fc->map.len = 0; - fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; - } - - if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { - git_blob_free((git_blob *)fc->blob); - fc->blob = NULL; - fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; - } - - fc->flags &= ~GIT_DIFF_FLAG__LOADED; -} - -void git_diff_file_content__clear(git_diff_file_content *fc) -{ - git_diff_file_content__unload(fc); - - /* for now, nothing else to do */ -} diff --git a/src/diff_file.h b/src/diff_file.h deleted file mode 100644 index 8d743e821..000000000 --- a/src/diff_file.h +++ /dev/null @@ -1,63 +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_diff_file_h__ -#define INCLUDE_diff_file_h__ - -#include "common.h" - -#include "diff.h" -#include "diff_driver.h" -#include "map.h" - -/* expanded information for one side of a delta */ -typedef struct { - git_repository *repo; - git_diff_file *file; - git_diff_driver *driver; - uint32_t flags; - uint32_t opts_flags; - git_object_size_t opts_max_size; - git_iterator_t src; - const git_blob *blob; - git_map map; -} git_diff_file_content; - -extern int git_diff_file_content__init_from_diff( - git_diff_file_content *fc, - git_diff *diff, - git_diff_delta *delta, - bool use_old); - -typedef struct { - const git_blob *blob; - const void *buf; - size_t buflen; - const char *as_path; -} git_diff_file_content_src; - -#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) } -#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) } - -extern int git_diff_file_content__init_from_src( - git_diff_file_content *fc, - git_repository *repo, - const git_diff_options *opts, - const git_diff_file_content_src *src, - git_diff_file *as_file); - -/* this loads the blob/file-on-disk as needed */ -extern int git_diff_file_content__load( - git_diff_file_content *fc, - git_diff_options *diff_opts); - -/* this releases the blob/file-in-memory */ -extern void git_diff_file_content__unload(git_diff_file_content *fc); - -/* this unloads and also releases any other resources */ -extern void git_diff_file_content__clear(git_diff_file_content *fc); - -#endif diff --git a/src/diff_generate.c b/src/diff_generate.c deleted file mode 100644 index cfaefba66..000000000 --- a/src/diff_generate.c +++ /dev/null @@ -1,1724 +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 "diff_generate.h" - -#include "diff.h" -#include "patch_generate.h" -#include "futils.h" -#include "config.h" -#include "attr_file.h" -#include "filter.h" -#include "pathspec.h" -#include "index.h" -#include "odb.h" -#include "submodule.h" - -#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ - (((DIFF)->base.opts.flags & (FLAG)) != 0) -#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ - (((DIFF)->base.opts.flags & (FLAG)) == 0) -#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ - (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ - ((DIFF)->base.opts.flags & ~(FLAG)) - -typedef struct { - struct git_diff base; - - git_vector pathspec; - - uint32_t diffcaps; - bool index_updated; -} git_diff_generated; - -static git_diff_delta *diff_delta__alloc( - git_diff_generated *diff, - git_delta_t status, - const char *path) -{ - git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); - if (!delta) - return NULL; - - delta->old_file.path = git_pool_strdup(&diff->base.pool, path); - if (delta->old_file.path == NULL) { - git__free(delta); - return NULL; - } - - delta->new_file.path = delta->old_file.path; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - switch (status) { - case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; - case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; - default: break; /* leave other status values alone */ - } - } - delta->status = status; - - return delta; -} - -static int diff_insert_delta( - git_diff_generated *diff, - git_diff_delta *delta, - const char *matched_pathspec) -{ - int error = 0; - - if (diff->base.opts.notify_cb) { - error = diff->base.opts.notify_cb( - &diff->base, delta, matched_pathspec, diff->base.opts.payload); - - if (error) { - git__free(delta); - - if (error > 0) /* positive value means to skip this delta */ - return 0; - else /* negative value means to cancel diff */ - return git_error_set_after_callback_function(error, "git_diff"); - } - } - - if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0) - git__free(delta); - - return error; -} - -static bool diff_pathspec_match( - const char **matched_pathspec, - git_diff_generated *diff, - const git_index_entry *entry) -{ - bool disable_pathspec_match = - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); - - /* If we're disabling fnmatch, then the iterator has already applied - * the filters to the files for us and we don't have to do anything. - * However, this only applies to *files* - the iterator will include - * directories that we need to recurse into when not autoexpanding, - * so we still need to apply the pathspec match to directories. - */ - if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && - disable_pathspec_match) { - *matched_pathspec = entry->path; - return true; - } - - return git_pathspec__match( - &diff->pathspec, entry->path, disable_pathspec_match, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - matched_pathspec, NULL); -} - -static void diff_delta__flag_known_size(git_diff_file *file) -{ - /* - * If we don't know the ID, that can only come from the workdir - * iterator, which means we *do* know the file size. This is a - * leaky abstraction, but alas. Otherwise, we test against the - * empty blob id. - */ - if (file->size || - !(file->flags & GIT_DIFF_FLAG_VALID_ID) || - git_oid_equal(&file->id, &git_oid__empty_blob_sha1)) - file->flags |= GIT_DIFF_FLAG_VALID_SIZE; -} - -static void diff_delta__flag_known_sizes(git_diff_delta *delta) -{ - diff_delta__flag_known_size(&delta->old_file); - diff_delta__flag_known_size(&delta->new_file); -} - -static int diff_delta__from_one( - git_diff_generated *diff, - git_delta_t status, - const git_index_entry *oitem, - const git_index_entry *nitem) -{ - const git_index_entry *entry = nitem; - bool has_old = false; - git_diff_delta *delta; - const char *matched_pathspec; - - GIT_ASSERT_ARG((oitem != NULL) ^ (nitem != NULL)); - - if (oitem) { - entry = oitem; - has_old = true; - } - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) - has_old = !has_old; - - if ((entry->flags & GIT_INDEX_ENTRY_VALID) != 0) - return 0; - - if (status == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) - return 0; - - if (status == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) - return 0; - - if (status == GIT_DELTA_UNREADABLE && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) - return 0; - - if (!diff_pathspec_match(&matched_pathspec, diff, entry)) - return 0; - - delta = diff_delta__alloc(diff, status, entry->path); - GIT_ERROR_CHECK_ALLOC(delta); - - /* This fn is just for single-sided diffs */ - GIT_ASSERT(status != GIT_DELTA_MODIFIED); - delta->nfiles = 1; - - if (has_old) { - delta->old_file.mode = entry->mode; - delta->old_file.size = entry->file_size; - delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; - git_oid_cpy(&delta->old_file.id, &entry->id); - delta->old_file.id_abbrev = GIT_OID_HEXSZ; - } else /* ADDED, IGNORED, UNTRACKED */ { - delta->new_file.mode = entry->mode; - delta->new_file.size = entry->file_size; - delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - git_oid_cpy(&delta->new_file.id, &entry->id); - delta->new_file.id_abbrev = GIT_OID_HEXSZ; - } - - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - if (has_old || !git_oid_is_zero(&delta->new_file.id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - diff_delta__flag_known_sizes(delta); - - return diff_insert_delta(diff, delta, matched_pathspec); -} - -static int diff_delta__from_two( - git_diff_generated *diff, - git_delta_t status, - const git_index_entry *old_entry, - uint32_t old_mode, - const git_index_entry *new_entry, - uint32_t new_mode, - const git_oid *new_id, - const char *matched_pathspec) -{ - const git_oid *old_id = &old_entry->id; - git_diff_delta *delta; - const char *canonical_path = old_entry->path; - - if (status == GIT_DELTA_UNMODIFIED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) - return 0; - - if (!new_id) - new_id = &new_entry->id; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - uint32_t temp_mode = old_mode; - const git_index_entry *temp_entry = old_entry; - const git_oid *temp_id = old_id; - - old_entry = new_entry; - new_entry = temp_entry; - old_mode = new_mode; - new_mode = temp_mode; - old_id = new_id; - new_id = temp_id; - } - - delta = diff_delta__alloc(diff, status, canonical_path); - GIT_ERROR_CHECK_ALLOC(delta); - delta->nfiles = 2; - - if (!git_index_entry_is_conflict(old_entry)) { - delta->old_file.size = old_entry->file_size; - delta->old_file.mode = old_mode; - git_oid_cpy(&delta->old_file.id, old_id); - delta->old_file.id_abbrev = GIT_OID_HEXSZ; - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | - GIT_DIFF_FLAG_EXISTS; - } - - if (!git_index_entry_is_conflict(new_entry)) { - git_oid_cpy(&delta->new_file.id, new_id); - delta->new_file.id_abbrev = GIT_OID_HEXSZ; - delta->new_file.size = new_entry->file_size; - delta->new_file.mode = new_mode; - delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; - delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - - if (!git_oid_is_zero(&new_entry->id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - } - - diff_delta__flag_known_sizes(delta); - - return diff_insert_delta(diff, delta, matched_pathspec); -} - -static git_diff_delta *diff_delta__last_for_item( - git_diff_generated *diff, - const git_index_entry *item) -{ - git_diff_delta *delta = git_vector_last(&diff->base.deltas); - if (!delta) - return NULL; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_DELETED: - if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_ADDED: - if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_UNTRACKED: - if (diff->base.strcomp(delta->new_file.path, item->path) == 0 && - git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_MODIFIED: - if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || - (delta->new_file.mode == item->mode && - git_oid__cmp(&delta->new_file.id, &item->id) == 0)) - return delta; - break; - default: - break; - } - - return NULL; -} - -static char *diff_strdup_prefix(git_pool *pool, const char *prefix) -{ - size_t len = strlen(prefix); - - /* append '/' at end if needed */ - if (len > 0 && prefix[len - 1] != '/') - return git_pool_strcat(pool, prefix, "/"); - else - return git_pool_strndup(pool, prefix, len + 1); -} - -GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) -{ - return delta->old_file.path ? - delta->old_file.path : delta->new_file.path; -} - -static int diff_delta_i2w_cmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -static int diff_delta_i2w_casecmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta) -{ - uint32_t flags = opts ? opts->flags : 0; - - if (delta->status == GIT_DELTA_UNMODIFIED && - (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - return true; - - if (delta->status == GIT_DELTA_IGNORED && - (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNTRACKED && - (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNREADABLE && - (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) - return true; - - return false; -} - - -static const char *diff_mnemonic_prefix( - git_iterator_t type, bool left_side) -{ - const char *pfx = ""; - - switch (type) { - 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; - } - - /* note: without a deeper look at pathspecs, there is no easy way - * to get the (o)bject / (w)ork tree mnemonics working... - */ - - return pfx; -} - -static void diff_set_ignore_case(git_diff *diff, bool ignore_case) -{ - if (!ignore_case) { - diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; - - diff->strcomp = git__strcmp; - diff->strncomp = git__strncmp; - diff->pfxcomp = git__prefixcmp; - diff->entrycomp = git_diff__entry_cmp; - - git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); - } else { - diff->opts.flags |= GIT_DIFF_IGNORE_CASE; - - diff->strcomp = git__strcasecmp; - diff->strncomp = git__strncasecmp; - diff->pfxcomp = git__prefixcmp_icase; - diff->entrycomp = git_diff__entry_icmp; - - git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); - } - - git_vector_sort(&diff->deltas); -} - -static void diff_generated_free(git_diff *d) -{ - git_diff_generated *diff = (git_diff_generated *)d; - - git_attr_session__free(&diff->base.attrsession); - git_vector_free_deep(&diff->base.deltas); - - git_pathspec__vfree(&diff->pathspec); - git_pool_clear(&diff->base.pool); - - git__memzero(diff, sizeof(*diff)); - git__free(diff); -} - -static git_diff_generated *diff_generated_alloc( - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter) -{ - git_diff_generated *diff; - git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; - - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(old_iter, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(new_iter, NULL); - - if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) - return NULL; - - GIT_REFCOUNT_INC(&diff->base); - diff->base.type = GIT_DIFF_TYPE_GENERATED; - diff->base.repo = repo; - diff->base.old_src = old_iter->type; - diff->base.new_src = new_iter->type; - diff->base.patch_fn = git_patch_generated_from_diff; - diff->base.free_fn = diff_generated_free; - git_attr_session__init(&diff->base.attrsession, repo); - memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); - - if (git_pool_init(&diff->base.pool, 1) < 0 || - git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { - git_diff_free(&diff->base); - return NULL; - } - - /* Use case-insensitive compare if either iterator has - * the ignore_case bit set */ - diff_set_ignore_case( - &diff->base, - git_iterator_ignore_case(old_iter) || - git_iterator_ignore_case(new_iter)); - - return diff; -} - -static int diff_generated_apply_options( - git_diff_generated *diff, - const git_diff_options *opts) -{ - git_config *cfg = NULL; - git_repository *repo = diff->base.repo; - git_pool *pool = &diff->base.pool; - int val; - - if (opts) { - /* copy user options (except case sensitivity info from iterators) */ - bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); - memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); - DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); - - /* initialize pathspec from options */ - if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) - return -1; - } - - /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) - diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; - - /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) - diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - /* load config values that affect diff behavior */ - if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) - return val; - - if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) - diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; - - 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__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) - diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; - - 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 */ - - /* If not given explicit `opts`, check `diff.xyz` configs */ - if (!opts) { - int context = git_config__get_int_force(cfg, "diff.context", 3); - diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; - - /* add other defaults here */ - } - - /* Reverse src info if diff is reversed */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - 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_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 */ - if (diff->base.opts.ignore_submodules <= 0) { - git_config_entry *entry; - git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); - - if (entry && git_submodule_parse_ignore( - &diff->base.opts.ignore_submodules, entry->value) < 0) - git_error_clear(); - git_config_entry_free(entry); - } - - /* if either prefix is not set, figure out appropriate value */ - if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { - const char *use_old = DIFF_OLD_PREFIX_DEFAULT; - const char *use_new = DIFF_NEW_PREFIX_DEFAULT; - - if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) - use_old = use_new = ""; - else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { - use_old = diff_mnemonic_prefix(diff->base.old_src, true); - use_new = diff_mnemonic_prefix(diff->base.new_src, false); - } - - if (!diff->base.opts.old_prefix) - diff->base.opts.old_prefix = use_old; - if (!diff->base.opts.new_prefix) - diff->base.opts.new_prefix = use_new; - } - - /* strdup prefix from pool so we're not dependent on external data */ - diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); - diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - const char *tmp_prefix = diff->base.opts.old_prefix; - diff->base.opts.old_prefix = diff->base.opts.new_prefix; - diff->base.opts.new_prefix = tmp_prefix; - } - - git_config_free(cfg); - - /* check strdup results for error */ - return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; -} - -int git_diff__oid_for_file( - git_oid *out, - git_diff *diff, - const char *path, - uint16_t mode, - git_object_size_t size) -{ - git_index_entry entry; - - if (size > UINT32_MAX) { - git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); - return -1; - } - - memset(&entry, 0, sizeof(entry)); - entry.mode = mode; - entry.file_size = (uint32_t)size; - entry.path = (char *)path; - - return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); -} - -int git_diff__oid_for_entry( - git_oid *out, - git_diff *d, - const git_index_entry *src, - uint16_t mode, - const git_oid *update_match) -{ - git_diff_generated *diff; - git_str full_path = GIT_STR_INIT; - git_index_entry entry = *src; - git_filter_list *fl = NULL; - int error = 0; - - GIT_ASSERT(d->type == GIT_DIFF_TYPE_GENERATED); - diff = (git_diff_generated *)d; - - memset(out, 0, sizeof(*out)); - - if (git_repository_workdir_path(&full_path, diff->base.repo, entry.path) < 0) - return -1; - - if (!mode) { - struct stat st; - - diff->base.perf.stat_calls++; - - if (p_stat(full_path.ptr, &st) < 0) { - error = git_fs_path_set_error(errno, entry.path, "stat"); - git_str_dispose(&full_path); - return error; - } - - git_index_entry__init_from_stat(&entry, - &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); - } - - /* calculate OID for file if possible */ - if (S_ISGITLINK(mode)) { - git_submodule *sm; - - if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { - const git_oid *sm_oid = git_submodule_wd_id(sm); - if (sm_oid) - git_oid_cpy(out, sm_oid); - git_submodule_free(sm); - } else { - /* if submodule lookup failed probably just in an intermediate - * state where some init hasn't happened, so ignore the error - */ - git_error_clear(); - } - } else if (S_ISLNK(mode)) { - error = git_odb__hashlink(out, full_path.ptr); - diff->base.perf.oid_calculations++; - } else if (!git__is_sizet(entry.file_size)) { - git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", - entry.path); - error = -1; - } else if (!(error = git_filter_list_load(&fl, - diff->base.repo, NULL, entry.path, - GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) - { - int fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) - error = fd; - else { - error = git_odb__hashfd_filtered( - out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl); - p_close(fd); - diff->base.perf.oid_calculations++; - } - - git_filter_list_free(fl); - } - - /* update index for entry if requested */ - if (!error && update_match && git_oid_equal(out, update_match)) { - git_index *idx; - git_index_entry updated_entry; - - memcpy(&updated_entry, &entry, sizeof(git_index_entry)); - updated_entry.mode = mode; - git_oid_cpy(&updated_entry.id, out); - - if (!(error = git_repository_index__weakptr(&idx, - diff->base.repo))) { - error = git_index_add(idx, &updated_entry); - diff->index_updated = true; - } - } - - git_str_dispose(&full_path); - return error; -} - -typedef struct { - git_repository *repo; - git_iterator *old_iter; - git_iterator *new_iter; - const git_index_entry *oitem; - const git_index_entry *nitem; - git_strmap *submodule_cache; - bool submodule_cache_initialized; -} diff_in_progress; - -#define MODE_BITS_MASK 0000777 - -static int maybe_modified_submodule( - git_delta_t *status, - git_oid *found_oid, - git_diff_generated *diff, - diff_in_progress *info) -{ - int error = 0; - git_submodule *sub; - unsigned int sm_status = 0; - git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; - git_strmap *submodule_cache = NULL; - - *status = GIT_DELTA_UNMODIFIED; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || - ign == GIT_SUBMODULE_IGNORE_ALL) - return 0; - - if (diff->base.repo->submodule_cache != NULL) { - submodule_cache = diff->base.repo->submodule_cache; - } else { - if (!info->submodule_cache_initialized) { - info->submodule_cache_initialized = true; - /* - * Try to cache the submodule information to avoid having to parse it for - * every submodule. It is okay if it fails, the cache will still be NULL - * and the submodules will be attempted to be looked up individually. - */ - git_submodule_cache_init(&info->submodule_cache, diff->base.repo); - } - submodule_cache = info->submodule_cache; - } - - if ((error = git_submodule__lookup_with_cache( - &sub, diff->base.repo, info->nitem->path, submodule_cache)) < 0) { - - /* GIT_EEXISTS means dir with .git in it was found - ignore it */ - if (error == GIT_EEXISTS) { - git_error_clear(); - error = 0; - } - return error; - } - - if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) - /* ignore it */; - else if ((error = git_submodule__status( - &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) - /* return error below */; - - /* check IS_WD_UNMODIFIED because this case is only used - * when the new side of the diff is the working directory - */ - else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) - *status = GIT_DELTA_MODIFIED; - - /* now that we have a HEAD OID, check if HEAD moved */ - else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && - !git_oid_equal(&info->oitem->id, found_oid)) - *status = GIT_DELTA_MODIFIED; - - git_submodule_free(sub); - return error; -} - -static int maybe_modified( - git_diff_generated *diff, - diff_in_progress *info) -{ - git_oid noid; - git_delta_t status = GIT_DELTA_MODIFIED; - const git_index_entry *oitem = info->oitem; - 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_WORKDIR); - bool modified_uncertain = false; - const char *matched_pathspec; - int error = 0; - - if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) - return 0; - - memset(&noid, 0, sizeof(noid)); - - /* on platforms with no symlinks, preserve mode of existing symlinks */ - if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && - !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) - nmode = omode; - - /* on platforms with no execmode, just preserve old mode */ - if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && - (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && - new_is_workdir) - nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); - - /* if one side is a conflict, mark the whole delta as conflicted */ - if (git_index_entry_is_conflict(oitem) || - git_index_entry_is_conflict(nitem)) { - status = GIT_DELTA_CONFLICTED; - - /* support "assume unchanged" (poorly, b/c we still stat everything) */ - } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) { - status = GIT_DELTA_UNMODIFIED; - - /* support "skip worktree" index bit */ - } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) { - status = GIT_DELTA_UNMODIFIED; - - /* if basic type of file changed, then split into delete and add */ - } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { - status = GIT_DELTA_TYPECHANGE; - } - - else if (nmode == GIT_FILEMODE_UNREADABLE) { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); - return error; - } - - else { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); - return error; - } - - /* 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_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_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); - - status = GIT_DELTA_UNMODIFIED; - - if (S_ISGITLINK(nmode)) { - if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) - return error; - } - - /* if the stat data looks different, then mark modified - this just - * means that the OID will be recalculated below to confirm change - */ - else if (omode != nmode || oitem->file_size != nitem->file_size) { - status = GIT_DELTA_MODIFIED; - modified_uncertain = - (oitem->file_size <= 0 && nitem->file_size > 0); - } - else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || - (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || - oitem->ino != nitem->ino || - oitem->uid != nitem->uid || - oitem->gid != nitem->gid || - git_index_entry_newer_than_index(nitem, index)) - { - status = GIT_DELTA_MODIFIED; - modified_uncertain = true; - } - - /* if mode is GITLINK and submodules are ignored, then skip */ - } else if (S_ISGITLINK(nmode) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { - status = GIT_DELTA_UNMODIFIED; - } - - /* 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_is_zero(&nitem->id)) { - const git_oid *update_check = - DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? - &oitem->id : NULL; - - if ((error = git_diff__oid_for_entry( - &noid, &diff->base, nitem, nmode, update_check)) < 0) - return error; - - /* if oid matches, then mark unmodified (except submodules, where - * the filesystem content may be modified even if the oid still - * matches between the index and the workdir HEAD) - */ - if (omode == nmode && !S_ISGITLINK(omode) && - git_oid_equal(&oitem->id, &noid)) - status = GIT_DELTA_UNMODIFIED; - } - - /* If we want case changes, then break this into a delete of the old - * and an add of the new so that consumers can act accordingly (eg, - * checkout will update the case on disk.) - */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && - strcmp(oitem->path, nitem->path) != 0) { - - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); - - return error; - } - - return diff_delta__from_two( - diff, status, oitem, omode, nitem, nmode, - git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); -} - -static bool entry_is_prefixed( - git_diff_generated *diff, - const git_index_entry *item, - const git_index_entry *prefix_item) -{ - size_t pathlen; - - if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) - return false; - - pathlen = strlen(prefix_item->path); - - return (prefix_item->path[pathlen - 1] == '/' || - item->path[pathlen] == '\0' || - item->path[pathlen] == '/'); -} - -static int iterator_current( - const git_index_entry **entry, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance( - const git_index_entry **entry, - git_iterator *iterator) -{ - const git_index_entry *prev_entry = *entry; - int cmp, error; - - /* if we're looking for conflicts, we only want to report - * one conflict for each file, instead of all three sides. - * so if this entry is a conflict for this file, and the - * previous one was a conflict for the same file, skip it. - */ - while ((error = git_iterator_advance(entry, iterator)) == 0) { - if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || - !git_index_entry_is_conflict(prev_entry) || - !git_index_entry_is_conflict(*entry)) - break; - - cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? - strcasecmp(prev_entry->path, (*entry)->path) : - strcmp(prev_entry->path, (*entry)->path); - - if (cmp) - break; - } - - if (error == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance_into( - const git_index_entry **entry, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance_over( - const git_index_entry **entry, - git_iterator_status_t *status, - git_iterator *iterator) -{ - int error = git_iterator_advance_over(entry, status, iterator); - - if (error == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int handle_unmatched_new_item( - git_diff_generated *diff, diff_in_progress *info) -{ - int error = 0; - const git_index_entry *nitem = info->nitem; - git_delta_t delta_type = GIT_DELTA_UNTRACKED; - bool contains_oitem; - - /* check if this is a prefix of the other side */ - contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); - - /* update delta_type if this item is conflicted */ - if (git_index_entry_is_conflict(nitem)) - delta_type = GIT_DELTA_CONFLICTED; - - /* update delta_type if this item is ignored */ - else if (git_iterator_current_is_ignored(info->new_iter)) - delta_type = GIT_DELTA_IGNORED; - - if (nitem->mode == GIT_FILEMODE_TREE) { - bool recurse_into_dir = contains_oitem; - - /* check if user requests recursion into this type of dir */ - recurse_into_dir = contains_oitem || - (delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || - (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); - - /* do not advance into directories that contain a .git file */ - if (recurse_into_dir && !contains_oitem) { - git_str *full = NULL; - if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) - return -1; - if (full && git_fs_path_contains(full, DOT_GIT)) { - /* TODO: warning if not a valid git repository */ - recurse_into_dir = false; - } - } - - /* still have to look into untracked directories to match core git - - * with no untracked files, directory is treated as ignored - */ - if (!recurse_into_dir && - delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) - { - git_diff_delta *last; - git_iterator_status_t untracked_state; - - /* attempt to insert record for this directory */ - if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) - return error; - - /* if delta wasn't created (because of rules), just skip ahead */ - last = diff_delta__last_for_item(diff, nitem); - if (!last) - return iterator_advance(&info->nitem, info->new_iter); - - /* iterate into dir looking for an actual untracked file */ - if ((error = iterator_advance_over( - &info->nitem, &untracked_state, info->new_iter)) < 0) - return error; - - /* if we found nothing that matched our pathlist filter, exclude */ - if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { - git_vector_pop(&diff->base.deltas); - git__free(last); - } - - /* if we found nothing or just ignored items, update the record */ - if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || - untracked_state == GIT_ITERATOR_STATUS_EMPTY) { - last->status = GIT_DELTA_IGNORED; - - /* remove the record if we don't want ignored records */ - if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { - git_vector_pop(&diff->base.deltas); - git__free(last); - } - } - - return 0; - } - - /* try to advance into directory if necessary */ - if (recurse_into_dir) { - error = iterator_advance_into(&info->nitem, info->new_iter); - - /* if directory is empty, can't advance into it, so skip it */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = iterator_advance(&info->nitem, info->new_iter); - } - - return error; - } - } - - else if (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && - git_iterator_current_tree_is_ignored(info->new_iter)) - /* 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_WORKDIR) { - if (delta_type != GIT_DELTA_CONFLICTED) - delta_type = GIT_DELTA_ADDED; - } - - else if (nitem->mode == GIT_FILEMODE_COMMIT) { - /* ignore things that are not actual submodules */ - if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { - git_error_clear(); - delta_type = GIT_DELTA_IGNORED; - - /* if this contains a tracked item, treat as normal TREE */ - if (contains_oitem) { - error = iterator_advance_into(&info->nitem, info->new_iter); - if (error != GIT_ENOTFOUND) - return error; - - git_error_clear(); - return iterator_advance(&info->nitem, info->new_iter); - } - } - } - - else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) - delta_type = GIT_DELTA_UNTRACKED; - else - delta_type = GIT_DELTA_UNREADABLE; - } - - /* Actually create the record for this item if necessary */ - if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) - return error; - - /* If user requested TYPECHANGE records, then check for that instead of - * just generating an ADDED/UNTRACKED record - */ - if (delta_type != GIT_DELTA_IGNORED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && - contains_oitem) - { - /* this entry was prefixed with a tree - make TYPECHANGE */ - git_diff_delta *last = diff_delta__last_for_item(diff, nitem); - if (last) { - last->status = GIT_DELTA_TYPECHANGE; - last->old_file.mode = GIT_FILEMODE_TREE; - } - } - - return iterator_advance(&info->nitem, info->new_iter); -} - -static int handle_unmatched_old_item( - git_diff_generated *diff, diff_in_progress *info) -{ - git_delta_t delta_type = GIT_DELTA_DELETED; - int error; - - /* update delta_type if this item is conflicted */ - if (git_index_entry_is_conflict(info->oitem)) - delta_type = GIT_DELTA_CONFLICTED; - - if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) - return error; - - /* if we are generating TYPECHANGE records then check for that - * instead of just generating a DELETE record - */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && - entry_is_prefixed(diff, info->nitem, info->oitem)) - { - /* this entry has become a tree! convert to TYPECHANGE */ - git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); - if (last) { - last->status = GIT_DELTA_TYPECHANGE; - last->new_file.mode = GIT_FILEMODE_TREE; - } - - /* If new_iter is a workdir iterator, then this situation - * will certainly be followed by a series of untracked items. - * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... - */ - if (S_ISDIR(info->nitem->mode) && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) - return iterator_advance(&info->nitem, info->new_iter); - } - - return iterator_advance(&info->oitem, info->old_iter); -} - -static int handle_matched_item( - git_diff_generated *diff, diff_in_progress *info) -{ - int error = 0; - - if ((error = maybe_modified(diff, info)) < 0) - return error; - - if (!(error = iterator_advance(&info->oitem, info->old_iter))) - error = iterator_advance(&info->nitem, info->new_iter); - - return error; -} - -int git_diff__from_iterators( - git_diff **out, - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter, - const git_diff_options *opts) -{ - git_diff_generated *diff; - diff_in_progress info = {0}; - int error = 0; - - *out = NULL; - - diff = diff_generated_alloc(repo, old_iter, new_iter); - GIT_ERROR_CHECK_ALLOC(diff); - - info.repo = repo; - info.old_iter = old_iter; - info.new_iter = new_iter; - - /* make iterators have matching icase behavior */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { - if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || - (error = git_iterator_set_ignore_case(new_iter, true)) < 0) - goto cleanup; - } - - /* finish initialization */ - if ((error = diff_generated_apply_options(diff, opts)) < 0) - goto cleanup; - - if ((error = iterator_current(&info.oitem, old_iter)) < 0 || - (error = iterator_current(&info.nitem, new_iter)) < 0) - goto cleanup; - - /* run iterators building diffs */ - while (!error && (info.oitem || info.nitem)) { - int cmp; - - /* report progress */ - if (opts && opts->progress_cb) { - if ((error = opts->progress_cb(&diff->base, - info.oitem ? info.oitem->path : NULL, - info.nitem ? info.nitem->path : NULL, - opts->payload))) - break; - } - - cmp = info.oitem ? - (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; - - /* create DELETED records for old items not matched in new */ - if (cmp < 0) - error = handle_unmatched_old_item(diff, &info); - - /* create ADDED, TRACKED, or IGNORED records for new items not - * matched in old (and/or descend into directories as needed) - */ - else if (cmp > 0) - error = handle_unmatched_new_item(diff, &info); - - /* otherwise item paths match, so create MODIFIED record - * (or ADDED and DELETED pair if type changed) - */ - else - error = handle_matched_item(diff, &info); - } - - diff->base.perf.stat_calls += - old_iter->stat_calls + new_iter->stat_calls; - -cleanup: - if (!error) - *out = &diff->base; - else - git_diff_free(&diff->base); - if (info.submodule_cache) - git_submodule_cache_free(info.submodule_cache); - - return error; -} - -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, - git_repository *repo, - git_tree *old_tree, - git_tree *new_tree, - const git_diff_options *opts) -{ - 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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - /* for tree to tree diff, be case sensitive even if the index is - * currently case insensitive, unless the user explicitly asked - * for case insensitivity - */ - if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) - iflag = GIT_ITERATOR_IGNORE_CASE; - - 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; - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -static int diff_load_index(git_index **index, git_repository *repo) -{ - int error = git_repository_index__weakptr(index, repo); - - /* reload the repository index when user did not pass one in */ - if (!error && git_index_read(*index, false) < 0) - git_error_clear(); - - return error; -} - -int git_diff_tree_to_index( - git_diff **out, - git_repository *repo, - git_tree *old_tree, - git_index *index, - const git_diff_options *opts) -{ - 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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if (!index && (error = diff_load_index(&index, repo)) < 0) - return error; - - index_ignore_case = index->ignore_case; - - 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 (index_ignore_case) - diff_set_ignore_case(diff, true); - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff_index_to_workdir( - git_diff **out, - git_repository *repo, - 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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if (!index && (error = diff_load_index(&index, repo)) < 0) - return error; - - 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; - - if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) - if ((error = git_index_write(index)) < 0) - goto out; - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff_tree_to_workdir( - git_diff **out, - git_repository *repo, - 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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - 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; - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff_tree_to_workdir_with_index( - git_diff **out, - git_repository *repo, - git_tree *tree, - const git_diff_options *opts) -{ - git_diff *d1 = NULL, *d2 = NULL; - git_index *index = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if ((error = diff_load_index(&index, repo)) < 0) - return error; - - if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && - !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) - error = git_diff_merge(d1, d2); - - git_diff_free(d2); - - if (error) { - git_diff_free(d1); - d1 = NULL; - } - - *out = d1; - return error; -} - -int git_diff_index_to_index( - git_diff **out, - git_repository *repo, - git_index *old_index, - git_index *new_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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(old_index); - GIT_ASSERT_ARG(new_index); - - *out = NULL; - - 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 (old_index->ignore_case || new_index->ignore_case) - diff_set_ignore_case(diff, true); - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff__paired_foreach( - git_diff *head2idx, - git_diff *idx2wd, - int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), - void *payload) -{ - int cmp, error = 0; - git_diff_delta *h2i, *i2w; - size_t i, j, i_max, j_max; - int (*strcomp)(const char *, const char *) = git__strcmp; - bool h2i_icase, i2w_icase, icase_mismatch; - - i_max = head2idx ? head2idx->deltas.length : 0; - j_max = idx2wd ? idx2wd->deltas.length : 0; - if (!i_max && !j_max) - return 0; - - /* At some point, tree-to-index diffs will probably never ignore case, - * even if that isn't true now. Index-to-workdir diffs may or may not - * ignore case, but the index filename for the idx2wd diff should - * still be using the canonical case-preserving name. - * - * Therefore the main thing we need to do here is make sure the diffs - * are traversed in a compatible order. To do this, we temporarily - * resort a mismatched diff to get the order correct. - * - * In order to traverse renames in the index->workdir, we need to - * ensure that we compare the index name on both sides, so we - * always sort by the old name in the i2w list. - */ - h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); - i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); - - icase_mismatch = - (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); - - if (icase_mismatch && h2i_icase) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); - git_vector_sort(&head2idx->deltas); - } - - if (i2w_icase && !icase_mismatch) { - strcomp = git__strcasecmp; - - git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp); - git_vector_sort(&idx2wd->deltas); - } else if (idx2wd != NULL) { - git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp); - git_vector_sort(&idx2wd->deltas); - } - - for (i = 0, j = 0; i < i_max || j < j_max; ) { - h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; - i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; - - cmp = !i2w ? -1 : !h2i ? 1 : - strcomp(h2i->new_file.path, i2w->old_file.path); - - if (cmp < 0) { - i++; i2w = NULL; - } else if (cmp > 0) { - j++; h2i = NULL; - } else { - i++; j++; - } - - if ((error = cb(h2i, i2w, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - /* restore case-insensitive delta sort */ - if (icase_mismatch && h2i_icase) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); - git_vector_sort(&head2idx->deltas); - } - - /* restore idx2wd sort by new path */ - if (idx2wd != NULL) { - git_vector_set_cmp(&idx2wd->deltas, - i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); - git_vector_sort(&idx2wd->deltas); - } - - return error; -} - -int git_diff__commit( - git_diff **out, - git_repository *repo, - const git_commit *commit, - const git_diff_options *opts) -{ - git_commit *parent = NULL; - git_diff *commit_diff = NULL; - git_tree *old_tree = NULL, *new_tree = NULL; - size_t parents; - int error = 0; - - *out = NULL; - - if ((parents = git_commit_parentcount(commit)) > 1) { - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - error = -1; - git_error_set(GIT_ERROR_INVALID, "commit %s is a merge commit", - git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); - goto on_error; - } - - if (parents > 0) - if ((error = git_commit_parent(&parent, commit, 0)) < 0 || - (error = git_commit_tree(&old_tree, parent)) < 0) - goto on_error; - - if ((error = git_commit_tree(&new_tree, commit)) < 0 || - (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) - goto on_error; - - *out = commit_diff; - -on_error: - git_tree_free(new_tree); - git_tree_free(old_tree); - git_commit_free(parent); - - return error; -} - diff --git a/src/diff_generate.h b/src/diff_generate.h deleted file mode 100644 index b782f29c6..000000000 --- a/src/diff_generate.h +++ /dev/null @@ -1,130 +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_diff_generate_h__ -#define INCLUDE_diff_generate_h__ - -#include "common.h" - -#include "diff.h" -#include "pool.h" -#include "index.h" - -enum { - GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ - GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ - GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ - GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ - GIT_DIFFCAPS_USE_DEV = (1 << 4) /* use st_dev? */ -}; - -#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) -#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA) - -enum { - GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */ - GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ - GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ - GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ - GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */ - GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */ - - GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */ - GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */ - GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18), - GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19), - GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20) -}; - -#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF) - -#define GIT_DIFF__VERBOSE (1 << 30) - -extern void git_diff_addref(git_diff *diff); - -extern bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta); - -extern int git_diff__from_iterators( - git_diff **diff_ptr, - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter, - const git_diff_options *opts); - -extern int git_diff__commit( - git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); - -extern int git_diff__paired_foreach( - git_diff *idx2head, - git_diff *wd2idx, - int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), - void *payload); - -/* Merge two `git_diff`s according to the callback given by `cb`. */ - -typedef git_diff_delta *(*git_diff__merge_cb)( - const git_diff_delta *left, - const git_diff_delta *right, - git_pool *pool); - -extern int git_diff__merge( - git_diff *onto, const git_diff *from, git_diff__merge_cb cb); - -extern git_diff_delta *git_diff__merge_like_cgit( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool); - -/* Duplicate a `git_diff_delta` out of the `git_pool` */ -extern git_diff_delta *git_diff__delta_dup( - const git_diff_delta *d, git_pool *pool); - -extern int git_diff__oid_for_file( - git_oid *out, - git_diff *diff, - const char *path, - uint16_t mode, - git_object_size_t size); - -extern int git_diff__oid_for_entry( - git_oid *out, - git_diff *diff, - const git_index_entry *src, - uint16_t mode, - const git_oid *update_match); - -/* - * Sometimes a git_diff_file will have a zero size; this attempts to - * fill in the size without loading the blob if possible. If that is - * not possible, then it will return the git_odb_object that had to be - * loaded and the caller can use it or dispose of it as needed. - */ -GIT_INLINE(int) git_diff_file__resolve_zero_size( - git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) -{ - int error; - git_odb *odb; - size_t len; - git_object_t type; - - if ((error = git_repository_odb(&odb, repo)) < 0) - return error; - - error = git_odb__read_header_or_object( - odb_obj, &len, &type, odb, &file->id); - - git_odb_free(odb); - - if (!error) { - file->size = (git_object_size_t)len; - file->flags |= GIT_DIFF_FLAG_VALID_SIZE; - } - - return error; -} - -#endif diff --git a/src/diff_parse.c b/src/diff_parse.c deleted file mode 100644 index 75e41a544..000000000 --- a/src/diff_parse.c +++ /dev/null @@ -1,107 +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 "diff_parse.h" - -#include "diff.h" -#include "patch.h" -#include "patch_parse.h" - -static void diff_parsed_free(git_diff *d) -{ - git_diff_parsed *diff = (git_diff_parsed *)d; - git_patch *patch; - size_t i; - - git_vector_foreach(&diff->patches, i, patch) - git_patch_free(patch); - - git_vector_free(&diff->patches); - - git_vector_free(&diff->base.deltas); - git_pool_clear(&diff->base.pool); - - git__memzero(diff, sizeof(*diff)); - git__free(diff); -} - -static git_diff_parsed *diff_parsed_alloc(void) -{ - git_diff_parsed *diff; - - if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL) - return NULL; - - GIT_REFCOUNT_INC(&diff->base); - diff->base.type = GIT_DIFF_TYPE_PARSED; - diff->base.strcomp = git__strcmp; - diff->base.strncomp = git__strncmp; - diff->base.pfxcomp = git__prefixcmp; - diff->base.entrycomp = git_diff__entry_cmp; - diff->base.patch_fn = git_patch_parsed_from_diff; - diff->base.free_fn = diff_parsed_free; - - if (git_diff_options_init(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { - git__free(diff); - return NULL; - } - - diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE; - - if (git_pool_init(&diff->base.pool, 1) < 0 || - git_vector_init(&diff->patches, 0, NULL) < 0 || - git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { - git_diff_free(&diff->base); - return NULL; - } - - git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp); - - return diff; -} - -int git_diff_from_buffer( - git_diff **out, - const char *content, - size_t content_len) -{ - git_diff_parsed *diff; - git_patch *patch; - git_patch_parse_ctx *ctx = NULL; - int error = 0; - - *out = NULL; - - diff = diff_parsed_alloc(); - GIT_ERROR_CHECK_ALLOC(diff); - - ctx = git_patch_parse_ctx_init(content, content_len, NULL); - GIT_ERROR_CHECK_ALLOC(ctx); - - while (ctx->parse_ctx.remain_len) { - if ((error = git_patch_parse(&patch, ctx)) < 0) - break; - - git_vector_insert(&diff->patches, patch); - git_vector_insert(&diff->base.deltas, patch->delta); - } - - if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) { - git_error_clear(); - error = 0; - } - - git_patch_parse_ctx_free(ctx); - - if (error < 0) - git_diff_free(&diff->base); - else - *out = &diff->base; - - return error; -} - diff --git a/src/diff_parse.h b/src/diff_parse.h deleted file mode 100644 index 876782128..000000000 --- a/src/diff_parse.h +++ /dev/null @@ -1,20 +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_diff_parse_h__ -#define INCLUDE_diff_parse_h__ - -#include "common.h" - -#include "diff.h" - -typedef struct { - struct git_diff base; - - git_vector patches; -} git_diff_parsed; - -#endif diff --git a/src/diff_print.c b/src/diff_print.c deleted file mode 100644 index 6c5a2cdc8..000000000 --- a/src/diff_print.c +++ /dev/null @@ -1,830 +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 "common.h" - -#include "buf.h" -#include "diff.h" -#include "diff_file.h" -#include "patch_generate.h" -#include "futils.h" -#include "zstream.h" -#include "blob.h" -#include "delta.h" -#include "git2/sys/diff.h" - -typedef struct { - git_diff_format_t format; - git_diff_line_cb print_cb; - void *payload; - - git_str *buf; - git_diff_line line; - - const char *old_prefix; - const char *new_prefix; - uint32_t flags; - int id_strlen; - - int (*strcomp)(const char *, const char *); -} diff_print_info; - -static int diff_print_info_init__common( - diff_print_info *pi, - git_str *out, - git_repository *repo, - git_diff_format_t format, - git_diff_line_cb cb, - void *payload) -{ - pi->format = format; - pi->print_cb = cb; - pi->payload = payload; - pi->buf = out; - - if (!pi->id_strlen) { - if (!repo) - pi->id_strlen = GIT_ABBREV_DEFAULT; - else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) - return -1; - } - - if (pi->id_strlen > GIT_OID_HEXSZ) - pi->id_strlen = GIT_OID_HEXSZ; - - memset(&pi->line, 0, sizeof(pi->line)); - pi->line.old_lineno = -1; - pi->line.new_lineno = -1; - pi->line.num_lines = 1; - - return 0; -} - -static int diff_print_info_init_fromdiff( - diff_print_info *pi, - git_str *out, - git_diff *diff, - git_diff_format_t format, - git_diff_line_cb cb, - void *payload) -{ - git_repository *repo = diff ? diff->repo : NULL; - - memset(pi, 0, sizeof(diff_print_info)); - - if (diff) { - pi->flags = diff->opts.flags; - pi->id_strlen = diff->opts.id_abbrev; - pi->old_prefix = diff->opts.old_prefix; - pi->new_prefix = diff->opts.new_prefix; - - pi->strcomp = diff->strcomp; - } - - return diff_print_info_init__common(pi, out, repo, format, cb, payload); -} - -static int diff_print_info_init_frompatch( - diff_print_info *pi, - git_str *out, - git_patch *patch, - git_diff_format_t format, - git_diff_line_cb cb, - void *payload) -{ - GIT_ASSERT_ARG(patch); - - memset(pi, 0, sizeof(diff_print_info)); - - pi->flags = patch->diff_opts.flags; - pi->id_strlen = patch->diff_opts.id_abbrev; - pi->old_prefix = patch->diff_opts.old_prefix; - pi->new_prefix = patch->diff_opts.new_prefix; - - return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload); -} - -static char diff_pick_suffix(int mode) -{ - if (S_ISDIR(mode)) - return '/'; - else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ - /* in git, modes are very regular, so we must have 0100755 mode */ - return '*'; - else - return ' '; -} - -char git_diff_status_char(git_delta_t status) -{ - char code; - - switch (status) { - case GIT_DELTA_ADDED: code = 'A'; break; - case GIT_DELTA_DELETED: code = 'D'; break; - case GIT_DELTA_MODIFIED: code = 'M'; break; - case GIT_DELTA_RENAMED: code = 'R'; break; - case GIT_DELTA_COPIED: code = 'C'; break; - case GIT_DELTA_IGNORED: code = 'I'; break; - case GIT_DELTA_UNTRACKED: code = '?'; break; - case GIT_DELTA_TYPECHANGE: code = 'T'; break; - case GIT_DELTA_UNREADABLE: code = 'X'; break; - default: code = ' '; break; - } - - return code; -} - -static int diff_print_one_name_only( - const git_diff_delta *delta, float progress, void *data) -{ - diff_print_info *pi = data; - git_str *out = pi->buf; - - GIT_UNUSED(progress); - - if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && - delta->status == GIT_DELTA_UNMODIFIED) - return 0; - - git_str_clear(out); - git_str_puts(out, delta->new_file.path); - git_str_putc(out, '\n'); - if (git_str_oom(out)) - return -1; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(out); - pi->line.content_len = git_str_len(out); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_one_name_status( - const git_diff_delta *delta, float progress, void *data) -{ - diff_print_info *pi = data; - git_str *out = pi->buf; - char old_suffix, new_suffix, code = git_diff_status_char(delta->status); - int(*strcomp)(const char *, const char *) = pi->strcomp ? - pi->strcomp : git__strcmp; - - GIT_UNUSED(progress); - - if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') - return 0; - - old_suffix = diff_pick_suffix(delta->old_file.mode); - new_suffix = diff_pick_suffix(delta->new_file.mode); - - git_str_clear(out); - - if (delta->old_file.path != delta->new_file.path && - strcomp(delta->old_file.path,delta->new_file.path) != 0) - git_str_printf(out, "%c\t%s%c %s%c\n", code, - delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); - else if (delta->old_file.mode != delta->new_file.mode && - delta->old_file.mode != 0 && delta->new_file.mode != 0) - git_str_printf(out, "%c\t%s%c %s%c\n", code, - delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); - else if (old_suffix != ' ') - git_str_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); - else - git_str_printf(out, "%c\t%s\n", code, delta->old_file.path); - if (git_str_oom(out)) - return -1; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(out); - pi->line.content_len = git_str_len(out); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_one_raw( - const git_diff_delta *delta, float progress, void *data) -{ - diff_print_info *pi = data; - git_str *out = pi->buf; - int id_abbrev; - char code = git_diff_status_char(delta->status); - char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; - - GIT_UNUSED(progress); - - if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') - return 0; - - git_str_clear(out); - - id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev : - delta->new_file.id_abbrev; - - if (pi->id_strlen > id_abbrev) { - git_error_set(GIT_ERROR_PATCH, - "the patch input contains %d id characters (cannot print %d)", - id_abbrev, pi->id_strlen); - return -1; - } - - git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id); - git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id); - - git_str_printf( - out, (pi->id_strlen <= GIT_OID_HEXSZ) ? - ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c", - delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); - - if (delta->similarity > 0) - git_str_printf(out, "%03u", delta->similarity); - - if (delta->old_file.path != delta->new_file.path) - git_str_printf( - out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); - else - git_str_printf( - out, "\t%s\n", delta->old_file.path ? - delta->old_file.path : delta->new_file.path); - - if (git_str_oom(out)) - return -1; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(out); - pi->line.content_len = git_str_len(out); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_modes( - git_str *out, const git_diff_delta *delta) -{ - git_str_printf(out, "old mode %o\n", delta->old_file.mode); - git_str_printf(out, "new mode %o\n", delta->new_file.mode); - - return git_str_oom(out) ? -1 : 0; -} - -static int diff_print_oid_range( - git_str *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]; - - if (delta->old_file.mode && - id_strlen > delta->old_file.id_abbrev) { - git_error_set(GIT_ERROR_PATCH, - "the patch input contains %d id characters (cannot print %d)", - delta->old_file.id_abbrev, id_strlen); - return -1; - } - - if ((delta->new_file.mode && - id_strlen > delta->new_file.id_abbrev)) { - git_error_set(GIT_ERROR_PATCH, - "the patch input contains %d id characters (cannot print %d)", - delta->new_file.id_abbrev, id_strlen); - return -1; - } - - git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id); - git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); - - if (delta->old_file.mode == delta->new_file.mode) { - if (print_index) - git_str_printf(out, "index %s..%s %o\n", - start_oid, end_oid, delta->old_file.mode); - } else { - if (delta->old_file.mode == 0) - git_str_printf(out, "new file mode %o\n", delta->new_file.mode); - else if (delta->new_file.mode == 0) - git_str_printf(out, "deleted file mode %o\n", delta->old_file.mode); - else - diff_print_modes(out, delta); - - if (print_index) - git_str_printf(out, "index %s..%s\n", start_oid, end_oid); - } - - return git_str_oom(out) ? -1 : 0; -} - -static int diff_delta_format_path( - git_str *out, const char *prefix, const char *filename) -{ - if (!filename) { - /* don't prefix "/dev/null" */ - return git_str_puts(out, "/dev/null"); - } - - if (git_str_joinpath(out, prefix, filename) < 0) - return -1; - - return git_str_quote(out); -} - -static int diff_delta_format_with_paths( - git_str *out, - const git_diff_delta *delta, - const char *template, - const char *oldpath, - const char *newpath) -{ - if (git_oid_is_zero(&delta->old_file.id)) - oldpath = "/dev/null"; - - if (git_oid_is_zero(&delta->new_file.id)) - newpath = "/dev/null"; - - return git_str_printf(out, template, oldpath, newpath); -} - -static int diff_delta_format_similarity_header( - git_str *out, - const git_diff_delta *delta) -{ - git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; - const char *type; - int error = 0; - - if (delta->similarity > 100) { - git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity); - error = -1; - goto done; - } - - GIT_ASSERT(delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED); - if (delta->status == GIT_DELTA_RENAMED) - type = "rename"; - else - type = "copy"; - - if ((error = git_str_puts(&old_path, delta->old_file.path)) < 0 || - (error = git_str_puts(&new_path, delta->new_file.path)) < 0 || - (error = git_str_quote(&old_path)) < 0 || - (error = git_str_quote(&new_path)) < 0) - goto done; - - git_str_printf(out, - "similarity index %d%%\n" - "%s from %s\n" - "%s to %s\n", - delta->similarity, - type, old_path.ptr, - type, new_path.ptr); - - if (git_str_oom(out)) - error = -1; - -done: - git_str_dispose(&old_path); - git_str_dispose(&new_path); - - return error; -} - -static bool delta_is_unchanged(const git_diff_delta *delta) -{ - 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 || - delta->new_file.mode == GIT_FILEMODE_COMMIT) - return false; - - if (git_oid_equal(&delta->old_file.id, &delta->new_file.id)) - return true; - - return false; -} - -int git_diff_delta__format_file_header( - git_str *out, - const git_diff_delta *delta, - const char *oldpfx, - const char *newpfx, - int id_strlen, - bool print_index) -{ - git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; - bool unchanged = delta_is_unchanged(delta); - int error = 0; - - if (!oldpfx) - oldpfx = DIFF_OLD_PREFIX_DEFAULT; - if (!newpfx) - newpfx = DIFF_NEW_PREFIX_DEFAULT; - if (!id_strlen) - id_strlen = GIT_ABBREV_DEFAULT; - - if ((error = diff_delta_format_path( - &old_path, oldpfx, delta->old_file.path)) < 0 || - (error = diff_delta_format_path( - &new_path, newpfx, delta->new_file.path)) < 0) - goto done; - - git_str_clear(out); - - git_str_printf(out, "diff --git %s %s\n", - old_path.ptr, new_path.ptr); - - if (unchanged && delta->old_file.mode != delta->new_file.mode) - diff_print_modes(out, delta); - - if (delta->status == GIT_DELTA_RENAMED || - (delta->status == GIT_DELTA_COPIED && unchanged)) { - if ((error = diff_delta_format_similarity_header(out, delta)) < 0) - goto done; - } - - if (!unchanged) { - if ((error = diff_print_oid_range(out, delta, - id_strlen, print_index)) < 0) - goto done; - - if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) - diff_delta_format_with_paths(out, delta, - "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr); - } - - if (git_str_oom(out)) - error = -1; - -done: - git_str_dispose(&old_path); - git_str_dispose(&new_path); - - return error; -} - -static int format_binary( - diff_print_info *pi, - git_diff_binary_t type, - const char *data, - size_t datalen, - size_t inflatedlen) -{ - const char *typename = type == GIT_DIFF_BINARY_DELTA ? - "delta" : "literal"; - const char *scan, *end; - - git_str_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen); - pi->line.num_lines++; - - for (scan = data, end = data + datalen; scan < end; ) { - size_t chunk_len = end - scan; - if (chunk_len > 52) - chunk_len = 52; - - if (chunk_len <= 26) - git_str_putc(pi->buf, (char)chunk_len + 'A' - 1); - else - git_str_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); - - git_str_encode_base85(pi->buf, scan, chunk_len); - git_str_putc(pi->buf, '\n'); - - if (git_str_oom(pi->buf)) - return -1; - - scan += chunk_len; - pi->line.num_lines++; - } - git_str_putc(pi->buf, '\n'); - - if (git_str_oom(pi->buf)) - return -1; - - return 0; -} - -static int diff_print_patch_file_binary_noshow( - diff_print_info *pi, git_diff_delta *delta, - const char *old_pfx, const char *new_pfx) -{ - git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; - int error; - - if ((error = diff_delta_format_path(&old_path, old_pfx, delta->old_file.path)) < 0 || - (error = diff_delta_format_path(&new_path, new_pfx, delta->new_file.path)) < 0 || - (error = diff_delta_format_with_paths(pi->buf, delta, "Binary files %s and %s differ\n", - old_path.ptr, new_path.ptr)) < 0) - goto done; - - pi->line.num_lines = 1; - -done: - git_str_dispose(&old_path); - git_str_dispose(&new_path); - return error; -} - -static int diff_print_patch_file_binary( - diff_print_info *pi, git_diff_delta *delta, - const char *old_pfx, const char *new_pfx, - const git_diff_binary *binary) -{ - size_t pre_binary_size; - int error; - - if (delta->status == GIT_DELTA_UNMODIFIED) - return 0; - - if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data) - return diff_print_patch_file_binary_noshow( - pi, delta, old_pfx, new_pfx); - - pre_binary_size = pi->buf->size; - git_str_printf(pi->buf, "GIT binary patch\n"); - pi->line.num_lines++; - - if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, - binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || - (error = format_binary(pi, binary->old_file.type, binary->old_file.data, - binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { - if (error == GIT_EBUFS) { - git_error_clear(); - git_str_truncate(pi->buf, pre_binary_size); - - return diff_print_patch_file_binary_noshow( - pi, delta, old_pfx, new_pfx); - } - } - - pi->line.num_lines++; - return error; -} - -static int diff_print_patch_file( - const git_diff_delta *delta, float progress, void *data) -{ - int error; - diff_print_info *pi = data; - const char *oldpfx = - pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; - const char *newpfx = - pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; - - bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || - (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 : - delta->new_file.id_abbrev; - - GIT_UNUSED(progress); - - if (S_ISDIR(delta->new_file.mode) || - delta->status == GIT_DELTA_UNMODIFIED || - delta->status == GIT_DELTA_IGNORED || - delta->status == GIT_DELTA_UNREADABLE || - (delta->status == GIT_DELTA_UNTRACKED && - (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) - return 0; - - if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx, - id_strlen, print_index)) < 0) - return error; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(pi->buf); - pi->line.content_len = git_str_len(pi->buf); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_patch_binary( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *data) -{ - diff_print_info *pi = data; - const char *old_pfx = - pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; - const char *new_pfx = - pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; - int error; - - git_str_clear(pi->buf); - - if ((error = diff_print_patch_file_binary( - pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0) - return error; - - pi->line.origin = GIT_DIFF_LINE_BINARY; - pi->line.content = git_str_cstr(pi->buf); - pi->line.content_len = git_str_len(pi->buf); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_patch_hunk( - const git_diff_delta *d, - const git_diff_hunk *h, - void *data) -{ - diff_print_info *pi = data; - - if (S_ISDIR(d->new_file.mode)) - return 0; - - pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; - pi->line.content = h->header; - pi->line.content_len = h->header_len; - - return pi->print_cb(d, h, &pi->line, pi->payload); -} - -static int diff_print_patch_line( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *data) -{ - diff_print_info *pi = data; - - if (S_ISDIR(delta->new_file.mode)) - return 0; - - return pi->print_cb(delta, hunk, line, pi->payload); -} - -/* print a git_diff to an output callback */ -int git_diff_print( - git_diff *diff, - git_diff_format_t format, - git_diff_line_cb print_cb, - void *payload) -{ - int error; - git_str buf = GIT_STR_INIT; - diff_print_info pi; - git_diff_file_cb print_file = NULL; - git_diff_binary_cb print_binary = NULL; - git_diff_hunk_cb print_hunk = NULL; - git_diff_line_cb print_line = NULL; - - switch (format) { - case GIT_DIFF_FORMAT_PATCH: - print_file = diff_print_patch_file; - print_binary = diff_print_patch_binary; - 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; - case GIT_DIFF_FORMAT_RAW: - print_file = diff_print_one_raw; - break; - case GIT_DIFF_FORMAT_NAME_ONLY: - print_file = diff_print_one_name_only; - break; - case GIT_DIFF_FORMAT_NAME_STATUS: - print_file = diff_print_one_name_status; - break; - default: - git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format); - return -1; - } - - if ((error = diff_print_info_init_fromdiff(&pi, &buf, diff, format, print_cb, payload)) < 0) - goto out; - - if ((error = git_diff_foreach(diff, print_file, print_binary, print_hunk, print_line, &pi)) != 0) { - git_error_set_after_callback_function(error, "git_diff_print"); - goto out; - } - -out: - git_str_dispose(&buf); - return error; -} - -int git_diff_print_callback__to_buf( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - git_str *output = payload; - GIT_UNUSED(delta); GIT_UNUSED(hunk); - - if (!output) { - git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided"); - return -1; - } - - if (line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION || - line->origin == GIT_DIFF_LINE_CONTEXT) - git_str_putc(output, line->origin); - - return git_str_put(output, line->content, line->content_len); -} - -int git_diff_print_callback__to_file_handle( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - FILE *fp = payload ? payload : stdout; - int error; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - - if (line->origin == GIT_DIFF_LINE_CONTEXT || - line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION) { - while ((error = fputc(line->origin, fp)) == EINTR) - continue; - if (error) { - git_error_set(GIT_ERROR_OS, "could not write status"); - return -1; - } - } - - if (fwrite(line->content, line->content_len, 1, fp) != 1) { - git_error_set(GIT_ERROR_OS, "could not write line"); - return -1; - } - - return 0; -} - -/* print a git_diff to a git_str */ -int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format) -{ - git_str str = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_diff_print(diff, format, git_diff_print_callback__to_buf, &str)) < 0) - goto done; - - error = git_buf_fromstr(out, &str); - -done: - git_str_dispose(&str); - return error; -} - -/* print a git_patch to an output callback */ -int git_patch_print( - git_patch *patch, - git_diff_line_cb print_cb, - void *payload) -{ - git_str temp = GIT_STR_INIT; - diff_print_info pi; - int error; - - GIT_ASSERT_ARG(patch); - GIT_ASSERT_ARG(print_cb); - - if ((error = diff_print_info_init_frompatch(&pi, &temp, patch, - GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0) - goto out; - - if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary, - diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) { - git_error_set_after_callback_function(error, "git_patch_print"); - goto out; - } - -out: - git_str_dispose(&temp); - return error; -} - -/* print a git_patch to a git_str */ -int git_patch_to_buf(git_buf *out, git_patch *patch) -{ - GIT_BUF_WRAP_PRIVATE(out, git_patch__to_buf, patch); -} - -int git_patch__to_buf(git_str *out, git_patch *patch) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(patch); - - return git_patch_print(patch, git_diff_print_callback__to_buf, out); -} diff --git a/src/diff_stats.c b/src/diff_stats.c deleted file mode 100644 index 259939844..000000000 --- a/src/diff_stats.c +++ /dev/null @@ -1,376 +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 "diff_stats.h" - -#include "buf.h" -#include "common.h" -#include "vector.h" -#include "diff.h" -#include "patch_generate.h" - -#define DIFF_RENAME_FILE_SEPARATOR " => " -#define STATS_FULL_MIN_SCALE 7 - -typedef struct { - size_t insertions; - size_t deletions; -} diff_file_stats; - -struct git_diff_stats { - git_diff *diff; - diff_file_stats *filestats; - - size_t files_changed; - size_t insertions; - size_t deletions; - size_t renames; - - size_t max_name; - size_t max_filestat; - int max_digits; -}; - -static int digits_for_value(size_t val) -{ - int count = 1; - size_t placevalue = 10; - - while (val >= placevalue) { - ++count; - placevalue *= 10; - } - - return count; -} - -static int diff_file_stats_full_to_buf( - git_str *out, - const git_diff_delta *delta, - const diff_file_stats *filestat, - const git_diff_stats *stats, - size_t width) -{ - const char *old_path = NULL, *new_path = NULL, *adddel_path = NULL; - size_t padding; - git_object_size_t old_size, new_size; - - old_path = delta->old_file.path; - new_path = delta->new_file.path; - old_size = delta->old_file.size; - new_size = delta->new_file.size; - - if (old_path && new_path && strcmp(old_path, new_path) != 0) { - size_t common_dirlen; - int error; - - padding = stats->max_name - strlen(old_path) - strlen(new_path); - - if ((common_dirlen = git_fs_path_common_dirlen(old_path, new_path)) && - common_dirlen <= INT_MAX) { - error = git_str_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}", - (int) common_dirlen, old_path, - old_path + common_dirlen, - new_path + common_dirlen); - } else { - error = git_str_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s", - old_path, new_path); - } - - if (error < 0) - goto on_error; - } else { - adddel_path = new_path ? new_path : old_path; - if (git_str_printf(out, " %s", adddel_path) < 0) - goto on_error; - - padding = stats->max_name - strlen(adddel_path); - - if (stats->renames > 0) - padding += strlen(DIFF_RENAME_FILE_SEPARATOR); - } - - if (git_str_putcn(out, ' ', padding) < 0 || - git_str_puts(out, " | ") < 0) - goto on_error; - - if (delta->flags & GIT_DIFF_FLAG_BINARY) { - if (git_str_printf(out, - "Bin %" PRId64 " -> %" PRId64 " bytes", old_size, new_size) < 0) - goto on_error; - } - else { - if (git_str_printf(out, - "%*" PRIuZ, stats->max_digits, - filestat->insertions + filestat->deletions) < 0) - goto on_error; - - if (filestat->insertions || filestat->deletions) { - if (git_str_putc(out, ' ') < 0) - goto on_error; - - if (!width) { - if (git_str_putcn(out, '+', filestat->insertions) < 0 || - git_str_putcn(out, '-', filestat->deletions) < 0) - goto on_error; - } else { - size_t total = filestat->insertions + filestat->deletions; - size_t full = (total * width + stats->max_filestat / 2) / - stats->max_filestat; - size_t plus = full * filestat->insertions / total; - size_t minus = full - plus; - - if (git_str_putcn(out, '+', max(plus, 1)) < 0 || - git_str_putcn(out, '-', max(minus, 1)) < 0) - goto on_error; - } - } - } - - git_str_putc(out, '\n'); - -on_error: - return (git_str_oom(out) ? -1 : 0); -} - -static int diff_file_stats_number_to_buf( - git_str *out, - const git_diff_delta *delta, - const diff_file_stats *filestats) -{ - int error; - const char *path = delta->new_file.path; - - if (delta->flags & GIT_DIFF_FLAG_BINARY) - error = git_str_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path); - else - error = git_str_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", - filestats->insertions, filestats->deletions, path); - - return error; -} - -static int diff_file_stats_summary_to_buf( - git_str *out, - const git_diff_delta *delta) -{ - if (delta->old_file.mode != delta->new_file.mode) { - if (delta->old_file.mode == 0) { - git_str_printf(out, " create mode %06o %s\n", - delta->new_file.mode, delta->new_file.path); - } - else if (delta->new_file.mode == 0) { - git_str_printf(out, " delete mode %06o %s\n", - delta->old_file.mode, delta->old_file.path); - } - else { - git_str_printf(out, " mode change %06o => %06o %s\n", - delta->old_file.mode, delta->new_file.mode, delta->new_file.path); - } - } - - return 0; -} - -int git_diff_get_stats( - git_diff_stats **out, - git_diff *diff) -{ - size_t i, deltas; - size_t total_insertions = 0, total_deletions = 0; - git_diff_stats *stats = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - - stats = git__calloc(1, sizeof(git_diff_stats)); - GIT_ERROR_CHECK_ALLOC(stats); - - deltas = git_diff_num_deltas(diff); - - stats->filestats = git__calloc(deltas, sizeof(diff_file_stats)); - if (!stats->filestats) { - git__free(stats); - return -1; - } - - stats->diff = diff; - GIT_REFCOUNT_INC(diff); - - for (i = 0; i < deltas && !error; ++i) { - git_patch *patch = NULL; - size_t add = 0, remove = 0, namelen; - const git_diff_delta *delta; - - if ((error = git_patch_from_diff(&patch, diff, i)) < 0) - break; - - /* keep a count of renames because it will affect formatting */ - delta = patch->delta; - - /* TODO ugh */ - namelen = strlen(delta->new_file.path); - if (delta->old_file.path && strcmp(delta->old_file.path, delta->new_file.path) != 0) { - namelen += strlen(delta->old_file.path); - stats->renames++; - } - - /* and, of course, count the line stats */ - error = git_patch_line_stats(NULL, &add, &remove, patch); - - git_patch_free(patch); - - stats->filestats[i].insertions = add; - stats->filestats[i].deletions = remove; - - total_insertions += add; - total_deletions += remove; - - if (stats->max_name < namelen) - stats->max_name = namelen; - if (stats->max_filestat < add + remove) - stats->max_filestat = add + remove; - } - - stats->files_changed = deltas; - stats->insertions = total_insertions; - stats->deletions = total_deletions; - stats->max_digits = digits_for_value(stats->max_filestat + 1); - - if (error < 0) { - git_diff_stats_free(stats); - stats = NULL; - } - - *out = stats; - return error; -} - -size_t git_diff_stats_files_changed( - const git_diff_stats *stats) -{ - GIT_ASSERT_ARG(stats); - - return stats->files_changed; -} - -size_t git_diff_stats_insertions( - const git_diff_stats *stats) -{ - GIT_ASSERT_ARG(stats); - - return stats->insertions; -} - -size_t git_diff_stats_deletions( - const git_diff_stats *stats) -{ - GIT_ASSERT_ARG(stats); - - return stats->deletions; -} - -int git_diff_stats_to_buf( - git_buf *out, - const git_diff_stats *stats, - git_diff_stats_format_t format, - size_t width) -{ - GIT_BUF_WRAP_PRIVATE(out, git_diff__stats_to_buf, stats, format, width); -} - -int git_diff__stats_to_buf( - git_str *out, - const git_diff_stats *stats, - git_diff_stats_format_t format, - size_t width) -{ - int error = 0; - size_t i; - const git_diff_delta *delta; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(stats); - - if (format & GIT_DIFF_STATS_NUMBER) { - for (i = 0; i < stats->files_changed; ++i) { - if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) - continue; - - error = diff_file_stats_number_to_buf( - out, delta, &stats->filestats[i]); - if (error < 0) - return error; - } - } - - if (format & GIT_DIFF_STATS_FULL) { - if (width > 0) { - if (width > stats->max_name + stats->max_digits + 5) - width -= (stats->max_name + stats->max_digits + 5); - if (width < STATS_FULL_MIN_SCALE) - width = STATS_FULL_MIN_SCALE; - } - if (width > stats->max_filestat) - width = 0; - - for (i = 0; i < stats->files_changed; ++i) { - if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) - continue; - - error = diff_file_stats_full_to_buf( - out, delta, &stats->filestats[i], stats, width); - if (error < 0) - return error; - } - } - - if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) { - git_str_printf( - out, " %" PRIuZ " file%s changed", - stats->files_changed, stats->files_changed != 1 ? "s" : ""); - - if (stats->insertions || stats->deletions == 0) - git_str_printf( - out, ", %" PRIuZ " insertion%s(+)", - stats->insertions, stats->insertions != 1 ? "s" : ""); - - if (stats->deletions || stats->insertions == 0) - git_str_printf( - out, ", %" PRIuZ " deletion%s(-)", - stats->deletions, stats->deletions != 1 ? "s" : ""); - - git_str_putc(out, '\n'); - - if (git_str_oom(out)) - return -1; - } - - if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) { - for (i = 0; i < stats->files_changed; ++i) { - if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) - continue; - - error = diff_file_stats_summary_to_buf(out, delta); - if (error < 0) - return error; - } - } - - return error; -} - -void git_diff_stats_free(git_diff_stats *stats) -{ - if (stats == NULL) - return; - - git_diff_free(stats->diff); /* bumped refcount in constructor */ - git__free(stats->filestats); - git__free(stats); -} diff --git a/src/diff_stats.h b/src/diff_stats.h deleted file mode 100644 index c71862b4e..000000000 --- a/src/diff_stats.h +++ /dev/null @@ -1,18 +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_diff_stats_h__ -#define INCLUDE_diff_stats_h__ - -#include "common.h" - -int git_diff__stats_to_buf( - git_str *out, - const git_diff_stats *stats, - git_diff_stats_format_t format, - size_t width); - -#endif diff --git a/src/diff_tform.c b/src/diff_tform.c deleted file mode 100644 index 913d649b0..000000000 --- a/src/diff_tform.c +++ /dev/null @@ -1,1121 +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 "diff_tform.h" - -#include "git2/config.h" -#include "git2/blob.h" -#include "git2/sys/hashsig.h" - -#include "diff.h" -#include "diff_generate.h" -#include "fs_path.h" -#include "futils.h" -#include "config.h" - -git_diff_delta *git_diff__delta_dup( - const git_diff_delta *d, git_pool *pool) -{ - git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); - if (!delta) - return NULL; - - memcpy(delta, d, sizeof(git_diff_delta)); - GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); - - if (d->old_file.path != NULL) { - delta->old_file.path = git_pool_strdup(pool, d->old_file.path); - if (delta->old_file.path == NULL) - goto fail; - } - - if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) { - delta->new_file.path = git_pool_strdup(pool, d->new_file.path); - if (delta->new_file.path == NULL) - goto fail; - } else { - delta->new_file.path = delta->old_file.path; - } - - return delta; - -fail: - git__free(delta); - return NULL; -} - -git_diff_delta *git_diff__merge_like_cgit( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool) -{ - git_diff_delta *dup; - - /* Emulate C git for merging two diffs (a la 'git diff '). - * - * When C git does a diff between the work dir and a tree, it actually - * diffs with the index but uses the workdir contents. This emulates - * those choices so we can emulate the type of diff. - * - * We have three file descriptions here, let's call them: - * f1 = a->old_file - * f2 = a->new_file AND b->old_file - * f3 = b->new_file - */ - - /* If one of the diffs is a conflict, just dup it */ - if (b->status == GIT_DELTA_CONFLICTED) - return git_diff__delta_dup(b, pool); - if (a->status == GIT_DELTA_CONFLICTED) - return git_diff__delta_dup(a, pool); - - /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ - if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) - return git_diff__delta_dup(a, pool); - - /* otherwise, base this diff on the 'b' diff */ - if ((dup = git_diff__delta_dup(b, pool)) == NULL) - return NULL; - - /* If 'a' status is uninteresting, then we're done */ - if (a->status == GIT_DELTA_UNMODIFIED || - a->status == GIT_DELTA_UNTRACKED || - a->status == GIT_DELTA_UNREADABLE) - return dup; - - GIT_ASSERT_WITH_RETVAL(b->status != GIT_DELTA_UNMODIFIED, NULL); - - /* A cgit exception is that the diff of a file that is only in the - * index (i.e. not in HEAD nor workdir) is given as empty. - */ - if (dup->status == GIT_DELTA_DELETED) { - if (a->status == GIT_DELTA_ADDED) { - dup->status = GIT_DELTA_UNMODIFIED; - dup->nfiles = 2; - } - /* else don't overwrite DELETE status */ - } else { - dup->status = a->status; - dup->nfiles = a->nfiles; - } - - git_oid_cpy(&dup->old_file.id, &a->old_file.id); - dup->old_file.mode = a->old_file.mode; - dup->old_file.size = a->old_file.size; - dup->old_file.flags = a->old_file.flags; - - return dup; -} - -int git_diff__merge( - git_diff *onto, const git_diff *from, git_diff__merge_cb cb) -{ - int error = 0; - git_pool onto_pool; - git_vector onto_new; - git_diff_delta *delta; - bool ignore_case, reversed; - unsigned int i, j; - - GIT_ASSERT_ARG(onto); - GIT_ASSERT_ARG(from); - - if (!from->deltas.length) - return 0; - - ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); - reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0); - - if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) || - reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) { - git_error_set(GIT_ERROR_INVALID, - "attempt to merge diffs created with conflicting options"); - return -1; - } - - if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || - git_pool_init(&onto_pool, 1) < 0) - return -1; - - for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { - git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); - const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); - int cmp = !f ? -1 : !o ? 1 : - STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); - - if (cmp < 0) { - delta = git_diff__delta_dup(o, &onto_pool); - i++; - } else if (cmp > 0) { - delta = git_diff__delta_dup(f, &onto_pool); - j++; - } else { - const git_diff_delta *left = reversed ? f : o; - const git_diff_delta *right = reversed ? o : f; - - delta = cb(left, right, &onto_pool); - i++; - j++; - } - - /* the ignore rules for the target may not match the source - * or the result of a merged delta could be skippable... - */ - if (delta && git_diff_delta__should_skip(&onto->opts, delta)) { - git__free(delta); - continue; - } - - if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) - break; - } - - if (!error) { - git_vector_swap(&onto->deltas, &onto_new); - git_pool_swap(&onto->pool, &onto_pool); - - if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0) - onto->old_src = from->old_src; - else - onto->new_src = from->new_src; - - /* prefix strings also come from old pool, so recreate those.*/ - onto->opts.old_prefix = - git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); - onto->opts.new_prefix = - git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); - } - - git_vector_free_deep(&onto_new); - git_pool_clear(&onto_pool); - - return error; -} - -int git_diff_merge(git_diff *onto, const git_diff *from) -{ - return git_diff__merge(onto, from, git_diff__merge_like_cgit); -} - -int git_diff_find_similar__hashsig_for_file( - void **out, const git_diff_file *f, const char *path, void *p) -{ - git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; - - GIT_UNUSED(f); - return git_hashsig_create_fromfile((git_hashsig **)out, path, opt); -} - -int git_diff_find_similar__hashsig_for_buf( - void **out, const git_diff_file *f, const char *buf, size_t len, void *p) -{ - git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; - - GIT_UNUSED(f); - return git_hashsig_create((git_hashsig **)out, buf, len, opt); -} - -void git_diff_find_similar__hashsig_free(void *sig, void *payload) -{ - GIT_UNUSED(payload); - git_hashsig_free(sig); -} - -int git_diff_find_similar__calc_similarity( - int *score, void *siga, void *sigb, void *payload) -{ - int error; - - GIT_UNUSED(payload); - error = git_hashsig_compare(siga, sigb); - if (error < 0) - return error; - - *score = error; - return 0; -} - -#define DEFAULT_THRESHOLD 50 -#define DEFAULT_BREAK_REWRITE_THRESHOLD 60 -#define DEFAULT_RENAME_LIMIT 1000 - -static int normalize_find_opts( - git_diff *diff, - git_diff_find_options *opts, - const git_diff_find_options *given) -{ - git_config *cfg = NULL; - git_hashsig_option_t hashsig_opts; - - GIT_ERROR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options"); - - if (diff->repo != NULL && - git_repository_config__weakptr(&cfg, diff->repo) < 0) - return -1; - - if (given) - memcpy(opts, given, sizeof(*opts)); - - if (!given || - (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG) - { - if (cfg) { - char *rule = - git_config__get_string_force(cfg, "diff.renames", "true"); - int boolval; - - if (!git__parse_bool(&boolval, rule) && !boolval) - /* don't set FIND_RENAMES if bool value is false */; - else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy")) - opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; - else - opts->flags |= GIT_DIFF_FIND_RENAMES; - - git__free(rule); - } else { - /* set default flag */ - opts->flags |= GIT_DIFF_FIND_RENAMES; - } - } - - /* some flags imply others */ - - if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) { - /* if we are only looking for exact matches, then don't turn - * MODIFIED items into ADD/DELETE pairs because it's too picky - */ - opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES); - - /* similarly, don't look for self-rewrites to split */ - opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES; - } - - if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES) - opts->flags |= GIT_DIFF_FIND_RENAMES; - - if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED) - opts->flags |= GIT_DIFF_FIND_COPIES; - - if (opts->flags & GIT_DIFF_BREAK_REWRITES) - opts->flags |= GIT_DIFF_FIND_REWRITES; - -#define USE_DEFAULT(X) ((X) == 0 || (X) > 100) - - if (USE_DEFAULT(opts->rename_threshold)) - opts->rename_threshold = DEFAULT_THRESHOLD; - - if (USE_DEFAULT(opts->rename_from_rewrite_threshold)) - opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD; - - if (USE_DEFAULT(opts->copy_threshold)) - opts->copy_threshold = DEFAULT_THRESHOLD; - - if (USE_DEFAULT(opts->break_rewrite_threshold)) - opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD; - -#undef USE_DEFAULT - - if (!opts->rename_limit) { - if (cfg) { - opts->rename_limit = git_config__get_int_force( - cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT); - } - - if (opts->rename_limit <= 0) - opts->rename_limit = DEFAULT_RENAME_LIMIT; - } - - /* assign the internal metric with whitespace flag as payload */ - if (!opts->metric) { - opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); - GIT_ERROR_CHECK_ALLOC(opts->metric); - - opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; - opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; - opts->metric->free_signature = git_diff_find_similar__hashsig_free; - opts->metric->similarity = git_diff_find_similar__calc_similarity; - - if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE) - hashsig_opts = GIT_HASHSIG_IGNORE_WHITESPACE; - else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE) - hashsig_opts = GIT_HASHSIG_NORMAL; - else - hashsig_opts = GIT_HASHSIG_SMART_WHITESPACE; - hashsig_opts |= GIT_HASHSIG_ALLOW_SMALL_FILES; - opts->metric->payload = (void *)hashsig_opts; - } - - return 0; -} - -static int insert_delete_side_of_split( - git_diff *diff, git_vector *onto, const git_diff_delta *delta) -{ - /* make new record for DELETED side of split */ - git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool); - GIT_ERROR_CHECK_ALLOC(deleted); - - deleted->status = GIT_DELTA_DELETED; - deleted->nfiles = 1; - memset(&deleted->new_file, 0, sizeof(deleted->new_file)); - deleted->new_file.path = deleted->old_file.path; - deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - return git_vector_insert(onto, deleted); -} - -static int apply_splits_and_deletes( - git_diff *diff, size_t expected_size, bool actually_split) -{ - git_vector onto = GIT_VECTOR_INIT; - size_t i; - git_diff_delta *delta; - - if (git_vector_init(&onto, expected_size, diff->deltas._cmp) < 0) - return -1; - - /* build new delta list without TO_DELETE and splitting TO_SPLIT */ - git_vector_foreach(&diff->deltas, i, delta) { - if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) - continue; - - if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) { - delta->similarity = 0; - - if (insert_delete_side_of_split(diff, &onto, delta) < 0) - goto on_error; - - if (diff->new_src == GIT_ITERATOR_WORKDIR) - delta->status = GIT_DELTA_UNTRACKED; - else - delta->status = GIT_DELTA_ADDED; - delta->nfiles = 1; - memset(&delta->old_file, 0, sizeof(delta->old_file)); - delta->old_file.path = delta->new_file.path; - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - } - - /* clean up delta before inserting into new list */ - GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); - - if (delta->status != GIT_DELTA_COPIED && - delta->status != GIT_DELTA_RENAMED && - (delta->status != GIT_DELTA_MODIFIED || actually_split)) - delta->similarity = 0; - - /* insert into new list */ - if (git_vector_insert(&onto, delta) < 0) - goto on_error; - } - - /* cannot return an error past this point */ - - /* free deltas from old list that didn't make it to the new one */ - git_vector_foreach(&diff->deltas, i, delta) { - if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) - git__free(delta); - } - - /* swap new delta list into place */ - git_vector_swap(&diff->deltas, &onto); - git_vector_free(&onto); - git_vector_sort(&diff->deltas); - - return 0; - -on_error: - git_vector_free_deep(&onto); - - return -1; -} - -GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) -{ - git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2); - return (idx & 1) ? &delta->new_file : &delta->old_file; -} - -typedef struct { - size_t idx; - git_iterator_t src; - git_repository *repo; - git_diff_file *file; - git_str data; - git_odb_object *odb_obj; - git_blob *blob; -} similarity_info; - -static int similarity_init( - similarity_info *info, git_diff *diff, size_t file_idx) -{ - info->idx = file_idx; - info->src = (file_idx & 1) ? diff->new_src : diff->old_src; - info->repo = diff->repo; - info->file = similarity_get_file(diff, file_idx); - info->odb_obj = NULL; - info->blob = NULL; - git_str_init(&info->data, 0); - - if ((info->file->flags & GIT_DIFF_FLAG_VALID_SIZE) || - info->src == GIT_ITERATOR_WORKDIR) - return 0; - - return git_diff_file__resolve_zero_size( - info->file, &info->odb_obj, info->repo); -} - -static int similarity_sig( - similarity_info *info, - const git_diff_find_options *opts, - void **cache) -{ - int error = 0; - git_diff_file *file = info->file; - - if (info->src == GIT_ITERATOR_WORKDIR) { - if ((error = git_repository_workdir_path( - &info->data, info->repo, file->path)) < 0) - return error; - - /* if path is not a regular file, just skip this item */ - if (!git_fs_path_isfile(info->data.ptr)) - return 0; - - /* TODO: apply wd-to-odb filters to file data if necessary */ - - error = opts->metric->file_signature( - &cache[info->idx], info->file, - info->data.ptr, opts->metric->payload); - } else { - /* if we didn't initially know the size, we might have an odb_obj - * around from earlier, so convert that, otherwise load the blob now - */ - if (info->odb_obj != NULL) - error = git_object__from_odb_object( - (git_object **)&info->blob, info->repo, - info->odb_obj, GIT_OBJECT_BLOB); - else - error = git_blob_lookup(&info->blob, info->repo, &file->id); - - if (error < 0) { - /* if lookup fails, just skip this item in similarity calc */ - git_error_clear(); - } else { - size_t sz; - - /* index size may not be actual blob size if filtered */ - if (file->size != git_blob_rawsize(info->blob)) - file->size = git_blob_rawsize(info->blob); - - sz = git__is_sizet(file->size) ? (size_t)file->size : (size_t)-1; - - error = opts->metric->buffer_signature( - &cache[info->idx], info->file, - git_blob_rawcontent(info->blob), sz, opts->metric->payload); - } - } - - return error; -} - -static void similarity_unload(similarity_info *info) -{ - if (info->odb_obj) - git_odb_object_free(info->odb_obj); - - if (info->blob) - git_blob_free(info->blob); - else - git_str_dispose(&info->data); -} - -#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) - -/* - score < 0 means files cannot be compared - * - score >= 100 means files are exact match - * - score == 0 means files are completely different - */ -static int similarity_measure( - int *score, - git_diff *diff, - const git_diff_find_options *opts, - void **cache, - size_t a_idx, - size_t b_idx) -{ - git_diff_file *a_file = similarity_get_file(diff, a_idx); - git_diff_file *b_file = similarity_get_file(diff, b_idx); - bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); - int error = 0; - similarity_info a_info, b_info; - - *score = -1; - - /* don't try to compare things that aren't files */ - if (!GIT_MODE_ISBLOB(a_file->mode) || !GIT_MODE_ISBLOB(b_file->mode)) - return 0; - - /* if exact match is requested, force calculation of missing OIDs now */ - if (exact_match) { - 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_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; - } - - /* check OID match as a quick test */ - if (git_oid__cmp(&a_file->id, &b_file->id) == 0) { - *score = 100; - return 0; - } - - /* don't calculate signatures if we are doing exact match */ - if (exact_match) { - *score = 0; - return 0; - } - - memset(&a_info, 0, sizeof(a_info)); - memset(&b_info, 0, sizeof(b_info)); - - /* set up similarity data (will try to update missing file sizes) */ - if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0) - return error; - if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0) - goto cleanup; - - /* check if file sizes are nowhere near each other */ - if (a_file->size > 127 && - b_file->size > 127 && - (a_file->size > (b_file->size << 3) || - b_file->size > (a_file->size << 3))) - goto cleanup; - - /* update signature cache if needed */ - if (!cache[a_idx]) { - if ((error = similarity_sig(&a_info, opts, cache)) < 0) - goto cleanup; - } - if (!cache[b_idx]) { - if ((error = similarity_sig(&b_info, opts, cache)) < 0) - goto cleanup; - } - - /* calculate similarity provided that the metric choose to process - * both the a and b files (some may not if file is too big, etc). - */ - if (cache[a_idx] && cache[b_idx]) - error = opts->metric->similarity( - score, cache[a_idx], cache[b_idx], opts->metric->payload); - -cleanup: - similarity_unload(&a_info); - similarity_unload(&b_info); - - return error; -} - -static int calc_self_similarity( - git_diff *diff, - const git_diff_find_options *opts, - size_t delta_idx, - void **cache) -{ - int error, similarity = -1; - git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); - - if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0) - return 0; - - error = similarity_measure( - &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1); - if (error < 0) - return error; - - if (similarity >= 0) { - delta->similarity = (uint16_t)similarity; - delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY; - } - - return 0; -} - -static bool is_rename_target( - git_diff *diff, - const git_diff_find_options *opts, - size_t delta_idx, - void **cache) -{ - git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); - - /* skip things that aren't plain blobs */ - if (!GIT_MODE_ISBLOB(delta->new_file.mode)) - return false; - - /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as - * targets; maybe include UNTRACKED if requested. - */ - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_DELETED: - case GIT_DELTA_IGNORED: - case GIT_DELTA_CONFLICTED: - return false; - - case GIT_DELTA_MODIFIED: - if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && - !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) - return false; - - if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) - return false; - - if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && - delta->similarity < opts->break_rewrite_threshold) { - delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; - break; - } - if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && - delta->similarity < opts->rename_from_rewrite_threshold) { - delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; - break; - } - - return false; - - case GIT_DELTA_UNTRACKED: - if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) - return false; - break; - - default: /* all other status values should be checked */ - break; - } - - delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET; - return true; -} - -static bool is_rename_source( - git_diff *diff, - const git_diff_find_options *opts, - size_t delta_idx, - void **cache) -{ - git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); - - /* skip things that aren't blobs */ - if (!GIT_MODE_ISBLOB(delta->old_file.mode)) - return false; - - switch (delta->status) { - case GIT_DELTA_ADDED: - case GIT_DELTA_UNTRACKED: - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_IGNORED: - case GIT_DELTA_CONFLICTED: - return false; - - case GIT_DELTA_DELETED: - case GIT_DELTA_TYPECHANGE: - break; - - case GIT_DELTA_UNMODIFIED: - if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)) - return false; - if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED)) - delta->flags |= GIT_DIFF_FLAG__TO_DELETE; - break; - - default: /* MODIFIED, RENAMED, COPIED */ - /* if we're finding copies, this could be a source */ - if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES)) - break; - - /* otherwise, this is only a source if we can split it */ - if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && - !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) - return false; - - if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) - return false; - - if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && - delta->similarity < opts->break_rewrite_threshold) { - delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; - break; - } - - if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && - delta->similarity < opts->rename_from_rewrite_threshold) - break; - - return false; - } - - delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE; - return true; -} - -GIT_INLINE(bool) delta_is_split(git_diff_delta *delta) -{ - return (delta->status == GIT_DELTA_TYPECHANGE || - (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0); -} - -GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) -{ - return (delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_UNTRACKED || - delta->status == GIT_DELTA_UNREADABLE || - delta->status == GIT_DELTA_IGNORED); -} - -GIT_INLINE(void) delta_make_rename( - git_diff_delta *to, const git_diff_delta *from, uint16_t similarity) -{ - to->status = GIT_DELTA_RENAMED; - to->similarity = similarity; - to->nfiles = 2; - memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); - to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; -} - -typedef struct { - size_t idx; - uint16_t similarity; -} diff_find_match; - -int git_diff_find_similar( - git_diff *diff, - const git_diff_find_options *given_opts) -{ - size_t s, t; - int error = 0, result; - uint16_t similarity; - git_diff_delta *src, *tgt; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - size_t num_deltas, num_srcs = 0, num_tgts = 0; - size_t tried_srcs = 0, tried_tgts = 0; - size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; - size_t sigcache_size; - void **sigcache = NULL; /* cache of similarity metric file signatures */ - diff_find_match *tgt2src = NULL; - diff_find_match *src2tgt = NULL; - diff_find_match *tgt2src_copy = NULL; - diff_find_match *best_match; - git_diff_file swap; - - GIT_ASSERT_ARG(diff); - - if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) - return error; - - num_deltas = diff->deltas.length; - - /* TODO: maybe abort if deltas.length > rename_limit ??? */ - if (!num_deltas || !git__is_uint32(num_deltas)) - goto cleanup; - - /* No flags set; nothing to do */ - if ((opts.flags & GIT_DIFF_FIND_ALL) == 0) - goto cleanup; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&sigcache_size, num_deltas, 2); - sigcache = git__calloc(sigcache_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(sigcache); - - /* Label rename sources and targets - * - * This will also set self-similarity scores for MODIFIED files and - * mark them for splitting if break-rewrites is enabled - */ - git_vector_foreach(&diff->deltas, t, tgt) { - if (is_rename_source(diff, &opts, t, sigcache)) - ++num_srcs; - - if (is_rename_target(diff, &opts, t, sigcache)) - ++num_tgts; - - if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) - num_rewrites++; - } - - /* if there are no candidate srcs or tgts, we're done */ - if (!num_srcs || !num_tgts) - goto cleanup; - - src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); - GIT_ERROR_CHECK_ALLOC(src2tgt); - tgt2src = git__calloc(num_deltas, sizeof(diff_find_match)); - GIT_ERROR_CHECK_ALLOC(tgt2src); - - if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { - tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match)); - GIT_ERROR_CHECK_ALLOC(tgt2src_copy); - } - - /* - * Find best-fit matches for rename / copy candidates - */ - -find_best_matches: - tried_tgts = num_bumped = 0; - - git_vector_foreach(&diff->deltas, t, tgt) { - /* skip things that are not rename targets */ - if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) - continue; - - tried_srcs = 0; - - git_vector_foreach(&diff->deltas, s, src) { - /* skip things that are not rename sources */ - if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) - continue; - - /* calculate similarity for this pair and find best match */ - if (s == t) - result = -1; /* don't measure self-similarity here */ - else if ((error = similarity_measure( - &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) - goto cleanup; - - if (result < 0) - continue; - similarity = (uint16_t)result; - - /* is this a better rename? */ - if (tgt2src[t].similarity < similarity && - src2tgt[s].similarity < similarity) - { - /* eject old mapping */ - if (src2tgt[s].similarity > 0) { - tgt2src[src2tgt[s].idx].similarity = 0; - num_bumped++; - } - if (tgt2src[t].similarity > 0) { - src2tgt[tgt2src[t].idx].similarity = 0; - num_bumped++; - } - - /* write new mapping */ - tgt2src[t].idx = s; - tgt2src[t].similarity = similarity; - src2tgt[s].idx = t; - src2tgt[s].similarity = similarity; - } - - /* keep best absolute match for copies */ - if (tgt2src_copy != NULL && - tgt2src_copy[t].similarity < similarity) - { - tgt2src_copy[t].idx = s; - tgt2src_copy[t].similarity = similarity; - } - - if (++tried_srcs >= num_srcs) - break; - - /* cap on maximum targets we'll examine (per "tgt" file) */ - if (tried_srcs > opts.rename_limit) - break; - } - - if (++tried_tgts >= num_tgts) - break; - } - - if (num_bumped > 0) /* try again if we bumped some items */ - goto find_best_matches; - - /* - * Rewrite the diffs with renames / copies - */ - - git_vector_foreach(&diff->deltas, t, tgt) { - /* skip things that are not rename targets */ - if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) - continue; - - /* check if this delta was the target of a similarity */ - if (tgt2src[t].similarity) - best_match = &tgt2src[t]; - else if (tgt2src_copy && tgt2src_copy[t].similarity) - best_match = &tgt2src_copy[t]; - else - continue; - - s = best_match->idx; - src = GIT_VECTOR_GET(&diff->deltas, s); - - /* possible scenarios: - * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME - * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE - * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME - * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT - * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY - */ - - if (src->status == GIT_DELTA_DELETED) { - - if (delta_is_new_only(tgt)) { - - if (best_match->similarity < opts.rename_threshold) - continue; - - delta_make_rename(tgt, src, best_match->similarity); - - src->flags |= GIT_DIFF_FLAG__TO_DELETE; - num_rewrites++; - } else { - GIT_ASSERT(delta_is_split(tgt)); - - if (best_match->similarity < opts.rename_from_rewrite_threshold) - continue; - - memcpy(&swap, &tgt->old_file, sizeof(swap)); - - delta_make_rename(tgt, src, best_match->similarity); - num_rewrites--; - - GIT_ASSERT(src->status == GIT_DELTA_DELETED); - memcpy(&src->old_file, &swap, sizeof(src->old_file)); - memset(&src->new_file, 0, sizeof(src->new_file)); - src->new_file.path = src->old_file.path; - src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - num_updates++; - - if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { - /* what used to be at src t is now at src s */ - tgt2src[src2tgt[t].idx].idx = s; - } - } - } - - else if (delta_is_split(src)) { - - if (delta_is_new_only(tgt)) { - - if (best_match->similarity < opts.rename_threshold) - continue; - - delta_make_rename(tgt, src, best_match->similarity); - - 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)); - src->old_file.path = src->new_file.path; - src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - num_rewrites--; - - num_updates++; - } else { - GIT_ASSERT(delta_is_split(src)); - - if (best_match->similarity < opts.rename_from_rewrite_threshold) - continue; - - memcpy(&swap, &tgt->old_file, sizeof(swap)); - - delta_make_rename(tgt, src, best_match->similarity); - num_rewrites--; - num_updates++; - - memcpy(&src->old_file, &swap, sizeof(src->old_file)); - - /* if we've just swapped the new element into the correct - * place, clear the SPLIT and RENAME_TARGET flags - */ - if (tgt2src[s].idx == t && - tgt2src[s].similarity > - opts.rename_from_rewrite_threshold) { - src->status = GIT_DELTA_RENAMED; - src->similarity = tgt2src[s].similarity; - tgt2src[s].similarity = 0; - src->flags &= ~(GIT_DIFF_FLAG__TO_SPLIT | GIT_DIFF_FLAG__IS_RENAME_TARGET); - num_rewrites--; - } - /* otherwise, if we just overwrote a source, update mapping */ - else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { - /* what used to be at src t is now at src s */ - tgt2src[src2tgt[t].idx].idx = s; - } - - num_updates++; - } - } - - else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { - if (tgt2src_copy[t].similarity < opts.copy_threshold) - continue; - - /* always use best possible source for copy */ - best_match = &tgt2src_copy[t]; - src = GIT_VECTOR_GET(&diff->deltas, best_match->idx); - - if (delta_is_split(tgt)) { - error = insert_delete_side_of_split(diff, &diff->deltas, tgt); - if (error < 0) - goto cleanup; - num_rewrites--; - } - - if (!delta_is_split(tgt) && !delta_is_new_only(tgt)) - continue; - - tgt->status = GIT_DELTA_COPIED; - tgt->similarity = best_match->similarity; - tgt->nfiles = 2; - memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); - tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - - num_updates++; - } - } - - /* - * Actually split and delete entries as needed - */ - - if (num_rewrites > 0 || num_updates > 0) - error = apply_splits_and_deletes( - diff, diff->deltas.length - num_rewrites, - FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && - !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); - -cleanup: - git__free(tgt2src); - git__free(src2tgt); - git__free(tgt2src_copy); - - if (sigcache) { - for (t = 0; t < num_deltas * 2; ++t) { - if (sigcache[t] != NULL) - opts.metric->free_signature(sigcache[t], opts.metric->payload); - } - git__free(sigcache); - } - - if (!given_opts || !given_opts->metric) - git__free(opts.metric); - - return error; -} - -#undef FLAG_SET diff --git a/src/diff_tform.h b/src/diff_tform.h deleted file mode 100644 index 7abb8b3fe..000000000 --- a/src/diff_tform.h +++ /dev/null @@ -1,25 +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_diff_tform_h__ -#define INCLUDE_diff_tform_h__ - -#include "common.h" - -#include "diff_file.h" - -extern int git_diff_find_similar__hashsig_for_file( - void **out, const git_diff_file *f, const char *path, void *p); - -extern int git_diff_find_similar__hashsig_for_buf( - void **out, const git_diff_file *f, const char *buf, size_t len, void *p); - -extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); - -extern int git_diff_find_similar__calc_similarity( - int *score, void *siga, void *sigb, void *payload); - -#endif diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c deleted file mode 100644 index 3f6eccac1..000000000 --- a/src/diff_xdiff.c +++ /dev/null @@ -1,260 +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 "diff_xdiff.h" - -#include "git2/errors.h" -#include "diff.h" -#include "diff_driver.h" -#include "patch_generate.h" - -static int git_xdiff_scan_int(const char **str, int *value) -{ - const char *scan = *str; - int v = 0, digits = 0; - /* find next digit */ - for (scan = *str; *scan && !git__isdigit(*scan); scan++); - /* parse next number */ - for (; git__isdigit(*scan); scan++, digits++) - v = (v * 10) + (*scan - '0'); - *str = scan; - *value = v; - return (digits > 0) ? 0 : -1; -} - -static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) -{ - /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ - if (*header != '@') - goto fail; - if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) - goto fail; - if (*header == ',') { - if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) - goto fail; - } else - hunk->old_lines = 1; - if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) - goto fail; - if (*header == ',') { - if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) - goto fail; - } else - hunk->new_lines = 1; - if (hunk->old_start < 0 || hunk->new_start < 0) - goto fail; - - return 0; - -fail: - git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff"); - return -1; -} - -typedef struct { - git_xdiff_output *xo; - git_patch_generated *patch; - git_diff_hunk hunk; - int old_lineno, new_lineno; - mmfile_t xd_old_data, xd_new_data; -} git_xdiff_info; - -static int diff_update_lines( - git_xdiff_info *info, - git_diff_line *line, - const char *content, - size_t content_len) -{ - const char *scan = content, *scan_end = content + content_len; - - for (line->num_lines = 0; scan < scan_end; ++scan) - if (*scan == '\n') - ++line->num_lines; - - line->content = content; - line->content_len = content_len; - - /* expect " "/"-"/"+", then data */ - switch (line->origin) { - case GIT_DIFF_LINE_ADDITION: - case GIT_DIFF_LINE_DEL_EOFNL: - line->old_lineno = -1; - line->new_lineno = info->new_lineno; - info->new_lineno += (int)line->num_lines; - break; - case GIT_DIFF_LINE_DELETION: - case GIT_DIFF_LINE_ADD_EOFNL: - line->old_lineno = info->old_lineno; - line->new_lineno = -1; - info->old_lineno += (int)line->num_lines; - break; - case GIT_DIFF_LINE_CONTEXT: - case GIT_DIFF_LINE_CONTEXT_EOFNL: - line->old_lineno = info->old_lineno; - line->new_lineno = info->new_lineno; - info->old_lineno += (int)line->num_lines; - info->new_lineno += (int)line->num_lines; - break; - default: - git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x", - (unsigned int)line->origin); - return -1; - } - - return 0; -} - -static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) -{ - git_xdiff_info *info = priv; - git_patch_generated *patch = info->patch; - const git_diff_delta *delta = patch->base.delta; - git_patch_generated_output *output = &info->xo->output; - git_diff_line line; - size_t buffer_len; - - if (len == 1) { - output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); - if (output->error < 0) - return output->error; - - info->hunk.header_len = bufs[0].size; - if (info->hunk.header_len >= sizeof(info->hunk.header)) - info->hunk.header_len = sizeof(info->hunk.header) - 1; - - /* Sanitize the hunk header in case there is invalid Unicode */ - buffer_len = git_utf8_valid_buf_length(bufs[0].ptr, info->hunk.header_len); - /* Sanitizing the hunk header may delete the newline, so add it back again if there is room */ - if (buffer_len < info->hunk.header_len) { - bufs[0].ptr[buffer_len] = '\n'; - buffer_len += 1; - info->hunk.header_len = buffer_len; - } - - memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); - info->hunk.header[info->hunk.header_len] = '\0'; - - if (output->hunk_cb != NULL && - (output->error = output->hunk_cb( - delta, &info->hunk, output->payload))) - return output->error; - - info->old_lineno = info->hunk.old_start; - info->new_lineno = info->hunk.new_start; - } - - if (len == 2 || len == 3) { - /* expect " "/"-"/"+", then data */ - line.origin = - (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : - (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : - GIT_DIFF_LINE_CONTEXT; - - if (line.origin == GIT_DIFF_LINE_ADDITION) - line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; - else if (line.origin == GIT_DIFF_LINE_DELETION) - line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; - else - line.content_offset = -1; - - output->error = diff_update_lines( - info, &line, bufs[1].ptr, bufs[1].size); - - if (!output->error && output->data_cb != NULL) - output->error = output->data_cb( - delta, &info->hunk, &line, output->payload); - } - - if (len == 3 && !output->error) { - /* If we have a '+' and a third buf, then we have added a line - * without a newline and the old code had one, so DEL_EOFNL. - * If we have a '-' and a third buf, then we have removed a line - * with out a newline but added a blank line, so ADD_EOFNL. - */ - line.origin = - (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : - (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : - GIT_DIFF_LINE_CONTEXT_EOFNL; - - line.content_offset = -1; - - output->error = diff_update_lines( - info, &line, bufs[2].ptr, bufs[2].size); - - if (!output->error && output->data_cb != NULL) - output->error = output->data_cb( - delta, &info->hunk, &line, output->payload); - } - - return output->error; -} - -static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) -{ - git_xdiff_output *xo = (git_xdiff_output *)output; - git_xdiff_info info; - git_diff_find_context_payload findctxt; - - memset(&info, 0, sizeof(info)); - info.patch = patch; - info.xo = xo; - - xo->callback.priv = &info; - - git_diff_find_context_init( - &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); - xo->config.find_func_priv = &findctxt; - - if (xo->config.find_func != NULL) - xo->config.flags |= XDL_EMIT_FUNCNAMES; - else - xo->config.flags &= ~XDL_EMIT_FUNCNAMES; - - /* TODO: check ofile.opts_flags to see if driver-specific per-file - * updates are needed to xo->params.flags - */ - - if (git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch) < 0 || - git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch) < 0) - return -1; - - xdl_diff(&info.xd_old_data, &info.xd_new_data, - &xo->params, &xo->config, &xo->callback); - - git_diff_find_context_clear(&findctxt); - - return xo->output.error; -} - -void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) -{ - uint32_t flags = opts ? opts->flags : 0; - - xo->output.diff_cb = git_xdiff; - - xo->config.ctxlen = opts ? opts->context_lines : 3; - xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0; - - if (flags & GIT_DIFF_IGNORE_WHITESPACE) - xo->params.flags |= XDF_WHITESPACE_FLAGS; - if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) - xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; - if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) - xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; - if (flags & GIT_DIFF_INDENT_HEURISTIC) - xo->params.flags |= XDF_INDENT_HEURISTIC; - - if (flags & GIT_DIFF_PATIENCE) - xo->params.flags |= XDF_PATIENCE_DIFF; - if (flags & GIT_DIFF_MINIMAL) - xo->params.flags |= XDF_NEED_MINIMAL; - - if (flags & GIT_DIFF_IGNORE_BLANK_LINES) - xo->params.flags |= XDF_IGNORE_BLANK_LINES; - - xo->callback.out_line = git_xdiff_cb; -} diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h deleted file mode 100644 index 9b303e9dc..000000000 --- a/src/diff_xdiff.h +++ /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. - */ -#ifndef INCLUDE_diff_xdiff_h__ -#define INCLUDE_diff_xdiff_h__ - -#include "common.h" - -#include "diff.h" -#include "xdiff/xdiff.h" -#include "patch_generate.h" - -/* xdiff cannot cope with large files. these files should not be passed to - * xdiff. callers should treat these large files as binary. - */ -#define GIT_XDIFF_MAX_SIZE (INT64_C(1024) * 1024 * 1023) - -/* A git_xdiff_output is a git_patch_generate_output with extra fields - * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb - * field of the output to use xdiff to generate the diffs. - */ -typedef struct { - git_patch_generated_output output; - - xdemitconf_t config; - xpparam_t params; - xdemitcb_t callback; -} git_xdiff_output; - -void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts); - -#endif diff --git a/src/email.c b/src/email.c deleted file mode 100644 index e19a2928c..000000000 --- a/src/email.c +++ /dev/null @@ -1,315 +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 "email.h" - -#include "common.h" -#include "buf.h" -#include "diff_generate.h" -#include "diff_stats.h" -#include "patch.h" -#include "date.h" - -#include "git2/email.h" -#include "git2/patch.h" -#include "git2/version.h" - -/* - * Git uses a "magic" timestamp to indicate that an email message - * is from `git format-patch` (or our equivalent). - */ -#define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001" - -GIT_INLINE(int) include_prefix( - size_t patch_count, - git_email_create_options *opts) -{ - return ((!opts->subject_prefix || *opts->subject_prefix) || - (opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || - opts->reroll_number || - (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))); -} - -static int append_prefix( - git_str *out, - size_t patch_idx, - size_t patch_count, - git_email_create_options *opts) -{ - const char *subject_prefix = opts->subject_prefix ? - opts->subject_prefix : "PATCH"; - - git_str_putc(out, '['); - - if (*subject_prefix) - git_str_puts(out, subject_prefix); - - if (opts->reroll_number) { - if (*subject_prefix) - git_str_putc(out, ' '); - - git_str_printf(out, "v%" PRIuZ, opts->reroll_number); - } - - if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || - (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) { - size_t start_number = opts->start_number ? - opts->start_number : 1; - - if (*subject_prefix || opts->reroll_number) - git_str_putc(out, ' '); - - git_str_printf(out, "%" PRIuZ "/%" PRIuZ, - patch_idx + (start_number - 1), - patch_count + (start_number - 1)); - } - - git_str_puts(out, "]"); - - return git_str_oom(out) ? -1 : 0; -} - -static int append_date( - git_str *out, - const git_time *date) -{ - int error; - - if ((error = git_str_printf(out, "Date: ")) == 0 && - (error = git_date_rfc2822_fmt(out, date->time, date->offset)) == 0) - error = git_str_putc(out, '\n'); - - return error; -} - -static int append_subject( - git_str *out, - size_t patch_idx, - size_t patch_count, - const char *summary, - git_email_create_options *opts) -{ - bool prefix = include_prefix(patch_count, opts); - size_t summary_len = summary ? strlen(summary) : 0; - int error; - - if (summary_len) { - const char *nl = strchr(summary, '\n'); - - if (nl) - summary_len = (nl - summary); - } - - if ((error = git_str_puts(out, "Subject: ")) < 0) - return error; - - if (prefix && - (error = append_prefix(out, patch_idx, patch_count, opts)) < 0) - return error; - - if (prefix && summary_len && (error = git_str_putc(out, ' ')) < 0) - return error; - - if (summary_len && - (error = git_str_put(out, summary, summary_len)) < 0) - return error; - - return git_str_putc(out, '\n'); -} - -static int append_header( - git_str *out, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const git_signature *author, - git_email_create_options *opts) -{ - char id[GIT_OID_HEXSZ]; - int error; - - if ((error = git_oid_fmt(id, commit_id)) < 0 || - (error = git_str_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 || - (error = git_str_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 || - (error = append_date(out, &author->when)) < 0 || - (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0) - return error; - - if ((error = git_str_putc(out, '\n')) < 0) - return error; - - return 0; -} - -static int append_body(git_str *out, const char *body) -{ - size_t body_len; - int error; - - if (!body) - return 0; - - body_len = strlen(body); - - if ((error = git_str_puts(out, body)) < 0) - return error; - - if (body_len && body[body_len - 1] != '\n') - error = git_str_putc(out, '\n'); - - return error; -} - -static int append_diffstat(git_str *out, git_diff *diff) -{ - git_diff_stats *stats = NULL; - unsigned int format_flags; - int error; - - format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY; - - if ((error = git_diff_get_stats(&stats, diff)) == 0 && - (error = git_diff__stats_to_buf(out, stats, format_flags, 0)) == 0) - error = git_str_putc(out, '\n'); - - git_diff_stats_free(stats); - return error; -} - -static int append_patches(git_str *out, git_diff *diff) -{ - size_t i, deltas; - int error = 0; - - deltas = git_diff_num_deltas(diff); - - for (i = 0; i < deltas; ++i) { - git_patch *patch = NULL; - - if ((error = git_patch_from_diff(&patch, diff, i)) >= 0) - error = git_patch__to_buf(out, patch); - - git_patch_free(patch); - - if (error < 0) - break; - } - - return error; -} - -int git_email__append_from_diff( - git_str *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *given_opts) -{ - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count); - GIT_ASSERT_ARG(commit_id); - GIT_ASSERT_ARG(author); - - GIT_ERROR_CHECK_VERSION(given_opts, - GIT_EMAIL_CREATE_OPTIONS_VERSION, - "git_email_create_options"); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_email_create_options)); - - if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 && - (error = append_body(out, body)) == 0 && - (error = git_str_puts(out, "---\n")) == 0 && - (error = append_diffstat(out, diff)) == 0 && - (error = append_patches(out, diff)) == 0) - error = git_str_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n"); - - return error; -} - -int git_email_create_from_diff( - git_buf *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *given_opts) -{ - git_str email = GIT_STR_INIT; - int error; - - git_buf_tostr(&email, out); - - error = git_email__append_from_diff(&email, diff, patch_idx, - patch_count, commit_id, summary, body, author, - given_opts); - - if (error == 0) - error = git_buf_fromstr(out, &email); - - git_str_dispose(&email); - return error; -} - -int git_email_create_from_commit( - git_buf *out, - git_commit *commit, - const git_email_create_options *given_opts) -{ - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - git_diff *diff = NULL; - git_repository *repo; - git_diff_options *diff_opts; - git_diff_find_options *find_opts; - const git_signature *author; - const char *summary, *body; - const git_oid *commit_id; - int error = -1; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(commit); - - GIT_ERROR_CHECK_VERSION(given_opts, - GIT_EMAIL_CREATE_OPTIONS_VERSION, - "git_email_create_options"); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_email_create_options)); - - repo = git_commit_owner(commit); - author = git_commit_author(commit); - summary = git_commit_summary(commit); - body = git_commit_body(commit); - commit_id = git_commit_id(commit); - diff_opts = &opts.diff_opts; - find_opts = &opts.diff_find_opts; - - if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) - goto done; - - if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 && - (error = git_diff_find_similar(diff, find_opts)) < 0) - goto done; - - error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts); - -done: - git_diff_free(diff); - return error; -} diff --git a/src/email.h b/src/email.h deleted file mode 100644 index 083e56d5c..000000000 --- a/src/email.h +++ /dev/null @@ -1,25 +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_email_h__ -#define INCLUDE_email_h__ - -#include "common.h" - -#include "git2/email.h" - -extern int git_email__append_from_diff( - git_str *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *given_opts); - -#endif diff --git a/src/errors.c b/src/errors.c deleted file mode 100644 index 3614b9ce5..000000000 --- a/src/errors.c +++ /dev/null @@ -1,238 +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 "common.h" - -#include "threadstate.h" -#include "posix.h" -#include "str.h" -#include "libgit2.h" - -/******************************************** - * New error handling - ********************************************/ - -static git_error g_git_oom_error = { - "Out of memory", - GIT_ERROR_NOMEMORY -}; - -static git_error g_git_uninitialized_error = { - "libgit2 has not been initialized; you must call git_libgit2_init", - GIT_ERROR_INVALID -}; - -static void set_error_from_buffer(int error_class) -{ - git_error *error = &GIT_THREADSTATE->error_t; - git_str *buf = &GIT_THREADSTATE->error_buf; - - error->message = buf->ptr; - error->klass = error_class; - - GIT_THREADSTATE->last_error = error; -} - -static void set_error(int error_class, char *string) -{ - git_str *buf = &GIT_THREADSTATE->error_buf; - - git_str_clear(buf); - if (string) { - git_str_puts(buf, string); - git__free(string); - } - - set_error_from_buffer(error_class); -} - -void git_error_set_oom(void) -{ - GIT_THREADSTATE->last_error = &g_git_oom_error; -} - -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) -{ -#ifdef GIT_WIN32 - DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; -#endif - int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; - git_str *buf = &GIT_THREADSTATE->error_buf; - - git_str_clear(buf); - if (fmt) { - git_str_vprintf(buf, fmt, ap); - if (error_class == GIT_ERROR_OS) - git_str_PUTS(buf, ": "); - } - - if (error_class == GIT_ERROR_OS) { -#ifdef GIT_WIN32 - char * win32_error = git_win32_get_error_message(win32_error_code); - if (win32_error) { - git_str_puts(buf, win32_error); - git__free(win32_error); - - SetLastError(0); - } - else -#endif - if (error_code) - git_str_puts(buf, strerror(error_code)); - - if (error_code) - errno = 0; - } - - if (!git_str_oom(buf)) - set_error_from_buffer(error_class); -} - -int git_error_set_str(int error_class, const char *string) -{ - git_str *buf = &GIT_THREADSTATE->error_buf; - - GIT_ASSERT_ARG(string); - - git_str_clear(buf); - git_str_puts(buf, string); - - if (git_str_oom(buf)) - return -1; - - set_error_from_buffer(error_class); - return 0; -} - -void git_error_clear(void) -{ - if (GIT_THREADSTATE->last_error != NULL) { - set_error(0, NULL); - GIT_THREADSTATE->last_error = NULL; - } - - errno = 0; -#ifdef GIT_WIN32 - SetLastError(0); -#endif -} - -const git_error *git_error_last(void) -{ - /* If the library is not initialized, return a static error. */ - if (!git_libgit2_init_count()) - return &g_git_uninitialized_error; - - return GIT_THREADSTATE->last_error; -} - -int git_error_state_capture(git_error_state *state, int error_code) -{ - git_error *error = GIT_THREADSTATE->last_error; - git_str *error_buf = &GIT_THREADSTATE->error_buf; - - memset(state, 0, sizeof(git_error_state)); - - if (!error_code) - return 0; - - state->error_code = error_code; - state->oom = (error == &g_git_oom_error); - - if (error) { - state->error_msg.klass = error->klass; - - if (state->oom) - state->error_msg.message = g_git_oom_error.message; - else - state->error_msg.message = git_str_detach(error_buf); - } - - git_error_clear(); - return error_code; -} - -int git_error_state_restore(git_error_state *state) -{ - int ret = 0; - - git_error_clear(); - - if (state && state->error_msg.message) { - if (state->oom) - git_error_set_oom(); - else - set_error(state->error_msg.klass, state->error_msg.message); - - ret = state->error_code; - memset(state, 0, sizeof(git_error_state)); - } - - return ret; -} - -void git_error_state_free(git_error_state *state) -{ - if (!state) - return; - - if (!state->oom) - git__free(state->error_msg.message); - - memset(state, 0, sizeof(git_error_state)); -} - -int git_error_system_last(void) -{ -#ifdef GIT_WIN32 - return GetLastError(); -#else - return errno; -#endif -} - -void git_error_system_set(int code) -{ -#ifdef GIT_WIN32 - SetLastError(code); -#else - errno = code; -#endif -} - -/* Deprecated error values and functions */ - -#ifndef GIT_DEPRECATE_HARD -const git_error *giterr_last(void) -{ - return git_error_last(); -} - -void giterr_clear(void) -{ - git_error_clear(); -} - -void giterr_set_str(int error_class, const char *string) -{ - git_error_set_str(error_class, string); -} - -void giterr_set_oom(void) -{ - git_error_set_oom(); -} -#endif diff --git a/src/errors.h b/src/errors.h deleted file mode 100644 index a2f60f752..000000000 --- a/src/errors.h +++ /dev/null @@ -1,81 +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_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 f920135da..fbf0cab60 100644 --- a/src/features.h.in +++ b/src/features.h.in @@ -46,8 +46,17 @@ #cmakedefine GIT_SHA1_WIN32 1 #cmakedefine GIT_SHA1_COMMON_CRYPTO 1 #cmakedefine GIT_SHA1_OPENSSL 1 +#cmakedefine GIT_SHA1_OPENSSL_DYNAMIC 1 #cmakedefine GIT_SHA1_MBEDTLS 1 +#cmakedefine GIT_SHA256_BUILTIN 1 +#cmakedefine GIT_SHA256_WIN32 1 +#cmakedefine GIT_SHA256_COMMON_CRYPTO 1 +#cmakedefine GIT_SHA256_OPENSSL 1 +#cmakedefine GIT_SHA256_OPENSSL_DYNAMIC 1 +#cmakedefine GIT_SHA256_MBEDTLS 1 + #cmakedefine GIT_RAND_GETENTROPY 1 +#cmakedefine GIT_RAND_GETLOADAVG 1 #endif diff --git a/src/fetch.c b/src/fetch.c deleted file mode 100644 index e9f30d9bc..000000000 --- a/src/fetch.c +++ /dev/null @@ -1,214 +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 "fetch.h" - -#include "git2/oid.h" -#include "git2/refs.h" -#include "git2/revwalk.h" -#include "git2/transport.h" -#include "git2/sys/remote.h" - -#include "remote.h" -#include "refspec.h" -#include "pack.h" -#include "netops.h" -#include "repository.h" -#include "refs.h" - -static int maybe_want(git_remote *remote, git_remote_head *head, git_refspec *tagspec, git_remote_autotag_option_t tagopt) -{ - int match = 0, valid; - - if (git_reference_name_is_valid(&valid, head->name) < 0) - return -1; - - if (!valid) - return 0; - - if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { - /* - * If tagopt is --tags, always request tags - * in addition to the remote's refspecs - */ - if (git_refspec_src_matches(tagspec, head->name)) - match = 1; - } - - if (!match && git_remote__matching_refspec(remote, head->name)) - match = 1; - - if (!match) - return 0; - - return git_vector_insert(&remote->refs, head); -} - -static int mark_local(git_remote *remote) -{ - git_remote_head *head; - git_odb *odb; - size_t i; - - if (git_repository_odb__weakptr(&odb, remote->repo) < 0) - return -1; - - git_vector_foreach(&remote->refs, i, head) { - /* If we have the object, mark it so we don't ask for it */ - if (git_odb_exists(odb, &head->oid)) - head->local = 1; - else - remote->need_pack = 1; - } - - return 0; -} - -static int maybe_want_oid(git_remote *remote, git_refspec *spec) -{ - git_remote_head *oid_head; - - oid_head = git__calloc(1, sizeof(git_remote_head)); - GIT_ERROR_CHECK_ALLOC(oid_head); - - git_oid_fromstr(&oid_head->oid, spec->src); - - if (spec->dst) { - oid_head->name = git__strdup(spec->dst); - GIT_ERROR_CHECK_ALLOC(oid_head->name); - } - - if (git_vector_insert(&remote->local_heads, oid_head) < 0 || - git_vector_insert(&remote->refs, oid_head) < 0) - return -1; - - return 0; -} - -static int filter_wants(git_remote *remote, const git_fetch_options *opts) -{ - git_remote_head **heads; - git_refspec tagspec, head, *spec; - int error = 0; - git_odb *odb; - size_t i, heads_len; - unsigned int remote_caps; - unsigned int oid_mask = GIT_REMOTE_CAPABILITY_TIP_OID | - GIT_REMOTE_CAPABILITY_REACHABLE_OID; - git_remote_autotag_option_t tagopt = remote->download_tags; - - if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) - tagopt = opts->download_tags; - - git_vector_clear(&remote->refs); - if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0) - return error; - - /* - * The fetch refspec can be NULL, and what this means is that the - * user didn't specify one. This is fine, as it means that we're - * not interested in any particular branch but just the remote's - * HEAD, which will be stored in FETCH_HEAD after the fetch. - */ - if (remote->active_refspecs.length == 0) { - if ((error = git_refspec__parse(&head, "HEAD", true)) < 0) - goto cleanup; - - error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs); - git_refspec__dispose(&head); - - if (error < 0) - goto cleanup; - } - - if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) - goto cleanup; - - if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0 || - (error = git_remote_capabilities(&remote_caps, remote)) < 0) - goto cleanup; - - /* Handle remote heads */ - for (i = 0; i < heads_len; i++) { - if ((error = maybe_want(remote, heads[i], &tagspec, tagopt)) < 0) - goto cleanup; - } - - /* Handle explicitly specified OID specs */ - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (!git_oid__is_hexstr(spec->src)) - continue; - - if (!(remote_caps & oid_mask)) { - git_error_set(GIT_ERROR_INVALID, "cannot fetch a specific object from the remote repository"); - error = -1; - goto cleanup; - } - - if ((error = maybe_want_oid(remote, spec)) < 0) - goto cleanup; - } - - error = mark_local(remote); - -cleanup: - git_refspec__dispose(&tagspec); - - return error; -} - -/* - * In this first version, we push all our refs in and start sending - * them out. When we get an ACK we hide that commit and continue - * traversing until we're done - */ -int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts) -{ - git_transport *t = remote->transport; - - remote->need_pack = 0; - - if (filter_wants(remote, opts) < 0) - return -1; - - /* Don't try to negotiate when we don't want anything */ - if (!remote->need_pack) - return 0; - - /* - * Now we have everything set up so we can start tell the - * server what we want and what we have. - */ - return t->negotiate_fetch(t, - remote->repo, - (const git_remote_head * const *)remote->refs.contents, - remote->refs.length); -} - -int git_fetch_download_pack(git_remote *remote) -{ - git_transport *t = remote->transport; - - if (!remote->need_pack) - return 0; - - return t->download_pack(t, remote->repo, &remote->stats); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_fetch_init_options(git_fetch_options *opts, unsigned int version) -{ - return git_fetch_options_init(opts, version); -} -#endif diff --git a/src/fetch.h b/src/fetch.h deleted file mode 100644 index 10b6731f0..000000000 --- a/src/fetch.h +++ /dev/null @@ -1,22 +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_fetch_h__ -#define INCLUDE_fetch_h__ - -#include "common.h" - -#include "git2/remote.h" - -#include "netops.h" - -int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts); - -int git_fetch_download_pack(git_remote *remote); - -int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); - -#endif diff --git a/src/fetchhead.c b/src/fetchhead.c deleted file mode 100644 index 6511124ef..000000000 --- a/src/fetchhead.c +++ /dev/null @@ -1,338 +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 "fetchhead.h" - -#include "git2/types.h" -#include "git2/oid.h" - -#include "str.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) -{ - const git_fetchhead_ref *one = (const git_fetchhead_ref *)a; - const git_fetchhead_ref *two = (const git_fetchhead_ref *)b; - - if (one->is_merge && !two->is_merge) - return -1; - if (two->is_merge && !one->is_merge) - return 1; - - if (one->ref_name && two->ref_name) - return strcmp(one->ref_name, two->ref_name); - else if (one->ref_name) - return -1; - else if (two->ref_name) - return 1; - - 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_str buf = GIT_STR_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_str_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, - unsigned int is_merge, - const char *ref_name, - const char *remote_url) -{ - git_fetchhead_ref *fetchhead_ref; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(oid); - - *out = NULL; - - fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref)); - GIT_ERROR_CHECK_ALLOC(fetchhead_ref); - - memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref)); - - git_oid_cpy(&fetchhead_ref->oid, oid); - fetchhead_ref->is_merge = is_merge; - - 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 = sanitized_remote_url(remote_url); - GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url); - } - - *out = fetchhead_ref; - - return 0; -} - -static int fetchhead_ref_write( - git_filebuf *file, - git_fetchhead_ref *fetchhead_ref) -{ - char oid[GIT_OID_HEXSZ + 1]; - const char *type, *name; - int head = 0; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(fetchhead_ref); - - git_oid_fmt(oid, &fetchhead_ref->oid); - oid[GIT_OID_HEXSZ] = '\0'; - - if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) { - type = "branch "; - name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR); - } else if(git__prefixcmp(fetchhead_ref->ref_name, - GIT_REFS_TAGS_DIR) == 0) { - type = "tag "; - name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR); - } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) { - head = 1; - } else { - type = ""; - name = fetchhead_ref->ref_name; - } - - if (head) - return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url); - - return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n", - oid, - (fetchhead_ref->is_merge) ? "" : "not-for-merge", - type, - name, - fetchhead_ref->remote_url); -} - -int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str path = GIT_STR_INIT; - unsigned int i; - git_fetchhead_ref *fetchhead_ref; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(fetchhead_refs); - - if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) - return -1; - - if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) { - git_str_dispose(&path); - return -1; - } - - git_str_dispose(&path); - - git_vector_sort(fetchhead_refs); - - git_vector_foreach(fetchhead_refs, i, fetchhead_ref) - fetchhead_ref_write(&file, fetchhead_ref); - - return git_filebuf_commit(&file); -} - -static int fetchhead_ref_parse( - git_oid *oid, - unsigned int *is_merge, - git_str *ref_name, - const char **remote_url, - char *line, - size_t line_num) -{ - char *oid_str, *is_merge_str, *desc, *name = NULL; - const char *type = NULL; - int error = 0; - - *remote_url = NULL; - - if (!*line) { - git_error_set(GIT_ERROR_FETCHHEAD, - "empty line in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */ - if ((oid_str = git__strsep(&line, "\t")) == NULL) { - oid_str = line; - line += strlen(line); - - *is_merge = 1; - } - - if (strlen(oid_str) != GIT_OID_HEXSZ) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid object ID in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if (git_oid_fromstr(oid, oid_str) < 0) { - const git_error *oid_err = git_error_last(); - const char *err_msg = oid_err ? oid_err->message : "invalid object ID"; - - git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ, - err_msg, line_num); - return -1; - } - - /* Parse new data from newer git clients */ - if (*line) { - if ((is_merge_str = git__strsep(&line, "\t")) == NULL) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid description data in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if (*is_merge_str == '\0') - *is_merge = 1; - else if (strcmp(is_merge_str, "not-for-merge") == 0) - *is_merge = 0; - else { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if ((desc = line) == NULL) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid description in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if (git__prefixcmp(desc, "branch '") == 0) { - type = GIT_REFS_HEADS_DIR; - name = desc + 8; - } else if (git__prefixcmp(desc, "tag '") == 0) { - type = GIT_REFS_TAGS_DIR; - name = desc + 5; - } else if (git__prefixcmp(desc, "'") == 0) - name = desc + 1; - - if (name) { - if ((desc = strstr(name, "' ")) == NULL || - git__prefixcmp(desc, "' of ") != 0) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid description in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - *desc = '\0'; - desc += 5; - } - - *remote_url = desc; - } - - git_str_clear(ref_name); - - if (type) - git_str_join(ref_name, '/', type, name); - else if(name) - git_str_puts(ref_name, name); - - return error; -} - -int git_repository_fetchhead_foreach(git_repository *repo, - git_repository_fetchhead_foreach_cb cb, - void *payload) -{ - git_str path = GIT_STR_INIT, file = GIT_STR_INIT, name = GIT_STR_INIT; - const char *ref_name; - git_oid oid; - const char *remote_url; - unsigned int is_merge = 0; - char *buffer, *line; - size_t line_num = 0; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cb); - - if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) - return -1; - - if ((error = git_futils_readbuffer(&file, git_str_cstr(&path))) < 0) - goto done; - - buffer = file.ptr; - - while ((line = git__strsep(&buffer, "\n")) != NULL) { - ++line_num; - - if ((error = fetchhead_ref_parse( - &oid, &is_merge, &name, &remote_url, line, line_num)) < 0) - goto done; - - if (git_str_len(&name) > 0) - ref_name = git_str_cstr(&name); - else - ref_name = NULL; - - error = cb(ref_name, remote_url, &oid, is_merge, payload); - if (error) { - git_error_set_after_callback(error); - goto done; - } - } - - if (*buffer) { - git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1); - error = -1; - goto done; - } - -done: - git_str_dispose(&file); - git_str_dispose(&path); - git_str_dispose(&name); - - return error; -} - -void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref) -{ - if (fetchhead_ref == NULL) - return; - - git__free(fetchhead_ref->remote_url); - git__free(fetchhead_ref->ref_name); - git__free(fetchhead_ref); -} - diff --git a/src/fetchhead.h b/src/fetchhead.h deleted file mode 100644 index 9e5171010..000000000 --- a/src/fetchhead.h +++ /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. - */ -#ifndef INCLUDE_fetchhead_h__ -#define INCLUDE_fetchhead_h__ - -#include "common.h" - -#include "oid.h" -#include "vector.h" - -typedef struct git_fetchhead_ref { - git_oid oid; - unsigned int is_merge; - char *ref_name; - char *remote_url; -} git_fetchhead_ref; - -int git_fetchhead_ref_create( - git_fetchhead_ref **fetchhead_ref_out, - git_oid *oid, - unsigned int is_merge, - const char *ref_name, - const char *remote_url); - -int git_fetchhead_ref_cmp(const void *a, const void *b); - -int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs); - -void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref); - -#endif diff --git a/src/filebuf.c b/src/filebuf.c deleted file mode 100644 index eafcba3bd..000000000 --- a/src/filebuf.c +++ /dev/null @@ -1,595 +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 "filebuf.h" - -#include "futils.h" - -static const size_t WRITE_BUFFER_SIZE = (4096 * 2); - -enum buferr_t { - BUFERR_OK = 0, - BUFERR_WRITE, - BUFERR_ZLIB, - BUFERR_MEM -}; - -#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } - -static int verify_last_error(git_filebuf *file) -{ - switch (file->last_error) { - case BUFERR_WRITE: - git_error_set(GIT_ERROR_OS, "failed to write out file"); - return -1; - - case BUFERR_MEM: - git_error_set_oom(); - return -1; - - case BUFERR_ZLIB: - git_error_set(GIT_ERROR_ZLIB, - "Buffer error when writing out ZLib data"); - return -1; - - default: - return 0; - } -} - -static int lock_file(git_filebuf *file, int flags, mode_t mode) -{ - if (git_fs_path_exists(file->path_lock) == true) { - 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_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 { - file->fd = git_futils_creat_locked(file->path_lock, mode); - } - - if (file->fd < 0) - return file->fd; - - file->fd_is_open = true; - - if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { - git_file source; - char buffer[FILEIO_BUFSIZE]; - ssize_t read_bytes; - int error = 0; - - source = p_open(file->path_original, O_RDONLY); - if (source < 0) { - git_error_set(GIT_ERROR_OS, - "failed to open file '%s' for reading", - file->path_original); - return -1; - } - - while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { - if ((error = p_write(file->fd, buffer, read_bytes)) < 0) - break; - if (file->compute_digest) - git_hash_update(&file->digest, buffer, read_bytes); - } - - p_close(source); - - if (read_bytes < 0) { - git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); - return -1; - } else if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); - return -1; - } - } - - return 0; -} - -void git_filebuf_cleanup(git_filebuf *file) -{ - if (file->fd_is_open && file->fd >= 0) - p_close(file->fd); - - if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) - p_unlink(file->path_lock); - - if (file->compute_digest) { - git_hash_ctx_cleanup(&file->digest); - file->compute_digest = 0; - } - - if (file->buffer) - git__free(file->buffer); - - /* use the presence of z_buf to decide if we need to deflateEnd */ - if (file->z_buf) { - git__free(file->z_buf); - deflateEnd(&file->zs); - } - - if (file->path_original) - git__free(file->path_original); - if (file->path_lock) - git__free(file->path_lock); - - memset(file, 0x0, sizeof(git_filebuf)); - file->fd = -1; -} - -GIT_INLINE(int) flush_buffer(git_filebuf *file) -{ - int result = file->write(file, file->buffer, file->buf_pos); - file->buf_pos = 0; - return result; -} - -int git_filebuf_flush(git_filebuf *file) -{ - return flush_buffer(file); -} - -static int write_normal(git_filebuf *file, void *source, size_t len) -{ - if (len > 0) { - if (p_write(file->fd, (void *)source, len) < 0) { - file->last_error = BUFERR_WRITE; - return -1; - } - - if (file->compute_digest) - git_hash_update(&file->digest, source, len); - } - - return 0; -} - -static int write_deflate(git_filebuf *file, void *source, size_t len) -{ - z_stream *zs = &file->zs; - - if (len > 0 || file->flush_mode == Z_FINISH) { - zs->next_in = source; - zs->avail_in = (uInt)len; - - do { - size_t have; - - zs->next_out = file->z_buf; - zs->avail_out = (uInt)file->buf_size; - - if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { - file->last_error = BUFERR_ZLIB; - return -1; - } - - have = file->buf_size - (size_t)zs->avail_out; - - if (p_write(file->fd, file->z_buf, have) < 0) { - file->last_error = BUFERR_WRITE; - return -1; - } - - } while (zs->avail_out == 0); - - GIT_ASSERT(zs->avail_in == 0); - - if (file->compute_digest) - git_hash_update(&file->digest, source, len); - } - - return 0; -} - -#define MAX_SYMLINK_DEPTH 5 - -static int resolve_symlink(git_str *out, const char *path) -{ - int i, error, root; - ssize_t ret; - struct stat st; - git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; - - if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || - (error = git_str_puts(&curpath, path)) < 0) - return error; - - for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { - error = p_lstat(curpath.ptr, &st); - if (error < 0 && errno == ENOENT) { - error = git_str_puts(out, curpath.ptr); - goto cleanup; - } - - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); - error = -1; - goto cleanup; - } - - if (!S_ISLNK(st.st_mode)) { - error = git_str_puts(out, curpath.ptr); - goto cleanup; - } - - ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); - if (ret < 0) { - git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); - error = -1; - goto cleanup; - } - - if (ret == GIT_PATH_MAX) { - git_error_set(GIT_ERROR_INVALID, "symlink target too long"); - error = -1; - goto cleanup; - } - - /* readlink(2) won't NUL-terminate for us */ - target.ptr[ret] = '\0'; - target.size = ret; - - root = git_fs_path_root(target.ptr); - if (root >= 0) { - if ((error = git_str_sets(&curpath, target.ptr)) < 0) - goto cleanup; - } else { - git_str dir = GIT_STR_INIT; - - if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) - goto cleanup; - - git_str_swap(&curpath, &dir); - git_str_dispose(&dir); - - if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) - goto cleanup; - } - } - - git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); - error = -1; - -cleanup: - git_str_dispose(&curpath); - git_str_dispose(&target); - return error; -} - -int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) -{ - return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); -} - -int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) -{ - int compression, error = -1; - size_t path_len, alloc_len; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(path); - GIT_ASSERT(file->buffer == NULL); - - memset(file, 0x0, sizeof(git_filebuf)); - - if (flags & GIT_FILEBUF_DO_NOT_BUFFER) - file->do_not_buffer = true; - - if (flags & GIT_FILEBUF_FSYNC) - file->do_fsync = true; - - file->buf_size = size; - file->buf_pos = 0; - file->fd = -1; - file->last_error = BUFERR_OK; - - /* Allocate the main cache buffer */ - if (!file->do_not_buffer) { - file->buffer = git__malloc(file->buf_size); - GIT_ERROR_CHECK_ALLOC(file->buffer); - } - - /* If we are hashing on-write, allocate a new hash context */ - if (flags & GIT_FILEBUF_HASH_CONTENTS) { - file->compute_digest = 1; - - if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) - goto cleanup; - } - - compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; - - /* If we are deflating on-write, */ - if (compression != 0) { - /* Initialize the ZLib stream */ - if (deflateInit(&file->zs, compression) != Z_OK) { - git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); - goto cleanup; - } - - /* Allocate the Zlib cache buffer */ - file->z_buf = git__malloc(file->buf_size); - GIT_ERROR_CHECK_ALLOC(file->z_buf); - - /* Never flush */ - file->flush_mode = Z_NO_FLUSH; - file->write = &write_deflate; - } else { - file->write = &write_normal; - } - - /* If we are writing to a temp file */ - if (flags & GIT_FILEBUF_TEMPORARY) { - git_str tmp_path = GIT_STR_INIT; - - /* Open the file as temporary for locking */ - file->fd = git_futils_mktmp(&tmp_path, path, mode); - - if (file->fd < 0) { - git_str_dispose(&tmp_path); - goto cleanup; - } - file->fd_is_open = true; - file->created_lock = true; - - /* No original path */ - file->path_original = NULL; - file->path_lock = git_str_detach(&tmp_path); - GIT_ERROR_CHECK_ALLOC(file->path_lock); - } else { - git_str resolved_path = GIT_STR_INIT; - - if ((error = resolve_symlink(&resolved_path, path)) < 0) - goto cleanup; - - /* Save the original path of the file */ - path_len = resolved_path.size; - file->path_original = git_str_detach(&resolved_path); - - /* create the locking path by appending ".lock" to the original */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); - file->path_lock = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(file->path_lock); - - memcpy(file->path_lock, file->path_original, path_len); - memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); - - if (git_fs_path_isdir(file->path_original)) { - git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); - error = GIT_EDIRECTORY; - goto cleanup; - } - - /* open the file for locking */ - if ((error = lock_file(file, flags, mode)) < 0) - goto cleanup; - - file->created_lock = true; - } - - return 0; - -cleanup: - git_filebuf_cleanup(file); - return error; -} - -int git_filebuf_hash(unsigned char *out, git_filebuf *file) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(file->compute_digest); - - flush_buffer(file); - - if (verify_last_error(file) < 0) - return -1; - - git_hash_final(out, &file->digest); - git_hash_ctx_cleanup(&file->digest); - file->compute_digest = 0; - - return 0; -} - -int git_filebuf_commit_at(git_filebuf *file, const char *path) -{ - git__free(file->path_original); - file->path_original = git__strdup(path); - GIT_ERROR_CHECK_ALLOC(file->path_original); - - return git_filebuf_commit(file); -} - -int git_filebuf_commit(git_filebuf *file) -{ - /* temporary files cannot be committed */ - GIT_ASSERT_ARG(file); - GIT_ASSERT(file->path_original); - - file->flush_mode = Z_FINISH; - flush_buffer(file); - - if (verify_last_error(file) < 0) - goto on_error; - - file->fd_is_open = false; - - if (file->do_fsync && p_fsync(file->fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); - goto on_error; - } - - if (p_close(file->fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); - goto on_error; - } - - file->fd = -1; - - if (p_rename(file->path_lock, file->path_original) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); - goto on_error; - } - - if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) - goto on_error; - - file->did_rename = true; - - git_filebuf_cleanup(file); - return 0; - -on_error: - git_filebuf_cleanup(file); - return -1; -} - -GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) -{ - memcpy(file->buffer + file->buf_pos, buf, len); - file->buf_pos += len; -} - -int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) -{ - const unsigned char *buf = buff; - - ENSURE_BUF_OK(file); - - if (file->do_not_buffer) - return file->write(file, (void *)buff, len); - - for (;;) { - size_t space_left = file->buf_size - file->buf_pos; - - /* cache if it's small */ - if (space_left > len) { - add_to_cache(file, buf, len); - return 0; - } - - add_to_cache(file, buf, space_left); - if (flush_buffer(file) < 0) - return -1; - - len -= space_left; - buf += space_left; - } -} - -int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) -{ - size_t space_left = file->buf_size - file->buf_pos; - - *buffer = NULL; - - ENSURE_BUF_OK(file); - - if (len > file->buf_size) { - file->last_error = BUFERR_MEM; - return -1; - } - - if (space_left <= len) { - if (flush_buffer(file) < 0) - return -1; - } - - *buffer = (file->buffer + file->buf_pos); - file->buf_pos += len; - - return 0; -} - -int git_filebuf_printf(git_filebuf *file, const char *format, ...) -{ - va_list arglist; - size_t space_left, len, alloclen; - int written, res; - char *tmp_buffer; - - ENSURE_BUF_OK(file); - - space_left = file->buf_size - file->buf_pos; - - do { - va_start(arglist, format); - written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); - va_end(arglist); - - if (written < 0) { - file->last_error = BUFERR_MEM; - return -1; - } - - len = written; - if (len + 1 <= space_left) { - file->buf_pos += len; - return 0; - } - - if (flush_buffer(file) < 0) - return -1; - - space_left = file->buf_size - file->buf_pos; - - } while (len + 1 <= space_left); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || - !(tmp_buffer = git__malloc(alloclen))) { - file->last_error = BUFERR_MEM; - return -1; - } - - va_start(arglist, format); - written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); - va_end(arglist); - - if (written < 0) { - git__free(tmp_buffer); - file->last_error = BUFERR_MEM; - return -1; - } - - res = git_filebuf_write(file, tmp_buffer, len); - git__free(tmp_buffer); - - return res; -} - -int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) -{ - int res; - struct stat st; - - if (file->fd_is_open) - res = p_fstat(file->fd, &st); - else - res = p_stat(file->path_original, &st); - - if (res < 0) { - git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", - file->path_original); - return res; - } - - if (mtime) - *mtime = st.st_mtime; - if (size) - *size = (size_t)st.st_size; - - return 0; -} diff --git a/src/filebuf.h b/src/filebuf.h deleted file mode 100644 index adbb19936..000000000 --- a/src/filebuf.h +++ /dev/null @@ -1,94 +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_filebuf_h__ -#define INCLUDE_filebuf_h__ - -#include "common.h" - -#include "futils.h" -#include "hash.h" -#include - -#ifdef GIT_THREADS -# define GIT_FILEBUF_THREADS -#endif - -#define GIT_FILEBUF_HASH_CONTENTS (1 << 0) -#define GIT_FILEBUF_APPEND (1 << 2) -#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) -#define GIT_FILEBUF_DEFLATE_SHIFT (7) - -#define GIT_FILELOCK_EXTENSION ".lock\0" -#define GIT_FILELOCK_EXTLENGTH 6 - -typedef struct git_filebuf git_filebuf; -struct git_filebuf { - char *path_original; - char *path_lock; - - int (*write)(git_filebuf *file, void *source, size_t len); - - bool compute_digest; - git_hash_ctx digest; - - unsigned char *buffer; - unsigned char *z_buf; - - z_stream zs; - int flush_mode; - - size_t buf_size, buf_pos; - git_file fd; - bool fd_is_open; - bool created_lock; - bool did_rename; - bool do_not_buffer; - bool do_fsync; - int last_error; -}; - -#define GIT_FILEBUF_INIT {0} - -/* - * The git_filebuf object lifecycle is: - * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. - * - * - Call git_filebuf_open() to initialize the filebuf for use. - * - * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), - * git_filebuf_reserve() as you like. The error codes for these - * functions don't need to be checked. They are stored internally - * by the file buffer. - * - * - While you are writing, you may call git_filebuf_hash() to get - * the hash of all you have written so far. This function will - * fail if any of the previous writes to the buffer failed. - * - * - To close the git_filebuf, you may call git_filebuf_commit() or - * git_filebuf_commit_at() to save the file, or - * git_filebuf_cleanup() to abandon the file. All of these will - * free the git_filebuf object. Likewise, all of these will fail - * if any of the previous writes to the buffer failed, and set - * an error code accordingly. - */ -int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); -int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); -int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); - -int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); -int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); -int git_filebuf_commit(git_filebuf *lock); -int git_filebuf_commit_at(git_filebuf *lock, const char *path); -void git_filebuf_cleanup(git_filebuf *lock); -int git_filebuf_hash(unsigned char *out, git_filebuf *file); -int git_filebuf_flush(git_filebuf *file); -int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); - -#endif diff --git a/src/filter.c b/src/filter.c deleted file mode 100644 index 2712e8c60..000000000 --- a/src/filter.c +++ /dev/null @@ -1,1191 +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 "filter.h" - -#include "buf.h" -#include "common.h" -#include "futils.h" -#include "hash.h" -#include "repository.h" -#include "runtime.h" -#include "git2/sys/filter.h" -#include "git2/config.h" -#include "blob.h" -#include "attr_file.h" -#include "array.h" -#include "path.h" - -struct git_filter_source { - git_repository *repo; - const char *path; - git_oid oid; /* zero if unknown (which is likely) */ - uint16_t filemode; /* zero if unknown */ - git_filter_mode_t mode; - git_filter_options options; -}; - -typedef struct { - const char *filter_name; - git_filter *filter; - void *payload; -} git_filter_entry; - -struct git_filter_list { - git_array_t(git_filter_entry) filters; - git_filter_source source; - git_str *temp_buf; - char path[GIT_FLEX_ARRAY]; -}; - -typedef struct { - char *filter_name; - git_filter *filter; - int priority; - int initialized; - size_t nattrs, nmatches; - char *attrdata; - const char *attrs[GIT_FLEX_ARRAY]; -} git_filter_def; - -static int filter_def_priority_cmp(const void *a, const void *b) -{ - int pa = ((const git_filter_def *)a)->priority; - int pb = ((const git_filter_def *)b)->priority; - return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; -} - -struct git_filter_registry { - git_rwlock lock; - git_vector filters; -}; - -static struct git_filter_registry filter_registry; - -static void git_filter_global_shutdown(void); - - -static int filter_def_scan_attrs( - git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str) -{ - const char *start, *scan = attr_str; - int has_eq; - - *nattr = *nmatch = 0; - - if (!scan) - return 0; - - while (*scan) { - while (git__isspace(*scan)) scan++; - - for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) { - if (*scan == '=') - has_eq = 1; - } - - if (scan > start) { - (*nattr)++; - if (has_eq || *start == '-' || *start == '+' || *start == '!') - (*nmatch)++; - - if (has_eq) - git_str_putc(attrs, '='); - git_str_put(attrs, start, scan - start); - git_str_putc(attrs, '\0'); - } - } - - return 0; -} - -static void filter_def_set_attrs(git_filter_def *fdef) -{ - char *scan = fdef->attrdata; - size_t i; - - for (i = 0; i < fdef->nattrs; ++i) { - const char *name, *value; - - switch (*scan) { - case '=': - name = scan + 1; - for (scan++; *scan != '='; scan++) /* find '=' */; - *scan++ = '\0'; - value = scan; - break; - case '-': - name = scan + 1; value = git_attr__false; break; - case '+': - name = scan + 1; value = git_attr__true; break; - case '!': - name = scan + 1; value = git_attr__unset; break; - default: - name = scan; value = NULL; break; - } - - fdef->attrs[i] = name; - fdef->attrs[i + fdef->nattrs] = value; - - scan += strlen(scan) + 1; - } -} - -static int filter_def_name_key_check(const void *key, const void *fdef) -{ - const char *name = - fdef ? ((const git_filter_def *)fdef)->filter_name : NULL; - return name ? git__strcmp(key, name) : -1; -} - -static int filter_def_filter_key_check(const void *key, const void *fdef) -{ - const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; - return (key == filter) ? 0 : -1; -} - -/* Note: callers must lock the registry before calling this function */ -static int filter_registry_insert( - const char *name, git_filter *filter, int priority) -{ - git_filter_def *fdef; - size_t nattr = 0, nmatch = 0, alloc_len; - git_str attrs = GIT_STR_INIT; - - if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0) - return -1; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *)); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def)); - - fdef = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(fdef); - - fdef->filter_name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(fdef->filter_name); - - fdef->filter = filter; - fdef->priority = priority; - fdef->nattrs = nattr; - fdef->nmatches = nmatch; - fdef->attrdata = git_str_detach(&attrs); - - filter_def_set_attrs(fdef); - - if (git_vector_insert(&filter_registry.filters, fdef) < 0) { - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - return -1; - } - - git_vector_sort(&filter_registry.filters); - return 0; -} - -int git_filter_global_init(void) -{ - git_filter *crlf = NULL, *ident = NULL; - int error = 0; - - if (git_rwlock_init(&filter_registry.lock) < 0) - return -1; - - if ((error = git_vector_init(&filter_registry.filters, 2, - filter_def_priority_cmp)) < 0) - goto done; - - if ((crlf = git_crlf_filter_new()) == NULL || - filter_registry_insert( - GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 || - (ident = git_ident_filter_new()) == NULL || - filter_registry_insert( - GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) - error = -1; - - if (!error) - error = git_runtime_shutdown_register(git_filter_global_shutdown); - -done: - if (error) { - git_filter_free(crlf); - git_filter_free(ident); - } - - return error; -} - -static void git_filter_global_shutdown(void) -{ - size_t pos; - git_filter_def *fdef; - - if (git_rwlock_wrlock(&filter_registry.lock) < 0) - return; - - git_vector_foreach(&filter_registry.filters, pos, fdef) { - if (fdef->filter && fdef->filter->shutdown) { - fdef->filter->shutdown(fdef->filter); - fdef->initialized = false; - } - - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - } - - git_vector_free(&filter_registry.filters); - - git_rwlock_wrunlock(&filter_registry.lock); - git_rwlock_free(&filter_registry.lock); -} - -/* Note: callers must lock the registry before calling this function */ -static int filter_registry_find(size_t *pos, const char *name) -{ - return git_vector_search2( - pos, &filter_registry.filters, filter_def_name_key_check, name); -} - -/* Note: callers must lock the registry before calling this function */ -static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) -{ - git_filter_def *fdef = NULL; - - if (!filter_registry_find(pos, name)) - fdef = git_vector_get(&filter_registry.filters, *pos); - - return fdef; -} - - -int git_filter_register( - const char *name, git_filter *filter, int priority) -{ - int error; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(filter); - - if (git_rwlock_wrlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - if (!filter_registry_find(NULL, name)) { - git_error_set( - GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name); - error = GIT_EEXISTS; - goto done; - } - - error = filter_registry_insert(name, filter, priority); - -done: - git_rwlock_wrunlock(&filter_registry.lock); - return error; -} - -int git_filter_unregister(const char *name) -{ - size_t pos; - git_filter_def *fdef; - int error = 0; - - GIT_ASSERT_ARG(name); - - /* cannot unregister default filters */ - if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { - git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name); - return -1; - } - - if (git_rwlock_wrlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { - git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name); - error = GIT_ENOTFOUND; - goto done; - } - - git_vector_remove(&filter_registry.filters, pos); - - if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { - fdef->filter->shutdown(fdef->filter); - fdef->initialized = false; - } - - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - -done: - git_rwlock_wrunlock(&filter_registry.lock); - return error; -} - -static int filter_initialize(git_filter_def *fdef) -{ - int error = 0; - - if (!fdef->initialized && fdef->filter && fdef->filter->initialize) { - if ((error = fdef->filter->initialize(fdef->filter)) < 0) - return error; - } - - fdef->initialized = true; - return 0; -} - -git_filter *git_filter_lookup(const char *name) -{ - size_t pos; - git_filter_def *fdef; - git_filter *filter = NULL; - - if (git_rwlock_rdlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return NULL; - } - - if ((fdef = filter_registry_lookup(&pos, name)) == NULL || - (!fdef->initialized && filter_initialize(fdef) < 0)) - goto done; - - filter = fdef->filter; - -done: - git_rwlock_rdunlock(&filter_registry.lock); - return filter; -} - -void git_filter_free(git_filter *filter) -{ - git__free(filter); -} - -git_repository *git_filter_source_repo(const git_filter_source *src) -{ - return src->repo; -} - -const char *git_filter_source_path(const git_filter_source *src) -{ - return src->path; -} - -uint16_t git_filter_source_filemode(const git_filter_source *src) -{ - return src->filemode; -} - -const git_oid *git_filter_source_id(const git_filter_source *src) -{ - return git_oid_is_zero(&src->oid) ? NULL : &src->oid; -} - -git_filter_mode_t git_filter_source_mode(const git_filter_source *src) -{ - return src->mode; -} - -uint32_t git_filter_source_flags(const git_filter_source *src) -{ - return src->options.flags; -} - -static int filter_list_new( - git_filter_list **out, const git_filter_source *src) -{ - git_filter_list *fl = NULL; - size_t pathlen = src->path ? strlen(src->path) : 0, alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - fl = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(fl); - - if (src->path) - memcpy(fl->path, src->path, pathlen); - fl->source.repo = src->repo; - fl->source.path = fl->path; - fl->source.mode = src->mode; - - memcpy(&fl->source.options, &src->options, sizeof(git_filter_options)); - - *out = fl; - return 0; -} - -static int filter_list_check_attributes( - const char ***out, - git_repository *repo, - git_filter_session *filter_session, - git_filter_def *fdef, - const git_filter_source *src) -{ - const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); - git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT; - size_t i; - int error; - - GIT_ERROR_CHECK_ALLOC(strs); - - if ((src->options.flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) - attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM; - - if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0) - attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD; - - if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { - attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT; - -#ifndef GIT_DEPRECATE_HARD - if (src->options.commit_id) - git_oid_cpy(&attr_opts.attr_commit_id, src->options.commit_id); - else -#endif - git_oid_cpy(&attr_opts.attr_commit_id, &src->options.attr_commit_id); - } - - error = git_attr_get_many_with_session( - strs, repo, filter_session->attr_session, &attr_opts, 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) { - git_error_clear(); - git__free((void *)strs); - return 0; - } - - for (i = 0; !error && i < fdef->nattrs; ++i) { - const char *want = fdef->attrs[fdef->nattrs + i]; - git_attr_value_t want_type, found_type; - - if (!want) - continue; - - want_type = git_attr_value(want); - found_type = git_attr_value(strs[i]); - - if (want_type != found_type) - error = GIT_ENOTFOUND; - else if (want_type == GIT_ATTR_VALUE_STRING && - strcmp(want, strs[i]) && - strcmp(want, "*")) - error = GIT_ENOTFOUND; - } - - if (error) - git__free((void *)strs); - else - *out = strs; - - return error; -} - -int git_filter_list_new( - git_filter_list **out, - git_repository *repo, - git_filter_mode_t mode, - uint32_t flags) -{ - git_filter_source src = { 0 }; - src.repo = repo; - src.path = NULL; - src.mode = mode; - src.options.flags = flags; - return filter_list_new(out, &src); -} - -int git_filter_list__load( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - git_filter_session *filter_session) -{ - int error = 0; - git_filter_list *fl = NULL; - git_filter_source src = { 0 }; - git_filter_entry *fe; - size_t idx; - git_filter_def *fdef; - - if (git_rwlock_rdlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - src.repo = repo; - src.path = path; - src.mode = mode; - - memcpy(&src.options, &filter_session->options, sizeof(git_filter_options)); - - if (blob) - git_oid_cpy(&src.oid, git_blob_id(blob)); - - git_vector_foreach(&filter_registry.filters, idx, fdef) { - const char **values = NULL; - void *payload = NULL; - - if (!fdef || !fdef->filter) - continue; - - if (fdef->nattrs > 0) { - error = filter_list_check_attributes( - &values, repo, - filter_session, fdef, &src); - - if (error == GIT_ENOTFOUND) { - error = 0; - continue; - } else if (error < 0) - break; - } - - if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) - break; - - if (fdef->filter->check) - error = fdef->filter->check( - fdef->filter, &payload, &src, values); - - git__free((void *)values); - - if (error == GIT_PASSTHROUGH) - error = 0; - else if (error < 0) - break; - else { - if (!fl) { - if ((error = filter_list_new(&fl, &src)) < 0) - break; - - fl->temp_buf = filter_session->temp_buf; - } - - fe = git_array_alloc(fl->filters); - GIT_ERROR_CHECK_ALLOC(fe); - - fe->filter = fdef->filter; - fe->filter_name = fdef->filter_name; - fe->payload = payload; - } - } - - git_rwlock_rdunlock(&filter_registry.lock); - - if (error && fl != NULL) { - git_array_clear(fl->filters); - git__free(fl); - fl = NULL; - } - - *filters = fl; - return error; -} - -int git_filter_list_load_ext( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - git_filter_options *opts) -{ - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - - if (opts) - memcpy(&filter_session.options, opts, sizeof(git_filter_options)); - - return git_filter_list__load( - filters, repo, blob, path, mode, &filter_session); -} - -int git_filter_list_load( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - uint32_t flags) -{ - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - - filter_session.options.flags = flags; - - return git_filter_list__load( - filters, repo, blob, path, mode, &filter_session); -} - -void git_filter_list_free(git_filter_list *fl) -{ - uint32_t i; - - if (!fl) - return; - - for (i = 0; i < git_array_size(fl->filters); ++i) { - git_filter_entry *fe = git_array_get(fl->filters, i); - if (fe->filter->cleanup) - fe->filter->cleanup(fe->filter, fe->payload); - } - - git_array_clear(fl->filters); - git__free(fl); -} - -int git_filter_list_contains( - git_filter_list *fl, - const char *name) -{ - size_t i; - - GIT_ASSERT_ARG(name); - - if (!fl) - return 0; - - for (i = 0; i < fl->filters.size; i++) { - if (strcmp(fl->filters.ptr[i].filter_name, name) == 0) - return 1; - } - - return 0; -} - -int git_filter_list_push( - git_filter_list *fl, git_filter *filter, void *payload) -{ - int error = 0; - size_t pos; - git_filter_def *fdef = NULL; - git_filter_entry *fe; - - GIT_ASSERT_ARG(fl); - GIT_ASSERT_ARG(filter); - - if (git_rwlock_rdlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - if (git_vector_search2( - &pos, &filter_registry.filters, - filter_def_filter_key_check, filter) == 0) - fdef = git_vector_get(&filter_registry.filters, pos); - - git_rwlock_rdunlock(&filter_registry.lock); - - if (fdef == NULL) { - git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter"); - return -1; - } - - if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) - return error; - - fe = git_array_alloc(fl->filters); - GIT_ERROR_CHECK_ALLOC(fe); - fe->filter = filter; - fe->payload = payload; - - return 0; -} - -size_t git_filter_list_length(const git_filter_list *fl) -{ - return fl ? git_array_size(fl->filters) : 0; -} - -struct buf_stream { - git_writestream parent; - git_str *target; - bool complete; -}; - -static int buf_stream_write( - git_writestream *s, const char *buffer, size_t len) -{ - struct buf_stream *buf_stream = (struct buf_stream *)s; - GIT_ASSERT_ARG(buf_stream); - GIT_ASSERT(buf_stream->complete == 0); - - return git_str_put(buf_stream->target, buffer, len); -} - -static int buf_stream_close(git_writestream *s) -{ - struct buf_stream *buf_stream = (struct buf_stream *)s; - GIT_ASSERT_ARG(buf_stream); - - GIT_ASSERT(buf_stream->complete == 0); - buf_stream->complete = 1; - - return 0; -} - -static void buf_stream_free(git_writestream *s) -{ - GIT_UNUSED(s); -} - -static void buf_stream_init(struct buf_stream *writer, git_str *target) -{ - memset(writer, 0, sizeof(struct buf_stream)); - - writer->parent.write = buf_stream_write; - writer->parent.close = buf_stream_close; - writer->parent.free = buf_stream_free; - writer->target = target; - - git_str_clear(target); -} - -int git_filter_list_apply_to_buffer( - git_buf *out, - git_filter_list *filters, - const char *in, - size_t in_len) -{ - GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len); -} - -int git_filter_list__apply_to_buffer( - git_str *out, - git_filter_list *filters, - const char *in, - size_t in_len) -{ - struct buf_stream writer; - int error; - - buf_stream_init(&writer, out); - - if ((error = git_filter_list_stream_buffer(filters, - in, in_len, &writer.parent)) < 0) - return error; - - GIT_ASSERT(writer.complete); - return error; -} - -int git_filter_list__convert_buf( - git_str *out, - git_filter_list *filters, - git_str *in) -{ - int error; - - if (!filters || git_filter_list_length(filters) == 0) { - git_str_swap(out, in); - git_str_dispose(in); - return 0; - } - - error = git_filter_list__apply_to_buffer(out, filters, - in->ptr, in->size); - - if (!error) - git_str_dispose(in); - - return error; -} - -int git_filter_list_apply_to_file( - git_buf *out, - git_filter_list *filters, - git_repository *repo, - const char *path) -{ - GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path); -} - -int git_filter_list__apply_to_file( - git_str *out, - git_filter_list *filters, - git_repository *repo, - const char *path) -{ - struct buf_stream writer; - int error; - - buf_stream_init(&writer, out); - - if ((error = git_filter_list_stream_file( - filters, repo, path, &writer.parent)) < 0) - return error; - - GIT_ASSERT(writer.complete); - return error; -} - -static int buf_from_blob(git_str *out, git_blob *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"); - return -1; - } - - git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize); - return 0; -} - -int git_filter_list_apply_to_blob( - git_buf *out, - git_filter_list *filters, - git_blob *blob) -{ - GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob); -} - -int git_filter_list__apply_to_blob( - git_str *out, - git_filter_list *filters, - git_blob *blob) -{ - struct buf_stream writer; - int error; - - buf_stream_init(&writer, out); - - if ((error = git_filter_list_stream_blob( - filters, blob, &writer.parent)) < 0) - return error; - - GIT_ASSERT(writer.complete); - return error; -} - -struct buffered_stream { - git_writestream parent; - git_filter *filter; - int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *); - int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *); - const git_filter_source *source; - void **payload; - git_str input; - git_str temp_buf; - git_str *output; - git_writestream *target; -}; - -static int buffered_stream_write( - git_writestream *s, const char *buffer, size_t len) -{ - struct buffered_stream *buffered_stream = (struct buffered_stream *)s; - GIT_ASSERT_ARG(buffered_stream); - - return git_str_put(&buffered_stream->input, buffer, len); -} - -static int buffered_stream_close(git_writestream *s) -{ - struct buffered_stream *buffered_stream = (struct buffered_stream *)s; - git_str *writebuf; - git_error_state error_state = {0}; - int error; - - GIT_ASSERT_ARG(buffered_stream); - - error = buffered_stream->write_fn( - buffered_stream->filter, - buffered_stream->payload, - buffered_stream->output, - &buffered_stream->input, - buffered_stream->source); - - if (error == GIT_PASSTHROUGH) { - writebuf = &buffered_stream->input; - } else if (error == 0) { - writebuf = buffered_stream->output; - } else { - /* close stream before erroring out taking care - * to preserve the original error */ - git_error_state_capture(&error_state, error); - buffered_stream->target->close(buffered_stream->target); - git_error_state_restore(&error_state); - return error; - } - - if ((error = buffered_stream->target->write( - buffered_stream->target, writebuf->ptr, writebuf->size)) == 0) - error = buffered_stream->target->close(buffered_stream->target); - - return error; -} - -static void buffered_stream_free(git_writestream *s) -{ - struct buffered_stream *buffered_stream = (struct buffered_stream *)s; - - if (buffered_stream) { - git_str_dispose(&buffered_stream->input); - git_str_dispose(&buffered_stream->temp_buf); - git__free(buffered_stream); - } -} - -int git_filter_buffered_stream_new( - git_writestream **out, - git_filter *filter, - int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), - git_str *temp_buf, - void **payload, - const git_filter_source *source, - git_writestream *target) -{ - struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); - GIT_ERROR_CHECK_ALLOC(buffered_stream); - - buffered_stream->parent.write = buffered_stream_write; - buffered_stream->parent.close = buffered_stream_close; - buffered_stream->parent.free = buffered_stream_free; - buffered_stream->filter = filter; - buffered_stream->write_fn = write_fn; - buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; - buffered_stream->payload = payload; - buffered_stream->source = source; - buffered_stream->target = target; - - if (temp_buf) - git_str_clear(temp_buf); - - *out = (git_writestream *)buffered_stream; - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -static int buffered_legacy_stream_new( - git_writestream **out, - git_filter *filter, - int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *), - git_str *temp_buf, - void **payload, - const git_filter_source *source, - git_writestream *target) -{ - struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); - GIT_ERROR_CHECK_ALLOC(buffered_stream); - - buffered_stream->parent.write = buffered_stream_write; - buffered_stream->parent.close = buffered_stream_close; - buffered_stream->parent.free = buffered_stream_free; - buffered_stream->filter = filter; - buffered_stream->legacy_write_fn = legacy_write_fn; - buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; - buffered_stream->payload = payload; - buffered_stream->source = source; - buffered_stream->target = target; - - if (temp_buf) - git_str_clear(temp_buf); - - *out = (git_writestream *)buffered_stream; - return 0; -} -#endif - -static int setup_stream( - git_writestream **out, - git_filter_entry *fe, - git_filter_list *filters, - git_writestream *last_stream) -{ -#ifndef GIT_DEPRECATE_HARD - GIT_ASSERT(fe->filter->stream || fe->filter->apply); - - /* - * If necessary, create a stream that proxies the traditional - * application. - */ - if (!fe->filter->stream) { - /* Create a stream that proxies the one-shot apply */ - return buffered_legacy_stream_new(out, - fe->filter, fe->filter->apply, filters->temp_buf, - &fe->payload, &filters->source, last_stream); - } -#endif - - GIT_ASSERT(fe->filter->stream); - return fe->filter->stream(out, fe->filter, - &fe->payload, &filters->source, last_stream); -} - -static int stream_list_init( - git_writestream **out, - git_vector *streams, - git_filter_list *filters, - git_writestream *target) -{ - git_writestream *last_stream = target; - size_t i; - int error = 0; - - *out = NULL; - - if (!filters) { - *out = target; - return 0; - } - - /* Create filters last to first to get the chaining direction */ - for (i = 0; i < git_array_size(filters->filters); ++i) { - size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ? - git_array_size(filters->filters) - 1 - i : i; - - git_filter_entry *fe = git_array_get(filters->filters, filter_idx); - git_writestream *filter_stream; - - error = setup_stream(&filter_stream, fe, filters, last_stream); - - if (error < 0) - goto out; - - git_vector_insert(streams, filter_stream); - last_stream = filter_stream; - } - -out: - if (error) - last_stream->close(last_stream); - else - *out = last_stream; - - return error; -} - -static void filter_streams_free(git_vector *streams) -{ - git_writestream *stream; - size_t i; - - git_vector_foreach(streams, i, stream) - stream->free(stream); - git_vector_free(streams); -} - -int git_filter_list_stream_file( - git_filter_list *filters, - git_repository *repo, - const char *path, - git_writestream *target) -{ - char buf[FILTERIO_BUFSIZE]; - git_str abspath = GIT_STR_INIT; - const char *base = repo ? git_repository_workdir(repo) : NULL; - git_vector filter_streams = GIT_VECTOR_INIT; - git_writestream *stream_start; - ssize_t readlen; - int fd = -1, error, initialized = 0; - - if ((error = stream_list_init( - &stream_start, &filter_streams, filters, target)) < 0 || - (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 || - (error = git_path_validate_str_length(repo, &abspath)) < 0) - goto done; - - initialized = 1; - - if ((fd = git_futils_open_ro(abspath.ptr)) < 0) { - error = fd; - goto done; - } - - while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) { - if ((error = stream_start->write(stream_start, buf, readlen)) < 0) - goto done; - } - - if (readlen < 0) - error = -1; - -done: - if (initialized) - error |= stream_start->close(stream_start); - - if (fd >= 0) - p_close(fd); - filter_streams_free(&filter_streams); - git_str_dispose(&abspath); - return error; -} - -int git_filter_list_stream_buffer( - git_filter_list *filters, - const char *buffer, - size_t len, - git_writestream *target) -{ - git_vector filter_streams = GIT_VECTOR_INIT; - git_writestream *stream_start; - int error, initialized = 0; - - if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0) - goto out; - initialized = 1; - - if ((error = stream_start->write(stream_start, buffer, len)) < 0) - goto out; - -out: - if (initialized) - error |= stream_start->close(stream_start); - - filter_streams_free(&filter_streams); - return error; -} - -int git_filter_list_stream_blob( - git_filter_list *filters, - git_blob *blob, - git_writestream *target) -{ - git_str in = GIT_STR_INIT; - - if (buf_from_blob(&in, blob) < 0) - return -1; - - if (filters) - git_oid_cpy(&filters->source.oid, git_blob_id(blob)); - - return git_filter_list_stream_buffer(filters, in.ptr, in.size, target); -} - -int git_filter_init(git_filter *filter, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD - -int git_filter_list_stream_data( - git_filter_list *filters, - git_buf *data, - git_writestream *target) -{ - return git_filter_list_stream_buffer(filters, data->ptr, data->size, target); -} - -int git_filter_list_apply_to_data( - git_buf *tgt, git_filter_list *filters, git_buf *src) -{ - return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size); -} - -#endif diff --git a/src/filter.h b/src/filter.h deleted file mode 100644 index 58cb4b424..000000000 --- a/src/filter.h +++ /dev/null @@ -1,85 +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_filter_h__ -#define INCLUDE_filter_h__ - -#include "common.h" - -#include "attr_file.h" -#include "git2/filter.h" -#include "git2/sys/filter.h" - -/* Amount of file to examine for NUL byte when checking binary-ness */ -#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 - -typedef struct { - git_filter_options options; - git_attr_session *attr_session; - git_str *temp_buf; -} git_filter_session; - -#define GIT_FILTER_SESSION_INIT {GIT_FILTER_OPTIONS_INIT, 0} - -extern int git_filter_global_init(void); - -extern void git_filter_free(git_filter *filter); - -extern int git_filter_list__load( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - git_filter_session *filter_session); - -int git_filter_list__apply_to_buffer( - git_str *out, - git_filter_list *filters, - const char *in, - size_t in_len); -int git_filter_list__apply_to_file( - git_str *out, - git_filter_list *filters, - git_repository *repo, - const char *path); -int git_filter_list__apply_to_blob( - git_str *out, - git_filter_list *filters, - git_blob *blob); - -/* - * The given input buffer will be converted to the given output buffer. - * The input buffer will be freed (_if_ it was allocated). - */ -extern int git_filter_list__convert_buf( - git_str *out, - git_filter_list *filters, - git_str *in); - -extern int git_filter_list__apply_to_file( - git_str *out, - git_filter_list *filters, - git_repository *repo, - const char *path); - -/* - * Available filters - */ - -extern git_filter *git_crlf_filter_new(void); -extern git_filter *git_ident_filter_new(void); - -extern int git_filter_buffered_stream_new( - git_writestream **out, - git_filter *filter, - int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), - git_str *temp_buf, - void **payload, - const git_filter_source *source, - git_writestream *target); - -#endif diff --git a/src/fs_path.c b/src/fs_path.c deleted file mode 100644 index a67617eca..000000000 --- a/src/fs_path.c +++ /dev/null @@ -1,2071 +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 "fs_path.h" - -#include "posix.h" -#include "repository.h" -#ifdef GIT_WIN32 -#include "win32/posix.h" -#include "win32/w32_buffer.h" -#include "win32/w32_util.h" -#include "win32/version.h" -#include -#else -#include -#endif -#include -#include - -static int dos_drive_prefix_length(const char *path) -{ - int i; - - /* - * Does it start with an ASCII letter (i.e. highest bit not set), - * followed by a colon? - */ - if (!(0x80 & (unsigned char)*path)) - return *path && path[1] == ':' ? 2 : 0; - - /* - * While drive letters must be letters of the English alphabet, it is - * possible to assign virtually _any_ Unicode character via `subst` as - * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff - * like this: - * - * subst ֍: %USERPROFILE%\Desktop - */ - for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) - ; /* skip first UTF-8 character */ - return path[i] == ':' ? i + 1 : 0; -} - -#ifdef GIT_WIN32 -static bool looks_like_network_computer_name(const char *path, int pos) -{ - if (pos < 3) - return false; - - if (path[0] != '/' || path[1] != '/') - return false; - - while (pos-- > 2) { - if (path[pos] == '/') - return false; - } - - return true; -} -#endif - -/* - * Based on the Android implementation, BSD licensed. - * http://android.git.kernel.org/ - * - * 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. - */ -int git_fs_path_basename_r(git_str *buffer, const char *path) -{ - const char *endp, *startp; - int len, result; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - startp = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* All slashes becomes "/" */ - if (endp == path && *endp == '/') { - startp = "/"; - len = 1; - goto Exit; - } - - /* Find the start of the base */ - startp = endp; - while (startp > path && *(startp - 1) != '/') - startp--; - - /* Cast is safe because max path < max int */ - len = (int)(endp - startp + 1); - -Exit: - result = len; - - if (buffer != NULL && git_str_set(buffer, startp, len) < 0) - return -1; - - return result; -} - -/* - * Determine if the path is a Windows prefix and, if so, returns - * its actual length. If it is not a prefix, returns -1. - */ -static int win32_prefix_length(const char *path, int len) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(path); - GIT_UNUSED(len); -#else - /* - * Mimic unix behavior where '/.git' returns '/': 'C:/.git' - * will return 'C:/' here - */ - if (dos_drive_prefix_length(path) == len) - return len; - - /* - * Similarly checks if we're dealing with a network computer name - * '//computername/.git' will return '//computername/' - */ - if (looks_like_network_computer_name(path, len)) - return len; -#endif - - return -1; -} - -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git_fs_path_dirname_r(git_str *buffer, const char *path) -{ - const char *endp; - int is_prefix = 0, len; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - path = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - 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; - } - - /* Find the start of the dir */ - while (endp > path && *endp != '/') - endp--; - - /* Either the dir is "/" or there are no slashes */ - if (endp == path) { - path = (*endp == '/') ? "/" : "."; - len = 1; - goto Exit; - } - - do { - endp--; - } while (endp > path && *endp == '/'); - - 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; - } - - /* Cast is safe because max path < max int */ - len = (int)(endp - path + 1); - -Exit: - if (buffer) { - if (git_str_set(buffer, path, len) < 0) - return -1; - if (is_prefix && git_str_putc(buffer, '/') < 0) - return -1; - } - - return len; -} - - -char *git_fs_path_dirname(const char *path) -{ - git_str buf = GIT_STR_INIT; - char *dirname; - - git_fs_path_dirname_r(&buf, path); - dirname = git_str_detach(&buf); - git_str_dispose(&buf); /* avoid memleak if error occurs */ - - return dirname; -} - -char *git_fs_path_basename(const char *path) -{ - git_str buf = GIT_STR_INIT; - char *basename; - - git_fs_path_basename_r(&buf, path); - basename = git_str_detach(&buf); - git_str_dispose(&buf); /* avoid memleak if error occurs */ - - return basename; -} - -size_t git_fs_path_basename_offset(git_str *buffer) -{ - ssize_t slash; - - if (!buffer || buffer->size <= 0) - return 0; - - slash = git_str_rfind_next(buffer, '/'); - - if (slash >= 0 && buffer->ptr[slash] == '/') - return (size_t)(slash + 1); - - return 0; -} - -int git_fs_path_root(const char *path) -{ - int offset = 0, prefix_len; - - /* Does the root of the path look like a windows drive ? */ - if ((prefix_len = dos_drive_prefix_length(path))) - offset += prefix_len; - -#ifdef GIT_WIN32 - /* Are we dealing with a windows network path? */ - else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || - (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) - { - offset += 2; - - /* Skip the computer name segment */ - while (path[offset] && path[offset] != '/' && path[offset] != '\\') - offset++; - } - - if (path[offset] == '\\') - return offset; -#endif - - if (path[offset] == '/') - return offset; - - return -1; /* Not a real error - signals that path is not rooted */ -} - -static void path_trim_slashes(git_str *path) -{ - int ceiling = git_fs_path_root(path->ptr) + 1; - - if (ceiling < 0) - return; - - while (path->size > (size_t)ceiling) { - if (path->ptr[path->size-1] != '/') - break; - - path->ptr[path->size-1] = '\0'; - path->size--; - } -} - -int git_fs_path_join_unrooted( - git_str *path_out, const char *path, const char *base, ssize_t *root_at) -{ - ssize_t root; - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(path); - - root = (ssize_t)git_fs_path_root(path); - - if (base != NULL && root < 0) { - if (git_str_joinpath(path_out, base, path) < 0) - return -1; - - root = (ssize_t)strlen(base); - } else { - if (git_str_sets(path_out, path) < 0) - return -1; - - if (root < 0) - root = 0; - else if (base) - git_fs_path_equal_or_prefixed(base, path, &root); - } - - if (root_at) - *root_at = root; - - return 0; -} - -void git_fs_path_squash_slashes(git_str *path) -{ - char *p, *q; - - if (path->size == 0) - return; - - for (p = path->ptr, q = path->ptr; *q; p++, q++) { - *p = *q; - - while (*q == '/' && *(q+1) == '/') { - path->size--; - q++; - } - } - - *p = '\0'; -} - -int git_fs_path_prettify(git_str *path_out, const char *path, const char *base) -{ - char buf[GIT_PATH_MAX]; - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(path); - - /* construct path if needed */ - if (base != NULL && git_fs_path_root(path) < 0) { - if (git_str_joinpath(path_out, base, path) < 0) - return -1; - path = path_out->ptr; - } - - if (p_realpath(path, buf) == NULL) { - /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */ - int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; - git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path); - - git_str_clear(path_out); - - return error; - } - - return git_str_sets(path_out, buf); -} - -int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base) -{ - int error = git_fs_path_prettify(path_out, path, base); - return (error < 0) ? error : git_fs_path_to_dir(path_out); -} - -int git_fs_path_to_dir(git_str *path) -{ - if (path->asize > 0 && - git_str_len(path) > 0 && - path->ptr[git_str_len(path) - 1] != '/') - git_str_putc(path, '/'); - - return git_str_oom(path) ? -1 : 0; -} - -void git_fs_path_string_to_dir(char *path, size_t size) -{ - size_t end = strlen(path); - - if (end && path[end - 1] != '/' && end < size) { - path[end] = '/'; - path[end + 1] = '\0'; - } -} - -int git__percent_decode(git_str *decoded_out, const char *input) -{ - int len, hi, lo, i; - - GIT_ASSERT_ARG(decoded_out); - GIT_ASSERT_ARG(input); - - len = (int)strlen(input); - git_str_clear(decoded_out); - - for(i = 0; i < len; i++) - { - char c = input[i]; - - if (c != '%') - goto append; - - if (i >= len - 2) - goto append; - - hi = git__fromhex(input[i + 1]); - lo = git__fromhex(input[i + 2]); - - if (hi < 0 || lo < 0) - goto append; - - c = (char)(hi << 4 | lo); - i += 2; - -append: - if (git_str_putc(decoded_out, c) < 0) - return -1; - } - - return 0; -} - -static int error_invalid_local_file_uri(const char *uri) -{ - git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri); - return -1; -} - -static int local_file_url_prefixlen(const char *file_url) -{ - int len = -1; - - if (git__prefixcmp(file_url, "file://") == 0) { - if (file_url[7] == '/') - len = 8; - else if (git__prefixcmp(file_url + 7, "localhost/") == 0) - len = 17; - } - - return len; -} - -bool git_fs_path_is_local_file_url(const char *file_url) -{ - return (local_file_url_prefixlen(file_url) > 0); -} - -int git_fs_path_fromurl(git_str *local_path_out, const char *file_url) -{ - int offset; - - GIT_ASSERT_ARG(local_path_out); - GIT_ASSERT_ARG(file_url); - - if ((offset = local_file_url_prefixlen(file_url)) < 0 || - file_url[offset] == '\0' || file_url[offset] == '/') - return error_invalid_local_file_uri(file_url); - -#ifndef GIT_WIN32 - offset--; /* A *nix absolute path starts with a forward slash */ -#endif - - git_str_clear(local_path_out); - return git__percent_decode(local_path_out, file_url + offset); -} - -int git_fs_path_walk_up( - git_str *path, - const char *ceiling, - int (*cb)(void *data, const char *), - void *data) -{ - int error = 0; - git_str iter; - ssize_t stop = 0, scan; - char oldc = '\0'; - - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(cb); - - if (ceiling != NULL) { - if (git__prefixcmp(path->ptr, ceiling) == 0) - stop = (ssize_t)strlen(ceiling); - else - stop = git_str_len(path); - } - scan = git_str_len(path); - - /* empty path: yield only once */ - if (!scan) { - error = cb(data, ""); - if (error) - git_error_set_after_callback(error); - return error; - } - - iter.ptr = path->ptr; - iter.size = git_str_len(path); - iter.asize = path->asize; - - while (scan >= stop) { - error = cb(data, iter.ptr); - iter.ptr[scan] = oldc; - - if (error) { - git_error_set_after_callback(error); - break; - } - - scan = git_str_rfind_next(&iter, '/'); - if (scan >= 0) { - scan++; - oldc = iter.ptr[scan]; - iter.size = scan; - iter.ptr[scan] = '\0'; - } - } - - if (scan >= 0) - iter.ptr[scan] = oldc; - - /* relative path: yield for the last component */ - if (!error && stop == 0 && iter.ptr[0] != '/') { - error = cb(data, ""); - if (error) - git_error_set_after_callback(error); - } - - return error; -} - -bool git_fs_path_exists(const char *path) -{ - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - return p_access(path, F_OK) == 0; -} - -bool git_fs_path_isdir(const char *path) -{ - struct stat st; - if (p_stat(path, &st) < 0) - return false; - - return S_ISDIR(st.st_mode) != 0; -} - -bool git_fs_path_isfile(const char *path) -{ - struct stat st; - - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - if (p_stat(path, &st) < 0) - return false; - - return S_ISREG(st.st_mode) != 0; -} - -bool git_fs_path_islink(const char *path) -{ - struct stat st; - - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - if (p_lstat(path, &st) < 0) - return false; - - return S_ISLNK(st.st_mode) != 0; -} - -#ifdef GIT_WIN32 - -bool git_fs_path_is_empty_dir(const char *path) -{ - git_win32_path filter_w; - bool empty = false; - - if (git_win32__findfirstfile_filter(filter_w, path)) { - WIN32_FIND_DATAW findData; - HANDLE hFind = FindFirstFileW(filter_w, &findData); - - /* FindFirstFile will fail if there are no children to the given - * path, which can happen if the given path is a file (and obviously - * has no children) or if the given path is an empty mount point. - * (Most directories have at least directory entries '.' and '..', - * but ridiculously another volume mounted in another drive letter's - * path space do not, and thus have nothing to enumerate.) If - * FindFirstFile fails, check if this is a directory-like thing - * (a mount point). - */ - if (hFind == INVALID_HANDLE_VALUE) - return git_fs_path_isdir(path); - - /* If the find handle was created successfully, then it's a directory */ - empty = true; - - do { - /* Allow the enumeration to return . and .. and still be considered - * empty. In the special case of drive roots (i.e. C:\) where . and - * .. do not occur, we can still consider the path to be an empty - * directory if there's nothing there. */ - if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) { - empty = false; - break; - } - } while (FindNextFileW(hFind, &findData)); - - FindClose(hFind); - } - - return empty; -} - -#else - -static int path_found_entry(void *payload, git_str *path) -{ - GIT_UNUSED(payload); - return !git_fs_path_is_dot_or_dotdot(path->ptr); -} - -bool git_fs_path_is_empty_dir(const char *path) -{ - int error; - git_str dir = GIT_STR_INIT; - - if (!git_fs_path_isdir(path)) - return false; - - if ((error = git_str_sets(&dir, path)) != 0) - git_error_clear(); - else - error = git_fs_path_direach(&dir, 0, path_found_entry, NULL); - - git_str_dispose(&dir); - - return !error; -} - -#endif - -int git_fs_path_set_error(int errno_value, const char *path, const char *action) -{ - switch (errno_value) { - case ENOENT: - case ENOTDIR: - git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action); - return GIT_ENOTFOUND; - - case EINVAL: - case ENAMETOOLONG: - git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path); - return GIT_EINVALIDSPEC; - - case EEXIST: - git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path); - return GIT_EEXISTS; - - case EACCES: - git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path); - return GIT_ELOCKED; - - default: - git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path); - return -1; - } -} - -int git_fs_path_lstat(const char *path, struct stat *st) -{ - if (p_lstat(path, st) == 0) - return 0; - - return git_fs_path_set_error(errno, path, "stat"); -} - -static bool _check_dir_contents( - git_str *dir, - const char *sub, - bool (*predicate)(const char *)) -{ - bool result; - size_t dir_size = git_str_len(dir); - size_t sub_size = strlen(sub); - size_t alloc_size; - - /* leave base valid even if we could not make space for subdir */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) || - GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) || - git_str_try_grow(dir, alloc_size, false) < 0) - return false; - - /* save excursion */ - if (git_str_joinpath(dir, dir->ptr, sub) < 0) - return false; - - result = predicate(dir->ptr); - - /* restore path */ - git_str_truncate(dir, dir_size); - return result; -} - -bool git_fs_path_contains(git_str *dir, const char *item) -{ - return _check_dir_contents(dir, item, &git_fs_path_exists); -} - -bool git_fs_path_contains_dir(git_str *base, const char *subdir) -{ - return _check_dir_contents(base, subdir, &git_fs_path_isdir); -} - -bool git_fs_path_contains_file(git_str *base, const char *file) -{ - return _check_dir_contents(base, file, &git_fs_path_isfile); -} - -int git_fs_path_find_dir(git_str *dir) -{ - int error = 0; - char buf[GIT_PATH_MAX]; - - if (p_realpath(dir->ptr, buf) != NULL) - error = git_str_sets(dir, buf); - - /* call dirname if this is not a directory */ - if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */ - error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0; - - if (!error) - error = git_fs_path_to_dir(dir); - - return error; -} - -int git_fs_path_resolve_relative(git_str *path, size_t ceiling) -{ - char *base, *to, *from, *next; - size_t len; - - GIT_ERROR_CHECK_ALLOC_STR(path); - - if (ceiling > path->size) - ceiling = path->size; - - /* recognize drive prefixes, etc. that should not be backed over */ - if (ceiling == 0) - ceiling = git_fs_path_root(path->ptr) + 1; - - /* recognize URL prefixes that should not be backed over */ - if (ceiling == 0) { - for (next = path->ptr; *next && git__isalpha(*next); ++next); - if (next[0] == ':' && next[1] == '/' && next[2] == '/') - ceiling = (next + 3) - path->ptr; - } - - base = to = from = path->ptr + ceiling; - - while (*from) { - for (next = from; *next && *next != '/'; ++next); - - len = next - from; - - if (len == 1 && from[0] == '.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == '.' && from[1] == '.') { - /* error out if trying to up one from a hard base */ - if (to == base && ceiling != 0) { - git_error_set(GIT_ERROR_INVALID, - "cannot strip root component off url"); - return -1; - } - - /* no more path segments to strip, - * use '../' as a new base path */ - if (to == base) { - if (*next == '/') - len++; - - if (to != from) - memmove(to, from, len); - - to += len; - /* this is now the base, can't back up from a - * relative prefix */ - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == '/') to--; - while (to > base && to[-1] != '/') to--; - } - } else { - if (*next == '/' && *from != '/') - len++; - - if (to != from) - memmove(to, from, len); - - to += len; - } - - from += len; - - while (*from == '/') from++; - } - - *to = '\0'; - - path->size = to - path->ptr; - - return 0; -} - -int git_fs_path_apply_relative(git_str *target, const char *relpath) -{ - return git_str_joinpath(target, git_str_cstr(target), relpath) || - git_fs_path_resolve_relative(target, 0); -} - -int git_fs_path_cmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2, - int (*compare)(const char *, const char *, size_t)) -{ - unsigned char c1, c2; - size_t len = len1 < len2 ? len1 : len2; - int cmp; - - cmp = compare(name1, name2, len); - if (cmp) - return cmp; - - c1 = name1[len]; - c2 = name2[len]; - - if (c1 == '\0' && isdir1) - c1 = '/'; - - if (c2 == '\0' && isdir2) - c2 = '/'; - - return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; -} - -size_t git_fs_path_common_dirlen(const char *one, const char *two) -{ - const char *p, *q, *dirsep = NULL; - - for (p = one, q = two; *p && *q; p++, q++) { - if (*p == '/' && *q == '/') - dirsep = p; - else if (*p != *q) - break; - } - - return dirsep ? (dirsep - one) + 1 : 0; -} - -int git_fs_path_make_relative(git_str *path, const char *parent) -{ - const char *p, *q, *p_dirsep, *q_dirsep; - size_t plen = path->size, newlen, alloclen, depth = 1, i, offset; - - for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { - if (*p == '/' && *q == '/') { - p_dirsep = p; - q_dirsep = q; - } - else if (*p != *q) - break; - } - - /* need at least 1 common path segment */ - if ((p_dirsep == path->ptr || q_dirsep == parent) && - (*p_dirsep != '/' || *q_dirsep != '/')) { - git_error_set(GIT_ERROR_INVALID, - "%s is not a parent of %s", parent, path->ptr); - return GIT_ENOTFOUND; - } - - if (*p == '/' && !*q) - p++; - else if (!*p && *q == '/') - q++; - else if (!*p && !*q) - return git_str_clear(path), 0; - else { - p = p_dirsep + 1; - q = q_dirsep + 1; - } - - plen -= (p - path->ptr); - - if (!*q) - return git_str_set(path, p, plen); - - for (; (q = strchr(q, '/')) && *(q + 1); q++) - depth++; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3); - GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1); - - /* save the offset as we might realllocate the pointer */ - offset = p - path->ptr; - if (git_str_try_grow(path, alloclen, 1) < 0) - return -1; - p = path->ptr + offset; - - memmove(path->ptr + (depth * 3), p, plen + 1); - - for (i = 0; i < depth; i++) - memcpy(path->ptr + (i * 3), "../", 3); - - path->size = newlen; - return 0; -} - -bool git_fs_path_has_non_ascii(const char *path, size_t pathlen) -{ - const uint8_t *scan = (const uint8_t *)path, *end; - - for (end = scan + pathlen; scan < end; ++scan) - if (*scan & 0x80) - return true; - - return false; -} - -#ifdef GIT_USE_ICONV - -int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic) -{ - git_str_init(&ic->buf, 0); - ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); - return 0; -} - -void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic) -{ - if (ic) { - if (ic->map != (iconv_t)-1) - iconv_close(ic->map); - git_str_dispose(&ic->buf); - } -} - -int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen) -{ - char *nfd = (char*)*in, *nfc; - size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv; - int retry = 1; - - if (!ic || ic->map == (iconv_t)-1 || - !git_fs_path_has_non_ascii(*in, *inlen)) - return 0; - - git_str_clear(&ic->buf); - - while (1) { - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1); - if (git_str_grow(&ic->buf, alloclen) < 0) - return -1; - - nfc = ic->buf.ptr + ic->buf.size; - nfclen = ic->buf.asize - ic->buf.size; - - rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); - - ic->buf.size = (nfc - ic->buf.ptr); - - if (rv != (size_t)-1) - break; - - /* if we cannot convert the data (probably because iconv thinks - * it is not valid UTF-8 source data), then use original data - */ - if (errno != E2BIG) - return 0; - - /* make space for 2x the remaining data to be converted - * (with per retry overhead to avoid infinite loops) - */ - wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); - - if (retry++ > 4) - goto fail; - } - - ic->buf.ptr[ic->buf.size] = '\0'; - - *in = ic->buf.ptr; - *inlen = ic->buf.size; - - return 0; - -fail: - git_error_set(GIT_ERROR_OS, "unable to convert unicode path data"); - return -1; -} - -static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; -static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; - -/* Check if the platform is decomposing unicode data for us. We will - * emulate core Git and prefer to use precomposed unicode data internally - * on these platforms, composing the decomposed unicode on the fly. - * - * This mainly happens on the Mac where HDFS stores filenames as - * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will - * return decomposed unicode from readdir() even when the actual - * filesystem is storing precomposed unicode. - */ -bool git_fs_path_does_decompose_unicode(const char *root) -{ - git_str nfc_path = GIT_STR_INIT; - git_str nfd_path = GIT_STR_INIT; - int fd; - bool found_decomposed = false; - size_t orig_len; - const char *trailer; - - /* Create a file using a precomposed path and then try to find it - * using the decomposed name. If the lookup fails, then we will mark - * that we should precompose unicode for this repository. - */ - if (git_str_joinpath(&nfc_path, root, nfc_file) < 0) - goto done; - - /* record original path length before trailer */ - orig_len = nfc_path.size; - - if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0) - goto done; - p_close(fd); - - trailer = nfc_path.ptr + orig_len; - - /* try to look up as NFD path */ - if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 || - git_str_puts(&nfd_path, trailer) < 0) - goto done; - - found_decomposed = git_fs_path_exists(nfd_path.ptr); - - /* remove temporary file (using original precomposed path) */ - (void)p_unlink(nfc_path.ptr); - -done: - git_str_dispose(&nfc_path); - git_str_dispose(&nfd_path); - return found_decomposed; -} - -#else - -bool git_fs_path_does_decompose_unicode(const char *root) -{ - GIT_UNUSED(root); - return false; -} - -#endif - -#if defined(__sun) || defined(__GNU__) -typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; -#else -typedef struct dirent path_dirent_data; -#endif - -int git_fs_path_direach( - git_str *path, - uint32_t flags, - int (*fn)(void *, git_str *), - void *arg) -{ - int error = 0; - ssize_t wd_len; - DIR *dir; - struct dirent *de; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; -#endif - - GIT_UNUSED(flags); - - if (git_fs_path_to_dir(path) < 0) - return -1; - - wd_len = git_str_len(path); - - if ((dir = opendir(path->ptr)) == NULL) { - git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr); - if (errno == ENOENT) - return GIT_ENOTFOUND; - - return -1; - } - -#ifdef GIT_USE_ICONV - if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) - (void)git_fs_path_iconv_init_precompose(&ic); -#endif - - while ((de = readdir(dir)) != NULL) { - const char *de_path = de->d_name; - size_t de_len = strlen(de_path); - - if (git_fs_path_is_dot_or_dotdot(de_path)) - continue; - -#ifdef GIT_USE_ICONV - if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0) - break; -#endif - - if ((error = git_str_put(path, de_path, de_len)) < 0) - break; - - git_error_clear(); - error = fn(arg, path); - - git_str_truncate(path, wd_len); /* restore path */ - - /* Only set our own error if the callback did not set one already */ - if (error != 0) { - if (!git_error_last()) - git_error_set_after_callback(error); - - break; - } - } - - closedir(dir); - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif - - return error; -} - -#if defined(GIT_WIN32) && !defined(__MINGW32__) - -/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7 - * and better. - */ -#ifndef FIND_FIRST_EX_LARGE_FETCH -# define FIND_FIRST_EX_LARGE_FETCH 2 -#endif - -int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags) -{ - git_win32_path path_filter; - - static int is_win7_or_later = -1; - if (is_win7_or_later < 0) - is_win7_or_later = git_has_win32_version(6, 1, 0); - - GIT_ASSERT_ARG(diriter); - GIT_ASSERT_ARG(path); - - memset(diriter, 0, sizeof(git_fs_path_diriter)); - diriter->handle = INVALID_HANDLE_VALUE; - - if (git_str_puts(&diriter->path_utf8, path) < 0) - return -1; - - path_trim_slashes(&diriter->path_utf8); - - if (diriter->path_utf8.size == 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); - return -1; - } - - if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 || - !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) { - git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path); - return -1; - } - - diriter->handle = FindFirstFileExW( - path_filter, - is_win7_or_later ? FindExInfoBasic : FindExInfoStandard, - &diriter->current, - FindExSearchNameMatch, - NULL, - is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0); - - if (diriter->handle == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path); - return -1; - } - - diriter->parent_utf8_len = diriter->path_utf8.size; - diriter->flags = flags; - return 0; -} - -static int diriter_update_paths(git_fs_path_diriter *diriter) -{ - size_t filename_len, path_len; - - filename_len = wcslen(diriter->current.cFileName); - - if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) || - GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2)) - return -1; - - if (path_len > GIT_WIN_PATH_UTF16) { - git_error_set(GIT_ERROR_FILESYSTEM, - "invalid path '%.*ls\\%ls' (path too long)", - diriter->parent_len, diriter->path, diriter->current.cFileName); - return -1; - } - - diriter->path[diriter->parent_len] = L'\\'; - memcpy(&diriter->path[diriter->parent_len+1], - diriter->current.cFileName, filename_len * sizeof(wchar_t)); - diriter->path[path_len-1] = L'\0'; - - git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len); - - if (diriter->parent_utf8_len > 0 && - diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') - git_str_putc(&diriter->path_utf8, '/'); - - git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); - - if (git_str_oom(&diriter->path_utf8)) - return -1; - - return 0; -} - -int git_fs_path_diriter_next(git_fs_path_diriter *diriter) -{ - bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); - - do { - /* Our first time through, we already have the data from - * FindFirstFileW. Use it, otherwise get the next file. - */ - if (!diriter->needs_next) - diriter->needs_next = 1; - else if (!FindNextFileW(diriter->handle, &diriter->current)) - return GIT_ITEROVER; - } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName)); - - if (diriter_update_paths(diriter) < 0) - return -1; - - return 0; -} - -int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len); - - *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1]; - *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1; - return 0; -} - -int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - - *out = diriter->path_utf8.ptr; - *out_len = diriter->path_utf8.size; - return 0; -} - -int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diriter); - - return git_win32__file_attribute_to_stat(out, - (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current, - diriter->path); -} - -void git_fs_path_diriter_free(git_fs_path_diriter *diriter) -{ - if (diriter == NULL) - return; - - git_str_dispose(&diriter->path_utf8); - - if (diriter->handle != INVALID_HANDLE_VALUE) { - FindClose(diriter->handle); - diriter->handle = INVALID_HANDLE_VALUE; - } -} - -#else - -int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags) -{ - GIT_ASSERT_ARG(diriter); - GIT_ASSERT_ARG(path); - - memset(diriter, 0, sizeof(git_fs_path_diriter)); - - if (git_str_puts(&diriter->path, path) < 0) - return -1; - - path_trim_slashes(&diriter->path); - - if (diriter->path.size == 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); - return -1; - } - - if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) { - git_str_dispose(&diriter->path); - - git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path); - return -1; - } - -#ifdef GIT_USE_ICONV - if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) - (void)git_fs_path_iconv_init_precompose(&diriter->ic); -#endif - - diriter->parent_len = diriter->path.size; - diriter->flags = flags; - - return 0; -} - -int git_fs_path_diriter_next(git_fs_path_diriter *diriter) -{ - struct dirent *de; - const char *filename; - size_t filename_len; - bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); - int error = 0; - - GIT_ASSERT_ARG(diriter); - - errno = 0; - - do { - if ((de = readdir(diriter->dir)) == NULL) { - if (!errno) - return GIT_ITEROVER; - - git_error_set(GIT_ERROR_OS, - "could not read directory '%s'", diriter->path.ptr); - return -1; - } - } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name)); - - filename = de->d_name; - filename_len = strlen(filename); - -#ifdef GIT_USE_ICONV - if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 && - (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) - return error; -#endif - - git_str_truncate(&diriter->path, diriter->parent_len); - - if (diriter->parent_len > 0 && - diriter->path.ptr[diriter->parent_len-1] != '/') - git_str_putc(&diriter->path, '/'); - - git_str_put(&diriter->path, filename, filename_len); - - if (git_str_oom(&diriter->path)) - return -1; - - return error; -} - -int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - GIT_ASSERT(diriter->path.size > diriter->parent_len); - - *out = &diriter->path.ptr[diriter->parent_len+1]; - *out_len = diriter->path.size - diriter->parent_len - 1; - return 0; -} - -int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - - *out = diriter->path.ptr; - *out_len = diriter->path.size; - return 0; -} - -int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diriter); - - return git_fs_path_lstat(diriter->path.ptr, out); -} - -void git_fs_path_diriter_free(git_fs_path_diriter *diriter) -{ - if (diriter == NULL) - return; - - if (diriter->dir) { - closedir(diriter->dir); - diriter->dir = NULL; - } - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&diriter->ic); -#endif - - git_str_dispose(&diriter->path); -} - -#endif - -int git_fs_path_dirload( - git_vector *contents, - const char *path, - size_t prefix_len, - uint32_t flags) -{ - git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT; - const char *name; - size_t name_len; - char *dup; - int error; - - GIT_ASSERT_ARG(contents); - GIT_ASSERT_ARG(path); - - if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0) - return error; - - while ((error = git_fs_path_diriter_next(&iter)) == 0) { - if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0) - break; - - GIT_ASSERT(name_len > prefix_len); - - dup = git__strndup(name + prefix_len, name_len - prefix_len); - GIT_ERROR_CHECK_ALLOC(dup); - - if ((error = git_vector_insert(contents, dup)) < 0) - break; - } - - if (error == GIT_ITEROVER) - error = 0; - - git_fs_path_diriter_free(&iter); - return error; -} - -int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path) -{ - if (git_fs_path_is_local_file_url(url_or_path)) - return git_fs_path_fromurl(local_path_out, url_or_path); - else - return git_str_sets(local_path_out, url_or_path); -} - -/* Reject paths like AUX or COM1, or those versions that end in a dot or - * colon. ("AUX." or "AUX:") - */ -GIT_INLINE(bool) validate_dospath( - const char *component, - size_t len, - const char dospath[3], - bool trailing_num) -{ - size_t last = trailing_num ? 4 : 3; - - if (len < last || git__strncasecmp(component, dospath, 3) != 0) - return true; - - if (trailing_num && (component[3] < '1' || component[3] > '9')) - return true; - - return (len > last && - component[last] != '.' && - component[last] != ':'); -} - -GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags) -{ - if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\') - return false; - - if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/') - return false; - - if (flags & GIT_FS_PATH_REJECT_NT_CHARS) { - if (c < 32) - return false; - - switch (c) { - case '<': - case '>': - case ':': - case '"': - case '|': - case '?': - case '*': - return false; - } - } - - return true; -} - -/* - * We fundamentally don't like some paths when dealing with user-inputted - * strings (to avoid escaping a sandbox): we don't want dot or dot-dot - * anywhere, we want to avoid writing weird paths on Windows that can't - * be handled by tools that use the non-\\?\ APIs, we don't want slashes - * or double slashes at the end of paths that can make them ambiguous. - * - * For checkout, we don't want to recurse into ".git" either. - */ -static bool validate_component( - const char *component, - size_t len, - unsigned int flags) -{ - if (len == 0) - return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT); - - if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && - len == 1 && component[0] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && - len == 2 && component[0] == '.' && component[1] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) && - component[len - 1] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) && - component[len - 1] == ' ') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) && - component[len - 1] == ':') - return false; - - if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) { - if (!validate_dospath(component, len, "CON", false) || - !validate_dospath(component, len, "PRN", false) || - !validate_dospath(component, len, "AUX", false) || - !validate_dospath(component, len, "NUL", false) || - !validate_dospath(component, len, "COM", true) || - !validate_dospath(component, len, "LPT", true)) - return false; - } - - return true; -} - -#ifdef GIT_WIN32 -GIT_INLINE(bool) validate_length( - const char *path, - size_t len, - size_t utf8_char_len) -{ - GIT_UNUSED(path); - GIT_UNUSED(len); - - return (utf8_char_len <= MAX_PATH); -} -#endif - -bool git_fs_path_str_is_valid_ext( - const git_str *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len), - void *payload) -{ - const char *start, *c; - size_t len = 0; - - if (!flags) - return true; - - for (start = c = path->ptr; *c && len < path->size; c++, len++) { - if (!validate_char(*c, flags)) - return false; - - if (validate_char_cb && !validate_char_cb(*c, payload)) - return false; - - if (*c != '/') - continue; - - if (!validate_component(start, (c - start), flags)) - return false; - - if (validate_component_cb && - !validate_component_cb(start, (c - start), payload)) - return false; - - start = c + 1; - } - - /* - * We want to support paths specified as either `const char *` - * or `git_str *`; we pass size as `SIZE_MAX` when we use a - * `const char *` to avoid a `strlen`. Ensure that we didn't - * have a NUL in the buffer if there was a non-SIZE_MAX length. - */ - if (path->size != SIZE_MAX && len != path->size) - return false; - - if (!validate_component(start, (c - start), flags)) - return false; - - if (validate_component_cb && - !validate_component_cb(start, (c - start), payload)) - return false; - -#ifdef GIT_WIN32 - if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) { - size_t utf8_len = git_utf8_char_length(path->ptr, len); - - if (!validate_length(path->ptr, len, utf8_len)) - return false; - - if (validate_length_cb && - !validate_length_cb(path->ptr, len, utf8_len)) - return false; - } -#else - GIT_UNUSED(validate_length_cb); -#endif - - return true; -} - -int git_fs_path_validate_str_length_with_suffix( - git_str *path, - size_t suffix_len) -{ -#ifdef GIT_WIN32 - size_t utf8_len = git_utf8_char_length(path->ptr, path->size); - size_t total_len; - - if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) || - total_len > MAX_PATH) { - - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", - (int)path->size, path->ptr); - return -1; - } -#else - GIT_UNUSED(path); - GIT_UNUSED(suffix_len); -#endif - - return 0; -} - -int git_fs_path_normalize_slashes(git_str *out, const char *path) -{ - int error; - char *p; - - if ((error = git_str_puts(out, path)) < 0) - return error; - - for (p = out->ptr; *p; p++) { - if (*p == '\\') - *p = '/'; - } - - return 0; -} - -bool git_fs_path_supports_symlinks(const char *dir) -{ - git_str path = GIT_STR_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_str_dispose(&path); - return supported; -} - -static git_fs_path__mock_owner_t mock_owner = GIT_FS_PATH_MOCK_OWNER_NONE; - -void git_fs_path__set_owner(git_fs_path__mock_owner_t owner) -{ - mock_owner = owner; -} - -#ifdef GIT_WIN32 -static PSID *sid_dup(PSID sid) -{ - DWORD len; - PSID dup; - - len = GetLengthSid(sid); - - if ((dup = git__malloc(len)) == NULL) - return NULL; - - if (!CopySid(len, dup, sid)) { - git_error_set(GIT_ERROR_OS, "could not duplicate sid"); - git__free(dup); - return NULL; - } - - return dup; -} - -static int current_user_sid(PSID *out) -{ - TOKEN_USER *info = NULL; - HANDLE token = NULL; - DWORD len = 0; - int error = -1; - - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { - git_error_set(GIT_ERROR_OS, "could not lookup process information"); - goto done; - } - - if (GetTokenInformation(token, TokenUser, NULL, 0, &len) || - GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - git_error_set(GIT_ERROR_OS, "could not lookup token metadata"); - goto done; - } - - info = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(info); - - if (!GetTokenInformation(token, TokenUser, info, len, &len)) { - git_error_set(GIT_ERROR_OS, "could not lookup current user"); - goto done; - } - - if ((*out = sid_dup(info->User.Sid))) - error = 0; - -done: - if (token) - CloseHandle(token); - - git__free(info); - return error; -} - -static int file_owner_sid(PSID *out, const char *path) -{ - git_win32_path path_w32; - PSECURITY_DESCRIPTOR descriptor = NULL; - PSID owner_sid; - DWORD ret; - int error = -1; - - if (git_win32_path_from_utf8(path_w32, path) < 0) - return -1; - - ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT, - OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - &owner_sid, NULL, NULL, NULL, &descriptor); - - if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND) - error = GIT_ENOTFOUND; - else if (ret != ERROR_SUCCESS) - git_error_set(GIT_ERROR_OS, "failed to get security information"); - else if (!IsValidSid(owner_sid)) - git_error_set(GIT_ERROR_OS, "file owner is not valid"); - else if ((*out = sid_dup(owner_sid))) - error = 0; - - if (descriptor) - LocalFree(descriptor); - - return error; -} - -int git_fs_path_owner_is_current_user(bool *out, const char *path) -{ - PSID owner_sid = NULL, user_sid = NULL; - int error = -1; - - if (mock_owner) { - *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER); - return 0; - } - - if ((error = file_owner_sid(&owner_sid, path)) < 0 || - (error = current_user_sid(&user_sid)) < 0) - goto done; - - *out = EqualSid(owner_sid, user_sid); - error = 0; - -done: - git__free(owner_sid); - git__free(user_sid); - return error; -} - -int git_fs_path_owner_is_system(bool *out, const char *path) -{ - PSID owner_sid; - - if (mock_owner) { - *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM); - return 0; - } - - if (file_owner_sid(&owner_sid, path) < 0) - return -1; - - *out = IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || - IsWellKnownSid(owner_sid, WinLocalSystemSid); - - git__free(owner_sid); - return 0; -} - -int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path) -{ - PSID owner_sid = NULL, user_sid = NULL; - int error = -1; - - if (mock_owner) { - *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM || - mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER); - return 0; - } - - if (file_owner_sid(&owner_sid, path) < 0) - goto done; - - if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || - IsWellKnownSid(owner_sid, WinLocalSystemSid)) { - *out = 1; - error = 0; - goto done; - } - - if (current_user_sid(&user_sid) < 0) - goto done; - - *out = EqualSid(owner_sid, user_sid); - error = 0; - -done: - git__free(owner_sid); - git__free(user_sid); - return error; -} - -#else - -static int fs_path_owner_is(bool *out, const char *path, uid_t *uids, size_t uids_len) -{ - struct stat st; - size_t i; - - *out = false; - - if (p_lstat(path, &st) != 0) { - if (errno == ENOENT) - return GIT_ENOTFOUND; - - git_error_set(GIT_ERROR_OS, "could not stat '%s'", path); - return -1; - } - - for (i = 0; i < uids_len; i++) { - if (uids[i] == st.st_uid) { - *out = true; - break; - } - } - - return 0; -} - -int git_fs_path_owner_is_current_user(bool *out, const char *path) -{ - uid_t userid = geteuid(); - - if (mock_owner) { - *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER); - return 0; - } - - return fs_path_owner_is(out, path, &userid, 1); -} - -int git_fs_path_owner_is_system(bool *out, const char *path) -{ - uid_t userid = 0; - - if (mock_owner) { - *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM); - return 0; - } - - return fs_path_owner_is(out, path, &userid, 1); -} - -int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path) -{ - uid_t userids[2] = { geteuid(), 0 }; - - if (mock_owner) { - *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM || - mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER); - return 0; - } - - return fs_path_owner_is(out, path, userids, 2); -} - -#endif - -int git_fs_path_find_executable(git_str *fullpath, const char *executable) -{ -#ifdef GIT_WIN32 - git_win32_path fullpath_w, executable_w; - int error; - - if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) - return -1; - - error = git_win32_path_find_executable(fullpath_w, executable_w); - - if (error == 0) - error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); - - return error; -#else - git_str path = GIT_STR_INIT; - const char *current_dir, *term; - bool found = false; - - if (git__getenv(&path, "PATH") < 0) - return -1; - - current_dir = path.ptr; - - while (*current_dir) { - if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) - term = strchr(current_dir, '\0'); - - git_str_clear(fullpath); - if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || - git_str_putc(fullpath, '/') < 0 || - git_str_puts(fullpath, executable) < 0) - return -1; - - if (git_fs_path_isfile(fullpath->ptr)) { - found = true; - break; - } - - current_dir = term; - - while (*current_dir == GIT_PATH_LIST_SEPARATOR) - current_dir++; - } - - git_str_dispose(&path); - - if (found) - return 0; - - git_str_clear(fullpath); - return GIT_ENOTFOUND; -#endif -} diff --git a/src/fs_path.h b/src/fs_path.h deleted file mode 100644 index 7e6a22d4f..000000000 --- a/src/fs_path.h +++ /dev/null @@ -1,772 +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_fs_path_h__ -#define INCLUDE_fs_path_h__ - -#include "common.h" - -#include "posix.h" -#include "str.h" -#include "vector.h" -#include "utf8.h" - -/** - * Path manipulation utils - * - * These are path utilities that munge paths without actually - * looking at the real filesystem. - */ - -/* - * The dirname() function shall take a pointer to a character string - * that contains a pathname, and return a pointer to a string that is a - * pathname of the parent directory of that file. Trailing '/' characters - * in the path are not counted as part of the path. - * - * If path does not contain a '/', then dirname() shall return a pointer to - * the string ".". If path is a null pointer or points to an empty string, - * dirname() shall return a pointer to the string "." . - * - * The `git_fs_path_dirname` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str` - * if the buffer pointer is not NULL. - * It returns an error code < 0 if there is an allocation error, otherwise - * the length of the dirname (which will be > 0). - */ -extern char *git_fs_path_dirname(const char *path); -extern int git_fs_path_dirname_r(git_str *buffer, const char *path); - -/* - * This function returns the basename of the file, which is the last - * part of its full name given by fname, with the drive letter and - * leading directories stripped off. For example, the basename of - * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. - * - * Trailing slashes and backslashes are significant: the basename of - * c:/foo/bar/ is an empty string after the rightmost slash. - * - * The `git_fs_path_basename` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`. - * It returns an error code < 0 if there is an allocation error, otherwise - * the length of the basename (which will be >= 0). - */ -extern char *git_fs_path_basename(const char *path); -extern int git_fs_path_basename_r(git_str *buffer, const char *path); - -/* Return the offset of the start of the basename. Unlike the other - * basename functions, this returns 0 if the path is empty. - */ -extern size_t git_fs_path_basename_offset(git_str *buffer); - -/** - * Find offset to root of path if path has one. - * - * This will return a number >= 0 which is the offset to the start of the - * path, if the path is rooted (i.e. "/rooted/path" returns 0 and - * "c:/windows/rooted/path" returns 2). If the path is not rooted, this - * returns -1. - */ -extern int git_fs_path_root(const char *path); - -/** - * Ensure path has a trailing '/'. - */ -extern int git_fs_path_to_dir(git_str *path); - -/** - * Ensure string has a trailing '/' if there is space for it. - */ -extern void git_fs_path_string_to_dir(char *path, size_t size); - -/** - * Taken from git.git; returns nonzero if the given path is "." or "..". - */ -GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) -{ - return (name[0] == '.' && - (name[1] == '\0' || - (name[1] == '.' && name[2] == '\0'))); -} - -#ifdef GIT_WIN32 -GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name) -{ - return (name[0] == L'.' && - (name[1] == L'\0' || - (name[1] == L'.' && name[2] == L'\0'))); -} - -#define git_fs_path_is_absolute(p) \ - (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) - -#define git_fs_path_is_dirsep(p) \ - ((p) == '/' || (p) == '\\') - -/** - * Convert backslashes in path to forward slashes. - */ -GIT_INLINE(void) git_fs_path_mkposix(char *path) -{ - while (*path) { - if (*path == '\\') - *path = '/'; - - path++; - } -} -#else -# define git_fs_path_mkposix(p) /* blank */ - -#define git_fs_path_is_absolute(p) \ - ((p)[0] == '/') - -#define git_fs_path_is_dirsep(p) \ - ((p) == '/') - -#endif - -/** - * Check if string is a relative path (i.e. starts with "./" or "../") - */ -GIT_INLINE(int) git_fs_path_is_relative(const char *p) -{ - return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); -} - -/** - * Check if string is at end of path segment (i.e. looking at '/' or '\0') - */ -GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p) -{ - return !*p || *p == '/'; -} - -extern int git__percent_decode(git_str *decoded_out, const char *input); - -/** - * Extract path from file:// URL. - */ -extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url); - - -/** - * Path filesystem utils - * - * These are path utilities that actually access the filesystem. - */ - -/** - * Check if a file exists and can be accessed. - * @return true or false - */ -extern bool git_fs_path_exists(const char *path); - -/** - * Check if the given path points to a directory. - * @return true or false - */ -extern bool git_fs_path_isdir(const char *path); - -/** - * Check if the given path points to a regular file. - * @return true or false - */ -extern bool git_fs_path_isfile(const char *path); - -/** - * Check if the given path points to a symbolic link. - * @return true or false - */ -extern bool git_fs_path_islink(const char *path); - -/** - * Check if the given path is a directory, and is empty. - */ -extern bool git_fs_path_is_empty_dir(const char *path); - -/** - * Stat a file and/or link and set error if needed. - */ -extern int git_fs_path_lstat(const char *path, struct stat *st); - -/** - * Check if the parent directory contains the item. - * - * @param dir Directory to check. - * @param item Item that might be in the directory. - * @return 0 if item exists in directory, <0 otherwise. - */ -extern bool git_fs_path_contains(git_str *dir, const char *item); - -/** - * Check if the given path contains the given subdirectory. - * - * @param parent Directory path that might contain subdir - * @param subdir Subdirectory name to look for in parent - * @return true if subdirectory exists, false otherwise. - */ -extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir); - -/** - * Determine the common directory length between two paths, including - * the final path separator. For example, given paths 'a/b/c/1.txt - * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this - * will return the length of the string 'a/b/c/', which is 6. - * - * @param one The first path - * @param two The second path - * @return The length of the common directory - */ -extern size_t git_fs_path_common_dirlen(const char *one, const char *two); - -/** - * Make the path relative to the given parent path. - * - * @param path The path to make relative - * @param parent The parent path to make path relative to - * @return 0 if path was made relative, GIT_ENOTFOUND - * if there was not common root between the paths, - * or <0. - */ -extern int git_fs_path_make_relative(git_str *path, const char *parent); - -/** - * Check if the given path contains the given file. - * - * @param dir Directory path that might contain file - * @param file File name to look for in parent - * @return true if file exists, false otherwise. - */ -extern bool git_fs_path_contains_file(git_str *dir, const char *file); - -/** - * Prepend base to unrooted path or just copy path over. - * - * This will optionally return the index into the path where the "root" - * is, either the end of the base directory prefix or the path root. - */ -extern int git_fs_path_join_unrooted( - git_str *path_out, const char *path, const char *base, ssize_t *root_at); - -/** - * Removes multiple occurrences of '/' in a row, squashing them into a - * single '/'. - */ -extern void git_fs_path_squash_slashes(git_str *path); - -/** - * Clean up path, prepending base if it is not already rooted. - */ -extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base); - -/** - * Clean up path, prepending base if it is not already rooted and - * appending a slash. - */ -extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base); - -/** - * Get a directory from a path. - * - * If path is a directory, this acts like `git_fs_path_prettify_dir` - * (cleaning up path and appending a '/'). If path is a normal file, - * this prettifies it, then removed the filename a la dirname and - * appends the trailing '/'. If the path does not exist, it is - * treated like a regular filename. - */ -extern int git_fs_path_find_dir(git_str *dir); - -/** - * Resolve relative references within a path. - * - * This eliminates "./" and "../" relative references inside a path, - * as well as condensing multiple slashes into single ones. It will - * not touch the path before the "ceiling" length. - * - * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL - * prefix and not touch that part of the path. - */ -extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling); - -/** - * Apply a relative path to base path. - * - * Note that the base path could be a filename or a URL and this - * should still work. The relative path is walked segment by segment - * with three rules: series of slashes will be condensed to a single - * slash, "." will be eaten with no change, and ".." will remove a - * segment from the base path. - */ -extern int git_fs_path_apply_relative(git_str *target, const char *relpath); - -enum { - GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0), - GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), - GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), -}; - -/** - * Walk each directory entry, except '.' and '..', calling fn(state). - * - * @param pathbuf Buffer the function reads the initial directory - * path from, and updates with each successive entry's name. - * @param flags Combination of GIT_FS_PATH_DIR flags. - * @param callback Callback for each entry. Passed the `payload` and each - * successive path inside the directory as a full path. This may - * safely append text to the pathbuf if needed. Return non-zero to - * cancel iteration (and return value will be propagated back). - * @param payload Passed to callback as first argument. - * @return 0 on success or error code from OS error or from callback - */ -extern int git_fs_path_direach( - git_str *pathbuf, - uint32_t flags, - int (*callback)(void *payload, git_str *path), - void *payload); - -/** - * Sort function to order two paths - */ -extern int git_fs_path_cmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2, - int (*compare)(const char *, const char *, size_t)); - -/** - * Invoke callback up path directory by directory until the ceiling is - * reached (inclusive of a final call at the root_path). - * - * Returning anything other than 0 from the callback function - * will stop the iteration and propagate the error to the caller. - * - * @param pathbuf Buffer the function reads the directory from and - * and updates with each successive name. - * @param ceiling Prefix of path at which to stop walking up. If NULL, - * this will walk all the way up to the root. If not a prefix of - * pathbuf, the callback will be invoked a single time on the - * original input path. - * @param callback Function to invoke on each path. Passed the `payload` - * and the buffer containing the current path. The path should not - * be modified in any way. Return non-zero to stop iteration. - * @param payload Passed to fn as the first ath. - */ -extern int git_fs_path_walk_up( - git_str *pathbuf, - const char *ceiling, - int (*callback)(void *payload, const char *path), - void *payload); - - -enum { - GIT_FS_PATH_NOTEQUAL = 0, - GIT_FS_PATH_EQUAL = 1, - GIT_FS_PATH_PREFIX = 2 -}; - -/* - * Determines if a path is equal to or potentially a child of another. - * @param parent The possible parent - * @param child The possible child - */ -GIT_INLINE(int) git_fs_path_equal_or_prefixed( - const char *parent, - const char *child, - ssize_t *prefixlen) -{ - const char *p = parent, *c = child; - int lastslash = 0; - - while (*p && *c) { - lastslash = (*p == '/'); - - if (*p++ != *c++) - return GIT_FS_PATH_NOTEQUAL; - } - - if (*p != '\0') - return GIT_FS_PATH_NOTEQUAL; - - if (*c == '\0') { - if (prefixlen) - *prefixlen = p - parent; - - return GIT_FS_PATH_EQUAL; - } - - if (*c == '/' || lastslash) { - if (prefixlen) - *prefixlen = (p - parent) - lastslash; - - return GIT_FS_PATH_PREFIX; - } - - return GIT_FS_PATH_NOTEQUAL; -} - -/* translate errno to libgit2 error code and set error message */ -extern int git_fs_path_set_error( - int errno_value, const char *path, const char *action); - -/* check if non-ascii characters are present in filename */ -extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen); - -#define GIT_PATH_REPO_ENCODING "UTF-8" - -#ifdef __APPLE__ -#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" -#else -#define GIT_PATH_NATIVE_ENCODING "UTF-8" -#endif - -#ifdef GIT_USE_ICONV - -#include - -typedef struct { - iconv_t map; - git_str buf; -} git_fs_path_iconv_t; - -#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT } - -/* Init iconv data for converting decomposed UTF-8 to precomposed */ -extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic); - -/* Clear allocated iconv data */ -extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic); - -/* - * Rewrite `in` buffer using iconv map if necessary, replacing `in` - * pointer internal iconv buffer if rewrite happened. The `in` pointer - * will be left unchanged if no rewrite was needed. - */ -extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen); - -#endif /* GIT_USE_ICONV */ - -extern bool git_fs_path_does_decompose_unicode(const char *root); - - -typedef struct git_fs_path_diriter git_fs_path_diriter; - -#if defined(GIT_WIN32) && !defined(__MINGW32__) - -struct git_fs_path_diriter -{ - git_win32_path path; - size_t parent_len; - - git_str path_utf8; - size_t parent_utf8_len; - - HANDLE handle; - - unsigned int flags; - - WIN32_FIND_DATAW current; - unsigned int needs_next; -}; - -#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE } - -#else - -struct git_fs_path_diriter -{ - git_str path; - size_t parent_len; - - unsigned int flags; - - DIR *dir; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic; -#endif -}; - -#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT } - -#endif - -/** - * Initialize a directory iterator. - * - * @param diriter Pointer to a diriter structure that will be setup. - * @param path The path that will be iterated over - * @param flags Directory reader flags - * @return 0 or an error code - */ -extern int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags); - -/** - * Advance the directory iterator. Will return GIT_ITEROVER when - * the iteration has completed successfully. - * - * @param diriter The directory iterator - * @return 0, GIT_ITEROVER, or an error code - */ -extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter); - -/** - * Returns the file name of the current item in the iterator. - * - * @param out Pointer to store the path in - * @param out_len Pointer to store the length of the path in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter); - -/** - * Returns the full path of the current item in the iterator; that - * is the current filename plus the path of the directory that the - * iterator was constructed with. - * - * @param out Pointer to store the path in - * @param out_len Pointer to store the length of the path in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter); - -/** - * Performs an `lstat` on the current item in the iterator. - * - * @param out Pointer to store the stat data in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter); - -/** - * Closes the directory iterator. - * - * @param diriter The directory iterator - */ -extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter); - -/** - * Load all directory entries (except '.' and '..') into a vector. - * - * For cases where `git_fs_path_direach()` is not appropriate, this - * allows you to load the filenames in a directory into a vector - * of strings. That vector can then be sorted, iterated, or whatever. - * Remember to free alloc of the allocated strings when you are done. - * - * @param contents Vector to fill with directory entry names. - * @param path The directory to read from. - * @param prefix_len When inserting entries, the trailing part of path - * will be prefixed after this length. I.e. given path "/a/b" and - * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. - * @param flags Combination of GIT_FS_PATH_DIR flags. - */ -extern int git_fs_path_dirload( - git_vector *contents, - const char *path, - size_t prefix_len, - uint32_t flags); - - -/* Used for paths to repositories on the filesystem */ -extern bool git_fs_path_is_local_file_url(const char *file_url); -extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path); - -/* Flags to determine path validity in `git_fs_path_isvalid` */ -#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0) -#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1) -#define GIT_FS_PATH_REJECT_SLASH (1 << 2) -#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3) -#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4) -#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5) -#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6) -#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7) -#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8) -#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9) - -#define GIT_FS_PATH_REJECT_MAX (1 << 9) - -/* Default path safety for writing files to disk: since we use the - * Win32 "File Namespace" APIs ("\\?\") we need to protect from - * paths that the normal Win32 APIs would not write. - */ -#ifdef GIT_WIN32 -# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ - GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ - GIT_FS_PATH_REJECT_TRAVERSAL | \ - GIT_FS_PATH_REJECT_BACKSLASH | \ - GIT_FS_PATH_REJECT_TRAILING_DOT | \ - GIT_FS_PATH_REJECT_TRAILING_SPACE | \ - GIT_FS_PATH_REJECT_TRAILING_COLON | \ - GIT_FS_PATH_REJECT_DOS_PATHS | \ - GIT_FS_PATH_REJECT_NT_CHARS -#else -# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ - GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ - GIT_FS_PATH_REJECT_TRAVERSAL -#endif - -/** - * Validate a filesystem path; with custom callbacks per-character and - * per-path component. - */ -extern bool git_fs_path_str_is_valid_ext( - const git_str *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), - void *payload); - -GIT_INLINE(bool) git_fs_path_is_valid_ext( - const char *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), - void *payload) -{ - const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_fs_path_str_is_valid_ext( - &str, - flags, - validate_char_cb, - validate_component_cb, - validate_length_cb, - payload); -} - -/** - * Validate a filesystem path. This ensures that the given path is legal - * and does not contain any "unsafe" components like path traversal ('.' - * or '..'), characters that are inappropriate for lesser filesystems - * (trailing ' ' or ':' characters), or filenames ("component names") - * that are not supported ('AUX', 'COM1"). - */ -GIT_INLINE(bool) git_fs_path_is_valid( - const char *path, - unsigned int flags) -{ - const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL); -} - -/** Validate a filesystem path in a `git_str`. */ -GIT_INLINE(bool) git_fs_path_str_is_valid( - const git_str *path, - unsigned int flags) -{ - return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL); -} - -extern int git_fs_path_validate_str_length_with_suffix( - git_str *path, - size_t suffix_len); - -/** - * Validate an on-disk path, taking into account that it will have a - * suffix appended (eg, `.lock`). - */ -GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix( - const char *path, - size_t path_len, - size_t suffix_len) -{ -#ifdef GIT_WIN32 - size_t path_chars, total_chars; - - path_chars = git_utf8_char_length(path, path_len); - - if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || - total_chars > MAX_PATH) { - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); - return -1; - } - return 0; -#else - GIT_UNUSED(path); - GIT_UNUSED(path_len); - GIT_UNUSED(suffix_len); - return 0; -#endif -} - -/** - * Validate an path on the filesystem. This ensures that the given - * path is valid for the operating system/platform; for example, this - * will ensure that the given absolute path is smaller than MAX_PATH on - * Windows. - * - * For paths within the working directory, you should use ensure that - * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`. - */ -GIT_INLINE(int) git_fs_path_validate_filesystem( - const char *path, - size_t path_len) -{ - return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0); -} - -/** - * Convert any backslashes into slashes - */ -int git_fs_path_normalize_slashes(git_str *out, const char *path); - -bool git_fs_path_supports_symlinks(const char *dir); - -typedef enum { - GIT_FS_PATH_MOCK_OWNER_NONE = 0, /* do filesystem lookups as normal */ - GIT_FS_PATH_MOCK_OWNER_SYSTEM = 1, - GIT_FS_PATH_MOCK_OWNER_CURRENT_USER = 2, - GIT_FS_PATH_MOCK_OWNER_OTHER = 3 -} git_fs_path__mock_owner_t; - -/** - * Sets the mock ownership for files; subsequent calls to - * `git_fs_path_owner_is_*` functions will return this data until cleared - * with `GIT_FS_PATH_MOCK_OWNER_NONE`. - */ -void git_fs_path__set_owner(git_fs_path__mock_owner_t owner); - -/** - * Verify that the file in question is owned by an administrator or system - * account. - */ -int git_fs_path_owner_is_system(bool *out, const char *path); - -/** - * Verify that the file in question is owned by the current user; - */ - -int git_fs_path_owner_is_current_user(bool *out, const char *path); - -/** - * Verify that the file in question is owned by an administrator or system - * account _or_ the current user; - */ -int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path); - -/** - * Search the current PATH for the given executable, returning the full - * path if it is found. - */ -int git_fs_path_find_executable(git_str *fullpath, const char *executable); - -#endif diff --git a/src/futils.c b/src/futils.c deleted file mode 100644 index 42c35955e..000000000 --- a/src/futils.c +++ /dev/null @@ -1,1194 +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 "futils.h" - -#include "runtime.h" -#include "strmap.h" -#include "hash.h" -#include "rand.h" - -#include -#if GIT_WIN32 -#include "win32/findfile.h" -#endif - -#define GIT_FILEMODE_DEFAULT 0100666 - -int git_futils_mkpath2file(const char *file_path, const mode_t mode) -{ - return git_futils_mkdir( - file_path, mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); -} - -int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode) -{ - const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; - unsigned int tries = 32; - int fd; - - while (tries--) { - uint64_t rand = git_rand_next(); - - git_str_sets(path_out, filename); - git_str_puts(path_out, "_git2_"); - git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t)); - - if (git_str_oom(path_out)) - return -1; - - /* Note that we open with O_CREAT | O_EXCL */ - if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0) - return fd; - } - - git_error_set(GIT_ERROR_OS, - "failed to create temporary file '%s'", path_out->ptr); - git_str_dispose(path_out); - return -1; -} - -int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) -{ - int fd; - - if (git_futils_mkpath2file(path, dirmode) < 0) - return -1; - - fd = p_creat(path, mode); - if (fd < 0) { - git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path); - return -1; - } - - return fd; -} - -int git_futils_creat_locked(const char *path, const mode_t mode) -{ - int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, - mode); - - if (fd < 0) { - int error = errno; - git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path); - switch (error) { - case EEXIST: - return GIT_ELOCKED; - case ENOENT: - return GIT_ENOTFOUND; - default: - return -1; - } - } - - return fd; -} - -int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) -{ - if (git_futils_mkpath2file(path, dirmode) < 0) - return -1; - - return git_futils_creat_locked(path, mode); -} - -int git_futils_open_ro(const char *path) -{ - int fd = p_open(path, O_RDONLY); - if (fd < 0) - return git_fs_path_set_error(errno, path, "open"); - return fd; -} - -int git_futils_truncate(const char *path, int mode) -{ - int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); - if (fd < 0) - return git_fs_path_set_error(errno, path, "open"); - - close(fd); - return 0; -} - -int git_futils_filesize(uint64_t *out, git_file fd) -{ - struct stat sb; - - if (p_fstat(fd, &sb)) { - git_error_set(GIT_ERROR_OS, "failed to stat file descriptor"); - return -1; - } - - 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) -{ - if (S_ISREG(raw_mode)) - return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); - else if (S_ISLNK(raw_mode)) - return S_IFLNK; - else if (S_ISGITLINK(raw_mode)) - return S_IFGITLINK; - else if (S_ISDIR(raw_mode)) - return S_IFDIR; - else - return 0; -} - -int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) -{ - ssize_t read_size = 0; - size_t alloc_len; - - git_str_clear(buf); - - if (!git__is_ssizet(len)) { - git_error_set(GIT_ERROR_INVALID, "read too large"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); - if (git_str_grow(buf, alloc_len) < 0) - return -1; - - /* p_read loops internally to read len bytes */ - read_size = p_read(fd, buf->ptr, len); - - if (read_size != (ssize_t)len) { - git_error_set(GIT_ERROR_OS, "failed to read descriptor"); - git_str_dispose(buf); - return -1; - } - - buf->ptr[read_size] = '\0'; - buf->size = read_size; - - return 0; -} - -int git_futils_readbuffer_updated( - git_str *out, - const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], - int *updated) -{ - int error; - git_file fd; - struct stat st; - git_str buf = GIT_STR_INIT; - unsigned char checksum_new[GIT_HASH_SHA1_SIZE]; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(path && *path); - - if (updated != NULL) - *updated = 0; - - if (p_stat(path, &st) < 0) - return git_fs_path_set_error(errno, path, "stat"); - - - if (S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_INVALID, "requested file is a directory"); - return GIT_ENOTFOUND; - } - - if (!git__is_sizet(st.st_size+1)) { - git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path); - return -1; - } - - if ((fd = git_futils_open_ro(path)) < 0) - return fd; - - if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { - p_close(fd); - return -1; - } - - p_close(fd); - - if (checksum) { - if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) { - git_str_dispose(&buf); - return error; - } - - /* - * If we were given a checksum, we only want to use it if it's different - */ - if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) { - git_str_dispose(&buf); - if (updated) - *updated = 0; - - return 0; - } - - memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE); - } - - /* - * If we're here, the file did change, or the user didn't have an old version - */ - if (updated != NULL) - *updated = 1; - - git_str_swap(out, &buf); - git_str_dispose(&buf); - - return 0; -} - -int git_futils_readbuffer(git_str *buf, const char *path) -{ - return git_futils_readbuffer_updated(buf, path, NULL, NULL); -} - -int git_futils_writebuffer( - const git_str *buf, const char *path, int flags, mode_t mode) -{ - int fd, do_fsync = 0, error = 0; - - if (!flags) - flags = O_CREAT | O_TRUNC | O_WRONLY; - - if ((flags & O_FSYNC) != 0) - do_fsync = 1; - - flags &= ~O_FSYNC; - - if (!mode) - mode = GIT_FILEMODE_DEFAULT; - - if ((fd = p_open(path, flags, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); - return fd; - } - - if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) { - git_error_set(GIT_ERROR_OS, "could not write to '%s'", path); - (void)p_close(fd); - return error; - } - - if (do_fsync && (error = p_fsync(fd)) < 0) { - git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path); - p_close(fd); - return error; - } - - if ((error = p_close(fd)) < 0) { - git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); - return error; - } - - if (do_fsync && (flags & O_CREAT)) - error = git_futils_fsync_parent(path); - - return error; -} - -int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) -{ - if (git_futils_mkpath2file(to, dirmode) < 0) - return -1; - - if (p_rename(from, to) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to); - return -1; - } - - return 0; -} - -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); -} - -int git_futils_mmap_ro_file(git_map *out, const char *path) -{ - git_file fd = git_futils_open_ro(path); - uint64_t len; - int result; - - if (fd < 0) - return fd; - - 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); - result = -1; - goto out; - } - - result = git_futils_mmap_ro(out, fd, 0, (size_t)len); -out: - p_close(fd); - return result; -} - -void git_futils_mmap_free(git_map *out) -{ - p_munmap(out); -} - -GIT_INLINE(int) mkdir_validate_dir( - const char *path, - struct stat *st, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - /* with exclusive create, existing dir is an error */ - if ((flags & GIT_MKDIR_EXCL) != 0) { - git_error_set(GIT_ERROR_FILESYSTEM, - "failed to make directory '%s': directory exists", path); - return GIT_EEXISTS; - } - - if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || - (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { - if (p_unlink(path) < 0) { - git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'", - S_ISLNK(st->st_mode) ? "symlink" : "file", path); - return GIT_EEXISTS; - } - - opts->perfdata.mkdir_calls++; - - if (p_mkdir(path, mode) < 0) { - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); - return GIT_EEXISTS; - } - } - - else if (S_ISLNK(st->st_mode)) { - /* Re-stat the target, make sure it's a directory */ - opts->perfdata.stat_calls++; - - if (p_stat(path, st) < 0) { - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); - return GIT_EEXISTS; - } - } - - else if (!S_ISDIR(st->st_mode)) { - git_error_set(GIT_ERROR_FILESYSTEM, - "failed to make directory '%s': directory exists", path); - return GIT_EEXISTS; - } - - return 0; -} - -GIT_INLINE(int) mkdir_validate_mode( - const char *path, - struct stat *st, - bool terminal_path, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || - (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { - - opts->perfdata.chmod_calls++; - - if (p_chmod(path, mode) < 0) { - git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path); - return -1; - } - } - - return 0; -} - -GIT_INLINE(int) mkdir_canonicalize( - git_str *path, - uint32_t flags) -{ - ssize_t root_len; - - if (path->size == 0) { - git_error_set(GIT_ERROR_OS, "attempt to create empty path"); - return -1; - } - - /* Trim trailing slashes (except the root) */ - if ((root_len = git_fs_path_root(path->ptr)) < 0) - root_len = 0; - else - root_len++; - - while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') - path->ptr[--path->size] = '\0'; - - /* if we are not supposed to made the last element, truncate it */ - if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { - git_fs_path_dirname_r(path, path->ptr); - flags |= GIT_MKDIR_SKIP_LAST; - } - if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { - git_fs_path_dirname_r(path, path->ptr); - } - - /* We were either given the root path (or trimmed it to - * the root), we don't have anything to do. - */ - if (path->size <= (size_t)root_len) - git_str_clear(path); - - return 0; -} - -int git_futils_mkdir( - const char *path, - mode_t mode, - uint32_t flags) -{ - git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT; - const char *relative; - struct git_futils_mkdir_options opts = { 0 }; - struct stat st; - size_t depth = 0; - int len = 0, root_len, error; - - if ((error = git_str_puts(&make_path, path)) < 0 || - (error = mkdir_canonicalize(&make_path, flags)) < 0 || - (error = git_str_puts(&parent_path, make_path.ptr)) < 0 || - make_path.size == 0) - goto done; - - root_len = git_fs_path_root(make_path.ptr); - - /* find the first parent directory that exists. this will be used - * as the base to dirname_relative. - */ - for (relative = make_path.ptr; parent_path.size; ) { - error = p_lstat(parent_path.ptr, &st); - - if (error == 0) { - break; - } else if (errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); - error = -1; - goto done; - } - - depth++; - - /* examine the parent of the current path */ - if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { - error = len; - goto done; - } - - GIT_ASSERT(len); - - /* - * We've walked all the given path's parents and it's either relative - * (the parent is simply '.') or rooted (the length is less than or - * 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 == 1 && parent_path.ptr[0] == '/') || - len <= root_len) { - relative = make_path.ptr; - break; - } - - relative = make_path.ptr + len + 1; - - /* not recursive? just make this directory relative to its parent. */ - if ((flags & GIT_MKDIR_PATH) == 0) - break; - } - - /* we found an item at the location we're trying to create, - * validate it. - */ - if (depth == 0) { - error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); - - if (!error) - error = mkdir_validate_mode( - make_path.ptr, &st, true, mode, flags, &opts); - - goto done; - } - - /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when - * canonicalizing `make_path`. - */ - flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); - - error = git_futils_mkdir_relative(relative, - parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); - -done: - git_str_dispose(&make_path); - git_str_dispose(&parent_path); - return error; -} - -int git_futils_mkdir_r(const char *path, const mode_t mode) -{ - return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); -} - -int git_futils_mkdir_relative( - const char *relative_path, - const char *base, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - git_str make_path = GIT_STR_INIT; - ssize_t root = 0, min_root_len; - char lastch = '/', *tail; - struct stat st; - struct git_futils_mkdir_options empty_opts = {0}; - int error; - - if (!opts) - opts = &empty_opts; - - /* build path and find "root" where we should start calling mkdir */ - if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0) - return -1; - - if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || - make_path.size == 0) - goto done; - - /* if we are not supposed to make the whole path, reset root */ - if ((flags & GIT_MKDIR_PATH) == 0) - root = git_str_rfind(&make_path, '/'); - - /* advance root past drive name or network mount prefix */ - min_root_len = git_fs_path_root(make_path.ptr); - if (root < min_root_len) - root = min_root_len; - while (root >= 0 && make_path.ptr[root] == '/') - ++root; - - /* clip root to make_path length */ - if (root > (ssize_t)make_path.size) - root = (ssize_t)make_path.size; /* i.e. NUL byte of string */ - if (root < 0) - root = 0; - - /* walk down tail of path making each directory */ - for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { - bool mkdir_attempted = false; - - /* advance tail to include next path component */ - while (*tail == '/') - tail++; - while (*tail && *tail != '/') - tail++; - - /* truncate path at next component */ - lastch = *tail; - *tail = '\0'; - st.st_mode = 0; - - if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) - continue; - - /* See what's going on with this path component */ - opts->perfdata.stat_calls++; - -retry_lstat: - if (p_lstat(make_path.ptr, &st) < 0) { - if (mkdir_attempted || errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr); - error = -1; - goto done; - } - - git_error_clear(); - opts->perfdata.mkdir_calls++; - mkdir_attempted = true; - if (p_mkdir(make_path.ptr, mode) < 0) { - if (errno == EEXIST) - goto retry_lstat; - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr); - error = -1; - goto done; - } - } else { - if ((error = mkdir_validate_dir( - make_path.ptr, &st, mode, flags, opts)) < 0) - goto done; - } - - /* chmod if requested and necessary */ - if ((error = mkdir_validate_mode( - make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) - goto done; - - if (opts->dir_map && opts->pool) { - char *cache_path; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); - 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); - - if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) - goto done; - } - } - - error = 0; - - /* check that full path really is a directory if requested & needed */ - if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && - lastch != '\0') { - opts->perfdata.stat_calls++; - - if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_OS, "path is not a directory '%s'", - make_path.ptr); - error = GIT_ENOTFOUND; - } - } - -done: - git_str_dispose(&make_path); - return error; -} - -typedef struct { - const char *base; - size_t baselen; - uint32_t flags; - int depth; -} futils__rmdir_data; - -#define FUTILS_MAX_DEPTH 100 - -static int futils__error_cannot_rmdir(const char *path, const char *filemsg) -{ - if (filemsg) - git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s", - path, filemsg); - else - git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path); - - return -1; -} - -static int futils__rm_first_parent(git_str *path, const char *ceiling) -{ - int error = GIT_ENOTFOUND; - struct stat st; - - while (error == GIT_ENOTFOUND) { - git_str_rtruncate_at_char(path, '/'); - - if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) - error = 0; - else if (p_lstat_posixly(path->ptr, &st) == 0) { - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - error = p_unlink(path->ptr); - else if (!S_ISDIR(st.st_mode)) - error = -1; /* fail to remove non-regular file */ - } else if (errno != ENOTDIR) - error = -1; - } - - if (error) - futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); - - return error; -} - -static int futils__rmdir_recurs_foreach(void *opaque, git_str *path) -{ - int error = 0; - futils__rmdir_data *data = opaque; - struct stat st; - - if (data->depth > FUTILS_MAX_DEPTH) - error = futils__error_cannot_rmdir( - path->ptr, "directory nesting too deep"); - - else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { - if (errno == ENOENT) - error = 0; - else if (errno == ENOTDIR) { - /* asked to remove a/b/c/d/e and a/b is a normal file */ - if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) - error = futils__rm_first_parent(path, data->base); - else - futils__error_cannot_rmdir( - path->ptr, "parent is not directory"); - } - else - error = git_fs_path_set_error(errno, path->ptr, "rmdir"); - } - - else if (S_ISDIR(st.st_mode)) { - data->depth++; - - error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data); - - data->depth--; - - if (error < 0) - return error; - - if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) - return error; - - if ((error = p_rmdir(path->ptr)) < 0) { - if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && - (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) - error = 0; - else - error = git_fs_path_set_error(errno, path->ptr, "rmdir"); - } - } - - else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { - if (p_unlink(path->ptr) < 0) - error = git_fs_path_set_error(errno, path->ptr, "remove"); - } - - else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) - error = futils__error_cannot_rmdir(path->ptr, "still present"); - - return error; -} - -static int futils__rmdir_empty_parent(void *opaque, const char *path) -{ - futils__rmdir_data *data = opaque; - int error = 0; - - if (strlen(path) <= data->baselen) - error = GIT_ITEROVER; - - else if (p_rmdir(path) < 0) { - int en = errno; - - if (en == ENOENT || en == ENOTDIR) { - /* do nothing */ - } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && - en == EBUSY) { - error = git_fs_path_set_error(errno, path, "rmdir"); - } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { - error = GIT_ITEROVER; - } else { - error = git_fs_path_set_error(errno, path, "rmdir"); - } - } - - return error; -} - -int git_futils_rmdir_r( - const char *path, const char *base, uint32_t flags) -{ - int error; - git_str fullpath = GIT_STR_INIT; - futils__rmdir_data data; - - /* build path and find "root" where we should start calling mkdir */ - if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0) - return -1; - - memset(&data, 0, sizeof(data)); - data.base = base ? base : ""; - data.baselen = base ? strlen(base) : 0; - data.flags = flags; - - error = futils__rmdir_recurs_foreach(&data, &fullpath); - - /* remove now-empty parents if requested */ - if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) - error = git_fs_path_walk_up( - &fullpath, base, futils__rmdir_empty_parent, &data); - - if (error == GIT_ITEROVER) { - git_error_clear(); - error = 0; - } - - git_str_dispose(&fullpath); - - return error; -} - -int git_futils_fake_symlink(const char *target, const char *path) -{ - int retcode = GIT_ERROR; - int fd = git_futils_creat_withpath(path, 0755, 0644); - if (fd >= 0) { - retcode = p_write(fd, target, strlen(target)); - p_close(fd); - } - return retcode; -} - -static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) -{ - int error = 0; - char buffer[FILEIO_BUFSIZE]; - ssize_t len = 0; - - while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) - /* p_write() does not have the same semantics as write(). It loops - * internally and will return 0 when it has completed writing. - */ - error = p_write(ofd, buffer, len); - - if (len < 0) { - git_error_set(GIT_ERROR_OS, "read error while copying file"); - error = (int)len; - } - - if (error < 0) - git_error_set(GIT_ERROR_OS, "write error while copying file"); - - if (close_fd_when_done) { - p_close(ifd); - p_close(ofd); - } - - return error; -} - -int git_futils_cp(const char *from, const char *to, mode_t filemode) -{ - int ifd, ofd; - - if ((ifd = git_futils_open_ro(from)) < 0) - return ifd; - - if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { - p_close(ifd); - return git_fs_path_set_error(errno, to, "open for writing"); - } - - return cp_by_fd(ifd, ofd, true); -} - -int git_futils_touch(const char *path, time_t *when) -{ - struct p_timeval times[2]; - int ret; - - times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); - times[0].tv_usec = times[1].tv_usec = 0; - - ret = p_utimes(path, times); - - return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0; -} - -static int cp_link(const char *from, const char *to, size_t link_size) -{ - int error = 0; - ssize_t read_len; - char *link_data; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1); - link_data = git__malloc(alloc_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(from, link_data, link_size); - if (read_len != (ssize_t)link_size) { - git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from); - error = -1; - } - else { - link_data[read_len] = '\0'; - - if (p_symlink(link_data, to) < 0) { - git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'", - link_data, to); - error = -1; - } - } - - git__free(link_data); - return error; -} - -typedef struct { - const char *to_root; - git_str to; - ssize_t from_prefix; - uint32_t flags; - uint32_t mkdir_flags; - mode_t dirmode; -} cp_r_info; - -#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) - -static int _cp_r_mkdir(cp_r_info *info, git_str *from) -{ - int error = 0; - - /* create root directory the first time we need to create a directory */ - if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { - error = git_futils_mkdir( - info->to_root, info->dirmode, - (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); - - info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; - } - - /* create directory with root as base to prevent excess chmods */ - if (!error) - error = git_futils_mkdir_relative( - from->ptr + info->from_prefix, info->to_root, - info->dirmode, info->mkdir_flags, NULL); - - return error; -} - -static int _cp_r_callback(void *ref, git_str *from) -{ - int error = 0; - cp_r_info *info = ref; - struct stat from_st, to_st; - bool exists = false; - - if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && - from->ptr[git_fs_path_basename_offset(from)] == '.') - return 0; - - if ((error = git_str_joinpath( - &info->to, info->to_root, from->ptr + info->from_prefix)) < 0) - return error; - - if (!(error = git_fs_path_lstat(info->to.ptr, &to_st))) - exists = true; - else if (error != GIT_ENOTFOUND) - return error; - else { - git_error_clear(); - error = 0; - } - - if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0) - return error; - - if (S_ISDIR(from_st.st_mode)) { - mode_t oldmode = info->dirmode; - - /* if we are not chmod'ing, then overwrite dirmode */ - if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) - info->dirmode = from_st.st_mode; - - /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ - if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) - error = _cp_r_mkdir(info, from); - - /* recurse onto target directory */ - if (!error && (!exists || S_ISDIR(to_st.st_mode))) - error = git_fs_path_direach(from, 0, _cp_r_callback, info); - - if (oldmode != 0) - info->dirmode = oldmode; - - return error; - } - - if (exists) { - if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) - return 0; - - if (p_unlink(info->to.ptr) < 0) { - git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'", - info->to.ptr); - return GIT_EEXISTS; - } - } - - /* Done if this isn't a regular file or a symlink */ - if (!S_ISREG(from_st.st_mode) && - (!S_ISLNK(from_st.st_mode) || - (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) - return 0; - - /* Make container directory on demand if needed */ - if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && - (error = _cp_r_mkdir(info, from)) < 0) - return error; - - /* make symlink or regular file */ - if (info->flags & GIT_CPDIR_LINK_FILES) { - if ((error = p_link(from->ptr, info->to.ptr)) < 0) - git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr); - } else if (S_ISLNK(from_st.st_mode)) { - error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - } else { - mode_t usemode = from_st.st_mode; - - if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) - usemode = GIT_PERMS_FOR_WRITE(usemode); - - error = git_futils_cp(from->ptr, info->to.ptr, usemode); - } - - return error; -} - -int git_futils_cp_r( - const char *from, - const char *to, - uint32_t flags, - mode_t dirmode) -{ - int error; - git_str path = GIT_STR_INIT; - cp_r_info info; - - if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */ - return -1; - - memset(&info, 0, sizeof(info)); - info.to_root = to; - info.flags = flags; - info.dirmode = dirmode; - info.from_prefix = path.size; - git_str_init(&info.to, 0); - - /* precalculate mkdir flags */ - if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { - /* if not creating empty dirs, then use mkdir to create the path on - * demand right before files are copied. - */ - info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; - if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) - info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; - } else { - /* otherwise, we will do simple mkdir as directories are encountered */ - info.mkdir_flags = - ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; - } - - error = _cp_r_callback(&info, &path); - - git_str_dispose(&path); - git_str_dispose(&info.to); - - return error; -} - -int git_futils_filestamp_check( - git_futils_filestamp *stamp, const char *path) -{ - struct stat st; - - /* if the stamp is NULL, then always reload */ - if (stamp == NULL) - return 1; - - if (p_stat(path, &st) < 0) - return GIT_ENOTFOUND; - - if (stamp->mtime.tv_sec == st.st_mtime && -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec == st.st_mtime_nsec && -#endif - stamp->size == (uint64_t)st.st_size && - stamp->ino == (unsigned int)st.st_ino) - return 0; - - stamp->mtime.tv_sec = st.st_mtime; -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec = st.st_mtime_nsec; -#endif - stamp->size = (uint64_t)st.st_size; - stamp->ino = (unsigned int)st.st_ino; - - return 1; -} - -void git_futils_filestamp_set( - git_futils_filestamp *target, const git_futils_filestamp *source) -{ - if (source) - memcpy(target, source, sizeof(*target)); - else - memset(target, 0, sizeof(*target)); -} - - -void git_futils_filestamp_set_from_stat( - git_futils_filestamp *stamp, struct stat *st) -{ - if (st) { - stamp->mtime.tv_sec = st->st_mtime; -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec = st->st_mtime_nsec; -#else - stamp->mtime.tv_nsec = 0; -#endif - stamp->size = (uint64_t)st->st_size; - stamp->ino = (unsigned int)st->st_ino; - } else { - memset(stamp, 0, sizeof(*stamp)); - } -} - -int git_futils_fsync_dir(const char *path) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(path); - return 0; -#else - int fd, error = -1; - - if ((fd = p_open(path, O_RDONLY)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path); - return -1; - } - - if ((error = p_fsync(fd)) < 0) - git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path); - - p_close(fd); - return error; -#endif -} - -int git_futils_fsync_parent(const char *path) -{ - char *parent; - int error; - - if ((parent = git_fs_path_dirname(path)) == NULL) - return -1; - - error = git_futils_fsync_dir(parent); - git__free(parent); - return error; -} diff --git a/src/futils.h b/src/futils.h deleted file mode 100644 index a82ec41cc..000000000 --- a/src/futils.h +++ /dev/null @@ -1,402 +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_futils_h__ -#define INCLUDE_futils_h__ - -#include "common.h" - -#include "map.h" -#include "posix.h" -#include "fs_path.h" -#include "pool.h" -#include "strmap.h" -#include "hash.h" - -/** - * Filebuffer methods - * - * Read whole files into an in-memory buffer for processing - */ -extern int git_futils_readbuffer(git_str *obj, const char *path); -extern int git_futils_readbuffer_updated( - git_str *obj, - const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], - int *updated); -extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); - -/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We - * support these internally and they will be removed before the `open` call. - */ -#ifndef O_FSYNC -# define O_FSYNC (1 << 31) -#endif - -extern int git_futils_writebuffer( - const git_str *buf, const char *path, int open_flags, mode_t mode); - -/** - * File utils - * - * These are custom filesystem-related helper methods. They are - * rather high level, and wrap the underlying POSIX methods - * - * All these methods return 0 on success, - * or an error code on failure and an error message is set. - */ - -/** - * Create and open a file, while also - * creating all the folders in its path - */ -extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); - -/** - * Create and open a process-locked file - */ -extern int git_futils_creat_locked(const char *path, const mode_t mode); - -/** - * Create and open a process-locked file, while - * also creating all the folders in its path - */ -extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); - -/** - * Create a path recursively. - */ -extern int git_futils_mkdir_r(const char *path, const mode_t mode); - -/** - * Flags to pass to `git_futils_mkdir`. - * - * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. - * * GIT_MKDIR_PATH says to make all components in the path. - * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation - * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path - * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path - * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path - * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST - * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs - * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs - * - * Note that the chmod options will be executed even if the directory already - * exists, unless GIT_MKDIR_EXCL is given. - */ -typedef enum { - GIT_MKDIR_EXCL = 1, - GIT_MKDIR_PATH = 2, - GIT_MKDIR_CHMOD = 4, - GIT_MKDIR_CHMOD_PATH = 8, - GIT_MKDIR_SKIP_LAST = 16, - GIT_MKDIR_SKIP_LAST2 = 32, - GIT_MKDIR_VERIFY_DIR = 64, - GIT_MKDIR_REMOVE_FILES = 128, - GIT_MKDIR_REMOVE_SYMLINKS = 256 -} git_futils_mkdir_flags; - -struct git_futils_mkdir_perfdata -{ - size_t stat_calls; - size_t mkdir_calls; - size_t chmod_calls; -}; - -struct git_futils_mkdir_options -{ - git_strmap *dir_map; - git_pool *pool; - struct git_futils_mkdir_perfdata perfdata; -}; - -/** - * Create a directory or entire path. - * - * This makes a directory (and the entire path leading up to it if requested), - * and optionally chmods the directory immediately after (or each part of the - * path if requested). - * - * @param path The path to create, relative to base. - * @param base Root for relative path. These directories will never be made. - * @param mode The mode to use for created directories. - * @param flags Combination of the mkdir flags above. - * @param opts Extended options, or null. - * @return 0 on success, else error code - */ -extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); - -/** - * Create a directory or entire path. Similar to `git_futils_mkdir_relative` - * without performance data. - */ -extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); - -/** - * Create all the folders required to contain - * the full path of a file - */ -extern int git_futils_mkpath2file(const char *path, const mode_t mode); - -/** - * Flags to pass to `git_futils_rmdir_r`. - * - * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty - * dirs and generate error if any files are found. - * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy. - * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. - * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base - * if removing this item leaves them empty - * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR - * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself - */ -typedef enum { - GIT_RMDIR_EMPTY_HIERARCHY = 0, - GIT_RMDIR_REMOVE_FILES = (1 << 0), - GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), - GIT_RMDIR_EMPTY_PARENTS = (1 << 2), - GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), - GIT_RMDIR_SKIP_ROOT = (1 << 4) -} git_futils_rmdir_flags; - -/** - * Remove path and any files and directories beneath it. - * - * @param path Path to the top level directory to process. - * @param base Root for relative path. - * @param flags Combination of git_futils_rmdir_flags values - * @return 0 on success; -1 on error. - */ -extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); - -/** - * Create and open a temporary file with a `_git2_` suffix in a - * protected directory; the file created will created will honor - * the current `umask`. Writes the filename into path_out. - * - * This function uses a high-quality PRNG seeded by the system's - * entropy pool _where available_ and falls back to a simple seed - * (time plus system information) when not. This is suitable for - * writing within a protected directory, but the system's safe - * temporary file creation functions should be preferred where - * available when writing into world-writable (temp) directories. - * - * @return On success, an open file descriptor, else an error code < 0. - */ -extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode); - -/** - * Move a file on the filesystem, create the - * destination path if it doesn't exist - */ -extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); - -/** - * Copy a file - * - * The filemode will be used for the newly created file. - */ -extern int git_futils_cp( - const char *from, - const char *to, - mode_t filemode); - -/** - * Set the files atime and mtime to the given time, or the current time - * if `ts` is NULL. - */ -extern int git_futils_touch(const char *path, time_t *when); - -/** - * Flags that can be passed to `git_futils_cp_r`. - * - * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no - * files under them (otherwise directories will only be created lazily - * when a file inside them is copied). - * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. - * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. - * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, - * otherwise they are silently skipped. - * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` - * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the - * source file to the target; with this flag, always use 0666 (or 0777 if - * source has exec bits set) for target. - * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files - */ -typedef enum { - GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), - GIT_CPDIR_COPY_SYMLINKS = (1u << 1), - GIT_CPDIR_COPY_DOTFILES = (1u << 2), - GIT_CPDIR_OVERWRITE = (1u << 3), - GIT_CPDIR_CHMOD_DIRS = (1u << 4), - GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), - GIT_CPDIR_LINK_FILES = (1u << 6) -} git_futils_cpdir_flags; - -/** - * Copy a directory tree. - * - * This copies directories and files from one root to another. You can - * pass a combination of GIT_CPDIR flags as defined above. - * - * If you pass the CHMOD flag, then the dirmode will be applied to all - * directories that are created during the copy, overriding the natural - * permissions. If you do not pass the CHMOD flag, then the dirmode - * will actually be copied from the source files and the `dirmode` arg - * will be ignored. - */ -extern int git_futils_cp_r( - const char *from, - const char *to, - uint32_t flags, - mode_t dirmode); - -/** - * Open a file readonly and set error if needed. - */ -extern int git_futils_open_ro(const char *path); - -/** - * Truncate a file, creating it if it doesn't exist. - */ -extern int git_futils_truncate(const char *path, int mode); - -/** - * Get the filesize in bytes of a file - */ -extern int git_futils_filesize(uint64_t *out, git_file fd); - -#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0) -#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) -#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) - -#define GIT_MODE_PERMS_MASK 0777 -#define GIT_MODE_TYPE_MASK 0170000 -#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) -#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) - -/** - * Convert a mode_t from the OS to a legal git mode_t value. - */ -extern mode_t git_futils_canonical_mode(mode_t raw_mode); - - -/** - * Read-only map all or part of a file into memory. - * When possible this function should favor a virtual memory - * style mapping over some form of malloc()+read(), as the - * data access will be random and is not likely to touch the - * majority of the region requested. - * - * @param out buffer to populate with the mapping information. - * @param fd open descriptor to configure the mapping from. - * @param begin first byte to map, this should be page aligned. - * @param len number of bytes to map. - * @return - * - 0 on success; - * - -1 on error. - */ -extern int git_futils_mmap_ro( - git_map *out, - git_file fd, - off64_t begin, - size_t len); - -/** - * Read-only map an entire file. - * - * @param out buffer to populate with the mapping information. - * @param path path to file to be opened. - * @return - * - 0 on success; - * - GIT_ENOTFOUND if not found; - * - -1 on an unspecified OS related error. - */ -extern int git_futils_mmap_ro_file( - git_map *out, - const char *path); - -/** - * Release the memory associated with a previous memory mapping. - * @param map the mapping description previously configured. - */ -extern void git_futils_mmap_free(git_map *map); - -/** - * Create a "fake" symlink (text file containing the target path). - * - * @param target original symlink target - * @param path symlink file to be created - * @return 0 on success, -1 on error - */ -extern int git_futils_fake_symlink(const char *target, const char *path); - -/** - * A file stamp represents a snapshot of information about a file that can - * be used to test if the file changes. This portable implementation is - * based on stat data about that file, but it is possible that OS specific - * versions could be implemented in the future. - */ -typedef struct { - struct timespec mtime; - uint64_t size; - unsigned int ino; -} git_futils_filestamp; - -/** - * Compare stat information for file with reference info. - * - * This function updates the file stamp to current data for the given path - * and returns 0 if the file is up-to-date relative to the prior setting, - * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't - * exist. This will not call git_error_set, so you must set the error if you - * plan to return an error. - * - * @param stamp File stamp to be checked - * @param path Path to stat and check if changed - * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat - */ -extern int git_futils_filestamp_check( - git_futils_filestamp *stamp, const char *path); - -/** - * Set or reset file stamp data - * - * This writes the target file stamp. If the source is NULL, this will set - * the target stamp to values that will definitely be out of date. If the - * source is not NULL, this copies the source values to the target. - * - * @param tgt File stamp to write to - * @param src File stamp to copy from or NULL to clear the target - */ -extern void git_futils_filestamp_set( - git_futils_filestamp *tgt, const git_futils_filestamp *src); - -/** - * Set file stamp data from stat structure - */ -extern void git_futils_filestamp_set_from_stat( - git_futils_filestamp *stamp, struct stat *st); - -/** - * `fsync` the parent directory of the given path, if `fsync` is - * supported for directories on this platform. - * - * @param path Path of the directory to sync. - * @return 0 on success, -1 on error - */ -extern int git_futils_fsync_dir(const char *path); - -/** - * `fsync` the parent directory of the given path, if `fsync` is - * supported for directories on this platform. - * - * @param path Path of the file whose parent directory should be synced. - * @return 0 on success, -1 on error - */ -extern int git_futils_fsync_parent(const char *path); - -#endif diff --git a/src/graph.c b/src/graph.c deleted file mode 100644 index 35e914f74..000000000 --- a/src/graph.c +++ /dev/null @@ -1,249 +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 "common.h" - -#include "revwalk.h" -#include "merge.h" -#include "git2/graph.h" - -static int interesting(git_pqueue *list, git_commit_list *roots) -{ - unsigned int i; - - for (i = 0; i < git_pqueue_size(list); i++) { - git_commit_list_node *commit = git_pqueue_get(list, i); - if ((commit->flags & STALE) == 0) - return 1; - } - - while(roots) { - if ((roots->item->flags & STALE) == 0) - return 1; - roots = roots->next; - } - - return 0; -} - -static int mark_parents(git_revwalk *walk, git_commit_list_node *one, - git_commit_list_node *two) -{ - unsigned int i; - git_commit_list *roots = NULL; - git_pqueue list; - - /* if the commit is repeated, we have a our merge base already */ - if (one == two) { - one->flags |= PARENT1 | PARENT2 | RESULT; - return 0; - } - - if (git_pqueue_init(&list, 0, 2, git_commit_list_generation_cmp) < 0) - return -1; - - if (git_commit_list_parse(walk, one) < 0) - goto on_error; - one->flags |= PARENT1; - if (git_pqueue_insert(&list, one) < 0) - goto on_error; - - if (git_commit_list_parse(walk, two) < 0) - goto on_error; - two->flags |= PARENT2; - if (git_pqueue_insert(&list, two) < 0) - goto on_error; - - /* as long as there are non-STALE commits */ - while (interesting(&list, roots)) { - git_commit_list_node *commit = git_pqueue_pop(&list); - unsigned int flags; - - if (commit == NULL) - break; - - flags = commit->flags & (PARENT1 | PARENT2 | STALE); - if (flags == (PARENT1 | PARENT2)) { - if (!(commit->flags & RESULT)) - commit->flags |= RESULT; - /* we mark the parents of a merge stale */ - flags |= STALE; - } - - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if ((p->flags & flags) == flags) - continue; - - if (git_commit_list_parse(walk, p) < 0) - goto on_error; - - p->flags |= flags; - if (git_pqueue_insert(&list, p) < 0) - goto on_error; - } - - /* Keep track of root commits, to make sure the path gets marked */ - if (commit->out_degree == 0) { - if (git_commit_list_insert(commit, &roots) == NULL) - goto on_error; - } - } - - git_commit_list_free(&roots); - git_pqueue_free(&list); - return 0; - -on_error: - git_commit_list_free(&roots); - git_pqueue_free(&list); - return -1; -} - - -static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two, - size_t *ahead, size_t *behind) -{ - git_commit_list_node *commit; - git_pqueue pq; - int error = 0, i; - *ahead = 0; - *behind = 0; - - if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0) - return -1; - - if ((error = git_pqueue_insert(&pq, one)) < 0 || - (error = git_pqueue_insert(&pq, two)) < 0) - goto done; - - while ((commit = git_pqueue_pop(&pq)) != NULL) { - if (commit->flags & RESULT || - (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2)) - continue; - else if (commit->flags & PARENT1) - (*ahead)++; - else if (commit->flags & PARENT2) - (*behind)++; - - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if ((error = git_pqueue_insert(&pq, p)) < 0) - goto done; - } - commit->flags |= RESULT; - } - -done: - git_pqueue_free(&pq); - return error; -} - -int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, - const git_oid *local, const git_oid *upstream) -{ - git_revwalk *walk; - git_commit_list_node *commit_u, *commit_l; - - if (git_revwalk_new(&walk, repo) < 0) - return -1; - - commit_u = git_revwalk__commit_lookup(walk, upstream); - if (commit_u == NULL) - goto on_error; - - commit_l = git_revwalk__commit_lookup(walk, local); - if (commit_l == NULL) - goto on_error; - - if (mark_parents(walk, commit_l, commit_u) < 0) - goto on_error; - if (ahead_behind(commit_l, commit_u, ahead, behind) < 0) - goto on_error; - - git_revwalk_free(walk); - - return 0; - -on_error: - git_revwalk_free(walk); - return -1; -} - -int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor) -{ - if (git_oid_equal(commit, ancestor)) - return 0; - - return git_graph_reachable_from_any(repo, ancestor, commit, 1); -} - -int git_graph_reachable_from_any( - git_repository *repo, - const git_oid *commit_id, - const git_oid descendant_array[], - size_t length) -{ - git_revwalk *walk = NULL; - git_vector list; - git_commit_list *result = NULL; - git_commit_list_node *commit; - size_t i; - uint32_t minimum_generation = 0xffffffff; - int error = 0; - - if (!length) - return 0; - - for (i = 0; i < length; ++i) { - if (git_oid_equal(commit_id, &descendant_array[i])) - return 1; - } - - if ((error = git_vector_init(&list, length + 1, NULL)) < 0) - return error; - - if ((error = git_revwalk_new(&walk, repo)) < 0) - goto done; - - for (i = 0; i < length; i++) { - commit = git_revwalk__commit_lookup(walk, &descendant_array[i]); - if (commit == NULL) { - error = -1; - goto done; - } - - git_vector_insert(&list, commit); - if (minimum_generation > commit->generation) - minimum_generation = commit->generation; - } - - commit = git_revwalk__commit_lookup(walk, commit_id); - if (commit == NULL) { - error = -1; - goto done; - } - - if (minimum_generation > commit->generation) - minimum_generation = commit->generation; - - if ((error = git_merge__bases_many(&result, walk, commit, &list, minimum_generation)) < 0) - goto done; - - if (result) { - error = git_oid_equal(commit_id, &result->item->oid); - } else { - /* No merge-base found, it's not a descendant */ - error = 0; - } - -done: - git_commit_list_free(&result); - git_vector_free(&list); - git_revwalk_free(walk); - return error; -} diff --git a/src/hash.c b/src/hash.c deleted file mode 100644 index 98ceb05d2..000000000 --- a/src/hash.c +++ /dev/null @@ -1,142 +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 "hash.h" - -int git_hash_global_init(void) -{ - return git_hash_sha1_global_init(); -} - -int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm) -{ - int error; - - switch (algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - error = git_hash_sha1_ctx_init(&ctx->ctx.sha1); - break; - default: - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - error = -1; - } - - ctx->algorithm = algorithm; - return error; -} - -void git_hash_ctx_cleanup(git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1); - return; - default: - /* unreachable */ ; - } -} - -int git_hash_init(git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_init(&ctx->ctx.sha1); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_update(&ctx->ctx.sha1, data, len); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_final(unsigned char *out, git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_final(out, &ctx->ctx.sha1); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_buf( - unsigned char *out, - const void *data, - size_t len, - git_hash_algorithm_t algorithm) -{ - git_hash_ctx ctx; - int error = 0; - - if (git_hash_ctx_init(&ctx, algorithm) < 0) - return -1; - - if ((error = git_hash_update(&ctx, data, len)) >= 0) - error = git_hash_final(out, &ctx); - - git_hash_ctx_cleanup(&ctx); - - return error; -} - -int git_hash_vec( - unsigned char *out, - git_str_vec *vec, - size_t n, - git_hash_algorithm_t algorithm) -{ - git_hash_ctx ctx; - size_t i; - int error = 0; - - if (git_hash_ctx_init(&ctx, algorithm) < 0) - return -1; - - for (i = 0; i < n; i++) { - if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) - goto done; - } - - error = git_hash_final(out, &ctx); - -done: - git_hash_ctx_cleanup(&ctx); - - return error; -} - -int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len) -{ - static char hex[] = "0123456789abcdef"; - char *str = out; - size_t i; - - for (i = 0; i < hash_len; i++) { - *str++ = hex[hash[i] >> 4]; - *str++ = hex[hash[i] & 0x0f]; - } - - *str++ = '\0'; - - return 0; -} diff --git a/src/hash.h b/src/hash.h deleted file mode 100644 index 507c1cb25..000000000 --- a/src/hash.h +++ /dev/null @@ -1,46 +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_hash_h__ -#define INCLUDE_hash_h__ - -#include "common.h" - -#include "hash/sha1.h" - -typedef struct { - void *data; - size_t len; -} git_str_vec; - -typedef enum { - GIT_HASH_ALGORITHM_NONE = 0, - GIT_HASH_ALGORITHM_SHA1 -} git_hash_algorithm_t; - -typedef struct git_hash_ctx { - union { - git_hash_sha1_ctx sha1; - } ctx; - git_hash_algorithm_t algorithm; -} git_hash_ctx; - -int git_hash_global_init(void); - -int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm); -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(unsigned char *out, git_hash_ctx *c); - -int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm); -int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm); - -int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len); - -#endif diff --git a/src/hash/sha1.h b/src/hash/sha1.h deleted file mode 100644 index 4b4dae3f8..000000000 --- a/src/hash/sha1.h +++ /dev/null @@ -1,40 +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_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 - -#define GIT_HASH_SHA1_SIZE 20 - -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(unsigned char *out, git_hash_sha1_ctx *c); - -#endif diff --git a/src/hash/sha1/collisiondetect.c b/src/hash/sha1/collisiondetect.c deleted file mode 100644 index ec7059c4c..000000000 --- a/src/hash/sha1/collisiondetect.c +++ /dev/null @@ -1,48 +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 "collisiondetect.h" - -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) -{ - GIT_ASSERT_ARG(ctx); - SHA1DCInit(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - SHA1DCUpdate(&ctx->c, data, len); - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - if (SHA1DCFinal(out, &ctx->c)) { - git_error_set(GIT_ERROR_SHA1, "SHA1 collision attack detected"); - return -1; - } - - return 0; -} diff --git a/src/hash/sha1/collisiondetect.h b/src/hash/sha1/collisiondetect.h deleted file mode 100644 index eb88e86c1..000000000 --- a/src/hash/sha1/collisiondetect.h +++ /dev/null @@ -1,19 +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_hash_sha1_collisiondetect_h__ -#define INCLUDE_hash_sha1_collisiondetect_h__ - -#include "hash/sha1.h" - -#include "sha1dc/sha1.h" - -struct git_hash_sha1_ctx { - SHA1_CTX c; -}; - -#endif diff --git a/src/hash/sha1/common_crypto.c b/src/hash/sha1/common_crypto.c deleted file mode 100644 index 9d608f449..000000000 --- a/src/hash/sha1/common_crypto.c +++ /dev/null @@ -1,57 +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 "common_crypto.h" - -#define CC_LONG_MAX ((CC_LONG)-1) - -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) -{ - GIT_ASSERT_ARG(ctx); - CC_SHA1_Init(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - const unsigned char *data = _data; - - GIT_ASSERT_ARG(ctx); - - while (len > 0) { - CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; - - CC_SHA1_Update(&ctx->c, data, chunk); - - data += chunk; - len -= chunk; - } - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - CC_SHA1_Final(out, &ctx->c); - return 0; -} diff --git a/src/hash/sha1/common_crypto.h b/src/hash/sha1/common_crypto.h deleted file mode 100644 index a5fcfb33e..000000000 --- a/src/hash/sha1/common_crypto.h +++ /dev/null @@ -1,19 +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_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/sha1/generic.c b/src/hash/sha1/generic.c deleted file mode 100644 index 85b34c578..000000000 --- a/src/hash/sha1/generic.c +++ /dev/null @@ -1,300 +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 "generic.h" - -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) - -/* - * Force usage of rol or ror by selecting the one with the smaller constant. - * It _can_ generate slightly smaller code (a constant of 1 is special), but - * perhaps more importantly it's possibly faster on any uarch that does a - * rotate with a loop. - */ - -#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })) -#define SHA_ROL(x,n) SHA_ASM("rol", x, n) -#define SHA_ROR(x,n) SHA_ASM("ror", x, n) - -#else - -#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) -#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) -#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) - -#endif - -/* - * If you have 32 registers or more, the compiler can (and should) - * try to change the array[] accesses into registers. However, on - * machines with less than ~25 registers, that won't really work, - * and at least gcc will make an unholy mess of it. - * - * So to avoid that mess which just slows things down, we force - * the stores to memory to actually happen (we might be better off - * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as - * suggested by Artur Skawina - that will also make gcc unable to - * try to do the silly "optimize away loads" part because it won't - * see what the value will be). - * - * Ben Herrenschmidt reports that on PPC, the C version comes close - * to the optimized asm with this (ie on PPC you don't want that - * 'volatile', since there are lots of registers). - * - * On ARM we get the best code generation by forcing a full memory barrier - * between each SHA_ROUND, otherwise gcc happily get wild with spilling and - * the stack frame size simply explode and performance goes down the drain. - */ - -#if defined(__i386__) || defined(__x86_64__) - #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) -#elif defined(__GNUC__) && defined(__arm__) - #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) -#else - #define setW(x, val) (W(x) = (val)) -#endif - -/* - * Performance might be improved if the CPU architecture is OK with - * unaligned 32-bit loads and a fast ntohl() is available. - * Otherwise fall back to byte loads and shifts which is portable, - * and is faster on architectures with memory alignment issues. - */ - -#if defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) || \ - defined(__ppc__) || defined(__ppc64__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__s390__) || defined(__s390x__) - -#define get_be32(p) ntohl(*(const unsigned int *)(p)) -#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) - -#else - -#define get_be32(p) ( \ - (*((const unsigned char *)(p) + 0) << 24) | \ - (*((const unsigned char *)(p) + 1) << 16) | \ - (*((const unsigned char *)(p) + 2) << 8) | \ - (*((const unsigned char *)(p) + 3) << 0) ) -#define put_be32(p, v) do { \ - unsigned int __v = (v); \ - *((unsigned char *)(p) + 0) = __v >> 24; \ - *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; } while (0) - -#endif - -/* This "rolls" over the 512-bit array */ -#define W(x) (array[(x)&15]) - -/* - * Where do we get the source from? The first 16 iterations get it from - * the input data, the next mix it from the 512-bit array. - */ -#define SHA_SRC(t) get_be32(data + t) -#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) - -#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ - unsigned int TEMP = input(t); setW(t, TEMP); \ - E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ - B = SHA_ROR(B, 2); } while (0) - -#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) -#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) -#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) -#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_sha1_ctx *ctx, const unsigned int *data) -{ - unsigned int A,B,C,D,E; - unsigned int array[16]; - - A = ctx->H[0]; - B = ctx->H[1]; - C = ctx->H[2]; - D = ctx->H[3]; - E = ctx->H[4]; - - /* Round 1 - iterations 0-16 take their input from 'data' */ - T_0_15( 0, A, B, C, D, E); - T_0_15( 1, E, A, B, C, D); - T_0_15( 2, D, E, A, B, C); - T_0_15( 3, C, D, E, A, B); - T_0_15( 4, B, C, D, E, A); - T_0_15( 5, A, B, C, D, E); - T_0_15( 6, E, A, B, C, D); - T_0_15( 7, D, E, A, B, C); - T_0_15( 8, C, D, E, A, B); - T_0_15( 9, B, C, D, E, A); - T_0_15(10, A, B, C, D, E); - T_0_15(11, E, A, B, C, D); - T_0_15(12, D, E, A, B, C); - T_0_15(13, C, D, E, A, B); - T_0_15(14, B, C, D, E, A); - T_0_15(15, A, B, C, D, E); - - /* Round 1 - tail. Input from 512-bit mixing array */ - T_16_19(16, E, A, B, C, D); - T_16_19(17, D, E, A, B, C); - T_16_19(18, C, D, E, A, B); - T_16_19(19, B, C, D, E, A); - - /* Round 2 */ - T_20_39(20, A, B, C, D, E); - T_20_39(21, E, A, B, C, D); - T_20_39(22, D, E, A, B, C); - T_20_39(23, C, D, E, A, B); - T_20_39(24, B, C, D, E, A); - T_20_39(25, A, B, C, D, E); - T_20_39(26, E, A, B, C, D); - T_20_39(27, D, E, A, B, C); - T_20_39(28, C, D, E, A, B); - T_20_39(29, B, C, D, E, A); - T_20_39(30, A, B, C, D, E); - T_20_39(31, E, A, B, C, D); - T_20_39(32, D, E, A, B, C); - T_20_39(33, C, D, E, A, B); - T_20_39(34, B, C, D, E, A); - T_20_39(35, A, B, C, D, E); - T_20_39(36, E, A, B, C, D); - T_20_39(37, D, E, A, B, C); - T_20_39(38, C, D, E, A, B); - T_20_39(39, B, C, D, E, A); - - /* Round 3 */ - T_40_59(40, A, B, C, D, E); - T_40_59(41, E, A, B, C, D); - T_40_59(42, D, E, A, B, C); - T_40_59(43, C, D, E, A, B); - T_40_59(44, B, C, D, E, A); - T_40_59(45, A, B, C, D, E); - T_40_59(46, E, A, B, C, D); - T_40_59(47, D, E, A, B, C); - T_40_59(48, C, D, E, A, B); - T_40_59(49, B, C, D, E, A); - T_40_59(50, A, B, C, D, E); - T_40_59(51, E, A, B, C, D); - T_40_59(52, D, E, A, B, C); - T_40_59(53, C, D, E, A, B); - T_40_59(54, B, C, D, E, A); - T_40_59(55, A, B, C, D, E); - T_40_59(56, E, A, B, C, D); - T_40_59(57, D, E, A, B, C); - T_40_59(58, C, D, E, A, B); - T_40_59(59, B, C, D, E, A); - - /* Round 4 */ - T_60_79(60, A, B, C, D, E); - T_60_79(61, E, A, B, C, D); - T_60_79(62, D, E, A, B, C); - T_60_79(63, C, D, E, A, B); - T_60_79(64, B, C, D, E, A); - T_60_79(65, A, B, C, D, E); - T_60_79(66, E, A, B, C, D); - T_60_79(67, D, E, A, B, C); - T_60_79(68, C, D, E, A, B); - T_60_79(69, B, C, D, E, A); - T_60_79(70, A, B, C, D, E); - T_60_79(71, E, A, B, C, D); - T_60_79(72, D, E, A, B, C); - T_60_79(73, C, D, E, A, B); - T_60_79(74, B, C, D, E, A); - T_60_79(75, A, B, C, D, E); - T_60_79(76, E, A, B, C, D); - T_60_79(77, D, E, A, B, C); - T_60_79(78, C, D, E, A, B); - T_60_79(79, B, C, D, E, A); - - ctx->H[0] += A; - ctx->H[1] += B; - ctx->H[2] += C; - ctx->H[3] += D; - ctx->H[4] += E; -} - -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; - - /* Initialize H with the magic constants (see FIPS180 for constants) */ - ctx->H[0] = 0x67452301; - ctx->H[1] = 0xefcdab89; - ctx->H[2] = 0x98badcfe; - ctx->H[3] = 0x10325476; - ctx->H[4] = 0xc3d2e1f0; - - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - unsigned int lenW = ctx->size & 63; - - ctx->size += len; - - /* Read the data into W and process blocks as they get full */ - if (lenW) { - unsigned int left = 64 - lenW; - if (len < left) - left = (unsigned int)len; - memcpy(lenW + (char *)ctx->W, data, left); - lenW = (lenW + left) & 63; - len -= left; - data = ((const char *)data + left); - if (lenW) - return 0; - hash__block(ctx, ctx->W); - } - while (len >= 64) { - hash__block(ctx, data); - data = ((const char *)data + 64); - len -= 64; - } - if (len) - memcpy(ctx->W, data, len); - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - static const unsigned char pad[64] = { 0x80 }; - unsigned int padlen[2]; - int i; - - /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ - padlen[0] = htonl((uint32_t)(ctx->size >> 29)); - padlen[1] = htonl((uint32_t)(ctx->size << 3)); - - i = ctx->size & 63; - 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++) - put_be32(out + i*4, ctx->H[i]); - - return 0; -} diff --git a/src/hash/sha1/generic.h b/src/hash/sha1/generic.h deleted file mode 100644 index 53fc0823e..000000000 --- a/src/hash/sha1/generic.h +++ /dev/null @@ -1,19 +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_hash_sha1_generic_h__ -#define INCLUDE_hash_sha1_generic_h__ - -#include "hash/sha1.h" - -struct git_hash_sha1_ctx { - uint64_t size; - unsigned int H[5]; - unsigned int W[16]; -}; - -#endif diff --git a/src/hash/sha1/mbedtls.c b/src/hash/sha1/mbedtls.c deleted file mode 100644 index 56016bec8..000000000 --- a/src/hash/sha1/mbedtls.c +++ /dev/null @@ -1,46 +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 "mbedtls.h" - -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) -{ - if (ctx) - mbedtls_sha1_free(&ctx->c); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_init(&ctx->c); - mbedtls_sha1_starts(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_update(&ctx->c, data, len); - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_finish(&ctx->c, out); - return 0; -} diff --git a/src/hash/sha1/mbedtls.h b/src/hash/sha1/mbedtls.h deleted file mode 100644 index 15f7462a4..000000000 --- a/src/hash/sha1/mbedtls.h +++ /dev/null @@ -1,19 +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_hash_sha1_mbedtls_h__ -#define INCLUDE_hash_sha1_mbedtls_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - mbedtls_sha1_context c; -}; - -#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/hash/sha1/openssl.c b/src/hash/sha1/openssl.c deleted file mode 100644 index 64bf99b3c..000000000 --- a/src/hash/sha1/openssl.c +++ /dev/null @@ -1,59 +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 "openssl.h" - -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) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Init(&ctx->c) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to initialize hash context"); - return -1; - } - - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Update(&ctx->c, data, len) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to update hash"); - return -1; - } - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Final(out, &ctx->c) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to finalize hash"); - return -1; - } - - return 0; -} diff --git a/src/hash/sha1/openssl.h b/src/hash/sha1/openssl.h deleted file mode 100644 index a223ca03e..000000000 --- a/src/hash/sha1/openssl.h +++ /dev/null @@ -1,19 +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_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/sha1/sha1dc/sha1.c b/src/hash/sha1/sha1dc/sha1.c deleted file mode 100644 index 929822728..000000000 --- a/src/hash/sha1/sha1dc/sha1.c +++ /dev/null @@ -1,1909 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow (danshu@microsoft.com) -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#include -#include -#include -#include /* make sure macros like _BIG_ENDIAN visible */ -#endif - -#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C -#include SHA1DC_CUSTOM_INCLUDE_SHA1_C -#endif - -#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT -#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 -#endif - -#include "sha1.h" -#include "ubc_check.h" - -#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ - defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ - defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ - defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ - defined(__386) || defined(_M_X64) || defined(_M_AMD64)) -#define SHA1DC_ON_INTEL_LIKE_PROCESSOR -#endif - -/* - Because Little-Endian architectures are most common, - we only set SHA1DC_BIGENDIAN if one of these conditions is met. - Note that all MSFT platforms are little endian, - so none of these will be defined under the MSC compiler. - If you are compiling on a big endian platform and your compiler does not define one of these, - you will have to add whatever macros your tool chain defines to indicate Big-Endianness. - */ - -#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) -/* - * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn - * rev #165881). See - * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html - * - * This also works under clang since 3.2, it copied the GCC-ism. See - * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ - * predefined macro", 2012-07-27) - */ -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike */ -#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) -/* - * Should detect Big Endian under glibc.git since 14245eb70e ("entered - * into RCS", 1992-11-25). Defined in which will have been - * brought in by standard headers. See glibc.git and - * https://sourceforge.net/p/predef/wiki/Endianness/ - */ -#if __BYTE_ORDER == __BIG_ENDIAN -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike or glibc */ -#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) -/* - * *BSD and newlib (embedded linux, cygwin, etc). - * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents - * this condition from matching with Solaris/sparc. - * (Solaris defines only one endian macro) - */ -#if _BYTE_ORDER == _BIG_ENDIAN -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike or glibc or *BSD or newlib */ -#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ - defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ - defined(__sparc)) -/* - * Should define Big Endian for a whitelist of known processors. See - * https://sourceforge.net/p/predef/wiki/Endianness/ and - * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html - */ -#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 or */ - -/* We do nothing more here for now */ -/*#error "Uncomment this to see if you fall through all the detection"*/ - -#endif /* Big Endian detection */ - -#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) -#undef SHA1DC_BIGENDIAN -#endif -#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) -#define SHA1DC_BIGENDIAN -#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 /*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)))) - -#define sha1_bswap32(x) \ - {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} - -#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) - -#ifdef SHA1DC_BIGENDIAN - #define sha1_load(m, t, temp) { temp = m[t]; } -#else - #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } -#endif - -#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x - -#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) -#define sha1_f2(b,c,d) ((b)^(c)^(d)) -#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) -#define sha1_f4(b,c,d) ((b)^(c)^(d)) - -#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } - -#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } - -#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ - {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} - -#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } - - -#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; - -#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION -void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) -{ - uint32_t W[80]; - uint32_t a,b,c,d,e; - unsigned i; - - memcpy(W, m, 16 * 4); - for (i = 16; i < 80; ++i) - W[i] = sha1_mix(W, i); - - a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; - - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); - - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); - - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); - - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} -#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ - - -static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) -{ - uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; - - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); - - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); - - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); - - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} - - - -void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) -{ - uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; - uint32_t temp; - -#ifdef DOSTORESTATE00 - SHA1_STORE_STATE(0) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); - -#ifdef DOSTORESTATE01 - SHA1_STORE_STATE(1) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); - -#ifdef DOSTORESTATE02 - SHA1_STORE_STATE(2) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); - -#ifdef DOSTORESTATE03 - SHA1_STORE_STATE(3) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); - -#ifdef DOSTORESTATE04 - SHA1_STORE_STATE(4) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); - -#ifdef DOSTORESTATE05 - SHA1_STORE_STATE(5) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); - -#ifdef DOSTORESTATE06 - SHA1_STORE_STATE(6) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); - -#ifdef DOSTORESTATE07 - SHA1_STORE_STATE(7) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); - -#ifdef DOSTORESTATE08 - SHA1_STORE_STATE(8) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); - -#ifdef DOSTORESTATE09 - SHA1_STORE_STATE(9) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); - -#ifdef DOSTORESTATE10 - SHA1_STORE_STATE(10) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); - -#ifdef DOSTORESTATE11 - SHA1_STORE_STATE(11) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); - -#ifdef DOSTORESTATE12 - SHA1_STORE_STATE(12) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); - -#ifdef DOSTORESTATE13 - SHA1_STORE_STATE(13) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); - -#ifdef DOSTORESTATE14 - SHA1_STORE_STATE(14) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); - -#ifdef DOSTORESTATE15 - SHA1_STORE_STATE(15) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); - -#ifdef DOSTORESTATE16 - SHA1_STORE_STATE(16) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); - -#ifdef DOSTORESTATE17 - SHA1_STORE_STATE(17) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); - -#ifdef DOSTORESTATE18 - SHA1_STORE_STATE(18) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); - -#ifdef DOSTORESTATE19 - SHA1_STORE_STATE(19) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); - - - -#ifdef DOSTORESTATE20 - SHA1_STORE_STATE(20) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); - -#ifdef DOSTORESTATE21 - SHA1_STORE_STATE(21) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); - -#ifdef DOSTORESTATE22 - SHA1_STORE_STATE(22) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); - -#ifdef DOSTORESTATE23 - SHA1_STORE_STATE(23) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); - -#ifdef DOSTORESTATE24 - SHA1_STORE_STATE(24) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); - -#ifdef DOSTORESTATE25 - SHA1_STORE_STATE(25) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); - -#ifdef DOSTORESTATE26 - SHA1_STORE_STATE(26) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); - -#ifdef DOSTORESTATE27 - SHA1_STORE_STATE(27) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); - -#ifdef DOSTORESTATE28 - SHA1_STORE_STATE(28) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); - -#ifdef DOSTORESTATE29 - SHA1_STORE_STATE(29) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); - -#ifdef DOSTORESTATE30 - SHA1_STORE_STATE(30) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); - -#ifdef DOSTORESTATE31 - SHA1_STORE_STATE(31) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); - -#ifdef DOSTORESTATE32 - SHA1_STORE_STATE(32) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); - -#ifdef DOSTORESTATE33 - SHA1_STORE_STATE(33) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); - -#ifdef DOSTORESTATE34 - SHA1_STORE_STATE(34) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); - -#ifdef DOSTORESTATE35 - SHA1_STORE_STATE(35) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); - -#ifdef DOSTORESTATE36 - SHA1_STORE_STATE(36) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); - -#ifdef DOSTORESTATE37 - SHA1_STORE_STATE(37) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); - -#ifdef DOSTORESTATE38 - SHA1_STORE_STATE(38) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); - -#ifdef DOSTORESTATE39 - SHA1_STORE_STATE(39) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); - - - -#ifdef DOSTORESTATE40 - SHA1_STORE_STATE(40) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); - -#ifdef DOSTORESTATE41 - SHA1_STORE_STATE(41) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); - -#ifdef DOSTORESTATE42 - SHA1_STORE_STATE(42) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); - -#ifdef DOSTORESTATE43 - SHA1_STORE_STATE(43) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); - -#ifdef DOSTORESTATE44 - SHA1_STORE_STATE(44) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); - -#ifdef DOSTORESTATE45 - SHA1_STORE_STATE(45) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); - -#ifdef DOSTORESTATE46 - SHA1_STORE_STATE(46) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); - -#ifdef DOSTORESTATE47 - SHA1_STORE_STATE(47) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); - -#ifdef DOSTORESTATE48 - SHA1_STORE_STATE(48) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); - -#ifdef DOSTORESTATE49 - SHA1_STORE_STATE(49) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); - -#ifdef DOSTORESTATE50 - SHA1_STORE_STATE(50) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); - -#ifdef DOSTORESTATE51 - SHA1_STORE_STATE(51) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); - -#ifdef DOSTORESTATE52 - SHA1_STORE_STATE(52) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); - -#ifdef DOSTORESTATE53 - SHA1_STORE_STATE(53) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); - -#ifdef DOSTORESTATE54 - SHA1_STORE_STATE(54) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); - -#ifdef DOSTORESTATE55 - SHA1_STORE_STATE(55) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); - -#ifdef DOSTORESTATE56 - SHA1_STORE_STATE(56) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); - -#ifdef DOSTORESTATE57 - SHA1_STORE_STATE(57) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); - -#ifdef DOSTORESTATE58 - SHA1_STORE_STATE(58) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); - -#ifdef DOSTORESTATE59 - SHA1_STORE_STATE(59) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); - - - - -#ifdef DOSTORESTATE60 - SHA1_STORE_STATE(60) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); - -#ifdef DOSTORESTATE61 - SHA1_STORE_STATE(61) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); - -#ifdef DOSTORESTATE62 - SHA1_STORE_STATE(62) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); - -#ifdef DOSTORESTATE63 - SHA1_STORE_STATE(63) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); - -#ifdef DOSTORESTATE64 - SHA1_STORE_STATE(64) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); - -#ifdef DOSTORESTATE65 - SHA1_STORE_STATE(65) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); - -#ifdef DOSTORESTATE66 - SHA1_STORE_STATE(66) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); - -#ifdef DOSTORESTATE67 - SHA1_STORE_STATE(67) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); - -#ifdef DOSTORESTATE68 - SHA1_STORE_STATE(68) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); - -#ifdef DOSTORESTATE69 - SHA1_STORE_STATE(69) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); - -#ifdef DOSTORESTATE70 - SHA1_STORE_STATE(70) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); - -#ifdef DOSTORESTATE71 - SHA1_STORE_STATE(71) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); - -#ifdef DOSTORESTATE72 - SHA1_STORE_STATE(72) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); - -#ifdef DOSTORESTATE73 - SHA1_STORE_STATE(73) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); - -#ifdef DOSTORESTATE74 - SHA1_STORE_STATE(74) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); - -#ifdef DOSTORESTATE75 - SHA1_STORE_STATE(75) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); - -#ifdef DOSTORESTATE76 - SHA1_STORE_STATE(76) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); - -#ifdef DOSTORESTATE77 - SHA1_STORE_STATE(77) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); - -#ifdef DOSTORESTATE78 - SHA1_STORE_STATE(78) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); - -#ifdef DOSTORESTATE79 - SHA1_STORE_STATE(79) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); - - - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} - - - - -#define SHA1_RECOMPRESS(t) \ -static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ -{ \ - uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ - if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ - if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ - if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ - if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ - if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ - if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ - if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ - if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ - if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ - if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ - if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ - if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ - if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ - if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ - if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ - if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ - if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ - if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ - if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ - if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ - if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ - if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ - if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ - if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ - if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ - if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ - if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ - if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ - if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ - if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ - if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ - if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ - if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ - if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ - if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ - if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ - if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ - if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ - if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ - if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ - if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ - if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ - if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ - if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ - if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ - if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ - if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ - if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ - if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ - if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ - if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ - if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ - if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ - if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ - if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ - if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ - if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ - if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ - if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ - if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ - if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ - if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ - if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ - if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ - if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ - if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ - if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ - if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ - if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ - if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ - if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ - if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ - if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ - if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ - if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ - if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ - if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ - if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ - if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ - if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ - ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ - a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ - if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ - if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ - if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ - if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ - if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ - if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ - if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ - if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ - if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ - if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ - if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ - if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ - if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ - if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ - if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ - if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ - if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ - if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ - if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ - if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ - if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ - if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ - if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ - if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ - if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ - if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ - if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ - if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ - if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ - if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ - if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ - if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ - if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ - if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ - if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ - if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ - if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ - if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ - if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ - if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ - if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ - if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ - if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ - if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ - if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ - if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ - if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ - if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ - if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ - if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ - if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ - if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ - if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ - if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ - if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ - if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ - if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ - if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ - if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ - if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ - if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ - if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ - if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ - if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ - if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ - if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ - if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ - if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ - if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ - if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ - if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ - if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ - if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ - if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ - if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ - if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ - if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ - if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ - if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ - if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ - ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ -} - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */ -#endif - -#ifdef DOSTORESTATE0 -SHA1_RECOMPRESS(0) -#endif - -#ifdef DOSTORESTATE1 -SHA1_RECOMPRESS(1) -#endif - -#ifdef DOSTORESTATE2 -SHA1_RECOMPRESS(2) -#endif - -#ifdef DOSTORESTATE3 -SHA1_RECOMPRESS(3) -#endif - -#ifdef DOSTORESTATE4 -SHA1_RECOMPRESS(4) -#endif - -#ifdef DOSTORESTATE5 -SHA1_RECOMPRESS(5) -#endif - -#ifdef DOSTORESTATE6 -SHA1_RECOMPRESS(6) -#endif - -#ifdef DOSTORESTATE7 -SHA1_RECOMPRESS(7) -#endif - -#ifdef DOSTORESTATE8 -SHA1_RECOMPRESS(8) -#endif - -#ifdef DOSTORESTATE9 -SHA1_RECOMPRESS(9) -#endif - -#ifdef DOSTORESTATE10 -SHA1_RECOMPRESS(10) -#endif - -#ifdef DOSTORESTATE11 -SHA1_RECOMPRESS(11) -#endif - -#ifdef DOSTORESTATE12 -SHA1_RECOMPRESS(12) -#endif - -#ifdef DOSTORESTATE13 -SHA1_RECOMPRESS(13) -#endif - -#ifdef DOSTORESTATE14 -SHA1_RECOMPRESS(14) -#endif - -#ifdef DOSTORESTATE15 -SHA1_RECOMPRESS(15) -#endif - -#ifdef DOSTORESTATE16 -SHA1_RECOMPRESS(16) -#endif - -#ifdef DOSTORESTATE17 -SHA1_RECOMPRESS(17) -#endif - -#ifdef DOSTORESTATE18 -SHA1_RECOMPRESS(18) -#endif - -#ifdef DOSTORESTATE19 -SHA1_RECOMPRESS(19) -#endif - -#ifdef DOSTORESTATE20 -SHA1_RECOMPRESS(20) -#endif - -#ifdef DOSTORESTATE21 -SHA1_RECOMPRESS(21) -#endif - -#ifdef DOSTORESTATE22 -SHA1_RECOMPRESS(22) -#endif - -#ifdef DOSTORESTATE23 -SHA1_RECOMPRESS(23) -#endif - -#ifdef DOSTORESTATE24 -SHA1_RECOMPRESS(24) -#endif - -#ifdef DOSTORESTATE25 -SHA1_RECOMPRESS(25) -#endif - -#ifdef DOSTORESTATE26 -SHA1_RECOMPRESS(26) -#endif - -#ifdef DOSTORESTATE27 -SHA1_RECOMPRESS(27) -#endif - -#ifdef DOSTORESTATE28 -SHA1_RECOMPRESS(28) -#endif - -#ifdef DOSTORESTATE29 -SHA1_RECOMPRESS(29) -#endif - -#ifdef DOSTORESTATE30 -SHA1_RECOMPRESS(30) -#endif - -#ifdef DOSTORESTATE31 -SHA1_RECOMPRESS(31) -#endif - -#ifdef DOSTORESTATE32 -SHA1_RECOMPRESS(32) -#endif - -#ifdef DOSTORESTATE33 -SHA1_RECOMPRESS(33) -#endif - -#ifdef DOSTORESTATE34 -SHA1_RECOMPRESS(34) -#endif - -#ifdef DOSTORESTATE35 -SHA1_RECOMPRESS(35) -#endif - -#ifdef DOSTORESTATE36 -SHA1_RECOMPRESS(36) -#endif - -#ifdef DOSTORESTATE37 -SHA1_RECOMPRESS(37) -#endif - -#ifdef DOSTORESTATE38 -SHA1_RECOMPRESS(38) -#endif - -#ifdef DOSTORESTATE39 -SHA1_RECOMPRESS(39) -#endif - -#ifdef DOSTORESTATE40 -SHA1_RECOMPRESS(40) -#endif - -#ifdef DOSTORESTATE41 -SHA1_RECOMPRESS(41) -#endif - -#ifdef DOSTORESTATE42 -SHA1_RECOMPRESS(42) -#endif - -#ifdef DOSTORESTATE43 -SHA1_RECOMPRESS(43) -#endif - -#ifdef DOSTORESTATE44 -SHA1_RECOMPRESS(44) -#endif - -#ifdef DOSTORESTATE45 -SHA1_RECOMPRESS(45) -#endif - -#ifdef DOSTORESTATE46 -SHA1_RECOMPRESS(46) -#endif - -#ifdef DOSTORESTATE47 -SHA1_RECOMPRESS(47) -#endif - -#ifdef DOSTORESTATE48 -SHA1_RECOMPRESS(48) -#endif - -#ifdef DOSTORESTATE49 -SHA1_RECOMPRESS(49) -#endif - -#ifdef DOSTORESTATE50 -SHA1_RECOMPRESS(50) -#endif - -#ifdef DOSTORESTATE51 -SHA1_RECOMPRESS(51) -#endif - -#ifdef DOSTORESTATE52 -SHA1_RECOMPRESS(52) -#endif - -#ifdef DOSTORESTATE53 -SHA1_RECOMPRESS(53) -#endif - -#ifdef DOSTORESTATE54 -SHA1_RECOMPRESS(54) -#endif - -#ifdef DOSTORESTATE55 -SHA1_RECOMPRESS(55) -#endif - -#ifdef DOSTORESTATE56 -SHA1_RECOMPRESS(56) -#endif - -#ifdef DOSTORESTATE57 -SHA1_RECOMPRESS(57) -#endif - -#ifdef DOSTORESTATE58 -SHA1_RECOMPRESS(58) -#endif - -#ifdef DOSTORESTATE59 -SHA1_RECOMPRESS(59) -#endif - -#ifdef DOSTORESTATE60 -SHA1_RECOMPRESS(60) -#endif - -#ifdef DOSTORESTATE61 -SHA1_RECOMPRESS(61) -#endif - -#ifdef DOSTORESTATE62 -SHA1_RECOMPRESS(62) -#endif - -#ifdef DOSTORESTATE63 -SHA1_RECOMPRESS(63) -#endif - -#ifdef DOSTORESTATE64 -SHA1_RECOMPRESS(64) -#endif - -#ifdef DOSTORESTATE65 -SHA1_RECOMPRESS(65) -#endif - -#ifdef DOSTORESTATE66 -SHA1_RECOMPRESS(66) -#endif - -#ifdef DOSTORESTATE67 -SHA1_RECOMPRESS(67) -#endif - -#ifdef DOSTORESTATE68 -SHA1_RECOMPRESS(68) -#endif - -#ifdef DOSTORESTATE69 -SHA1_RECOMPRESS(69) -#endif - -#ifdef DOSTORESTATE70 -SHA1_RECOMPRESS(70) -#endif - -#ifdef DOSTORESTATE71 -SHA1_RECOMPRESS(71) -#endif - -#ifdef DOSTORESTATE72 -SHA1_RECOMPRESS(72) -#endif - -#ifdef DOSTORESTATE73 -SHA1_RECOMPRESS(73) -#endif - -#ifdef DOSTORESTATE74 -SHA1_RECOMPRESS(74) -#endif - -#ifdef DOSTORESTATE75 -SHA1_RECOMPRESS(75) -#endif - -#ifdef DOSTORESTATE76 -SHA1_RECOMPRESS(76) -#endif - -#ifdef DOSTORESTATE77 -SHA1_RECOMPRESS(77) -#endif - -#ifdef DOSTORESTATE78 -SHA1_RECOMPRESS(78) -#endif - -#ifdef DOSTORESTATE79 -SHA1_RECOMPRESS(79) -#endif - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) -{ - switch (step) - { -#ifdef DOSTORESTATE0 - case 0: - sha1recompress_fast_0(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE1 - case 1: - sha1recompress_fast_1(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE2 - case 2: - sha1recompress_fast_2(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE3 - case 3: - sha1recompress_fast_3(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE4 - case 4: - sha1recompress_fast_4(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE5 - case 5: - sha1recompress_fast_5(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE6 - case 6: - sha1recompress_fast_6(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE7 - case 7: - sha1recompress_fast_7(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE8 - case 8: - sha1recompress_fast_8(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE9 - case 9: - sha1recompress_fast_9(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE10 - case 10: - sha1recompress_fast_10(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE11 - case 11: - sha1recompress_fast_11(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE12 - case 12: - sha1recompress_fast_12(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE13 - case 13: - sha1recompress_fast_13(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE14 - case 14: - sha1recompress_fast_14(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE15 - case 15: - sha1recompress_fast_15(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE16 - case 16: - sha1recompress_fast_16(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE17 - case 17: - sha1recompress_fast_17(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE18 - case 18: - sha1recompress_fast_18(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE19 - case 19: - sha1recompress_fast_19(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE20 - case 20: - sha1recompress_fast_20(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE21 - case 21: - sha1recompress_fast_21(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE22 - case 22: - sha1recompress_fast_22(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE23 - case 23: - sha1recompress_fast_23(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE24 - case 24: - sha1recompress_fast_24(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE25 - case 25: - sha1recompress_fast_25(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE26 - case 26: - sha1recompress_fast_26(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE27 - case 27: - sha1recompress_fast_27(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE28 - case 28: - sha1recompress_fast_28(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE29 - case 29: - sha1recompress_fast_29(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE30 - case 30: - sha1recompress_fast_30(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE31 - case 31: - sha1recompress_fast_31(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE32 - case 32: - sha1recompress_fast_32(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE33 - case 33: - sha1recompress_fast_33(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE34 - case 34: - sha1recompress_fast_34(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE35 - case 35: - sha1recompress_fast_35(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE36 - case 36: - sha1recompress_fast_36(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE37 - case 37: - sha1recompress_fast_37(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE38 - case 38: - sha1recompress_fast_38(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE39 - case 39: - sha1recompress_fast_39(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE40 - case 40: - sha1recompress_fast_40(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE41 - case 41: - sha1recompress_fast_41(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE42 - case 42: - sha1recompress_fast_42(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE43 - case 43: - sha1recompress_fast_43(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE44 - case 44: - sha1recompress_fast_44(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE45 - case 45: - sha1recompress_fast_45(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE46 - case 46: - sha1recompress_fast_46(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE47 - case 47: - sha1recompress_fast_47(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE48 - case 48: - sha1recompress_fast_48(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE49 - case 49: - sha1recompress_fast_49(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE50 - case 50: - sha1recompress_fast_50(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE51 - case 51: - sha1recompress_fast_51(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE52 - case 52: - sha1recompress_fast_52(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE53 - case 53: - sha1recompress_fast_53(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE54 - case 54: - sha1recompress_fast_54(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE55 - case 55: - sha1recompress_fast_55(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE56 - case 56: - sha1recompress_fast_56(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE57 - case 57: - sha1recompress_fast_57(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE58 - case 58: - sha1recompress_fast_58(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE59 - case 59: - sha1recompress_fast_59(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE60 - case 60: - sha1recompress_fast_60(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE61 - case 61: - sha1recompress_fast_61(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE62 - case 62: - sha1recompress_fast_62(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE63 - case 63: - sha1recompress_fast_63(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE64 - case 64: - sha1recompress_fast_64(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE65 - case 65: - sha1recompress_fast_65(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE66 - case 66: - sha1recompress_fast_66(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE67 - case 67: - sha1recompress_fast_67(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE68 - case 68: - sha1recompress_fast_68(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE69 - case 69: - sha1recompress_fast_69(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE70 - case 70: - sha1recompress_fast_70(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE71 - case 71: - sha1recompress_fast_71(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE72 - case 72: - sha1recompress_fast_72(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE73 - case 73: - sha1recompress_fast_73(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE74 - case 74: - sha1recompress_fast_74(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE75 - case 75: - sha1recompress_fast_75(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE76 - case 76: - sha1recompress_fast_76(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE77 - case 77: - sha1recompress_fast_77(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE78 - case 78: - sha1recompress_fast_78(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE79 - case 79: - sha1recompress_fast_79(ihvin, ihvout, me2, state); - break; -#endif - default: - abort(); - } - -} - - - -static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) -{ - unsigned i, j; - uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; - uint32_t ihvtmp[5]; - - ctx->ihv1[0] = ctx->ihv[0]; - ctx->ihv1[1] = ctx->ihv[1]; - ctx->ihv1[2] = ctx->ihv[2]; - ctx->ihv1[3] = ctx->ihv[3]; - ctx->ihv1[4] = ctx->ihv[4]; - - sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); - - if (ctx->detect_coll) - { - if (ctx->ubc_check) - { - ubc_check(ctx->m1, ubc_dv_mask); - } - - if (ubc_dv_mask[0] != 0) - { - for (i = 0; sha1_dvs[i].dvType != 0; ++i) - { - if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) - { - for (j = 0; j < 80; ++j) - ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; - - sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); - - /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ - if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) - || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) - { - ctx->found_collision = 1; - - if (ctx->safe_hash) - { - sha1_compression_W(ctx->ihv, ctx->m1); - sha1_compression_W(ctx->ihv, ctx->m1); - } - - break; - } - } - } - } - } -} - -void SHA1DCInit(SHA1_CTX *ctx) -{ - ctx->total = 0; - ctx->ihv[0] = 0x67452301; - ctx->ihv[1] = 0xEFCDAB89; - ctx->ihv[2] = 0x98BADCFE; - ctx->ihv[3] = 0x10325476; - ctx->ihv[4] = 0xC3D2E1F0; - ctx->found_collision = 0; - ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; - ctx->ubc_check = 1; - ctx->detect_coll = 1; - ctx->reduced_round_coll = 0; - ctx->callback = NULL; -} - -void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) -{ - if (safehash) - ctx->safe_hash = 1; - else - ctx->safe_hash = 0; -} - - -void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) -{ - if (ubc_check) - ctx->ubc_check = 1; - else - ctx->ubc_check = 0; -} - -void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) -{ - if (detect_coll) - ctx->detect_coll = 1; - else - ctx->detect_coll = 0; -} - -void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) -{ - if (reduced_round_coll) - ctx->reduced_round_coll = 1; - else - ctx->reduced_round_coll = 0; -} - -void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) -{ - ctx->callback = callback; -} - -void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) -{ - unsigned left, fill; - - if (len == 0) - return; - - left = ctx->total & 63; - fill = 64 - left; - - if (left && len >= fill) - { - ctx->total += fill; - memcpy(ctx->buffer + left, buf, fill); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); - buf += fill; - len -= fill; - left = 0; - } - while (len >= 64) - { - ctx->total += 64; - -#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) - sha1_process(ctx, (uint32_t*)(buf)); -#else - memcpy(ctx->buffer, buf, 64); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); -#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ - buf += 64; - len -= 64; - } - if (len > 0) - { - ctx->total += len; - memcpy(ctx->buffer + left, buf, len); - } -} - -static const unsigned char sha1_padding[64] = -{ - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) -{ - uint32_t last = ctx->total & 63; - uint32_t padn = (last < 56) ? (56 - last) : (120 - last); - uint64_t total; - SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); - - total = ctx->total - padn; - total <<= 3; - ctx->buffer[56] = (unsigned char)(total >> 56); - ctx->buffer[57] = (unsigned char)(total >> 48); - ctx->buffer[58] = (unsigned char)(total >> 40); - ctx->buffer[59] = (unsigned char)(total >> 32); - ctx->buffer[60] = (unsigned char)(total >> 24); - ctx->buffer[61] = (unsigned char)(total >> 16); - ctx->buffer[62] = (unsigned char)(total >> 8); - ctx->buffer[63] = (unsigned char)(total); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); - output[0] = (unsigned char)(ctx->ihv[0] >> 24); - output[1] = (unsigned char)(ctx->ihv[0] >> 16); - output[2] = (unsigned char)(ctx->ihv[0] >> 8); - output[3] = (unsigned char)(ctx->ihv[0]); - output[4] = (unsigned char)(ctx->ihv[1] >> 24); - output[5] = (unsigned char)(ctx->ihv[1] >> 16); - output[6] = (unsigned char)(ctx->ihv[1] >> 8); - output[7] = (unsigned char)(ctx->ihv[1]); - output[8] = (unsigned char)(ctx->ihv[2] >> 24); - output[9] = (unsigned char)(ctx->ihv[2] >> 16); - output[10] = (unsigned char)(ctx->ihv[2] >> 8); - output[11] = (unsigned char)(ctx->ihv[2]); - output[12] = (unsigned char)(ctx->ihv[3] >> 24); - output[13] = (unsigned char)(ctx->ihv[3] >> 16); - output[14] = (unsigned char)(ctx->ihv[3] >> 8); - output[15] = (unsigned char)(ctx->ihv[3]); - output[16] = (unsigned char)(ctx->ihv[4] >> 24); - output[17] = (unsigned char)(ctx->ihv[4] >> 16); - output[18] = (unsigned char)(ctx->ihv[4] >> 8); - output[19] = (unsigned char)(ctx->ihv[4]); - return ctx->found_collision; -} - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C -#endif diff --git a/src/hash/sha1/sha1dc/sha1.h b/src/hash/sha1/sha1dc/sha1.h deleted file mode 100644 index 1e4e94be5..000000000 --- a/src/hash/sha1/sha1dc/sha1.h +++ /dev/null @@ -1,110 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -#ifndef SHA1DC_SHA1_H -#define SHA1DC_SHA1_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif - -/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ -/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ -void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); - -/* -// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). -// Where 0 <= T < 80 -// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) -// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. -// The function will return: -// ihvin: The reconstructed input chaining value. -// ihvout: The reconstructed output chaining value. -*/ -typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); - -/* A callback function type that can be set to be called when a collision block has been found: */ -/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ -typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); - -/* The SHA-1 context. */ -typedef struct { - uint64_t total; - uint32_t ihv[5]; - unsigned char buffer[64]; - int found_collision; - int safe_hash; - int detect_coll; - int ubc_check; - int reduced_round_coll; - collision_block_callback callback; - - uint32_t ihv1[5]; - uint32_t ihv2[5]; - uint32_t m1[80]; - uint32_t m2[80]; - uint32_t states[80][5]; -} SHA1_CTX; - -/* Initialize SHA-1 context. */ -void SHA1DCInit(SHA1_CTX*); - -/* - Function to enable safe SHA-1 hashing: - Collision attacks are thwarted by hashing a detected near-collision block 3 times. - Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: - The best collision attacks against SHA-1 have complexity about 2^60, - thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. - An attacker would be better off using a generic birthday search of complexity 2^80. - - Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, - but it will result in a different SHA-1 hash for messages where a collision attack was detected. - This will automatically invalidate SHA-1 based digital signature forgeries. - Enabled by default. -*/ -void SHA1DCSetSafeHash(SHA1_CTX*, int); - -/* - Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). - Enabled by default - */ -void SHA1DCSetUseUBC(SHA1_CTX*, int); - -/* - Function to disable or enable the use of Collision Detection. - Enabled by default. - */ -void SHA1DCSetUseDetectColl(SHA1_CTX*, int); - -/* function to disable or enable the detection of reduced-round SHA-1 collisions */ -/* disabled by default */ -void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); - -/* function to set a callback function, pass NULL to disable */ -/* by default no callback set */ -void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); - -/* update SHA-1 context with buffer contents */ -void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); - -/* obtain SHA-1 hash from SHA-1 context */ -/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ -int SHA1DCFinal(unsigned char[20], SHA1_CTX*); - -#if defined(__cplusplus) -} -#endif - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H -#endif - -#endif diff --git a/src/hash/sha1/sha1dc/ubc_check.c b/src/hash/sha1/sha1dc/ubc_check.c deleted file mode 100644 index b3beff2af..000000000 --- a/src/hash/sha1/sha1dc/ubc_check.c +++ /dev/null @@ -1,372 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -/* -// this file was generated by the 'parse_bitrel' program in the tools section -// using the data files from directory 'tools/data/3565' -// -// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check -// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) -// dm[80] is the expanded message block XOR-difference defined by the DV -// testt is the step to do the recompression from for collision detection -// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check -// -// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs -// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met -// thus one needs to do the recompression check for each DV that has its bit set -// -// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded -// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c -// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section -*/ - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif -#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C -#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C -#endif -#include "ubc_check.h" - -static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; -static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; -static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; -static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; -static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; -static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; -static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; -static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; -static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; -static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; -static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; -static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; -static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; -static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; -static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; -static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; -static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; -static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; -static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; -static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; -static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; -static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; -static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; -static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; -static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; -static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; -static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; -static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; -static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; -static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; -static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; -static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; - -dv_info_t sha1_dvs[] = -{ - {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } -, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } -, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } -, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } -, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } -, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } -, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } -, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } -, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } -, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } -, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } -, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } -, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } -, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } -, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } -, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } -, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } -, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } -, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } -, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } -, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } -, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } -, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } -, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } -, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } -, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } -, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } -, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } -, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } -, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } -, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } -, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } -, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} -}; -void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) -{ - uint32_t mask = ~((uint32_t)(0)); - mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); - mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); - mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); - mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); - mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); - mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); - mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); - mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); - mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); - mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); - mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); - mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); - mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); - mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); - mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); - mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); - mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); - mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); - mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); - mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); - mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); - mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); - mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); - mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); - mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); - mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) - mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); - mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) - mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) - mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) - mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) - mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); - if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) - mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) - mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); - if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) - mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) - mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); - if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) - mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); - if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) - mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); - if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) - mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); - if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) - mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); - mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); - if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) - mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); - mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); - if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) - mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); - mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); - mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); - if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) - mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); - mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); - if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) - mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) - mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) - mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); - mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) - mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) - mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); - if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) - mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); - if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) - mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) - mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); - mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) - mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) - mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); - if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) - mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); - if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) - mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) - mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) - mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) - mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); - if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) - mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) - mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); - mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); - mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) - mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); - mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); - mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); - mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); - mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); - mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); - mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); - mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); - mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); - mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); - mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); - if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) - mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); - if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) - mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); - if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) - mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); - if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) - mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); - if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) - mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); - mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); - if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) - mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); -if (mask) { - - if (mask & DV_I_43_0_bit) - if ( - !((W[61]^(W[62]>>5)) & (1<<1)) - || !(!((W[59]^(W[63]>>25)) & (1<<5))) - || !((W[58]^(W[63]>>30)) & (1<<0)) - ) mask &= ~DV_I_43_0_bit; - if (mask & DV_I_44_0_bit) - if ( - !((W[62]^(W[63]>>5)) & (1<<1)) - || !(!((W[60]^(W[64]>>25)) & (1<<5))) - || !((W[59]^(W[64]>>30)) & (1<<0)) - ) mask &= ~DV_I_44_0_bit; - if (mask & DV_I_46_2_bit) - mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); - if (mask & DV_I_47_2_bit) - if ( - !((W[62]^(W[63]>>5)) & (1<<2)) - || !(!((W[41]^W[43]) & (1<<6))) - ) mask &= ~DV_I_47_2_bit; - if (mask & DV_I_48_2_bit) - if ( - !((W[63]^(W[64]>>5)) & (1<<2)) - || !(!((W[48]^(W[49]<<5)) & (1<<6))) - ) mask &= ~DV_I_48_2_bit; - if (mask & DV_I_49_2_bit) - if ( - !(!((W[49]^(W[50]<<5)) & (1<<6))) - || !((W[42]^W[50]) & (1<<1)) - || !(!((W[39]^(W[40]<<5)) & (1<<6))) - || !((W[38]^W[40]) & (1<<1)) - ) mask &= ~DV_I_49_2_bit; - if (mask & DV_I_50_0_bit) - mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); - if (mask & DV_I_50_2_bit) - mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); - if (mask & DV_I_51_0_bit) - mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); - if (mask & DV_I_51_2_bit) - if ( - !(!((W[51]^(W[52]<<5)) & (1<<6))) - || !(!((W[49]^W[51]) & (1<<6))) - || !(!((W[37]^(W[37]>>5)) & (1<<1))) - || !(!((W[35]^(W[39]>>25)) & (1<<5))) - ) mask &= ~DV_I_51_2_bit; - if (mask & DV_I_52_0_bit) - mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); - if (mask & DV_II_46_2_bit) - mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); - if (mask & DV_II_48_0_bit) - if ( - !(!((W[36]^(W[40]>>25)) & (1<<3))) - || !((W[35]^(W[40]<<2)) & (1<<30)) - ) mask &= ~DV_II_48_0_bit; - if (mask & DV_II_49_0_bit) - if ( - !(!((W[37]^(W[41]>>25)) & (1<<3))) - || !((W[36]^(W[41]<<2)) & (1<<30)) - ) mask &= ~DV_II_49_0_bit; - if (mask & DV_II_49_2_bit) - if ( - !(!((W[53]^(W[54]<<5)) & (1<<6))) - || !(!((W[51]^W[53]) & (1<<6))) - || !((W[50]^W[54]) & (1<<1)) - || !(!((W[45]^(W[46]<<5)) & (1<<6))) - || !(!((W[37]^(W[41]>>25)) & (1<<5))) - || !((W[36]^(W[41]>>30)) & (1<<0)) - ) mask &= ~DV_II_49_2_bit; - if (mask & DV_II_50_0_bit) - if ( - !((W[55]^W[58]) & (1<<29)) - || !(!((W[38]^(W[42]>>25)) & (1<<3))) - || !((W[37]^(W[42]<<2)) & (1<<30)) - ) mask &= ~DV_II_50_0_bit; - if (mask & DV_II_50_2_bit) - if ( - !(!((W[54]^(W[55]<<5)) & (1<<6))) - || !(!((W[52]^W[54]) & (1<<6))) - || !((W[51]^W[55]) & (1<<1)) - || !((W[45]^W[47]) & (1<<1)) - || !(!((W[38]^(W[42]>>25)) & (1<<5))) - || !((W[37]^(W[42]>>30)) & (1<<0)) - ) mask &= ~DV_II_50_2_bit; - if (mask & DV_II_51_0_bit) - if ( - !(!((W[39]^(W[43]>>25)) & (1<<3))) - || !((W[38]^(W[43]<<2)) & (1<<30)) - ) mask &= ~DV_II_51_0_bit; - if (mask & DV_II_51_2_bit) - if ( - !(!((W[55]^(W[56]<<5)) & (1<<6))) - || !(!((W[53]^W[55]) & (1<<6))) - || !((W[52]^W[56]) & (1<<1)) - || !((W[46]^W[48]) & (1<<1)) - || !(!((W[39]^(W[43]>>25)) & (1<<5))) - || !((W[38]^(W[43]>>30)) & (1<<0)) - ) mask &= ~DV_II_51_2_bit; - if (mask & DV_II_52_0_bit) - if ( - !(!((W[59]^W[60]) & (1<<29))) - || !(!((W[40]^(W[44]>>25)) & (1<<3))) - || !(!((W[40]^(W[44]>>25)) & (1<<4))) - || !((W[39]^(W[44]<<2)) & (1<<30)) - ) mask &= ~DV_II_52_0_bit; - if (mask & DV_II_53_0_bit) - if ( - !((W[58]^W[61]) & (1<<29)) - || !(!((W[57]^(W[61]>>25)) & (1<<4))) - || !(!((W[41]^(W[45]>>25)) & (1<<3))) - || !(!((W[41]^(W[45]>>25)) & (1<<4))) - ) mask &= ~DV_II_53_0_bit; - if (mask & DV_II_54_0_bit) - if ( - !(!((W[58]^(W[62]>>25)) & (1<<4))) - || !(!((W[42]^(W[46]>>25)) & (1<<3))) - || !(!((W[42]^(W[46]>>25)) & (1<<4))) - ) mask &= ~DV_II_54_0_bit; - if (mask & DV_II_55_0_bit) - if ( - !(!((W[59]^(W[63]>>25)) & (1<<4))) - || !(!((W[57]^(W[59]>>25)) & (1<<4))) - || !(!((W[43]^(W[47]>>25)) & (1<<3))) - || !(!((W[43]^(W[47]>>25)) & (1<<4))) - ) mask &= ~DV_II_55_0_bit; - if (mask & DV_II_56_0_bit) - if ( - !(!((W[60]^(W[64]>>25)) & (1<<4))) - || !(!((W[44]^(W[48]>>25)) & (1<<3))) - || !(!((W[44]^(W[48]>>25)) & (1<<4))) - ) mask &= ~DV_II_56_0_bit; -} - - dvmask[0]=mask; -} - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C -#endif diff --git a/src/hash/sha1/sha1dc/ubc_check.h b/src/hash/sha1/sha1dc/ubc_check.h deleted file mode 100644 index d7e17dc73..000000000 --- a/src/hash/sha1/sha1dc/ubc_check.h +++ /dev/null @@ -1,52 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -/* -// this file was generated by the 'parse_bitrel' program in the tools section -// using the data files from directory 'tools/data/3565' -// -// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check -// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) -// dm[80] is the expanded message block XOR-difference defined by the DV -// testt is the step to do the recompression from for collision detection -// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check -// -// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs -// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met -// thus one needs to do the recompression check for each DV that has its bit set -*/ - -#ifndef SHA1DC_UBC_CHECK_H -#define SHA1DC_UBC_CHECK_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif - -#define DVMASKSIZE 1 -typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; -extern dv_info_t sha1_dvs[]; -void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); - -#define DOSTORESTATE58 -#define DOSTORESTATE65 - -#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) - -#if defined(__cplusplus) -} -#endif - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H -#endif - -#endif diff --git a/src/hash/sha1/win32.c b/src/hash/sha1/win32.c deleted file mode 100644 index b89dfbad8..000000000 --- a/src/hash/sha1/win32.c +++ /dev/null @@ -1,333 +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 "win32.h" - -#include "runtime.h" - -#include -#include - -#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 */ - -/* Initialize CNG, if available */ -GIT_INLINE(int) hash_cng_prov_init(void) -{ - char dll_path[MAX_PATH]; - DWORD dll_path_len, size_len; - - /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ - if (!git_has_win32_version(6, 0, 1)) { - git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform"); - return -1; - } - - /* Load bcrypt.dll explicitly from the system directory */ - if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || - dll_path_len > MAX_PATH || - StringCchCat(dll_path, MAX_PATH, "\\") < 0 || - StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || - (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) { - git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded"); - return -1; - } - - /* Load the function addresses */ - if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || - (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || - (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || - (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || - (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || - (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || - (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded"); - return -1; - } - - /* Load the SHA1 algorithm */ - if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); - return -1; - } - - /* Get storage space for the hash object */ - if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { - hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "algorithm handle could not be found"); - return -1; - } - - hash_prov.type = CNG; - return 0; -} - -GIT_INLINE(void) hash_cng_prov_shutdown(void) -{ - hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); - FreeLibrary(hash_prov.prov.cng.dll); - - hash_prov.type = INVALID; -} - -/* Initialize CryptoAPI */ -GIT_INLINE(int) hash_cryptoapi_prov_init() -{ - if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - git_error_set(GIT_ERROR_OS, "legacy hash context could not be started"); - return -1; - } - - hash_prov.type = CRYPTOAPI; - return 0; -} - -GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) -{ - CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); - - hash_prov.type = INVALID; -} - -static void sha1_shutdown(void) -{ - if (hash_prov.type == CNG) - hash_cng_prov_shutdown(); - else if(hash_prov.type == CRYPTOAPI) - hash_cryptoapi_prov_shutdown(); -} - -int git_hash_sha1_global_init(void) -{ - int error = 0; - - if (hash_prov.type != INVALID) - return 0; - - if ((error = hash_cng_prov_init()) < 0) - error = hash_cryptoapi_prov_init(); - - if (!error) - error = git_runtime_shutdown_register(sha1_shutdown); - - return error; -} - -/* CryptoAPI: available in Windows XP and newer */ - -GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) -{ - ctx->type = CRYPTOAPI; - ctx->prov = &hash_prov; - - return git_hash_sha1_init(ctx); -} - -GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) -{ - if (ctx->ctx.cryptoapi.valid) - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); - - if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { - ctx->ctx.cryptoapi.valid = 0; - git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); - return -1; - } - - ctx->ctx.cryptoapi.valid = 1; - return 0; -} - -GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - const BYTE *data = (BYTE *)_data; - - GIT_ASSERT(ctx->ctx.cryptoapi.valid); - - while (len > 0) { - DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; - - if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { - git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); - return -1; - } - - data += chunk; - len -= chunk; - } - - return 0; -} - -GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - DWORD len = GIT_HASH_SHA1_SIZE; - int error = 0; - - GIT_ASSERT(ctx->ctx.cryptoapi.valid); - - if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { - git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); - error = -1; - } - - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); - ctx->ctx.cryptoapi.valid = 0; - - return error; -} - -GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) -{ - if (ctx->ctx.cryptoapi.valid) - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); -} - -/* CNG: Available in Windows Server 2008 and newer */ - -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; - - if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { - git__free(ctx->ctx.cng.hash_object); - - git_error_set(GIT_ERROR_OS, "hash implementation could not be created"); - return -1; - } - - ctx->type = CNG; - ctx->prov = &hash_prov; - - return 0; -} - -GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) -{ - BYTE hash[GIT_OID_RAWSZ]; - - if (!ctx->ctx.cng.updated) - return 0; - - /* CNG needs to be finished to restart */ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); - return -1; - } - - ctx->ctx.cng.updated = 0; - - return 0; -} - -GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - PBYTE data = (PBYTE)_data; - - while (len > 0) { - ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; - - if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash could not be updated"); - return -1; - } - - data += chunk; - len -= chunk; - } - - return 0; -} - -GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash could not be finished"); - return -1; - } - - ctx->ctx.cng.updated = 0; - - return 0; -} - -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); -} - -/* Indirection between CryptoAPI and CNG */ - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - int error = 0; - - GIT_ASSERT_ARG(ctx); - - /* - * When compiled with GIT_THREADS, the global hash_prov data is - * initialized with git_libgit2_init. Otherwise, it must be initialized - * at first use. - */ - if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) - return error; - - 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_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - if (!ctx) - return; - else if (ctx->type == CNG) - hash_ctx_cng_cleanup(ctx); - else if(ctx->type == CRYPTOAPI) - hash_ctx_cryptoapi_cleanup(ctx); -} diff --git a/src/hash/sha1/win32.h b/src/hash/sha1/win32.h deleted file mode 100644 index 791d20a42..000000000 --- a/src/hash/sha1/win32.h +++ /dev/null @@ -1,128 +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_hash_sha1_win32_h__ -#define INCLUDE_hash_sha1_win32_h__ - -#include "hash/sha1.h" - -#include -#include - -enum hash_win32_prov_type { - INVALID = 0, - CRYPTOAPI, - CNG -}; - -/* - * CryptoAPI is available for hashing on Windows XP and newer. - */ - -struct hash_cryptoapi_prov { - HCRYPTPROV handle; -}; - -/* - * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is - * preferred, however it is only available on Windows 2008 and newer and - * must therefore be dynamically loaded, and we must inline constants that - * would not exist when building in pre-Windows 2008 environments. - */ - -/* Function declarations for CNG */ -typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, - LPCWSTR pszAlgId, - LPCWSTR pszImplementation, - DWORD dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)( - HANDLE /* BCRYPT_HANDLE */ hObject, - LPCWSTR pszProperty, - PUCHAR pbOutput, - ULONG cbOutput, - ULONG *pcbResult, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, - HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, - PUCHAR pbHashObject, ULONG cbHashObject, - PUCHAR pbSecret, - ULONG cbSecret, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash, - PUCHAR pbOutput, - ULONG cbOutput, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash, - PUCHAR pbInput, - ULONG cbInput, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash); - -typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, - ULONG dwFlags); - -struct hash_cng_prov { - /* DLL for CNG */ - HINSTANCE dll; - - /* Function pointers for CNG */ - hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider; - hash_win32_cng_get_property_fn get_property; - hash_win32_cng_create_hash_fn create_hash; - hash_win32_cng_finish_hash_fn finish_hash; - hash_win32_cng_hash_data_fn hash_data; - hash_win32_cng_destroy_hash_fn destroy_hash; - hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider; - - HANDLE /* BCRYPT_ALG_HANDLE */ handle; - DWORD hash_object_size; -}; - -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 */ - -struct hash_cryptoapi_ctx { - bool valid; - HCRYPTHASH hash_handle; -}; - -struct hash_cng_ctx { - bool updated; - HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; - PBYTE hash_object; -}; - -struct git_hash_sha1_ctx { - enum hash_win32_prov_type type; - git_hash_prov *prov; - - union { - struct hash_cryptoapi_ctx cryptoapi; - struct hash_cng_ctx cng; - } ctx; -}; - -#endif diff --git a/src/hashsig.c b/src/hashsig.c deleted file mode 100644 index 6b4fb8352..000000000 --- a/src/hashsig.c +++ /dev/null @@ -1,375 +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 "common.h" - -#include "git2/sys/hashsig.h" -#include "futils.h" -#include "util.h" - -typedef uint32_t hashsig_t; -typedef uint64_t hashsig_state; - -#define HASHSIG_SCALE 100 - -#define HASHSIG_MAX_RUN 80 -#define HASHSIG_HASH_START INT64_C(0x012345678ABCDEF0) -#define HASHSIG_HASH_SHIFT 5 - -#define HASHSIG_HASH_MIX(S,CH) \ - (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH) - -#define HASHSIG_HEAP_SIZE ((1 << 7) - 1) -#define HASHSIG_HEAP_MIN_SIZE 4 - -typedef int (*hashsig_cmp)(const void *a, const void *b, void *); - -typedef struct { - int size, asize; - hashsig_cmp cmp; - hashsig_t values[HASHSIG_HEAP_SIZE]; -} hashsig_heap; - -struct git_hashsig { - hashsig_heap mins; - hashsig_heap maxs; - size_t lines; - git_hashsig_option_t opt; -}; - -#define HEAP_LCHILD_OF(I) (((I)<<1)+1) -#define HEAP_RCHILD_OF(I) (((I)<<1)+2) -#define HEAP_PARENT_OF(I) (((I)-1)>>1) - -static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) -{ - h->size = 0; - h->asize = HASHSIG_HEAP_SIZE; - h->cmp = cmp; -} - -static int hashsig_cmp_max(const void *a, const void *b, void *payload) -{ - hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; - GIT_UNUSED(payload); - return (av < bv) ? -1 : (av > bv) ? 1 : 0; -} - -static int hashsig_cmp_min(const void *a, const void *b, void *payload) -{ - hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; - GIT_UNUSED(payload); - return (av > bv) ? -1 : (av < bv) ? 1 : 0; -} - -static void hashsig_heap_up(hashsig_heap *h, int el) -{ - int parent_el = HEAP_PARENT_OF(el); - - while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) { - hashsig_t t = h->values[el]; - h->values[el] = h->values[parent_el]; - h->values[parent_el] = t; - - el = parent_el; - parent_el = HEAP_PARENT_OF(el); - } -} - -static void hashsig_heap_down(hashsig_heap *h, int el) -{ - hashsig_t v, lv, rv; - - /* 'el < h->size / 2' tests if el is bottom row of heap */ - - while (el < h->size / 2) { - int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel; - - v = h->values[el]; - lv = h->values[lel]; - rv = h->values[rel]; - - if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0) - break; - - swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel; - - h->values[el] = h->values[swapel]; - h->values[swapel] = v; - - el = swapel; - } -} - -static void hashsig_heap_sort(hashsig_heap *h) -{ - /* only need to do this at the end for signature comparison */ - git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL); -} - -static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) -{ - /* if heap is not full, insert new element */ - if (h->size < h->asize) { - h->values[h->size++] = val; - hashsig_heap_up(h, h->size - 1); - } - - /* if heap is full, pop top if new element should replace it */ - else if (h->cmp(&val, &h->values[0], NULL) > 0) { - h->size--; - h->values[0] = h->values[h->size]; - hashsig_heap_down(h, 0); - } - -} - -typedef struct { - int use_ignores; - uint8_t ignore_ch[256]; -} hashsig_in_progress; - -static int hashsig_in_progress_init( - hashsig_in_progress *prog, git_hashsig *sig) -{ - int i; - - /* no more than one can be set */ - GIT_ASSERT(!(sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) || - !(sig->opt & GIT_HASHSIG_SMART_WHITESPACE)); - - if (sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) { - for (i = 0; i < 256; ++i) - prog->ignore_ch[i] = git__isspace_nonlf(i); - prog->use_ignores = 1; - } else if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) { - for (i = 0; i < 256; ++i) - prog->ignore_ch[i] = git__isspace(i); - prog->use_ignores = 1; - } else { - memset(prog, 0, sizeof(*prog)); - } - - return 0; -} - -static int hashsig_add_hashes( - git_hashsig *sig, - const uint8_t *data, - size_t size, - hashsig_in_progress *prog) -{ - const uint8_t *scan = data, *end = data + size; - hashsig_state state = HASHSIG_HASH_START; - int use_ignores = prog->use_ignores, len; - uint8_t ch; - - while (scan < end) { - state = HASHSIG_HASH_START; - - for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) { - ch = *scan; - - if (use_ignores) - for (; scan < end && git__isspace_nonlf(ch); ch = *scan) - ++scan; - else if (sig->opt & - (GIT_HASHSIG_IGNORE_WHITESPACE | GIT_HASHSIG_SMART_WHITESPACE)) - for (; scan < end && ch == '\r'; ch = *scan) - ++scan; - - /* peek at next character to decide what to do next */ - if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) - use_ignores = (ch == '\n'); - - if (scan >= end) - break; - ++scan; - - /* check run terminator */ - if (ch == '\n' || ch == '\0') { - sig->lines++; - break; - } - - ++len; - HASHSIG_HASH_MIX(state, ch); - } - - if (len > 0) { - hashsig_heap_insert(&sig->mins, (hashsig_t)state); - hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - - while (scan < end && (*scan == '\n' || !*scan)) - ++scan; - } - } - - prog->use_ignores = use_ignores; - - return 0; -} - -static int hashsig_finalize_hashes(git_hashsig *sig) -{ - if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE && - !(sig->opt & GIT_HASHSIG_ALLOW_SMALL_FILES)) { - git_error_set(GIT_ERROR_INVALID, - "file too small for similarity signature calculation"); - return GIT_EBUFS; - } - - hashsig_heap_sort(&sig->mins); - hashsig_heap_sort(&sig->maxs); - - return 0; -} - -static git_hashsig *hashsig_alloc(git_hashsig_option_t opts) -{ - git_hashsig *sig = git__calloc(1, sizeof(git_hashsig)); - if (!sig) - return NULL; - - hashsig_heap_init(&sig->mins, hashsig_cmp_min); - hashsig_heap_init(&sig->maxs, hashsig_cmp_max); - sig->opt = opts; - - return sig; -} - -int git_hashsig_create( - git_hashsig **out, - const char *buf, - size_t buflen, - git_hashsig_option_t opts) -{ - int error; - hashsig_in_progress prog; - git_hashsig *sig = hashsig_alloc(opts); - GIT_ERROR_CHECK_ALLOC(sig); - - if ((error = hashsig_in_progress_init(&prog, sig)) < 0) - return error; - - error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog); - - if (!error) - error = hashsig_finalize_hashes(sig); - - if (!error) - *out = sig; - else - git_hashsig_free(sig); - - return error; -} - -int git_hashsig_create_fromfile( - git_hashsig **out, - const char *path, - git_hashsig_option_t opts) -{ - uint8_t buf[0x1000]; - ssize_t buflen = 0; - int error = 0, fd; - hashsig_in_progress prog; - git_hashsig *sig = hashsig_alloc(opts); - GIT_ERROR_CHECK_ALLOC(sig); - - if ((fd = git_futils_open_ro(path)) < 0) { - git__free(sig); - return fd; - } - - if ((error = hashsig_in_progress_init(&prog, sig)) < 0) { - p_close(fd); - return error; - } - - while (!error) { - if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { - if ((error = (int)buflen) < 0) - git_error_set(GIT_ERROR_OS, - "read error on '%s' calculating similarity hashes", path); - break; - } - - error = hashsig_add_hashes(sig, buf, buflen, &prog); - } - - p_close(fd); - - if (!error) - error = hashsig_finalize_hashes(sig); - - if (!error) - *out = sig; - else - git_hashsig_free(sig); - - return error; -} - -void git_hashsig_free(git_hashsig *sig) -{ - git__free(sig); -} - -static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) -{ - int matches = 0, i, j, cmp; - - GIT_ASSERT_WITH_RETVAL(a->cmp == b->cmp, 0); - - /* hash heaps are sorted - just look for overlap vs total */ - - for (i = 0, j = 0; i < a->size && j < b->size; ) { - cmp = a->cmp(&a->values[i], &b->values[j], NULL); - - if (cmp < 0) - ++i; - else if (cmp > 0) - ++j; - else { - ++i; ++j; ++matches; - } - } - - return HASHSIG_SCALE * (matches * 2) / (a->size + b->size); -} - -int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) -{ - /* if we have no elements in either file then each file is either - * empty or blank. if we're ignoring whitespace then the files are - * similar, otherwise they're dissimilar. - */ - if (a->mins.size == 0 && b->mins.size == 0) { - if ((!a->lines && !b->lines) || - (a->opt & GIT_HASHSIG_IGNORE_WHITESPACE)) - return HASHSIG_SCALE; - else - return 0; - } - - /* if we have fewer than the maximum number of elements, then just use - * one array since the two arrays will be the same - */ - if (a->mins.size < HASHSIG_HEAP_SIZE) { - return hashsig_heap_compare(&a->mins, &b->mins); - } else { - int mins, maxs; - - if ((mins = hashsig_heap_compare(&a->mins, &b->mins)) < 0) - return mins; - if ((maxs = hashsig_heap_compare(&a->maxs, &b->maxs)) < 0) - return maxs; - - return (mins + maxs) / 2; - } -} diff --git a/src/ident.c b/src/ident.c deleted file mode 100644 index 53095864e..000000000 --- a/src/ident.c +++ /dev/null @@ -1,139 +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 "common.h" - -#include "git2/sys/filter.h" -#include "filter.h" -#include "str.h" - -static int ident_find_id( - const char **id_start, const char **id_end, const char *start, size_t len) -{ - const char *end = start + len, *found = NULL; - - while (len > 3 && (found = memchr(start, '$', len)) != NULL) { - size_t remaining = (size_t)(end - found) - 1; - if (remaining < 3) - return GIT_ENOTFOUND; - - start = found + 1; - len = remaining; - - if (start[0] == 'I' && start[1] == 'd') - break; - } - - if (len < 3 || !found) - return GIT_ENOTFOUND; - *id_start = found; - - if ((found = memchr(start + 2, '$', len - 2)) == NULL) - return GIT_ENOTFOUND; - - *id_end = found + 1; - return 0; -} - -static int ident_insert_id( - git_str *to, const git_str *from, const git_filter_source *src) -{ - char oid[GIT_OID_HEXSZ+1]; - const char *id_start, *id_end, *from_end = from->ptr + from->size; - size_t need_size; - - /* replace $Id$ with blob id */ - - if (!git_filter_source_id(src)) - return GIT_PASSTHROUGH; - - git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src)); - - if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) - return GIT_PASSTHROUGH; - - need_size = (size_t)(id_start - from->ptr) + - 5 /* "$Id: " */ + GIT_OID_HEXSZ + 2 /* " $" */ + - (size_t)(from_end - id_end); - - if (git_str_grow(to, need_size) < 0) - return -1; - - git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); - git_str_put(to, "$Id: ", 5); - git_str_put(to, oid, GIT_OID_HEXSZ); - git_str_put(to, " $", 2); - git_str_put(to, id_end, (size_t)(from_end - id_end)); - - return git_str_oom(to) ? -1 : 0; -} - -static int ident_remove_id( - git_str *to, const git_str *from) -{ - const char *id_start, *id_end, *from_end = from->ptr + from->size; - size_t need_size; - - if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) - return GIT_PASSTHROUGH; - - need_size = (size_t)(id_start - from->ptr) + - 4 /* "$Id$" */ + (size_t)(from_end - id_end); - - if (git_str_grow(to, need_size) < 0) - return -1; - - git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); - git_str_put(to, "$Id$", 4); - git_str_put(to, id_end, (size_t)(from_end - id_end)); - - return git_str_oom(to) ? -1 : 0; -} - -static int ident_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *src) -{ - GIT_UNUSED(self); GIT_UNUSED(payload); - - /* Don't filter binary files */ - if (git_str_is_binary(from)) - return GIT_PASSTHROUGH; - - if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) - return ident_insert_id(to, from, src); - else - return ident_remove_id(to, from); -} - -static int ident_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, ident_apply, NULL, payload, src, next); -} - -git_filter *git_ident_filter_new(void) -{ - git_filter *f = git__calloc(1, sizeof(git_filter)); - if (f == NULL) - return NULL; - - f->version = GIT_FILTER_VERSION; - f->attributes = "+ident"; /* apply to files with ident attribute set */ - f->shutdown = git_filter_free; - f->stream = ident_stream; - - return f; -} diff --git a/src/idxmap.c b/src/idxmap.c deleted file mode 100644 index bc23608f2..000000000 --- a/src/idxmap.c +++ /dev/null @@ -1,157 +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 "idxmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) -__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) - -/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ -static kh_inline khint_t idxentry_hash(const git_index_entry *e) -{ - const char *s = e->path; - khint_t h = (khint_t)git__tolower(*s); - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); - return h + GIT_INDEX_ENTRY_STAGE(e); -} - -#define idxentry_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) -#define idxentry_icase_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) - -__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_new(git_idxmap **out) -{ - *out = kh_init(idx); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -int git_idxmap_icase_new(git_idxmap_icase **out) -{ - *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); -} - -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; -} - -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); - return 0; -} diff --git a/src/idxmap.h b/src/idxmap.h deleted file mode 100644 index 76170ef32..000000000 --- a/src/idxmap.h +++ /dev/null @@ -1,177 +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_idxmap_h__ -#define INCLUDE_idxmap_h__ - -#include "common.h" - -#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; - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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 deleted file mode 100644 index cee58d7f1..000000000 --- a/src/ignore.c +++ /dev/null @@ -1,652 +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 "ignore.h" - -#include "git2/ignore.h" -#include "common.h" -#include "attrcache.h" -#include "fs_path.h" -#include "config.h" -#include "wildmatch.h" -#include "path.h" - -#define GIT_IGNORE_INTERNAL "[internal]exclude" - -#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n" - -/** - * A negative ignore pattern can negate a positive one without - * wildcards if it is a basename only and equals the basename of - * the positive pattern. Thus - * - * foo/bar - * !bar - * - * would result in foo/bar being unignored again while - * - * moo/foo/bar - * !foo/bar - * - * would do nothing. The reverse also holds true: a positive - * basename pattern can be negated by unignoring the basename in - * subdirectories. Thus - * - * bar - * !foo/bar - * - * would result in foo/bar being unignored again. As with the - * first case, - * - * foo/bar - * !moo/foo/bar - * - * would do nothing, again. - */ -static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) -{ - int (*cmp)(const char *, const char *, size_t); - git_attr_fnmatch *longer, *shorter; - char *p; - - if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 - || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) - return false; - - if (neg->flags & GIT_ATTR_FNMATCH_ICASE) - cmp = git__strncasecmp; - else - cmp = git__strncmp; - - /* If lengths match we need to have an exact match */ - if (rule->length == neg->length) { - return cmp(rule->pattern, neg->pattern, rule->length) == 0; - } else if (rule->length < neg->length) { - shorter = rule; - longer = neg; - } else { - shorter = neg; - longer = rule; - } - - /* Otherwise, we need to check if the shorter - * rule is a basename only (that is, it contains - * no path separator) and, if so, if it - * matches the tail of the longer rule */ - p = longer->pattern + longer->length - shorter->length; - - if (p[-1] != '/') - return false; - if (memchr(shorter->pattern, '/', shorter->length) != NULL) - return false; - - return cmp(p, shorter->pattern, shorter->length) == 0; -} - -/** - * A negative ignore can only unignore a file which is given explicitly before, thus - * - * foo - * !foo/bar - * - * does not unignore 'foo/bar' as it's not in the list. However - * - * foo/ - * !foo/bar - * - * does unignore 'foo/bar', as it is contained within the 'foo/' rule. - */ -static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) -{ - int error = 0, wildmatch_flags, effective_flags; - size_t i; - git_attr_fnmatch *rule; - char *path; - git_str buf = GIT_STR_INIT; - - *out = 0; - - wildmatch_flags = WM_PATHNAME; - if (match->flags & GIT_ATTR_FNMATCH_ICASE) - wildmatch_flags |= WM_CASEFOLD; - - /* path of the file relative to the workdir, so we match the rules in subdirs */ - if (match->containing_dir) { - git_str_puts(&buf, match->containing_dir); - } - if (git_str_puts(&buf, match->pattern) < 0) - return -1; - - path = git_str_detach(&buf); - - git_vector_foreach(rules, i, rule) { - if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) { - if (does_negate_pattern(rule, match)) { - error = 0; - *out = 1; - goto out; - } - else - continue; - } - - git_str_clear(&buf); - if (rule->containing_dir) - git_str_puts(&buf, rule->containing_dir); - git_str_puts(&buf, rule->pattern); - - if (git_str_oom(&buf)) - goto out; - - /* - * if rule isn't for full path we match without PATHNAME flag - * as lines like *.txt should match something like dir/test.txt - * requiring * to also match / - */ - effective_flags = wildmatch_flags; - if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH)) - effective_flags &= ~WM_PATHNAME; - - /* if we found a match, we want to keep this rule */ - if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) { - *out = 1; - error = 0; - goto out; - } - } - - error = 0; - -out: - git__free(path); - git_str_dispose(&buf); - return error; -} - -static int parse_ignore_file( - 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; - - 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 */ - if (attrs->entry && - git_fs_path_root(attrs->entry->path) < 0 && - !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE)) - context = attrs->entry->path; - - if (git_mutex_lock(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock ignore file"); - return -1; - } - - while (!error && *scan) { - int valid_rule = 1; - - if (!match && !(match = git__calloc(1, sizeof(*match)))) { - error = -1; - break; - } - - match->flags = - GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; - - if (!(error = git_attr_fnmatch__parse( - match, &attrs->pool, context, &scan))) - { - match->flags |= GIT_ATTR_FNMATCH_IGNORE; - - if (ignore_case) - match->flags |= GIT_ATTR_FNMATCH_ICASE; - - scan = git__next_line(scan); - - /* - * If a negative match doesn't actually do anything, - * throw it away. As we cannot always verify whether a - * rule containing wildcards negates another rule, we - * do not optimize away these rules, though. - * */ - if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE - && !(match->flags & GIT_ATTR_FNMATCH_HASWILD)) - error = does_negate_rule(&valid_rule, &attrs->rules, match); - - if (!error && valid_rule) - error = git_vector_insert(&attrs->rules, match); - } - - if (error != 0 || !valid_rule) { - match->pattern = NULL; - - if (error == GIT_ENOTFOUND) - error = 0; - } else { - match = NULL; /* vector now "owns" the match */ - } - } - - git_mutex_unlock(&attrs->lock); - git__free(match); - - return error; -} - -static int push_ignore_file( - git_ignores *ignores, - git_vector *which_list, - const char *base, - const char *filename) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; - git_attr_file *file = NULL; - int error = 0; - - error = git_attr_cache__get(&file, ignores->repo, NULL, &source, parse_ignore_file, false); - - if (error < 0) - return error; - - if (file != NULL) { - if ((error = git_vector_insert(which_list, file)) < 0) - git_attr_file__free(file); - } - - return error; -} - -static int push_one_ignore(void *payload, const char *path) -{ - git_ignores *ign = payload; - ign->depth++; - return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE); -} - -static int get_internal_ignores(git_attr_file **out, git_repository *repo) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_MEMORY, NULL, GIT_IGNORE_INTERNAL }; - int error; - - if ((error = git_attr_cache__init(repo)) < 0) - return error; - - error = git_attr_cache__get(out, repo, NULL, &source, 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, false); - - return error; -} - -int git_ignore__for_path( - git_repository *repo, - const char *path, - git_ignores *ignores) -{ - int error = 0; - const char *workdir = git_repository_workdir(repo); - git_str infopath = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ignores); - GIT_ASSERT_ARG(path); - - memset(ignores, 0, sizeof(*ignores)); - ignores->repo = repo; - - /* Read the ignore_case flag */ - if ((error = git_repository__configmap_lookup( - &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) - goto cleanup; - - if ((error = git_attr_cache__init(repo)) < 0) - goto cleanup; - - /* given a unrooted path in a non-bare repo, resolve it */ - if (workdir && git_fs_path_root(path) < 0) { - git_str local = GIT_STR_INIT; - - if ((error = git_fs_path_dirname_r(&local, path)) < 0 || - (error = git_fs_path_resolve_relative(&local, 0)) < 0 || - (error = git_fs_path_to_dir(&local)) < 0 || - (error = git_str_joinpath(&ignores->dir, workdir, local.ptr)) < 0 || - (error = git_path_validate_str_length(repo, &ignores->dir)) < 0) { - /* Nothing, we just want to stop on the first error */ - } - - git_str_dispose(&local); - } else { - if (!(error = git_str_joinpath(&ignores->dir, path, ""))) - error = git_path_validate_str_length(NULL, &ignores->dir); - } - - if (error < 0) - goto cleanup; - - if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir)) - ignores->dir_root = strlen(workdir); - - /* set up internals */ - if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0) - goto cleanup; - - /* load .gitignore up the path */ - if (workdir != NULL) { - error = git_fs_path_walk_up( - &ignores->dir, workdir, push_one_ignore, ignores); - 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) - error = push_ignore_file( - ignores, &ignores->ign_global, NULL, - git_repository_attr_cache(repo)->cfg_excl_file); - -cleanup: - git_str_dispose(&infopath); - if (error < 0) - git_ignore__free(ignores); - - return error; -} - -int git_ignore__push_dir(git_ignores *ign, const char *dir) -{ - if (git_str_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) - return -1; - - ign->depth++; - - return push_ignore_file( - ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); -} - -int git_ignore__pop_dir(git_ignores *ign) -{ - if (ign->ign_path.length > 0) { - git_attr_file *file = git_vector_last(&ign->ign_path); - const char *start = file->entry->path, *end; - - /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/") - * - file->path looks something like "a/b/.gitignore - * - * We are popping the last directory off ign->dir. We also want - * to remove the file from the vector if the popped directory - * matches the ignore path. We need to test if the "a/b" part of - * the file key matches the path we are about to pop. - */ - - if ((end = strrchr(start, '/')) != NULL) { - size_t dirlen = (end - start) + 1; - const char *relpath = ign->dir.ptr + ign->dir_root; - size_t pathlen = ign->dir.size - ign->dir_root; - - if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) { - git_vector_pop(&ign->ign_path); - git_attr_file__free(file); - } - } - } - - if (--ign->depth > 0) { - git_str_rtruncate_at_char(&ign->dir, '/'); - git_fs_path_to_dir(&ign->dir); - } - - return 0; -} - -void git_ignore__free(git_ignores *ignores) -{ - unsigned int i; - git_attr_file *file; - - git_attr_file__free(ignores->ign_internal); - - git_vector_foreach(&ignores->ign_path, i, file) { - git_attr_file__free(file); - ignores->ign_path.contents[i] = NULL; - } - git_vector_free(&ignores->ign_path); - - git_vector_foreach(&ignores->ign_global, i, file) { - git_attr_file__free(file); - ignores->ign_global.contents[i] = NULL; - } - git_vector_free(&ignores->ign_global); - - git_str_dispose(&ignores->dir); -} - -static bool ignore_lookup_in_rules( - int *ignored, git_attr_file *file, git_attr_path *path) -{ - size_t j; - git_attr_fnmatch *match; - - git_vector_rforeach(&file->rules, j, match) { - if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && - path->is_dir == GIT_DIR_FLAG_FALSE) - continue; - if (git_attr_fnmatch__match(match, path)) { - *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? - GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; - return true; - } - } - - return false; -} - -int git_ignore__lookup( - int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag) -{ - size_t i; - git_attr_file *file; - git_attr_path path; - - *out = GIT_IGNORE_NOTFOUND; - - if (git_attr_path__init( - &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0) - return -1; - - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) - goto cleanup; - - /* 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; - } - - /* last process global ignores */ - git_vector_foreach(&ignores->ign_global, i, file) { - if (ignore_lookup_in_rules(out, file, &path)) - goto cleanup; - } - -cleanup: - git_attr_path__free(&path); - return 0; -} - -int git_ignore_add_rule(git_repository *repo, const char *rules) -{ - int error; - git_attr_file *ign_internal = NULL; - - if ((error = get_internal_ignores(&ign_internal, repo)) < 0) - return error; - - error = parse_ignore_file(repo, ign_internal, rules, false); - git_attr_file__free(ign_internal); - - return error; -} - -int git_ignore_clear_internal_rules(git_repository *repo) -{ - int error; - git_attr_file *ign_internal; - - if ((error = get_internal_ignores(&ign_internal, repo)) < 0) - return error; - - if (!(error = git_attr_file__clear_rules(ign_internal, true))) - error = parse_ignore_file( - repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false); - - git_attr_file__free(ign_internal); - return error; -} - -int git_ignore_path_is_ignored( - int *ignored, - git_repository *repo, - const char *pathname) -{ - int error; - const char *workdir; - git_attr_path path; - git_ignores ignores; - unsigned int i; - git_attr_file *file; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ignored); - GIT_ASSERT_ARG(pathname); - - workdir = git_repository_workdir(repo); - - memset(&path, 0, sizeof(path)); - memset(&ignores, 0, sizeof(ignores)); - - if (!git__suffixcmp(pathname, "/")) - dir_flag = GIT_DIR_FLAG_TRUE; - else if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 || - (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) - goto cleanup; - - while (1) { - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) - goto cleanup; - - /* next process files in the path */ - git_vector_foreach(&ignores.ign_path, i, file) { - if (ignore_lookup_in_rules(ignored, file, &path)) - goto cleanup; - } - - /* last process global ignores */ - git_vector_foreach(&ignores.ign_global, i, file) { - if (ignore_lookup_in_rules(ignored, file, &path)) - goto cleanup; - } - - /* move up one directory */ - if (path.basename == path.path) - break; - path.basename[-1] = '\0'; - while (path.basename > path.path && *path.basename != '/') - path.basename--; - if (path.basename > path.path) - path.basename++; - path.is_dir = 1; - - if ((error = git_ignore__pop_dir(&ignores)) < 0) - break; - } - - *ignored = 0; - -cleanup: - git_attr_path__free(&path); - git_ignore__free(&ignores); - return error; -} - -int git_ignore__check_pathspec_for_exact_ignores( - git_repository *repo, - git_vector *vspec, - bool no_fnmatch) -{ - int error = 0; - size_t i; - git_attr_fnmatch *match; - int ignored; - git_str path = GIT_STR_INIT; - const char *filename; - git_index *idx; - - if ((error = git_repository__ensure_not_bare( - repo, "validate pathspec")) < 0 || - (error = git_repository_index(&idx, repo)) < 0) - return error; - - git_vector_foreach(vspec, i, match) { - /* skip wildcard matches (if they are being used) */ - if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 && - !no_fnmatch) - continue; - - filename = match->pattern; - - /* if file is already in the index, it's fine */ - if (git_index_get_bypath(idx, filename, 0) != NULL) - continue; - - if ((error = git_repository_workdir_path(&path, repo, filename)) < 0) - break; - - /* is there a file on disk that matches this exactly? */ - if (!git_fs_path_isfile(path.ptr)) - continue; - - /* is that file ignored? */ - if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0) - break; - - if (ignored) { - git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'", - filename); - error = GIT_EINVALIDSPEC; - break; - } - } - - git_index_free(idx); - git_str_dispose(&path); - - return error; -} diff --git a/src/ignore.h b/src/ignore.h deleted file mode 100644 index aa5ca62b7..000000000 --- a/src/ignore.h +++ /dev/null @@ -1,65 +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_ignore_h__ -#define INCLUDE_ignore_h__ - -#include "common.h" - -#include "repository.h" -#include "vector.h" -#include "attr_file.h" - -#define GIT_IGNORE_FILE ".gitignore" -#define GIT_IGNORE_FILE_INREPO "exclude" -#define GIT_IGNORE_FILE_XDG "ignore" - -/* The git_ignores structure maintains three sets of ignores: - * - internal ignores - * - per directory ignores - * - global ignores (at lower priority than the others) - * As you traverse from one directory to another, you can push and pop - * directories onto git_ignores list efficiently. - */ -typedef struct { - git_repository *repo; - git_str dir; /* current directory reflected in ign_path */ - git_attr_file *ign_internal; - git_vector ign_path; - git_vector ign_global; - size_t dir_root; /* offset in dir to repo root */ - int ignore_case; - int depth; -} git_ignores; - -extern int git_ignore__for_path( - git_repository *repo, const char *path, git_ignores *ign); - -extern int git_ignore__push_dir(git_ignores *ign, const char *dir); - -extern int git_ignore__pop_dir(git_ignores *ign); - -extern void git_ignore__free(git_ignores *ign); - -enum { - GIT_IGNORE_UNCHECKED = -2, - GIT_IGNORE_NOTFOUND = -1, - GIT_IGNORE_FALSE = 0, - GIT_IGNORE_TRUE = 1 -}; - -extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag); - -/* command line Git sometimes generates an error message if given a - * pathspec that contains an exact match to an ignored file (provided - * --force isn't also given). This makes it easy to check it that has - * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored - * exact matches (that are not already present in the index). - */ -extern int git_ignore__check_pathspec_for_exact_ignores( - git_repository *repo, git_vector *pathspec, bool no_fnmatch); - -#endif diff --git a/src/index.c b/src/index.c deleted file mode 100644 index aa97c6421..000000000 --- a/src/index.c +++ /dev/null @@ -1,3765 +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 "index.h" - -#include - -#include "repository.h" -#include "tree.h" -#include "tree-cache.h" -#include "hash.h" -#include "iterator.h" -#include "pathspec.h" -#include "ignore.h" -#include "blob.h" -#include "idxmap.h" -#include "diff.h" -#include "varint.h" -#include "path.h" - -#include "git2/odb.h" -#include "git2/oid.h" -#include "git2/blob.h" -#include "git2/config.h" -#include "git2/sys/index.h" - -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); - -#define minimal_entry_size (offsetof(struct entry_short, path)) - -static const size_t INDEX_HEADER_SIZE = 12; - -static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2; -static const unsigned int INDEX_VERSION_NUMBER_LB = 2; -static const unsigned int INDEX_VERSION_NUMBER_EXT = 3; -static const unsigned int INDEX_VERSION_NUMBER_COMP = 4; -static const unsigned int INDEX_VERSION_NUMBER_UB = 4; - -static const unsigned int INDEX_HEADER_SIG = 0x44495243; -static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; -static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; -static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'}; - -#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx))) - -struct index_header { - uint32_t signature; - uint32_t version; - uint32_t entry_count; -}; - -struct index_extension { - char signature[4]; - uint32_t extension_size; -}; - -struct entry_time { - uint32_t seconds; - uint32_t nanoseconds; -}; - -struct entry_short { - struct entry_time ctime; - struct entry_time mtime; - uint32_t dev; - uint32_t ino; - uint32_t mode; - uint32_t uid; - uint32_t gid; - uint32_t file_size; - git_oid oid; - uint16_t flags; - char path[1]; /* arbitrary length */ -}; - -struct entry_long { - struct entry_time ctime; - struct entry_time mtime; - uint32_t dev; - uint32_t ino; - uint32_t mode; - uint32_t uid; - uint32_t gid; - uint32_t file_size; - git_oid oid; - uint16_t flags; - uint16_t flags_extended; - char path[1]; /* arbitrary length */ -}; - -struct entry_srch_key { - const char *path; - size_t pathlen; - int stage; -}; - -struct entry_internal { - git_index_entry entry; - size_t pathlen; - char path[GIT_FLEX_ARRAY]; -}; - -struct reuc_entry_internal { - git_index_reuc_entry entry; - size_t pathlen; - char path[GIT_FLEX_ARRAY]; -}; - -bool git_index__enforce_unsaved_safety = false; - -/* local declarations */ -static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size); -static int read_header(struct index_header *dest, const void *buffer); - -static int parse_index(git_index *index, const char *buffer, size_t buffer_size); -static bool is_index_extended(git_index *index); -static int write_index(unsigned char checksum[GIT_HASH_SHA1_SIZE], size_t *checksum_size, 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; - const struct entry_internal *entry = array_member; - int cmp; - size_t len1, len2, len; - - len1 = srch_key->pathlen; - len2 = entry->pathlen; - len = len1 < len2 ? len1 : len2; - - cmp = memcmp(srch_key->path, entry->path, len); - if (cmp) - return cmp; - if (len1 < len2) - return -1; - if (len1 > len2) - return 1; - - if (srch_key->stage != GIT_INDEX_STAGE_ANY) - return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); - - return 0; -} - -int git_index_entry_isrch(const void *key, const void *array_member) -{ - const struct entry_srch_key *srch_key = key; - const struct entry_internal *entry = array_member; - int cmp; - size_t len1, len2, len; - - len1 = srch_key->pathlen; - len2 = entry->pathlen; - len = len1 < len2 ? len1 : len2; - - cmp = strncasecmp(srch_key->path, entry->path, len); - - if (cmp) - return cmp; - if (len1 < len2) - return -1; - if (len1 > len2) - return 1; - - if (srch_key->stage != GIT_INDEX_STAGE_ANY) - return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); - - return 0; -} - -static int index_entry_srch_path(const void *path, const void *array_member) -{ - const git_index_entry *entry = array_member; - - return strcmp((const char *)path, entry->path); -} - -static int index_entry_isrch_path(const void *path, const void *array_member) -{ - const git_index_entry *entry = array_member; - - return strcasecmp((const char *)path, entry->path); -} - -int git_index_entry_cmp(const void *a, const void *b) -{ - int diff; - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - diff = strcmp(entry_a->path, entry_b->path); - - if (diff == 0) - diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); - - return diff; -} - -int git_index_entry_icmp(const void *a, const void *b) -{ - int diff; - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - diff = strcasecmp(entry_a->path, entry_b->path); - - if (diff == 0) - diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); - - return diff; -} - -static int conflict_name_cmp(const void *a, const void *b) -{ - const git_index_name_entry *name_a = a; - const git_index_name_entry *name_b = b; - - if (name_a->ancestor && !name_b->ancestor) - return 1; - - if (!name_a->ancestor && name_b->ancestor) - return -1; - - if (name_a->ancestor) - return strcmp(name_a->ancestor, name_b->ancestor); - - if (!name_a->ours || !name_b->ours) - return 0; - - return strcmp(name_a->ours, name_b->ours); -} - -/** - * TODO: enable this when resolving case insensitive conflicts - */ -#if 0 -static int conflict_name_icmp(const void *a, const void *b) -{ - const git_index_name_entry *name_a = a; - const git_index_name_entry *name_b = b; - - if (name_a->ancestor && !name_b->ancestor) - return 1; - - if (!name_a->ancestor && name_b->ancestor) - return -1; - - if (name_a->ancestor) - return strcasecmp(name_a->ancestor, name_b->ancestor); - - if (!name_a->ours || !name_b->ours) - return 0; - - return strcasecmp(name_a->ours, name_b->ours); -} -#endif - -static int reuc_srch(const void *key, const void *array_member) -{ - const git_index_reuc_entry *reuc = array_member; - - return strcmp(key, reuc->path); -} - -static int reuc_isrch(const void *key, const void *array_member) -{ - const git_index_reuc_entry *reuc = array_member; - - return strcasecmp(key, reuc->path); -} - -static int reuc_cmp(const void *a, const void *b) -{ - const git_index_reuc_entry *info_a = a; - const git_index_reuc_entry *info_b = b; - - return strcmp(info_a->path, info_b->path); -} - -static int reuc_icmp(const void *a, const void *b) -{ - const git_index_reuc_entry *info_a = a; - const git_index_reuc_entry *info_b = b; - - return strcasecmp(info_a->path, info_b->path); -} - -static void index_entry_reuc_free(git_index_reuc_entry *reuc) -{ - git__free(reuc); -} - -static void index_entry_free(git_index_entry *entry) -{ - if (!entry) - return; - - memset(&entry->id, 0, sizeof(entry->id)); - git__free(entry); -} - -unsigned int git_index__create_mode(unsigned int mode) -{ - if (S_ISLNK(mode)) - return S_IFLNK; - - if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) - return (S_IFLNK | S_IFDIR); - - return S_IFREG | GIT_PERMS_CANONICAL(mode); -} - -static unsigned int index_merge_mode( - git_index *index, git_index_entry *existing, unsigned int mode) -{ - if (index->no_symlinks && S_ISREG(mode) && - existing && S_ISLNK(existing->mode)) - return existing->mode; - - if (index->distrust_filemode && S_ISREG(mode)) - return (existing && S_ISREG(existing->mode)) ? - existing->mode : git_index__create_mode(0666); - - return git_index__create_mode(mode); -} - -GIT_INLINE(int) index_find_in_entries( - size_t *out, git_vector *entries, git_vector_cmp entry_srch, - const char *path, size_t path_len, int stage) -{ - struct entry_srch_key srch_key; - srch_key.path = path; - srch_key.pathlen = !path_len ? strlen(path) : path_len; - srch_key.stage = stage; - return git_vector_bsearch2(out, entries, entry_srch, &srch_key); -} - -GIT_INLINE(int) index_find( - size_t *out, git_index *index, - const char *path, size_t path_len, int stage) -{ - git_vector_sort(&index->entries); - - return index_find_in_entries( - out, &index->entries, index->entries_search, path, path_len, stage); -} - -void git_index__set_ignore_case(git_index *index, bool ignore_case) -{ - index->ignore_case = ignore_case; - - if (ignore_case) { - index->entries_cmp_path = git__strcasecmp_cb; - index->entries_search = git_index_entry_isrch; - index->entries_search_path = index_entry_isrch_path; - index->reuc_search = reuc_isrch; - } else { - index->entries_cmp_path = git__strcmp_cb; - index->entries_search = git_index_entry_srch; - index->entries_search_path = index_entry_srch_path; - index->reuc_search = reuc_srch; - } - - git_vector_set_cmp(&index->entries, - ignore_case ? git_index_entry_icmp : git_index_entry_cmp); - git_vector_sort(&index->entries); - - git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); - git_vector_sort(&index->reuc); -} - -int git_index_open(git_index **index_out, const char *index_path) -{ - git_index *index; - int error = -1; - - GIT_ASSERT_ARG(index_out); - - index = git__calloc(1, sizeof(git_index)); - GIT_ERROR_CHECK_ALLOC(index); - - if (git_pool_init(&index->tree_pool, 1) < 0) - goto fail; - - if (index_path != NULL) { - index->index_file_path = git__strdup(index_path); - if (!index->index_file_path) - goto fail; - - /* Check if index file is stored on disk already */ - if (git_fs_path_exists(index->index_file_path) == true) - index->on_disk = 1; - } - - if (git_vector_init(&index->entries, 32, 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; - index->entries_search = git_index_entry_srch; - index->entries_search_path = index_entry_srch_path; - index->reuc_search = reuc_srch; - index->version = INDEX_VERSION_NUMBER_DEFAULT; - - if (index_path != NULL && (error = git_index_read(index, true)) < 0) - goto fail; - - *index_out = index; - GIT_REFCOUNT_INC(index); - - return 0; - -fail: - git_pool_clear(&index->tree_pool); - git_index_free(index); - return error; -} - -int git_index_new(git_index **out) -{ - return git_index_open(out, NULL); -} - -static void index_free(git_index *index) -{ - /* index iterators increment the refcount of the index, so if we - * get here then there should be no outstanding iterators. - */ - if (git_atomic32_get(&index->readers)) - return; - - git_index_clear(index); - git_idxmap_free(index->entries_map); - git_vector_free(&index->entries); - git_vector_free(&index->names); - git_vector_free(&index->reuc); - git_vector_free(&index->deleted); - - git__free(index->index_file_path); - - git__memzero(index, sizeof(*index)); - git__free(index); -} - -void git_index_free(git_index *index) -{ - if (index == NULL) - return; - - GIT_REFCOUNT_DEC(index, index_free); -} - -/* call with locked index */ -static void index_free_deleted(git_index *index) -{ - int readers = (int)git_atomic32_get(&index->readers); - size_t i; - - if (readers > 0 || !index->deleted.length) - return; - - for (i = 0; i < index->deleted.length; ++i) { - git_index_entry *ie = git_atomic_swap(index->deleted.contents[i], NULL); - index_entry_free(ie); - } - - git_vector_clear(&index->deleted); -} - -/* call with locked index */ -static int index_remove_entry(git_index *index, size_t pos) -{ - int error = 0; - git_index_entry *entry = git_vector_get(&index->entries, pos); - - if (entry != NULL) { - git_tree_cache_invalidate_path(index->tree, entry->path); - index_map_delete(index->entries_map, entry, index->ignore_case); - } - - error = git_vector_remove(&index->entries, pos); - - if (!error) { - if (git_atomic32_get(&index->readers) > 0) { - error = git_vector_insert(&index->deleted, entry); - } else { - index_entry_free(entry); - } - - index->dirty = 1; - } - - return error; -} - -int git_index_clear(git_index *index) -{ - int error = 0; - - GIT_ASSERT_ARG(index); - - index->dirty = 1; - index->tree = NULL; - git_pool_clear(&index->tree_pool); - - 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); - - 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; -} - -static int create_index_error(int error, const char *msg) -{ - git_error_set_str(GIT_ERROR_INDEX, msg); - return error; -} - -int git_index_set_caps(git_index *index, int caps) -{ - unsigned int old_ignore_case; - - GIT_ASSERT_ARG(index); - - old_ignore_case = index->ignore_case; - - if (caps == GIT_INDEX_CAPABILITY_FROM_OWNER) { - git_repository *repo = INDEX_OWNER(index); - int val; - - if (!repo) - return create_index_error( - -1, "cannot access repository to set index caps"); - - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_IGNORECASE)) - index->ignore_case = (val != 0); - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FILEMODE)) - index->distrust_filemode = (val == 0); - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_SYMLINKS)) - index->no_symlinks = (val == 0); - } - else { - index->ignore_case = ((caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); - index->distrust_filemode = ((caps & GIT_INDEX_CAPABILITY_NO_FILEMODE) != 0); - index->no_symlinks = ((caps & GIT_INDEX_CAPABILITY_NO_SYMLINKS) != 0); - } - - if (old_ignore_case != index->ignore_case) { - git_index__set_ignore_case(index, (bool)index->ignore_case); - } - - return 0; -} - -int git_index_caps(const git_index *index) -{ - return ((index->ignore_case ? GIT_INDEX_CAPABILITY_IGNORE_CASE : 0) | - (index->distrust_filemode ? GIT_INDEX_CAPABILITY_NO_FILEMODE : 0) | - (index->no_symlinks ? GIT_INDEX_CAPABILITY_NO_SYMLINKS : 0)); -} - -#ifndef GIT_DEPRECATE_HARD -const git_oid *git_index_checksum(git_index *index) -{ - return (git_oid *)index->checksum; -} -#endif - -/** - * Returns 1 for changed, 0 for not changed and <0 for errors - */ -static int compare_checksum(git_index *index) -{ - int fd; - ssize_t bytes_read; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0) - return fd; - - if (p_lseek(fd, (0 - (ssize_t)checksum_size), SEEK_END) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_OS, "failed to seek to end of file"); - return -1; - } - - bytes_read = p_read(fd, checksum, checksum_size); - p_close(fd); - - if (bytes_read < (ssize_t)checksum_size) - return -1; - - return !!memcmp(checksum, index->checksum, checksum_size); -} - -int git_index_read(git_index *index, int force) -{ - int error = 0, updated; - git_str buffer = GIT_STR_INIT; - git_futils_filestamp stamp = index->stamp; - - if (!index->index_file_path) - return create_index_error(-1, - "failed to read index: The index is in-memory only"); - - index->on_disk = git_fs_path_exists(index->index_file_path); - - if (!index->on_disk) { - if (force && (error = git_index_clear(index)) < 0) - return error; - - index->dirty = 0; - return 0; - } - - if ((updated = git_futils_filestamp_check(&stamp, index->index_file_path) < 0) || - ((updated = compare_checksum(index)) < 0)) { - git_error_set( - GIT_ERROR_INDEX, - "failed to read index: '%s' no longer exists", - index->index_file_path); - return updated; - } - - if (!updated && !force) - return 0; - - error = git_futils_readbuffer(&buffer, index->index_file_path); - if (error < 0) - return error; - - index->tree = NULL; - git_pool_clear(&index->tree_pool); - - error = git_index_clear(index); - - if (!error) - error = parse_index(index, buffer.ptr, buffer.size); - - if (!error) { - git_futils_filestamp_set(&index->stamp, &stamp); - index->dirty = 0; - } - - git_str_dispose(&buffer); - return error; -} - -int git_index_read_safely(git_index *index) -{ - if (git_index__enforce_unsaved_safety && index->dirty) { - git_error_set(GIT_ERROR_INDEX, - "the index has unsaved changes that would be overwritten by this operation"); - return GIT_EINDEXDIRTY; - } - - return git_index_read(index, false); -} - -static bool is_racy_entry(git_index *index, const git_index_entry *entry) -{ - /* Git special-cases submodules in the check */ - if (S_ISGITLINK(entry->mode)) - return false; - - return git_index_entry_newer_than_index(entry, index); -} - -/* - * Force the next diff to take a look at those entries which have the - * same timestamp as the current index. - */ -static int truncate_racily_clean(git_index *index) -{ - size_t i; - int error; - git_index_entry *entry; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_vector paths = GIT_VECTOR_INIT; - git_diff_delta *delta; - - /* Nothing to do if there's no repo to talk about */ - if (!INDEX_OWNER(index)) - return 0; - - /* If there's no workdir, we can't know where to even check */ - if (!git_repository_workdir(INDEX_OWNER(index))) - return 0; - - diff_opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - git_vector_foreach(&index->entries, i, entry) { - if ((entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE) == 0 && - is_racy_entry(index, entry)) - git_vector_insert(&paths, (char *)entry->path); - } - - if (paths.length == 0) - goto done; - - diff_opts.pathspec.count = paths.length; - diff_opts.pathspec.strings = (char **)paths.contents; - - if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) - return error; - - git_vector_foreach(&diff->deltas, i, delta) { - entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0); - - /* Ensure that we have a stage 0 for this file (ie, it's not a - * conflict), otherwise smudging it is quite pointless. - */ - if (entry) { - entry->file_size = 0; - index->dirty = 1; - } - } - -done: - git_diff_free(diff); - git_vector_free(&paths); - return 0; -} - -unsigned git_index_version(git_index *index) -{ - GIT_ASSERT_ARG(index); - - return index->version; -} - -int git_index_set_version(git_index *index, unsigned int version) -{ - GIT_ASSERT_ARG(index); - - if (version < INDEX_VERSION_NUMBER_LB || - version > INDEX_VERSION_NUMBER_UB) { - git_error_set(GIT_ERROR_INDEX, "invalid version number"); - return -1; - } - - index->version = version; - - return 0; -} - -int git_index_write(git_index *index) -{ - git_indexwriter writer = GIT_INDEXWRITER_INIT; - int error; - - truncate_racily_clean(index); - - if ((error = git_indexwriter_init(&writer, index)) == 0 && - (error = git_indexwriter_commit(&writer)) == 0) - index->dirty = 0; - - git_indexwriter_cleanup(&writer); - - return error; -} - -const char *git_index_path(const git_index *index) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - return index->index_file_path; -} - -int git_index_write_tree(git_oid *oid, git_index *index) -{ - git_repository *repo; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(index); - - repo = INDEX_OWNER(index); - - if (repo == NULL) - return create_index_error(-1, "Failed to write tree. " - "the index file is not backed up by an existing repository"); - - return git_tree__write_index(oid, index, repo); -} - -int git_index_write_tree_to( - git_oid *oid, git_index *index, git_repository *repo) -{ - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(repo); - - return git_tree__write_index(oid, index, repo); -} - -size_t git_index_entrycount(const git_index *index) -{ - GIT_ASSERT_ARG(index); - - return index->entries.length; -} - -const git_index_entry *git_index_get_byindex( - git_index *index, size_t n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - - git_vector_sort(&index->entries); - return git_vector_get(&index->entries, n); -} - -const git_index_entry *git_index_get_bypath( - git_index *index, const char *path, int stage) -{ - git_index_entry key = {{ 0 }}; - git_index_entry *value; - - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - - key.path = path; - GIT_INDEX_ENTRY_STAGE_SET(&key, stage); - - 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 (!value) { - git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); - return NULL; - } - - return value; -} - -void git_index_entry__init_from_stat( - git_index_entry *entry, struct stat *st, bool trust_mode) -{ - entry->ctime.seconds = (int32_t)st->st_ctime; - entry->mtime.seconds = (int32_t)st->st_mtime; -#if defined(GIT_USE_NSEC) - entry->mtime.nanoseconds = st->st_mtime_nsec; - entry->ctime.nanoseconds = st->st_ctime_nsec; -#endif - entry->dev = st->st_rdev; - entry->ino = st->st_ino; - entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? - git_index__create_mode(0666) : git_index__create_mode(st->st_mode); - entry->uid = st->st_uid; - entry->gid = st->st_gid; - entry->file_size = (uint32_t)st->st_size; -} - -static void index_entry_adjust_namemask( - git_index_entry *entry, - size_t path_length) -{ - entry->flags &= ~GIT_INDEX_ENTRY_NAMEMASK; - - if (path_length < GIT_INDEX_ENTRY_NAMEMASK) - entry->flags |= path_length & GIT_INDEX_ENTRY_NAMEMASK; - else - entry->flags |= GIT_INDEX_ENTRY_NAMEMASK; -} - -/* When `from_workdir` is true, we will validate the paths to avoid placing - * paths that are invalid for the working directory on the current filesystem - * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This - * function will *always* prevent `.git` and directory traversal `../` from - * being added to the index. - */ -static int index_entry_create( - git_index_entry **out, - git_repository *repo, - const char *path, - struct stat *st, - bool from_workdir) -{ - size_t pathlen = strlen(path), alloclen; - struct entry_internal *entry; - unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; - uint16_t mode = 0; - - /* always reject placing `.git` in the index and directory traversal. - * when requested, disallow platform-specific filenames and upgrade to - * the platform-specific `.git` tests (eg, `git~1`, etc). - */ - if (from_workdir) - path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; - if (st) - mode = st->st_mode; - - if (!git_path_is_valid(repo, path, mode, path_valid_flags)) { - git_error_set(GIT_ERROR_INDEX, "invalid path: '%s'", path); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(struct entry_internal), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - entry = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->pathlen = pathlen; - memcpy(entry->path, path, pathlen); - entry->entry.path = entry->path; - - *out = (git_index_entry *)entry; - return 0; -} - -static int index_entry_init( - git_index_entry **entry_out, - git_index *index, - const char *rel_path) -{ - int error = 0; - git_index_entry *entry = NULL; - git_str path = GIT_STR_INIT; - struct stat st; - git_oid oid; - git_repository *repo; - - if (INDEX_OWNER(index) == NULL) - return create_index_error(-1, - "could not initialize index entry. " - "Index is not backed up by an existing repository."); - - /* - * FIXME: this is duplicated with the work in - * git_blob__create_from_paths. It should accept an optional stat - * structure so we can pass in the one we have to do here. - */ - repo = INDEX_OWNER(index); - if (git_repository__ensure_not_bare(repo, "create blob from file") < 0) - return GIT_EBAREREPO; - - if (git_repository_workdir_path(&path, repo, rel_path) < 0) - return -1; - - error = git_fs_path_lstat(path.ptr, &st); - git_str_dispose(&path); - - if (error < 0) - return error; - - if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, &st, true) < 0) - return -1; - - /* write the blob to disk and get the oid and stat info */ - error = git_blob__create_from_paths( - &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); - - if (error < 0) { - index_entry_free(entry); - return error; - } - - entry->id = oid; - git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); - - *entry_out = (git_index_entry *)entry; - return 0; -} - -static git_index_reuc_entry *reuc_entry_alloc(const char *path) -{ - size_t pathlen = strlen(path), - structlen = sizeof(struct reuc_entry_internal), - alloclen; - struct reuc_entry_internal *entry; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, structlen, pathlen) || - GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1)) - return NULL; - - entry = git__calloc(1, alloclen); - if (!entry) - return NULL; - - entry->pathlen = pathlen; - memcpy(entry->path, path, pathlen); - entry->entry.path = entry->path; - - return (git_index_reuc_entry *)entry; -} - -static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, - const char *path, - int ancestor_mode, const git_oid *ancestor_oid, - int our_mode, const git_oid *our_oid, - int their_mode, const git_oid *their_oid) -{ - git_index_reuc_entry *reuc = NULL; - - GIT_ASSERT_ARG(reuc_out); - GIT_ASSERT_ARG(path); - - *reuc_out = reuc = reuc_entry_alloc(path); - GIT_ERROR_CHECK_ALLOC(reuc); - - if ((reuc->mode[0] = ancestor_mode) > 0) { - GIT_ASSERT(ancestor_oid); - git_oid_cpy(&reuc->oid[0], ancestor_oid); - } - - if ((reuc->mode[1] = our_mode) > 0) { - GIT_ASSERT(our_oid); - git_oid_cpy(&reuc->oid[1], our_oid); - } - - if ((reuc->mode[2] = their_mode) > 0) { - GIT_ASSERT(their_oid); - git_oid_cpy(&reuc->oid[2], their_oid); - } - - return 0; -} - -static void index_entry_cpy( - git_index_entry *tgt, - const git_index_entry *src) -{ - const char *tgt_path = tgt->path; - memcpy(tgt, src, sizeof(*tgt)); - tgt->path = tgt_path; -} - -static int index_entry_dup( - git_index_entry **out, - git_index *index, - const git_index_entry *src) -{ - if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) - return -1; - - index_entry_cpy(*out, src); - return 0; -} - -static void index_entry_cpy_nocache( - git_index_entry *tgt, - const git_index_entry *src) -{ - git_oid_cpy(&tgt->id, &src->id); - tgt->mode = src->mode; - tgt->flags = src->flags; - tgt->flags_extended = (src->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS); -} - -static int index_entry_dup_nocache( - git_index_entry **out, - git_index *index, - const git_index_entry *src) -{ - if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) - return -1; - - index_entry_cpy_nocache(*out, src); - return 0; -} - -static int has_file_name(git_index *index, - const git_index_entry *entry, size_t pos, int ok_to_replace) -{ - size_t len = strlen(entry->path); - int stage = GIT_INDEX_ENTRY_STAGE(entry); - const char *name = entry->path; - - while (pos < index->entries.length) { - struct entry_internal *p = index->entries.contents[pos++]; - - if (len >= p->pathlen) - break; - if (memcmp(name, p->path, len)) - break; - if (GIT_INDEX_ENTRY_STAGE(&p->entry) != stage) - continue; - if (p->path[len] != '/') - continue; - if (!ok_to_replace) - return -1; - - if (index_remove_entry(index, --pos) < 0) - break; - } - return 0; -} - -/* - * Do we have another file with a pathname that is a proper - * subset of the name we're trying to add? - */ -static int has_dir_name(git_index *index, - const git_index_entry *entry, int ok_to_replace) -{ - int stage = GIT_INDEX_ENTRY_STAGE(entry); - const char *name = entry->path; - const char *slash = name + strlen(name); - - for (;;) { - size_t len, pos; - - for (;;) { - if (*--slash == '/') - break; - if (slash <= entry->path) - return 0; - } - len = slash - name; - - if (!index_find(&pos, index, name, len, stage)) { - if (!ok_to_replace) - return -1; - - if (index_remove_entry(index, pos) < 0) - break; - continue; - } - - /* - * Trivial optimization: if we find an entry that - * already matches the sub-directory, then we know - * we're ok, and we can exit. - */ - for (; pos < index->entries.length; ++pos) { - struct entry_internal *p = index->entries.contents[pos]; - - if (p->pathlen <= len || - p->path[len] != '/' || - memcmp(p->path, name, len)) - break; /* not our subdirectory */ - - if (GIT_INDEX_ENTRY_STAGE(&p->entry) == stage) - return 0; - } - } - - return 0; -} - -static int check_file_directory_collision(git_index *index, - git_index_entry *entry, size_t pos, int ok_to_replace) -{ - if (has_file_name(index, entry, pos, ok_to_replace) < 0 || - has_dir_name(index, entry, ok_to_replace) < 0) { - git_error_set(GIT_ERROR_INDEX, - "'%s' appears as both a file and a directory", entry->path); - return -1; - } - - return 0; -} - -static int canonicalize_directory_path( - git_index *index, - git_index_entry *entry, - git_index_entry *existing) -{ - const git_index_entry *match, *best = NULL; - char *search, *sep; - size_t pos, search_len, best_len; - - if (!index->ignore_case) - return 0; - - /* item already exists in the index, simply re-use the existing case */ - if (existing) { - memcpy((char *)entry->path, existing->path, strlen(existing->path)); - return 0; - } - - /* nothing to do */ - if (strchr(entry->path, '/') == NULL) - return 0; - - if ((search = git__strdup(entry->path)) == NULL) - return -1; - - /* starting at the parent directory and descending to the root, find the - * common parent directory. - */ - while (!best && (sep = strrchr(search, '/'))) { - sep[1] = '\0'; - - search_len = strlen(search); - - git_vector_bsearch2( - &pos, &index->entries, index->entries_search_path, search); - - while ((match = git_vector_get(&index->entries, pos))) { - if (GIT_INDEX_ENTRY_STAGE(match) != 0) { - /* conflicts do not contribute to canonical paths */ - } else if (strncmp(search, match->path, search_len) == 0) { - /* prefer an exact match to the input filename */ - best = match; - best_len = search_len; - break; - } else if (strncasecmp(search, match->path, search_len) == 0) { - /* continue walking, there may be a path with an exact - * (case sensitive) match later in the index, but use this - * as the best match until that happens. - */ - if (!best) { - best = match; - best_len = search_len; - } - } else { - break; - } - - pos++; - } - - sep[0] = '\0'; - } - - if (best) - memcpy((char *)entry->path, best->path, best_len); - - git__free(search); - return 0; -} - -static int index_no_dups(void **old, void *new) -{ - const git_index_entry *entry = new; - GIT_UNUSED(old); - git_error_set(GIT_ERROR_INDEX, "'%s' appears multiple times at stage %d", - entry->path, GIT_INDEX_ENTRY_STAGE(entry)); - return GIT_EEXISTS; -} - -static void index_existing_and_best( - git_index_entry **existing, - size_t *existing_position, - git_index_entry **best, - git_index *index, - const git_index_entry *entry) -{ - git_index_entry *e; - size_t pos; - int error; - - error = index_find(&pos, - index, entry->path, 0, GIT_INDEX_ENTRY_STAGE(entry)); - - if (error == 0) { - *existing = index->entries.contents[pos]; - *existing_position = pos; - *best = index->entries.contents[pos]; - return; - } - - *existing = NULL; - *existing_position = 0; - *best = NULL; - - if (GIT_INDEX_ENTRY_STAGE(entry) == 0) { - for (; pos < index->entries.length; pos++) { - int (*strcomp)(const char *a, const char *b) = - index->ignore_case ? git__strcasecmp : git__strcmp; - - e = index->entries.contents[pos]; - - if (strcomp(entry->path, e->path) != 0) - break; - - if (GIT_INDEX_ENTRY_STAGE(e) == GIT_INDEX_STAGE_ANCESTOR) { - *best = e; - continue; - } else { - *best = e; - break; - } - } - } -} - -/* index_insert takes ownership of the new entry - if it can't insert - * it, then it will return an error **and also free the entry**. When - * it replaces an existing entry, it will update the entry_ptr with the - * actual entry in the index (and free the passed in one). - * - * trust_path is whether we use the given path, or whether (on case - * insensitive systems only) we try to canonicalize the given path to - * be within an existing directory. - * - * trust_mode is whether we trust the mode in entry_ptr. - * - * trust_id is whether we trust the id or it should be validated. - */ -static int index_insert( - git_index *index, - git_index_entry **entry_ptr, - int replace, - bool trust_path, - bool trust_mode, - bool trust_id) -{ - git_index_entry *existing, *best, *entry; - size_t path_length, position; - int error; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(entry_ptr); - - entry = *entry_ptr; - - /* Make sure that the path length flag is correct */ - path_length = ((struct entry_internal *)entry)->pathlen; - index_entry_adjust_namemask(entry, path_length); - - /* This entry is now up-to-date and should not be checked for raciness */ - entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; - - git_vector_sort(&index->entries); - - /* - * Look if an entry with this path already exists, either staged, or (if - * this entry is a regular staged item) as the "ours" side of a conflict. - */ - index_existing_and_best(&existing, &position, &best, index, entry); - - /* Update the file mode */ - entry->mode = trust_mode ? - git_index__create_mode(entry->mode) : - index_merge_mode(index, best, entry->mode); - - /* Canonicalize the directory name */ - if (!trust_path && (error = canonicalize_directory_path(index, entry, best)) < 0) - goto out; - - /* Ensure that the given id exists (unless it's a submodule) */ - if (!trust_id && INDEX_OWNER(index) && - (entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) { - - if (!git_object__is_valid(INDEX_OWNER(index), &entry->id, - git_object__type_from_filemode(entry->mode))) { - error = -1; - goto out; - } - } - - /* Look for tree / blob name collisions, removing conflicts if requested */ - if ((error = check_file_directory_collision(index, entry, position, replace)) < 0) - goto out; - - /* - * If we are replacing an existing item, overwrite the existing entry - * and return it in place of the passed in one. - */ - if (existing) { - if (replace) { - index_entry_cpy(existing, entry); - - if (trust_path) - memcpy((char *)existing->path, entry->path, strlen(entry->path)); - } - - index_entry_free(entry); - *entry_ptr = existing; - } else { - /* - * If replace is not requested or no existing entry exists, 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 || - (error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) - goto out; - } - - index->dirty = 1; - -out: - if (error < 0) { - index_entry_free(*entry_ptr); - *entry_ptr = NULL; - } - - return error; -} - -static int index_conflict_to_reuc(git_index *index, const char *path) -{ - const git_index_entry *conflict_entries[3]; - int ancestor_mode, our_mode, their_mode; - git_oid const *ancestor_oid, *our_oid, *their_oid; - int ret; - - if ((ret = git_index_conflict_get(&conflict_entries[0], - &conflict_entries[1], &conflict_entries[2], index, path)) < 0) - return ret; - - ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode; - our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode; - their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode; - - ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->id; - our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->id; - their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->id; - - if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid, - our_mode, our_oid, their_mode, their_oid)) >= 0) - ret = git_index_conflict_remove(index, path); - - return ret; -} - -GIT_INLINE(bool) is_file_or_link(const int filemode) -{ - return (filemode == GIT_FILEMODE_BLOB || - filemode == GIT_FILEMODE_BLOB_EXECUTABLE || - filemode == GIT_FILEMODE_LINK); -} - -GIT_INLINE(bool) valid_filemode(const int filemode) -{ - return (is_file_or_link(filemode) || filemode == GIT_FILEMODE_COMMIT); -} - -int git_index_add_from_buffer( - git_index *index, const git_index_entry *source_entry, - const void *buffer, size_t len) -{ - git_index_entry *entry = NULL; - int error = 0; - git_oid id; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(source_entry && source_entry->path); - - if (INDEX_OWNER(index) == NULL) - return create_index_error(-1, - "could not initialize index entry. " - "Index is not backed up by an existing repository."); - - if (!is_file_or_link(source_entry->mode)) { - git_error_set(GIT_ERROR_INDEX, "invalid filemode"); - 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_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 = (uint32_t)len; - - if ((error = index_insert(index, &entry, 1, true, true, true)) < 0) - return error; - - /* Adding implies conflict was resolved, move conflict entries to REUC */ - if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND) - return error; - - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} - -static int add_repo_as_submodule(git_index_entry **out, git_index *index, const char *path) -{ - git_repository *sub; - git_str abspath = GIT_STR_INIT; - git_repository *repo = INDEX_OWNER(index); - git_reference *head; - git_index_entry *entry; - struct stat st; - int error; - - if ((error = git_repository_workdir_path(&abspath, repo, path)) < 0) - return error; - - if ((error = p_stat(abspath.ptr, &st)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat repository dir"); - return -1; - } - - if (index_entry_create(&entry, INDEX_OWNER(index), path, &st, true) < 0) - return -1; - - git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); - - if ((error = git_repository_open(&sub, abspath.ptr)) < 0) - return error; - - if ((error = git_repository_head(&head, sub)) < 0) - return error; - - git_oid_cpy(&entry->id, git_reference_target(head)); - entry->mode = GIT_FILEMODE_COMMIT; - - git_reference_free(head); - git_repository_free(sub); - git_str_dispose(&abspath); - - *out = entry; - return 0; -} - -int git_index_add_bypath(git_index *index, const char *path) -{ - git_index_entry *entry = NULL; - int ret; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if ((ret = index_entry_init(&entry, index, path)) == 0) - ret = index_insert(index, &entry, 1, false, false, true); - - /* If we were given a directory, let's see if it's a submodule */ - if (ret < 0 && ret != GIT_EDIRECTORY) - return ret; - - if (ret == GIT_EDIRECTORY) { - git_submodule *sm; - git_error_state err; - - git_error_state_capture(&err, ret); - - ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); - if (ret == GIT_ENOTFOUND) - return git_error_state_restore(&err); - - git_error_state_free(&err); - - /* - * EEXISTS means that there is a repository at that path, but it's not known - * as a submodule. We add its HEAD as an entry and don't register it. - */ - if (ret == GIT_EEXISTS) { - if ((ret = add_repo_as_submodule(&entry, index, path)) < 0) - return ret; - - if ((ret = index_insert(index, &entry, 1, false, false, true)) < 0) - return ret; - } else if (ret < 0) { - return ret; - } else { - ret = git_submodule_add_to_index(sm, false); - git_submodule_free(sm); - return ret; - } - } - - /* Adding implies conflict was resolved, move conflict entries to REUC */ - if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND) - return ret; - - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} - -int git_index_remove_bypath(git_index *index, const char *path) -{ - int ret; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if (((ret = git_index_remove(index, path, 0)) < 0 && - ret != GIT_ENOTFOUND) || - ((ret = index_conflict_to_reuc(index, path)) < 0 && - ret != GIT_ENOTFOUND)) - return ret; - - if (ret == GIT_ENOTFOUND) - git_error_clear(); - - return 0; -} - -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; - - GIT_ASSERT_ARG(index); - - if (!source_entries->length) - return 0; - - 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 ((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 ((error = git_vector_insert(&index->entries, entry)) < 0) - break; - - if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) - break; - - index->dirty = 1; - } - - if (!error) - git_vector_sort(&index->entries); - - return error; -} - - -int git_index_add(git_index *index, const git_index_entry *source_entry) -{ - git_index_entry *entry = NULL; - int ret; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(source_entry && source_entry->path); - - if (!valid_filemode(source_entry->mode)) { - git_error_set(GIT_ERROR_INDEX, "invalid entry mode"); - return -1; - } - - if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 || - (ret = index_insert(index, &entry, 1, true, true, false)) < 0) - return ret; - - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} - -int git_index_remove(git_index *index, const char *path, int stage) -{ - int error; - size_t position; - git_index_entry remove_key = {{ 0 }}; - - remove_key.path = path; - GIT_INDEX_ENTRY_STAGE_SET(&remove_key, stage); - - index_map_delete(index->entries_map, &remove_key, index->ignore_case); - - if (index_find(&position, index, path, 0, stage) < 0) { - git_error_set( - GIT_ERROR_INDEX, "index does not contain %s at stage %d", path, stage); - error = GIT_ENOTFOUND; - } else { - error = index_remove_entry(index, position); - } - - return error; -} - -int git_index_remove_directory(git_index *index, const char *dir, int stage) -{ - git_str pfx = GIT_STR_INIT; - int error = 0; - size_t pos; - git_index_entry *entry; - - if (!(error = git_str_sets(&pfx, dir)) && - !(error = git_fs_path_to_dir(&pfx))) - index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY); - - while (!error) { - entry = git_vector_get(&index->entries, pos); - if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0) - break; - - if (GIT_INDEX_ENTRY_STAGE(entry) != stage) { - ++pos; - continue; - } - - error = index_remove_entry(index, pos); - - /* removed entry at 'pos' so we don't need to increment */ - } - - git_str_dispose(&pfx); - - return error; -} - -int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix) -{ - int error = 0; - size_t pos; - const git_index_entry *entry; - - index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY); - entry = git_vector_get(&index->entries, pos); - if (!entry || git__prefixcmp(entry->path, prefix) != 0) - error = GIT_ENOTFOUND; - - if (!error && at_pos) - *at_pos = pos; - - return error; -} - -int git_index__find_pos( - size_t *out, git_index *index, const char *path, size_t path_len, int stage) -{ - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - return index_find(out, index, path, path_len, stage); -} - -int git_index_find(size_t *at_pos, git_index *index, const char *path) -{ - size_t pos; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if (git_vector_bsearch2( - &pos, &index->entries, index->entries_search_path, path) < 0) { - git_error_set(GIT_ERROR_INDEX, "index does not contain %s", path); - return GIT_ENOTFOUND; - } - - /* Since our binary search only looked at path, we may be in the - * middle of a list of stages. - */ - for (; pos > 0; --pos) { - const git_index_entry *prev = git_vector_get(&index->entries, pos - 1); - - if (index->entries_cmp_path(prev->path, path) != 0) - break; - } - - if (at_pos) - *at_pos = pos; - - return 0; -} - -int git_index_conflict_add(git_index *index, - const git_index_entry *ancestor_entry, - const git_index_entry *our_entry, - const git_index_entry *their_entry) -{ - git_index_entry *entries[3] = { 0 }; - unsigned short i; - int ret = 0; - - GIT_ASSERT_ARG(index); - - if ((ancestor_entry && - (ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0) || - (our_entry && - (ret = index_entry_dup(&entries[1], index, our_entry)) < 0) || - (their_entry && - (ret = index_entry_dup(&entries[2], index, their_entry)) < 0)) - goto on_error; - - /* Validate entries */ - for (i = 0; i < 3; i++) { - if (entries[i] && !valid_filemode(entries[i]->mode)) { - git_error_set(GIT_ERROR_INDEX, "invalid filemode for stage %d entry", - i + 1); - ret = -1; - goto on_error; - } - } - - /* Remove existing index entries for each path */ - for (i = 0; i < 3; i++) { - if (entries[i] == NULL) - continue; - - if ((ret = git_index_remove(index, entries[i]->path, 0)) != 0) { - if (ret != GIT_ENOTFOUND) - goto on_error; - - git_error_clear(); - ret = 0; - } - } - - /* Add the conflict entries */ - for (i = 0; i < 3; i++) { - if (entries[i] == NULL) - continue; - - /* Make sure stage is correct */ - GIT_INDEX_ENTRY_STAGE_SET(entries[i], i + 1); - - if ((ret = index_insert(index, &entries[i], 1, true, true, false)) < 0) - goto on_error; - - entries[i] = NULL; /* don't free if later entry fails */ - } - - return 0; - -on_error: - for (i = 0; i < 3; i++) { - if (entries[i] != NULL) - index_entry_free(entries[i]); - } - - return ret; -} - -static int index_conflict__get_byindex( - const git_index_entry **ancestor_out, - const git_index_entry **our_out, - const git_index_entry **their_out, - git_index *index, - size_t n) -{ - const git_index_entry *conflict_entry; - const char *path = NULL; - size_t count; - int stage, len = 0; - - GIT_ASSERT_ARG(ancestor_out); - GIT_ASSERT_ARG(our_out); - GIT_ASSERT_ARG(their_out); - GIT_ASSERT_ARG(index); - - *ancestor_out = NULL; - *our_out = NULL; - *their_out = NULL; - - for (count = git_index_entrycount(index); n < count; ++n) { - conflict_entry = git_vector_get(&index->entries, n); - - if (path && index->entries_cmp_path(conflict_entry->path, path) != 0) - break; - - stage = GIT_INDEX_ENTRY_STAGE(conflict_entry); - path = conflict_entry->path; - - switch (stage) { - case 3: - *their_out = conflict_entry; - len++; - break; - case 2: - *our_out = conflict_entry; - len++; - break; - case 1: - *ancestor_out = conflict_entry; - len++; - break; - default: - break; - }; - } - - return len; -} - -int git_index_conflict_get( - const git_index_entry **ancestor_out, - const git_index_entry **our_out, - const git_index_entry **their_out, - git_index *index, - const char *path) -{ - size_t pos; - int len = 0; - - GIT_ASSERT_ARG(ancestor_out); - GIT_ASSERT_ARG(our_out); - GIT_ASSERT_ARG(their_out); - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - *ancestor_out = NULL; - *our_out = NULL; - *their_out = NULL; - - if (git_index_find(&pos, index, path) < 0) - return GIT_ENOTFOUND; - - if ((len = index_conflict__get_byindex( - ancestor_out, our_out, their_out, index, pos)) < 0) - return len; - else if (len == 0) - return GIT_ENOTFOUND; - - return 0; -} - -static int index_conflict_remove(git_index *index, const char *path) -{ - size_t pos = 0; - git_index_entry *conflict_entry; - int error = 0; - - if (path != NULL && git_index_find(&pos, index, path) < 0) - return GIT_ENOTFOUND; - - while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) { - - if (path != NULL && - index->entries_cmp_path(conflict_entry->path, path) != 0) - break; - - if (GIT_INDEX_ENTRY_STAGE(conflict_entry) == 0) { - pos++; - continue; - } - - if ((error = index_remove_entry(index, pos)) < 0) - break; - } - - return error; -} - -int git_index_conflict_remove(git_index *index, const char *path) -{ - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - return index_conflict_remove(index, path); -} - -int git_index_conflict_cleanup(git_index *index) -{ - GIT_ASSERT_ARG(index); - return index_conflict_remove(index, NULL); -} - -int git_index_has_conflicts(const git_index *index) -{ - size_t i; - git_index_entry *entry; - - GIT_ASSERT_ARG(index); - - git_vector_foreach(&index->entries, i, entry) { - if (GIT_INDEX_ENTRY_STAGE(entry) > 0) - return 1; - } - - return 0; -} - -int git_index_iterator_new( - git_index_iterator **iterator_out, - git_index *index) -{ - git_index_iterator *it; - int error; - - GIT_ASSERT_ARG(iterator_out); - GIT_ASSERT_ARG(index); - - it = git__calloc(1, sizeof(git_index_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - - if ((error = git_index_snapshot_new(&it->snap, index)) < 0) { - git__free(it); - return error; - } - - it->index = index; - - *iterator_out = it; - return 0; -} - -int git_index_iterator_next( - const git_index_entry **out, - git_index_iterator *it) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(it); - - if (it->cur >= git_vector_length(&it->snap)) - return GIT_ITEROVER; - - *out = (git_index_entry *)git_vector_get(&it->snap, it->cur++); - return 0; -} - -void git_index_iterator_free(git_index_iterator *it) -{ - if (it == NULL) - return; - - git_index_snapshot_release(&it->snap, it->index); - git__free(it); -} - -int git_index_conflict_iterator_new( - git_index_conflict_iterator **iterator_out, - git_index *index) -{ - git_index_conflict_iterator *it = NULL; - - GIT_ASSERT_ARG(iterator_out); - GIT_ASSERT_ARG(index); - - it = git__calloc(1, sizeof(git_index_conflict_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - - it->index = index; - - *iterator_out = it; - return 0; -} - -int git_index_conflict_next( - const git_index_entry **ancestor_out, - const git_index_entry **our_out, - const git_index_entry **their_out, - git_index_conflict_iterator *iterator) -{ - const git_index_entry *entry; - int len; - - GIT_ASSERT_ARG(ancestor_out); - GIT_ASSERT_ARG(our_out); - GIT_ASSERT_ARG(their_out); - GIT_ASSERT_ARG(iterator); - - *ancestor_out = NULL; - *our_out = NULL; - *their_out = NULL; - - while (iterator->cur < iterator->index->entries.length) { - entry = git_index_get_byindex(iterator->index, iterator->cur); - - if (git_index_entry_is_conflict(entry)) { - if ((len = index_conflict__get_byindex( - ancestor_out, - our_out, - their_out, - iterator->index, - iterator->cur)) < 0) - return len; - - iterator->cur += len; - return 0; - } - - iterator->cur++; - } - - return GIT_ITEROVER; -} - -void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator) -{ - if (iterator == NULL) - return; - - git__free(iterator); -} - -size_t git_index_name_entrycount(git_index *index) -{ - GIT_ASSERT_ARG(index); - return index->names.length; -} - -const git_index_name_entry *git_index_name_get_byindex( - git_index *index, size_t n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - - git_vector_sort(&index->names); - return git_vector_get(&index->names, n); -} - -static void index_name_entry_free(git_index_name_entry *ne) -{ - if (!ne) - return; - git__free(ne->ancestor); - git__free(ne->ours); - git__free(ne->theirs); - git__free(ne); -} - -int git_index_name_add(git_index *index, - const char *ancestor, const char *ours, const char *theirs) -{ - git_index_name_entry *conflict_name; - - GIT_ASSERT_ARG((ancestor && ours) || (ancestor && theirs) || (ours && theirs)); - - conflict_name = git__calloc(1, sizeof(git_index_name_entry)); - GIT_ERROR_CHECK_ALLOC(conflict_name); - - if ((ancestor && !(conflict_name->ancestor = git__strdup(ancestor))) || - (ours && !(conflict_name->ours = git__strdup(ours))) || - (theirs && !(conflict_name->theirs = git__strdup(theirs))) || - git_vector_insert(&index->names, conflict_name) < 0) - { - index_name_entry_free(conflict_name); - return -1; - } - - index->dirty = 1; - return 0; -} - -int git_index_name_clear(git_index *index) -{ - size_t i; - git_index_name_entry *conflict_name; - - GIT_ASSERT_ARG(index); - - git_vector_foreach(&index->names, i, conflict_name) - index_name_entry_free(conflict_name); - - git_vector_clear(&index->names); - - index->dirty = 1; - - return 0; -} - -size_t git_index_reuc_entrycount(git_index *index) -{ - GIT_ASSERT_ARG(index); - return index->reuc.length; -} - -static int index_reuc_on_dup(void **old, void *new) -{ - index_entry_reuc_free(*old); - *old = new; - return GIT_EEXISTS; -} - -static int index_reuc_insert( - git_index *index, - git_index_reuc_entry *reuc) -{ - int res; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(reuc && reuc->path != NULL); - GIT_ASSERT(git_vector_is_sorted(&index->reuc)); - - res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup); - index->dirty = 1; - - return res == GIT_EEXISTS ? 0 : res; -} - -int git_index_reuc_add(git_index *index, const char *path, - int ancestor_mode, const git_oid *ancestor_oid, - int our_mode, const git_oid *our_oid, - int their_mode, const git_oid *their_oid) -{ - git_index_reuc_entry *reuc = NULL; - int error = 0; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, - ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 || - (error = index_reuc_insert(index, reuc)) < 0) - index_entry_reuc_free(reuc); - - return error; -} - -int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path) -{ - return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path); -} - -const git_index_reuc_entry *git_index_reuc_get_bypath( - git_index *index, const char *path) -{ - size_t pos; - - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(path, NULL); - - if (!index->reuc.length) - return NULL; - - GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); - - if (git_index_reuc_find(&pos, index, path) < 0) - return NULL; - - return git_vector_get(&index->reuc, pos); -} - -const git_index_reuc_entry *git_index_reuc_get_byindex( - git_index *index, size_t n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); - - return git_vector_get(&index->reuc, n); -} - -int git_index_reuc_remove(git_index *index, size_t position) -{ - int error; - git_index_reuc_entry *reuc; - - GIT_ASSERT_ARG(index); - GIT_ASSERT(git_vector_is_sorted(&index->reuc)); - - reuc = git_vector_get(&index->reuc, position); - error = git_vector_remove(&index->reuc, position); - - if (!error) - index_entry_reuc_free(reuc); - - index->dirty = 1; - return error; -} - -int git_index_reuc_clear(git_index *index) -{ - size_t i; - - GIT_ASSERT_ARG(index); - - for (i = 0; i < index->reuc.length; ++i) - index_entry_reuc_free(git_atomic_swap(index->reuc.contents[i], NULL)); - - git_vector_clear(&index->reuc); - - index->dirty = 1; - - return 0; -} - -static int index_error_invalid(const char *message) -{ - git_error_set(GIT_ERROR_INDEX, "invalid data in index - %s", message); - return -1; -} - -static int read_reuc(git_index *index, const char *buffer, size_t size) -{ - const char *endptr; - size_t len; - int i; - - /* If called multiple times, the vector might already be initialized */ - if (index->reuc._alloc_size == 0 && - git_vector_init(&index->reuc, 16, reuc_cmp) < 0) - return -1; - - while (size) { - git_index_reuc_entry *lost; - - len = p_strnlen(buffer, size) + 1; - if (size <= len) - return index_error_invalid("reading reuc entries"); - - lost = reuc_entry_alloc(buffer); - GIT_ERROR_CHECK_ALLOC(lost); - - size -= len; - buffer += len; - - /* read 3 ASCII octal numbers for stage entries */ - for (i = 0; i < 3; i++) { - int64_t tmp; - - if (git__strntol64(&tmp, buffer, size, &endptr, 8) < 0 || - !endptr || endptr == buffer || *endptr || - tmp < 0 || tmp > UINT32_MAX) { - index_entry_reuc_free(lost); - return index_error_invalid("reading reuc entry stage"); - } - - lost->mode[i] = (uint32_t)tmp; - - len = (endptr + 1) - buffer; - if (size <= len) { - index_entry_reuc_free(lost); - return index_error_invalid("reading reuc entry stage"); - } - - size -= len; - buffer += len; - } - - /* read up to 3 OIDs for stage entries */ - for (i = 0; i < 3; i++) { - if (!lost->mode[i]) - continue; - if (size < 20) { - index_entry_reuc_free(lost); - return index_error_invalid("reading reuc entry oid"); - } - - git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); - size -= 20; - buffer += 20; - } - - /* entry was read successfully - insert into reuc vector */ - if (git_vector_insert(&index->reuc, lost) < 0) - return -1; - } - - /* entries are guaranteed to be sorted on-disk */ - git_vector_set_sorted(&index->reuc, true); - - return 0; -} - - -static int read_conflict_names(git_index *index, const char *buffer, size_t size) -{ - size_t len; - - /* This gets called multiple times, the vector might already be initialized */ - if (index->names._alloc_size == 0 && - git_vector_init(&index->names, 16, conflict_name_cmp) < 0) - return -1; - -#define read_conflict_name(ptr) \ - len = p_strnlen(buffer, size) + 1; \ - if (size < len) { \ - index_error_invalid("reading conflict name entries"); \ - goto out_err; \ - } \ - if (len == 1) \ - ptr = NULL; \ - else { \ - ptr = git__malloc(len); \ - GIT_ERROR_CHECK_ALLOC(ptr); \ - memcpy(ptr, buffer, len); \ - } \ - \ - buffer += len; \ - size -= len; - - while (size) { - git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry)); - GIT_ERROR_CHECK_ALLOC(conflict_name); - - read_conflict_name(conflict_name->ancestor); - read_conflict_name(conflict_name->ours); - read_conflict_name(conflict_name->theirs); - - if (git_vector_insert(&index->names, conflict_name) < 0) - goto out_err; - - continue; - -out_err: - git__free(conflict_name->ancestor); - git__free(conflict_name->ours); - git__free(conflict_name->theirs); - git__free(conflict_name); - return -1; - } - -#undef read_conflict_name - - /* entries are guaranteed to be sorted on-disk */ - git_vector_set_sorted(&index->names, true); - - return 0; -} - -static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flags) -{ - if (varint_len) { - if (flags & GIT_INDEX_ENTRY_EXTENDED) - return offsetof(struct entry_long, path) + path_len + 1 + varint_len; - else - return offsetof(struct entry_short, path) + path_len + 1 + varint_len; - } else { -#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) - if (flags & GIT_INDEX_ENTRY_EXTENDED) - return entry_size(struct entry_long, path_len); - else - return entry_size(struct entry_short, path_len); -#undef entry_size - } -} - -static int read_entry( - git_index_entry **out, - size_t *out_size, - git_index *index, - const void *buffer, - size_t buffer_size, - const char *last) -{ - size_t path_length, entry_size; - const char *path_ptr; - struct entry_short source; - git_index_entry entry = {{0}}; - bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP; - char *tmp_path = NULL; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - if (checksum_size + minimal_entry_size > buffer_size) - return -1; - - /* buffer is not guaranteed to be aligned */ - memcpy(&source, buffer, sizeof(struct entry_short)); - - entry.ctime.seconds = (git_time_t)ntohl(source.ctime.seconds); - entry.ctime.nanoseconds = ntohl(source.ctime.nanoseconds); - entry.mtime.seconds = (git_time_t)ntohl(source.mtime.seconds); - entry.mtime.nanoseconds = ntohl(source.mtime.nanoseconds); - entry.dev = ntohl(source.dev); - entry.ino = ntohl(source.ino); - entry.mode = ntohl(source.mode); - entry.uid = ntohl(source.uid); - entry.gid = ntohl(source.gid); - entry.file_size = ntohl(source.file_size); - git_oid_cpy(&entry.id, &source.oid); - entry.flags = ntohs(source.flags); - - if (entry.flags & GIT_INDEX_ENTRY_EXTENDED) { - uint16_t flags_raw; - size_t flags_offset; - - flags_offset = offsetof(struct entry_long, flags_extended); - memcpy(&flags_raw, (const char *) buffer + flags_offset, - sizeof(flags_raw)); - flags_raw = ntohs(flags_raw); - - memcpy(&entry.flags_extended, &flags_raw, sizeof(flags_raw)); - path_ptr = (const char *) buffer + offsetof(struct entry_long, path); - } else - path_ptr = (const char *) buffer + offsetof(struct entry_short, path); - - if (!compressed) { - path_length = entry.flags & GIT_INDEX_ENTRY_NAMEMASK; - - /* if this is a very long string, we must find its - * real length without overflowing */ - if (path_length == 0xFFF) { - const char *path_end; - - path_end = memchr(path_ptr, '\0', buffer_size); - if (path_end == NULL) - return -1; - - path_length = path_end - path_ptr; - } - - entry_size = index_entry_size(path_length, 0, entry.flags); - entry.path = (char *)path_ptr; - } else { - size_t varint_len, last_len, prefix_len, suffix_len, path_len; - uintmax_t strip_len; - - strip_len = git_decode_varint((const unsigned char *)path_ptr, &varint_len); - last_len = strlen(last); - - if (varint_len == 0 || last_len < strip_len) - return index_error_invalid("incorrect prefix length"); - - prefix_len = last_len - (size_t)strip_len; - suffix_len = strlen(path_ptr + varint_len); - - GIT_ERROR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len); - GIT_ERROR_CHECK_ALLOC_ADD(&path_len, path_len, 1); - - if (path_len > GIT_PATH_MAX) - return index_error_invalid("unreasonable path length"); - - tmp_path = git__malloc(path_len); - GIT_ERROR_CHECK_ALLOC(tmp_path); - - memcpy(tmp_path, last, prefix_len); - memcpy(tmp_path + prefix_len, path_ptr + varint_len, suffix_len + 1); - entry_size = index_entry_size(suffix_len, varint_len, entry.flags); - entry.path = tmp_path; - } - - if (entry_size == 0) - return -1; - - if (checksum_size + entry_size > buffer_size) - return -1; - - if (index_entry_dup(out, index, &entry) < 0) { - git__free(tmp_path); - return -1; - } - - git__free(tmp_path); - *out_size = entry_size; - return 0; -} - -static int read_header(struct index_header *dest, const void *buffer) -{ - const struct index_header *source = buffer; - - dest->signature = ntohl(source->signature); - if (dest->signature != INDEX_HEADER_SIG) - return index_error_invalid("incorrect header signature"); - - dest->version = ntohl(source->version); - if (dest->version < INDEX_VERSION_NUMBER_LB || - dest->version > INDEX_VERSION_NUMBER_UB) - return index_error_invalid("incorrect header version"); - - dest->entry_count = ntohl(source->entry_count); - return 0; -} - -static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size) -{ - struct index_extension dest; - size_t total_size; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - /* buffer is not guaranteed to be aligned */ - memcpy(&dest, buffer, sizeof(struct index_extension)); - dest.extension_size = ntohl(dest.extension_size); - - total_size = dest.extension_size + sizeof(struct index_extension); - - if (dest.extension_size > total_size || - buffer_size < total_size || - buffer_size - total_size < checksum_size) { - index_error_invalid("extension is truncated"); - return -1; - } - - /* optional extension */ - if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { - /* tree cache */ - if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { - if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size, &index->tree_pool) < 0) - return -1; - } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { - if (read_reuc(index, buffer + 8, dest.extension_size) < 0) - return -1; - } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) { - if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0) - return -1; - } - /* else, unsupported extension. We cannot parse this, but we can skip - * it by returning `total_size */ - } else { - /* we cannot handle non-ignorable extensions; - * in fact they aren't even defined in the standard */ - git_error_set(GIT_ERROR_INDEX, "unsupported mandatory extension: '%.4s'", dest.signature); - return -1; - } - - *read_len = total_size; - - return 0; -} - -static int parse_index(git_index *index, const char *buffer, size_t buffer_size) -{ - int error = 0; - unsigned int i; - struct index_header header = { 0 }; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - const char *last = NULL; - const char *empty = ""; - -#define seek_forward(_increase) { \ - if (_increase >= buffer_size) { \ - error = index_error_invalid("ran out of data while parsing"); \ - goto done; } \ - buffer += _increase; \ - buffer_size -= _increase;\ -} - - if (buffer_size < INDEX_HEADER_SIZE + checksum_size) - return index_error_invalid("insufficient buffer space"); - - /* Precalculate the SHA1 of the files's contents -- we'll match it to - * the provided SHA1 in the footer */ - git_hash_buf(checksum, buffer, buffer_size - checksum_size, GIT_HASH_ALGORITHM_SHA1); - - /* Parse header */ - if ((error = read_header(&header, buffer)) < 0) - return error; - - index->version = header.version; - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = empty; - - seek_forward(INDEX_HEADER_SIZE); - - GIT_ASSERT(!index->entries.length); - - 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 > checksum_size; ++i) { - git_index_entry *entry = NULL; - size_t entry_size; - - if ((error = read_entry(&entry, &entry_size, index, buffer, buffer_size, last)) < 0) { - error = index_error_invalid("invalid entry"); - goto done; - } - - if ((error = git_vector_insert(&index->entries, entry)) < 0) { - index_entry_free(entry); - goto done; - } - - if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) { - index_entry_free(entry); - goto done; - } - error = 0; - - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = entry->path; - - seek_forward(entry_size); - } - - if (i != header.entry_count) { - error = index_error_invalid("header entries changed while parsing"); - goto done; - } - - /* There's still space for some extensions! */ - while (buffer_size > checksum_size) { - size_t extension_size; - - if ((error = read_extension(&extension_size, index, buffer, buffer_size)) < 0) { - goto done; - } - - seek_forward(extension_size); - } - - if (buffer_size != checksum_size) { - error = index_error_invalid( - "buffer size does not match index footer size"); - goto done; - } - - /* 160-bit SHA-1 over the content of the index file before this checksum. */ - if (memcmp(checksum, buffer, checksum_size) != 0) { - error = index_error_invalid( - "calculated checksum does not match expected"); - goto done; - } - - memcpy(index->checksum, checksum, checksum_size); - -#undef seek_forward - - /* Entries are stored case-sensitively on disk, so re-sort now if - * in-memory index is supposed to be case-insensitive - */ - git_vector_set_sorted(&index->entries, !index->ignore_case); - git_vector_sort(&index->entries); - - index->dirty = 0; -done: - return error; -} - -static bool is_index_extended(git_index *index) -{ - size_t i, extended; - git_index_entry *entry; - - extended = 0; - - git_vector_foreach(&index->entries, i, entry) { - entry->flags &= ~GIT_INDEX_ENTRY_EXTENDED; - if (entry->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS) { - extended++; - entry->flags |= GIT_INDEX_ENTRY_EXTENDED; - } - } - - return (extended > 0); -} - -static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char *last) -{ - void *mem = NULL; - struct entry_short ondisk; - size_t path_len, disk_size; - int varint_len = 0; - char *path; - const char *path_start = entry->path; - size_t same_len = 0; - - path_len = ((struct entry_internal *)entry)->pathlen; - - if (last) { - const char *last_c = last; - - while (*path_start == *last_c) { - if (!*path_start || !*last_c) - break; - ++path_start; - ++last_c; - ++same_len; - } - path_len -= same_len; - varint_len = git_encode_varint(NULL, 0, strlen(last) - same_len); - } - - disk_size = index_entry_size(path_len, varint_len, entry->flags); - - if (git_filebuf_reserve(file, &mem, disk_size) < 0) - return -1; - - memset(mem, 0x0, disk_size); - - /** - * Yes, we have to truncate. - * - * The on-disk format for Index entries clearly defines - * the time and size fields to be 4 bytes each -- so even if - * we store these values with 8 bytes on-memory, they must - * be truncated to 4 bytes before writing to disk. - * - * In 2038 I will be either too dead or too rich to care about this - */ - ondisk.ctime.seconds = htonl((uint32_t)entry->ctime.seconds); - ondisk.mtime.seconds = htonl((uint32_t)entry->mtime.seconds); - ondisk.ctime.nanoseconds = htonl(entry->ctime.nanoseconds); - ondisk.mtime.nanoseconds = htonl(entry->mtime.nanoseconds); - ondisk.dev = htonl(entry->dev); - ondisk.ino = htonl(entry->ino); - ondisk.mode = htonl(entry->mode); - ondisk.uid = htonl(entry->uid); - ondisk.gid = htonl(entry->gid); - ondisk.file_size = htonl((uint32_t)entry->file_size); - - git_oid_cpy(&ondisk.oid, &entry->id); - - ondisk.flags = htons(entry->flags); - - if (entry->flags & GIT_INDEX_ENTRY_EXTENDED) { - const size_t path_offset = offsetof(struct entry_long, path); - struct entry_long ondisk_ext; - memcpy(&ondisk_ext, &ondisk, sizeof(struct entry_short)); - ondisk_ext.flags_extended = htons(entry->flags_extended & - GIT_INDEX_ENTRY_EXTENDED_FLAGS); - memcpy(mem, &ondisk_ext, path_offset); - path = (char *)mem + path_offset; - disk_size -= path_offset; - } else { - const size_t path_offset = offsetof(struct entry_short, path); - memcpy(mem, &ondisk, path_offset); - path = (char *)mem + path_offset; - disk_size -= path_offset; - } - - if (last) { - varint_len = git_encode_varint((unsigned char *) path, - disk_size, strlen(last) - same_len); - GIT_ASSERT(varint_len > 0); - - path += varint_len; - disk_size -= varint_len; - - /* - * If using path compression, we are not allowed - * to have additional trailing NULs. - */ - GIT_ASSERT(disk_size == path_len + 1); - } else { - /* - * If no path compression is used, we do have - * NULs as padding. As such, simply assert that - * we have enough space left to write the path. - */ - GIT_ASSERT(disk_size > path_len); - } - - memcpy(path, path_start, path_len + 1); - - return 0; -} - -static int write_entries(git_index *index, git_filebuf *file) -{ - int error = 0; - size_t i; - git_vector case_sorted = GIT_VECTOR_INIT, *entries = NULL; - git_index_entry *entry; - const char *last = NULL; - - /* If index->entries is sorted case-insensitively, then we need - * to re-sort it case-sensitively before writing */ - if (index->ignore_case) { - if ((error = git_vector_dup(&case_sorted, &index->entries, git_index_entry_cmp)) < 0) - goto done; - - git_vector_sort(&case_sorted); - entries = &case_sorted; - } else { - entries = &index->entries; - } - - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = ""; - - git_vector_foreach(entries, i, entry) { - if ((error = write_disk_entry(file, entry, last)) < 0) - break; - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = entry->path; - } - -done: - git_vector_free(&case_sorted); - return error; -} - -static int write_extension(git_filebuf *file, struct index_extension *header, git_str *data) -{ - struct index_extension ondisk; - - memset(&ondisk, 0x0, sizeof(struct index_extension)); - memcpy(&ondisk, header, 4); - ondisk.extension_size = htonl(header->extension_size); - - git_filebuf_write(file, &ondisk, sizeof(struct index_extension)); - return git_filebuf_write(file, data->ptr, data->size); -} - -static int create_name_extension_data(git_str *name_buf, git_index_name_entry *conflict_name) -{ - int error = 0; - - if (conflict_name->ancestor == NULL) - error = git_str_put(name_buf, "\0", 1); - else - error = git_str_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1); - - if (error != 0) - goto on_error; - - if (conflict_name->ours == NULL) - error = git_str_put(name_buf, "\0", 1); - else - error = git_str_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1); - - if (error != 0) - goto on_error; - - if (conflict_name->theirs == NULL) - error = git_str_put(name_buf, "\0", 1); - else - error = git_str_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1); - -on_error: - return error; -} - -static int write_name_extension(git_index *index, git_filebuf *file) -{ - git_str name_buf = GIT_STR_INIT; - git_vector *out = &index->names; - git_index_name_entry *conflict_name; - struct index_extension extension; - size_t i; - int error = 0; - - git_vector_foreach(out, i, conflict_name) { - if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0) - goto done; - } - - memset(&extension, 0x0, sizeof(struct index_extension)); - memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4); - extension.extension_size = (uint32_t)name_buf.size; - - error = write_extension(file, &extension, &name_buf); - - git_str_dispose(&name_buf); - -done: - return error; -} - -static int create_reuc_extension_data(git_str *reuc_buf, git_index_reuc_entry *reuc) -{ - int i; - int error = 0; - - if ((error = git_str_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0) - return error; - - for (i = 0; i < 3; i++) { - if ((error = git_str_printf(reuc_buf, "%o", reuc->mode[i])) < 0 || - (error = git_str_put(reuc_buf, "\0", 1)) < 0) - return error; - } - - for (i = 0; i < 3; i++) { - if (reuc->mode[i] && (error = git_str_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0) - return error; - } - - return 0; -} - -static int write_reuc_extension(git_index *index, git_filebuf *file) -{ - git_str reuc_buf = GIT_STR_INIT; - git_vector *out = &index->reuc; - git_index_reuc_entry *reuc; - struct index_extension extension; - size_t i; - int error = 0; - - git_vector_foreach(out, i, reuc) { - if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0) - goto done; - } - - memset(&extension, 0x0, sizeof(struct index_extension)); - memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4); - extension.extension_size = (uint32_t)reuc_buf.size; - - error = write_extension(file, &extension, &reuc_buf); - - git_str_dispose(&reuc_buf); - -done: - return error; -} - -static int write_tree_extension(git_index *index, git_filebuf *file) -{ - struct index_extension extension; - git_str buf = GIT_STR_INIT; - int error; - - if (index->tree == NULL) - return 0; - - if ((error = git_tree_cache_write(&buf, index->tree)) < 0) - return error; - - memset(&extension, 0x0, sizeof(struct index_extension)); - memcpy(&extension.signature, INDEX_EXT_TREECACHE_SIG, 4); - extension.extension_size = (uint32_t)buf.size; - - error = write_extension(file, &extension, &buf); - - git_str_dispose(&buf); - - return error; -} - -static void clear_uptodate(git_index *index) -{ - git_index_entry *entry; - size_t i; - - git_vector_foreach(&index->entries, i, entry) - entry->flags_extended &= ~GIT_INDEX_ENTRY_UPTODATE; -} - -static int write_index( - unsigned char checksum[GIT_HASH_SHA1_SIZE], - size_t *checksum_size, - git_index *index, - git_filebuf *file) -{ - struct index_header header; - bool is_extended; - uint32_t index_version_number; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(file); - - *checksum_size = GIT_HASH_SHA1_SIZE; - - if (index->version <= INDEX_VERSION_NUMBER_EXT) { - is_extended = is_index_extended(index); - index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB; - } else { - index_version_number = index->version; - } - - header.signature = htonl(INDEX_HEADER_SIG); - header.version = htonl(index_version_number); - header.entry_count = htonl((uint32_t)index->entries.length); - - if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) - return -1; - - if (write_entries(index, file) < 0) - return -1; - - /* write the tree cache extension */ - if (index->tree != NULL && write_tree_extension(index, file) < 0) - return -1; - - /* write the rename conflict extension */ - if (index->names.length > 0 && write_name_extension(index, file) < 0) - return -1; - - /* write the reuc extension */ - if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0) - return -1; - - /* get out the hash for all the contents we've appended to the file */ - git_filebuf_hash(checksum, file); - - /* write it at the end of the file */ - if (git_filebuf_write(file, checksum, *checksum_size) < 0) - return -1; - - /* file entries are no longer up to date */ - clear_uptodate(index); - - return 0; -} - -int git_index_entry_stage(const git_index_entry *entry) -{ - return GIT_INDEX_ENTRY_STAGE(entry); -} - -int git_index_entry_is_conflict(const git_index_entry *entry) -{ - return (GIT_INDEX_ENTRY_STAGE(entry) > 0); -} - -typedef struct read_tree_data { - git_index *index; - git_vector *old_entries; - git_vector *new_entries; - git_vector_cmp entry_cmp; - git_tree_cache *tree; -} read_tree_data; - -static int read_tree_cb( - const char *root, const git_tree_entry *tentry, void *payload) -{ - read_tree_data *data = payload; - git_index_entry *entry = NULL, *old_entry; - git_str path = GIT_STR_INIT; - size_t pos; - - if (git_tree_entry__is_tree(tentry)) - return 0; - - if (git_str_joinpath(&path, root, tentry->filename) < 0) - return -1; - - if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, NULL, false) < 0) - return -1; - - entry->mode = tentry->attr; - git_oid_cpy(&entry->id, git_tree_entry_id(tentry)); - - /* look for corresponding old entry and copy data to new entry */ - if (data->old_entries != NULL && - !index_find_in_entries( - &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) && - (old_entry = git_vector_get(data->old_entries, pos)) != NULL && - entry->mode == old_entry->mode && - git_oid_equal(&entry->id, &old_entry->id)) - { - index_entry_cpy(entry, old_entry); - entry->flags_extended = 0; - } - - index_entry_adjust_namemask(entry, path.size); - git_str_dispose(&path); - - if (git_vector_insert(data->new_entries, entry) < 0) { - index_entry_free(entry); - return -1; - } - - return 0; -} - -int git_index_read_tree(git_index *index, const git_tree *tree) -{ - int error = 0; - git_vector entries = GIT_VECTOR_INIT; - git_idxmap *entries_map; - read_tree_data data; - size_t i; - git_index_entry *e; - - if (git_idxmap_new(&entries_map) < 0) - return -1; - - git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ - - data.index = index; - data.old_entries = &index->entries; - data.new_entries = &entries; - data.entry_cmp = index->entries_search; - - index->tree = NULL; - git_pool_clear(&index->tree_pool); - - git_vector_sort(&index->entries); - - if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) - goto cleanup; - - if ((error = index_map_resize(entries_map, entries.length, index->ignore_case)) < 0) - goto cleanup; - - git_vector_foreach(&entries, i, e) { - 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; - } - } - - error = 0; - - git_vector_sort(&entries); - - if ((error = git_index_clear(index)) < 0) { - /* well, this isn't good */; - } else { - git_vector_swap(&entries, &index->entries); - entries_map = git_atomic_swap(index->entries_map, entries_map); - } - - index->dirty = 1; - -cleanup: - git_vector_free(&entries); - git_idxmap_free(entries_map); - if (error < 0) - return error; - - error = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); - - return error; -} - -static int git_index_read_iterator( - git_index *index, - git_iterator *new_iterator, - size_t new_length_hint) -{ - git_vector new_entries = GIT_VECTOR_INIT, - remove_entries = GIT_VECTOR_INIT; - git_idxmap *new_entries_map = NULL; - git_iterator *index_iterator = NULL; - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *old_entry, *new_entry; - git_index_entry *entry; - size_t i; - int error; - - GIT_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_new(&new_entries_map)) < 0) - goto done; - - 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; - - if ((error = git_iterator_for_index(&index_iterator, - git_index_owner(index), index, &opts)) < 0 || - ((error = git_iterator_current(&old_entry, index_iterator)) < 0 && - error != GIT_ITEROVER) || - ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && - error != GIT_ITEROVER)) - goto done; - - while (true) { - git_index_entry - *dup_entry = NULL, - *add_entry = NULL, - *remove_entry = NULL; - int diff; - - error = 0; - - if (old_entry && new_entry) - diff = git_index_entry_cmp(old_entry, new_entry); - else if (!old_entry && new_entry) - diff = 1; - else if (old_entry && !new_entry) - diff = -1; - else - break; - - if (diff < 0) { - remove_entry = (git_index_entry *)old_entry; - } else if (diff > 0) { - dup_entry = (git_index_entry *)new_entry; - } else { - /* Path and stage are equal, if the OID is equal, keep it to - * keep the stat cache data. - */ - if (git_oid_equal(&old_entry->id, &new_entry->id) && - old_entry->mode == new_entry->mode) { - add_entry = (git_index_entry *)old_entry; - } else { - dup_entry = (git_index_entry *)new_entry; - remove_entry = (git_index_entry *)old_entry; - } - } - - if (dup_entry) { - if ((error = index_entry_dup_nocache(&add_entry, index, dup_entry)) < 0) - goto done; - - index_entry_adjust_namemask(add_entry, - ((struct entry_internal *)add_entry)->pathlen); - } - - /* invalidate this path in the tree cache if this is new (to - * invalidate the parent trees) - */ - if (dup_entry && !remove_entry && index->tree) - git_tree_cache_invalidate_path(index->tree, dup_entry->path); - - if (add_entry) { - if ((error = git_vector_insert(&new_entries, add_entry)) == 0) - error = index_map_set(new_entries_map, add_entry, - index->ignore_case); - } - - if (remove_entry && error >= 0) - error = git_vector_insert(&remove_entries, remove_entry); - - if (error < 0) { - git_error_set(GIT_ERROR_INDEX, "failed to insert entry"); - goto done; - } - - if (diff <= 0) { - if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 && - error != GIT_ITEROVER) - goto done; - } - - if (diff >= 0) { - if ((error = git_iterator_advance(&new_entry, new_iterator)) < 0 && - error != GIT_ITEROVER) - goto done; - } - } - - 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_atomic_swap(index->entries_map, new_entries_map); - - git_vector_foreach(&remove_entries, i, entry) { - if (index->tree) - git_tree_cache_invalidate_path(index->tree, entry->path); - - index_entry_free(entry); - } - - clear_uptodate(index); - - index->dirty = 1; - error = 0; - -done: - git_idxmap_free(new_entries_map); - git_vector_free(&new_entries); - git_vector_free(&remove_entries); - git_iterator_free(index_iterator); - return error; -} - -int git_index_read_index( - git_index *index, - const git_index *new_index) -{ - git_iterator *new_iterator = NULL; - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_CONFLICTS; - - if ((error = git_iterator_for_index(&new_iterator, - git_index_owner(new_index), (git_index *)new_index, &opts)) < 0 || - (error = git_index_read_iterator(index, new_iterator, - new_index->entries.length)) < 0) - goto done; - -done: - git_iterator_free(new_iterator); - return error; -} - -git_repository *git_index_owner(const git_index *index) -{ - return INDEX_OWNER(index); -} - -enum { - INDEX_ACTION_NONE = 0, - INDEX_ACTION_UPDATE = 1, - INDEX_ACTION_REMOVE = 2, - INDEX_ACTION_ADDALL = 3 -}; - -int git_index_add_all( - git_index *index, - const git_strarray *paths, - unsigned int flags, - git_index_matched_path_cb cb, - void *payload) -{ - int error; - git_repository *repo; - git_iterator *wditer = NULL; - git_pathspec ps; - bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; - - GIT_ASSERT_ARG(index); - - repo = INDEX_OWNER(index); - if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0) - return error; - - if ((error = git_pathspec__init(&ps, paths)) < 0) - return error; - - /* optionally check that pathspec doesn't mention any ignored files */ - if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 && - (flags & GIT_INDEX_ADD_FORCE) == 0 && - (error = git_ignore__check_pathspec_for_exact_ignores( - repo, &ps.pathspec, no_fnmatch)) < 0) - goto cleanup; - - error = index_apply_to_wd_diff(index, INDEX_ACTION_ADDALL, paths, flags, cb, payload); - - if (error) - git_error_set_after_callback(error); - -cleanup: - git_iterator_free(wditer); - git_pathspec__clear(&ps); - - return error; -} - -struct foreach_diff_data { - git_index *index; - const git_pathspec *pathspec; - unsigned int flags; - git_index_matched_path_cb cb; - void *payload; -}; - -static int apply_each_file(const git_diff_delta *delta, float progress, void *payload) -{ - struct foreach_diff_data *data = payload; - const char *match, *path; - int error = 0; - - GIT_UNUSED(progress); - - path = delta->old_file.path; - - /* We only want those which match the pathspecs */ - if (!git_pathspec__match( - &data->pathspec->pathspec, path, false, (bool)data->index->ignore_case, - &match, NULL)) - return 0; - - if (data->cb) - error = data->cb(path, match, data->payload); - - if (error > 0) /* skip this entry */ - return 0; - if (error < 0) /* actual error */ - return error; - - /* If the workdir item does not exist, remove it from the index. */ - if ((delta->new_file.flags & GIT_DIFF_FLAG_EXISTS) == 0) - error = git_index_remove_bypath(data->index, path); - else - error = git_index_add_bypath(data->index, delta->new_file.path); - - return error; -} - -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) -{ - int error; - git_diff *diff; - git_pathspec ps; - git_repository *repo; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct foreach_diff_data data = { - index, - NULL, - flags, - cb, - payload, - }; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(action == INDEX_ACTION_UPDATE || action == INDEX_ACTION_ADDALL); - - repo = INDEX_OWNER(index); - - if (!repo) { - return create_index_error(-1, - "cannot run update; the index is not backed up by a repository."); - } - - /* - * We do the matching ourselves instead of passing the list to - * diff because we want to tell the callback which one - * matched, which we do not know if we ask diff to filter for us. - */ - if ((error = git_pathspec__init(&ps, paths)) < 0) - return error; - - opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - if (action == INDEX_ACTION_ADDALL) { - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS; - - if (flags == GIT_INDEX_ADD_FORCE) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; - } - - if ((error = git_diff_index_to_workdir(&diff, repo, index, &opts)) < 0) - goto cleanup; - - data.pathspec = &ps; - error = git_diff_foreach(diff, apply_each_file, NULL, NULL, NULL, &data); - git_diff_free(diff); - - if (error) /* make sure error is set if callback stopped iteration */ - git_error_set_after_callback(error); - -cleanup: - git_pathspec__clear(&ps); - return error; -} - -static int index_apply_to_all( - git_index *index, - int action, - const git_strarray *paths, - git_index_matched_path_cb cb, - void *payload) -{ - int error = 0; - size_t i; - git_pathspec ps; - const char *match; - git_str path = GIT_STR_INIT; - - GIT_ASSERT_ARG(index); - - if ((error = git_pathspec__init(&ps, paths)) < 0) - return error; - - git_vector_sort(&index->entries); - - for (i = 0; !error && i < index->entries.length; ++i) { - git_index_entry *entry = git_vector_get(&index->entries, i); - - /* check if path actually matches */ - if (!git_pathspec__match( - &ps.pathspec, entry->path, false, (bool)index->ignore_case, - &match, NULL)) - continue; - - /* issue notification callback if requested */ - if (cb && (error = cb(entry->path, match, payload)) != 0) { - if (error > 0) { /* return > 0 means skip this one */ - error = 0; - continue; - } - if (error < 0) /* return < 0 means abort */ - break; - } - - /* index manipulation may alter entry, so don't depend on it */ - if ((error = git_str_sets(&path, entry->path)) < 0) - break; - - switch (action) { - case INDEX_ACTION_NONE: - break; - case INDEX_ACTION_UPDATE: - error = git_index_add_bypath(index, path.ptr); - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - - error = git_index_remove_bypath(index, path.ptr); - - if (!error) /* back up foreach if we removed this */ - i--; - } - break; - case INDEX_ACTION_REMOVE: - if (!(error = git_index_remove_bypath(index, path.ptr))) - i--; /* back up foreach if we removed this */ - break; - default: - git_error_set(GIT_ERROR_INVALID, "unknown index action %d", action); - error = -1; - break; - } - } - - git_str_dispose(&path); - git_pathspec__clear(&ps); - - return error; -} - -int git_index_remove_all( - git_index *index, - const git_strarray *pathspec, - git_index_matched_path_cb cb, - void *payload) -{ - int error = index_apply_to_all( - index, INDEX_ACTION_REMOVE, pathspec, cb, payload); - - if (error) /* make sure error is set if callback stopped iteration */ - git_error_set_after_callback(error); - - return error; -} - -int git_index_update_all( - git_index *index, - const git_strarray *pathspec, - git_index_matched_path_cb cb, - void *payload) -{ - int error = index_apply_to_wd_diff(index, INDEX_ACTION_UPDATE, pathspec, 0, cb, payload); - if (error) /* make sure error is set if callback stopped iteration */ - git_error_set_after_callback(error); - - return error; -} - -int git_index_snapshot_new(git_vector *snap, git_index *index) -{ - int error; - - GIT_REFCOUNT_INC(index); - - git_atomic32_inc(&index->readers); - git_vector_sort(&index->entries); - - error = git_vector_dup(snap, &index->entries, index->entries._cmp); - - if (error < 0) - git_index_snapshot_release(snap, index); - - return error; -} - -void git_index_snapshot_release(git_vector *snap, git_index *index) -{ - git_vector_free(snap); - - git_atomic32_dec(&index->readers); - - git_index_free(index); -} - -int git_index_snapshot_find( - size_t *out, git_vector *entries, git_vector_cmp entry_srch, - const char *path, size_t path_len, int stage) -{ - return index_find_in_entries(out, entries, entry_srch, path, path_len, stage); -} - -int git_indexwriter_init( - git_indexwriter *writer, - git_index *index) -{ - int error; - - GIT_REFCOUNT_INC(index); - - writer->index = index; - - if (!index->index_file_path) - return create_index_error(-1, - "failed to write index: The index is in-memory only"); - - if ((error = git_filebuf_open( - &writer->file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) { - - if (error == GIT_ELOCKED) - git_error_set(GIT_ERROR_INDEX, "the index is locked; this might be due to a concurrent or crashed process"); - - return error; - } - - writer->should_write = 1; - - return 0; -} - -int git_indexwriter_init_for_operation( - git_indexwriter *writer, - git_repository *repo, - unsigned int *checkout_strategy) -{ - git_index *index; - int error; - - if ((error = git_repository_index__weakptr(&index, repo)) < 0 || - (error = git_indexwriter_init(writer, index)) < 0) - return error; - - writer->should_write = (*checkout_strategy & GIT_CHECKOUT_DONT_WRITE_INDEX) == 0; - *checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; - - return 0; -} - -int git_indexwriter_commit(git_indexwriter *writer) -{ - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - int error; - - if (!writer->should_write) - return 0; - - git_vector_sort(&writer->index->entries); - git_vector_sort(&writer->index->reuc); - - if ((error = write_index(checksum, &checksum_size, writer->index, &writer->file)) < 0) { - git_indexwriter_cleanup(writer); - return error; - } - - if ((error = git_filebuf_commit(&writer->file)) < 0) - return error; - - if ((error = git_futils_filestamp_check( - &writer->index->stamp, writer->index->index_file_path)) < 0) { - git_error_set(GIT_ERROR_OS, "could not read index timestamp"); - return -1; - } - - writer->index->dirty = 0; - writer->index->on_disk = 1; - memcpy(writer->index->checksum, checksum, checksum_size); - - git_index_free(writer->index); - writer->index = NULL; - - return 0; -} - -void git_indexwriter_cleanup(git_indexwriter *writer) -{ - git_filebuf_cleanup(&writer->file); - - git_index_free(writer->index); - writer->index = NULL; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD -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); -} -#endif diff --git a/src/index.h b/src/index.h deleted file mode 100644 index 71bb096f7..000000000 --- a/src/index.h +++ /dev/null @@ -1,194 +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_index_h__ -#define INCLUDE_index_h__ - -#include "common.h" - -#include "futils.h" -#include "filebuf.h" -#include "vector.h" -#include "idxmap.h" -#include "tree-cache.h" -#include "git2/odb.h" -#include "git2/index.h" - -#define GIT_INDEX_FILE "index" -#define GIT_INDEX_FILE_MODE 0666 - -extern bool git_index__enforce_unsaved_safety; - -struct git_index { - git_refcount rc; - - char *index_file_path; - git_futils_filestamp stamp; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - - git_vector entries; - git_idxmap *entries_map; - - git_vector deleted; /* deleted entries if readers > 0 */ - git_atomic32 readers; /* number of active iterators */ - - unsigned int on_disk:1; - unsigned int ignore_case:1; - unsigned int distrust_filemode:1; - unsigned int no_symlinks:1; - unsigned int dirty:1; /* whether we have unsaved changes */ - - git_tree_cache *tree; - git_pool tree_pool; - - git_vector names; - git_vector reuc; - - git_vector_cmp entries_cmp_path; - git_vector_cmp entries_search; - git_vector_cmp entries_search_path; - git_vector_cmp reuc_search; - - unsigned int version; -}; - -struct git_index_iterator { - git_index *index; - git_vector snap; - size_t cur; -}; - -struct git_index_conflict_iterator { - git_index *index; - size_t cur; -}; - -extern void git_index_entry__init_from_stat( - git_index_entry *entry, struct stat *st, bool trust_mode); - -/* Index entry comparison functions for array sorting */ -extern int git_index_entry_cmp(const void *a, const void *b); -extern int git_index_entry_icmp(const void *a, const void *b); - -/* Index entry search functions for search using a search spec */ -extern int git_index_entry_srch(const void *a, const void *b); -extern int git_index_entry_isrch(const void *a, const void *b); - -/* Index time handling functions */ -GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_time *two) -{ - if (one->seconds != two->seconds) - return false; - -#ifdef GIT_USE_NSEC - if (one->nanoseconds != two->nanoseconds) - return false; -#endif - - return true; -} - -/* - * Test if the given index time is newer than the given existing index entry. - * If the timestamps are exactly equivalent, then the given index time is - * considered "racily newer" than the existing index entry. - */ -GIT_INLINE(bool) git_index_entry_newer_than_index( - const git_index_entry *entry, git_index *index) -{ - /* If we never read the index, we can't have this race either */ - if (!index || index->stamp.mtime.tv_sec == 0) - return false; - - /* If the timestamp is the same or newer than the index, it's racy */ -#if defined(GIT_USE_NSEC) - if ((int32_t)index->stamp.mtime.tv_sec < entry->mtime.seconds) - return true; - else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds) - return false; - else - return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds; -#else - return ((int32_t)index->stamp.mtime.tv_sec) <= entry->mtime.seconds; -#endif -} - -/* Search index for `path`, returning GIT_ENOTFOUND if it does not exist - * (but not setting an error message). - * - * `at_pos` is set to the position where it is or would be inserted. - * Pass `path_len` as strlen of path or 0 to call strlen internally. - */ -extern int git_index__find_pos( - size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage); - -extern int git_index__fill(git_index *index, const git_vector *source_entries); - -extern void git_index__set_ignore_case(git_index *index, bool ignore_case); - -extern unsigned int git_index__create_mode(unsigned int mode); - -GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index) -{ - return &index->stamp; -} - -GIT_INLINE(unsigned char *) git_index__checksum(git_index *index) -{ - return index->checksum; -} - -/* Copy the current entries vector *and* increment the index refcount. - * Call `git_index__release_snapshot` when done. - */ -extern int git_index_snapshot_new(git_vector *snap, git_index *index); -extern void git_index_snapshot_release(git_vector *snap, git_index *index); - -/* Allow searching in a snapshot; entries must already be sorted! */ -extern int git_index_snapshot_find( - size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch, - const char *path, size_t path_len, int stage); - -/* Replace an index with a new index */ -int git_index_read_index(git_index *index, const git_index *new_index); - -GIT_INLINE(int) git_index_is_dirty(git_index *index) -{ - return index->dirty; -} - -extern int git_index_read_safely(git_index *index); - -typedef struct { - git_index *index; - git_filebuf file; - unsigned int should_write:1; -} git_indexwriter; - -#define GIT_INDEXWRITER_INIT { NULL, GIT_FILEBUF_INIT } - -/* Lock the index for eventual writing. */ -extern int git_indexwriter_init(git_indexwriter *writer, git_index *index); - -/* Lock the index for eventual writing by a repository operation: a merge, - * revert, cherry-pick or a rebase. Note that the given checkout strategy - * will be updated for the operation's use so that checkout will not write - * the index. - */ -extern int git_indexwriter_init_for_operation( - git_indexwriter *writer, - git_repository *repo, - unsigned int *checkout_strategy); - -/* Write the index and unlock it. */ -extern int git_indexwriter_commit(git_indexwriter *writer); - -/* Cleanup an index writing session, unlocking the file (if it is still - * locked and freeing any data structures. - */ -extern void git_indexwriter_cleanup(git_indexwriter *writer); - -#endif diff --git a/src/indexer.c b/src/indexer.c deleted file mode 100644 index f9a32e7ac..000000000 --- a/src/indexer.c +++ /dev/null @@ -1,1413 +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 "indexer.h" - -#include "git2/indexer.h" -#include "git2/object.h" - -#include "commit.h" -#include "tree.h" -#include "tag.h" -#include "pack.h" -#include "mwindow.h" -#include "posix.h" -#include "pack.h" -#include "filebuf.h" -#include "oid.h" -#include "oidarray.h" -#include "oidmap.h" -#include "zstream.h" -#include "object.h" - -size_t git_indexer__max_objects = UINT32_MAX; - -#define UINT31_MAX (0x7FFFFFFF) - -struct entry { - git_oid oid; - uint32_t crc; - uint32_t offset; - uint64_t offset_long; -}; - -struct git_indexer { - unsigned int parsed_header :1, - pack_committed :1, - have_stream :1, - have_delta :1, - do_fsync :1, - do_verify :1; - struct git_pack_header hdr; - struct git_pack_file *pack; - unsigned int mode; - off64_t off; - off64_t entry_start; - git_object_t entry_type; - git_str entry_data; - git_packfile_stream stream; - size_t nr_objects; - git_vector objects; - git_vector deltas; - unsigned int fanout[256]; - git_hash_ctx hash_ctx; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - char name[(GIT_HASH_SHA1_SIZE * 2) + 1]; - git_indexer_progress_cb progress_cb; - void *progress_payload; - char objbuf[8*1024]; - - /* OIDs referenced from pack objects. Used for verification. */ - git_oidmap *expected_oids; - - /* Needed to look up objects which we want to inject to fix a thin pack */ - git_odb *odb; - - /* Fields for calculating the packfile trailer (hash of everything before it) */ - char inbuf[GIT_OID_RAWSZ]; - size_t inbuf_len; - git_hash_ctx trailer; -}; - -struct delta_info { - off64_t delta_off; -}; - -#ifndef GIT_DEPRECATE_HARD -const git_oid *git_indexer_hash(const git_indexer *idx) -{ - return (git_oid *)idx->checksum; -} -#endif - -const char *git_indexer_name(const git_indexer *idx) -{ - return idx->name; -} - -static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) -{ - int error; - git_map map; - - if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) - return error; - - memcpy(hdr, map.data, sizeof(*hdr)); - p_munmap(&map); - - /* Verify we recognize this pack file format. */ - if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { - git_error_set(GIT_ERROR_INDEXER, "wrong pack signature"); - return -1; - } - - if (!pack_version_ok(hdr->hdr_version)) { - git_error_set(GIT_ERROR_INDEXER, "wrong pack version"); - return -1; - } - - return 0; -} - -static int objects_cmp(const void *a, const void *b) -{ - const struct entry *entrya = a; - const struct entry *entryb = b; - - return git_oid__cmp(&entrya->oid, &entryb->oid); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_indexer_init_options(git_indexer_options *opts, unsigned int version) -{ - return git_indexer_options_init(opts, version); -} -#endif - -int git_indexer_new( - git_indexer **out, - const char *prefix, - unsigned int mode, - git_odb *odb, - git_indexer_options *in_opts) -{ - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - git_indexer *idx; - git_str path = GIT_STR_INIT, tmp_path = GIT_STR_INIT; - static const char suff[] = "/pack"; - int error, fd = -1; - - if (in_opts) - memcpy(&opts, in_opts, sizeof(opts)); - - idx = git__calloc(1, sizeof(git_indexer)); - GIT_ERROR_CHECK_ALLOC(idx); - idx->odb = odb; - idx->progress_cb = opts.progress_cb; - idx->progress_payload = opts.progress_cb_payload; - idx->mode = mode ? mode : GIT_PACK_FILE_MODE; - git_str_init(&idx->entry_data, 0); - - if ((error = git_hash_ctx_init(&idx->hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = git_hash_ctx_init(&idx->trailer, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = git_oidmap_new(&idx->expected_oids)) < 0) - goto cleanup; - - idx->do_verify = opts.verify; - - if (git_repository__fsync_gitdir) - idx->do_fsync = 1; - - error = git_str_joinpath(&path, prefix, suff); - if (error < 0) - goto cleanup; - - fd = git_futils_mktmp(&tmp_path, git_str_cstr(&path), idx->mode); - git_str_dispose(&path); - if (fd < 0) - goto cleanup; - - error = git_packfile_alloc(&idx->pack, git_str_cstr(&tmp_path)); - git_str_dispose(&tmp_path); - - if (error < 0) - goto cleanup; - - idx->pack->mwf.fd = fd; - if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) - goto cleanup; - - *out = idx; - return 0; - -cleanup: - if (fd != -1) - p_close(fd); - - if (git_str_len(&tmp_path) > 0) - p_unlink(git_str_cstr(&tmp_path)); - - if (idx->pack != NULL) - p_unlink(idx->pack->pack_name); - - git_str_dispose(&path); - git_str_dispose(&tmp_path); - git__free(idx); - return -1; -} - -void git_indexer__set_fsync(git_indexer *idx, int do_fsync) -{ - idx->do_fsync = !!do_fsync; -} - -/* Try to store the delta so we can try to resolve it later */ -static int store_delta(git_indexer *idx) -{ - struct delta_info *delta; - - delta = git__calloc(1, sizeof(struct delta_info)); - GIT_ERROR_CHECK_ALLOC(delta); - delta->delta_off = idx->entry_start; - - if (git_vector_insert(&idx->deltas, delta) < 0) - return -1; - - return 0; -} - -static int hash_header(git_hash_ctx *ctx, off64_t len, git_object_t type) -{ - char buffer[64]; - size_t hdrlen; - int error; - - if ((error = git_odb__format_object_header(&hdrlen, - buffer, sizeof(buffer), (size_t)len, type)) < 0) - return error; - - return git_hash_update(ctx, buffer, hdrlen); -} - -static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream) -{ - ssize_t read; - - GIT_ASSERT_ARG(idx); - GIT_ASSERT_ARG(stream); - - do { - if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0) - break; - - if (idx->do_verify) - git_str_put(&idx->entry_data, idx->objbuf, read); - - git_hash_update(&idx->hash_ctx, idx->objbuf, read); - } while (read > 0); - - if (read < 0) - return (int)read; - - return 0; -} - -/* In order to create the packfile stream, we need to skip over the delta base description */ -static int advance_delta_offset(git_indexer *idx, git_object_t type) -{ - git_mwindow *w = NULL; - - GIT_ASSERT_ARG(type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA); - - if (type == GIT_OBJECT_REF_DELTA) { - idx->off += GIT_OID_RAWSZ; - } else { - off64_t base_off; - int error = get_delta_base(&base_off, idx->pack, &w, &idx->off, type, idx->entry_start); - git_mwindow_close(&w); - if (error < 0) - return error; - } - - return 0; -} - -/* Read from the stream and discard any output */ -static int read_object_stream(git_indexer *idx, git_packfile_stream *stream) -{ - ssize_t read; - - GIT_ASSERT_ARG(stream); - - do { - read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf)); - } while (read > 0); - - if (read < 0) - return (int)read; - - return 0; -} - -static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, off64_t start, off64_t size) -{ - void *ptr; - uint32_t crc; - unsigned int left, len; - git_mwindow *w = NULL; - - crc = crc32(0L, Z_NULL, 0); - while (size) { - ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left); - if (ptr == NULL) - return -1; - - len = min(left, (unsigned int)size); - crc = crc32(crc, ptr, len); - size -= len; - start += len; - git_mwindow_close(&w); - } - - *crc_out = htonl(crc); - return 0; -} - -static int add_expected_oid(git_indexer *idx, const git_oid *oid) -{ - /* - * 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 - * not have to expect it. - */ - if ((!idx->odb || !git_odb_exists(idx->odb, 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); - 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; - git_oid *expected; - int error = 0; - - if (obj->type != GIT_OBJECT_BLOB && - obj->type != GIT_OBJECT_TREE && - obj->type != GIT_OBJECT_COMMIT && - obj->type != GIT_OBJECT_TAG) - return 0; - - if (git_object__from_raw(&object, obj->data, obj->len, obj->type) < 0) { - /* - * parse_raw returns EINVALID on invalid data; downgrade - * that to a normal -1 error code. - */ - error = -1; - goto out; - } - - if ((expected = git_oidmap_get(idx->expected_oids, &object->cached.oid)) != NULL) { - git_oidmap_delete(idx->expected_oids, &object->cached.oid); - git__free(expected); - } - - /* - * Check whether this is a known object. If so, we can just continue as - * we assume that the ODB has a complete graph. - */ - if (idx->odb && git_odb_exists(idx->odb, &object->cached.oid)) - return 0; - - switch (obj->type) { - case GIT_OBJECT_TREE: - { - git_tree *tree = (git_tree *) object; - git_tree_entry *entry; - size_t i; - - git_array_foreach(tree->entries, i, entry) - if (add_expected_oid(idx, entry->oid) < 0) - goto out; - - break; - } - case GIT_OBJECT_COMMIT: - { - git_commit *commit = (git_commit *) object; - git_oid *parent_oid; - size_t i; - - git_array_foreach(commit->parent_ids, i, parent_oid) - if (add_expected_oid(idx, parent_oid) < 0) - goto out; - - if (add_expected_oid(idx, &commit->tree_id) < 0) - goto out; - - break; - } - case GIT_OBJECT_TAG: - { - git_tag *tag = (git_tag *) object; - - if (add_expected_oid(idx, &tag->target) < 0) - goto out; - - break; - } - case GIT_OBJECT_BLOB: - default: - break; - } - -out: - git_object_free(object); - - return error; -} - -static int store_object(git_indexer *idx) -{ - int i, error; - git_oid oid; - struct entry *entry; - off64_t entry_size; - struct git_pack_entry *pentry; - off64_t entry_start = idx->entry_start; - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - pentry = git__calloc(1, sizeof(struct git_pack_entry)); - GIT_ERROR_CHECK_ALLOC(pentry); - - if (git_hash_final(oid.id, &idx->hash_ctx)) { - git__free(pentry); - goto on_error; - } - entry_size = idx->off - entry_start; - if (entry_start > UINT31_MAX) { - entry->offset = UINT32_MAX; - entry->offset_long = entry_start; - } else { - entry->offset = (uint32_t)entry_start; - } - - if (idx->do_verify) { - git_rawobj rawobj = { - idx->entry_data.ptr, - idx->entry_data.size, - idx->entry_type - }; - - if ((error = check_object_connectivity(idx, &rawobj)) < 0) - goto on_error; - } - - git_oid_cpy(&pentry->sha1, &oid); - pentry->offset = entry_start; - - 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; - } - - 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); - - if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) - goto on_error; - - /* Add the object to the list */ - if (git_vector_insert(&idx->objects, entry) < 0) - goto on_error; - - for (i = oid.id[0]; i < 256; ++i) { - idx->fanout[i]++; - } - - return 0; - -on_error: - git__free(entry); - - return -1; -} - -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, off64_t entry_start) -{ - int i; - - if (entry_start > UINT31_MAX) { - entry->offset = UINT32_MAX; - entry->offset_long = entry_start; - } else { - entry->offset = (uint32_t)entry_start; - } - - pentry->offset = entry_start; - - 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; - } - - /* Add the object to the list */ - if (git_vector_insert(&idx->objects, entry) < 0) - return -1; - - for (i = entry->oid.id[0]; i < 256; ++i) { - idx->fanout[i]++; - } - - return 0; -} - -static int hash_and_save(git_indexer *idx, git_rawobj *obj, off64_t entry_start) -{ - git_oid oid; - size_t entry_size; - struct entry *entry; - struct git_pack_entry *pentry = NULL; - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - if (git_odb__hashobj(&oid, obj) < 0) { - git_error_set(GIT_ERROR_INDEXER, "failed to hash object"); - goto on_error; - } - - pentry = git__calloc(1, sizeof(struct git_pack_entry)); - GIT_ERROR_CHECK_ALLOC(pentry); - - git_oid_cpy(&pentry->sha1, &oid); - git_oid_cpy(&entry->oid, &oid); - entry->crc = crc32(0L, Z_NULL, 0); - - entry_size = (size_t)(idx->off - entry_start); - if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) - goto on_error; - - return save_entry(idx, entry, pentry, entry_start); - -on_error: - git__free(pentry); - git__free(entry); - git__free(obj->data); - return -1; -} - -static int do_progress_callback(git_indexer *idx, git_indexer_progress *stats) -{ - if (idx->progress_cb) - return git_error_set_after_callback_function( - idx->progress_cb(stats, idx->progress_payload), - "indexer progress"); - return 0; -} - -/* Hash everything but the last 20B of input */ -static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) -{ - size_t to_expell, to_keep; - - if (size == 0) - return; - - /* Easy case, dump the buffer and the data minus the last 20 bytes */ - if (size >= GIT_OID_RAWSZ) { - git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len); - git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ); - - data += size - GIT_OID_RAWSZ; - memcpy(idx->inbuf, data, GIT_OID_RAWSZ); - idx->inbuf_len = GIT_OID_RAWSZ; - return; - } - - /* We can just append */ - if (idx->inbuf_len + size <= GIT_OID_RAWSZ) { - memcpy(idx->inbuf + idx->inbuf_len, data, size); - idx->inbuf_len += size; - return; - } - - /* We need to partially drain the buffer and then append */ - to_keep = GIT_OID_RAWSZ - size; - to_expell = idx->inbuf_len - to_keep; - - git_hash_update(&idx->trailer, idx->inbuf, to_expell); - - memmove(idx->inbuf, idx->inbuf + to_expell, to_keep); - memcpy(idx->inbuf + to_keep, data, size); - idx->inbuf_len += size - to_expell; -} - -#if defined(NO_MMAP) || !defined(GIT_WIN32) - -static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) -{ - size_t remaining_size = size; - const char *ptr = (const char *)data; - - /* Handle data size larger that ssize_t */ - while (remaining_size > 0) { - ssize_t nb; - HANDLE_EINTR(nb, p_pwrite(idx->pack->mwf.fd, (void *)ptr, - remaining_size, offset)); - if (nb <= 0) - return -1; - - ptr += nb; - offset += nb; - remaining_size -= nb; - } - - return 0; -} - -static int append_to_pack(git_indexer *idx, const void *data, size_t size) -{ - if (write_at(idx, data, idx->pack->mwf.size, size) < 0) { - git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); - return -1; - } - - return 0; -} - -#else - -/* - * Windows may keep different views to a networked file for the mmap- and - * open-accessed versions of a file, so any writes done through - * `write(2)`/`pwrite(2)` may not be reflected on the data that `mmap(2)` is - * able to read. - */ - -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; - off64_t page_start; - unsigned char *map_data; - git_map map; - int error; - - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(size); - - if ((error = git__mmap_alignment(&mmap_alignment)) < 0) - return error; - - /* the offset needs to be at the mmap boundary for the platform */ - page_offset = offset % mmap_alignment; - page_start = offset - page_offset; - - if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) - return error; - - map_data = (unsigned char *)map.data; - memcpy(map_data + page_offset, data, size); - p_munmap(&map); - - return 0; -} - -static int append_to_pack(git_indexer *idx, const void *data, size_t size) -{ - off64_t new_size; - size_t mmap_alignment; - size_t page_offset; - off64_t page_start; - off64_t current_size = idx->pack->mwf.size; - int error; - - if (!size) - return 0; - - if ((error = git__mmap_alignment(&mmap_alignment)) < 0) - return error; - - /* Write a single byte to force the file system to allocate space now or - * report an error, since we can't report errors when writing using mmap. - * Round the size up to the nearest page so that we only need to perform file - * I/O when we add a page, instead of whenever we write even a single byte. */ - new_size = current_size + size; - page_offset = new_size % mmap_alignment; - page_start = new_size - page_offset; - - if (p_pwrite(idx->pack->mwf.fd, data, 1, page_start + mmap_alignment - 1) < 0) { - git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); - return -1; - } - - return write_at(idx, data, idx->pack->mwf.size, size); -} - -#endif - -static int read_stream_object(git_indexer *idx, git_indexer_progress *stats) -{ - git_packfile_stream *stream = &idx->stream; - off64_t entry_start = idx->off; - size_t entry_size; - git_object_t type; - git_mwindow *w = NULL; - int error; - - if (idx->pack->mwf.size <= idx->off + 20) - return GIT_EBUFS; - - if (!idx->have_stream) { - error = git_packfile_unpack_header(&entry_size, &type, idx->pack, &w, &idx->off); - if (error == GIT_EBUFS) { - idx->off = entry_start; - return error; - } - if (error < 0) - return error; - - git_mwindow_close(&w); - idx->entry_start = entry_start; - git_hash_init(&idx->hash_ctx); - git_str_clear(&idx->entry_data); - - if (type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA) { - error = advance_delta_offset(idx, type); - if (error == GIT_EBUFS) { - idx->off = entry_start; - return error; - } - if (error < 0) - return error; - - idx->have_delta = 1; - } else { - idx->have_delta = 0; - - error = hash_header(&idx->hash_ctx, entry_size, type); - if (error < 0) - return error; - } - - idx->have_stream = 1; - idx->entry_type = type; - - error = git_packfile_stream_open(stream, idx->pack, idx->off); - if (error < 0) - return error; - } - - if (idx->have_delta) { - error = read_object_stream(idx, stream); - } else { - error = hash_object_stream(idx, stream); - } - - idx->off = stream->curpos; - if (error == GIT_EBUFS) - return error; - - /* We want to free the stream reasorces no matter what here */ - idx->have_stream = 0; - git_packfile_stream_dispose(stream); - - if (error < 0) - return error; - - if (idx->have_delta) { - error = store_delta(idx); - } else { - error = store_object(idx); - } - - if (error < 0) - return error; - - if (!idx->have_delta) { - stats->indexed_objects++; - } - stats->received_objects++; - - if ((error = do_progress_callback(idx, stats)) != 0) - return error; - - return 0; -} - -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; - git_mwindow_file *mwf = &idx->pack->mwf; - - GIT_ASSERT_ARG(idx); - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(stats); - - if ((error = append_to_pack(idx, data, size)) < 0) - return error; - - hash_partially(idx, data, (int)size); - - /* Make sure we set the new size of the pack */ - idx->pack->mwf.size += size; - - if (!idx->parsed_header) { - unsigned int total_objects; - - if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header)) - return 0; - - if ((error = parse_header(&idx->hdr, idx->pack)) < 0) - return error; - - idx->parsed_header = 1; - idx->nr_objects = ntohl(hdr->hdr_entries); - idx->off = sizeof(struct git_pack_header); - - if (idx->nr_objects <= git_indexer__max_objects) { - total_objects = (unsigned int)idx->nr_objects; - } else { - git_error_set(GIT_ERROR_INDEXER, "too many objects"); - return -1; - } - - 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) - return -1; - - if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0) - return -1; - - stats->received_objects = 0; - stats->local_objects = 0; - stats->total_deltas = 0; - stats->indexed_deltas = 0; - stats->indexed_objects = 0; - stats->total_objects = total_objects; - - if ((error = do_progress_callback(idx, stats)) != 0) - return error; - } - - /* Now that we have data in the pack, let's try to parse it */ - - /* As the file grows any windows we try to use will be out of date */ - if ((error = git_mwindow_free_all(mwf)) < 0) - goto on_error; - - while (stats->indexed_objects < idx->nr_objects) { - if ((error = read_stream_object(idx, stats)) != 0) { - if (error == GIT_EBUFS) - break; - else - goto on_error; - } - } - - return 0; - -on_error: - git_mwindow_free_all(mwf); - return error; -} - -static int index_path(git_str *path, git_indexer *idx, const char *suffix) -{ - const char prefix[] = "pack-"; - size_t slash = (size_t)path->size; - - /* search backwards for '/' */ - while (slash > 0 && path->ptr[slash - 1] != '/') - slash--; - - if (git_str_grow(path, slash + 1 + strlen(prefix) + - GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) - return -1; - - git_str_truncate(path, slash); - git_str_puts(path, prefix); - git_str_puts(path, idx->name); - git_str_puts(path, suffix); - - return git_str_oom(path) ? -1 : 0; -} - -/** - * Rewind the packfile by the trailer, as we might need to fix the - * packfile by injecting objects at the tail and must overwrite it. - */ -static int seek_back_trailer(git_indexer *idx) -{ - idx->pack->mwf.size -= GIT_OID_RAWSZ; - return git_mwindow_free_all(&idx->pack->mwf); -} - -static int inject_object(git_indexer *idx, git_oid *id) -{ - git_odb_object *obj = NULL; - struct entry *entry = NULL; - struct git_pack_entry *pentry = NULL; - unsigned char empty_checksum[GIT_HASH_SHA1_SIZE] = {0}; - unsigned char hdr[64]; - git_str buf = GIT_STR_INIT; - off64_t entry_start; - const void *data; - size_t len, hdr_len; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - int error; - - if ((error = seek_back_trailer(idx)) < 0) - goto cleanup; - - entry_start = idx->pack->mwf.size; - - if ((error = git_odb_read(&obj, idx->odb, id)) < 0) { - git_error_set(GIT_ERROR_INDEXER, "missing delta bases"); - goto cleanup; - } - - data = git_odb_object_data(obj); - len = git_odb_object_size(obj); - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->crc = crc32(0L, Z_NULL, 0); - - /* Write out the object header */ - if ((error = git_packfile__object_header(&hdr_len, hdr, len, git_odb_object_type(obj))) < 0 || - (error = append_to_pack(idx, hdr, hdr_len)) < 0) - goto cleanup; - - idx->pack->mwf.size += hdr_len; - entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); - - if ((error = git_zstream_deflatebuf(&buf, data, len)) < 0) - goto cleanup; - - /* And then the compressed object */ - if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) - goto cleanup; - - idx->pack->mwf.size += buf.size; - entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); - git_str_dispose(&buf); - - /* Write a fake trailer so the pack functions play ball */ - - if ((error = append_to_pack(idx, empty_checksum, checksum_size)) < 0) - goto cleanup; - - idx->pack->mwf.size += GIT_OID_RAWSZ; - - pentry = git__calloc(1, sizeof(struct git_pack_entry)); - GIT_ERROR_CHECK_ALLOC(pentry); - - git_oid_cpy(&pentry->sha1, id); - git_oid_cpy(&entry->oid, id); - idx->off = entry_start + hdr_len + len; - - error = save_entry(idx, entry, pentry, entry_start); - -cleanup: - if (error) { - git__free(entry); - git__free(pentry); - } - - git_odb_object_free(obj); - return error; -} - -static int fix_thin_pack(git_indexer *idx, git_indexer_progress *stats) -{ - int error, found_ref_delta = 0; - unsigned int i; - struct delta_info *delta; - size_t size; - git_object_t type; - git_mwindow *w = NULL; - off64_t curpos = 0; - unsigned char *base_info; - unsigned int left = 0; - git_oid base; - - GIT_ASSERT(git_vector_length(&idx->deltas) > 0); - - if (idx->odb == NULL) { - git_error_set(GIT_ERROR_INDEXER, "cannot fix a thin pack without an ODB"); - return -1; - } - - /* Loop until we find the first REF delta */ - git_vector_foreach(&idx->deltas, i, delta) { - if (!delta) - continue; - - curpos = delta->delta_off; - error = git_packfile_unpack_header(&size, &type, idx->pack, &w, &curpos); - if (error < 0) - return error; - - if (type == GIT_OBJECT_REF_DELTA) { - found_ref_delta = 1; - break; - } - } - - if (!found_ref_delta) { - git_error_set(GIT_ERROR_INDEXER, "no REF_DELTA found, cannot inject object"); - return -1; - } - - /* curpos now points to the base information, which is an OID */ - base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left); - if (base_info == NULL) { - git_error_set(GIT_ERROR_INDEXER, "failed to map delta information"); - return -1; - } - - git_oid_fromraw(&base, base_info); - git_mwindow_close(&w); - - if (has_entry(idx, &base)) - return 0; - - if (inject_object(idx, &base) < 0) - return -1; - - stats->local_objects++; - - return 0; -} - -static int resolve_deltas(git_indexer *idx, git_indexer_progress *stats) -{ - unsigned int i; - int error; - struct delta_info *delta; - int progressed = 0, non_null = 0, progress_cb_result; - - while (idx->deltas.length > 0) { - progressed = 0; - non_null = 0; - git_vector_foreach(&idx->deltas, i, delta) { - git_rawobj obj = {0}; - - if (!delta) - continue; - - non_null = 1; - idx->off = delta->delta_off; - if ((error = git_packfile_unpack(&obj, idx->pack, &idx->off)) < 0) { - if (error == GIT_PASSTHROUGH) { - /* We have not seen the base object, we'll try again later. */ - continue; - } - return -1; - } - - if (idx->do_verify && check_object_connectivity(idx, &obj) < 0) - /* TODO: error? continue? */ - continue; - - if (hash_and_save(idx, &obj, delta->delta_off) < 0) - continue; - - git__free(obj.data); - stats->indexed_objects++; - stats->indexed_deltas++; - progressed = 1; - if ((progress_cb_result = do_progress_callback(idx, stats)) < 0) - return progress_cb_result; - - /* remove from the list */ - git_vector_set(NULL, &idx->deltas, i, NULL); - git__free(delta); - } - - /* if none were actually set, we're done */ - if (!non_null) - break; - - if (!progressed && (fix_thin_pack(idx, stats) < 0)) { - return -1; - } - } - - return 0; -} - -static int update_header_and_rehash(git_indexer *idx, git_indexer_progress *stats) -{ - void *ptr; - size_t chunk = 1024*1024; - off64_t hashed = 0; - git_mwindow *w = NULL; - git_mwindow_file *mwf; - unsigned int left; - - mwf = &idx->pack->mwf; - - git_hash_init(&idx->trailer); - - - /* Update the header to include the number of local objects we injected */ - idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); - if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) - return -1; - - /* - * We now use the same technique as before to determine the - * hash. We keep reading up to the end and let - * hash_partially() keep the existing trailer out of the - * calculation. - */ - if (git_mwindow_free_all(mwf) < 0) - return -1; - - idx->inbuf_len = 0; - while (hashed < mwf->size) { - ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); - if (ptr == NULL) - return -1; - - hash_partially(idx, ptr, left); - hashed += left; - - git_mwindow_close(&w); - } - - return 0; -} - -int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) -{ - git_mwindow *w = NULL; - unsigned int i, long_offsets = 0, left; - int error; - struct git_pack_idx_header hdr; - git_str filename = GIT_STR_INIT; - struct entry *entry; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - git_filebuf index_file = {0}; - void *packfile_trailer; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - bool mismatch; - - if (!idx->parsed_header) { - git_error_set(GIT_ERROR_INDEXER, "incomplete pack header"); - return -1; - } - - /* Test for this before resolve_deltas(), as it plays with idx->off */ - if (idx->off + (ssize_t)checksum_size < idx->pack->mwf.size) { - git_error_set(GIT_ERROR_INDEXER, "unexpected data at the end of the pack"); - return -1; - } - if (idx->off + (ssize_t)checksum_size > idx->pack->mwf.size) { - git_error_set(GIT_ERROR_INDEXER, "missing trailer at the end of the pack"); - return -1; - } - - packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - checksum_size, checksum_size, &left); - if (packfile_trailer == NULL) { - git_mwindow_close(&w); - goto on_error; - } - - /* Compare the packfile trailer as it was sent to us and what we calculated */ - git_hash_final(checksum, &idx->trailer); - mismatch = !!memcmp(checksum, packfile_trailer, checksum_size); - git_mwindow_close(&w); - - if (mismatch) { - git_error_set(GIT_ERROR_INDEXER, "packfile trailer mismatch"); - return -1; - } - - /* Freeze the number of deltas */ - stats->total_deltas = stats->total_objects - stats->indexed_objects; - - if ((error = resolve_deltas(idx, stats)) < 0) - return error; - - if (stats->indexed_objects != stats->total_objects) { - git_error_set(GIT_ERROR_INDEXER, "early EOF"); - return -1; - } - - if (stats->local_objects > 0) { - if (update_header_and_rehash(idx, stats) < 0) - return -1; - - git_hash_final(checksum, &idx->trailer); - write_at(idx, checksum, idx->pack->mwf.size - checksum_size, checksum_size); - } - - /* - * Is the resulting graph fully connected or are we still - * missing some objects? In the second case, we can - * bail out due to an incomplete and thus corrupt - * packfile. - */ - if (git_oidmap_size(idx->expected_oids) > 0) { - git_error_set(GIT_ERROR_INDEXER, "packfile is missing %"PRIuZ" objects", - git_oidmap_size(idx->expected_oids)); - return -1; - } - - git_vector_sort(&idx->objects); - - /* Use the trailer hash as the pack file name to ensure - * files with different contents have different names */ - memcpy(idx->checksum, checksum, checksum_size); - if (git_hash_fmt(idx->name, checksum, checksum_size) < 0) - return -1; - - git_str_sets(&filename, idx->pack->pack_name); - git_str_shorten(&filename, strlen("pack")); - git_str_puts(&filename, "idx"); - if (git_str_oom(&filename)) - return -1; - - if (git_filebuf_open(&index_file, filename.ptr, - GIT_FILEBUF_HASH_CONTENTS | - (idx->do_fsync ? GIT_FILEBUF_FSYNC : 0), - idx->mode) < 0) - goto on_error; - - /* Write out the header */ - hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); - hdr.idx_version = htonl(2); - git_filebuf_write(&index_file, &hdr, sizeof(hdr)); - - /* Write out the fanout table */ - for (i = 0; i < 256; ++i) { - uint32_t n = htonl(idx->fanout[i]); - git_filebuf_write(&index_file, &n, sizeof(n)); - } - - /* Write out the object names (SHA-1 hashes) */ - git_vector_foreach(&idx->objects, i, entry) { - git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid)); - } - - /* Write out the CRC32 values */ - git_vector_foreach(&idx->objects, i, entry) { - git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t)); - } - - /* Write out the offsets */ - git_vector_foreach(&idx->objects, i, entry) { - uint32_t n; - - if (entry->offset == UINT32_MAX) - n = htonl(0x80000000 | long_offsets++); - else - n = htonl(entry->offset); - - git_filebuf_write(&index_file, &n, sizeof(uint32_t)); - } - - /* Write out the long offsets */ - git_vector_foreach(&idx->objects, i, entry) { - uint32_t split[2]; - - if (entry->offset != UINT32_MAX) - continue; - - split[0] = htonl(entry->offset_long >> 32); - split[1] = htonl(entry->offset_long & 0xffffffff); - - git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2); - } - - /* Write out the packfile trailer to the index */ - if (git_filebuf_write(&index_file, checksum, checksum_size) < 0) - goto on_error; - - /* Write out the hash of the idx */ - if (git_filebuf_hash(checksum, &index_file) < 0) - goto on_error; - - git_filebuf_write(&index_file, checksum, checksum_size); - - /* Figure out what the final name should be */ - if (index_path(&filename, idx, ".idx") < 0) - goto on_error; - - /* Commit file */ - if (git_filebuf_commit_at(&index_file, filename.ptr) < 0) - goto on_error; - - if (git_mwindow_free_all(&idx->pack->mwf) < 0) - goto on_error; - -#if !defined(NO_MMAP) && defined(GIT_WIN32) - /* - * Some non-Windows remote filesystems fail when truncating files if the - * file permissions change after opening the file (done by p_mkstemp). - * - * Truncation is only needed when mmap is used to undo rounding up to next - * page_size in append_to_pack. - */ - if (p_ftruncate(idx->pack->mwf.fd, idx->pack->mwf.size) < 0) { - git_error_set(GIT_ERROR_OS, "failed to truncate pack file '%s'", idx->pack->pack_name); - return -1; - } -#endif - - if (idx->do_fsync && p_fsync(idx->pack->mwf.fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to fsync packfile"); - goto on_error; - } - - /* We need to close the descriptor here so Windows doesn't choke on commit_at */ - if (p_close(idx->pack->mwf.fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to close packfile"); - goto on_error; - } - - idx->pack->mwf.fd = -1; - - if (index_path(&filename, idx, ".pack") < 0) - goto on_error; - - /* And don't forget to rename the packfile to its new place. */ - if (p_rename(idx->pack->pack_name, git_str_cstr(&filename)) < 0) - goto on_error; - - /* And fsync the parent directory if we're asked to. */ - if (idx->do_fsync && - git_futils_fsync_parent(git_str_cstr(&filename)) < 0) - goto on_error; - - idx->pack_committed = 1; - - git_str_dispose(&filename); - return 0; - -on_error: - git_mwindow_free_all(&idx->pack->mwf); - git_filebuf_cleanup(&index_file); - git_str_dispose(&filename); - return -1; -} - -void git_indexer_free(git_indexer *idx) -{ - const git_oid *key; - git_oid *value; - size_t iter; - - if (idx == NULL) - return; - - if (idx->have_stream) - git_packfile_stream_dispose(&idx->stream); - - git_vector_free_deep(&idx->objects); - - if (idx->pack->idx_cache) { - struct git_pack_entry *pentry; - git_oidmap_foreach_value(idx->pack->idx_cache, pentry, { - git__free(pentry); - }); - - git_oidmap_free(idx->pack->idx_cache); - } - - git_vector_free_deep(&idx->deltas); - - git_packfile_free(idx->pack, !idx->pack_committed); - - 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); - git_str_dispose(&idx->entry_data); - git_oidmap_free(idx->expected_oids); - git__free(idx); -} diff --git a/src/indexer.h b/src/indexer.h deleted file mode 100644 index 8ee6115a6..000000000 --- a/src/indexer.h +++ /dev/null @@ -1,16 +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_indexer_h__ -#define INCLUDE_indexer_h__ - -#include "common.h" - -#include "git2/indexer.h" - -extern void git_indexer__set_fsync(git_indexer *idx, int do_fsync); - -#endif diff --git a/src/integer.h b/src/integer.h deleted file mode 100644 index 63277177b..000000000 --- a/src/integer.h +++ /dev/null @@ -1,218 +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_integer_h__ -#define INCLUDE_integer_h__ - -/** @return true if p fits into the range of a size_t */ -GIT_INLINE(int) git__is_sizet(int64_t p) -{ - size_t r = (size_t)p; - return p == (int64_t)r; -} - -/** @return true if p fits into the range of an ssize_t */ -GIT_INLINE(int) git__is_ssizet(size_t p) -{ - ssize_t r = (ssize_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) -{ - uint32_t r = (uint32_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of an unsigned long */ -GIT_INLINE(int) git__is_ulong(int64_t p) -{ - unsigned long r = (unsigned long)p; - return p == (int64_t)r; -} - -/** @return true if p fits into the range of an int */ -GIT_INLINE(int) git__is_int(int64_t p) -{ - int r = (int)p; - return p == (int64_t)r; -} - -/* Use clang/gcc compiler intrinsics whenever possible */ -#if (__has_builtin(__builtin_add_overflow) || \ - (defined(__GNUC__) && (__GNUC__ >= 5))) - -# if (SIZE_MAX == UINT_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uadd_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umul_overflow(one, two, out) -# elif (SIZE_MAX == ULONG_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uaddl_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umull_overflow(one, two, out) -# elif (SIZE_MAX == ULLONG_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uaddll_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umulll_overflow(one, two, out) -# else -# 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) - -# define git__add_int64_overflow(out, one, two) \ - __builtin_add_overflow(one, two, out) - -/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */ -# if !defined(__clang__) || !defined(GIT_ARCH_32) -# define git__multiply_int64_overflow(out, one, two) \ - __builtin_mul_overflow(one, two, out) -# endif - -/* 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) - -#define git__add_int64_overflow(out, one, two) \ - (LongLongAdd(one, two, out) != S_OK) -#define git__multiply_int64_overflow(out, one, two) \ - (LongLongMult(one, two, out) != S_OK) - -#else - -/** - * Sets `one + two` into `out`, unless the arithmetic would overflow. - * @return false if the result fits in a `size_t`, true on overflow. - */ -GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two) -{ - if (SIZE_MAX - one < two) - return true; - *out = one + two; - return false; -} - -/** - * Sets `one * two` into `out`, unless the arithmetic would overflow. - * @return false if the result fits in a `size_t`, true on overflow. - */ -GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two) -{ - if (one && SIZE_MAX / one < two) - return true; - *out = one * two; - 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; -} - -GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two) -{ - if ((two > 0 && one > (INT64_MAX - two)) || - (two < 0 && one < (INT64_MIN - two))) - return true; - *out = one + two; - return false; -} - -#endif - -/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */ -#if !defined(git__multiply_int64_overflow) -GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two) -{ - /* - * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`, - * without incurring in undefined behavior. That is done by performing the - * comparison with a division instead of a multiplication, which translates - * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats: - * - * - The comparison sign is inverted when both sides of the inequality are - * multiplied/divided by a negative number, so if `one < 0` the comparison - * needs to be flipped. - * - `INT64_MAX / -1` itself overflows (or traps), so that case should be - * avoided. - * - Since the overflow flag is defined as the discrepance between the result - * of performing the multiplication in a signed integer at twice the width - * of the operands, and the truncated+sign-extended version of that same - * result, there are four cases where the result is the opposite of what - * would be expected: - * * `INT64_MIN * -1` / `-1 * INT64_MIN` - * * `INT64_MIN * 1 / `1 * INT64_MIN` - */ - if (one && two) { - if (one > 0 && two > 0) { - if (INT64_MAX / one < two) - return true; - } else if (one < 0 && two < 0) { - if ((one == -1 && two == INT64_MIN) || - (two == -1 && one == INT64_MIN)) { - *out = INT64_MIN; - return false; - } - if (INT64_MAX / one > two) - return true; - } else if (one > 0 && two < 0) { - if ((one == 1 && two == INT64_MIN) || - (INT64_MIN / one > two)) - return true; - } else if (one == -1) { - if (INT64_MIN / two > one) - return true; - } else { - if ((one == INT64_MIN && two == 1) || - (INT64_MIN / one < two)) - return true; - } - } - *out = one * two; - return false; -} -#endif - -#endif diff --git a/src/iterator.c b/src/iterator.c deleted file mode 100644 index 15bb63dc8..000000000 --- a/src/iterator.c +++ /dev/null @@ -1,2439 +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 "iterator.h" - -#include "tree.h" -#include "index.h" -#include "path.h" - -#define GIT_ITERATOR_FIRST_ACCESS (1 << 15) -#define GIT_ITERATOR_HONOR_IGNORES (1 << 16) -#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17) - -#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) -#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) -#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) -#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) -#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) -#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS) -#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) -#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) -#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) -#define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) - - -static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) -{ - if (ignore_case) - iter->flags |= GIT_ITERATOR_IGNORE_CASE; - else - iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; - - iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp; - iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp; - iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; - iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; - - git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); -} - -static int iterator_range_init( - git_iterator *iter, const char *start, const char *end) -{ - if (start && *start) { - iter->start = git__strdup(start); - GIT_ERROR_CHECK_ALLOC(iter->start); - - iter->start_len = strlen(iter->start); - } - - if (end && *end) { - iter->end = git__strdup(end); - GIT_ERROR_CHECK_ALLOC(iter->end); - - iter->end_len = strlen(iter->end); - } - - iter->started = (iter->start == NULL); - iter->ended = false; - - return 0; -} - -static void iterator_range_free(git_iterator *iter) -{ - if (iter->start) { - git__free(iter->start); - iter->start = NULL; - iter->start_len = 0; - } - - if (iter->end) { - git__free(iter->end); - iter->end = NULL; - iter->end_len = 0; - } -} - -static int iterator_reset_range( - git_iterator *iter, const char *start, const char *end) -{ - iterator_range_free(iter); - return iterator_range_init(iter, start, end); -} - -static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist) -{ - size_t i; - - if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0) - return -1; - - for (i = 0; i < pathlist->count; i++) { - if (!pathlist->strings[i]) - continue; - - if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0) - return -1; - } - - return 0; -} - -static int iterator_init_common( - git_iterator *iter, - git_repository *repo, - git_index *index, - git_iterator_options *given_opts) -{ - static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator_options *options = given_opts ? given_opts : &default_opts; - bool ignore_case; - int precompose; - int error; - - iter->repo = repo; - iter->index = index; - iter->flags = options->flags; - - if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) { - ignore_case = true; - } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) { - ignore_case = false; - } else if (repo) { - git_index *index; - - if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) - return error; - - ignore_case = !!index->ignore_case; - - if (ignore_case == 1) - iter->flags |= GIT_ITERATOR_IGNORE_CASE; - else - iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE; - } else { - ignore_case = false; - } - - /* try to look up precompose and set flag if appropriate */ - if (repo && - (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && - (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { - - if (git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) < 0) - git_error_clear(); - else if (precompose) - iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; - } - - if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND)) - iter->flags |= GIT_ITERATOR_INCLUDE_TREES; - - if ((error = iterator_range_init(iter, options->start, options->end)) < 0 || - (error = iterator_pathlist_init(iter, &options->pathlist)) < 0) - return error; - - iterator_set_ignore_case(iter, ignore_case); - return 0; -} - -static void iterator_clear(git_iterator *iter) -{ - iter->started = false; - iter->ended = false; - iter->stat_calls = 0; - iter->pathlist_walk_idx = 0; - iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; -} - -GIT_INLINE(bool) iterator_has_started( - git_iterator *iter, const char *path, bool is_submodule) -{ - size_t path_len; - - if (iter->start == NULL || iter->started == true) - return true; - - /* the starting path is generally a prefix - we have started once we - * are prefixed by this path - */ - iter->started = (iter->prefixcomp(path, iter->start) >= 0); - - if (iter->started) - return true; - - path_len = strlen(path); - - /* if, however, we are a submodule, then we support `start` being - * suffixed with a `/` for crazy legacy reasons. match `submod` - * with a start path of `submod/`. - */ - if (is_submodule && iter->start_len && path_len == iter->start_len - 1 && - iter->start[iter->start_len-1] == '/') - return true; - - /* if, however, our current path is a directory, and our starting path - * is _beneath_ that directory, then recurse into the directory (even - * though we have not yet "started") - */ - if (path_len > 0 && path[path_len-1] == '/' && - iter->strncomp(path, iter->start, path_len) == 0) - return true; - - return false; -} - -GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path) -{ - if (iter->end == NULL) - return false; - else if (iter->ended) - return true; - - iter->ended = (iter->prefixcomp(path, iter->end) > 0); - return iter->ended; -} - -/* walker for the index and tree iterator that allows it to walk the sorted - * pathlist entries alongside sorted iterator entries. - */ -static bool iterator_pathlist_next_is(git_iterator *iter, const char *path) -{ - char *p; - size_t path_len, p_len, cmp_len, i; - int cmp; - - if (iter->pathlist.length == 0) - return true; - - git_vector_sort(&iter->pathlist); - - path_len = strlen(path); - - /* for comparison, drop the trailing slash on the current '/' */ - if (path_len && path[path_len-1] == '/') - path_len--; - - for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { - p = iter->pathlist.contents[i]; - p_len = strlen(p); - - if (p_len && p[p_len-1] == '/') - p_len--; - - cmp_len = min(path_len, p_len); - - /* see if the pathlist entry is a prefix of this path */ - cmp = iter->strncomp(p, path, cmp_len); - - /* prefix match - see if there's an exact match, or if we were - * given a path that matches the directory - */ - if (cmp == 0) { - /* if this pathlist entry is not suffixed with a '/' then - * it matches a path that is a file or a directory. - * (eg, pathlist = "foo" and path is "foo" or "foo/" or - * "foo/something") - */ - if (p[cmp_len] == '\0' && - (path[cmp_len] == '\0' || path[cmp_len] == '/')) - return true; - - /* if this pathlist entry _is_ suffixed with a '/' then - * it matches only paths that are directories. - * (eg, pathlist = "foo/" and path is "foo/" or "foo/something") - */ - if (p[cmp_len] == '/' && path[cmp_len] == '/') - return true; - } - - /* this pathlist entry sorts before the given path, try the next */ - else if (cmp < 0) { - iter->pathlist_walk_idx++; - continue; - } - - /* this pathlist sorts after the given path, no match. */ - else if (cmp > 0) { - break; - } - } - - return false; -} - -typedef enum { - ITERATOR_PATHLIST_NONE = 0, - ITERATOR_PATHLIST_IS_FILE = 1, - ITERATOR_PATHLIST_IS_DIR = 2, - ITERATOR_PATHLIST_IS_PARENT = 3, - ITERATOR_PATHLIST_FULL = 4 -} iterator_pathlist_search_t; - -static iterator_pathlist_search_t iterator_pathlist_search( - git_iterator *iter, const char *path, size_t path_len) -{ - const char *p; - size_t idx; - int error; - - if (iter->pathlist.length == 0) - return ITERATOR_PATHLIST_FULL; - - git_vector_sort(&iter->pathlist); - - error = git_vector_bsearch2(&idx, &iter->pathlist, - (git_vector_cmp)iter->strcomp, path); - - /* the given path was found in the pathlist. since the pathlist only - * matches directories when they're suffixed with a '/', analyze the - * path string to determine whether it's a directory or not. - */ - if (error == 0) { - if (path_len && path[path_len-1] == '/') - return ITERATOR_PATHLIST_IS_DIR; - - return ITERATOR_PATHLIST_IS_FILE; - } - - /* at this point, the path we're examining may be a directory (though we - * don't know that yet, since we're avoiding a stat unless it's necessary) - * so walk the pathlist looking for the given path with a '/' after it, - */ - while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { - if (iter->prefixcomp(p, path) != 0) - break; - - /* an exact match would have been matched by the bsearch above */ - GIT_ASSERT_WITH_RETVAL(p[path_len], ITERATOR_PATHLIST_NONE); - - /* is this a literal directory entry (eg `foo/`) or a file beneath */ - if (p[path_len] == '/') { - return (p[path_len+1] == '\0') ? - ITERATOR_PATHLIST_IS_DIR : - ITERATOR_PATHLIST_IS_PARENT; - } - - if (p[path_len] > '/') - break; - - idx++; - } - - return ITERATOR_PATHLIST_NONE; -} - -/* Empty iterator */ - -static int empty_iterator_noop(const git_index_entry **e, git_iterator *i) -{ - GIT_UNUSED(i); - - if (e) - *e = NULL; - - return GIT_ITEROVER; -} - -static int empty_iterator_advance_over( - const git_index_entry **e, - git_iterator_status_t *s, - git_iterator *i) -{ - *s = GIT_ITERATOR_STATUS_EMPTY; - return empty_iterator_noop(e, i); -} - -static int empty_iterator_reset(git_iterator *i) -{ - GIT_UNUSED(i); - return 0; -} - -static void empty_iterator_free(git_iterator *i) -{ - GIT_UNUSED(i); -} - -typedef struct { - git_iterator base; - git_iterator_callbacks cb; -} empty_iterator; - -int git_iterator_for_nothing( - git_iterator **out, - git_iterator_options *options) -{ - empty_iterator *iter; - - static git_iterator_callbacks callbacks = { - empty_iterator_noop, - empty_iterator_noop, - empty_iterator_noop, - empty_iterator_advance_over, - empty_iterator_reset, - empty_iterator_free - }; - - *out = NULL; - - iter = git__calloc(1, sizeof(empty_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = GIT_ITERATOR_EMPTY; - iter->base.cb = &callbacks; - iter->base.flags = options->flags; - - *out = &iter->base; - return 0; -} - -/* Tree iterator */ - -typedef struct { - git_tree_entry *tree_entry; - const char *parent_path; -} tree_iterator_entry; - -typedef struct { - git_tree *tree; - - /* path to this particular frame (folder) */ - git_str path; - - /* a sorted list of the entries for this frame (folder), these are - * actually pointers to the iterator's entry pool. - */ - git_vector entries; - tree_iterator_entry *current; - - size_t next_idx; - - /* on case insensitive iterations, we also have an array of other - * paths that were case insensitively equal to this one, and their - * tree objects. we have coalesced the tree entries into this frame. - * a child `tree_iterator_entry` will contain a pointer to its actual - * parent path. - */ - git_vector similar_trees; - git_array_t(git_str) similar_paths; -} tree_iterator_frame; - -typedef struct { - git_iterator base; - git_tree *root; - git_array_t(tree_iterator_frame) frames; - - git_index_entry entry; - git_str entry_path; - - /* a pool of entries to reduce the number of allocations */ - git_pool entry_pool; -} tree_iterator; - -GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame( - tree_iterator *iter) -{ - return iter->frames.size > 1 ? - &iter->frames.ptr[iter->frames.size-2] : NULL; -} - -GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame( - tree_iterator *iter) -{ - return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; -} - -GIT_INLINE(int) tree_entry_cmp( - const git_tree_entry *a, const git_tree_entry *b, bool icase) -{ - return git_fs_path_cmp( - a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, - b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, - icase ? git__strncasecmp : git__strncmp); -} - -GIT_INLINE(int) tree_iterator_entry_cmp_icase( - const void *ptr_a, const void *ptr_b) -{ - const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; - const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - - return tree_entry_cmp(a->tree_entry, b->tree_entry, true); -} - -static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b) -{ - const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; - const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - - int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true); - - /* stabilize the sort order for filenames that are (case insensitively) - * the same by examining the parent path (case sensitively) before - * falling back to a case sensitive sort of the filename. - */ - if (!c && a->parent_path != b->parent_path) - c = git__strcmp(a->parent_path, b->parent_path); - - if (!c) - c = tree_entry_cmp(a->tree_entry, b->tree_entry, false); - - return c; -} - -static int tree_iterator_compute_path( - git_str *out, - tree_iterator_entry *entry) -{ - git_str_clear(out); - - if (entry->parent_path) - git_str_joinpath(out, entry->parent_path, entry->tree_entry->filename); - else - git_str_puts(out, entry->tree_entry->filename); - - if (git_tree_entry__is_tree(entry->tree_entry)) - git_str_putc(out, '/'); - - if (git_str_oom(out)) - return -1; - - return 0; -} - -static int tree_iterator_frame_init( - tree_iterator *iter, - git_tree *tree, - tree_iterator_entry *frame_entry) -{ - tree_iterator_frame *new_frame = NULL; - tree_iterator_entry *new_entry; - git_tree *dup = NULL; - git_tree_entry *tree_entry; - git_vector_cmp cmp; - size_t i; - int error = 0; - - new_frame = git_array_alloc(iter->frames); - GIT_ERROR_CHECK_ALLOC(new_frame); - - if ((error = git_tree_dup(&dup, tree)) < 0) - goto done; - - memset(new_frame, 0x0, sizeof(tree_iterator_frame)); - new_frame->tree = dup; - - if (frame_entry && - (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) - goto done; - - git_array_foreach(dup->entries, i, tree_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; - - if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0) - goto done; - } - - git_vector_set_sorted(&new_frame->entries, - !iterator__ignore_case(&iter->base)); - -done: - if (error < 0) { - git_tree_free(dup); - git_array_pop(iter->frames); - } - - return error; -} - -GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry( - tree_iterator_frame *frame) -{ - return frame->current; -} - -GIT_INLINE(int) tree_iterator_frame_push_neighbors( - tree_iterator *iter, - tree_iterator_frame *parent_frame, - tree_iterator_frame *frame, - const char *filename) -{ - tree_iterator_entry *entry, *new_entry; - git_tree *tree = NULL; - git_tree_entry *tree_entry; - git_str *path; - size_t new_size, i; - int error = 0; - - while (parent_frame->next_idx < parent_frame->entries.length) { - entry = parent_frame->entries.contents[parent_frame->next_idx]; - - if (strcasecmp(filename, entry->tree_entry->filename) != 0) - break; - - if ((error = git_tree_lookup(&tree, - iter->base.repo, entry->tree_entry->oid)) < 0) - break; - - if (git_vector_insert(&parent_frame->similar_trees, tree) < 0) - break; - - path = git_array_alloc(parent_frame->similar_paths); - GIT_ERROR_CHECK_ALLOC(path); - - memset(path, 0, sizeof(git_str)); - - if ((error = tree_iterator_compute_path(path, entry)) < 0) - break; - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, - frame->entries.length, tree->entries.size); - git_vector_size_hint(&frame->entries, new_size); - - git_array_foreach(tree->entries, i, tree_entry) { - new_entry = git_pool_malloc(&iter->entry_pool, 1); - GIT_ERROR_CHECK_ALLOC(new_entry); - - new_entry->tree_entry = tree_entry; - new_entry->parent_path = path->ptr; - - if ((error = git_vector_insert(&frame->entries, new_entry)) < 0) - break; - } - - if (error) - break; - - parent_frame->next_idx++; - } - - return error; -} - -GIT_INLINE(int) tree_iterator_frame_push( - tree_iterator *iter, tree_iterator_entry *entry) -{ - tree_iterator_frame *parent_frame, *frame; - git_tree *tree = NULL; - int error; - - if ((error = git_tree_lookup(&tree, - iter->base.repo, entry->tree_entry->oid)) < 0 || - (error = tree_iterator_frame_init(iter, tree, entry)) < 0) - goto done; - - parent_frame = tree_iterator_parent_frame(iter); - frame = tree_iterator_current_frame(iter); - - /* if we're case insensitive, then we may have another directory that - * is (case insensitively) equal to this one. coalesce those children - * into this tree. - */ - if (iterator__ignore_case(&iter->base)) - error = tree_iterator_frame_push_neighbors(iter, - parent_frame, frame, entry->tree_entry->filename); - -done: - git_tree_free(tree); - return error; -} - -static int tree_iterator_frame_pop(tree_iterator *iter) -{ - tree_iterator_frame *frame; - git_str *buf = NULL; - git_tree *tree; - size_t i; - - GIT_ASSERT(iter->frames.size); - - frame = git_array_pop(iter->frames); - - git_vector_free(&frame->entries); - git_tree_free(frame->tree); - - do { - buf = git_array_pop(frame->similar_paths); - git_str_dispose(buf); - } while (buf != NULL); - - git_array_clear(frame->similar_paths); - - git_vector_foreach(&frame->similar_trees, i, tree) - git_tree_free(tree); - - git_vector_free(&frame->similar_trees); - - git_str_dispose(&frame->path); - - return 0; -} - -static int tree_iterator_current( - const git_index_entry **out, git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - - if (!iterator__has_been_accessed(i)) - return iter->base.cb->advance(out, i); - - if (!iter->frames.size) { - *out = NULL; - return GIT_ITEROVER; - } - - *out = &iter->entry; - return 0; -} - -static void tree_iterator_set_current( - tree_iterator *iter, - tree_iterator_frame *frame, - tree_iterator_entry *entry) -{ - git_tree_entry *tree_entry = entry->tree_entry; - - frame->current = entry; - - memset(&iter->entry, 0x0, sizeof(git_index_entry)); - - iter->entry.mode = tree_entry->attr; - iter->entry.path = iter->entry_path.ptr; - git_oid_cpy(&iter->entry.id, tree_entry->oid); -} - -static int tree_iterator_advance(const git_index_entry **out, git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - int error = 0; - - iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - /* examine tree entries until we find the next one to return */ - while (true) { - tree_iterator_entry *prev_entry, *entry; - tree_iterator_frame *frame; - bool is_tree; - - if ((frame = tree_iterator_current_frame(iter)) == NULL) { - error = GIT_ITEROVER; - break; - } - - /* no more entries in this frame. pop the frame out */ - if (frame->next_idx == frame->entries.length) { - if ((error = tree_iterator_frame_pop(iter)) < 0) - break; - - continue; - } - - /* we may have coalesced the contents of case-insensitively same-named - * directories, so do the sort now. - */ - if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries)) - git_vector_sort(&frame->entries); - - /* we have more entries in the current frame, that's our next entry */ - prev_entry = tree_iterator_current_entry(frame); - entry = frame->entries.contents[frame->next_idx]; - frame->next_idx++; - - /* we can have collisions when iterating case insensitively. (eg, - * 'A/a' and 'a/A'). squash this one if it's already been seen. - */ - if (iterator__ignore_case(&iter->base) && - prev_entry && - tree_iterator_entry_cmp_icase(prev_entry, entry) == 0) - continue; - - if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0) - break; - - /* if this path is before our start, advance over this entry */ - if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false)) - continue; - - /* if this path is after our end, stop */ - if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) { - error = GIT_ITEROVER; - break; - } - - /* if we have a list of paths we're interested in, examine it */ - if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr)) - continue; - - is_tree = git_tree_entry__is_tree(entry->tree_entry); - - /* if we are *not* including trees then advance over this entry */ - if (is_tree && !iterator__include_trees(iter)) { - - /* if we've found a tree (and are not returning it to the caller) - * and we are autoexpanding, then we want to return the first - * child. push the new directory and advance. - */ - if (iterator__do_autoexpand(iter)) { - if ((error = tree_iterator_frame_push(iter, entry)) < 0) - break; - } - - continue; - } - - tree_iterator_set_current(iter, frame, entry); - - /* if we are autoexpanding, then push this as a new frame, so that - * the next call to `advance` will dive into this directory. - */ - if (is_tree && iterator__do_autoexpand(iter)) - error = tree_iterator_frame_push(iter, entry); - - break; - } - - if (out) - *out = (error == 0) ? &iter->entry : NULL; - - return error; -} - -static int tree_iterator_advance_into( - const git_index_entry **out, git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - tree_iterator_frame *frame; - tree_iterator_entry *prev_entry; - int error; - - if (out) - *out = NULL; - - if ((frame = tree_iterator_current_frame(iter)) == NULL) - return GIT_ITEROVER; - - /* get the last seen entry */ - prev_entry = tree_iterator_current_entry(frame); - - /* it's legal to call advance_into when auto-expand is on. in this case, - * we will have pushed a new (empty) frame on to the stack for this - * new directory. since it's empty, its current_entry should be null. - */ - GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); - - if (prev_entry) { - if (!git_tree_entry__is_tree(prev_entry->tree_entry)) - return 0; - - if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0) - return error; - } - - /* we've advanced into the directory in question, let advance - * find the first entry - */ - return tree_iterator_advance(out, i); -} - -static int tree_iterator_advance_over( - const git_index_entry **out, - git_iterator_status_t *status, - git_iterator *i) -{ - *status = GIT_ITERATOR_STATUS_NORMAL; - return git_iterator_advance(out, i); -} - -static void tree_iterator_clear(tree_iterator *iter) -{ - while (iter->frames.size) - tree_iterator_frame_pop(iter); - - git_array_clear(iter->frames); - - git_pool_clear(&iter->entry_pool); - git_str_clear(&iter->entry_path); - - iterator_clear(&iter->base); -} - -static int tree_iterator_init(tree_iterator *iter) -{ - int error; - - if ((error = git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry))) < 0 || - (error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0) - return error; - - iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - - return 0; -} - -static int tree_iterator_reset(git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - - tree_iterator_clear(iter); - return tree_iterator_init(iter); -} - -static void tree_iterator_free(git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - - tree_iterator_clear(iter); - - git_tree_free(iter->root); - git_str_dispose(&iter->entry_path); -} - -int git_iterator_for_tree( - git_iterator **out, - git_tree *tree, - git_iterator_options *options) -{ - tree_iterator *iter; - int error; - - static git_iterator_callbacks callbacks = { - tree_iterator_current, - tree_iterator_advance, - tree_iterator_advance_into, - tree_iterator_advance_over, - tree_iterator_reset, - tree_iterator_free - }; - - *out = NULL; - - if (tree == NULL) - return git_iterator_for_nothing(out, options); - - iter = git__calloc(1, sizeof(tree_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = GIT_ITERATOR_TREE; - iter->base.cb = &callbacks; - - if ((error = iterator_init_common(&iter->base, - git_tree_owner(tree), NULL, options)) < 0 || - (error = git_tree_dup(&iter->root, tree)) < 0 || - (error = tree_iterator_init(iter)) < 0) - goto on_error; - - *out = &iter->base; - return 0; - -on_error: - git_iterator_free(&iter->base); - return error; -} - -int git_iterator_current_tree_entry( - const git_tree_entry **tree_entry, git_iterator *i) -{ - tree_iterator *iter; - tree_iterator_frame *frame; - tree_iterator_entry *entry; - - GIT_ASSERT(i->type == GIT_ITERATOR_TREE); - - iter = (tree_iterator *)i; - - frame = tree_iterator_current_frame(iter); - entry = tree_iterator_current_entry(frame); - - *tree_entry = entry->tree_entry; - return 0; -} - -int git_iterator_current_parent_tree( - const git_tree **parent_tree, git_iterator *i, size_t depth) -{ - tree_iterator *iter; - tree_iterator_frame *frame; - - GIT_ASSERT(i->type == GIT_ITERATOR_TREE); - - iter = (tree_iterator *)i; - - GIT_ASSERT(depth < iter->frames.size); - frame = &iter->frames.ptr[iter->frames.size-depth-1]; - - *parent_tree = frame->tree; - return 0; -} - -/* Filesystem iterator */ - -typedef struct { - struct stat st; - size_t path_len; - iterator_pathlist_search_t match; - git_oid id; - char path[GIT_FLEX_ARRAY]; -} filesystem_iterator_entry; - -typedef struct { - git_vector entries; - git_pool entry_pool; - size_t next_idx; - - size_t path_len; - int is_ignored; -} filesystem_iterator_frame; - -typedef struct { - git_iterator base; - char *root; - size_t root_len; - - unsigned int dirload_flags; - - git_tree *tree; - git_index *index; - git_vector index_snapshot; - - git_array_t(filesystem_iterator_frame) frames; - git_ignores ignores; - - /* info about the current entry */ - git_index_entry entry; - git_str current_path; - int current_is_ignored; - - /* temporary buffer for advance_over */ - git_str tmp_buf; -} filesystem_iterator; - - -GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame( - filesystem_iterator *iter) -{ - return iter->frames.size > 1 ? - &iter->frames.ptr[iter->frames.size-2] : NULL; -} - -GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame( - filesystem_iterator *iter) -{ - return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; -} - -GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry( - filesystem_iterator_frame *frame) -{ - return frame->next_idx == 0 ? - NULL : frame->entries.contents[frame->next_idx-1]; -} - -static int filesystem_iterator_entry_cmp(const void *_a, const void *_b) -{ - const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; - const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; - - return git__strcmp(a->path, b->path); -} - -static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b) -{ - const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; - const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; - - return git__strcasecmp(a->path, b->path); -} - -#define FILESYSTEM_MAX_DEPTH 100 - -/** - * Figure out if an entry is a submodule. - * - * We consider it a submodule if the path is listed as a submodule in - * either the tree or the index. - */ -static int filesystem_iterator_is_submodule( - bool *out, filesystem_iterator *iter, const char *path, size_t path_len) -{ - bool is_submodule = false; - int error; - - *out = false; - - /* first see if this path is a submodule in HEAD */ - if (iter->tree) { - git_tree_entry *entry; - - error = git_tree_entry_bypath(&entry, iter->tree, path); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - - if (!error) { - is_submodule = (entry->attr == GIT_FILEMODE_COMMIT); - git_tree_entry_free(entry); - } - } - - if (!is_submodule && iter->base.index) { - size_t pos; - - error = git_index_snapshot_find(&pos, - &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - - if (!error) { - git_index_entry *e = git_vector_get(&iter->index_snapshot, pos); - is_submodule = (e->mode == GIT_FILEMODE_COMMIT); - } - } - - *out = is_submodule; - return 0; -} - -static void filesystem_iterator_frame_push_ignores( - filesystem_iterator *iter, - filesystem_iterator_entry *frame_entry, - filesystem_iterator_frame *new_frame) -{ - filesystem_iterator_frame *previous_frame; - const char *path = frame_entry ? frame_entry->path : ""; - - if (!iterator__honor_ignores(&iter->base)) - return; - - if (git_ignore__lookup(&new_frame->is_ignored, - &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) { - git_error_clear(); - new_frame->is_ignored = GIT_IGNORE_NOTFOUND; - } - - /* if this is not the top level directory... */ - if (frame_entry) { - const char *relative_path; - - previous_frame = filesystem_iterator_parent_frame(iter); - - /* push new ignores for files in this directory */ - relative_path = frame_entry->path + previous_frame->path_len; - - /* inherit ignored from parent if no rule specified */ - if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND) - new_frame->is_ignored = previous_frame->is_ignored; - - git_ignore__push_dir(&iter->ignores, relative_path); - } -} - -static void filesystem_iterator_frame_pop_ignores( - filesystem_iterator *iter) -{ - if (iterator__honor_ignores(&iter->base)) - git_ignore__pop_dir(&iter->ignores); -} - -GIT_INLINE(bool) filesystem_iterator_examine_path( - bool *is_dir_out, - iterator_pathlist_search_t *match_out, - filesystem_iterator *iter, - filesystem_iterator_entry *frame_entry, - const char *path, - size_t path_len) -{ - bool is_dir = 0; - iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL; - - *is_dir_out = false; - *match_out = ITERATOR_PATHLIST_NONE; - - if (iter->base.start_len) { - int cmp = iter->base.strncomp(path, iter->base.start, path_len); - - /* we haven't stat'ed `path` yet, so we don't yet know if it's a - * directory or not. special case if the current path may be a - * directory that matches the start prefix. - */ - if (cmp == 0) { - if (iter->base.start[path_len] == '/') - is_dir = true; - - else if (iter->base.start[path_len] != '\0') - cmp = -1; - } - - if (cmp < 0) - return false; - } - - if (iter->base.end_len) { - int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len); - - if (cmp > 0) - return false; - } - - /* if we have a pathlist that we're limiting to, examine this path now - * to avoid a `stat` if we're not interested in the path. - */ - if (iter->base.pathlist.length) { - /* if our parent was explicitly included, so too are we */ - if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT) - match = ITERATOR_PATHLIST_FULL; - else - match = iterator_pathlist_search(&iter->base, path, path_len); - - if (match == ITERATOR_PATHLIST_NONE) - return false; - - /* Ensure that the pathlist entry lines up with what we expected */ - if (match == ITERATOR_PATHLIST_IS_DIR || - match == ITERATOR_PATHLIST_IS_PARENT) - is_dir = true; - } - - *is_dir_out = is_dir; - *match_out = match; - return true; -} - -GIT_INLINE(bool) filesystem_iterator_is_dot_git( - filesystem_iterator *iter, const char *path, size_t path_len) -{ - size_t len; - - if (!iterator__ignore_dot_git(&iter->base)) - return false; - - if ((len = path_len) < 4) - return false; - - if (path[len - 1] == '/') - len--; - - if (git__tolower(path[len - 1]) != 't' || - git__tolower(path[len - 2]) != 'i' || - git__tolower(path[len - 3]) != 'g' || - git__tolower(path[len - 4]) != '.') - return false; - - return (len == 4 || path[len - 5] == '/'); -} - -static int filesystem_iterator_entry_hash( - filesystem_iterator *iter, - filesystem_iterator_entry *entry) -{ - git_str fullpath = GIT_STR_INIT; - int error; - - if (S_ISDIR(entry->st.st_mode)) { - memset(&entry->id, 0, GIT_OID_RAWSZ); - return 0; - } - - if (iter->base.type == GIT_ITERATOR_WORKDIR) - return git_repository_hashfile(&entry->id, - iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL); - - if (!(error = git_str_joinpath(&fullpath, iter->root, entry->path)) && - !(error = git_path_validate_str_length(iter->base.repo, &fullpath))) - error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJECT_BLOB); - - git_str_dispose(&fullpath); - return error; -} - -static int filesystem_iterator_entry_init( - filesystem_iterator_entry **out, - filesystem_iterator *iter, - filesystem_iterator_frame *frame, - const char *path, - size_t path_len, - struct stat *statbuf, - iterator_pathlist_search_t pathlist_match) -{ - filesystem_iterator_entry *entry; - size_t entry_size; - int error = 0; - - *out = NULL; - - /* Make sure to append two bytes, one for the path's null - * termination, one for a possible trailing '/' for folders. - */ - GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, - sizeof(filesystem_iterator_entry), path_len); - GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, entry_size, 2); - - entry = git_pool_malloc(&frame->entry_pool, entry_size); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->path_len = path_len; - entry->match = pathlist_match; - memcpy(entry->path, path, path_len); - memcpy(&entry->st, statbuf, sizeof(struct stat)); - - /* Suffix directory paths with a '/' */ - if (S_ISDIR(entry->st.st_mode)) - entry->path[entry->path_len++] = '/'; - - entry->path[entry->path_len] = '\0'; - - if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) - error = filesystem_iterator_entry_hash(iter, entry); - - if (!error) - *out = entry; - - return error; -} - -static int filesystem_iterator_frame_push( - filesystem_iterator *iter, - filesystem_iterator_entry *frame_entry) -{ - filesystem_iterator_frame *new_frame = NULL; - git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; - git_str root = GIT_STR_INIT; - const char *path; - filesystem_iterator_entry *entry; - struct stat statbuf; - size_t path_len; - int error; - - if (iter->frames.size == FILESYSTEM_MAX_DEPTH) { - git_error_set(GIT_ERROR_REPOSITORY, - "directory nesting too deep (%"PRIuZ")", iter->frames.size); - return -1; - } - - new_frame = git_array_alloc(iter->frames); - GIT_ERROR_CHECK_ALLOC(new_frame); - - memset(new_frame, 0, sizeof(filesystem_iterator_frame)); - - if (frame_entry) - git_str_joinpath(&root, iter->root, frame_entry->path); - else - git_str_puts(&root, iter->root); - - if (git_str_oom(&root) || - git_path_validate_str_length(iter->base.repo, &root) < 0) { - error = -1; - goto done; - } - - new_frame->path_len = frame_entry ? frame_entry->path_len : 0; - - /* Any error here is equivalent to the dir not existing, skip over it */ - if ((error = git_fs_path_diriter_init( - &diriter, root.ptr, iter->dirload_flags)) < 0) { - error = GIT_ENOTFOUND; - goto done; - } - - if ((error = git_vector_init(&new_frame->entries, 64, - iterator__ignore_case(&iter->base) ? - filesystem_iterator_entry_cmp_icase : - filesystem_iterator_entry_cmp)) < 0) - goto done; - - if ((error = git_pool_init(&new_frame->entry_pool, 1)) < 0) - goto done; - - /* check if this directory is ignored */ - filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame); - - while ((error = git_fs_path_diriter_next(&diriter)) == 0) { - iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL; - git_str path_str = GIT_STR_INIT; - bool dir_expected = false; - - if ((error = git_fs_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) - goto done; - - path_str.ptr = (char *)path; - path_str.size = path_len; - - if ((error = git_path_validate_str_length(iter->base.repo, &path_str)) < 0) - goto done; - - GIT_ASSERT(path_len > iter->root_len); - - /* remove the prefix if requested */ - path += iter->root_len; - path_len -= iter->root_len; - - /* examine start / end and the pathlist to see if this path is in it. - * note that since we haven't yet stat'ed the path, we cannot know - * whether it's a directory yet or not, so this can give us an - * expected type (S_IFDIR or S_IFREG) that we should examine) - */ - if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match, - iter, frame_entry, path, path_len)) - continue; - - /* TODO: don't need to stat if assume unchanged for this path and - * we have an index, we can just copy the data out of it. - */ - - if ((error = git_fs_path_diriter_stat(&statbuf, &diriter)) < 0) { - /* file was removed between readdir and lstat */ - if (error == GIT_ENOTFOUND) - continue; - - /* treat the file as unreadable */ - memset(&statbuf, 0, sizeof(statbuf)); - statbuf.st_mode = GIT_FILEMODE_UNREADABLE; - - error = 0; - } - - iter->base.stat_calls++; - - /* Ignore wacky things in the filesystem */ - if (!S_ISDIR(statbuf.st_mode) && - !S_ISREG(statbuf.st_mode) && - !S_ISLNK(statbuf.st_mode) && - statbuf.st_mode != GIT_FILEMODE_UNREADABLE) - continue; - - if (filesystem_iterator_is_dot_git(iter, path, path_len)) - continue; - - /* convert submodules to GITLINK and remove trailing slashes */ - if (S_ISDIR(statbuf.st_mode)) { - bool submodule = false; - - if ((error = filesystem_iterator_is_submodule(&submodule, - iter, path, path_len)) < 0) - goto done; - - if (submodule) - statbuf.st_mode = GIT_FILEMODE_COMMIT; - } - - /* Ensure that the pathlist entry lines up with what we expected */ - else if (dir_expected) - continue; - - if ((error = filesystem_iterator_entry_init(&entry, - iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0) - goto done; - - git_vector_insert(&new_frame->entries, entry); - } - - if (error == GIT_ITEROVER) - error = 0; - - /* sort now that directory suffix is added */ - git_vector_sort(&new_frame->entries); - -done: - if (error < 0) - git_array_pop(iter->frames); - - git_str_dispose(&root); - git_fs_path_diriter_free(&diriter); - return error; -} - -GIT_INLINE(int) filesystem_iterator_frame_pop(filesystem_iterator *iter) -{ - filesystem_iterator_frame *frame; - - GIT_ASSERT(iter->frames.size); - - frame = git_array_pop(iter->frames); - filesystem_iterator_frame_pop_ignores(iter); - - git_pool_clear(&frame->entry_pool); - git_vector_free(&frame->entries); - - return 0; -} - -static void filesystem_iterator_set_current( - filesystem_iterator *iter, - filesystem_iterator_entry *entry) -{ - /* - * Index entries are limited to 32 bit timestamps. We can safely - * cast this since workdir times are only used in the cache; any - * mismatch will cause a hash recomputation which is unfortunate - * but affects only people who set their filetimes to 2038. - * (Same with the file size.) - */ - iter->entry.ctime.seconds = (int32_t)entry->st.st_ctime; - iter->entry.mtime.seconds = (int32_t)entry->st.st_mtime; - -#if defined(GIT_USE_NSEC) - iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; - iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; -#else - iter->entry.ctime.nanoseconds = 0; - iter->entry.mtime.nanoseconds = 0; -#endif - - iter->entry.dev = entry->st.st_dev; - iter->entry.ino = entry->st.st_ino; - iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode); - iter->entry.uid = entry->st.st_uid; - iter->entry.gid = entry->st.st_gid; - iter->entry.file_size = (uint32_t)entry->st.st_size; - - if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) - git_oid_cpy(&iter->entry.id, &entry->id); - - iter->entry.path = entry->path; - - iter->current_is_ignored = GIT_IGNORE_UNCHECKED; -} - -static int filesystem_iterator_current( - const git_index_entry **out, git_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); - - if (!iter->frames.size) { - *out = NULL; - return GIT_ITEROVER; - } - - *out = &iter->entry; - return 0; -} - -static int filesystem_iterator_is_dir( - bool *is_dir, - const filesystem_iterator *iter, - const filesystem_iterator_entry *entry) -{ - struct stat st; - git_str fullpath = GIT_STR_INIT; - int error = 0; - - if (S_ISDIR(entry->st.st_mode)) { - *is_dir = 1; - goto done; - } - - if (!iterator__descend_symlinks(iter) || !S_ISLNK(entry->st.st_mode)) { - *is_dir = 0; - goto done; - } - - if ((error = git_str_joinpath(&fullpath, iter->root, entry->path)) < 0 || - (error = git_path_validate_str_length(iter->base.repo, &fullpath)) < 0 || - (error = p_stat(fullpath.ptr, &st)) < 0) - goto done; - - *is_dir = S_ISDIR(st.st_mode); - -done: - git_str_dispose(&fullpath); - return error; -} - -static int filesystem_iterator_advance( - const git_index_entry **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - bool is_dir; - int error = 0; - - iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - /* examine filesystem entries until we find the next one to return */ - while (true) { - filesystem_iterator_frame *frame; - filesystem_iterator_entry *entry; - - if ((frame = filesystem_iterator_current_frame(iter)) == NULL) { - error = GIT_ITEROVER; - break; - } - - /* no more entries in this frame. pop the frame out */ - if (frame->next_idx == frame->entries.length) { - filesystem_iterator_frame_pop(iter); - continue; - } - - /* we have more entries in the current frame, that's our next entry */ - entry = frame->entries.contents[frame->next_idx]; - frame->next_idx++; - - if ((error = filesystem_iterator_is_dir(&is_dir, iter, entry)) < 0) - break; - - if (is_dir) { - if (iterator__do_autoexpand(iter)) { - error = filesystem_iterator_frame_push(iter, entry); - - /* may get GIT_ENOTFOUND due to races or permission problems - * that we want to quietly swallow - */ - if (error == GIT_ENOTFOUND) - continue; - else if (error < 0) - break; - } - - if (!iterator__include_trees(iter)) - continue; - } - - filesystem_iterator_set_current(iter, entry); - break; - } - - if (out) - *out = (error == 0) ? &iter->entry : NULL; - - return error; -} - -static int filesystem_iterator_advance_into( - const git_index_entry **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - filesystem_iterator_frame *frame; - filesystem_iterator_entry *prev_entry; - int error; - - if (out) - *out = NULL; - - if ((frame = filesystem_iterator_current_frame(iter)) == NULL) - return GIT_ITEROVER; - - /* get the last seen entry */ - prev_entry = filesystem_iterator_current_entry(frame); - - /* it's legal to call advance_into when auto-expand is on. in this case, - * we will have pushed a new (empty) frame on to the stack for this - * new directory. since it's empty, its current_entry should be null. - */ - GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); - - if (prev_entry) { - if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT && - !S_ISDIR(prev_entry->st.st_mode)) - return 0; - - if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0) - return error; - } - - /* we've advanced into the directory in question, let advance - * find the first entry - */ - return filesystem_iterator_advance(out, i); -} - -int git_iterator_current_workdir_path(git_str **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - const git_index_entry *entry; - - if (i->type != GIT_ITERATOR_FS && - i->type != GIT_ITERATOR_WORKDIR) { - *out = NULL; - return 0; - } - - git_str_truncate(&iter->current_path, iter->root_len); - - if (git_iterator_current(&entry, i) < 0 || - git_str_puts(&iter->current_path, entry->path) < 0) - return -1; - - *out = &iter->current_path; - return 0; -} - -GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry) -{ -#if defined(GIT_WIN32) && !defined(__MINGW32__) - return (entry && entry->mode) ? - (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) : - GIT_DIR_FLAG_UNKNOWN; -#else - GIT_UNUSED(entry); - return GIT_DIR_FLAG_UNKNOWN; -#endif -} - -static void filesystem_iterator_update_ignored(filesystem_iterator *iter) -{ - filesystem_iterator_frame *frame; - git_dir_flag dir_flag = entry_dir_flag(&iter->entry); - - if (git_ignore__lookup(&iter->current_is_ignored, - &iter->ignores, iter->entry.path, dir_flag) < 0) { - git_error_clear(); - iter->current_is_ignored = GIT_IGNORE_NOTFOUND; - } - - /* use ignore from containing frame stack */ - if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) { - frame = filesystem_iterator_current_frame(iter); - iter->current_is_ignored = frame->is_ignored; - } -} - -GIT_INLINE(bool) filesystem_iterator_current_is_ignored( - filesystem_iterator *iter) -{ - if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED) - filesystem_iterator_update_ignored(iter); - - return (iter->current_is_ignored == GIT_IGNORE_TRUE); -} - -bool git_iterator_current_is_ignored(git_iterator *i) -{ - filesystem_iterator *iter = NULL; - - if (i->type != GIT_ITERATOR_WORKDIR) - return false; - - 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 = GIT_CONTAINER_OF(i, filesystem_iterator, base); - filesystem_iterator_frame *frame; - - if (i->type != GIT_ITERATOR_WORKDIR) - return false; - - frame = filesystem_iterator_current_frame(iter); - return (frame->is_ignored == GIT_IGNORE_TRUE); -} - -static int filesystem_iterator_advance_over( - const git_index_entry **out, - git_iterator_status_t *status, - git_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; - const char *base; - int error = 0; - - *out = NULL; - *status = GIT_ITERATOR_STATUS_NORMAL; - - GIT_ASSERT(iterator__has_been_accessed(i)); - - current_frame = filesystem_iterator_current_frame(iter); - GIT_ASSERT(current_frame); - - current_entry = filesystem_iterator_current_entry(current_frame); - GIT_ASSERT(current_entry); - - if ((error = git_iterator_current(&entry, i)) < 0) - return error; - - if (!S_ISDIR(entry->mode)) { - if (filesystem_iterator_current_is_ignored(iter)) - *status = GIT_ITERATOR_STATUS_IGNORED; - - return filesystem_iterator_advance(out, i); - } - - git_str_clear(&iter->tmp_buf); - if ((error = git_str_puts(&iter->tmp_buf, entry->path)) < 0) - return error; - - base = iter->tmp_buf.ptr; - - /* scan inside the directory looking for files. if we find nothing, - * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to - * IGNORED. if we find a real actual item, upgrade all the way to NORMAL - * and then stop. - * - * however, if we're here looking for a pathlist item (but are not - * actually in the pathlist ourselves) then start at FILTERED instead of - * EMPTY. callers then know that this path was not something they asked - * about. - */ - *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ? - GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY; - - while (entry && !iter->base.prefixcomp(entry->path, base)) { - if (filesystem_iterator_current_is_ignored(iter)) { - /* if we found an explicitly ignored item, then update from - * EMPTY to IGNORED - */ - *status = GIT_ITERATOR_STATUS_IGNORED; - } else if (S_ISDIR(entry->mode)) { - error = filesystem_iterator_advance_into(&entry, i); - - if (!error) - continue; - - /* this directory disappeared, ignore it */ - else if (error == GIT_ENOTFOUND) - error = 0; - - /* a real error occurred */ - else - break; - } else { - /* we found a non-ignored item, treat parent as untracked */ - *status = GIT_ITERATOR_STATUS_NORMAL; - break; - } - - if ((error = git_iterator_advance(&entry, i)) < 0) - break; - } - - /* wrap up scan back to base directory */ - while (entry && !iter->base.prefixcomp(entry->path, base)) { - if ((error = git_iterator_advance(&entry, i)) < 0) - break; - } - - if (!error) - *out = entry; - - return error; -} - -static void filesystem_iterator_clear(filesystem_iterator *iter) -{ - while (iter->frames.size) - filesystem_iterator_frame_pop(iter); - - git_array_clear(iter->frames); - git_ignore__free(&iter->ignores); - - git_str_dispose(&iter->tmp_buf); - - iterator_clear(&iter->base); -} - -static int filesystem_iterator_init(filesystem_iterator *iter) -{ - int error; - - if (iterator__honor_ignores(&iter->base) && - (error = git_ignore__for_path(iter->base.repo, - ".gitignore", &iter->ignores)) < 0) - return error; - - if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0) - return error; - - iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - - return 0; -} - -static int filesystem_iterator_reset(git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - - filesystem_iterator_clear(iter); - return filesystem_iterator_init(iter); -} - -static void filesystem_iterator_free(git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - git__free(iter->root); - git_str_dispose(&iter->current_path); - git_tree_free(iter->tree); - if (iter->index) - git_index_snapshot_release(&iter->index_snapshot, iter->index); - filesystem_iterator_clear(iter); -} - -static int iterator_for_filesystem( - git_iterator **out, - git_repository *repo, - const char *root, - git_index *index, - git_tree *tree, - git_iterator_t type, - git_iterator_options *options) -{ - filesystem_iterator *iter; - size_t root_len; - int error; - - static git_iterator_callbacks callbacks = { - filesystem_iterator_current, - filesystem_iterator_advance, - filesystem_iterator_advance_into, - filesystem_iterator_advance_over, - filesystem_iterator_reset, - filesystem_iterator_free - }; - - *out = NULL; - - if (root == NULL) - return git_iterator_for_nothing(out, options); - - iter = git__calloc(1, sizeof(filesystem_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = type; - iter->base.cb = &callbacks; - - root_len = strlen(root); - - iter->root = git__malloc(root_len+2); - GIT_ERROR_CHECK_ALLOC(iter->root); - - memcpy(iter->root, root, root_len); - - if (root_len == 0 || root[root_len-1] != '/') { - iter->root[root_len] = '/'; - root_len++; - } - iter->root[root_len] = '\0'; - iter->root_len = root_len; - - if ((error = git_str_puts(&iter->current_path, iter->root)) < 0) - goto on_error; - - if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0) - goto on_error; - - if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0) - goto on_error; - - if (index && - (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0) - goto on_error; - - iter->index = index; - iter->dirload_flags = - (iterator__ignore_case(&iter->base) ? - GIT_FS_PATH_DIR_IGNORE_CASE : 0) | - (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ? - GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE : 0); - - if ((error = filesystem_iterator_init(iter)) < 0) - goto on_error; - - *out = &iter->base; - return 0; - -on_error: - git_iterator_free(&iter->base); - return error; -} - -int git_iterator_for_filesystem( - git_iterator **out, - const char *root, - git_iterator_options *options) -{ - return iterator_for_filesystem(out, - NULL, root, NULL, NULL, GIT_ITERATOR_FS, options); -} - -int git_iterator_for_workdir_ext( - git_iterator **out, - git_repository *repo, - const char *repo_workdir, - git_index *index, - git_tree *tree, - git_iterator_options *given_opts) -{ - git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT; - - if (!repo_workdir) { - if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) - return GIT_EBAREREPO; - - repo_workdir = git_repository_workdir(repo); - } - - /* upgrade to a workdir iterator, adding necessary internal flags */ - if (given_opts) - memcpy(&options, given_opts, sizeof(git_iterator_options)); - - options.flags |= GIT_ITERATOR_HONOR_IGNORES | - GIT_ITERATOR_IGNORE_DOT_GIT; - - return iterator_for_filesystem(out, - repo, repo_workdir, index, tree, GIT_ITERATOR_WORKDIR, &options); -} - - -/* Index iterator */ - - -typedef struct { - git_iterator base; - git_vector entries; - size_t next_idx; - - /* the pseudotree entry */ - git_index_entry tree_entry; - git_str tree_buf; - bool skip_tree; - - const git_index_entry *entry; -} index_iterator; - -static int index_iterator_current( - const git_index_entry **out, git_iterator *i) -{ - index_iterator *iter = (index_iterator *)i; - - if (!iterator__has_been_accessed(i)) - return iter->base.cb->advance(out, i); - - if (iter->entry == NULL) { - *out = NULL; - return GIT_ITEROVER; - } - - *out = iter->entry; - return 0; -} - -static bool index_iterator_create_pseudotree( - const git_index_entry **out, - index_iterator *iter, - const char *path) -{ - const char *prev_path, *relative_path, *dirsep; - size_t common_len; - - prev_path = iter->entry ? iter->entry->path : ""; - - /* determine if the new path is in a different directory from the old */ - common_len = git_fs_path_common_dirlen(prev_path, path); - relative_path = path + common_len; - - if ((dirsep = strchr(relative_path, '/')) == NULL) - return false; - - git_str_clear(&iter->tree_buf); - git_str_put(&iter->tree_buf, path, (dirsep - path) + 1); - - iter->tree_entry.mode = GIT_FILEMODE_TREE; - iter->tree_entry.path = iter->tree_buf.ptr; - - *out = &iter->tree_entry; - return true; -} - -static int index_iterator_skip_pseudotree(index_iterator *iter) -{ - GIT_ASSERT(iterator__has_been_accessed(&iter->base)); - GIT_ASSERT(S_ISDIR(iter->entry->mode)); - - while (true) { - const git_index_entry *next_entry = NULL; - - if (++iter->next_idx >= iter->entries.length) - return GIT_ITEROVER; - - next_entry = iter->entries.contents[iter->next_idx]; - - if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path, - iter->tree_buf.size) != 0) - break; - } - - iter->skip_tree = false; - return 0; -} - -static int index_iterator_advance( - const git_index_entry **out, git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - const git_index_entry *entry = NULL; - bool is_submodule; - int error = 0; - - iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - while (true) { - if (iter->next_idx >= iter->entries.length) { - error = GIT_ITEROVER; - break; - } - - /* we were not asked to expand this pseudotree. advance over it. */ - if (iter->skip_tree) { - index_iterator_skip_pseudotree(iter); - continue; - } - - entry = iter->entries.contents[iter->next_idx]; - is_submodule = S_ISGITLINK(entry->mode); - - if (!iterator_has_started(&iter->base, entry->path, is_submodule)) { - iter->next_idx++; - continue; - } - - if (iterator_has_ended(&iter->base, entry->path)) { - error = GIT_ITEROVER; - break; - } - - /* if we have a list of paths we're interested in, examine it */ - if (!iterator_pathlist_next_is(&iter->base, entry->path)) { - iter->next_idx++; - continue; - } - - /* if this is a conflict, skip it unless we're including conflicts */ - if (git_index_entry_is_conflict(entry) && - !iterator__include_conflicts(&iter->base)) { - iter->next_idx++; - continue; - } - - /* we've found what will be our next _file_ entry. but if we are - * returning trees entries, we may need to return a pseudotree - * entry that will contain this. don't advance over this entry, - * though, we still need to return it on the next `advance`. - */ - if (iterator__include_trees(&iter->base) && - index_iterator_create_pseudotree(&entry, iter, entry->path)) { - - /* Note whether this pseudo tree should be expanded or not */ - iter->skip_tree = iterator__dont_autoexpand(&iter->base); - break; - } - - iter->next_idx++; - break; - } - - iter->entry = (error == 0) ? entry : NULL; - - if (out) - *out = iter->entry; - - return error; -} - -static int index_iterator_advance_into( - const git_index_entry **out, git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - - if (! S_ISDIR(iter->tree_entry.mode)) { - if (out) - *out = NULL; - - return 0; - } - - iter->skip_tree = false; - return index_iterator_advance(out, i); -} - -static int index_iterator_advance_over( - const git_index_entry **out, - git_iterator_status_t *status, - git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - const git_index_entry *entry; - int error; - - if ((error = index_iterator_current(&entry, i)) < 0) - return error; - - if (S_ISDIR(entry->mode)) - index_iterator_skip_pseudotree(iter); - - *status = GIT_ITERATOR_STATUS_NORMAL; - return index_iterator_advance(out, i); -} - -static void index_iterator_clear(index_iterator *iter) -{ - iterator_clear(&iter->base); -} - -static int index_iterator_init(index_iterator *iter) -{ - iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - iter->next_idx = 0; - iter->skip_tree = false; - return 0; -} - -static int index_iterator_reset(git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - - index_iterator_clear(iter); - return index_iterator_init(iter); -} - -static void index_iterator_free(git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - - git_index_snapshot_release(&iter->entries, iter->base.index); - git_str_dispose(&iter->tree_buf); -} - -int git_iterator_for_index( - git_iterator **out, - git_repository *repo, - git_index *index, - git_iterator_options *options) -{ - index_iterator *iter; - int error; - - static git_iterator_callbacks callbacks = { - index_iterator_current, - index_iterator_advance, - index_iterator_advance_into, - index_iterator_advance_over, - index_iterator_reset, - index_iterator_free - }; - - *out = NULL; - - if (index == NULL) - return git_iterator_for_nothing(out, options); - - iter = git__calloc(1, sizeof(index_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = GIT_ITERATOR_INDEX; - iter->base.cb = &callbacks; - - if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || - (error = git_index_snapshot_new(&iter->entries, index)) < 0 || - (error = index_iterator_init(iter)) < 0) - goto on_error; - - git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ? - git_index_entry_icmp : git_index_entry_cmp); - git_vector_sort(&iter->entries); - - *out = &iter->base; - return 0; - -on_error: - git_iterator_free(&iter->base); - return error; -} - - -/* Iterator API */ - -int git_iterator_reset_range( - git_iterator *i, const char *start, const char *end) -{ - if (iterator_reset_range(i, start, end) < 0) - return -1; - - return i->cb->reset(i); -} - -int git_iterator_set_ignore_case(git_iterator *i, bool ignore_case) -{ - GIT_ASSERT(!iterator__has_been_accessed(i)); - iterator_set_ignore_case(i, ignore_case); - return 0; -} - -void git_iterator_free(git_iterator *iter) -{ - if (iter == NULL) - return; - - iter->cb->free(iter); - - git_vector_free(&iter->pathlist); - git__free(iter->start); - git__free(iter->end); - - memset(iter, 0, sizeof(*iter)); - - git__free(iter); -} - -int git_iterator_foreach( - git_iterator *iterator, - git_iterator_foreach_cb cb, - void *data) -{ - const git_index_entry *iterator_item; - int error = 0; - - if ((error = git_iterator_current(&iterator_item, iterator)) < 0) - goto done; - - if ((error = cb(iterator_item, data)) != 0) - goto done; - - while (true) { - if ((error = git_iterator_advance(&iterator_item, iterator)) < 0) - goto done; - - if ((error = cb(iterator_item, data)) != 0) - goto done; - } - -done: - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -int git_iterator_walk( - git_iterator **iterators, - size_t cnt, - git_iterator_walk_cb cb, - void *data) -{ - const git_index_entry **iterator_item; /* next in each iterator */ - const git_index_entry **cur_items; /* current path in each iter */ - const git_index_entry *first_match; - size_t i, j; - int error = 0; - - iterator_item = git__calloc(cnt, sizeof(git_index_entry *)); - cur_items = git__calloc(cnt, sizeof(git_index_entry *)); - - GIT_ERROR_CHECK_ALLOC(iterator_item); - GIT_ERROR_CHECK_ALLOC(cur_items); - - /* Set up the iterators */ - for (i = 0; i < cnt; i++) { - error = git_iterator_current(&iterator_item[i], iterators[i]); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - - while (true) { - for (i = 0; i < cnt; i++) - cur_items[i] = NULL; - - first_match = NULL; - - /* Find the next path(s) to consume from each iterator */ - for (i = 0; i < cnt; i++) { - if (iterator_item[i] == NULL) - continue; - - if (first_match == NULL) { - first_match = iterator_item[i]; - cur_items[i] = iterator_item[i]; - } else { - int path_diff = git_index_entry_cmp(iterator_item[i], first_match); - - if (path_diff < 0) { - /* Found an index entry that sorts before the one we're - * looking at. Forget that we've seen the other and - * look at the other iterators for this path. - */ - for (j = 0; j < i; j++) - cur_items[j] = NULL; - - first_match = iterator_item[i]; - cur_items[i] = iterator_item[i]; - } else if (path_diff == 0) { - cur_items[i] = iterator_item[i]; - } - } - } - - if (first_match == NULL) - break; - - if ((error = cb(cur_items, data)) != 0) - goto done; - - /* Advance each iterator that participated */ - for (i = 0; i < cnt; i++) { - if (cur_items[i] == NULL) - continue; - - error = git_iterator_advance(&iterator_item[i], iterators[i]); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - } - -done: - git__free((git_index_entry **)iterator_item); - git__free((git_index_entry **)cur_items); - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} diff --git a/src/iterator.h b/src/iterator.h deleted file mode 100644 index 6bb8489d0..000000000 --- a/src/iterator.h +++ /dev/null @@ -1,322 +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_iterator_h__ -#define INCLUDE_iterator_h__ - -#include "common.h" - -#include "git2/index.h" -#include "vector.h" -#include "str.h" -#include "ignore.h" - -typedef struct git_iterator git_iterator; - -typedef enum { - 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 */ - GIT_ITERATOR_IGNORE_CASE = (1u << 0), - /** force case sensitivity for entry sort order */ - GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1), - /** return tree items in addition to blob items */ - GIT_ITERATOR_INCLUDE_TREES = (1u << 2), - /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ - GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), - /** convert precomposed unicode to decomposed unicode */ - GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), - /** never convert precomposed unicode to decomposed unicode */ - GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5), - /** include conflicts */ - GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6), - /** descend into symlinked directories */ - GIT_ITERATOR_DESCEND_SYMLINKS = (1u << 7), - /** hash files in workdir or filesystem iterators */ - GIT_ITERATOR_INCLUDE_HASH = (1u << 8) -} git_iterator_flag_t; - -typedef enum { - GIT_ITERATOR_STATUS_NORMAL = 0, - GIT_ITERATOR_STATUS_IGNORED = 1, - GIT_ITERATOR_STATUS_EMPTY = 2, - GIT_ITERATOR_STATUS_FILTERED = 3 -} git_iterator_status_t; - -typedef struct { - const char *start; - const char *end; - - /* paths to include in the iterator (literal). if set, any paths not - * listed here will be excluded from iteration. - */ - git_strarray pathlist; - - /* flags, from above */ - unsigned int flags; -} git_iterator_options; - -#define GIT_ITERATOR_OPTIONS_INIT {0} - -typedef struct { - int (*current)(const git_index_entry **, git_iterator *); - int (*advance)(const git_index_entry **, git_iterator *); - int (*advance_into)(const git_index_entry **, git_iterator *); - int (*advance_over)( - const git_index_entry **, git_iterator_status_t *, git_iterator *); - int (*reset)(git_iterator *); - void (*free)(git_iterator *); -} git_iterator_callbacks; - -struct git_iterator { - git_iterator_t type; - git_iterator_callbacks *cb; - - git_repository *repo; - git_index *index; - - char *start; - size_t start_len; - - char *end; - size_t end_len; - - bool started; - bool ended; - git_vector pathlist; - size_t pathlist_walk_idx; - int (*strcomp)(const char *a, const char *b); - int (*strncomp)(const char *a, const char *b, size_t n); - int (*prefixcomp)(const char *str, const char *prefix); - int (*entry_srch)(const void *key, const void *array_member); - size_t stat_calls; - unsigned int flags; -}; - -extern int git_iterator_for_nothing( - git_iterator **out, - git_iterator_options *options); - -/* tree iterators will match the ignore_case value from the index of the - * repository, unless you override with a non-zero flag value - */ -extern int git_iterator_for_tree( - git_iterator **out, - git_tree *tree, - git_iterator_options *options); - -/* index iterators will take the ignore_case value from the index; the - * ignore_case flags are not used - */ -extern int git_iterator_for_index( - git_iterator **out, - git_repository *repo, - git_index *index, - git_iterator_options *options); - -extern int git_iterator_for_workdir_ext( - git_iterator **out, - git_repository *repo, - const char *repo_workdir, - git_index *index, - git_tree *tree, - git_iterator_options *options); - -/* workdir iterators will match the ignore_case value from the index of the - * repository, unless you override with a non-zero flag value - */ -GIT_INLINE(int) git_iterator_for_workdir( - git_iterator **out, - git_repository *repo, - git_index *index, - git_tree *tree, - git_iterator_options *options) -{ - return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options); -} - -/* for filesystem iterators, you have to explicitly pass in the ignore_case - * behavior that you desire - */ -extern int git_iterator_for_filesystem( - git_iterator **out, - const char *root, - git_iterator_options *options); - -extern void git_iterator_free(git_iterator *iter); - -/* Return a git_index_entry structure for the current value the iterator - * is looking at or NULL if the iterator is at the end. - * - * The entry may noy be fully populated. Tree iterators will only have a - * value mode, OID, and path. Workdir iterators will not have an OID (but - * you can use `git_iterator_current_oid()` to calculate it on demand). - * - * You do not need to free the entry. It is still "owned" by the iterator. - * Once you call `git_iterator_advance()` then the old entry is no longer - * guaranteed to be valid - it may be freed or just overwritten in place. - */ -GIT_INLINE(int) git_iterator_current( - const git_index_entry **entry, git_iterator *iter) -{ - return iter->cb->current(entry, iter); -} - -/** - * Advance to the next item for the iterator. - * - * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If - * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree - * item will skip over all the items under that tree. - */ -GIT_INLINE(int) git_iterator_advance( - const git_index_entry **entry, git_iterator *iter) -{ - return iter->cb->advance(entry, iter); -} - -/** - * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set). - * - * git_iterator_advance() steps through all items being iterated over - * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES), - * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next - * sibling of a tree instead of going to the first child of the tree. In - * that case, use this function to advance to the first child of the tree. - * - * If the current item is not a tree, this is a no-op. - * - * For filesystem and working directory iterators, a tree (i.e. directory) - * can be empty. In that case, this function returns GIT_ENOTFOUND and - * does not advance. That can't happen for tree and index iterators. - */ -GIT_INLINE(int) git_iterator_advance_into( - const git_index_entry **entry, git_iterator *iter) -{ - return iter->cb->advance_into(entry, iter); -} - -/* Advance over a directory and check if it contains no files or just - * ignored files. - * - * In a tree or the index, all directories will contain files, but in the - * working directory it is possible to have an empty directory tree or a - * tree that only contains ignored files. Many Git operations treat these - * cases specially. This advances over a directory (presumably an - * untracked directory) but checks during the scan if there are any files - * and any non-ignored files. - */ -GIT_INLINE(int) git_iterator_advance_over( - const git_index_entry **entry, - git_iterator_status_t *status, - git_iterator *iter) -{ - return iter->cb->advance_over(entry, status, iter); -} - -/** - * Go back to the start of the iteration. - */ -GIT_INLINE(int) git_iterator_reset(git_iterator *iter) -{ - return iter->cb->reset(iter); -} - -/** - * Go back to the start of the iteration after updating the `start` and - * `end` pathname boundaries of the iteration. - */ -extern int git_iterator_reset_range( - git_iterator *iter, const char *start, const char *end); - -GIT_INLINE(git_iterator_t) git_iterator_type(git_iterator *iter) -{ - return iter->type; -} - -GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) -{ - return iter->repo; -} - -GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter) -{ - return iter->index; -} - -GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter) -{ - return iter->flags; -} - -GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) -{ - return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); -} - -extern int git_iterator_set_ignore_case( - git_iterator *iter, bool ignore_case); - -extern int git_iterator_current_tree_entry( - const git_tree_entry **entry_out, git_iterator *iter); - -extern int git_iterator_current_parent_tree( - const git_tree **tree_out, git_iterator *iter, size_t depth); - -extern bool git_iterator_current_is_ignored(git_iterator *iter); - -extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); - -/** - * Get full path of the current item from a workdir iterator. This will - * return NULL for a non-workdir iterator. The git_str is still owned by - * the iterator; this is exposed just for efficiency. - */ -extern int git_iterator_current_workdir_path( - git_str **path, git_iterator *iter); - -/** - * Retrieve the index stored in the iterator. - * - * Only implemented for the workdir and index iterators. - */ -extern git_index *git_iterator_index(git_iterator *iter); - -typedef int (*git_iterator_foreach_cb)( - const git_index_entry *entry, - void *data); - -/** - * Walk the given iterator and invoke the callback for each path - * contained in the iterator. - */ -extern int git_iterator_foreach( - git_iterator *iterator, - git_iterator_foreach_cb cb, - void *data); - -typedef int (*git_iterator_walk_cb)( - const git_index_entry **entries, - void *data); - -/** - * Walk the given iterators in lock-step. The given callback will be - * called for each unique path, with the index entry in each iterator - * (or NULL if the given iterator does not contain that path). - */ -extern int git_iterator_walk( - git_iterator **iterators, - size_t cnt, - git_iterator_walk_cb cb, - void *data); - -#endif diff --git a/src/khash.h b/src/khash.h deleted file mode 100644 index c9b7f131f..000000000 --- a/src/khash.h +++ /dev/null @@ -1,615 +0,0 @@ -/* The MIT License - - Copyright (c) 2008, 2009, 2011 by Attractive Chaos - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE 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. -*/ - -/* - An example: - -#include "khash.h" -KHASH_MAP_INIT_INT(32, char) -int main() { - int ret, is_missing; - khiter_t k; - khash_t(32) *h = kh_init(32); - k = kh_put(32, h, 5, &ret); - kh_value(h, k) = 10; - k = kh_get(32, h, 10); - is_missing = (k == kh_end(h)); - k = kh_get(32, h, 5); - kh_del(32, h, k); - for (k = kh_begin(h); k != kh_end(h); ++k) - if (kh_exist(h, k)) kh_value(h, k) = 1; - kh_destroy(32, h); - return 0; -} -*/ - -/* - 2013-05-02 (0.2.8): - - * Use quadratic probing. When the capacity is power of 2, stepping function - i*(i+1)/2 guarantees to traverse each bucket. It is better than double - hashing on cache performance and is more robust than linear probing. - - In theory, double hashing should be more robust than quadratic probing. - However, my implementation is probably not for large hash tables, because - the second hash function is closely tied to the first hash function, - which reduce the effectiveness of double hashing. - - Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php - - 2011-12-29 (0.2.7): - - * Minor code clean up; no actual effect. - - 2011-09-16 (0.2.6): - - * The capacity is a power of 2. This seems to dramatically improve the - speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - - - http://code.google.com/p/ulib/ - - http://nothings.org/computer/judy/ - - * Allow to optionally use linear probing which usually has better - performance for random input. Double hashing is still the default as it - is more robust to certain non-random input. - - * Added Wang's integer hash function (not used by default). This hash - function is more robust to certain non-random input. - - 2011-02-14 (0.2.5): - - * Allow to declare global functions. - - 2009-09-26 (0.2.4): - - * Improve portability - - 2008-09-19 (0.2.3): - - * Corrected the example - * Improved interfaces - - 2008-09-11 (0.2.2): - - * Improved speed a little in kh_put() - - 2008-09-10 (0.2.1): - - * Added kh_clear() - * Fixed a compiling error - - 2008-09-02 (0.2.0): - - * Changed to token concatenation which increases flexibility. - - 2008-08-31 (0.1.2): - - * Fixed a bug in kh_get(), which has not been tested previously. - - 2008-08-31 (0.1.1): - - * Added destructor -*/ - - -#ifndef __AC_KHASH_H -#define __AC_KHASH_H - -/*! - @header - - Generic hash table library. - */ - -#define AC_VERSION_KHASH_H "0.2.8" - -#include -#include -#include - -/* compiler specific configuration */ - -typedef uint32_t khint32_t; -typedef uint64_t khint64_t; - -#ifndef kh_inline -#ifdef _MSC_VER -#define kh_inline __inline -#elif defined(__GNUC__) -#define kh_inline __inline__ -#else -#define kh_inline -#endif -#endif /* kh_inline */ - -typedef khint32_t khint_t; -typedef khint_t khiter_t; - -#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) -#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) -#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) -#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) -#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) -#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) -#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) - -#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) - -#ifndef kroundup32 -#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -#endif - -#ifndef kcalloc -#define kcalloc(N,Z) calloc(N,Z) -#endif -#ifndef kmalloc -#define kmalloc(Z) malloc(Z) -#endif -#ifndef krealloc -#define krealloc(P,Z) realloc(P,Z) -#endif -#ifndef kreallocarray -#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) -#endif -#ifndef kfree -#define kfree(P) free(P) -#endif - -static const double __ac_HASH_UPPER = 0.77; - -#define __KHASH_TYPE(name, khkey_t, khval_t) \ - typedef struct kh_##name##_s { \ - khint_t n_buckets, size, n_occupied, upper_bound; \ - khint32_t *flags; \ - khkey_t *keys; \ - khval_t *vals; \ - } kh_##name##_t; - -#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ - extern kh_##name##_t *kh_init_##name(void); \ - extern void kh_destroy_##name(kh_##name##_t *h); \ - extern void kh_clear_##name(kh_##name##_t *h); \ - extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ - extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ - extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ - extern void kh_del_##name(kh_##name##_t *h, khint_t x); - -#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - SCOPE kh_##name##_t *kh_init_##name(void) { \ - return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ - } \ - SCOPE void kh_destroy_##name(kh_##name##_t *h) \ - { \ - if (h) { \ - kfree((void *)h->keys); kfree(h->flags); \ - kfree((void *)h->vals); \ - kfree(h); \ - } \ - } \ - SCOPE void kh_clear_##name(kh_##name##_t *h) \ - { \ - if (h && h->flags) { \ - memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ - h->size = h->n_occupied = 0; \ - } \ - } \ - SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ - { \ - if (h->n_buckets) { \ - khint_t k, i, last, mask, step = 0; \ - mask = h->n_buckets - 1; \ - k = __hash_func(key); i = k & mask; \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - i = (i + (++step)) & mask; \ - if (i == last) return h->n_buckets; \ - } \ - return __ac_iseither(h->flags, i)? h->n_buckets : i; \ - } else return 0; \ - } \ - SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ - { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ - khint32_t *new_flags = 0; \ - khint_t j = 1; \ - { \ - kroundup32(new_n_buckets); \ - if (new_n_buckets < 4) new_n_buckets = 4; \ - if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ - else { /* hash table size to be changed (shrink or expand); rehash */ \ - new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ - if (!new_flags) return -1; \ - memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (h->n_buckets < new_n_buckets) { /* expand */ \ - khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (!new_keys) { kfree(new_flags); return -1; } \ - h->keys = new_keys; \ - if (kh_is_map) { \ - khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - if (!new_vals) { kfree(new_flags); return -1; } \ - h->vals = new_vals; \ - } \ - } /* otherwise shrink */ \ - } \ - } \ - if (j) { /* rehashing is needed */ \ - for (j = 0; j != h->n_buckets; ++j) { \ - if (__ac_iseither(h->flags, j) == 0) { \ - khkey_t key = h->keys[j]; \ - khval_t val; \ - khint_t new_mask; \ - new_mask = new_n_buckets - 1; \ - if (kh_is_map) val = h->vals[j]; \ - __ac_set_isdel_true(h->flags, j); \ - while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ - khint_t k, i, step = 0; \ - k = __hash_func(key); \ - i = k & new_mask; \ - while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ - __ac_set_isempty_false(new_flags, i); \ - if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ - { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ - if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ - __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ - } else { /* write the element and jump out of the loop */ \ - h->keys[i] = key; \ - if (kh_is_map) h->vals[i] = val; \ - break; \ - } \ - } \ - } \ - } \ - if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ - h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - } \ - kfree(h->flags); /* free the working space */ \ - h->flags = new_flags; \ - h->n_buckets = new_n_buckets; \ - h->n_occupied = h->size; \ - h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ - } \ - return 0; \ - } \ - SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ - { \ - khint_t x; \ - if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ - if (h->n_buckets > (h->size<<1)) { \ - if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ - *ret = -1; return h->n_buckets; \ - } \ - } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ - *ret = -1; return h->n_buckets; \ - } \ - } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ - { \ - khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ - x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ - if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ - else { \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - if (__ac_isdel(h->flags, i)) site = i; \ - i = (i + (++step)) & mask; \ - if (i == last) { x = site; break; } \ - } \ - if (x == h->n_buckets) { \ - if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ - else x = i; \ - } \ - } \ - } \ - if (__ac_isempty(h->flags, x)) { /* not present at all */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; ++h->n_occupied; \ - *ret = 1; \ - } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; \ - *ret = 2; \ - } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ - return x; \ - } \ - SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ - { \ - if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ - __ac_set_isdel_true(h->flags, x); \ - --h->size; \ - } \ - } - -#define KHASH_DECLARE(name, khkey_t, khval_t) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_PROTOTYPES(name, khkey_t, khval_t) - -#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -/* --- BEGIN OF HASH FUNCTIONS --- */ - -/*! @function - @abstract Integer hash function - @param key The integer [khint32_t] - @return The hash value [khint_t] - */ -#define kh_int_hash_func(key) (khint32_t)(key) -/*! @function - @abstract Integer comparison function - */ -#define kh_int_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract 64-bit integer hash function - @param key The integer [khint64_t] - @return The hash value [khint_t] - */ -#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) -/*! @function - @abstract 64-bit integer comparison function - */ -#define kh_int64_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract const char* hash function - @param s Pointer to a null terminated string - @return The hash value - */ -static kh_inline khint_t __ac_X31_hash_string(const char *s) -{ - khint_t h = (khint_t)*s; - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; - return h; -} -/*! @function - @abstract Another interface to const char* hash function - @param key Pointer to a null terminated string [const char*] - @return The hash value [khint_t] - */ -#define kh_str_hash_func(key) __ac_X31_hash_string(key) -/*! @function - @abstract Const char* comparison function - */ -#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) - -static kh_inline khint_t __ac_Wang_hash(khint_t key) -{ - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} -#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) - -/* --- END OF HASH FUNCTIONS --- */ - -/* Other convenient macros... */ - -/*! - @abstract Type of the hash table. - @param name Name of the hash table [symbol] - */ -#define khash_t(name) kh_##name##_t - -/*! @function - @abstract Initiate a hash table. - @param name Name of the hash table [symbol] - @return Pointer to the hash table [khash_t(name)*] - */ -#define kh_init(name) kh_init_##name() - -/*! @function - @abstract Destroy a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_destroy(name, h) kh_destroy_##name(h) - -/*! @function - @abstract Reset a hash table without deallocating memory. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_clear(name, h) kh_clear_##name(h) - -/*! @function - @abstract Resize a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param s New size [khint_t] - */ -#define kh_resize(name, h, s) kh_resize_##name(h, s) - -/*! @function - @abstract Insert a key to the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @param r Extra return code: -1 if the operation failed; - 0 if the key is present in the hash table; - 1 if the bucket is empty (never used); 2 if the element in - the bucket has been deleted [int*] - @return Iterator to the inserted element [khint_t] - */ -#define kh_put(name, h, k, r) kh_put_##name(h, k, r) - -/*! @function - @abstract Retrieve a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] - */ -#define kh_get(name, h, k) kh_get_##name(h, k) - -/*! @function - @abstract Remove a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Iterator to the element to be deleted [khint_t] - */ -#define kh_del(name, h, k) kh_del_##name(h, k) - -/*! @function - @abstract Test whether a bucket contains data. - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return 1 if containing data; 0 otherwise [int] - */ -#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) - -/*! @function - @abstract Get key given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Key [type of keys] - */ -#define kh_key(h, x) ((h)->keys[x]) - -/*! @function - @abstract Get value given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Value [type of values] - @discussion For hash sets, calling this results in segfault. - */ -#define kh_val(h, x) ((h)->vals[x]) - -/*! @function - @abstract Alias of kh_val() - */ -#define kh_value(h, x) ((h)->vals[x]) - -/*! @function - @abstract Get the start iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The start iterator [khint_t] - */ -#define kh_begin(h) (khint_t)(0) - -/*! @function - @abstract Get the end iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The end iterator [khint_t] - */ -#define kh_end(h) ((h)->n_buckets) - -/*! @function - @abstract Get the number of elements in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of elements in the hash table [khint_t] - */ -#define kh_size(h) ((h)->size) - -/*! @function - @abstract Get the number of buckets in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of buckets in the hash table [khint_t] - */ -#define kh_n_buckets(h) ((h)->n_buckets) - -/*! @function - @abstract Iterate over the entries in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param kvar Variable to which key will be assigned - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (kvar) = kh_key(h,__i); \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/*! @function - @abstract Iterate over the values in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach_value(h, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/* More convenient interfaces */ - -/*! @function - @abstract Instantiate a hash set containing integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT(name) \ - KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT(name, khval_t) \ - KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT64(name) \ - KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT64(name, khval_t) \ - KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) - -typedef const char *kh_cstr_t; -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_STR(name) \ - KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_STR(name, khval_t) \ - KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) - -#endif /* __AC_KHASH_H */ diff --git a/src/libgit2.c b/src/libgit2.c deleted file mode 100644 index c99d5b942..000000000 --- a/src/libgit2.c +++ /dev/null @@ -1,420 +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 "libgit2.h" - -#include -#include "alloc.h" -#include "buf.h" -#include "cache.h" -#include "common.h" -#include "filter.h" -#include "hash.h" -#include "index.h" -#include "merge_driver.h" -#include "pool.h" -#include "mwindow.h" -#include "object.h" -#include "odb.h" -#include "rand.h" -#include "refs.h" -#include "runtime.h" -#include "sysdir.h" -#include "thread.h" -#include "threadstate.h" -#include "git2/global.h" -#include "streams/registry.h" -#include "streams/mbedtls.h" -#include "streams/openssl.h" -#include "transports/smart.h" -#include "transports/http.h" -#include "transports/ssh.h" - -#ifdef GIT_WIN32 -# include "win32/w32_leakcheck.h" -#endif - -/* Declarations for tuneable settings */ -extern size_t git_mwindow__window_size; -extern size_t git_mwindow__mapped_limit; -extern size_t git_mwindow__file_limit; -extern size_t git_indexer__max_objects; -extern bool git_disable_pack_keep_file_checks; -extern int git_odb__packed_priority; -extern int git_odb__loose_priority; - -char *git__user_agent; -char *git__ssl_ciphers; - -static void libgit2_settings_global_shutdown(void) -{ - git__free(git__user_agent); - git__free(git__ssl_ciphers); - git_repository__free_extensions(); -} - -static int git_libgit2_settings_global_init(void) -{ - return git_runtime_shutdown_register(libgit2_settings_global_shutdown); -} - -int git_libgit2_init(void) -{ - static git_runtime_init_fn init_fns[] = { -#ifdef GIT_WIN32 - git_win32_leakcheck_global_init, -#endif - git_allocator_global_init, - git_threadstate_global_init, - git_threads_global_init, - git_rand_global_init, - git_hash_global_init, - git_sysdir_global_init, - git_filter_global_init, - git_merge_driver_global_init, - git_transport_ssh_global_init, - git_stream_registry_global_init, - git_openssl_stream_global_init, - git_mbedtls_stream_global_init, - git_mwindow_global_init, - git_pool_global_init, - git_libgit2_settings_global_init - }; - - return git_runtime_init(init_fns, ARRAY_SIZE(init_fns)); -} - -int git_libgit2_init_count(void) -{ - return git_runtime_init_count(); -} - -int git_libgit2_shutdown(void) -{ - return git_runtime_shutdown(); -} - -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) -{ - return 0 -#ifdef GIT_THREADS - | GIT_FEATURE_THREADS -#endif -#ifdef GIT_HTTPS - | GIT_FEATURE_HTTPS -#endif -#if defined(GIT_SSH) - | GIT_FEATURE_SSH -#endif -#if defined(GIT_USE_NSEC) - | GIT_FEATURE_NSEC -#endif - ; -} - -static int config_level_to_sysdir(int *out, int config_level) -{ - switch (config_level) { - case GIT_CONFIG_LEVEL_SYSTEM: - *out = GIT_SYSDIR_SYSTEM; - return 0; - case GIT_CONFIG_LEVEL_XDG: - *out = GIT_SYSDIR_XDG; - return 0; - case GIT_CONFIG_LEVEL_GLOBAL: - *out = GIT_SYSDIR_GLOBAL; - return 0; - case GIT_CONFIG_LEVEL_PROGRAMDATA: - *out = GIT_SYSDIR_PROGRAMDATA; - return 0; - default: - break; - } - - git_error_set( - GIT_ERROR_INVALID, "invalid config path selector %d", config_level); - return -1; -} - -const char *git_libgit2__user_agent(void) -{ - return git__user_agent; -} - -const char *git_libgit2__ssl_ciphers(void) -{ - return git__ssl_ciphers; -} - -int git_libgit2_opts(int key, ...) -{ - int error = 0; - va_list ap; - - va_start(ap, key); - - switch (key) { - case GIT_OPT_SET_MWINDOW_SIZE: - git_mwindow__window_size = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_SIZE: - *(va_arg(ap, size_t *)) = git_mwindow__window_size; - break; - - case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: - git_mwindow__mapped_limit = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: - *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; - break; - - case GIT_OPT_SET_MWINDOW_FILE_LIMIT: - git_mwindow__file_limit = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_FILE_LIMIT: - *(va_arg(ap, size_t *)) = git_mwindow__file_limit; - break; - - case GIT_OPT_GET_SEARCH_PATH: - { - int sysdir = va_arg(ap, int); - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - int level; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = config_level_to_sysdir(&level, sysdir)) < 0 || - (error = git_sysdir_get(&tmp, level)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_SEARCH_PATH: - { - int level; - - if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0) - error = git_sysdir_set(level, va_arg(ap, const char *)); - } - break; - - case GIT_OPT_SET_CACHE_OBJECT_LIMIT: - { - git_object_t type = (git_object_t)va_arg(ap, int); - size_t size = va_arg(ap, size_t); - error = git_cache_set_max_object_size(type, size); - break; - } - - case GIT_OPT_SET_CACHE_MAX_SIZE: - git_cache__max_storage = va_arg(ap, ssize_t); - break; - - case GIT_OPT_ENABLE_CACHING: - git_cache__enabled = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_CACHED_MEMORY: - *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; - *(va_arg(ap, ssize_t *)) = git_cache__max_storage; - break; - - case GIT_OPT_GET_TEMPLATE_PATH: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_TEMPLATE_PATH: - error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *)); - break; - - case GIT_OPT_SET_SSL_CERT_LOCATIONS: -#ifdef GIT_OPENSSL - { - const char *file = va_arg(ap, const char *); - const char *path = va_arg(ap, const char *); - error = git_openssl__set_cert_location(file, path); - } -#elif defined(GIT_MBEDTLS) - { - const char *file = va_arg(ap, const char *); - const char *path = va_arg(ap, const char *); - error = git_mbedtls__set_cert_location(file, path); - } -#else - git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations"); - error = -1; -#endif - break; - case GIT_OPT_SET_USER_AGENT: - git__free(git__user_agent); - git__user_agent = git__strdup(va_arg(ap, const char *)); - if (!git__user_agent) { - git_error_set_oom(); - error = -1; - } - - break; - - case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: - git_object__strict_input_validation = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: - git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_SSL_CIPHERS: -#if (GIT_OPENSSL || GIT_MBEDTLS) - { - git__free(git__ssl_ciphers); - git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); - if (!git__ssl_ciphers) { - git_error_set_oom(); - error = -1; - } - } -#else - git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); - error = -1; -#endif - break; - - case GIT_OPT_GET_USER_AGENT: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_str_puts(&str, git__user_agent)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_ENABLE_OFS_DELTA: - git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_FSYNC_GITDIR: - git_repository__fsync_gitdir = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_WINDOWS_SHAREMODE: -#ifdef GIT_WIN32 - *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; -#endif - break; - - case GIT_OPT_SET_WINDOWS_SHAREMODE: -#ifdef GIT_WIN32 - git_win32__createfile_sharemode = va_arg(ap, unsigned long); -#endif - break; - - case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: - git_odb__strict_hash_verification = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_ALLOCATOR: - error = git_allocator_setup(va_arg(ap, git_allocator *)); - break; - - case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: - git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_PACK_MAX_OBJECTS: - git_indexer__max_objects = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_PACK_MAX_OBJECTS: - *(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; - - case GIT_OPT_SET_ODB_PACKED_PRIORITY: - git_odb__packed_priority = va_arg(ap, int); - break; - - case GIT_OPT_SET_ODB_LOOSE_PRIORITY: - git_odb__loose_priority = va_arg(ap, int); - break; - - case GIT_OPT_SET_EXTENSIONS: - { - const char **extensions = va_arg(ap, const char **); - size_t len = va_arg(ap, size_t); - error = git_repository__set_extensions(extensions, len); - } - break; - - case GIT_OPT_GET_EXTENSIONS: - { - git_strarray *out = va_arg(ap, git_strarray *); - char **extensions; - size_t len; - - if ((error = git_repository__extensions(&extensions, &len)) < 0) - break; - - out->strings = extensions; - out->count = len; - } - break; - - case GIT_OPT_GET_OWNER_VALIDATION: - *(va_arg(ap, int *)) = git_repository__validate_ownership; - break; - - case GIT_OPT_SET_OWNER_VALIDATION: - git_repository__validate_ownership = (va_arg(ap, int) != 0); - break; - - default: - git_error_set(GIT_ERROR_INVALID, "invalid option key"); - error = -1; - } - - va_end(ap); - - return error; -} diff --git a/src/libgit2.h b/src/libgit2.h deleted file mode 100644 index a898367ae..000000000 --- a/src/libgit2.h +++ /dev/null @@ -1,15 +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_libgit2_h__ -#define INCLUDE_libgit2_h__ - -extern int git_libgit2_init_count(void); - -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); - -#endif diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt new file mode 100644 index 000000000..0c7ddddba --- /dev/null +++ b/src/libgit2/CMakeLists.txt @@ -0,0 +1,131 @@ +# libgit2: the shared library: this CMakeLists.txt compiles the core +# git library functionality. + +add_library(libgit2 OBJECT) +set_target_properties(libgit2 PROPERTIES C_STANDARD 90) +set_target_properties(libgit2 PROPERTIES C_EXTENSIONS OFF) + +include(PkgBuildConfig) + +set(LIBGIT2_INCLUDES + "${PROJECT_BINARY_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/libgit2" + "${PROJECT_SOURCE_DIR}/src/util" + "${PROJECT_SOURCE_DIR}/include") + +if(WIN32 AND EMBED_SSH_PATH) + file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") + list(SORT SRC_SSH) + target_sources(libgit2 PRIVATE ${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() + +# Collect sourcefiles +file(GLOB SRC_H + "${PROJECT_SOURCE_DIR}/include/git2.h" + "${PROJECT_SOURCE_DIR}/include/git2/*.h" + "${PROJECT_SOURCE_DIR}/include/git2/sys/*.h") +list(SORT SRC_H) +target_sources(libgit2 PRIVATE ${SRC_H}) + +file(GLOB SRC_GIT2 *.c *.h + streams/*.c streams/*.h + transports/*.c transports/*.h + xdiff/*.c xdiff/*.h) +list(SORT SRC_GIT2) +target_sources(libgit2 PRIVATE ${SRC_GIT2}) + +if(WIN32 AND NOT CYGWIN) + # Add resource information on Windows + set(SRC_RC "git2.rc") +endif() + +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/xemit.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xmerge.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS -WX-) +else() + set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") + set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") + set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") + set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") + set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") +endif() + +ide_split_sources(libgit2) +list(APPEND LIBGIT2_OBJECTS $ $ ${LIBGIT2_DEPENDENCY_OBJECTS}) + +target_include_directories(libgit2 PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_include_directories(libgit2 SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) + +set(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) +set(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) +set(LIBGIT2_DEPENDENCY_INCLUDES ${LIBGIT2_DEPENDENCY_INCLUDES} PARENT_SCOPE) +set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) +set(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE) +set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) + +# +# Compile and link libgit2 +# + +add_library(libgit2package ${SRC_RC} ${LIBGIT2_OBJECTS}) +target_link_libraries(libgit2package ${LIBGIT2_SYSTEM_LIBS}) + +set_target_properties(libgit2package PROPERTIES C_STANDARD 90) +set_target_properties(libgit2package PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(libgit2package PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(libgit2package PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) +# Win64+MSVC+static libs = linker error +if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) + set_target_properties(libgit2package PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") +endif() + +ide_split_sources(libgit2package) + +if(SONAME) + set_target_properties(libgit2package PROPERTIES VERSION ${libgit2_VERSION}) + set_target_properties(libgit2package PROPERTIES SOVERSION "${libgit2_VERSION_MAJOR}.${libgit2_VERSION_MINOR}") + if(LIBGIT2_FILENAME) + target_compile_definitions(libgit2package PRIVATE LIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") + set_target_properties(libgit2package PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) + elseif(DEFINED LIBGIT2_PREFIX) + set_target_properties(libgit2package PROPERTIES PREFIX "${LIBGIT2_PREFIX}") + endif() +endif() + +pkg_build_config(NAME libgit2 + VERSION ${libgit2_VERSION} + DESCRIPTION "The git library, take 2" + LIBS_SELF git2 + PRIVATE_LIBS ${LIBGIT2_PC_LIBS} + REQUIRES ${LIBGIT2_PC_REQUIRES}) + +if(MSVC_IDE) + # Precompiled headers + set_target_properties(libgit2package PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") +endif() + +# Install +install(TARGETS libgit2package + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/git2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(FILES ${PROJECT_SOURCE_DIR}/include/git2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/libgit2/annotated_commit.c b/src/libgit2/annotated_commit.c new file mode 100644 index 000000000..e48947679 --- /dev/null +++ b/src/libgit2/annotated_commit.c @@ -0,0 +1,240 @@ +/* + * 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 "annotated_commit.h" + +#include "refs.h" +#include "cache.h" + +#include "git2/commit.h" +#include "git2/refs.h" +#include "git2/repository.h" +#include "git2/annotated_commit.h" +#include "git2/revparse.h" +#include "git2/tree.h" +#include "git2/index.h" + +static int annotated_commit_init( + git_annotated_commit **out, + git_commit *commit, + const char *description) +{ + git_annotated_commit *annotated_commit; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(commit); + + *out = NULL; + + annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); + GIT_ERROR_CHECK_ALLOC(annotated_commit); + + annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; + + if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0) + goto done; + + git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); + annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; + + if (!description) + description = annotated_commit->id_str; + + annotated_commit->description = git__strdup(description); + GIT_ERROR_CHECK_ALLOC(annotated_commit->description); + +done: + if (!error) + *out = annotated_commit; + + return error; +} + +static int annotated_commit_init_from_id( + git_annotated_commit **out, + git_repository *repo, + const git_oid *id, + const char *description) +{ + git_commit *commit = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(id); + + *out = NULL; + + if ((error = git_commit_lookup(&commit, repo, id)) < 0) + goto done; + + error = annotated_commit_init(out, commit, description); + +done: + git_commit_free(commit); + return error; +} + +int git_annotated_commit_lookup( + git_annotated_commit **out, + git_repository *repo, + const git_oid *id) +{ + return annotated_commit_init_from_id(out, repo, id, NULL); +} + +int git_annotated_commit_from_commit( + git_annotated_commit **out, + git_commit *commit) +{ + return annotated_commit_init(out, commit, NULL); +} + +int git_annotated_commit_from_revspec( + git_annotated_commit **out, + git_repository *repo, + const char *revspec) +{ + git_object *obj, *commit; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(revspec); + + if ((error = git_revparse_single(&obj, repo, revspec)) < 0) + return error; + + if ((error = git_object_peel(&commit, obj, GIT_OBJECT_COMMIT))) { + git_object_free(obj); + return error; + } + + error = annotated_commit_init(out, (git_commit *)commit, revspec); + + git_object_free(obj); + git_object_free(commit); + + return error; +} + +int git_annotated_commit_from_ref( + git_annotated_commit **out, + git_repository *repo, + const git_reference *ref) +{ + git_object *peeled; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ref); + + *out = NULL; + + if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_COMMIT)) < 0) + return error; + + error = annotated_commit_init_from_id(out, + repo, + git_object_id(peeled), + git_reference_name(ref)); + + if (!error) { + (*out)->ref_name = git__strdup(git_reference_name(ref)); + GIT_ERROR_CHECK_ALLOC((*out)->ref_name); + } + + git_object_free(peeled); + return error; +} + +int git_annotated_commit_from_head( + git_annotated_commit **out, + git_repository *repo) +{ + git_reference *head; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) + return -1; + + error = git_annotated_commit_from_ref(out, repo, head); + + git_reference_free(head); + return error; +} + +int git_annotated_commit_from_fetchhead( + git_annotated_commit **out, + git_repository *repo, + const char *branch_name, + const char *remote_url, + const git_oid *id) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(branch_name); + GIT_ASSERT_ARG(remote_url); + GIT_ASSERT_ARG(id); + + if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0) + return -1; + + (*out)->ref_name = git__strdup(branch_name); + GIT_ERROR_CHECK_ALLOC((*out)->ref_name); + + (*out)->remote_url = git__strdup(remote_url); + GIT_ERROR_CHECK_ALLOC((*out)->remote_url); + + return 0; +} + + +const git_oid *git_annotated_commit_id( + const git_annotated_commit *annotated_commit) +{ + GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); + return git_commit_id(annotated_commit->commit); +} + +const char *git_annotated_commit_ref( + const git_annotated_commit *annotated_commit) +{ + GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); + return annotated_commit->ref_name; +} + +void git_annotated_commit_free(git_annotated_commit *annotated_commit) +{ + if (annotated_commit == NULL) + return; + + switch (annotated_commit->type) { + case GIT_ANNOTATED_COMMIT_REAL: + git_commit_free(annotated_commit->commit); + git_tree_free(annotated_commit->tree); + git__free((char *)annotated_commit->description); + git__free((char *)annotated_commit->ref_name); + git__free((char *)annotated_commit->remote_url); + break; + case GIT_ANNOTATED_COMMIT_VIRTUAL: + git_index_free(annotated_commit->index); + git_array_clear(annotated_commit->parents); + break; + default: + abort(); + } + + git__free(annotated_commit); +} diff --git a/src/libgit2/annotated_commit.h b/src/libgit2/annotated_commit.h new file mode 100644 index 000000000..444a2ed10 --- /dev/null +++ b/src/libgit2/annotated_commit.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_annotated_commit_h__ +#define INCLUDE_annotated_commit_h__ + +#include "common.h" + +#include "oidarray.h" + +#include "git2/oid.h" + +typedef enum { + GIT_ANNOTATED_COMMIT_REAL = 1, + GIT_ANNOTATED_COMMIT_VIRTUAL = 2 +} git_annotated_commit_t; + +/** + * Internal structure for merge inputs. An annotated commit is generally + * "real" and backed by an actual commit in the repository, but merge will + * internally create "virtual" commits that are in-memory intermediate + * commits backed by an index. + */ +struct git_annotated_commit { + git_annotated_commit_t type; + + /* real commit */ + git_commit *commit; + git_tree *tree; + + /* virtual commit structure */ + git_index *index; + git_array_oid_t parents; + + /* how this commit was looked up */ + const char *description; + + const char *ref_name; + const char *remote_url; + + char id_str[GIT_OID_HEXSZ+1]; +}; + +extern int git_annotated_commit_from_head(git_annotated_commit **out, + git_repository *repo); +extern int git_annotated_commit_from_commit(git_annotated_commit **out, + git_commit *commit); + +#endif diff --git a/src/libgit2/apply.c b/src/libgit2/apply.c new file mode 100644 index 000000000..18304da4d --- /dev/null +++ b/src/libgit2/apply.c @@ -0,0 +1,898 @@ +/* + * 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/apply.h" +#include "git2/patch.h" +#include "git2/filter.h" +#include "git2/blob.h" +#include "git2/index.h" +#include "git2/checkout.h" +#include "git2/repository.h" +#include "array.h" +#include "patch.h" +#include "futils.h" +#include "delta.h" +#include "zstream.h" +#include "reader.h" +#include "index.h" +#include "apply.h" + +typedef struct { + /* The lines that we allocate ourself are allocated out of the pool. + * (Lines may have been allocated out of the diff.) + */ + git_pool pool; + 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, + size_t in_len, + size_t in_offset) +{ + out->content = in; + out->content_len = in_len; + out->content_offset = in_offset; +} + +#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT } + +static int patch_image_init_fromstr( + patch_image *out, const char *in, size_t in_len) +{ + git_diff_line *line; + const char *start, *end; + + memset(out, 0x0, sizeof(patch_image)); + + if (git_pool_init(&out->pool, sizeof(git_diff_line)) < 0) + return -1; + + if (!in_len) + return 0; + + for (start = in; start < in + in_len; start = end) { + end = memchr(start, '\n', in_len - (start - in)); + + if (end == NULL) + end = in + in_len; + + else if (end < in + in_len) + end++; + + line = git_pool_mallocz(&out->pool, 1); + GIT_ERROR_CHECK_ALLOC(line); + + if (git_vector_insert(&out->lines, line) < 0) + return -1; + + patch_line_init(line, start, (end - start), (start - in)); + } + + return 0; +} + +static void patch_image_free(patch_image *image) +{ + if (image == NULL) + return; + + git_pool_clear(&image->pool); + git_vector_free(&image->lines); +} + +static bool match_hunk( + patch_image *image, + patch_image *preimage, + size_t linenum) +{ + bool match = 0; + size_t i; + + /* Ensure this hunk is within the image boundaries. */ + if (git_vector_length(&preimage->lines) + linenum > + git_vector_length(&image->lines)) + return 0; + + match = 1; + + /* Check exact match. */ + for (i = 0; i < git_vector_length(&preimage->lines); i++) { + git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); + git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); + + if (preimage_line->content_len != image_line->content_len || + memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { + match = 0; + break; + } + } + + return match; +} + +static bool find_hunk_linenum( + size_t *out, + patch_image *image, + patch_image *preimage, + size_t linenum) +{ + size_t max = git_vector_length(&image->lines); + bool match; + + if (linenum > max) + linenum = max; + + match = match_hunk(image, preimage, linenum); + + *out = linenum; + return match; +} + +static int update_hunk( + patch_image *image, + size_t linenum, + patch_image *preimage, + patch_image *postimage) +{ + size_t postlen = git_vector_length(&postimage->lines); + size_t prelen = git_vector_length(&preimage->lines); + size_t i; + int error = 0; + + if (postlen > prelen) + error = git_vector_insert_null( + &image->lines, linenum, (postlen - prelen)); + else if (prelen > postlen) + error = git_vector_remove_range( + &image->lines, linenum, (prelen - postlen)); + + if (error) { + git_error_set_oom(); + return -1; + } + + for (i = 0; i < git_vector_length(&postimage->lines); i++) { + image->lines.contents[linenum + i] = + git_vector_get(&postimage->lines, i); + } + + return 0; +} + +typedef struct { + git_apply_options opts; + size_t skipped_new_lines; + size_t skipped_old_lines; +} apply_hunks_ctx; + +static int apply_hunk( + patch_image *image, + git_patch *patch, + git_patch_hunk *hunk, + apply_hunks_ctx *ctx) +{ + patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; + size_t line_num, i; + int error = 0; + + if (ctx->opts.hunk_cb) { + error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload); + + if (error) { + if (error > 0) { + ctx->skipped_new_lines += hunk->hunk.new_lines; + ctx->skipped_old_lines += hunk->hunk.old_lines; + error = 0; + } + + goto done; + } + } + + 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), *prev; + + if (!line) { + error = apply_err("preimage does not contain line %"PRIuZ, linenum); + 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; + } + } + + if (hunk->hunk.new_start) { + line_num = hunk->hunk.new_start - + ctx->skipped_new_lines + + ctx->skipped_old_lines - + 1; + } else { + line_num = 0; + } + + if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { + error = apply_err("hunk at line %d did not apply", + hunk->hunk.new_start); + goto done; + } + + error = update_hunk(image, line_num, &preimage, &postimage); + +done: + patch_image_free(&preimage); + patch_image_free(&postimage); + + return error; +} + +static int apply_hunks( + git_str *out, + const char *source, + size_t source_len, + git_patch *patch, + apply_hunks_ctx *ctx) +{ + git_patch_hunk *hunk; + git_diff_line *line; + patch_image image; + size_t i; + int error = 0; + + if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) + goto done; + + git_array_foreach(patch->hunks, i, hunk) { + if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0) + goto done; + } + + git_vector_foreach(&image.lines, i, line) + git_str_put(out, line->content, line->content_len); + +done: + patch_image_free(&image); + + return error; +} + +static int apply_binary_delta( + git_str *out, + const char *source, + size_t source_len, + git_diff_binary_file *binary_file) +{ + git_str inflated = GIT_STR_INIT; + int error = 0; + + /* no diff means identical contents */ + if (binary_file->datalen == 0) + return git_str_put(out, source, source_len); + + error = git_zstream_inflatebuf(&inflated, + binary_file->data, binary_file->datalen); + + if (!error && inflated.size != binary_file->inflatedlen) { + error = apply_err("inflated delta does not match expected length"); + git_str_dispose(out); + } + + if (error < 0) + goto done; + + if (binary_file->type == GIT_DIFF_BINARY_DELTA) { + void *data; + size_t data_len; + + error = git_delta_apply(&data, &data_len, (void *)source, source_len, + (void *)inflated.ptr, inflated.size); + + out->ptr = data; + out->size = data_len; + out->asize = data_len; + } + else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { + git_str_swap(out, &inflated); + } + else { + error = apply_err("unknown binary delta type"); + goto done; + } + +done: + git_str_dispose(&inflated); + return error; +} + +static int apply_binary( + git_str *out, + const char *source, + size_t source_len, + git_patch *patch) +{ + git_str reverse = GIT_STR_INIT; + int error = 0; + + if (!patch->binary.contains_data) { + error = apply_err("patch does not contain binary data"); + goto done; + } + + if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen) + goto done; + + /* first, apply the new_file delta to the given source */ + if ((error = apply_binary_delta(out, source, source_len, + &patch->binary.new_file)) < 0) + goto done; + + /* second, apply the old_file delta to sanity check the result */ + if ((error = apply_binary_delta(&reverse, out->ptr, out->size, + &patch->binary.old_file)) < 0) + goto done; + + /* Verify that the resulting file with the reverse patch applied matches the source file */ + if (source_len != reverse.size || + (source_len && memcmp(source, reverse.ptr, source_len) != 0)) { + error = apply_err("binary patch did not apply cleanly"); + goto done; + } + +done: + if (error < 0) + git_str_dispose(out); + + git_str_dispose(&reverse); + return error; +} + +int git_apply__patch( + git_str *contents_out, + char **filename_out, + unsigned int *mode_out, + const char *source, + size_t source_len, + git_patch *patch, + const git_apply_options *given_opts) +{ + apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT }; + char *filename = NULL; + unsigned int mode = 0; + int error = 0; + + GIT_ASSERT_ARG(contents_out); + GIT_ASSERT_ARG(filename_out); + GIT_ASSERT_ARG(mode_out); + GIT_ASSERT_ARG(source || !source_len); + GIT_ASSERT_ARG(patch); + + if (given_opts) + memcpy(&ctx.opts, given_opts, sizeof(git_apply_options)); + + *filename_out = NULL; + *mode_out = 0; + + if (patch->delta->status != GIT_DELTA_DELETED) { + const git_diff_file *newfile = &patch->delta->new_file; + + filename = git__strdup(newfile->path); + mode = newfile->mode ? + newfile->mode : GIT_FILEMODE_BLOB; + } + + if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) + error = apply_binary(contents_out, source, source_len, patch); + else if (patch->hunks.size) + error = apply_hunks(contents_out, source, source_len, patch, &ctx); + else + error = git_str_put(contents_out, source, source_len); + + if (error) + goto done; + + if (patch->delta->status == GIT_DELTA_DELETED && + git_str_len(contents_out) > 0) { + error = apply_err("removal patch leaves file contents"); + goto done; + } + + *filename_out = filename; + *mode_out = mode; + +done: + if (error < 0) + git__free(filename); + + return error; +} + +static int apply_one( + git_repository *repo, + git_reader *preimage_reader, + git_index *preimage, + git_reader *postimage_reader, + git_index *postimage, + git_diff *diff, + git_strmap *removed_paths, + size_t i, + const git_apply_options *opts) +{ + git_patch *patch = NULL; + git_str pre_contents = GIT_STR_INIT, post_contents = GIT_STR_INIT; + const git_diff_delta *delta; + char *filename = NULL; + unsigned int mode; + git_oid pre_id, post_id; + git_filemode_t pre_filemode; + git_index_entry pre_entry, post_entry; + bool skip_preimage = false; + int error; + + if ((error = git_patch_from_diff(&patch, diff, i)) < 0) + goto done; + + delta = git_patch_get_delta(patch); + + if (opts->delta_cb) { + error = opts->delta_cb(delta, opts->payload); + + if (error) { + if (error > 0) + error = 0; + + goto done; + } + } + + /* + * Ensure that the file has not been deleted or renamed if we're + * applying a modification delta. + */ + if (delta->status != GIT_DELTA_RENAMED && + delta->status != GIT_DELTA_ADDED) { + 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; + } + } + + /* + * We may be applying a second delta to an already seen file. If so, + * use the already modified data in the postimage instead of the + * content from the index or working directory. (Don't do this in + * the case of a rename, which must be specified before additional + * deltas since we apply deltas to the target filename.) + */ + if (delta->status != GIT_DELTA_RENAMED) { + if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, + postimage_reader, delta->old_file.path)) == 0) { + skip_preimage = true; + } else if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } else { + goto done; + } + } + + if (!skip_preimage && delta->status != GIT_DELTA_ADDED) { + error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, + preimage_reader, delta->old_file.path); + + /* ENOTFOUND means the preimage was not found; apply failed. */ + if (error == GIT_ENOTFOUND) + error = GIT_EAPPLYFAIL; + + /* When applying to BOTH, the index did not match the workdir. */ + if (error == GIT_READER_MISMATCH) + error = apply_err("%s: does not match index", delta->old_file.path); + + if (error < 0) + goto done; + + /* + * We need to populate the preimage data structure with the + * contents that we are using as the preimage for this file. + * This allows us to apply patches to files that have been + * modified in the working directory. During checkout, + * we will use this expected preimage as the baseline, and + * limit checkout to only the paths affected by patch + * application. (Without this, we would fail to write the + * postimage contents to any file that had been modified + * from HEAD on-disk, even if the patch application succeeded.) + * Use the contents from the delta where available - some + * fields may not be available, like the old file mode (eg in + * an exact rename situation) so trust the patch parsing to + * validate and use the preimage data in that case. + */ + if (preimage) { + memset(&pre_entry, 0, sizeof(git_index_entry)); + pre_entry.path = delta->old_file.path; + pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode; + git_oid_cpy(&pre_entry.id, &pre_id); + + if ((error = git_index_add(preimage, &pre_entry)) < 0) + goto done; + } + } + + 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_from_buffer(&post_id, repo, + post_contents.ptr, post_contents.size)) < 0) + goto done; + + memset(&post_entry, 0, sizeof(git_index_entry)); + post_entry.path = filename; + post_entry.mode = mode; + git_oid_cpy(&post_entry.id, &post_id); + + if ((error = git_index_add(postimage, &post_entry)) < 0) + goto done; + } + + if (delta->status == GIT_DELTA_RENAMED || + delta->status == GIT_DELTA_DELETED) + 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) + git_strmap_delete(removed_paths, delta->new_file.path); + +done: + git_str_dispose(&pre_contents); + git_str_dispose(&post_contents); + git__free(filename); + git_patch_free(patch); + + return error; +} + +static int apply_deltas( + git_repository *repo, + git_reader *pre_reader, + git_index *preimage, + git_reader *post_reader, + git_index *postimage, + git_diff *diff, + const git_apply_options *opts) +{ + git_strmap *removed_paths; + size_t i; + int error = 0; + + if (git_strmap_new(&removed_paths) < 0) + return -1; + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0) + goto done; + } + +done: + git_strmap_free(removed_paths); + return error; +} + +int git_apply_to_tree( + git_index **out, + git_repository *repo, + git_tree *preimage, + git_diff *diff, + const git_apply_options *given_opts) +{ + git_index *postimage = NULL; + git_reader *pre_reader = NULL, *post_reader = NULL; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + const git_diff_delta *delta; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(preimage); + GIT_ASSERT_ARG(diff); + + *out = NULL; + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_apply_options)); + + if ((error = git_reader_for_tree(&pre_reader, preimage)) < 0) + goto done; + + /* + * put the current tree into the postimage as-is - the diff will + * replace any entries contained therein + */ + if ((error = git_index_new(&postimage)) < 0 || + (error = git_index_read_tree(postimage, preimage)) < 0 || + (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) + goto done; + + /* + * Remove the old paths from the index before applying diffs - + * we need to do a full pass to remove them before adding deltas, + * in order to handle rename situations. + */ + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + 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) + goto done; + + *out = postimage; + +done: + if (error < 0) + git_index_free(postimage); + + git_reader_free(pre_reader); + git_reader_free(post_reader); + + return error; +} + +static int git_apply__to_workdir( + git_repository *repo, + git_diff *diff, + git_index *preimage, + git_index *postimage, + git_apply_location_t location, + git_apply_options *opts) +{ + git_vector paths = GIT_VECTOR_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + const git_diff_delta *delta; + size_t i; + int error; + + GIT_UNUSED(opts); + + /* + * Limit checkout to the paths affected by the diff; this ensures + * that other modifications in the working directory are unaffected. + */ + if ((error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL)) < 0) + goto done; + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if ((error = git_vector_insert(&paths, (void *)delta->old_file.path)) < 0) + goto done; + + if (strcmp(delta->old_file.path, delta->new_file.path) && + (error = git_vector_insert(&paths, (void *)delta->new_file.path)) < 0) + goto done; + } + + checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; + checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; + + if (location == GIT_APPLY_LOCATION_WORKDIR) + checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + + checkout_opts.paths.strings = (char **)paths.contents; + checkout_opts.paths.count = paths.length; + + checkout_opts.baseline_index = preimage; + + error = git_checkout_index(repo, postimage, &checkout_opts); + +done: + git_vector_free(&paths); + return error; +} + +static int git_apply__to_index( + git_repository *repo, + git_diff *diff, + git_index *preimage, + git_index *postimage, + git_apply_options *opts) +{ + git_index *index = NULL; + const git_diff_delta *delta; + const git_index_entry *entry; + size_t i; + int error; + + GIT_UNUSED(preimage); + GIT_UNUSED(opts); + + if ((error = git_repository_index(&index, repo)) < 0) + goto done; + + /* Remove deleted (or renamed) paths from the index. */ + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_RENAMED) { + if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) + goto done; + } + } + + /* Then add the changes back to the index. */ + for (i = 0; i < git_index_entrycount(postimage); i++) { + entry = git_index_get_byindex(postimage, i); + + if ((error = git_index_add(index, entry)) < 0) + goto done; + } + +done: + git_index_free(index); + return error; +} + +int git_apply_options_init(git_apply_options *opts, unsigned int version) +{ + GIT_ASSERT_ARG(opts); + + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT); + return 0; +} + +/* + * Handle the three application options ("locations"): + * + * GIT_APPLY_LOCATION_WORKDIR: the default, emulates `git apply`. + * Applies the diff only to the workdir items and ignores the index + * entirely. + * + * GIT_APPLY_LOCATION_INDEX: emulates `git apply --cached`. + * Applies the diff only to the index items and ignores the workdir + * completely. + * + * GIT_APPLY_LOCATION_BOTH: emulates `git apply --index`. + * Applies the diff to both the index items and the working directory + * items. + */ + +int git_apply( + git_repository *repo, + git_diff *diff, + git_apply_location_t location, + const git_apply_options *given_opts) +{ + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + git_index *index = NULL, *preimage = NULL, *postimage = NULL; + git_reader *pre_reader = NULL, *post_reader = NULL; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + int error = GIT_EINVALID; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(diff); + + GIT_ERROR_CHECK_VERSION( + given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options"); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_apply_options)); + + /* + * by default, we apply a patch directly to the working directory; + * in `--cached` or `--index` mode, we apply to the contents already + * in the index. + */ + switch (location) { + case GIT_APPLY_LOCATION_BOTH: + error = git_reader_for_workdir(&pre_reader, repo, true); + break; + case GIT_APPLY_LOCATION_INDEX: + error = git_reader_for_index(&pre_reader, repo, NULL); + break; + case GIT_APPLY_LOCATION_WORKDIR: + error = git_reader_for_workdir(&pre_reader, repo, false); + break; + default: + GIT_ASSERT(false); + } + + if (error < 0) + goto done; + + /* + * Build the preimage and postimage (differences). Note that + * this is not the complete preimage or postimage, it only + * contains the files affected by the patch. We want to avoid + * having the full repo index, so we will limit our checkout + * to only write these files that were affected by the diff. + */ + if ((error = git_index_new(&preimage)) < 0 || + (error = git_index_new(&postimage)) < 0 || + (error = git_reader_for_index(&post_reader, repo, postimage)) < 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); + break; + case GIT_APPLY_LOCATION_INDEX: + error = git_apply__to_index(repo, diff, preimage, postimage, &opts); + break; + case GIT_APPLY_LOCATION_WORKDIR: + error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); + break; + default: + GIT_ASSERT(false); + } + + if (error < 0) + goto done; + + error = git_indexwriter_commit(&indexwriter); + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(postimage); + git_index_free(preimage); + git_index_free(index); + git_reader_free(pre_reader); + git_reader_free(post_reader); + + return error; +} diff --git a/src/libgit2/apply.h b/src/libgit2/apply.h new file mode 100644 index 000000000..e990a7107 --- /dev/null +++ b/src/libgit2/apply.h @@ -0,0 +1,25 @@ +/* + * 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_apply_h__ +#define INCLUDE_apply_h__ + +#include "common.h" + +#include "git2/patch.h" +#include "git2/apply.h" +#include "str.h" + +extern int git_apply__patch( + git_str *out, + char **filename, + unsigned int *mode, + const char *source, + size_t source_len, + git_patch *patch, + const git_apply_options *opts); + +#endif diff --git a/src/libgit2/attr.c b/src/libgit2/attr.c new file mode 100644 index 000000000..1623b1d45 --- /dev/null +++ b/src/libgit2/attr.c @@ -0,0 +1,700 @@ +/* + * 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 "attr.h" + +#include "repository.h" +#include "sysdir.h" +#include "config.h" +#include "attr_file.h" +#include "ignore.h" +#include "git2/oid.h" +#include + +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; +const char *git_attr__unset = "[internal]__UNSET__"; + +git_attr_value_t git_attr_value(const char *attr) +{ + if (attr == NULL || attr == git_attr__unset) + return GIT_ATTR_VALUE_UNSPECIFIED; + + if (attr == git_attr__true) + return GIT_ATTR_VALUE_TRUE; + + if (attr == git_attr__false) + return GIT_ATTR_VALUE_FALSE; + + return GIT_ATTR_VALUE_STRING; +} + +static int collect_attr_files( + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *path, + git_vector *files); + +static void release_attr_files(git_vector *files); + +int git_attr_get_ext( + const char **value, + git_repository *repo, + git_attr_options *opts, + const char *pathname, + const char *name) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + size_t i, j; + git_attr_file *file; + git_attr_name attr; + git_attr_rule *rule; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + GIT_ASSERT_ARG(value); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); + + *value = NULL; + + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) + return -1; + + if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0) + goto cleanup; + + memset(&attr, 0, sizeof(attr)); + attr.name = name; + attr.name_hash = git_attr_file__name_hash(name); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + size_t pos; + + if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) { + *value = ((git_attr_assignment *)git_vector_get( + &rule->assigns, pos))->value; + goto cleanup; + } + } + } + +cleanup: + release_attr_files(&files); + git_attr_path__free(&path); + + return error; +} + +int git_attr_get( + const char **value, + git_repository *repo, + uint32_t flags, + const char *pathname, + const char *name) +{ + git_attr_options opts = GIT_ATTR_OPTIONS_INIT; + + opts.flags = flags; + + return git_attr_get_ext(value, repo, &opts, pathname, name); +} + + +typedef struct { + git_attr_name name; + git_attr_assignment *found; +} attr_get_many_info; + +int git_attr_get_many_with_session( + const char **values, + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *pathname, + size_t num_attr, + const char **names) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + size_t i, j, k; + git_attr_file *file; + git_attr_rule *rule; + attr_get_many_info *info = NULL; + size_t num_found = 0; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + if (!num_attr) + return 0; + + GIT_ASSERT_ARG(values); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(pathname); + GIT_ASSERT_ARG(names); + GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); + + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) + return -1; + + if ((error = collect_attr_files(repo, attr_session, opts, pathname, &files)) < 0) + goto cleanup; + + info = git__calloc(num_attr, sizeof(attr_get_many_info)); + GIT_ERROR_CHECK_ALLOC(info); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + for (k = 0; k < num_attr; k++) { + size_t pos; + + if (info[k].found != NULL) /* already found assignment */ + continue; + + if (!info[k].name.name) { + info[k].name.name = names[k]; + info[k].name.name_hash = git_attr_file__name_hash(names[k]); + } + + if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) { + info[k].found = (git_attr_assignment *) + git_vector_get(&rule->assigns, pos); + values[k] = info[k].found->value; + + if (++num_found == num_attr) + goto cleanup; + } + } + } + } + + for (k = 0; k < num_attr; k++) { + if (!info[k].found) + values[k] = NULL; + } + +cleanup: + release_attr_files(&files); + git_attr_path__free(&path); + git__free(info); + + return error; +} + +int git_attr_get_many( + const char **values, + git_repository *repo, + uint32_t flags, + const char *pathname, + size_t num_attr, + const char **names) +{ + git_attr_options opts = GIT_ATTR_OPTIONS_INIT; + + opts.flags = flags; + + return git_attr_get_many_with_session( + values, repo, NULL, &opts, pathname, num_attr, names); +} + +int git_attr_get_many_ext( + const char **values, + git_repository *repo, + git_attr_options *opts, + const char *pathname, + size_t num_attr, + const char **names) +{ + return git_attr_get_many_with_session( + values, repo, NULL, opts, pathname, num_attr, names); +} + +int git_attr_foreach( + git_repository *repo, + uint32_t flags, + const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + git_attr_options opts = GIT_ATTR_OPTIONS_INIT; + + opts.flags = flags; + + return git_attr_foreach_ext(repo, &opts, pathname, callback, payload); +} + +int git_attr_foreach_ext( + git_repository *repo, + git_attr_options *opts, + const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + size_t i, j, k; + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + git_strmap *seen = NULL; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(callback); + GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); + + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) + return -1; + + if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 || + (error = git_strmap_new(&seen)) < 0) + goto cleanup; + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + git_vector_foreach(&rule->assigns, k, assign) { + /* skip if higher priority assignment was already seen */ + if (git_strmap_exists(seen, assign->name)) + continue; + + if ((error = git_strmap_set(seen, assign->name, assign)) < 0) + goto cleanup; + + error = callback(assign->name, assign->value, payload); + if (error) { + git_error_set_after_callback(error); + goto cleanup; + } + } + } + } + +cleanup: + git_strmap_free(seen); + release_attr_files(&files); + git_attr_path__free(&path); + + return error; +} + +static int preload_attr_source( + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source) +{ + int error; + git_attr_file *preload = NULL; + + if (!source) + return 0; + + error = git_attr_cache__get(&preload, repo, attr_session, source, + git_attr_file__parse_buffer, true); + + if (!error) + git_attr_file__free(preload); + + return error; +} + +GIT_INLINE(int) preload_attr_file( + git_repository *repo, + git_attr_session *attr_session, + const char *base, + const char *filename) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; + + if (!filename) + return 0; + + source.base = base; + source.filename = filename; + + return preload_attr_source(repo, attr_session, &source); +} + +static int system_attr_file( + git_str *out, + git_attr_session *attr_session) +{ + int error; + + if (!attr_session) { + error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM); + + if (error == GIT_ENOTFOUND) + git_error_clear(); + + return error; + } + + if (!attr_session->init_sysdir) { + error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM); + + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error) + return error; + + attr_session->init_sysdir = 1; + } + + if (attr_session->sysdir.size == 0) + return GIT_ENOTFOUND; + + /* We can safely provide a git_str with no allocation (asize == 0) to + * a consumer. This allows them to treat this as a regular `git_str`, + * but their call to `git_str_dispose` will not attempt to free it. + */ + git_str_attach_notowned( + out, attr_session->sysdir.ptr, attr_session->sysdir.size); + return 0; +} + +static int attr_setup( + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts) +{ + git_str system = GIT_STR_INIT, info = GIT_STR_INIT; + git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE, NULL }; + git_attr_file_source head_source = { GIT_ATTR_FILE_SOURCE_HEAD, NULL, GIT_ATTR_FILE, NULL }; + git_attr_file_source commit_source = { GIT_ATTR_FILE_SOURCE_COMMIT, NULL, GIT_ATTR_FILE, NULL }; + git_index *idx = NULL; + const char *workdir; + int error = 0; + + if (attr_session && attr_session->init_setup) + return 0; + + 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. + */ + + if ((error = system_attr_file(&system, attr_session)) < 0 || + (error = preload_attr_file(repo, attr_session, NULL, system.ptr)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + + error = 0; + } + + if ((error = preload_attr_file(repo, attr_session, NULL, + git_repository_attr_cache(repo)->cfg_attr_file)) < 0) + goto out; + + if ((error = git_repository__item_path(&info, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = preload_attr_file(repo, attr_session, info.ptr, GIT_ATTR_FILE_INREPO)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + + error = 0; + } + + if ((workdir = git_repository_workdir(repo)) != NULL && + (error = preload_attr_file(repo, attr_session, workdir, GIT_ATTR_FILE)) < 0) + goto out; + + if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || + (error = preload_attr_source(repo, attr_session, &index_source)) < 0) + goto out; + + if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) && + (error = preload_attr_source(repo, attr_session, &head_source)) < 0) + goto out; + + if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0)) { +#ifndef GIT_DEPRECATE_HARD + if (opts->commit_id) + commit_source.commit_id = opts->commit_id; + else +#endif + commit_source.commit_id = &opts->attr_commit_id; + + if ((error = preload_attr_source(repo, attr_session, &commit_source)) < 0) + goto out; + } + + if (attr_session) + attr_session->init_setup = 1; + +out: + git_str_dispose(&system); + git_str_dispose(&info); + + return error; +} + +int git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values) +{ + int error; + git_attr_rule *macro = NULL; + git_pool *pool; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_attr_cache__init(repo)) < 0) + return error; + + macro = git__calloc(1, sizeof(git_attr_rule)); + GIT_ERROR_CHECK_ALLOC(macro); + + pool = &git_repository_attr_cache(repo)->pool; + + macro->match.pattern = git_pool_strdup(pool, name); + GIT_ERROR_CHECK_ALLOC(macro->match.pattern); + + macro->match.length = strlen(macro->match.pattern); + macro->match.flags = GIT_ATTR_FNMATCH_MACRO; + + error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); + + if (!error) + error = git_attr_cache__insert_macro(repo, macro); + + if (error < 0) + git_attr_rule__free(macro); + + return error; +} + +typedef struct { + git_repository *repo; + git_attr_session *attr_session; + git_attr_options *opts; + const char *workdir; + git_index *index; + git_vector *files; +} attr_walk_up_info; + +static int attr_decide_sources( + uint32_t flags, + bool has_wd, + bool has_index, + git_attr_file_source_t *srcs) +{ + int count = 0; + + switch (flags & 0x03) { + case GIT_ATTR_CHECK_FILE_THEN_INDEX: + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; + if (has_index) + srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; + break; + case GIT_ATTR_CHECK_INDEX_THEN_FILE: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; + break; + case GIT_ATTR_CHECK_INDEX_ONLY: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; + break; + } + + if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) + srcs[count++] = GIT_ATTR_FILE_SOURCE_HEAD; + + if ((flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0) + srcs[count++] = GIT_ATTR_FILE_SOURCE_COMMIT; + + return count; +} + +static int push_attr_source( + git_repository *repo, + git_attr_session *attr_session, + git_vector *list, + git_attr_file_source *source, + bool allow_macros) +{ + int error = 0; + git_attr_file *file = NULL; + + error = git_attr_cache__get(&file, repo, attr_session, + source, + git_attr_file__parse_buffer, + allow_macros); + + if (error < 0) + return error; + + if (file != NULL) { + if ((error = git_vector_insert(list, file)) < 0) + git_attr_file__free(file); + } + + return error; +} + +GIT_INLINE(int) push_attr_file( + git_repository *repo, + git_attr_session *attr_session, + git_vector *list, + const char *base, + const char *filename) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; + return push_attr_source(repo, attr_session, list, &source, true); +} + +static int push_one_attr(void *ref, const char *path) +{ + attr_walk_up_info *info = (attr_walk_up_info *)ref; + git_attr_file_source_t src[GIT_ATTR_FILE_NUM_SOURCES]; + int error = 0, n_src, i; + bool allow_macros; + + n_src = attr_decide_sources(info->opts ? info->opts->flags : 0, + info->workdir != NULL, + info->index != NULL, + src); + + allow_macros = info->workdir ? !strcmp(info->workdir, path) : false; + + for (i = 0; !error && i < n_src; ++i) { + git_attr_file_source source = { src[i], path, GIT_ATTR_FILE }; + + if (src[i] == GIT_ATTR_FILE_SOURCE_COMMIT && info->opts) { +#ifndef GIT_DEPRECATE_HARD + if (info->opts->commit_id) + source.commit_id = info->opts->commit_id; + else +#endif + source.commit_id = &info->opts->attr_commit_id; + } + + error = push_attr_source(info->repo, info->attr_session, info->files, + &source, allow_macros); + } + + return error; +} + +static void release_attr_files(git_vector *files) +{ + size_t i; + git_attr_file *file; + + git_vector_foreach(files, i, file) { + git_attr_file__free(file); + files->contents[i] = NULL; + } + git_vector_free(files); +} + +static int collect_attr_files( + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *path, + git_vector *files) +{ + int error = 0; + git_str dir = GIT_STR_INIT, attrfile = GIT_STR_INIT; + const char *workdir = git_repository_workdir(repo); + attr_walk_up_info info = { NULL }; + + GIT_ASSERT(!git_fs_path_is_absolute(path)); + + if ((error = attr_setup(repo, attr_session, opts)) < 0) + return error; + + /* Resolve path in a non-bare repo */ + if (workdir != NULL) { + if (!(error = git_repository_workdir_path(&dir, repo, path))) + error = git_fs_path_find_dir(&dir); + } + else { + error = git_fs_path_dirname_r(&dir, path); + } + + if (error < 0) + goto cleanup; + + /* in precedence order highest to lowest: + * - $GIT_DIR/info/attributes + * - path components with .gitattributes + * - config core.attributesfile + * - $GIT_PREFIX/etc/gitattributes + */ + + if ((error = git_repository__item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = push_attr_file(repo, attr_session, files, attrfile.ptr, GIT_ATTR_FILE_INREPO)) < 0) { + if (error != GIT_ENOTFOUND) + goto cleanup; + } + + info.repo = repo; + info.attr_session = attr_session; + info.opts = opts; + info.workdir = workdir; + if (git_repository_index__weakptr(&info.index, repo) < 0) + git_error_clear(); /* no error even if there is no index */ + info.files = files; + + if (!strcmp(dir.ptr, ".")) + error = push_one_attr(&info, ""); + else + error = git_fs_path_walk_up(&dir, workdir, push_one_attr, &info); + + if (error < 0) + goto cleanup; + + if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { + error = push_attr_file(repo, attr_session, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if (error < 0) + goto cleanup; + } + + if (!opts || (opts->flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { + error = system_attr_file(&dir, attr_session); + + if (!error) + error = push_attr_file(repo, attr_session, files, NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) + error = 0; + } + + cleanup: + if (error < 0) + release_attr_files(files); + git_str_dispose(&attrfile); + git_str_dispose(&dir); + + return error; +} diff --git a/src/libgit2/attr.h b/src/libgit2/attr.h new file mode 100644 index 000000000..977565205 --- /dev/null +++ b/src/libgit2/attr.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_attr_h__ +#define INCLUDE_attr_h__ + +#include "common.h" + +#include "attr_file.h" +#include "attrcache.h" + +#endif diff --git a/src/libgit2/attr_file.c b/src/libgit2/attr_file.c new file mode 100644 index 000000000..0eb881a9b --- /dev/null +++ b/src/libgit2/attr_file.c @@ -0,0 +1,1027 @@ +/* + * 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 "attr_file.h" + +#include "repository.h" +#include "filebuf.h" +#include "attrcache.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) +{ + bool unlock = !git_mutex_lock(&file->lock); + git_attr_file__clear_rules(file, false); + git_pool_clear(&file->pool); + if (unlock) + git_mutex_unlock(&file->lock); + git_mutex_free(&file->lock); + + git__memzero(file, sizeof(*file)); + git__free(file); +} + +int git_attr_file__new( + git_attr_file **out, + git_attr_file_entry *entry, + git_attr_file_source *source) +{ + git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file)); + GIT_ERROR_CHECK_ALLOC(attrs); + + if (git_mutex_init(&attrs->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to initialize lock"); + goto on_error; + } + + if (git_pool_init(&attrs->pool, 1) < 0) + goto on_error; + + GIT_REFCOUNT_INC(attrs); + attrs->entry = entry; + memcpy(&attrs->source, source, sizeof(git_attr_file_source)); + *out = attrs; + return 0; + +on_error: + git__free(attrs); + return -1; +} + +int git_attr_file__clear_rules(git_attr_file *file, bool need_lock) +{ + unsigned int i; + git_attr_rule *rule; + + if (need_lock && git_mutex_lock(&file->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); + return -1; + } + + git_vector_foreach(&file->rules, i, rule) + git_attr_rule__free(rule); + git_vector_free(&file->rules); + + if (need_lock) + git_mutex_unlock(&file->lock); + + return 0; +} + +void git_attr_file__free(git_attr_file *file) +{ + if (!file) + return; + GIT_REFCOUNT_DEC(file, attr_file_free); +} + +static int attr_file_oid_from_index( + git_oid *oid, git_repository *repo, const char *path) +{ + int error; + git_index *idx; + size_t pos; + const git_index_entry *entry; + + if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || + (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0) + return error; + + if (!(entry = git_index_get_byindex(idx, pos))) + return GIT_ENOTFOUND; + + *oid = entry->id; + return 0; +} + +int git_attr_file__load( + git_attr_file **out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_entry *entry, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros) +{ + int error = 0; + git_commit *commit = NULL; + git_tree *tree = NULL; + git_tree_entry *tree_entry = NULL; + git_blob *blob = NULL; + git_str content = GIT_STR_INIT; + const char *content_str; + git_attr_file *file; + struct stat st; + bool nonexistent = false; + int bom_offset; + git_str_bom_t bom; + git_oid id; + git_object_size_t blobsize; + + *out = NULL; + + switch (source->type) { + case GIT_ATTR_FILE_SOURCE_MEMORY: + /* in-memory attribute file doesn't need data */ + break; + case GIT_ATTR_FILE_SOURCE_INDEX: { + if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 || + (error = git_blob_lookup(&blob, repo, &id)) < 0) + return error; + + /* 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); + git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize); + break; + } + case GIT_ATTR_FILE_SOURCE_FILE: { + int fd = -1; + + /* For open or read errors, pretend that we got ENOTFOUND. */ + /* TODO: issue warning when warning API is available */ + + if (p_stat(entry->fullpath, &st) < 0 || + S_ISDIR(st.st_mode) || + (fd = git_futils_open_ro(entry->fullpath)) < 0 || + (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) + nonexistent = true; + + if (fd >= 0) + p_close(fd); + + break; + } + case GIT_ATTR_FILE_SOURCE_HEAD: + case GIT_ATTR_FILE_SOURCE_COMMIT: { + if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) { + if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0 || + (error = git_commit_tree(&tree, commit)) < 0) + goto cleanup; + } else { + if ((error = git_repository_head_tree(&tree, repo)) < 0) + goto cleanup; + } + + if ((error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0) { + /* + * If the attributes file does not exist, we can + * cache an empty file for this commit to prevent + * needless future lookups. + */ + if (error == GIT_ENOTFOUND) { + error = 0; + break; + } + + goto cleanup; + } + + if ((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_str_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->type); + return -1; + } + + if ((error = git_attr_file__new(&file, entry, source)) < 0) + goto cleanup; + + /* advance over a UTF8 BOM */ + content_str = git_str_cstr(&content); + bom_offset = git_str_detect_bom(&bom, &content); + + if (bom == GIT_STR_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, content_str, allow_macros)) < 0) { + git_attr_file__free(file); + goto cleanup; + } + + /* write cache breakers */ + if (nonexistent) + file->nonexistent = 1; + else if (source->type == GIT_ATTR_FILE_SOURCE_INDEX) + git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); + else if (source->type == GIT_ATTR_FILE_SOURCE_HEAD) + git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); + else if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) + git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); + else if (source->type == GIT_ATTR_FILE_SOURCE_FILE) + git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); + /* else always cacheable */ + + *out = file; + +cleanup: + git_blob_free(blob); + git_tree_entry_free(tree_entry); + git_tree_free(tree); + git_commit_free(commit); + git_str_dispose(&content); + + return error; +} + +int git_attr_file__out_of_date( + git_repository *repo, + git_attr_session *attr_session, + git_attr_file *file, + git_attr_file_source *source) +{ + if (!file) + return 1; + + /* we are never out of date if we just created this data in the same + * attr_session; otherwise, nonexistent files must be invalidated + */ + if (attr_session && attr_session->key == file->session_key) + return 0; + else if (file->nonexistent) + return 1; + + switch (file->source.type) { + case GIT_ATTR_FILE_SOURCE_MEMORY: + return 0; + + case GIT_ATTR_FILE_SOURCE_FILE: + return git_futils_filestamp_check( + &file->cache_data.stamp, file->entry->fullpath); + + case GIT_ATTR_FILE_SOURCE_INDEX: { + int error; + git_oid id; + + if ((error = attr_file_oid_from_index( + &id, repo, file->entry->path)) < 0) + return error; + + return (git_oid__cmp(&file->cache_data.oid, &id) != 0); + } + + case GIT_ATTR_FILE_SOURCE_HEAD: { + git_tree *tree = NULL; + int error = git_repository_head_tree(&tree, repo); + + if (error < 0) + return error; + + error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); + + git_tree_free(tree); + return error; + } + + case GIT_ATTR_FILE_SOURCE_COMMIT: { + git_commit *commit = NULL; + git_tree *tree = NULL; + int error; + + if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0) + return error; + + error = git_commit_tree(&tree, commit); + git_commit_free(commit); + + if (error < 0) + return error; + + error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); + + git_tree_free(tree); + return error; + } + + default: + git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source.type); + return -1; + } +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); +static void git_attr_rule__clear(git_attr_rule *rule); +static bool parse_optimized_patterns( + git_attr_fnmatch *spec, + git_pool *pool, + const char *pattern); + +int git_attr_file__parse_buffer( + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) +{ + 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_fs_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) { + git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); + return -1; + } + + while (!error && *scan) { + /* 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; + + /* 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 (error != GIT_ENOTFOUND) + goto out; + error = 0; + continue; + } + + 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); + + return error; +} + +uint32_t git_attr_file__name_hash(const char *name) +{ + uint32_t h = 5381; + int c; + + GIT_ASSERT_ARG(name); + + while ((c = (int)*name++) != 0) + h = ((h << 5) + h) + c; + return h; +} + +int git_attr_file__lookup_one( + git_attr_file *file, + git_attr_path *path, + const char *attr, + const char **value) +{ + size_t i; + git_attr_name name; + git_attr_rule *rule; + + *value = NULL; + + name.name = attr; + name.name_hash = git_attr_file__name_hash(attr); + + git_attr_file__foreach_matching_rule(file, path, i, rule) { + size_t pos; + + if (!git_vector_bsearch(&pos, &rule->assigns, &name)) { + *value = ((git_attr_assignment *) + git_vector_get(&rule->assigns, pos))->value; + break; + } + } + + return 0; +} + +int git_attr_file__load_standalone(git_attr_file **out, const char *path) +{ + git_str content = GIT_STR_INIT; + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; + git_attr_file *file = NULL; + int error; + + if ((error = git_futils_readbuffer(&content, path)) < 0) + goto out; + + /* + * 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_attr_file__new(&file, NULL, &source)) < 0 || + (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 || + (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0) + goto out; + + *out = file; +out: + if (error < 0) + git_attr_file__free(file); + git_str_dispose(&content); + + return error; +} + +bool git_attr_fnmatch__match( + git_attr_fnmatch *match, + git_attr_path *path) +{ + const char *relpath = path->path; + const char *filename; + int flags = 0; + + /* + * If the rule was generated in a subdirectory, we must only + * use it for paths inside that directory. We can thus return + * a non-match if the prefixes don't match. + */ + if (match->containing_dir) { + if (match->flags & GIT_ATTR_FNMATCH_ICASE) { + if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length)) + return 0; + } else { + if (git__prefixcmp(path->path, match->containing_dir)) + return 0; + } + + relpath += match->containing_dir_length; + } + + if (match->flags & GIT_ATTR_FNMATCH_ICASE) + flags |= WM_CASEFOLD; + + if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { + filename = relpath; + flags |= WM_PATHNAME; + } else { + filename = path->basename; + } + + if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { + bool samename; + + /* + * for attribute checks or checks at the root of this match's + * containing_dir (or root of the repository if no containing_dir), + * do not match. + */ + if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || + path->basename == relpath) + return false; + + /* fail match if this is a file with same name as ignored folder */ + samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? + !strcasecmp(match->pattern, relpath) : + !strcmp(match->pattern, relpath); + + if (samename) + return false; + + return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); + } + + return (wildmatch(match->pattern, filename, flags) == WM_MATCH); +} + +bool git_attr_rule__match( + git_attr_rule *rule, + git_attr_path *path) +{ + bool matched = git_attr_fnmatch__match(&rule->match, path); + + if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) + matched = !matched; + + return matched; +} + +git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name) +{ + size_t pos; + git_attr_name key; + key.name = name; + key.name_hash = git_attr_file__name_hash(name); + + if (git_vector_bsearch(&pos, &rule->assigns, &key)) + return NULL; + + return git_vector_get(&rule->assigns, pos); +} + +int git_attr_path__init( + git_attr_path *info, + const char *path, + const char *base, + git_dir_flag dir_flag) +{ + ssize_t root; + + /* build full path as best we can */ + git_str_init(&info->full, 0); + + if (git_fs_path_join_unrooted(&info->full, path, base, &root) < 0) + return -1; + + info->path = info->full.ptr + root; + + /* remove trailing slashes */ + while (info->full.size > 0) { + if (info->full.ptr[info->full.size - 1] != '/') + break; + info->full.size--; + } + info->full.ptr[info->full.size] = '\0'; + + /* skip leading slashes in path */ + while (*info->path == '/') + info->path++; + + /* find trailing basename component */ + info->basename = strrchr(info->path, '/'); + if (info->basename) + info->basename++; + if (!info->basename || !*info->basename) + info->basename = info->path; + + switch (dir_flag) + { + case GIT_DIR_FLAG_FALSE: + info->is_dir = 0; + break; + + case GIT_DIR_FLAG_TRUE: + info->is_dir = 1; + break; + + case GIT_DIR_FLAG_UNKNOWN: + default: + info->is_dir = (int)git_fs_path_isdir(info->full.ptr); + break; + } + + return 0; +} + +void git_attr_path__free(git_attr_path *info) +{ + git_str_dispose(&info->full); + info->path = NULL; + info->basename = NULL; +} + +/* + * From gitattributes(5): + * + * Patterns have the following format: + * + * - A blank line matches no files, so it can serve as a separator for + * readability. + * + * - A line starting with # serves as a comment. + * + * - An optional prefix ! which negates the pattern; any matching file + * excluded by a previous pattern will become included again. If a negated + * pattern matches, this will override lower precedence patterns sources. + * + * - If the pattern ends with a slash, it is removed for the purpose of the + * following description, but it would only find a match with a directory. In + * other words, foo/ will match a directory foo and paths underneath it, but + * will not match a regular file or a symbolic link foo (this is consistent + * with the way how pathspec works in general in git). + * + * - If the pattern does not contain a slash /, git treats it as a shell glob + * pattern and checks for a match against the pathname without leading + * directories. + * + * - Otherwise, git treats the pattern as a shell glob suitable for consumption + * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will + * not match a / in the pathname. For example, "Documentation/\*.html" matches + * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading + * slash matches the beginning of the pathname; for example, "/\*.c" matches + * "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 + * another error code there was an actual problem. + */ +int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *context, + const char **base) +{ + const char *pattern, *scan; + int slash_count, allow_space; + bool escaped; + + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(base && *base); + + if (parse_optimized_patterns(spec, pool, *base)) + return 0; + + spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); + allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); + + pattern = *base; + + 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; + } + + if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { + if (strncmp(pattern, "[attr]", 6) == 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; + pattern += 6; + } + /* else a character range like [a-e]* which is accepted */ + } + + if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + pattern++; + } + + slash_count = 0; + escaped = false; + /* Scan until a non-escaped whitespace. */ + for (scan = pattern; *scan != '\0'; ++scan) { + char c = *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 (slash_count == 1 && pattern == scan) + pattern++; + } 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; + + if ((spec->length = scan - pattern) == 0) + return GIT_ENOTFOUND; + + /* + * Remove one trailing \r in case this is a CRLF delimited + * file, in the case of Icon\r\r\n, we still leave the first + * \r there to match against. + */ + if (pattern[spec->length - 1] == '\r') + if (--spec->length == 0) + return GIT_ENOTFOUND; + + /* Remove trailing spaces. */ + spec->length -= trailing_space_length(pattern, spec->length); + + if (spec->length == 0) + return GIT_ENOTFOUND; + + if (pattern[spec->length - 1] == '/') { + spec->length--; + spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; + if (--slash_count <= 0) + spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; + } + + if (context) { + char *slash = strrchr(context, '/'); + size_t len; + if (slash) { + /* include the slash for easier matching */ + len = slash - context + 1; + spec->containing_dir = git_pool_strndup(pool, context, len); + spec->containing_dir_length = len; + } + } + + spec->pattern = git_pool_strndup(pool, pattern, spec->length); + + if (!spec->pattern) { + *base = git__next_line(pattern); + return -1; + } else { + /* strip '\' that might have been used for internal whitespace */ + spec->length = unescape_spaces(spec->pattern); + } + + return 0; +} + +static bool parse_optimized_patterns( + git_attr_fnmatch *spec, + git_pool *pool, + const char *pattern) +{ + if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) { + spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL; + spec->pattern = git_pool_strndup(pool, pattern, 1); + spec->length = 1; + + return true; + } + + return false; +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) +{ + const git_attr_name *a = a_raw; + const git_attr_name *b = b_raw; + + if (b->name_hash < a->name_hash) + return 1; + else if (b->name_hash > a->name_hash) + return -1; + else + return strcmp(b->name, a->name); +} + +static void git_attr_assignment__free(git_attr_assignment *assign) +{ + /* name and value are stored in a git_pool associated with the + * git_attr_file, so they do not need to be freed here + */ + assign->name = NULL; + assign->value = NULL; + git__free(assign); +} + +static int merge_assignments(void **old_raw, void *new_raw) +{ + git_attr_assignment **old = (git_attr_assignment **)old_raw; + git_attr_assignment *new = (git_attr_assignment *)new_raw; + + GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); + *old = new; + return GIT_EEXISTS; +} + +int git_attr_assignment__parse( + git_repository *repo, + git_pool *pool, + git_vector *assigns, + const char **base) +{ + int error; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + GIT_ASSERT_ARG(assigns && !assigns->length); + + git_vector_set_cmp(assigns, sort_by_hash_and_name); + + while (*scan && *scan != '\n') { + const char *name_start, *value_start; + + /* skip leading blanks */ + while (git__isspace(*scan) && *scan != '\n') scan++; + + /* allocate assign if needed */ + if (!assign) { + assign = git__calloc(1, sizeof(git_attr_assignment)); + GIT_ERROR_CHECK_ALLOC(assign); + GIT_REFCOUNT_INC(assign); + } + + assign->name_hash = 5381; + assign->value = git_attr__true; + + /* look for magic name prefixes */ + if (*scan == '-') { + assign->value = git_attr__false; + scan++; + } else if (*scan == '!') { + assign->value = git_attr__unset; /* explicit unspecified state */ + scan++; + } else if (*scan == '#') /* comment rest of line */ + break; + + /* find the name */ + name_start = scan; + while (*scan && !git__isspace(*scan) && *scan != '=') { + assign->name_hash = + ((assign->name_hash << 5) + assign->name_hash) + *scan; + scan++; + } + if (scan == name_start) { + /* must have found lone prefix (" - ") or leading = ("=foo") + * or end of buffer -- advance until whitespace and continue + */ + while (*scan && !git__isspace(*scan)) scan++; + continue; + } + + /* allocate permanent storage for name */ + assign->name = git_pool_strndup(pool, name_start, scan - name_start); + GIT_ERROR_CHECK_ALLOC(assign->name); + + /* if there is an equals sign, find the value */ + if (*scan == '=') { + for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); + + /* if we found a value, allocate permanent storage for it */ + if (scan > value_start) { + assign->value = git_pool_strndup(pool, value_start, scan - value_start); + GIT_ERROR_CHECK_ALLOC(assign->value); + } + } + + /* expand macros (if given a repo with a macro cache) */ + if (repo != NULL && assign->value == git_attr__true) { + git_attr_rule *macro = + git_attr_cache__lookup_macro(repo, assign->name); + + if (macro != NULL) { + unsigned int i; + git_attr_assignment *massign; + + git_vector_foreach(¯o->assigns, i, massign) { + GIT_REFCOUNT_INC(massign); + + error = git_vector_insert_sorted( + assigns, massign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) { + git_attr_assignment__free(assign); + return error; + } + } + } + } + + /* insert allocated assign into vector */ + error = git_vector_insert_sorted(assigns, assign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) + return error; + + /* clear assign since it is now "owned" by the vector */ + assign = NULL; + } + + if (assign != NULL) + git_attr_assignment__free(assign); + + *base = git__next_line(scan); + + return (assigns->length == 0) ? GIT_ENOTFOUND : 0; +} + +static void git_attr_rule__clear(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return; + + if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); + git_vector_free(&rule->assigns); + } + + /* match.pattern is stored in a git_pool, so no need to free */ + rule->match.pattern = NULL; + rule->match.length = 0; +} + +void git_attr_rule__free(git_attr_rule *rule) +{ + git_attr_rule__clear(rule); + git__free(rule); +} + +int git_attr_session__init(git_attr_session *session, git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + + memset(session, 0, sizeof(*session)); + session->key = git_atomic32_inc(&repo->attr_session_key); + + return 0; +} + +void git_attr_session__free(git_attr_session *session) +{ + if (!session) + return; + + git_str_dispose(&session->sysdir); + git_str_dispose(&session->tmp); + + memset(session, 0, sizeof(git_attr_session)); +} diff --git a/src/libgit2/attr_file.h b/src/libgit2/attr_file.h new file mode 100644 index 000000000..08630d1a6 --- /dev/null +++ b/src/libgit2/attr_file.h @@ -0,0 +1,241 @@ +/* + * 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_attr_file_h__ +#define INCLUDE_attr_file_h__ + +#include "common.h" + +#include "git2/oid.h" +#include "git2/attr.h" +#include "vector.h" +#include "pool.h" +#include "str.h" +#include "futils.h" + +#define GIT_ATTR_FILE ".gitattributes" +#define GIT_ATTR_FILE_INREPO "attributes" +#define GIT_ATTR_FILE_SYSTEM "gitattributes" +#define GIT_ATTR_FILE_XDG "attributes" + +#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) +#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) +#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) +#define GIT_ATTR_FNMATCH_MACRO (1U << 3) +#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) +#define GIT_ATTR_FNMATCH_HASWILD (1U << 5) +#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) +#define GIT_ATTR_FNMATCH_ICASE (1U << 7) +#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__INCOMING \ + (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) + +typedef enum { + GIT_ATTR_FILE_SOURCE_MEMORY = 0, + GIT_ATTR_FILE_SOURCE_FILE = 1, + GIT_ATTR_FILE_SOURCE_INDEX = 2, + GIT_ATTR_FILE_SOURCE_HEAD = 3, + GIT_ATTR_FILE_SOURCE_COMMIT = 4, + + GIT_ATTR_FILE_NUM_SOURCES = 5 +} git_attr_file_source_t; + +typedef struct { + /* The source location for the attribute file. */ + git_attr_file_source_t type; + + /* + * The filename of the attribute file to read (relative to the + * given base path). + */ + const char *base; + const char *filename; + + /* + * The commit ID when the given source type is a commit (or NULL + * for the repository's HEAD commit.) + */ + git_oid *commit_id; +} git_attr_file_source; + +extern const char *git_attr__true; +extern const char *git_attr__false; +extern const char *git_attr__unset; + +typedef struct { + char *pattern; + size_t length; + char *containing_dir; + size_t containing_dir_length; + unsigned int flags; +} git_attr_fnmatch; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of */ +} git_attr_rule; + +typedef struct { + git_refcount unused; + const char *name; + uint32_t name_hash; +} git_attr_name; + +typedef struct { + git_refcount rc; /* for macros */ + char *name; + uint32_t name_hash; + const char *value; +} git_attr_assignment; + +typedef struct git_attr_file_entry git_attr_file_entry; + +typedef struct { + git_refcount rc; + git_mutex lock; + git_attr_file_entry *entry; + git_attr_file_source source; + git_vector rules; /* vector of or */ + git_pool pool; + unsigned int nonexistent:1; + int session_key; + union { + git_oid oid; + git_futils_filestamp stamp; + } cache_data; +} git_attr_file; + +struct git_attr_file_entry { + git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES]; + const char *path; /* points into fullpath */ + char fullpath[GIT_FLEX_ARRAY]; +}; + +typedef struct { + git_str full; + char *path; + char *basename; + int is_dir; +} git_attr_path; + +/* A git_attr_session can provide an "instance" of reading, to prevent cache + * invalidation during a single operation instance (like checkout). + */ + +typedef struct { + int key; + unsigned int init_setup:1, + init_sysdir:1; + git_str sysdir; + git_str tmp; +} git_attr_session; + +extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo); +extern void git_attr_session__free(git_attr_session *session); + +extern int git_attr_get_many_with_session( + const char **values_out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *path, + size_t num_attr, + const char **names); + +typedef int (*git_attr_file_parser)( + git_repository *repo, + git_attr_file *file, + const char *data, + bool allow_macros); + +/* + * git_attr_file API + */ + +int git_attr_file__new( + git_attr_file **out, + git_attr_file_entry *entry, + git_attr_file_source *source); + +void git_attr_file__free(git_attr_file *file); + +int git_attr_file__load( + git_attr_file **out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_entry *ce, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros); + +int git_attr_file__load_standalone( + git_attr_file **out, const char *path); + +int git_attr_file__out_of_date( + git_repository *repo, git_attr_session *session, git_attr_file *file, git_attr_file_source *source); + +int git_attr_file__parse_buffer( + 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); + +int git_attr_file__lookup_one( + git_attr_file *file, + git_attr_path *path, + const char *attr, + const char **value); + +/* loop over rules in file from bottom to top */ +#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ + git_vector_rforeach(&(file)->rules, (iter), (rule)) \ + if (git_attr_rule__match((rule), (path))) + +uint32_t git_attr_file__name_hash(const char *name); + + +/* + * other utilities + */ + +extern int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base); + +extern bool git_attr_fnmatch__match( + git_attr_fnmatch *rule, + git_attr_path *path); + +extern void git_attr_rule__free(git_attr_rule *rule); + +extern bool git_attr_rule__match( + git_attr_rule *rule, + git_attr_path *path); + +extern git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name); + +typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag; + +extern int git_attr_path__init( + git_attr_path *out, + const char *path, + const char *base, + git_dir_flag is_dir); +extern void git_attr_path__free(git_attr_path *info); + +extern int git_attr_assignment__parse( + git_repository *repo, /* needed to expand macros */ + git_pool *pool, + git_vector *assigns, + const char **scan); + +#endif diff --git a/src/libgit2/attrcache.c b/src/libgit2/attrcache.c new file mode 100644 index 000000000..b16d95c3c --- /dev/null +++ b/src/libgit2/attrcache.c @@ -0,0 +1,478 @@ +/* + * 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 "attrcache.h" + +#include "repository.h" +#include "attr_file.h" +#include "config.h" +#include "sysdir.h" +#include "ignore.h" +#include "path.h" + +GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache) +{ + GIT_UNUSED(cache); /* avoid warning if threading is off */ + + if (git_mutex_lock(&cache->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to get attr cache lock"); + return -1; + } + return 0; +} + +GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache) +{ + GIT_UNUSED(cache); /* avoid warning if threading is off */ + git_mutex_unlock(&cache->lock); +} + +GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry( + git_attr_cache *cache, const char *path) +{ + return git_strmap_get(cache->files, path); +} + +int git_attr_cache__alloc_file_entry( + git_attr_file_entry **out, + git_repository *repo, + const char *base, + const char *path, + git_pool *pool) +{ + git_str fullpath_str = GIT_STR_INIT; + size_t baselen = 0, pathlen = strlen(path); + size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1; + git_attr_file_entry *ce; + + if (base != NULL && git_fs_path_root(path) < 0) { + baselen = strlen(base); + cachesize += baselen; + + if (baselen && base[baselen - 1] != '/') + cachesize++; + } + + ce = git_pool_mallocz(pool, cachesize); + GIT_ERROR_CHECK_ALLOC(ce); + + if (baselen) { + memcpy(ce->fullpath, base, baselen); + + if (base[baselen - 1] != '/') + ce->fullpath[baselen++] = '/'; + } + memcpy(&ce->fullpath[baselen], path, pathlen); + + fullpath_str.ptr = ce->fullpath; + fullpath_str.size = pathlen + baselen; + + if (git_path_validate_str_length(repo, &fullpath_str) < 0) + return -1; + + ce->path = &ce->fullpath[baselen]; + *out = ce; + + return 0; +} + +/* call with attrcache locked */ +static int attr_cache_make_entry( + git_attr_file_entry **out, git_repository *repo, const char *path) +{ + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry = NULL; + int error; + + if ((error = git_attr_cache__alloc_file_entry(&entry, repo, + git_repository_workdir(repo), path, &cache->pool)) < 0) + return error; + + if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0) + return error; + + *out = entry; + return error; +} + +/* insert entry or replace existing if we raced with another thread */ +static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file) +{ + git_attr_file_entry *entry; + git_attr_file *old; + + if (attr_cache_lock(cache) < 0) + return -1; + + entry = attr_cache_lookup_entry(cache, file->entry->path); + + GIT_REFCOUNT_OWN(file, entry); + GIT_REFCOUNT_INC(file); + + /* + * Replace the existing value if another thread has + * created it in the meantime. + */ + old = git_atomic_swap(entry->file[file->source.type], file); + + if (old) { + GIT_REFCOUNT_OWN(old, NULL); + git_attr_file__free(old); + } + + attr_cache_unlock(cache); + return 0; +} + +static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) +{ + int error = 0; + git_attr_file_entry *entry; + git_attr_file *oldfile = NULL; + + if (!file) + return 0; + + if ((error = attr_cache_lock(cache)) < 0) + return error; + + if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL) + oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); + + attr_cache_unlock(cache); + + if (oldfile == file) { + GIT_REFCOUNT_OWN(file, NULL); + git_attr_file__free(file); + } + + return error; +} + +/* Look up cache entry and file. + * - If entry is not present, create it while the cache is locked. + * - If file is present, increment refcount before returning it, so the + * cache can be unlocked and it won't go away. + */ +static int attr_cache_lookup( + git_attr_file **out_file, + git_attr_file_entry **out_entry, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source) +{ + int error = 0; + git_str path = GIT_STR_INIT; + const char *wd = git_repository_workdir(repo); + const char *filename; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry = NULL; + git_attr_file *file = NULL; + + /* join base and path as needed */ + if (source->base != NULL && git_fs_path_root(source->filename) < 0) { + git_str *p = attr_session ? &attr_session->tmp : &path; + + if (git_str_joinpath(p, source->base, source->filename) < 0 || + git_path_validate_str_length(repo, p) < 0) + return -1; + + filename = p->ptr; + } else { + filename = source->filename; + } + + if (wd && !git__prefixcmp(filename, wd)) + filename += strlen(wd); + + /* check cache for existing entry */ + if ((error = attr_cache_lock(cache)) < 0) + goto cleanup; + + entry = attr_cache_lookup_entry(cache, filename); + + if (!entry) { + error = attr_cache_make_entry(&entry, repo, filename); + } else if (entry->file[source->type] != NULL) { + file = entry->file[source->type]; + GIT_REFCOUNT_INC(file); + } + + attr_cache_unlock(cache); + +cleanup: + *out_file = file; + *out_entry = entry; + + git_str_dispose(&path); + return error; +} + +int git_attr_cache__get( + git_attr_file **out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros) +{ + int error = 0; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry = NULL; + git_attr_file *file = NULL, *updated = NULL; + + if ((error = attr_cache_lookup(&file, &entry, repo, attr_session, source)) < 0) + return error; + + /* 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, source)) > 0) + 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) { + if ((error = attr_cache_upsert(cache, updated)) < 0) { + git_attr_file__free(updated); + } else { + git_attr_file__free(file); /* offset incref from lookup */ + file = updated; + } + } + + /* if file could not be loaded */ + if (error < 0) { + /* remove existing entry */ + if (file) { + attr_cache_remove(cache, file); + git_attr_file__free(file); /* offset incref from lookup */ + file = NULL; + } + /* no error if file simply doesn't exist */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + } + + *out = file; + return error; +} + +bool git_attr_cache__is_cached( + git_repository *repo, + git_attr_file_source_t source_type, + const char *filename) +{ + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry; + git_strmap *files; + + if (!cache || !(files = cache->files)) + return false; + + if ((entry = git_strmap_get(files, filename)) == NULL) + return false; + + return entry && (entry->file[source_type] != NULL); +} + + +static int attr_cache__lookup_path( + char **out, git_config *cfg, const char *key, const char *fallback) +{ + git_str buf = GIT_STR_INIT; + int error; + git_config_entry *entry = NULL; + + *out = NULL; + + if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0) + return error; + + if (entry) { + const char *cfgval = entry->value; + + /* expand leading ~/ as needed */ + if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') { + if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2]))) + *out = git_str_detach(&buf); + } else if (cfgval) { + *out = git__strdup(cfgval); + } + } + else if (!git_sysdir_find_xdg_file(&buf, fallback)) { + *out = git_str_detach(&buf); + } + + git_config_entry_free(entry); + git_str_dispose(&buf); + + return error; +} + +static void attr_cache__free(git_attr_cache *cache) +{ + bool unlock; + + if (!cache) + return; + + unlock = (attr_cache_lock(cache) == 0); + + if (cache->files != NULL) { + git_attr_file_entry *entry; + git_attr_file *file; + int i; + + git_strmap_foreach_value(cache->files, entry, { + for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) { + if ((file = git_atomic_swap(entry->file[i], NULL)) != NULL) { + GIT_REFCOUNT_OWN(file, NULL); + git_attr_file__free(file); + } + } + }); + git_strmap_free(cache->files); + } + + if (cache->macros != NULL) { + git_attr_rule *rule; + + git_strmap_foreach_value(cache->macros, rule, { + git_attr_rule__free(rule); + }); + git_strmap_free(cache->macros); + } + + git_pool_clear(&cache->pool); + + git__free(cache->cfg_attr_file); + cache->cfg_attr_file = NULL; + + git__free(cache->cfg_excl_file); + cache->cfg_excl_file = NULL; + + if (unlock) + attr_cache_unlock(cache); + git_mutex_free(&cache->lock); + + git__free(cache); +} + +int git_attr_cache__init(git_repository *repo) +{ + int ret = 0; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_config *cfg = NULL; + + if (cache) + return 0; + + cache = git__calloc(1, sizeof(git_attr_cache)); + GIT_ERROR_CHECK_ALLOC(cache); + + /* set up lock */ + if (git_mutex_init(&cache->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache"); + git__free(cache); + return -1; + } + + if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) + goto cancel; + + /* cache config settings for attributes and ignores */ + ret = attr_cache__lookup_path( + &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); + if (ret < 0) + goto cancel; + + ret = attr_cache__lookup_path( + &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG); + if (ret < 0) + goto cancel; + + /* allocate hashtable for attribute and ignore file contents, + * hashtable for attribute macros, and string pool + */ + if ((ret = git_strmap_new(&cache->files)) < 0 || + (ret = git_strmap_new(&cache->macros)) < 0 || + (ret = git_pool_init(&cache->pool, 1)) < 0) + goto cancel; + + if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL) + goto cancel; /* raced with another thread, free this but no error */ + + git_config_free(cfg); + + /* insert default macros */ + return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf"); + +cancel: + attr_cache__free(cache); + git_config_free(cfg); + return ret; +} + +int git_attr_cache_flush(git_repository *repo) +{ + git_attr_cache *cache; + + /* this could be done less expensively, but for now, we'll just free + * the entire attrcache and let the next use reinitialize it... + */ + if (repo && (cache = git_atomic_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_attr_rule *preexisting; + bool locked = false; + int error = 0; + + /* + * 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; + } + + 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; + + return git_strmap_get(macros, name); +} diff --git a/src/libgit2/attrcache.h b/src/libgit2/attrcache.h new file mode 100644 index 000000000..b13e0e8f0 --- /dev/null +++ b/src/libgit2/attrcache.h @@ -0,0 +1,56 @@ +/* + * 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_attrcache_h__ +#define INCLUDE_attrcache_h__ + +#include "common.h" + +#include "attr_file.h" +#include "strmap.h" + +#define GIT_ATTR_CONFIG "core.attributesfile" +#define GIT_IGNORE_CONFIG "core.excludesfile" + +typedef struct { + char *cfg_attr_file; /* cached value of core.attributesfile */ + char *cfg_excl_file; /* cached value of core.excludesfile */ + git_strmap *files; /* hash path to git_attr_cache_entry records */ + git_strmap *macros; /* hash name to vector */ + git_mutex lock; + git_pool pool; +} git_attr_cache; + +extern int git_attr_cache__init(git_repository *repo); + +/* get file - loading and reload as needed */ +extern int git_attr_cache__get( + git_attr_file **file, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros); + +extern bool git_attr_cache__is_cached( + git_repository *repo, + git_attr_file_source_t source_type, + const char *filename); + +extern int git_attr_cache__alloc_file_entry( + git_attr_file_entry **out, + git_repository *repo, + const char *base, + const char *path, + git_pool *pool); + +extern int git_attr_cache__insert_macro( + git_repository *repo, git_attr_rule *macro); + +extern git_attr_rule *git_attr_cache__lookup_macro( + git_repository *repo, const char *name); + +#endif diff --git a/src/libgit2/blame.c b/src/libgit2/blame.c new file mode 100644 index 000000000..a6ab43efd --- /dev/null +++ b/src/libgit2/blame.c @@ -0,0 +1,557 @@ +/* + * 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 "blame.h" + +#include "git2/commit.h" +#include "git2/revparse.h" +#include "git2/revwalk.h" +#include "git2/tree.h" +#include "git2/diff.h" +#include "git2/blob.h" +#include "git2/signature.h" +#include "git2/mailmap.h" +#include "util.h" +#include "repository.h" +#include "blame_git.h" + + +static int hunk_byfinalline_search_cmp(const void *key, const void *entry) +{ + git_blame_hunk *hunk = (git_blame_hunk*)entry; + + size_t lineno = *(size_t*)key; + size_t lines_in_hunk = hunk->lines_in_hunk; + size_t final_start_line_number = hunk->final_start_line_number; + + if (lineno < final_start_line_number) + return -1; + if (lineno >= final_start_line_number + lines_in_hunk) + return 1; + return 0; +} + +static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); } +static int hunk_cmp(const void *_a, const void *_b) +{ + git_blame_hunk *a = (git_blame_hunk*)_a, + *b = (git_blame_hunk*)_b; + + if (a->final_start_line_number > b->final_start_line_number) + return 1; + else if (a->final_start_line_number < b->final_start_line_number) + return -1; + else + return 0; +} + +static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line) +{ + return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1); +} + +static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) +{ + return line <= hunk->final_start_line_number; +} + +static git_blame_hunk *new_hunk( + size_t start, + size_t lines, + size_t orig_start, + const char *path) +{ + git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk)); + if (!hunk) return NULL; + + hunk->lines_in_hunk = lines; + hunk->final_start_line_number = start; + hunk->orig_start_line_number = orig_start; + hunk->orig_path = path ? git__strdup(path) : NULL; + + return hunk; +} + +static void free_hunk(git_blame_hunk *hunk) +{ + git__free((void*)hunk->orig_path); + git_signature_free(hunk->final_signature); + git_signature_free(hunk->orig_signature); + git__free(hunk); +} + +static git_blame_hunk *dup_hunk(git_blame_hunk *hunk) +{ + git_blame_hunk *newhunk = new_hunk( + hunk->final_start_line_number, + hunk->lines_in_hunk, + hunk->orig_start_line_number, + hunk->orig_path); + + if (!newhunk) + return NULL; + + git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id); + git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id); + newhunk->boundary = hunk->boundary; + + if (git_signature_dup(&newhunk->final_signature, hunk->final_signature) < 0 || + git_signature_dup(&newhunk->orig_signature, hunk->orig_signature) < 0) { + free_hunk(newhunk); + return NULL; + } + + return newhunk; +} + +/* Starting with the hunk that includes start_line, shift all following hunks' + * final_start_line by shift_by lines */ +static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by) +{ + size_t i; + + if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) { + for (; i < v->length; i++) { + git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; + hunk->final_start_line_number += shift_by; + } + } +} + +git_blame *git_blame__alloc( + git_repository *repo, + git_blame_options opts, + const char *path) +{ + git_blame *gbr = git__calloc(1, sizeof(git_blame)); + if (!gbr) + return NULL; + + gbr->repository = repo; + gbr->options = opts; + + if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 || + git_vector_init(&gbr->paths, 8, paths_cmp) < 0 || + (gbr->path = git__strdup(path)) == NULL || + git_vector_insert(&gbr->paths, git__strdup(path)) < 0) + { + git_blame_free(gbr); + return NULL; + } + + if (opts.flags & GIT_BLAME_USE_MAILMAP && + git_mailmap_from_repository(&gbr->mailmap, repo) < 0) { + git_blame_free(gbr); + return NULL; + } + + return gbr; +} + +void git_blame_free(git_blame *blame) +{ + size_t i; + git_blame_hunk *hunk; + + if (!blame) return; + + git_vector_foreach(&blame->hunks, i, hunk) + free_hunk(hunk); + git_vector_free(&blame->hunks); + + git_vector_free_deep(&blame->paths); + + git_array_clear(blame->line_index); + + git_mailmap_free(blame->mailmap); + + git__free(blame->path); + git_blob_free(blame->final_blob); + git__free(blame); +} + +uint32_t git_blame_get_hunk_count(git_blame *blame) +{ + GIT_ASSERT_ARG(blame); + return (uint32_t)blame->hunks.length; +} + +const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index) +{ + GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); + return (git_blame_hunk*)git_vector_get(&blame->hunks, index); +} + +const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno) +{ + size_t i, new_lineno = lineno; + + GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); + + if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) { + return git_blame_get_hunk_byindex(blame, (uint32_t)i); + } + + return NULL; +} + +static int normalize_options( + git_blame_options *out, + const git_blame_options *in, + git_repository *repo) +{ + git_blame_options dummy = GIT_BLAME_OPTIONS_INIT; + if (!in) in = &dummy; + + memcpy(out, in, sizeof(git_blame_options)); + + /* No newest_commit => HEAD */ + if (git_oid_is_zero(&out->newest_commit)) { + if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) { + return -1; + } + } + + /* min_line 0 really means 1 */ + if (!out->min_line) out->min_line = 1; + /* max_line 0 really means N, but we don't know N yet */ + + /* Fix up option implications */ + if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; + if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; + if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE; + + return 0; +} + +static git_blame_hunk *split_hunk_in_vector( + git_vector *vec, + git_blame_hunk *hunk, + size_t rel_line, + bool return_new) +{ + size_t new_line_count; + git_blame_hunk *nh; + + /* Don't split if already at a boundary */ + if (rel_line <= 0 || + rel_line >= hunk->lines_in_hunk) + { + return hunk; + } + + new_line_count = hunk->lines_in_hunk - rel_line; + nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count, + hunk->orig_start_line_number + rel_line, hunk->orig_path); + + if (!nh) + return NULL; + + git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id); + git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id); + + /* Adjust hunk that was split */ + hunk->lines_in_hunk -= new_line_count; + git_vector_insert_sorted(vec, nh, NULL); + { + git_blame_hunk *ret = return_new ? nh : hunk; + return ret; + } +} + +/* + * Construct a list of char indices for where lines begin + * Adapted from core git: + * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789 + */ +static int index_blob_lines(git_blame *blame) +{ + const char *buf = blame->final_buf; + size_t len = blame->final_buf_size; + int num = 0, incomplete = 0, bol = 1; + size_t *i; + + if (len && buf[len-1] != '\n') + incomplete++; /* incomplete line at the end */ + while (len--) { + if (bol) { + i = git_array_alloc(blame->line_index); + GIT_ERROR_CHECK_ALLOC(i); + *i = buf - blame->final_buf; + bol = 0; + } + if (*buf++ == '\n') { + num++; + bol = 1; + } + } + i = git_array_alloc(blame->line_index); + GIT_ERROR_CHECK_ALLOC(i); + *i = buf - blame->final_buf; + blame->num_lines = num + incomplete; + return blame->num_lines; +} + +static git_blame_hunk *hunk_from_entry(git_blame__entry *e, git_blame *blame) +{ + git_blame_hunk *h = new_hunk( + e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path); + + if (!h) + return NULL; + + git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit)); + git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit)); + git_commit_author_with_mailmap( + &h->final_signature, e->suspect->commit, blame->mailmap); + git_signature_dup(&h->orig_signature, h->final_signature); + h->boundary = e->is_boundary ? 1 : 0; + return h; +} + +static int load_blob(git_blame *blame) +{ + int error; + + if (blame->final_blob) return 0; + + error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit); + if (error < 0) + goto cleanup; + error = git_object_lookup_bypath((git_object**)&blame->final_blob, + (git_object*)blame->final, blame->path, GIT_OBJECT_BLOB); + +cleanup: + return error; +} + +static int blame_internal(git_blame *blame) +{ + int error; + git_blame__entry *ent = NULL; + git_blame__origin *o; + + 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 = (size_t)git_blob_rawsize(blame->final_blob); + + ent = git__calloc(1, sizeof(git_blame__entry)); + GIT_ERROR_CHECK_ALLOC(ent); + + ent->num_lines = index_blob_lines(blame); + ent->lno = blame->options.min_line - 1; + ent->num_lines = ent->num_lines - blame->options.min_line + 1; + if (blame->options.max_line > 0) + ent->num_lines = blame->options.max_line - blame->options.min_line + 1; + ent->s_lno = ent->lno; + ent->suspect = o; + + blame->ent = ent; + + error = git_blame__like_git(blame, blame->options.flags); + +cleanup: + for (ent = blame->ent; ent; ) { + git_blame__entry *e = ent->next; + git_blame_hunk *h = hunk_from_entry(ent, blame); + + git_vector_insert(&blame->hunks, h); + + git_blame__free_entry(ent); + ent = e; + } + + return error; +} + +/******************************************************************************* + * File blaming + ******************************************************************************/ + +int git_blame_file( + git_blame **out, + git_repository *repo, + const char *path, + git_blame_options *options) +{ + int error = -1; + git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT; + git_blame *blame = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(path); + + if ((error = normalize_options(&normOptions, options, repo)) < 0) + goto on_error; + + blame = git_blame__alloc(repo, normOptions, path); + GIT_ERROR_CHECK_ALLOC(blame); + + if ((error = load_blob(blame)) < 0) + goto on_error; + + if ((error = blame_internal(blame)) < 0) + goto on_error; + + *out = blame; + return 0; + +on_error: + git_blame_free(blame); + return error; +} + +/******************************************************************************* + * Buffer blaming + *******************************************************************************/ + +static bool hunk_is_bufferblame(git_blame_hunk *hunk) +{ + return hunk && git_oid_is_zero(&hunk->final_commit_id); +} + +static int buffer_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *payload) +{ + git_blame *blame = (git_blame*)payload; + uint32_t wedge_line; + + GIT_UNUSED(delta); + + wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; + blame->current_diff_line = wedge_line; + + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line); + if (!blame->current_hunk) { + /* Line added at the end of the file */ + blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path); + GIT_ERROR_CHECK_ALLOC(blame->current_hunk); + + git_vector_insert(&blame->hunks, blame->current_hunk); + } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){ + /* If this hunk doesn't start between existing hunks, split a hunk up so it does */ + blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk, + wedge_line - blame->current_hunk->orig_start_line_number, true); + GIT_ERROR_CHECK_ALLOC(blame->current_hunk); + } + + return 0; +} + +static int ptrs_equal_cmp(const void *a, const void *b) { return ab ? 1 : 0; } +static int buffer_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + git_blame *blame = (git_blame*)payload; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + GIT_UNUSED(line); + + if (line->origin == GIT_DIFF_LINE_ADDITION) { + if (hunk_is_bufferblame(blame->current_hunk) && + hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) { + /* Append to the current buffer-blame hunk */ + blame->current_hunk->lines_in_hunk++; + shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1); + } else { + /* Create a new buffer-blame hunk with this line */ + shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); + blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path); + GIT_ERROR_CHECK_ALLOC(blame->current_hunk); + + git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); + } + blame->current_diff_line++; + } + + if (line->origin == GIT_DIFF_LINE_DELETION) { + /* Trim the line from the current hunk; remove it if it's now empty */ + size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1; + + if (--(blame->current_hunk->lines_in_hunk) == 0) { + size_t i; + shift_base--; + if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) { + git_vector_remove(&blame->hunks, i); + free_hunk(blame->current_hunk); + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i); + } + } + shift_hunks_by(&blame->hunks, shift_base, -1); + } + return 0; +} + +int git_blame_buffer( + git_blame **out, + git_blame *reference, + const char *buffer, + size_t buffer_len) +{ + git_blame *blame; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + size_t i; + git_blame_hunk *hunk; + + diffopts.context_lines = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(reference); + GIT_ASSERT_ARG(buffer && buffer_len); + + blame = git_blame__alloc(reference->repository, reference->options, reference->path); + GIT_ERROR_CHECK_ALLOC(blame); + + /* Duplicate all of the hunk structures in the reference blame */ + git_vector_foreach(&reference->hunks, i, hunk) { + git_blame_hunk *h = dup_hunk(hunk); + GIT_ERROR_CHECK_ALLOC(h); + + git_vector_insert(&blame->hunks, h); + } + + /* Diff to the reference blob */ + git_diff_blob_to_buffer(reference->final_blob, blame->path, + buffer, buffer_len, blame->path, &diffopts, + NULL, NULL, buffer_hunk_cb, buffer_line_cb, blame); + + *out = blame; + return 0; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_blame_init_options(git_blame_options *opts, unsigned int version) +{ + return git_blame_options_init(opts, version); +} +#endif diff --git a/src/libgit2/blame.h b/src/libgit2/blame.h new file mode 100644 index 000000000..4141e2bb5 --- /dev/null +++ b/src/libgit2/blame.h @@ -0,0 +1,95 @@ +#ifndef INCLUDE_blame_h__ +#define INCLUDE_blame_h__ + +#include "common.h" + +#include "git2/blame.h" +#include "vector.h" +#include "diff.h" +#include "array.h" +#include "git2/oid.h" + +/* + * One blob in a commit that is being suspected + */ +typedef struct git_blame__origin { + int refcnt; + struct git_blame__origin *previous; + git_commit *commit; + git_blob *blob; + char path[GIT_FLEX_ARRAY]; +} git_blame__origin; + +/* + * Each group of lines is described by a git_blame__entry; it can be split + * as we pass blame to the parents. They form a linked list in the + * scoreboard structure, sorted by the target line number. + */ +typedef struct git_blame__entry { + struct git_blame__entry *prev; + struct git_blame__entry *next; + + /* the first line of this group in the final image; + * internally all line numbers are 0 based. + */ + size_t lno; + + /* how many lines this group has */ + size_t num_lines; + + /* the commit that introduced this group into the final image */ + git_blame__origin *suspect; + + /* true if the suspect is truly guilty; false while we have not + * checked if the group came from one of its parents. + */ + bool guilty; + + /* true if the entry has been scanned for copies in the current parent + */ + bool scanned; + + /* the line number of the first line of this group in the + * suspect's file; internally all line numbers are 0 based. + */ + size_t s_lno; + + /* how significant this entry is -- cached to avoid + * scanning the lines over and over. + */ + unsigned score; + + /* Whether this entry has been tracked to a boundary commit. + */ + bool is_boundary; +} git_blame__entry; + +struct git_blame { + char *path; + git_repository *repository; + git_mailmap *mailmap; + git_blame_options options; + + git_vector hunks; + git_vector paths; + + git_blob *final_blob; + git_array_t(size_t) line_index; + + size_t current_diff_line; + git_blame_hunk *current_hunk; + + /* Scoreboard fields */ + git_commit *final; + git_blame__entry *ent; + int num_lines; + const char *final_buf; + size_t final_buf_size; +}; + +git_blame *git_blame__alloc( + git_repository *repo, + git_blame_options opts, + const char *path); + +#endif diff --git a/src/libgit2/blame_git.c b/src/libgit2/blame_git.c new file mode 100644 index 000000000..2504b338a --- /dev/null +++ b/src/libgit2/blame_git.c @@ -0,0 +1,685 @@ +/* + * 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 "blame_git.h" + +#include "commit.h" +#include "blob.h" +#include "xdiff/xinclude.h" +#include "diff_xdiff.h" + +/* + * Origin is refcounted and usually we keep the blob contents to be + * reused. + */ +static git_blame__origin *origin_incref(git_blame__origin *o) +{ + if (o) + o->refcnt++; + return o; +} + +static void origin_decref(git_blame__origin *o) +{ + if (o && --o->refcnt <= 0) { + if (o->previous) + origin_decref(o->previous); + git_blob_free(o->blob); + git_commit_free(o->commit); + git__free(o); + } +} + +/* Given a commit and a path in it, create a new origin structure. */ +static int make_origin(git_blame__origin **out, git_commit *commit, const char *path) +{ + git_blame__origin *o; + git_object *blob; + size_t path_len = strlen(path), alloc_len; + int error = 0; + + if ((error = git_object_lookup_bypath(&blob, (git_object*)commit, + path, GIT_OBJECT_BLOB)) < 0) + return error; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + o = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(o); + + o->commit = commit; + o->blob = (git_blob *) blob; + o->refcnt = 1; + strcpy(o->path, path); + + *out = o; + + return 0; +} + +/* Locate an existing origin or create a new one. */ +int git_blame__get_origin( + git_blame__origin **out, + git_blame *blame, + git_commit *commit, + const char *path) +{ + git_blame__entry *e; + + for (e = blame->ent; e; e = e->next) { + if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) { + *out = origin_incref(e->suspect); + } + } + return make_origin(out, commit, path); +} + +typedef struct blame_chunk_cb_data { + git_blame *blame; + git_blame__origin *target; + git_blame__origin *parent; + long tlno; + long plno; +}blame_chunk_cb_data; + +static bool same_suspect(git_blame__origin *a, git_blame__origin *b) +{ + if (a == b) + return true; + if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit))) + return false; + return 0 == strcmp(a->path, b->path); +} + +/* find the line number of the last line the target is suspected for */ +static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target) +{ + git_blame__entry *e; + size_t last_in_target = 0; + bool found = false; + + *out = 0; + + for (e=blame->ent; e; e=e->next) { + if (e->guilty || !same_suspect(e->suspect, target)) + continue; + if (last_in_target < e->s_lno + e->num_lines) { + found = true; + last_in_target = e->s_lno + e->num_lines; + } + } + + *out = last_in_target; + return found; +} + +/* + * It is known that lines between tlno to same came from parent, and e + * has an overlap with that range. it also is known that parent's + * line plno corresponds to e's line tlno. + * + * <---- e -----> + * <------> (entirely within) + * <------------> (extends past) + * <------------> (starts before) + * <------------------> (entirely encloses) + * + * Split e into potentially three parts; before this chunk, the chunk + * to be blamed for the parent, and after that portion. + */ +static void split_overlap(git_blame__entry *split, git_blame__entry *e, + size_t tlno, size_t plno, size_t same, git_blame__origin *parent) +{ + size_t chunk_end_lno; + + if (e->s_lno < tlno) { + /* there is a pre-chunk part not blamed on the parent */ + split[0].suspect = origin_incref(e->suspect); + split[0].lno = e->lno; + split[0].s_lno = e->s_lno; + split[0].num_lines = tlno - e->s_lno; + split[1].lno = e->lno + tlno - e->s_lno; + split[1].s_lno = plno; + } else { + split[1].lno = e->lno; + split[1].s_lno = plno + (e->s_lno - tlno); + } + + if (same < e->s_lno + e->num_lines) { + /* there is a post-chunk part not blamed on parent */ + split[2].suspect = origin_incref(e->suspect); + split[2].lno = e->lno + (same - e->s_lno); + split[2].s_lno = e->s_lno + (same - e->s_lno); + split[2].num_lines = e->s_lno + e->num_lines - same; + chunk_end_lno = split[2].lno; + } else { + chunk_end_lno = e->lno + e->num_lines; + } + split[1].num_lines = chunk_end_lno - split[1].lno; + + /* + * if it turns out there is nothing to blame the parent for, forget about + * the splitting. !split[1].suspect signals this. + */ + if (split[1].num_lines < 1) + return; + split[1].suspect = origin_incref(parent); +} + +/* + * Link in a new blame entry to the scoreboard. Entries that cover the same + * line range have been removed from the scoreboard previously. + */ +static void add_blame_entry(git_blame *blame, git_blame__entry *e) +{ + git_blame__entry *ent, *prev = NULL; + + origin_incref(e->suspect); + + for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next) + prev = ent; + + /* prev, if not NULL, is the last one that is below e */ + e->prev = prev; + if (prev) { + e->next = prev->next; + prev->next = e; + } else { + e->next = blame->ent; + blame->ent = e; + } + if (e->next) + e->next->prev = e; +} + +/* + * src typically is on-stack; we want to copy the information in it to + * a malloced blame_entry that is already on the linked list of the scoreboard. + * The origin of dst loses a refcnt while the origin of src gains one. + */ +static void dup_entry(git_blame__entry *dst, git_blame__entry *src) +{ + git_blame__entry *p, *n; + + p = dst->prev; + n = dst->next; + origin_incref(src->suspect); + origin_decref(dst->suspect); + memcpy(dst, src, sizeof(*src)); + dst->prev = p; + dst->next = n; + dst->score = 0; +} + +/* + * 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 int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) +{ + git_blame__entry *new_entry; + + if (split[0].suspect && split[2].suspect) { + /* The first part (reuse storage for the existing entry e */ + dup_entry(e, &split[0]); + + /* 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) { + /* + * The parent covers the entire area; reuse storage for e and replace it + * with the parent + */ + dup_entry(e, &split[1]); + } else if (split[0].suspect) { + /* 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; +} + +/* + * After splitting the blame, the origins used by the on-stack blame_entry + * should lose one refcnt each. + */ +static void decref_split(git_blame__entry *split) +{ + int i; + for (i=0; i<3; i++) + origin_decref(split[i].suspect); +} + +/* + * Helper for blame_chunk(). blame_entry e is known to overlap with the patch + * hunk; split it and pass blame to the parent. + */ +static int blame_overlap( + git_blame *blame, + git_blame__entry *e, + size_t tlno, + size_t plno, + size_t same, + git_blame__origin *parent) +{ + git_blame__entry split[3] = {{0}}; + + split_overlap(split, e, tlno, plno, same, parent); + if (split[1].suspect) + if (split_blame(blame, split, e) < 0) + return -1; + decref_split(split); + + return 0; +} + +/* + * Process one hunk from the patch between the current suspect for blame_entry + * e and its parent. Find and split the overlap, and pass blame to the + * overlapping part to the parent. + */ +static int blame_chunk( + git_blame *blame, + size_t tlno, + size_t plno, + size_t same, + git_blame__origin *target, + git_blame__origin *parent) +{ + git_blame__entry *e; + + for (e = blame->ent; e; e = e->next) { + if (e->guilty || !same_suspect(e->suspect, target)) + continue; + if (same <= e->s_lno) + continue; + if (tlno < e->s_lno + e->num_lines) { + if (blame_overlap(blame, e, tlno, plno, same, parent) < 0) + return -1; + } + } + + return 0; +} + +static int my_emit( + long start_a, long count_a, + long start_b, long count_b, + void *cb_data) +{ + blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data; + + 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; + + return 0; +} + +static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx) +{ + const int blk = 1024; + long trimmed = 0, recovered = 0; + char *ap = a->ptr + a->size; + char *bp = b->ptr + b->size; + long smaller = (long)((a->size < b->size) ? a->size : b->size); + + if (ctx) + return; + + while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) { + trimmed += blk; + ap -= blk; + bp -= blk; + } + + while (recovered < trimmed) + if (ap[recovered++] == '\n') + break; + a->size -= trimmed - recovered; + b->size -= trimmed - recovered; +} + +static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data, git_blame_options *options) +{ + xdemitconf_t xecfg = {0}; + xdemitcb_t ecb = {0}; + xpparam_t xpp = {0}; + + if (options->flags & GIT_BLAME_IGNORE_WHITESPACE) + xpp.flags |= XDF_IGNORE_WHITESPACE; + + xecfg.hunk_func = my_emit; + ecb.priv = cb_data; + + trim_common_tail(&file_a, &file_b, 0); + + if (file_a.size > GIT_XDIFF_MAX_SIZE || + file_b.size > GIT_XDIFF_MAX_SIZE) { + git_error_set(GIT_ERROR_INVALID, "file too large to blame"); + return -1; + } + + return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb); +} + +static void fill_origin_blob(git_blame__origin *o, mmfile_t *file) +{ + memset(file, 0, sizeof(*file)); + if (o->blob) { + file->ptr = (char*)git_blob_rawcontent(o->blob); + file->size = (long)git_blob_rawsize(o->blob); + } +} + +static int pass_blame_to_parent( + git_blame *blame, + git_blame__origin *target, + git_blame__origin *parent) +{ + size_t last_in_target; + mmfile_t file_p, file_o; + blame_chunk_cb_data d = { blame, target, parent, 0, 0 }; + + if (!find_last_in_target(&last_in_target, blame, target)) + return 1; /* nothing remains for this target */ + + fill_origin_blob(parent, &file_p); + fill_origin_blob(target, &file_o); + + if (diff_hunks(file_p, file_o, &d, &blame->options) < 0) + return -1; + + /* The reset (i.e. anything after tlno) are the same as the parent */ + if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0) + return -1; + + return 0; +} + +static int paths_on_dup(void **old, void *new) +{ + GIT_UNUSED(old); + git__free(new); + return -1; +} + +static git_blame__origin *find_origin( + git_blame *blame, + git_commit *parent, + git_blame__origin *origin) +{ + git_blame__origin *porigin = NULL; + git_diff *difflist = NULL; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_tree *otree=NULL, *ptree=NULL; + + /* Get the trees from this commit and its parent */ + if (0 != git_commit_tree(&otree, origin->commit) || + 0 != git_commit_tree(&ptree, parent)) + goto cleanup; + + /* Configure the diff */ + diffopts.context_lines = 0; + diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK; + + /* Check to see if files we're interested have changed */ + diffopts.pathspec.count = blame->paths.length; + diffopts.pathspec.strings = (char**)blame->paths.contents; + if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) + goto cleanup; + + if (!git_diff_num_deltas(difflist)) { + /* No changes; copy data */ + git_blame__get_origin(&porigin, blame, parent, origin->path); + } else { + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + int i; + + /* Generate a full diff between the two trees */ + git_diff_free(difflist); + diffopts.pathspec.count = 0; + if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) + goto cleanup; + + /* Let diff find renames */ + findopts.flags = GIT_DIFF_FIND_RENAMES; + if (0 != git_diff_find_similar(difflist, &findopts)) + goto cleanup; + + /* Find one that matches */ + for (i=0; i<(int)git_diff_num_deltas(difflist); i++) { + const git_diff_delta *delta = git_diff_get_delta(difflist, i); + + if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path)) + { + git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path), + paths_on_dup); + make_origin(&porigin, parent, delta->old_file.path); + } + } + } + +cleanup: + git_diff_free(difflist); + git_tree_free(otree); + git_tree_free(ptree); + return porigin; +} + +/* + * The blobs of origin and porigin exactly match, so everything origin is + * suspected for can be blamed on the parent. + */ +static int pass_whole_blame(git_blame *blame, + git_blame__origin *origin, git_blame__origin *porigin) +{ + git_blame__entry *e; + + if (!porigin->blob && + git_object_lookup((git_object**)&porigin->blob, blame->repository, + git_blob_id(origin->blob), GIT_OBJECT_BLOB) < 0) + return -1; + for (e=blame->ent; e; e=e->next) { + if (!same_suspect(e->suspect, origin)) + continue; + origin_incref(porigin); + origin_decref(e->suspect); + e->suspect = porigin; + } + + return 0; +} + +static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt) +{ + git_commit *commit = origin->commit; + int i, num_parents; + git_blame__origin *sg_buf[16]; + git_blame__origin *porigin, **sg_origin = sg_buf; + int ret, error = 0; + + num_parents = git_commit_parentcount(commit); + if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit)) + /* Stop at oldest specified commit */ + num_parents = 0; + else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1) + /* Limit search to the first parent */ + num_parents = 1; + + if (!num_parents) { + git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit)); + goto finish; + } else if (num_parents < (int)ARRAY_SIZE(sg_buf)) + memset(sg_buf, 0, sizeof(sg_buf)); + else { + sg_origin = git__calloc(num_parents, sizeof(*sg_origin)); + GIT_ERROR_CHECK_ALLOC(sg_origin); + } + + for (i=0; icommit, i)) < 0) + goto finish; + porigin = find_origin(blame, p, origin); + + if (!porigin) { + /* + * We only have to decrement the parent's + * reference count when no porigin has + * been created, as otherwise the commit + * is assigned to the created object. + */ + git_commit_free(p); + continue; + } + if (porigin->blob && origin->blob && + !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) { + error = pass_whole_blame(blame, origin, porigin); + origin_decref(porigin); + goto finish; + } + for (j = same = 0; jblob), git_blob_id(porigin->blob))) { + same = 1; + break; + } + if (!same) + sg_origin[i] = porigin; + else + origin_decref(porigin); + } + + /* Standard blame */ + for (i=0; iprevious) { + origin_incref(porigin); + origin->previous = porigin; + } + + if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) { + if (ret < 0) + error = -1; + + goto finish; + } + } + + /* TODO: optionally find moves in parents' files */ + + /* TODO: optionally find copies in parents' files */ + +finish: + for (i=0; i pair), + * merge them together. + */ +static void coalesce(git_blame *blame) +{ + git_blame__entry *ent, *next; + + for (ent=blame->ent; ent && (next = ent->next); ent = next) { + if (same_suspect(ent->suspect, next->suspect) && + ent->guilty == next->guilty && + ent->s_lno + ent->num_lines == next->s_lno) + { + ent->num_lines += next->num_lines; + ent->next = next->next; + if (ent->next) + ent->next->prev = ent; + origin_decref(next->suspect); + git__free(next); + ent->score = 0; + next = ent; /* again */ + } + } +} + +int git_blame__like_git(git_blame *blame, uint32_t opt) +{ + int error = 0; + + while (true) { + git_blame__entry *ent; + git_blame__origin *suspect = NULL; + + /* Find a suspect to break down */ + for (ent = blame->ent; !suspect && ent; ent = ent->next) + if (!ent->guilty) + suspect = ent->suspect; + if (!suspect) + break; + + /* We'll use this suspect later in the loop, so hold on to it for now. */ + origin_incref(suspect); + + if ((error = pass_blame(blame, suspect, opt)) < 0) + break; + + /* Take responsibility for the remaining entries */ + for (ent = blame->ent; ent; ent = ent->next) { + if (same_suspect(ent->suspect, suspect)) { + ent->guilty = true; + ent->is_boundary = !git_oid_cmp( + git_commit_id(suspect->commit), + &blame->options.oldest_commit); + } + } + origin_decref(suspect); + } + + if (!error) + coalesce(blame); + + return error; +} + +void git_blame__free_entry(git_blame__entry *ent) +{ + if (!ent) return; + origin_decref(ent->suspect); + git__free(ent); +} diff --git a/src/libgit2/blame_git.h b/src/libgit2/blame_git.h new file mode 100644 index 000000000..48b85a20d --- /dev/null +++ b/src/libgit2/blame_git.h @@ -0,0 +1,22 @@ +/* + * 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_blame_git__ +#define INCLUDE_blame_git__ + +#include "common.h" + +#include "blame.h" + +int git_blame__get_origin( + git_blame__origin **out, + git_blame *sb, + git_commit *commit, + const char *path); +void git_blame__free_entry(git_blame__entry *ent); +int git_blame__like_git(git_blame *sb, uint32_t flags); + +#endif diff --git a/src/libgit2/blob.c b/src/libgit2/blob.c new file mode 100644 index 000000000..b1680d3a8 --- /dev/null +++ b/src/libgit2/blob.c @@ -0,0 +1,528 @@ +/* + * 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 "blob.h" + +#include "git2/common.h" +#include "git2/object.h" +#include "git2/repository.h" +#include "git2/odb_backend.h" + +#include "buf.h" +#include "filebuf.h" +#include "filter.h" + +const void *git_blob_rawcontent(const git_blob *blob) +{ + GIT_ASSERT_ARG_WITH_RETVAL(blob, NULL); + + if (blob->raw) + return blob->data.raw.data; + else + return git_odb_object_data(blob->data.odb); +} + +git_object_size_t git_blob_rawsize(const git_blob *blob) +{ + GIT_ASSERT_ARG(blob); + + if (blob->raw) + return blob->data.raw.size; + else + return (git_object_size_t)git_odb_object_size(blob->data.odb); +} + +int git_blob__getbuf(git_str *buffer, git_blob *blob) +{ + git_object_size_t size = git_blob_rawsize(blob); + + GIT_ERROR_CHECK_BLOBSIZE(size); + return git_str_set(buffer, git_blob_rawcontent(blob), (size_t)size); +} + +void git_blob__free(void *_blob) +{ + git_blob *blob = (git_blob *) _blob; + if (!blob->raw) + git_odb_object_free(blob->data.odb); + git__free(blob); +} + +int git_blob__parse_raw(void *_blob, const char *data, size_t size) +{ + git_blob *blob = (git_blob *) _blob; + + GIT_ASSERT_ARG(blob); + + blob->raw = 1; + blob->data.raw.data = data; + blob->data.raw.size = size; + return 0; +} + +int git_blob__parse(void *_blob, git_odb_object *odb_obj) +{ + git_blob *blob = (git_blob *) _blob; + + GIT_ASSERT_ARG(blob); + + git_cached_obj_incref((git_cached_obj *)odb_obj); + blob->raw = 0; + blob->data.odb = odb_obj; + return 0; +} + +int git_blob_create_from_buffer( + git_oid *id, git_repository *repo, const void *buffer, size_t len) +{ + int error; + git_odb *odb; + git_odb_stream *stream; + + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(repo); + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJECT_BLOB)) < 0) + return error; + + if ((error = git_odb_stream_write(stream, buffer, len)) == 0) + error = git_odb_stream_finalize_write(id, stream); + + git_odb_stream_free(stream); + return error; +} + +static int write_file_stream( + git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size) +{ + int fd, error; + char buffer[GIT_BUFSIZE_FILEIO]; + git_odb_stream *stream = NULL; + ssize_t read_len = -1; + git_object_size_t written = 0; + + if ((error = git_odb_open_wstream( + &stream, odb, file_size, GIT_OBJECT_BLOB)) < 0) + return error; + + if ((fd = git_futils_open_ro(path)) < 0) { + git_odb_stream_free(stream); + return -1; + } + + while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { + error = git_odb_stream_write(stream, buffer, read_len); + written += read_len; + } + + p_close(fd); + + if (written != file_size || read_len < 0) { + git_error_set(GIT_ERROR_OS, "failed to read file into stream"); + error = -1; + } + + if (!error) + error = git_odb_stream_finalize_write(id, stream); + + git_odb_stream_free(stream); + return error; +} + +static int write_file_filtered( + git_oid *id, + git_object_size_t *size, + git_odb *odb, + const char *full_path, + git_filter_list *fl, + git_repository* repo) +{ + int error; + git_str tgt = GIT_STR_INIT; + + error = git_filter_list__apply_to_file(&tgt, fl, repo, full_path); + + /* Write the file to disk if it was properly filtered */ + if (!error) { + *size = tgt.size; + + error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB); + } + + git_str_dispose(&tgt); + return error; +} + +static int write_symlink( + git_oid *id, git_odb *odb, const char *path, size_t link_size) +{ + char *link_data; + ssize_t read_len; + int error; + + link_data = git__malloc(link_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, link_size); + if (read_len != (ssize_t)link_size) { + git_error_set(GIT_ERROR_OS, "failed to create blob: cannot read symlink '%s'", path); + git__free(link_data); + return -1; + } + + error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB); + git__free(link_data); + return error; +} + +int git_blob__create_from_paths( + git_oid *id, + struct stat *out_st, + git_repository *repo, + const char *content_path, + const char *hint_path, + mode_t hint_mode, + bool try_load_filters) +{ + int error; + struct stat st; + git_odb *odb = NULL; + git_object_size_t size; + mode_t mode; + git_str path = GIT_STR_INIT; + + GIT_ASSERT_ARG(hint_path || !try_load_filters); + + if (!content_path) { + if (git_repository_workdir_path(&path, repo, hint_path) < 0) + return -1; + + content_path = path.ptr; + } + + if ((error = git_fs_path_lstat(content_path, &st)) < 0 || + (error = git_repository_odb(&odb, repo)) < 0) + goto done; + + if (S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_ODB, "cannot create blob from '%s': it is a directory", content_path); + error = GIT_EDIRECTORY; + goto done; + } + + if (out_st) + memcpy(out_st, &st, sizeof(st)); + + size = st.st_size; + mode = hint_mode ? hint_mode : st.st_mode; + + if (S_ISLNK(mode)) { + error = write_symlink(id, odb, content_path, (size_t)size); + } else { + git_filter_list *fl = NULL; + + if (try_load_filters) + /* Load the filters for writing this file to the ODB */ + error = git_filter_list_load( + &fl, repo, NULL, hint_path, + GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); + + if (error < 0) + /* well, that didn't work */; + else if (fl == NULL) + /* No filters need to be applied to the document: we can stream + * directly from disk */ + error = write_file_stream(id, odb, content_path, size); + else { + /* We need to apply one or more filters */ + error = write_file_filtered(id, &size, odb, content_path, fl, repo); + + git_filter_list_free(fl); + } + + /* + * TODO: eventually support streaming filtered files, for files + * which are bigger than a given threshold. This is not a priority + * because applying a filter in streaming mode changes the final + * size of the blob, and without knowing its final size, the blob + * cannot be written in stream mode to the ODB. + * + * The plan is to do streaming writes to a tempfile on disk and then + * opening streaming that file to the ODB, using + * `write_file_stream`. + * + * CAREFULLY DESIGNED APIS YO + */ + } + +done: + git_odb_free(odb); + git_str_dispose(&path); + + return error; +} + +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_from_disk( + git_oid *id, git_repository *repo, const char *path) +{ + int error; + git_str full_path = GIT_STR_INIT; + const char *workdir, *hintpath = NULL; + + if ((error = git_fs_path_prettify(&full_path, path, NULL)) < 0) { + git_str_dispose(&full_path); + return error; + } + + workdir = git_repository_workdir(repo); + + if (workdir && !git__prefixcmp(full_path.ptr, workdir)) + hintpath = full_path.ptr + strlen(workdir); + + error = git_blob__create_from_paths( + id, NULL, repo, git_str_cstr(&full_path), hintpath, 0, !!hintpath); + + git_str_dispose(&full_path); + return error; +} + +typedef struct { + git_writestream parent; + git_filebuf fbuf; + git_repository *repo; + char *hintpath; +} blob_writestream; + +static int blob_writestream_close(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + return 0; +} + +static void blob_writestream_free(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + git__free(stream->hintpath); + git__free(stream); +} + +static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + return git_filebuf_write(&stream->fbuf, buffer, len); +} + +int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath) +{ + int error; + git_str path = GIT_STR_INIT; + blob_writestream *stream; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + stream = git__calloc(1, sizeof(blob_writestream)); + GIT_ERROR_CHECK_ALLOC(stream); + + if (hintpath) { + stream->hintpath = git__strdup(hintpath); + GIT_ERROR_CHECK_ALLOC(stream->hintpath); + } + + stream->repo = repo; + stream->parent.write = blob_writestream_write; + stream->parent.close = blob_writestream_close; + stream->parent.free = blob_writestream_free; + + if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 + || (error = git_str_joinpath(&path, path.ptr, "streamed")) < 0) + goto cleanup; + + if ((error = git_filebuf_open_withsize(&stream->fbuf, git_str_cstr(&path), GIT_FILEBUF_TEMPORARY, + 0666, 2 * 1024 * 1024)) < 0) + goto cleanup; + + *out = (git_writestream *) stream; + +cleanup: + if (error < 0) + blob_writestream_free((git_writestream *) stream); + + git_str_dispose(&path); + return error; +} + +int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream) +{ + int error; + blob_writestream *stream = (blob_writestream *) _stream; + + /* + * We can make this more officient by avoiding writing to + * disk, but for now let's re-use the helper functions we + * have. + */ + if ((error = git_filebuf_flush(&stream->fbuf)) < 0) + goto cleanup; + + error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock, + stream->hintpath, 0, !!stream->hintpath); + +cleanup: + blob_writestream_free(_stream); + return error; + +} + +int git_blob_is_binary(const git_blob *blob) +{ + git_str content = GIT_STR_INIT; + git_object_size_t size; + + GIT_ASSERT_ARG(blob); + + size = git_blob_rawsize(blob); + + git_str_attach_notowned(&content, git_blob_rawcontent(blob), + (size_t)min(size, GIT_FILTER_BYTES_TO_CHECK_NUL)); + return git_str_is_binary(&content); +} + +int git_blob_data_is_binary(const char *str, size_t len) +{ + git_str content = GIT_STR_INIT; + + git_str_attach_notowned(&content, str, len); + + return git_str_is_binary(&content); +} + +int git_blob_filter_options_init( + git_blob_filter_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_blob_filter_options, GIT_BLOB_FILTER_OPTIONS_INIT); + return 0; +} + +int git_blob_filter( + git_buf *out, + git_blob *blob, + const char *path, + git_blob_filter_options *given_opts) +{ + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; + git_filter_list *fl = NULL; + int error = 0; + + GIT_ASSERT_ARG(blob); + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(out); + + 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) + filter_opts.flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES; + + if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD) != 0) + filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD; + + if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { + filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_COMMIT; + +#ifndef GIT_DEPRECATE_HARD + if (opts.commit_id) + git_oid_cpy(&filter_opts.attr_commit_id, opts.commit_id); + else +#endif + git_oid_cpy(&filter_opts.attr_commit_id, &opts.attr_commit_id); + } + + if (!(error = git_filter_list_load_ext( + &fl, git_blob_owner(blob), blob, path, + GIT_FILTER_TO_WORKTREE, &filter_opts))) { + + error = git_filter_list_apply_to_blob(out, fl, blob); + + git_filter_list_free(fl); + } + + return error; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD +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); +} +#endif diff --git a/src/libgit2/blob.h b/src/libgit2/blob.h new file mode 100644 index 000000000..9a5dda225 --- /dev/null +++ b/src/libgit2/blob.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_blob_h__ +#define INCLUDE_blob_h__ + +#include "common.h" + +#include "git2/blob.h" +#include "repository.h" +#include "odb.h" +#include "futils.h" + +struct git_blob { + git_object object; + + union { + git_odb_object *odb; + struct { + const char *data; + git_object_size_t size; + } raw; + } data; + unsigned int raw:1; +}; + +#define GIT_ERROR_CHECK_BLOBSIZE(n) \ + do { \ + if (!git__is_sizet(n)) { \ + git_error_set(GIT_ERROR_NOMEMORY, "blob contents too large to fit in memory"); \ + return -1; \ + } \ + } while(0) + +void git_blob__free(void *blob); +int git_blob__parse(void *blob, git_odb_object *obj); +int git_blob__parse_raw(void *blob, const char *data, size_t size); +int git_blob__getbuf(git_str *buffer, git_blob *blob); + +extern int git_blob__create_from_paths( + git_oid *out_oid, + struct stat *out_st, + git_repository *repo, + const char *full_path, + const char *hint_path, + mode_t hint_mode, + bool apply_filters); + +#endif diff --git a/src/libgit2/branch.c b/src/libgit2/branch.c new file mode 100644 index 000000000..2dd7d2bb4 --- /dev/null +++ b/src/libgit2/branch.c @@ -0,0 +1,823 @@ +/* + * 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 "branch.h" + +#include "buf.h" +#include "commit.h" +#include "tag.h" +#include "config.h" +#include "refspec.h" +#include "refs.h" +#include "remote.h" +#include "annotated_commit.h" +#include "worktree.h" + +#include "git2/branch.h" + +static int retrieve_branch_reference( + git_reference **branch_reference_out, + git_repository *repo, + const char *branch_name, + bool is_remote) +{ + git_reference *branch = NULL; + int error = 0; + char *prefix; + git_str ref_name = GIT_STR_INIT; + + prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; + + if ((error = git_str_joinpath(&ref_name, prefix, branch_name)) < 0) + /* OOM */; + else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) + git_error_set( + GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'", + is_remote ? "remote-tracking" : "local", branch_name); + + *branch_reference_out = branch; /* will be NULL on error */ + + git_str_dispose(&ref_name); + return error; +} + +static int not_a_local_branch(const char *reference_name) +{ + git_error_set( + GIT_ERROR_INVALID, + "reference '%s' is not a local branch.", reference_name); + return -1; +} + +static bool branch_name_is_valid(const char *branch_name) +{ + /* + * Discourage branch name starting with dash, + * https://github.com/git/git/commit/6348624010888b + * and discourage HEAD as branch name, + * https://github.com/git/git/commit/a625b092cc5994 + */ + return branch_name[0] != '-' && git__strcmp(branch_name, "HEAD"); +} + +static int create_branch( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + const char *from, + int force) +{ + int is_unmovable_head = 0; + git_reference *branch = NULL; + git_str canonical_branch_name = GIT_STR_INIT, + log_message = GIT_STR_INIT; + int error = -1; + int bare = git_repository_is_bare(repository); + + GIT_ASSERT_ARG(branch_name); + GIT_ASSERT_ARG(commit); + GIT_ASSERT_ARG(ref_out); + GIT_ASSERT_ARG(git_commit_owner(commit) == repository); + + if (!branch_name_is_valid(branch_name)) { + git_error_set(GIT_ERROR_REFERENCE, "'%s' is not a valid branch name", branch_name); + error = -1; + goto cleanup; + } + + if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { + error = git_branch_is_head(branch); + git_reference_free(branch); + branch = NULL; + + if (error < 0) + goto cleanup; + + is_unmovable_head = error; + } + + if (is_unmovable_head && force) { + git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is " + "the current HEAD of the repository.", branch_name); + error = -1; + goto cleanup; + } + + if (git_str_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) + goto cleanup; + + if (git_str_printf(&log_message, "branch: Created from %s", from) < 0) + goto cleanup; + + error = git_reference_create(&branch, repository, + git_str_cstr(&canonical_branch_name), git_commit_id(commit), force, + git_str_cstr(&log_message)); + + if (!error) + *ref_out = branch; + +cleanup: + git_str_dispose(&canonical_branch_name); + git_str_dispose(&log_message); + return error; +} + +int git_branch_create( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + int force) +{ + char commit_id[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(commit_id, GIT_OID_HEXSZ + 1, git_commit_id(commit)); + return create_branch(ref_out, repository, branch_name, commit, commit_id, force); +} + +int git_branch_create_from_annotated( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_annotated_commit *commit, + int force) +{ + return create_branch(ref_out, + repository, branch_name, commit->commit, commit->description, force); +} + +static int branch_is_checked_out(git_repository *worktree, void *payload) +{ + git_reference *branch = (git_reference *) payload; + git_reference *head = NULL; + int error; + + if (git_repository_is_bare(worktree)) + return 0; + + if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + goto out; + } + + if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC) + goto out; + + error = !git__strcmp(head->target.symbolic, branch->name); + +out: + git_reference_free(head); + return error; +} + +int git_branch_is_checked_out(const git_reference *branch) +{ + GIT_ASSERT_ARG(branch); + + if (!git_reference_is_branch(branch)) + return 0; + return git_repository_foreach_worktree(git_reference_owner(branch), + branch_is_checked_out, (void *)branch) == 1; +} + +int git_branch_delete(git_reference *branch) +{ + int is_head; + git_str config_section = GIT_STR_INIT; + int error = -1; + + GIT_ASSERT_ARG(branch); + + if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) { + git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.", + git_reference_name(branch)); + return GIT_ENOTFOUND; + } + + if ((is_head = git_branch_is_head(branch)) < 0) + return is_head; + + if (is_head) { + git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is " + "the current HEAD of the repository.", git_reference_name(branch)); + return -1; + } + + if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) { + git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is " + "the current HEAD of a linked repository.", git_reference_name(branch)); + return -1; + } + + if (git_str_join(&config_section, '.', "branch", + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) + goto on_error; + + if (git_config_rename_section( + git_reference_owner(branch), git_str_cstr(&config_section), NULL) < 0) + goto on_error; + + error = git_reference_delete(branch); + +on_error: + git_str_dispose(&config_section); + return error; +} + +typedef struct { + git_reference_iterator *iter; + unsigned int flags; +} branch_iter; + +int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter) +{ + branch_iter *iter = (branch_iter *) _iter; + git_reference *ref; + int error; + + while ((error = git_reference_next(&ref, iter->iter)) == 0) { + if ((iter->flags & GIT_BRANCH_LOCAL) && + !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) { + *out = ref; + *out_type = GIT_BRANCH_LOCAL; + + return 0; + } else if ((iter->flags & GIT_BRANCH_REMOTE) && + !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { + *out = ref; + *out_type = GIT_BRANCH_REMOTE; + + return 0; + } else { + git_reference_free(ref); + } + } + + return error; +} + +int git_branch_iterator_new( + git_branch_iterator **out, + git_repository *repo, + git_branch_t list_flags) +{ + branch_iter *iter; + + iter = git__calloc(1, sizeof(branch_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->flags = list_flags; + + if (git_reference_iterator_new(&iter->iter, repo) < 0) { + git__free(iter); + return -1; + } + + *out = (git_branch_iterator *) iter; + + return 0; +} + +void git_branch_iterator_free(git_branch_iterator *_iter) +{ + branch_iter *iter = (branch_iter *) _iter; + + if (iter == NULL) + return; + + git_reference_iterator_free(iter->iter); + git__free(iter); +} + +int git_branch_move( + git_reference **out, + git_reference *branch, + const char *new_branch_name, + int force) +{ + git_str new_reference_name = GIT_STR_INIT, + old_config_section = GIT_STR_INIT, + new_config_section = GIT_STR_INIT, + log_message = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(branch); + GIT_ASSERT_ARG(new_branch_name); + + if (!git_reference_is_branch(branch)) + return not_a_local_branch(git_reference_name(branch)); + + if ((error = git_str_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) + goto done; + + if ((error = git_str_printf(&log_message, "branch: renamed %s to %s", + git_reference_name(branch), git_str_cstr(&new_reference_name))) < 0) + goto done; + + /* first update ref then config so failure won't trash config */ + + error = git_reference_rename( + out, branch, git_str_cstr(&new_reference_name), force, + git_str_cstr(&log_message)); + if (error < 0) + goto done; + + git_str_join(&old_config_section, '.', "branch", + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)); + git_str_join(&new_config_section, '.', "branch", new_branch_name); + + error = git_config_rename_section( + git_reference_owner(branch), + git_str_cstr(&old_config_section), + git_str_cstr(&new_config_section)); + +done: + git_str_dispose(&new_reference_name); + git_str_dispose(&old_config_section); + git_str_dispose(&new_config_section); + git_str_dispose(&log_message); + + return error; +} + +int git_branch_lookup( + git_reference **ref_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type) +{ + int error = -1; + + GIT_ASSERT_ARG(ref_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(branch_name); + + 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: + GIT_ASSERT(false); + } + return error; +} + +int git_branch_name( + const char **out, + const git_reference *ref) +{ + const char *branch_name; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + + branch_name = ref->name; + + if (git_reference_is_branch(ref)) { + branch_name += strlen(GIT_REFS_HEADS_DIR); + } else if (git_reference_is_remote(ref)) { + branch_name += strlen(GIT_REFS_REMOTES_DIR); + } else { + git_error_set(GIT_ERROR_INVALID, + "reference '%s' is neither a local nor a remote branch.", ref->name); + return -1; + } + *out = branch_name; + return 0; +} + +static int retrieve_upstream_configuration( + git_str *out, + const git_config *config, + const char *canonical_branch_name, + const char *format) +{ + git_str buf = GIT_STR_INIT; + int error; + + if (git_str_printf(&buf, format, + canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0) + return -1; + + error = git_config__get_string_buf(out, config, git_str_cstr(&buf)); + git_str_dispose(&buf); + return error; +} + +int git_branch_upstream_name( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_name, repo, refname); +} + +int git_branch__upstream_name( + git_str *out, + git_repository *repo, + const char *refname) +{ + git_str remote_name = GIT_STR_INIT; + git_str merge_name = GIT_STR_INIT; + git_str buf = GIT_STR_INIT; + int error = -1; + git_remote *remote = NULL; + const git_refspec *refspec; + git_config *config; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if (!git_reference__is_branch(refname)) + return not_a_local_branch(refname); + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + return error; + + if ((error = retrieve_upstream_configuration( + &remote_name, config, refname, "branch.%s.remote")) < 0) + goto cleanup; + + if ((error = retrieve_upstream_configuration( + &merge_name, config, refname, "branch.%s.merge")) < 0) + goto cleanup; + + if (git_str_len(&remote_name) == 0 || git_str_len(&merge_name) == 0) { + git_error_set(GIT_ERROR_REFERENCE, + "branch '%s' does not have an upstream", refname); + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (strcmp(".", git_str_cstr(&remote_name)) != 0) { + if ((error = git_remote_lookup(&remote, repo, git_str_cstr(&remote_name))) < 0) + goto cleanup; + + refspec = git_remote__matching_refspec(remote, git_str_cstr(&merge_name)); + if (!refspec) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (git_refspec__transform(&buf, refspec, git_str_cstr(&merge_name)) < 0) + goto cleanup; + } else + if (git_str_set(&buf, git_str_cstr(&merge_name), git_str_len(&merge_name)) < 0) + goto cleanup; + + git_str_swap(out, &buf); + +cleanup: + git_config_free(config); + git_remote_free(remote); + git_str_dispose(&remote_name); + git_str_dispose(&merge_name); + git_str_dispose(&buf); + return error; +} + +static int git_branch_upstream_with_format( + git_str *out, + git_repository *repo, + const char *refname, + const char *format, + const char *format_name) +{ + git_config *cfg; + int error; + + if (!git_reference__is_branch(refname)) + return not_a_local_branch(refname); + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || + (error = retrieve_upstream_configuration(out, cfg, refname, format)) < 0) + return error; + + if (git_str_len(out) == 0) { + git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream %s", refname, format_name); + error = GIT_ENOTFOUND; + } + + return error; +} + +int git_branch_upstream_remote( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_remote, repo, refname); +} + +int git_branch__upstream_remote( + git_str *out, + git_repository *repo, + const char *refname) +{ + return git_branch_upstream_with_format(out, repo, refname, "branch.%s.remote", "remote"); +} + +int git_branch_upstream_merge( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_merge, repo, refname); +} + +int git_branch__upstream_merge( + git_str *out, + git_repository *repo, + const char *refname) +{ + return git_branch_upstream_with_format(out, repo, refname, "branch.%s.merge", "merge"); +} + +int git_branch_remote_name( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__remote_name, repo, refname); +} + +int git_branch__remote_name( + git_str *out, + git_repository *repo, + const char *refname) +{ + git_strarray remote_list = {0}; + size_t i; + git_remote *remote; + const git_refspec *fetchspec; + int error = 0; + char *remote_name = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + /* Verify that this is a remote branch */ + if (!git_reference__is_remote(refname)) { + git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.", + refname); + error = GIT_ERROR; + goto cleanup; + } + + /* Get the remotes */ + if ((error = git_remote_list(&remote_list, repo)) < 0) + goto cleanup; + + /* Find matching remotes */ + for (i = 0; i < remote_list.count; i++) { + if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0) + continue; + + fetchspec = git_remote__matching_dst_refspec(remote, refname); + if (fetchspec) { + /* If we have not already set out yet, then set + * it to the matching remote name. Otherwise + * multiple remotes match this reference, and it + * is ambiguous. */ + if (!remote_name) { + remote_name = remote_list.strings[i]; + } else { + git_remote_free(remote); + + git_error_set(GIT_ERROR_REFERENCE, + "reference '%s' is ambiguous", refname); + error = GIT_EAMBIGUOUS; + goto cleanup; + } + } + + git_remote_free(remote); + } + + if (remote_name) { + git_str_clear(out); + error = git_str_puts(out, remote_name); + } else { + git_error_set(GIT_ERROR_REFERENCE, + "could not determine remote for '%s'", refname); + error = GIT_ENOTFOUND; + } + +cleanup: + if (error < 0) + git_str_dispose(out); + + git_strarray_dispose(&remote_list); + return error; +} + +int git_branch_upstream( + git_reference **tracking_out, + const git_reference *branch) +{ + int error; + git_str tracking_name = GIT_STR_INIT; + + if ((error = git_branch__upstream_name(&tracking_name, + git_reference_owner(branch), git_reference_name(branch))) < 0) + return error; + + error = git_reference_lookup( + tracking_out, + git_reference_owner(branch), + git_str_cstr(&tracking_name)); + + git_str_dispose(&tracking_name); + return error; +} + +static int unset_upstream(git_config *config, const char *shortname) +{ + git_str buf = GIT_STR_INIT; + + if (git_str_printf(&buf, "branch.%s.remote", shortname) < 0) + return -1; + + if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) + goto on_error; + + git_str_clear(&buf); + if (git_str_printf(&buf, "branch.%s.merge", shortname) < 0) + goto on_error; + + if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) + goto on_error; + + git_str_dispose(&buf); + return 0; + +on_error: + git_str_dispose(&buf); + return -1; +} + +int git_branch_set_upstream(git_reference *branch, const char *branch_name) +{ + git_str key = GIT_STR_INIT, remote_name = GIT_STR_INIT, merge_refspec = GIT_STR_INIT; + git_reference *upstream; + git_repository *repo; + git_remote *remote = NULL; + git_config *config; + const char *refname, *shortname; + int local, error; + const git_refspec *fetchspec; + + 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 = refname + strlen(GIT_REFS_HEADS_DIR); + + /* 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 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, branch_name, GIT_BRANCH_REMOTE) == 0) + local = 0; + else { + git_error_set(GIT_ERROR_REFERENCE, + "cannot set upstream for branch '%s'", shortname); + return GIT_ENOTFOUND; + } + + /* + * 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_str_puts(&remote_name, "."); + else + error = git_branch__remote_name(&remote_name, repo, git_reference_name(upstream)); + + if (error < 0) + goto on_error; + + /* Update the upstream branch config with the new name */ + if (git_str_printf(&key, "branch.%s.remote", shortname) < 0) + goto on_error; + + if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&remote_name)) < 0) + goto on_error; + + if (local) { + /* A local branch uses the upstream refname directly */ + if (git_str_puts(&merge_refspec, git_reference_name(upstream)) < 0) + goto on_error; + } else { + /* We transform the upstream branch name according to the remote's refspecs */ + if (git_remote_lookup(&remote, repo, git_str_cstr(&remote_name)) < 0) + goto on_error; + + fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); + 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_str_clear(&key); + if (git_str_printf(&key, "branch.%s.merge", shortname) < 0) + goto on_error; + + if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&merge_refspec)) < 0) + goto on_error; + + git_reference_free(upstream); + git_str_dispose(&key); + git_str_dispose(&remote_name); + git_str_dispose(&merge_refspec); + + return 0; + +on_error: + git_reference_free(upstream); + git_str_dispose(&key); + git_str_dispose(&remote_name); + git_str_dispose(&merge_refspec); + git_remote_free(remote); + + return -1; +} + +int git_branch_is_head( + const git_reference *branch) +{ + git_reference *head; + bool is_same = false; + int error; + + GIT_ASSERT_ARG(branch); + + if (!git_reference_is_branch(branch)) + return false; + + error = git_repository_head(&head, git_reference_owner(branch)); + + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) + return false; + + if (error < 0) + return -1; + + is_same = strcmp( + git_reference_name(branch), + git_reference_name(head)) == 0; + + git_reference_free(head); + + return is_same; +} + +int git_branch_name_is_valid(int *valid, const char *name) +{ + git_str ref_name = GIT_STR_INIT; + int error = 0; + + GIT_ASSERT(valid); + + *valid = 0; + + if (!name || !branch_name_is_valid(name)) + goto done; + + if ((error = git_str_puts(&ref_name, GIT_REFS_HEADS_DIR)) < 0 || + (error = git_str_puts(&ref_name, name)) < 0) + goto done; + + error = git_reference_name_is_valid(valid, ref_name.ptr); + +done: + git_str_dispose(&ref_name); + return error; +} diff --git a/src/libgit2/branch.h b/src/libgit2/branch.h new file mode 100644 index 000000000..b4db42a01 --- /dev/null +++ b/src/libgit2/branch.h @@ -0,0 +1,31 @@ +/* + * 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_branch_h__ +#define INCLUDE_branch_h__ + +#include "common.h" + +#include "str.h" + +int git_branch__remote_name( + git_str *out, + git_repository *repo, + const char *refname); +int git_branch__upstream_remote( + git_str *out, + git_repository *repo, + const char *refname); +int git_branch__upstream_merge( + git_str *out, + git_repository *repo, + const char *refname); +int git_branch__upstream_name( + git_str *tracking_name, + git_repository *repo, + const char *canonical_branch_name); + +#endif diff --git a/src/libgit2/buf.c b/src/libgit2/buf.c new file mode 100644 index 000000000..652f5dd52 --- /dev/null +++ b/src/libgit2/buf.c @@ -0,0 +1,126 @@ +/* + * 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 "buf.h" +#include "common.h" + +int git_buf_sanitize(git_buf *buf) +{ + GIT_ASSERT_ARG(buf); + + if (buf->reserved > 0) + buf->ptr[0] = '\0'; + else + buf->ptr = git_str__initstr; + + buf->size = 0; + return 0; +} + +int git_buf_tostr(git_str *out, git_buf *buf) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(buf); + + if (git_buf_sanitize(buf) < 0) + return -1; + + out->ptr = buf->ptr; + out->asize = buf->reserved; + out->size = buf->size; + + buf->ptr = git_str__initstr; + buf->reserved = 0; + buf->size = 0; + + return 0; +} + +int git_buf_fromstr(git_buf *out, git_str *str) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(str); + + out->ptr = str->ptr; + out->reserved = str->asize; + out->size = str->size; + + str->ptr = git_str__initstr; + str->asize = 0; + str->size = 0; + + return 0; +} + +void git_buf_dispose(git_buf *buf) +{ + if (!buf) + return; + + if (buf->ptr != git_str__initstr) + git__free(buf->ptr); + + buf->ptr = git_str__initstr; + buf->reserved = 0; + buf->size = 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_buf_grow(git_buf *buffer, size_t target_size) +{ + char *newptr; + + if (buffer->reserved >= target_size) + return 0; + + if (buffer->ptr == git_str__initstr) + newptr = git__malloc(target_size); + else + newptr = git__realloc(buffer->ptr, target_size); + + if (!newptr) + return -1; + + buffer->ptr = newptr; + buffer->reserved = target_size; + return 0; +} + +int git_buf_set(git_buf *buffer, const void *data, size_t datalen) +{ + size_t alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, datalen, 1); + + if (git_buf_grow(buffer, alloclen) < 0) + return -1; + + memmove(buffer->ptr, data, datalen); + buffer->size = datalen; + buffer->ptr[buffer->size] = '\0'; + + return 0; +} + +int git_buf_is_binary(const git_buf *buf) +{ + git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); + return git_str_is_binary(&str); +} + +int git_buf_contains_nul(const git_buf *buf) +{ + git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); + return git_str_contains_nul(&str); +} + +void git_buf_free(git_buf *buffer) +{ + git_buf_dispose(buffer); +} + +#endif diff --git a/src/libgit2/buf.h b/src/libgit2/buf.h new file mode 100644 index 000000000..4bc7f2709 --- /dev/null +++ b/src/libgit2/buf.h @@ -0,0 +1,50 @@ +/* + * 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_buf_h__ +#define INCLUDE_buf_h__ + +#include "git2/buffer.h" +#include "common.h" + +/* + * Adapts a private API that takes a `git_str` into a public API that + * takes a `git_buf`. + */ + +#define GIT_BUF_WRAP_PRIVATE(buf, fn, ...) \ + { \ + git_str str = GIT_STR_INIT; \ + int error; \ + if ((error = git_buf_tostr(&str, buf)) == 0 && \ + (error = fn(&str, __VA_ARGS__)) == 0) \ + error = git_buf_fromstr(buf, &str); \ + git_str_dispose(&str); \ + return error; \ +} + +/** + * "Sanitizes" a buffer from user input. This simply ensures that the + * `git_buf` has nice defaults if the user didn't set the members to + * anything, so that if we return early we don't leave it populated + * with nonsense. + */ +extern int git_buf_sanitize(git_buf *from_user); + +/** + * Populate a `git_str` from a `git_buf` for passing to libgit2 internal + * functions. Sanitizes the given `git_buf` before proceeding. The + * `git_buf` will no longer point to this memory. + */ +extern int git_buf_tostr(git_str *out, git_buf *buf); + +/** + * Populate a `git_buf` from a `git_str` for returning to a user. + * The `git_str` will no longer point to this memory. + */ +extern int git_buf_fromstr(git_buf *out, git_str *str); + +#endif diff --git a/src/libgit2/cache.c b/src/libgit2/cache.c new file mode 100644 index 000000000..2f68e357c --- /dev/null +++ b/src/libgit2/cache.c @@ -0,0 +1,253 @@ +/* + * 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 "cache.h" + +#include "repository.h" +#include "commit.h" +#include "thread.h" +#include "util.h" +#include "odb.h" +#include "object.h" +#include "git2/oid.h" + +bool git_cache__enabled = true; +ssize_t git_cache__max_storage = (256 * 1024 * 1024); +git_atomic_ssize git_cache__current_storage = {0}; + +static size_t git_cache__max_object_size[8] = { + 0, /* GIT_OBJECT__EXT1 */ + 4096, /* GIT_OBJECT_COMMIT */ + 4096, /* GIT_OBJECT_TREE */ + 0, /* GIT_OBJECT_BLOB */ + 4096, /* GIT_OBJECT_TAG */ + 0, /* GIT_OBJECT__EXT2 */ + 0, /* GIT_OBJECT_OFS_DELTA */ + 0 /* GIT_OBJECT_REF_DELTA */ +}; + +int git_cache_set_max_object_size(git_object_t type, size_t size) +{ + if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) { + git_error_set(GIT_ERROR_INVALID, "type out of range"); + return -1; + } + + git_cache__max_object_size[type] = size; + return 0; +} + +int git_cache_init(git_cache *cache) +{ + memset(cache, 0, sizeof(*cache)); + + 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; +} + +/* called with lock */ +static void clear_cache(git_cache *cache) +{ + git_cached_obj *evict = NULL; + + if (git_cache_size(cache) == 0) + return; + + git_oidmap_foreach_value(cache->map, evict, { + git_cached_obj_decref(evict); + }); + + git_oidmap_clear(cache->map); + git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory); + cache->used_memory = 0; +} + +void git_cache_clear(git_cache *cache) +{ + if (git_rwlock_wrlock(&cache->lock) < 0) + return; + + clear_cache(cache); + + git_rwlock_wrunlock(&cache->lock); +} + +void git_cache_dispose(git_cache *cache) +{ + git_cache_clear(cache); + git_oidmap_free(cache->map); + git_rwlock_free(&cache->lock); + git__memzero(cache, sizeof(*cache)); +} + +/* Called with lock */ +static void cache_evict_entries(git_cache *cache) +{ + 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) { + git_cached_obj *evict; + const git_oid *key; + + if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER) + break; + + evict_count--; + evicted_memory += evict->size; + git_oidmap_delete(cache->map, key); + git_cached_obj_decref(evict); + } + + cache->used_memory -= evicted_memory; + git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory); +} + +static bool cache_should_store(git_object_t object_type, size_t object_size) +{ + size_t max_size = git_cache__max_object_size[object_type]; + return git_cache__enabled && object_size < max_size; +} + +static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) +{ + git_cached_obj *entry; + + if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) + return NULL; + + if ((entry = git_oidmap_get(cache->map, oid)) != NULL) { + if (flags && entry->flags != flags) { + entry = NULL; + } else { + git_cached_obj_incref(entry); + } + } + + git_rwlock_rdunlock(&cache->lock); + + return entry; +} + +static void *cache_store(git_cache *cache, git_cached_obj *entry) +{ + git_cached_obj *stored_entry; + + git_cached_obj_incref(entry); + + if (!git_cache__enabled && cache->used_memory > 0) { + git_cache_clear(cache); + return entry; + } + + if (!cache_should_store(entry->type, entry->size)) + return entry; + + if (git_rwlock_wrlock(&cache->lock) < 0) + return entry; + + /* soften the load on the cache */ + if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage) + cache_evict_entries(cache); + + /* not found */ + 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); + } + } + /* found */ + else { + 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) { + 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 */ + } + } + + git_rwlock_wrunlock(&cache->lock); + return entry; +} + +void *git_cache_store_raw(git_cache *cache, git_odb_object *entry) +{ + entry->cached.flags = GIT_CACHE_STORE_RAW; + return cache_store(cache, (git_cached_obj *)entry); +} + +void *git_cache_store_parsed(git_cache *cache, git_object *entry) +{ + entry->cached.flags = GIT_CACHE_STORE_PARSED; + return cache_store(cache, (git_cached_obj *)entry); +} + +git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_RAW); +} + +git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_PARSED); +} + +void *git_cache_get_any(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_ANY); +} + +void git_cached_obj_decref(void *_obj) +{ + git_cached_obj *obj = _obj; + + if (git_atomic32_dec(&obj->refcount) == 0) { + switch (obj->flags) { + case GIT_CACHE_STORE_RAW: + git_odb_object__free(_obj); + break; + + case GIT_CACHE_STORE_PARSED: + git_object__free(_obj); + break; + + default: + git__free(_obj); + break; + } + } +} diff --git a/src/libgit2/cache.h b/src/libgit2/cache.h new file mode 100644 index 000000000..42c4fa80d --- /dev/null +++ b/src/libgit2/cache.h @@ -0,0 +1,69 @@ +/* + * 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_cache_h__ +#define INCLUDE_cache_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/oid.h" +#include "git2/odb.h" + +#include "thread.h" +#include "oidmap.h" + +enum { + GIT_CACHE_STORE_ANY = 0, + GIT_CACHE_STORE_RAW = 1, + GIT_CACHE_STORE_PARSED = 2 +}; + +typedef struct { + git_oid oid; + int16_t type; /* git_object_t value */ + uint16_t flags; /* GIT_CACHE_STORE value */ + size_t size; + git_atomic32 refcount; +} git_cached_obj; + +typedef struct { + git_oidmap *map; + git_rwlock lock; + ssize_t used_memory; +} git_cache; + +extern bool git_cache__enabled; +extern ssize_t git_cache__max_storage; +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_dispose(git_cache *cache); +void git_cache_clear(git_cache *cache); + +void *git_cache_store_raw(git_cache *cache, git_odb_object *entry); +void *git_cache_store_parsed(git_cache *cache, git_object *entry); + +git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid); +git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid); +void *git_cache_get_any(git_cache *cache, const git_oid *oid); + +GIT_INLINE(size_t) git_cache_size(git_cache *cache) +{ + return (size_t)git_oidmap_size(cache->map); +} + +GIT_INLINE(void) git_cached_obj_incref(void *_obj) +{ + git_cached_obj *obj = _obj; + git_atomic32_inc(&obj->refcount); +} + +void git_cached_obj_decref(void *_obj); + +#endif diff --git a/src/libgit2/checkout.c b/src/libgit2/checkout.c new file mode 100644 index 000000000..6a4643196 --- /dev/null +++ b/src/libgit2/checkout.c @@ -0,0 +1,2813 @@ +/* + * 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 "checkout.h" + +#include "git2/repository.h" +#include "git2/refs.h" +#include "git2/tree.h" +#include "git2/blob.h" +#include "git2/config.h" +#include "git2/diff.h" +#include "git2/submodule.h" +#include "git2/sys/index.h" +#include "git2/sys/filter.h" +#include "git2/merge.h" + +#include "refs.h" +#include "repository.h" +#include "index.h" +#include "filter.h" +#include "blob.h" +#include "diff.h" +#include "diff_generate.h" +#include "pathspec.h" +#include "diff_xdiff.h" +#include "fs_path.h" +#include "attr.h" +#include "pool.h" +#include "strmap.h" +#include "path.h" + +/* See docs/checkout-internals.md for more information */ + +enum { + CHECKOUT_ACTION__NONE = 0, + CHECKOUT_ACTION__REMOVE = 1, + CHECKOUT_ACTION__UPDATE_BLOB = 2, + CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, + CHECKOUT_ACTION__CONFLICT = 8, + CHECKOUT_ACTION__REMOVE_CONFLICT = 16, + CHECKOUT_ACTION__UPDATE_CONFLICT = 32, + CHECKOUT_ACTION__MAX = 32, + CHECKOUT_ACTION__REMOVE_AND_UPDATE = + (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE) +}; + +typedef struct { + git_repository *repo; + git_iterator *target; + git_diff *diff; + git_checkout_options opts; + bool opts_free_baseline; + char *pfx; + git_index *index; + git_pool pool; + git_vector removes; + git_vector remove_conflicts; + git_vector update_conflicts; + git_vector *update_reuc; + git_vector *update_names; + git_str target_path; + size_t target_len; + git_str tmp; + unsigned int strategy; + int can_symlink; + int respect_filemode; + bool reload_submodules; + size_t total_steps; + size_t completed_steps; + git_checkout_perfdata perfdata; + git_strmap *mkdir_map; + git_attr_session attr_session; +} checkout_data; + +typedef struct { + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; + + unsigned int name_collision:1, + directoryfile:1, + one_to_two:1, + binary:1, + submodule:1; +} checkout_conflictdata; + +static int checkout_notify( + checkout_data *data, + git_checkout_notify_t why, + const git_diff_delta *delta, + const git_index_entry *wditem) +{ + git_diff_file wdfile; + const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL; + const char *path = NULL; + + if (!data->opts.notify_cb || + (why & data->opts.notify_flags) == 0) + return 0; + + if (wditem) { + memset(&wdfile, 0, sizeof(wdfile)); + + git_oid_cpy(&wdfile.id, &wditem->id); + wdfile.path = wditem->path; + wdfile.size = wditem->file_size; + wdfile.flags = GIT_DIFF_FLAG_VALID_ID; + wdfile.mode = wditem->mode; + + workdir = &wdfile; + + path = wditem->path; + } + + if (delta) { + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_MODIFIED: + case GIT_DELTA_TYPECHANGE: + default: + baseline = &delta->old_file; + target = &delta->new_file; + break; + case GIT_DELTA_ADDED: + case GIT_DELTA_IGNORED: + case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: + target = &delta->new_file; + break; + case GIT_DELTA_DELETED: + baseline = &delta->old_file; + break; + } + + path = delta->old_file.path; + } + + { + int error = data->opts.notify_cb( + why, path, baseline, target, workdir, data->opts.notify_payload); + + return git_error_set_after_callback_function( + error, "git_checkout notification"); + } +} + +GIT_INLINE(bool) is_workdir_base_or_new( + const git_oid *workdir_id, + const git_diff_file *baseitem, + const git_diff_file *newitem) +{ + return (git_oid__cmp(&baseitem->id, workdir_id) == 0 || + git_oid__cmp(&newitem->id, workdir_id) == 0); +} + +GIT_INLINE(bool) is_filemode_changed(git_filemode_t a, git_filemode_t b, int respect_filemode) +{ + /* If core.filemode = false, ignore links in the repository and executable bit changes */ + if (!respect_filemode) { + if (a == S_IFLNK) + a = GIT_FILEMODE_BLOB; + if (b == S_IFLNK) + b = GIT_FILEMODE_BLOB; + + a &= ~0111; + b &= ~0111; + } + + return (a != b); +} + +static bool checkout_is_workdir_modified( + checkout_data *data, + const git_diff_file *baseitem, + const git_diff_file *newitem, + const git_index_entry *wditem) +{ + git_oid oid; + const git_index_entry *ie; + + /* handle "modified" submodule */ + if (wditem->mode == GIT_FILEMODE_COMMIT) { + git_submodule *sm; + unsigned int sm_status = 0; + const git_oid *sm_oid = NULL; + bool rval = false; + + if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0) { + git_error_clear(); + return true; + } + + if (git_submodule_status(&sm_status, data->repo, wditem->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED) < 0 || + GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) + rval = true; + else if ((sm_oid = git_submodule_wd_id(sm)) == NULL) + rval = false; + else + rval = (git_oid__cmp(&baseitem->id, sm_oid) != 0); + + git_submodule_free(sm); + return rval; + } + + /* + * Look at the cache to decide if the workdir is modified: if the + * cache contents match the workdir contents, then we do not need + * to examine the working directory directly, instead we can + * examine the cache to see if _it_ has been modified. This allows + * us to avoid touching the disk. + */ + ie = git_index_get_bypath(data->index, wditem->path, 0); + + if (ie != NULL && + !git_index_entry_newer_than_index(ie, data->index) && + git_index_time_eq(&wditem->mtime, &ie->mtime) && + wditem->file_size == ie->file_size && + !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { + + /* The workdir is modified iff the index entry is modified */ + return !is_workdir_base_or_new(&ie->id, baseitem, newitem) || + is_filemode_changed(baseitem->mode, ie->mode, data->respect_filemode); + } + + /* depending on where base is coming from, we may or may not know + * the actual size of the data, so we can't rely on this shortcut. + */ + if (baseitem->size && wditem->file_size != baseitem->size) + return true; + + /* if the workdir item is a directory, it cannot be a modified file */ + if (S_ISDIR(wditem->mode)) + return false; + + if (is_filemode_changed(baseitem->mode, wditem->mode, data->respect_filemode)) + return true; + + if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) + return false; + + /* Allow the checkout if the workdir is not modified *or* if the checkout + * target's contents are already in the working directory. + */ + return !is_workdir_base_or_new(&oid, baseitem, newitem); +} + +#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ + ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO) + +static int checkout_action_common( + int *action, + checkout_data *data, + const git_diff_delta *delta, + const git_index_entry *wd) +{ + git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + *action = (*action & ~CHECKOUT_ACTION__REMOVE); + + if ((*action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) { + if (S_ISGITLINK(delta->new_file.mode)) + *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB) | + CHECKOUT_ACTION__UPDATE_SUBMODULE; + + /* to "update" a symlink, we must remove the old one first */ + if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) + *action |= CHECKOUT_ACTION__REMOVE; + + /* if the file is on disk and doesn't match our mode, force update */ + if (wd && + GIT_PERMS_IS_EXEC(wd->mode) != GIT_PERMS_IS_EXEC(delta->new_file.mode)) + *action |= CHECKOUT_ACTION__REMOVE; + + notify = GIT_CHECKOUT_NOTIFY_UPDATED; + } + + if ((*action & CHECKOUT_ACTION__CONFLICT) != 0) + notify = GIT_CHECKOUT_NOTIFY_CONFLICT; + + return checkout_notify(data, notify, delta, wd); +} + +static int checkout_action_no_wd( + int *action, + checkout_data *data, + const git_diff_delta *delta) +{ + int error = 0; + + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: /* case 12 */ + error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL); + if (error) + return error; + *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ + *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT); + break; + case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ + if (delta->new_file.mode == GIT_FILEMODE_TREE) + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_DELETED: /* case 8 or 25 */ + *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, NULL); +} + +static int checkout_target_fullpath( + git_str **out, checkout_data *data, const char *path) +{ + git_str_truncate(&data->target_path, data->target_len); + + if (path && git_str_puts(&data->target_path, path) < 0) + return -1; + + if (git_path_validate_str_length(data->repo, &data->target_path) < 0) + return -1; + + *out = &data->target_path; + + return 0; +} + +static bool wd_item_is_removable( + checkout_data *data, const git_index_entry *wd) +{ + git_str *full; + + if (wd->mode != GIT_FILEMODE_TREE) + return true; + + if (checkout_target_fullpath(&full, data, wd->path) < 0) + return false; + + return !full || !git_fs_path_contains(full, DOT_GIT); +} + +static int checkout_queue_remove(checkout_data *data, const char *path) +{ + char *copy = git_pool_strdup(&data->pool, path); + GIT_ERROR_CHECK_ALLOC(copy); + return git_vector_insert(&data->removes, copy); +} + +/* note that this advances the iterator over the wd item */ +static int checkout_action_wd_only( + checkout_data *data, + git_iterator *workdir, + const git_index_entry **wditem, + git_vector *pathspec) +{ + int error = 0; + bool remove = false; + git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; + const git_index_entry *wd = *wditem; + + if (!git_pathspec__match( + pathspec, wd->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) { + + if (wd->mode == GIT_FILEMODE_TREE) + return git_iterator_advance_into(wditem, workdir); + else + return git_iterator_advance(wditem, workdir); + } + + /* check if item is tracked in the index but not in the checkout diff */ + if (data->index != NULL) { + size_t pos; + + error = git_index__find_pos( + &pos, data->index, wd->path, 0, GIT_INDEX_STAGE_ANY); + + if (wd->mode != GIT_FILEMODE_TREE) { + if (!error) { /* found by git_index__find_pos call */ + notify = GIT_CHECKOUT_NOTIFY_DIRTY; + remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); + } else if (error != GIT_ENOTFOUND) + return error; + else + error = 0; /* git_index__find_pos does not set error msg */ + } else { + /* for tree entries, we have to see if there are any index + * entries that are contained inside that tree + */ + const git_index_entry *e = git_index_get_byindex(data->index, pos); + + if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) + return git_iterator_advance_into(wditem, workdir); + } + } + + if (notify != GIT_CHECKOUT_NOTIFY_NONE) { + /* if we found something in the index, notify and advance */ + if ((error = checkout_notify(data, notify, NULL, wd)) != 0) + return error; + + if (remove && wd_item_is_removable(data, wd)) + error = checkout_queue_remove(data, wd->path); + + if (!error) + error = git_iterator_advance(wditem, workdir); + } else { + /* untracked or ignored - can't know which until we advance through */ + bool over = false, removable = wd_item_is_removable(data, wd); + git_iterator_status_t untracked_state; + + /* copy the entry for issuing notification callback later */ + git_index_entry saved_wd = *wd; + git_str_sets(&data->tmp, wd->path); + saved_wd.path = data->tmp.ptr; + + error = git_iterator_advance_over( + wditem, &untracked_state, workdir); + if (error == GIT_ITEROVER) + over = true; + else if (error < 0) + return error; + + if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) { + notify = GIT_CHECKOUT_NOTIFY_IGNORED; + remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); + } else { + notify = GIT_CHECKOUT_NOTIFY_UNTRACKED; + remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0); + } + + if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0) + return error; + + if (remove && removable) + error = checkout_queue_remove(data, saved_wd.path); + + if (!error && over) /* restore ITEROVER if needed */ + error = GIT_ITEROVER; + } + + return error; +} + +static bool submodule_is_config_only( + checkout_data *data, + const char *path) +{ + git_submodule *sm = NULL; + unsigned int sm_loc = 0; + bool rval = false; + + if (git_submodule_lookup(&sm, data->repo, path) < 0) + return true; + + if (git_submodule_location(&sm_loc, sm) < 0 || + sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG) + rval = true; + + git_submodule_free(sm); + + return rval; +} + +static bool checkout_is_empty_dir(checkout_data *data, const char *path) +{ + git_str *fullpath; + + if (checkout_target_fullpath(&fullpath, data, path) < 0) + return false; + + return git_fs_path_is_empty_dir(fullpath->ptr); +} + +static int checkout_action_with_wd( + int *action, + checkout_data *data, + const git_diff_delta *delta, + git_iterator *workdir, + const git_index_entry *wd) +{ + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) { + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); + *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE); + } + break; + case GIT_DELTA_ADDED: /* case 3, 4 or 6 */ + if (git_iterator_current_is_ignored(workdir)) + *action = CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, UPDATE_BLOB); + else + *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); + break; + case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */ + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); + else + *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); + break; + case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ + if (wd->mode != GIT_FILEMODE_COMMIT && + checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) + *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); + else + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ + if (delta->old_file.mode == GIT_FILEMODE_TREE) { + if (wd->mode == GIT_FILEMODE_TREE) + /* either deleting items in old tree will delete the wd dir, + * or we'll get a conflict when we attempt blob update... + */ + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + else if (wd->mode == GIT_FILEMODE_COMMIT) { + /* workdir is possibly a "phantom" submodule - treat as a + * tree if the only submodule info came from the config + */ + if (submodule_is_config_only(data, wd->path)) + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + else + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + } else + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); + } + else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + else + *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); + + /* don't update if the typechange is to a tree */ + if (delta->new_file.mode == GIT_FILEMODE_TREE) + *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, wd); +} + +static int checkout_action_with_wd_blocker( + int *action, + checkout_data *data, + const git_diff_delta *delta, + const git_index_entry *wd) +{ + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + /* should show delta as dirty / deleted */ + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); + break; + case GIT_DELTA_ADDED: + case GIT_DELTA_MODIFIED: + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + case GIT_DELTA_DELETED: + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); + break; + case GIT_DELTA_TYPECHANGE: + /* not 100% certain about this... */ + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, wd); +} + +static int checkout_action_with_wd_dir( + int *action, + checkout_data *data, + const git_diff_delta *delta, + git_iterator *workdir, + const git_index_entry *wd) +{ + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */ + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL)); + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); + break; + case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ + case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ + if (delta->old_file.mode == GIT_FILEMODE_COMMIT) + /* expected submodule (and maybe found one) */; + else if (delta->new_file.mode != GIT_FILEMODE_TREE) + *action = git_iterator_current_is_ignored(workdir) ? + CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, REMOVE_AND_UPDATE) : + CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */ + if (delta->old_file.mode != GIT_FILEMODE_TREE) + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); + break; + case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */ + if (delta->old_file.mode == GIT_FILEMODE_TREE) { + /* For typechange from dir, remove dir and add blob, but it is + * not safe to remove dir if it contains modified files. + * However, safely removing child files will remove the parent + * directory if is it left empty, so we can defer removing the + * dir and it will succeed if no children are left. + */ + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + } + else if (delta->new_file.mode != GIT_FILEMODE_TREE) + /* For typechange to dir, dir is already created so no action */ + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, wd); +} + +static int checkout_action_with_wd_dir_empty( + int *action, + checkout_data *data, + const git_diff_delta *delta) +{ + int error = checkout_action_no_wd(action, data, delta); + + /* We can always safely remove an empty directory. */ + if (error == 0 && *action != CHECKOUT_ACTION__NONE) + *action |= CHECKOUT_ACTION__REMOVE; + + return error; +} + +static int checkout_action( + int *action, + checkout_data *data, + git_diff_delta *delta, + git_iterator *workdir, + const git_index_entry **wditem, + git_vector *pathspec) +{ + int cmp = -1, error; + int (*strcomp)(const char *, const char *) = data->diff->strcomp; + int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp; + int (*advance)(const git_index_entry **, git_iterator *) = NULL; + + /* move workdir iterator to follow along with deltas */ + + while (1) { + const git_index_entry *wd = *wditem; + + if (!wd) + return checkout_action_no_wd(action, data, delta); + + cmp = strcomp(wd->path, delta->old_file.path); + + /* 1. wd before delta ("a/a" before "a/b") + * 2. wd prefixes delta & should expand ("a/" before "a/b") + * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c") + * 4. wd equals delta ("a/b" and "a/b") + * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b") + * 6. wd after delta ("a/c" after "a/b") + */ + + if (cmp < 0) { + cmp = pfxcomp(delta->old_file.path, wd->path); + + if (cmp == 0) { + if (wd->mode == GIT_FILEMODE_TREE) { + /* case 2 - entry prefixed by workdir tree */ + error = git_iterator_advance_into(wditem, workdir); + if (error < 0 && error != GIT_ITEROVER) + goto done; + continue; + } + + /* case 3 maybe - wd contains non-dir where dir expected */ + if (delta->old_file.path[strlen(wd->path)] == '/') { + error = checkout_action_with_wd_blocker( + action, data, delta, wd); + advance = git_iterator_advance; + goto done; + } + } + + /* case 1 - handle wd item (if it matches pathspec) */ + error = checkout_action_wd_only(data, workdir, wditem, pathspec); + if (error && error != GIT_ITEROVER) + goto done; + continue; + } + + if (cmp == 0) { + /* case 4 */ + error = checkout_action_with_wd(action, data, delta, workdir, wd); + advance = git_iterator_advance; + goto done; + } + + cmp = pfxcomp(wd->path, delta->old_file.path); + + if (cmp == 0) { /* case 5 */ + if (wd->path[strlen(delta->old_file.path)] != '/') + return checkout_action_no_wd(action, data, delta); + + if (delta->status == GIT_DELTA_TYPECHANGE) { + if (delta->old_file.mode == GIT_FILEMODE_TREE) { + error = checkout_action_with_wd(action, data, delta, workdir, wd); + advance = git_iterator_advance_into; + goto done; + } + + if (delta->new_file.mode == GIT_FILEMODE_TREE || + delta->new_file.mode == GIT_FILEMODE_COMMIT || + delta->old_file.mode == GIT_FILEMODE_COMMIT) + { + error = checkout_action_with_wd(action, data, delta, workdir, wd); + advance = git_iterator_advance; + goto done; + } + } + + return checkout_is_empty_dir(data, wd->path) ? + checkout_action_with_wd_dir_empty(action, data, delta) : + checkout_action_with_wd_dir(action, data, delta, workdir, wd); + } + + /* case 6 - wd is after delta */ + return checkout_action_no_wd(action, data, delta); + } + +done: + if (!error && advance != NULL && + (error = advance(wditem, workdir)) < 0) { + *wditem = NULL; + if (error == GIT_ITEROVER) + error = 0; + } + + return error; +} + +static int checkout_remaining_wd_items( + checkout_data *data, + git_iterator *workdir, + const git_index_entry *wd, + git_vector *spec) +{ + int error = 0; + + while (wd && !error) + error = checkout_action_wd_only(data, workdir, &wd, spec); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +GIT_INLINE(int) checkout_idxentry_cmp( + const git_index_entry *a, + const git_index_entry *b) +{ + if (!a && !b) + return 0; + else if (!a && b) + return -1; + else if(a && !b) + return 1; + else + return strcmp(a->path, b->path); +} + +static int checkout_conflictdata_cmp(const void *a, const void *b) +{ + const checkout_conflictdata *ca = a; + const checkout_conflictdata *cb = b; + int diff; + + if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 && + (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0) + diff = checkout_idxentry_cmp(ca->theirs, cb->theirs); + + return diff; +} + +static int checkout_conflictdata_empty( + const git_vector *conflicts, size_t idx, void *payload) +{ + checkout_conflictdata *conflict; + + GIT_UNUSED(payload); + + if ((conflict = git_vector_get(conflicts, idx)) == NULL) + return -1; + + if (conflict->ancestor || conflict->ours || conflict->theirs) + return 0; + + git__free(conflict); + return 1; +} + +GIT_INLINE(bool) conflict_pathspec_match( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec, + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs) +{ + /* if the pathspec matches ours *or* theirs, proceed */ + if (ours && git_pathspec__match(pathspec, ours->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (theirs && git_pathspec__match(pathspec, theirs->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (ancestor && git_pathspec__match(pathspec, ancestor->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + return false; +} + +GIT_INLINE(int) checkout_conflict_detect_submodule(checkout_conflictdata *conflict) +{ + conflict->submodule = ((conflict->ancestor && S_ISGITLINK(conflict->ancestor->mode)) || + (conflict->ours && S_ISGITLINK(conflict->ours->mode)) || + (conflict->theirs && S_ISGITLINK(conflict->theirs->mode))); + return 0; +} + +GIT_INLINE(int) checkout_conflict_detect_binary(git_repository *repo, checkout_conflictdata *conflict) +{ + git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL; + int error = 0; + + if (conflict->submodule) + return 0; + + if (conflict->ancestor) { + if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor->id)) < 0) + goto done; + + conflict->binary = git_blob_is_binary(ancestor_blob); + } + + if (!conflict->binary && conflict->ours) { + if ((error = git_blob_lookup(&our_blob, repo, &conflict->ours->id)) < 0) + goto done; + + conflict->binary = git_blob_is_binary(our_blob); + } + + if (!conflict->binary && conflict->theirs) { + if ((error = git_blob_lookup(&their_blob, repo, &conflict->theirs->id)) < 0) + goto done; + + conflict->binary = git_blob_is_binary(their_blob); + } + +done: + git_blob_free(ancestor_blob); + git_blob_free(our_blob); + git_blob_free(their_blob); + + return error; +} + +static int checkout_conflict_append_update( + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs, + void *payload) +{ + checkout_data *data = payload; + checkout_conflictdata *conflict; + int error; + + conflict = git__calloc(1, sizeof(checkout_conflictdata)); + GIT_ERROR_CHECK_ALLOC(conflict); + + conflict->ancestor = ancestor; + conflict->ours = ours; + conflict->theirs = theirs; + + if ((error = checkout_conflict_detect_submodule(conflict)) < 0 || + (error = checkout_conflict_detect_binary(data->repo, conflict)) < 0) + { + git__free(conflict); + return error; + } + + if (git_vector_insert(&data->update_conflicts, conflict)) + return -1; + + return 0; +} + +static int checkout_conflicts_foreach( + checkout_data *data, + git_index *index, + git_iterator *workdir, + git_vector *pathspec, + int (*cb)(const git_index_entry *, const git_index_entry *, const git_index_entry *, void *), + void *payload) +{ + git_index_conflict_iterator *iterator = NULL; + const git_index_entry *ancestor, *ours, *theirs; + int error = 0; + + if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0) + goto done; + + /* Collect the conflicts */ + while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) { + if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs)) + continue; + + if ((error = cb(ancestor, ours, theirs, payload)) < 0) + goto done; + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_index_conflict_iterator_free(iterator); + + return error; +} + +static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) +{ + git_index *index; + + /* Only write conflicts from sources that have them: indexes. */ + if ((index = git_iterator_index(data->target)) == NULL) + return 0; + + data->update_conflicts._cmp = checkout_conflictdata_cmp; + + if (checkout_conflicts_foreach(data, index, workdir, pathspec, checkout_conflict_append_update, data) < 0) + return -1; + + /* Collect the REUC and NAME entries */ + data->update_reuc = &index->reuc; + data->update_names = &index->names; + + return 0; +} + +GIT_INLINE(int) checkout_conflicts_cmp_entry( + const char *path, + const git_index_entry *entry) +{ + return strcmp((const char *)path, entry->path); +} + +static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) +{ + const char *path = p; + const checkout_conflictdata *conflict = c; + + if (!conflict->ancestor) + return 1; + + return checkout_conflicts_cmp_entry(path, conflict->ancestor); +} + +static checkout_conflictdata *checkout_conflicts_search_ancestor( + checkout_data *data, + const char *path) +{ + size_t pos; + + if (git_vector_bsearch2(&pos, &data->update_conflicts, checkout_conflicts_cmp_ancestor, path) < 0) + return NULL; + + return git_vector_get(&data->update_conflicts, pos); +} + +static checkout_conflictdata *checkout_conflicts_search_branch( + checkout_data *data, + const char *path) +{ + checkout_conflictdata *conflict; + size_t i; + + git_vector_foreach(&data->update_conflicts, i, conflict) { + int cmp = -1; + + if (conflict->ancestor) + break; + + if (conflict->ours) + cmp = checkout_conflicts_cmp_entry(path, conflict->ours); + else if (conflict->theirs) + cmp = checkout_conflicts_cmp_entry(path, conflict->theirs); + + if (cmp == 0) + return conflict; + } + + return NULL; +} + +static int checkout_conflicts_load_byname_entry( + checkout_conflictdata **ancestor_out, + checkout_conflictdata **ours_out, + checkout_conflictdata **theirs_out, + checkout_data *data, + const git_index_name_entry *name_entry) +{ + checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; + int error = 0; + + *ancestor_out = NULL; + *ours_out = NULL; + *theirs_out = NULL; + + if (!name_entry->ancestor) { + git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ancestor"); + error = -1; + goto done; + } + + if (!name_entry->ours && !name_entry->theirs) { + git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ours or theirs"); + error = -1; + goto done; + } + + if ((ancestor = checkout_conflicts_search_ancestor(data, + name_entry->ancestor)) == NULL) { + git_error_set(GIT_ERROR_INDEX, + "a NAME entry referenced ancestor entry '%s' which does not exist in the main index", + name_entry->ancestor); + error = -1; + goto done; + } + + if (name_entry->ours) { + if (strcmp(name_entry->ancestor, name_entry->ours) == 0) + ours = ancestor; + else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL || + ours->ours == NULL) { + git_error_set(GIT_ERROR_INDEX, + "a NAME entry referenced our entry '%s' which does not exist in the main index", + name_entry->ours); + error = -1; + goto done; + } + } + + if (name_entry->theirs) { + if (strcmp(name_entry->ancestor, name_entry->theirs) == 0) + theirs = ancestor; + else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) + theirs = ours; + else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL || + theirs->theirs == NULL) { + git_error_set(GIT_ERROR_INDEX, + "a NAME entry referenced their entry '%s' which does not exist in the main index", + name_entry->theirs); + error = -1; + goto done; + } + } + + *ancestor_out = ancestor; + *ours_out = ours; + *theirs_out = theirs; + +done: + return error; +} + +static int checkout_conflicts_coalesce_renames( + checkout_data *data) +{ + git_index *index; + const git_index_name_entry *name_entry; + checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; + size_t i, names; + int error = 0; + + if ((index = git_iterator_index(data->target)) == NULL) + return 0; + + /* Juggle entries based on renames */ + names = git_index_name_entrycount(index); + + for (i = 0; i < names; i++) { + name_entry = git_index_name_get_byindex(index, i); + + if ((error = checkout_conflicts_load_byname_entry( + &ancestor_conflict, &our_conflict, &their_conflict, + data, name_entry)) < 0) + goto done; + + if (our_conflict && our_conflict != ancestor_conflict) { + ancestor_conflict->ours = our_conflict->ours; + our_conflict->ours = NULL; + + if (our_conflict->theirs) + our_conflict->name_collision = 1; + + if (our_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (their_conflict && their_conflict != ancestor_conflict) { + ancestor_conflict->theirs = their_conflict->theirs; + their_conflict->theirs = NULL; + + if (their_conflict->ours) + their_conflict->name_collision = 1; + + if (their_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (our_conflict && our_conflict != ancestor_conflict && + their_conflict && their_conflict != ancestor_conflict) + ancestor_conflict->one_to_two = 1; + } + + git_vector_remove_matching( + &data->update_conflicts, checkout_conflictdata_empty, NULL); + +done: + return error; +} + +static int checkout_conflicts_mark_directoryfile( + checkout_data *data) +{ + git_index *index; + checkout_conflictdata *conflict; + const git_index_entry *entry; + size_t i, j, len; + const char *path; + int prefixed, error = 0; + + if ((index = git_iterator_index(data->target)) == NULL) + return 0; + + len = git_index_entrycount(index); + + /* Find d/f conflicts */ + git_vector_foreach(&data->update_conflicts, i, conflict) { + if ((conflict->ours && conflict->theirs) || + (!conflict->ours && !conflict->theirs)) + continue; + + path = conflict->ours ? + conflict->ours->path : conflict->theirs->path; + + if ((error = git_index_find(&j, index, path)) < 0) { + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_INDEX, + "index inconsistency, could not find entry for expected conflict '%s'", path); + + goto done; + } + + for (; j < len; j++) { + if ((entry = git_index_get_byindex(index, j)) == NULL) { + git_error_set(GIT_ERROR_INDEX, + "index inconsistency, truncated index while loading expected conflict '%s'", path); + error = -1; + goto done; + } + + prefixed = git_fs_path_equal_or_prefixed(path, entry->path, NULL); + + if (prefixed == GIT_FS_PATH_EQUAL) + continue; + + if (prefixed == GIT_FS_PATH_PREFIX) + conflict->directoryfile = 1; + + break; + } + } + +done: + return error; +} + +static int checkout_get_update_conflicts( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec) +{ + int error = 0; + + if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED) + return 0; + + if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 || + (error = checkout_conflicts_coalesce_renames(data)) < 0 || + (error = checkout_conflicts_mark_directoryfile(data)) < 0) + goto done; + +done: + return error; +} + +static int checkout_conflict_append_remove( + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs, + void *payload) +{ + checkout_data *data = payload; + const char *name; + + GIT_ASSERT_ARG(ancestor || ours || theirs); + + if (ancestor) + name = git__strdup(ancestor->path); + else if (ours) + name = git__strdup(ours->path); + else if (theirs) + name = git__strdup(theirs->path); + else + abort(); + + GIT_ERROR_CHECK_ALLOC(name); + + return git_vector_insert(&data->remove_conflicts, (char *)name); +} + +static int checkout_get_remove_conflicts( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec) +{ + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) + return 0; + + return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data); +} + +static int checkout_verify_paths( + git_repository *repo, + int action, + git_diff_delta *delta) +{ + unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; + + if (action & CHECKOUT_ACTION__REMOVE) { + if (!git_path_is_valid(repo, delta->old_file.path, delta->old_file.mode, flags)) { + git_error_set(GIT_ERROR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path); + return -1; + } + } + + if (action & ~CHECKOUT_ACTION__REMOVE) { + if (!git_path_is_valid(repo, delta->new_file.path, delta->new_file.mode, flags)) { + git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path); + return -1; + } + } + + return 0; +} + +static int checkout_get_actions( + uint32_t **actions_ptr, + size_t **counts_ptr, + checkout_data *data, + git_iterator *workdir) +{ + int error = 0, act; + const git_index_entry *wditem; + git_vector pathspec = GIT_VECTOR_INIT, *deltas; + git_pool pathpool; + git_diff_delta *delta; + size_t i, *counts = NULL; + uint32_t *actions = NULL; + + if (git_pool_init(&pathpool, 1) < 0) + return -1; + + if (data->opts.paths.count > 0 && + git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) + return -1; + + if ((error = git_iterator_current(&wditem, workdir)) < 0 && + error != GIT_ITEROVER) + goto fail; + + deltas = &data->diff->deltas; + + *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t)); + *actions_ptr = actions = git__calloc( + deltas->length ? deltas->length : 1, sizeof(uint32_t)); + if (!counts || !actions) { + error = -1; + goto fail; + } + + git_vector_foreach(deltas, i, delta) { + if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0) + error = checkout_verify_paths(data->repo, act, delta); + + if (error != 0) + goto fail; + + actions[i] = act; + + if (act & CHECKOUT_ACTION__REMOVE) + counts[CHECKOUT_ACTION__REMOVE]++; + if (act & CHECKOUT_ACTION__UPDATE_BLOB) + counts[CHECKOUT_ACTION__UPDATE_BLOB]++; + if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE) + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++; + if (act & CHECKOUT_ACTION__CONFLICT) + counts[CHECKOUT_ACTION__CONFLICT]++; + } + + error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec); + if (error) + goto fail; + + counts[CHECKOUT_ACTION__REMOVE] += data->removes.length; + + if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && + (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0) { + git_error_set(GIT_ERROR_CHECKOUT, "%"PRIuZ" %s checkout", + counts[CHECKOUT_ACTION__CONFLICT], + counts[CHECKOUT_ACTION__CONFLICT] == 1 ? + "conflict prevents" : "conflicts prevent"); + error = GIT_ECONFLICT; + goto fail; + } + + + if ((error = checkout_get_remove_conflicts(data, workdir, &pathspec)) < 0 || + (error = checkout_get_update_conflicts(data, workdir, &pathspec)) < 0) + goto fail; + + counts[CHECKOUT_ACTION__REMOVE_CONFLICT] = git_vector_length(&data->remove_conflicts); + counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->update_conflicts); + + git_pathspec__vfree(&pathspec); + git_pool_clear(&pathpool); + + return 0; + +fail: + *counts_ptr = NULL; + git__free(counts); + *actions_ptr = NULL; + git__free(actions); + + git_pathspec__vfree(&pathspec); + git_pool_clear(&pathpool); + + return error; +} + +static bool should_remove_existing(checkout_data *data) +{ + int ignorecase; + + if (git_repository__configmap_lookup(&ignorecase, data->repo, GIT_CONFIGMAP_IGNORECASE) < 0) { + ignorecase = 0; + } + + return (ignorecase && + (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0); +} + +#define MKDIR_NORMAL \ + GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR +#define MKDIR_REMOVE_EXISTING \ + MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS + +static int checkout_mkdir( + checkout_data *data, + const char *path, + const char *base, + mode_t mode, + unsigned int flags) +{ + struct git_futils_mkdir_options mkdir_opts = {0}; + int error; + + mkdir_opts.dir_map = data->mkdir_map; + mkdir_opts.pool = &data->pool; + + error = git_futils_mkdir_relative( + path, base, mode, flags, &mkdir_opts); + + data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; + data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls; + data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls; + + return error; +} + +static int mkpath2file( + checkout_data *data, const char *path, unsigned int mode) +{ + struct stat st; + bool remove_existing = should_remove_existing(data); + unsigned int flags = + (remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) | + GIT_MKDIR_SKIP_LAST; + int error; + + if ((error = checkout_mkdir( + data, path, data->opts.target_directory, mode, flags)) < 0) + return error; + + if (remove_existing) { + data->perfdata.stat_calls++; + + if (p_lstat(path, &st) == 0) { + + /* Some file, symlink or folder already exists at this name. + * We would have removed it in remove_the_old unless we're on + * a case inensitive filesystem (or the user has asked us not + * to). Remove the similarly named file to write the new. + */ + error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES); + } else if (errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); + return GIT_EEXISTS; + } else { + git_error_clear(); + } + } + + return error; +} + +struct checkout_stream { + git_writestream base; + const char *path; + int fd; + int open; +}; + +static int checkout_stream_write( + git_writestream *s, const char *buffer, size_t len) +{ + struct checkout_stream *stream = (struct checkout_stream *)s; + int ret; + + if ((ret = p_write(stream->fd, buffer, len)) < 0) + git_error_set(GIT_ERROR_OS, "could not write to '%s'", stream->path); + + return ret; +} + +static int checkout_stream_close(git_writestream *s) +{ + struct checkout_stream *stream = (struct checkout_stream *)s; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(stream->open); + + stream->open = 0; + return p_close(stream->fd); +} + +static void checkout_stream_free(git_writestream *s) +{ + GIT_UNUSED(s); +} + +static int blob_content_to_file( + checkout_data *data, + struct stat *st, + git_blob *blob, + const char *path, + const char *hint_path, + mode_t entry_filemode) +{ + int flags = data->opts.file_open_flags; + mode_t file_mode = data->opts.file_mode ? + data->opts.file_mode : entry_filemode; + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + struct checkout_stream writer; + mode_t mode; + git_filter_list *fl = NULL; + int fd; + int error = 0; + + GIT_ASSERT(hint_path != NULL); + + if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) + return error; + + if (flags <= 0) + flags = O_CREAT | O_TRUNC | O_WRONLY; + if (!(mode = file_mode)) + mode = GIT_FILEMODE_BLOB; + + if ((fd = p_open(path, flags, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); + return fd; + } + + filter_session.attr_session = &data->attr_session; + filter_session.temp_buf = &data->tmp; + + if (!data->opts.disable_filters && + (error = git_filter_list__load( + &fl, data->repo, blob, hint_path, + GIT_FILTER_TO_WORKTREE, &filter_session))) { + p_close(fd); + return error; + } + + /* setup the writer */ + memset(&writer, 0, sizeof(struct checkout_stream)); + writer.base.write = checkout_stream_write; + writer.base.close = checkout_stream_close; + writer.base.free = checkout_stream_free; + writer.path = path; + writer.fd = fd; + writer.open = 1; + + error = git_filter_list_stream_blob(fl, blob, &writer.base); + + GIT_ASSERT(writer.open == 0); + + git_filter_list_free(fl); + + if (error < 0) + return error; + + if (st) { + data->perfdata.stat_calls++; + + if ((error = p_stat(path, st)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); + return error; + } + + st->st_mode = entry_filemode; + } + + return 0; +} + +static int blob_content_to_link( + checkout_data *data, + struct stat *st, + git_blob *blob, + const char *path) +{ + git_str linktarget = GIT_STR_INIT; + int error; + + if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) + return error; + + if ((error = git_blob__getbuf(&linktarget, blob)) < 0) + return error; + + if (data->can_symlink) { + if ((error = p_symlink(git_str_cstr(&linktarget), path)) < 0) + git_error_set(GIT_ERROR_OS, "could not create symlink %s", path); + } else { + error = git_futils_fake_symlink(git_str_cstr(&linktarget), path); + } + + if (!error) { + data->perfdata.stat_calls++; + + if ((error = p_lstat(path, st)) < 0) + git_error_set(GIT_ERROR_CHECKOUT, "could not stat symlink %s", path); + + st->st_mode = GIT_FILEMODE_LINK; + } + + git_str_dispose(&linktarget); + + return error; +} + +static int checkout_update_index( + checkout_data *data, + const git_diff_file *file, + struct stat *st) +{ + git_index_entry entry; + + if (!data->index) + return 0; + + memset(&entry, 0, sizeof(entry)); + entry.path = (char *)file->path; /* cast to prevent warning */ + git_index_entry__init_from_stat(&entry, st, true); + git_oid_cpy(&entry.id, &file->id); + + return git_index_add(data->index, &entry); +} + +static int checkout_submodule_update_index( + checkout_data *data, + const git_diff_file *file) +{ + git_str *fullpath; + struct stat st; + + /* update the index unless prevented */ + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) + return 0; + + if (checkout_target_fullpath(&fullpath, data, file->path) < 0) + return -1; + + data->perfdata.stat_calls++; + if (p_stat(fullpath->ptr, &st) < 0) { + git_error_set( + GIT_ERROR_CHECKOUT, "could not stat submodule %s\n", file->path); + return GIT_ENOTFOUND; + } + + st.st_mode = GIT_FILEMODE_COMMIT; + + return checkout_update_index(data, file, &st); +} + +static int checkout_submodule( + checkout_data *data, + const git_diff_file *file) +{ + bool remove_existing = should_remove_existing(data); + int error = 0; + + /* Until submodules are supported, UPDATE_ONLY means do nothing here */ + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + return 0; + + if ((error = checkout_mkdir( + data, + file->path, data->opts.target_directory, data->opts.dir_mode, + remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) + return error; + + if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) { + /* I've observed repos with submodules in the tree that do not + * have a .gitmodules - core Git just makes an empty directory + */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return checkout_submodule_update_index(data, file); + } + + return error; + } + + /* TODO: Support checkout_strategy options. Two circumstances: + * 1 - submodule already checked out, but we need to move the HEAD + * to the new OID, or + * 2 - submodule not checked out and we should recursively check it out + * + * Checkout will not execute a pull on the submodule, but a clone + * command should probably be able to. Do we need a submodule callback? + */ + + return checkout_submodule_update_index(data, file); +} + +static void report_progress( + checkout_data *data, + const char *path) +{ + if (data->opts.progress_cb) + data->opts.progress_cb( + path, data->completed_steps, data->total_steps, + data->opts.progress_payload); +} + +static int checkout_safe_for_update_only( + checkout_data *data, const char *path, mode_t expected_mode) +{ + struct stat st; + + data->perfdata.stat_calls++; + + if (p_lstat(path, &st) < 0) { + /* if doesn't exist, then no error and no update */ + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + /* otherwise, stat error and no update */ + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); + return -1; + } + + /* only safe for update if this is the same type of file */ + if ((st.st_mode & ~0777) == (expected_mode & ~0777)) + return 1; + + return 0; +} + +static int checkout_write_content( + checkout_data *data, + const git_oid *oid, + const char *full_path, + const char *hint_path, + unsigned int mode, + struct stat *st) +{ + int error = 0; + git_blob *blob; + + if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) + return error; + + if (S_ISLNK(mode)) + error = blob_content_to_link(data, st, blob, full_path); + else + error = blob_content_to_file(data, st, blob, full_path, hint_path, mode); + + git_blob_free(blob); + + /* if we try to create the blob and an existing directory blocks it from + * being written, then there must have been a typechange conflict in a + * parent directory - suppress the error and try to continue. + */ + if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 && + (error == GIT_ENOTFOUND || error == GIT_EEXISTS)) + { + git_error_clear(); + error = 0; + } + + return error; +} + +static int checkout_blob( + checkout_data *data, + const git_diff_file *file) +{ + git_str *fullpath; + struct stat st; + int error = 0; + + if (checkout_target_fullpath(&fullpath, data, file->path) < 0) + return -1; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { + int rval = checkout_safe_for_update_only( + data, fullpath->ptr, file->mode); + + if (rval <= 0) + return rval; + } + + error = checkout_write_content( + data, &file->id, fullpath->ptr, file->path, file->mode, &st); + + /* update the index unless prevented */ + if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) + error = checkout_update_index(data, file, &st); + + /* update the submodule data if this was a new .gitmodules file */ + if (!error && strcmp(file->path, ".gitmodules") == 0) + data->reload_submodules = true; + + return error; +} + +static int checkout_remove_the_old( + unsigned int *actions, + checkout_data *data) +{ + int error = 0; + git_diff_delta *delta; + const char *str; + size_t i; + git_str *fullpath; + uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; + + if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) + flg |= GIT_RMDIR_SKIP_NONEMPTY; + + if (checkout_target_fullpath(&fullpath, data, NULL) < 0) + return -1; + + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__REMOVE) { + error = git_futils_rmdir_r( + delta->old_file.path, fullpath->ptr, flg); + + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->old_file.path); + + if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && + (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && + data->index != NULL) + { + (void)git_index_remove(data->index, delta->old_file.path, 0); + } + } + } + + git_vector_foreach(&data->removes, i, str) { + error = git_futils_rmdir_r(str, fullpath->ptr, flg); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, str); + + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && + data->index != NULL) + { + if (str[strlen(str) - 1] == '/') + (void)git_index_remove_directory(data->index, str, 0); + else + (void)git_index_remove(data->index, str, 0); + } + } + + return 0; +} + +static int checkout_create_the_new( + unsigned int *actions, + checkout_data *data) +{ + int error = 0; + git_diff_delta *delta; + size_t i; + + 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); + } + } + + 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); + } + } + + return 0; +} + +static int checkout_create_submodules( + unsigned int *actions, + checkout_data *data) +{ + git_diff_delta *delta; + size_t i; + + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { + int error = checkout_submodule(data, &delta->new_file); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->new_file.path); + } + } + + return 0; +} + +static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) +{ + int error = 0; + git_reference *ref = NULL; + git_object *head; + + if (!(error = git_repository_head(&ref, repo)) && + !(error = git_reference_peel(&head, ref, GIT_OBJECT_TREE))) + *out = (git_tree *)head; + + git_reference_free(ref); + + return error; +} + + +static int conflict_entry_name( + git_str *out, + const char *side_name, + const char *filename) +{ + if (git_str_puts(out, side_name) < 0 || + git_str_putc(out, ':') < 0 || + git_str_puts(out, filename) < 0) + return -1; + + return 0; +} + +static int checkout_path_suffixed(git_str *path, const char *suffix) +{ + size_t path_len; + int i = 0, error = 0; + + if ((error = git_str_putc(path, '~')) < 0 || (error = git_str_puts(path, suffix)) < 0) + return -1; + + path_len = git_str_len(path); + + while (git_fs_path_exists(git_str_cstr(path)) && i < INT_MAX) { + git_str_truncate(path, path_len); + + if ((error = git_str_putc(path, '_')) < 0 || + (error = git_str_printf(path, "%d", i)) < 0) + return error; + + i++; + } + + if (i == INT_MAX) { + git_str_truncate(path, path_len); + + git_error_set(GIT_ERROR_CHECKOUT, "could not write '%s': working directory file exists", path->ptr); + return GIT_EEXISTS; + } + + return 0; +} + +static int checkout_write_entry( + checkout_data *data, + checkout_conflictdata *conflict, + const git_index_entry *side) +{ + const char *hint_path = NULL, *suffix; + git_str *fullpath; + struct stat st; + int error; + + GIT_ASSERT(side == conflict->ours || side == conflict->theirs); + + if (checkout_target_fullpath(&fullpath, data, side->path) < 0) + return -1; + + if ((conflict->name_collision || conflict->directoryfile) && + (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && + (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { + + if (side == conflict->ours) + suffix = data->opts.our_label ? data->opts.our_label : + "ours"; + else + suffix = data->opts.their_label ? data->opts.their_label : + "theirs"; + + if (checkout_path_suffixed(fullpath, suffix) < 0) + return -1; + } + + hint_path = side->path; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0) + return error; + + if (!S_ISGITLINK(side->mode)) + return checkout_write_content(data, + &side->id, fullpath->ptr, hint_path, side->mode, &st); + + return 0; +} + +static int checkout_write_entries( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0) + error = checkout_write_entry(data, conflict, conflict->theirs); + + return error; +} + +static int checkout_merge_path( + git_str *out, + checkout_data *data, + checkout_conflictdata *conflict, + git_merge_file_result *result) +{ + const char *our_label_raw, *their_label_raw, *suffix; + int error = 0; + + if ((error = git_str_joinpath(out, data->opts.target_directory, result->path)) < 0 || + (error = git_path_validate_str_length(data->repo, out)) < 0) + return error; + + /* Most conflicts simply use the filename in the index */ + if (!conflict->name_collision) + return 0; + + /* Rename 2->1 conflicts need the branch name appended */ + our_label_raw = data->opts.our_label ? data->opts.our_label : "ours"; + their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs"; + suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw; + + if ((error = checkout_path_suffixed(out, suffix)) < 0) + return error; + + return 0; +} + +static int checkout_write_merge( + checkout_data *data, + checkout_conflictdata *conflict) +{ + git_str our_label = GIT_STR_INIT, their_label = GIT_STR_INIT, + path_suffixed = GIT_STR_INIT, path_workdir = GIT_STR_INIT, + in_data = GIT_STR_INIT, out_data = GIT_STR_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + git_filebuf output = GIT_FILEBUF_INIT; + git_filter_list *fl = NULL; + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + int error = 0; + + if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) + opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; + + if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3) + opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; + + opts.ancestor_label = data->opts.ancestor_label ? + data->opts.ancestor_label : "ancestor"; + opts.our_label = data->opts.our_label ? + data->opts.our_label : "ours"; + opts.their_label = data->opts.their_label ? + data->opts.their_label : "theirs"; + + /* If all the paths are identical, decorate the diff3 file with the branch + * names. Otherwise, append branch_name:path. + */ + if (conflict->ours && conflict->theirs && + strcmp(conflict->ours->path, conflict->theirs->path) != 0) { + + if ((error = conflict_entry_name( + &our_label, opts.our_label, conflict->ours->path)) < 0 || + (error = conflict_entry_name( + &their_label, opts.their_label, conflict->theirs->path)) < 0) + goto done; + + opts.our_label = git_str_cstr(&our_label); + opts.their_label = git_str_cstr(&their_label); + } + + if ((error = git_merge_file_from_index(&result, data->repo, + conflict->ancestor, conflict->ours, conflict->theirs, &opts)) < 0) + goto done; + + if (result.path == NULL || result.mode == 0) { + git_error_set(GIT_ERROR_CHECKOUT, "could not merge contents of file"); + error = GIT_ECONFLICT; + goto done; + } + + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) + goto done; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(data, git_str_cstr(&path_workdir), result.mode)) <= 0) + goto done; + + if (!data->opts.disable_filters) { + in_data.ptr = (char *)result.ptr; + in_data.size = result.len; + + filter_session.attr_session = &data->attr_session; + filter_session.temp_buf = &data->tmp; + + if ((error = git_filter_list__load( + &fl, data->repo, NULL, result.path, + GIT_FILTER_TO_WORKTREE, &filter_session)) < 0 || + (error = git_filter_list__convert_buf(&out_data, fl, &in_data)) < 0) + goto done; + } else { + out_data.ptr = (char *)result.ptr; + out_data.size = result.len; + } + + if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 || + (error = git_filebuf_open(&output, git_str_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || + (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 || + (error = git_filebuf_commit(&output)) < 0) + goto done; + +done: + git_filter_list_free(fl); + + git_str_dispose(&out_data); + git_str_dispose(&our_label); + git_str_dispose(&their_label); + + git_merge_file_result_free(&result); + git_str_dispose(&path_workdir); + git_str_dispose(&path_suffixed); + + return error; +} + +static int checkout_conflict_add( + checkout_data *data, + const git_index_entry *conflict) +{ + int error = git_index_remove(data->index, conflict->path, 0); + + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + return error; + + return git_index_add(data->index, conflict); +} + +static int checkout_conflict_update_index( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if (conflict->ancestor) + error = checkout_conflict_add(data, conflict->ancestor); + + if (!error && conflict->ours) + error = checkout_conflict_add(data, conflict->ours); + + if (!error && conflict->theirs) + error = checkout_conflict_add(data, conflict->theirs); + + return error; +} + +static int checkout_create_conflicts(checkout_data *data) +{ + checkout_conflictdata *conflict; + size_t i; + int error = 0; + + git_vector_foreach(&data->update_conflicts, i, conflict) { + + /* Both deleted: nothing to do */ + if (conflict->ours == NULL && conflict->theirs == NULL) + error = 0; + + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + conflict->ours) + error = checkout_write_entry(data, conflict, conflict->ours); + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + conflict->theirs) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Ignore the other side of name collisions. */ + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + !conflict->ours && conflict->name_collision) + error = 0; + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + !conflict->theirs && conflict->name_collision) + error = 0; + + /* For modify/delete, name collisions and d/f conflicts, write + * the file (potentially with the name mangled. + */ + else if (conflict->ours != NULL && conflict->theirs == NULL) + error = checkout_write_entry(data, conflict, conflict->ours); + else if (conflict->ours == NULL && conflict->theirs != NULL) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Add/add conflicts and rename 1->2 conflicts, write the + * ours/theirs sides (potentially name mangled). + */ + else if (conflict->one_to_two) + error = checkout_write_entries(data, conflict); + + /* If all sides are links, write the ours side */ + else if (S_ISLNK(conflict->ours->mode) && + S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + /* Link/file conflicts, write the file side */ + else if (S_ISLNK(conflict->ours->mode)) + error = checkout_write_entry(data, conflict, conflict->theirs); + else if (S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + + /* If any side is a gitlink, do nothing. */ + else if (conflict->submodule) + error = 0; + + /* If any side is binary, write the ours side */ + else if (conflict->binary) + error = checkout_write_entry(data, conflict, conflict->ours); + + else if (!error) + error = checkout_write_merge(data, conflict); + + /* Update the index extensions (REUC and NAME) if we're checking + * out a different index. (Otherwise just leave them there.) + */ + if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) + error = checkout_conflict_update_index(data, conflict); + + if (error) + break; + + data->completed_steps++; + report_progress(data, + conflict->ours ? conflict->ours->path : + (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); + } + + return error; +} + +static int checkout_remove_conflicts(checkout_data *data) +{ + const char *conflict; + size_t i; + + git_vector_foreach(&data->remove_conflicts, i, conflict) { + if (git_index_conflict_remove(data->index, conflict) < 0) + return -1; + + data->completed_steps++; + } + + return 0; +} + +static int checkout_extensions_update_index(checkout_data *data) +{ + const git_index_reuc_entry *reuc_entry; + const git_index_name_entry *name_entry; + size_t i; + int error = 0; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + return 0; + + if (data->update_reuc) { + git_vector_foreach(data->update_reuc, i, reuc_entry) { + if ((error = git_index_reuc_add(data->index, reuc_entry->path, + reuc_entry->mode[0], &reuc_entry->oid[0], + reuc_entry->mode[1], &reuc_entry->oid[1], + reuc_entry->mode[2], &reuc_entry->oid[2])) < 0) + goto done; + } + } + + if (data->update_names) { + git_vector_foreach(data->update_names, i, name_entry) { + if ((error = git_index_name_add(data->index, name_entry->ancestor, + name_entry->ours, name_entry->theirs)) < 0) + goto done; + } + } + +done: + return error; +} + +static void checkout_data_clear(checkout_data *data) +{ + if (data->opts_free_baseline) { + git_tree_free(data->opts.baseline); + data->opts.baseline = NULL; + } + + git_vector_free(&data->removes); + git_pool_clear(&data->pool); + + git_vector_free_deep(&data->remove_conflicts); + git_vector_free_deep(&data->update_conflicts); + + git__free(data->pfx); + data->pfx = NULL; + + git_str_dispose(&data->target_path); + git_str_dispose(&data->tmp); + + git_index_free(data->index); + data->index = NULL; + + git_strmap_free(data->mkdir_map); + data->mkdir_map = NULL; + + git_attr_session__free(&data->attr_session); +} + +static int validate_target_directory(checkout_data *data) +{ + int error; + + if ((error = git_path_validate_length(data->repo, data->opts.target_directory)) < 0) + return error; + + if (git_fs_path_isdir(data->opts.target_directory)) + return 0; + + error = checkout_mkdir(data, data->opts.target_directory, NULL, + GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR); + + return error; +} + +static int checkout_data_init( + checkout_data *data, + git_iterator *target, + const git_checkout_options *proposed) +{ + int error = 0; + git_repository *repo = git_iterator_owner(target); + + memset(data, 0, sizeof(*data)); + + if (!repo) { + git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout nothing"); + return -1; + } + + if ((!proposed || !proposed->target_directory) && + (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) + return error; + + data->repo = repo; + data->target = target; + + GIT_ERROR_CHECK_VERSION( + proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); + + if (!proposed) + GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTIONS_VERSION); + else + memmove(&data->opts, proposed, sizeof(git_checkout_options)); + + if (!data->opts.target_directory) + data->opts.target_directory = git_repository_workdir(repo); + else if ((error = validate_target_directory(data)) < 0) + goto cleanup; + + if ((error = git_repository_index(&data->index, data->repo)) < 0) + goto cleanup; + + /* refresh config and index content unless NO_REFRESH is given */ + if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { + git_config *cfg; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + goto cleanup; + + /* Reload the repository index (unless we're checking out the + * index; then it has the changes we're trying to check out + * and those should not be overwritten.) + */ + if (data->index != git_iterator_index(target)) { + if (data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) { + /* When forcing, we can blindly re-read the index */ + if ((error = git_index_read(data->index, false)) < 0) + goto cleanup; + } else { + /* + * When not being forced, we need to check for unresolved + * conflicts and unsaved changes in the index before + * proceeding. + */ + if (git_index_has_conflicts(data->index)) { + error = GIT_ECONFLICT; + git_error_set(GIT_ERROR_CHECKOUT, + "unresolved conflicts exist in the index"); + goto cleanup; + } + + if ((error = git_index_read_safely(data->index)) < 0) + goto cleanup; + } + + /* clean conflict data in the current index */ + git_index_name_clear(data->index); + git_index_reuc_clear(data->index); + } + } + + /* if you are forcing, allow all safe updates, plus recreate missing */ + if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING; + + /* if the repository does not actually have an index file, then this + * is an initial checkout (perhaps from clone), so we allow safe updates + */ + if (!data->index->on_disk && + (data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING; + + data->strategy = data->opts.checkout_strategy; + + /* opts->disable_filters is false by default */ + + if (!data->opts.dir_mode) + data->opts.dir_mode = GIT_DIR_MODE; + + if (!data->opts.file_open_flags) + data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; + + data->pfx = git_pathspec_prefix(&data->opts.paths); + + if ((error = git_repository__configmap_lookup( + &data->can_symlink, repo, GIT_CONFIGMAP_SYMLINKS)) < 0) + goto cleanup; + + if ((error = git_repository__configmap_lookup( + &data->respect_filemode, repo, GIT_CONFIGMAP_FILEMODE)) < 0) + goto cleanup; + + if (!data->opts.baseline && !data->opts.baseline_index) { + data->opts_free_baseline = true; + error = 0; + + /* if we don't have an index, this is an initial checkout and + * should be against an empty baseline + */ + if (data->index->on_disk) + error = checkout_lookup_head_tree(&data->opts.baseline, repo); + + if (error == GIT_EUNBORNBRANCH) { + error = 0; + git_error_clear(); + } + + if (error < 0) + goto cleanup; + } + + if ((data->opts.checkout_strategy & + (GIT_CHECKOUT_CONFLICT_STYLE_MERGE | GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)) == 0) { + git_config_entry *conflict_style = NULL; + git_config *cfg = NULL; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || + (error = git_config_get_entry(&conflict_style, cfg, "merge.conflictstyle")) < 0 || + error == GIT_ENOTFOUND) + ; + else if (error) + goto cleanup; + else if (strcmp(conflict_style->value, "merge") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE; + else if (strcmp(conflict_style->value, "diff3") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; + else if (strcmp(conflict_style->value, "zdiff3") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3; + else { + git_error_set(GIT_ERROR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'", + conflict_style->value); + error = -1; + git_config_entry_free(conflict_style); + goto cleanup; + } + git_config_entry_free(conflict_style); + } + + if ((error = git_pool_init(&data->pool, 1)) < 0 || + (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_str_puts(&data->target_path, data->opts.target_directory)) < 0 || + (error = git_fs_path_to_dir(&data->target_path)) < 0 || + (error = git_strmap_new(&data->mkdir_map)) < 0) + goto cleanup; + + data->target_len = git_str_len(&data->target_path); + + git_attr_session__init(&data->attr_session, data->repo); + +cleanup: + if (error < 0) + checkout_data_clear(data); + + return error; +} + +#define CHECKOUT_INDEX_DONT_WRITE_MASK \ + (GIT_CHECKOUT_DONT_UPDATE_INDEX | GIT_CHECKOUT_DONT_WRITE_INDEX) + +GIT_INLINE(void) setup_pathspecs( + git_iterator_options *iter_opts, + const git_checkout_options *checkout_opts) +{ + if (checkout_opts && + (checkout_opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) { + iter_opts->pathlist.count = checkout_opts->paths.count; + iter_opts->pathlist.strings = checkout_opts->paths.strings; + } +} + +int git_checkout_iterator( + git_iterator *target, + git_index *index, + const git_checkout_options *opts) +{ + int error = 0; + git_iterator *baseline = NULL, *workdir = NULL; + git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT, + workdir_opts = GIT_ITERATOR_OPTIONS_INIT; + checkout_data data = {0}; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + uint32_t *actions = NULL; + size_t *counts = NULL; + + /* initialize structures and options */ + error = checkout_data_init(&data, target, opts); + if (error < 0) + return error; + + diff_opts.flags = + GIT_DIFF_INCLUDE_UNMODIFIED | + GIT_DIFF_INCLUDE_UNREADABLE | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ + GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_TYPECHANGE | + GIT_DIFF_INCLUDE_TYPECHANGE_TREES | + GIT_DIFF_SKIP_BINARY_CHECK | + GIT_DIFF_INCLUDE_CASECHANGE; + if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) + diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + if (data.opts.paths.count > 0) + diff_opts.pathspec = data.opts.paths; + + /* set up iterators */ + + workdir_opts.flags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND; + workdir_opts.start = data.pfx; + workdir_opts.end = data.pfx; + + setup_pathspecs(&workdir_opts, opts); + + if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 || + (error = git_iterator_for_workdir_ext( + &workdir, data.repo, data.opts.target_directory, index, NULL, + &workdir_opts)) < 0) + goto cleanup; + + baseline_opts.flags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + baseline_opts.start = data.pfx; + baseline_opts.end = data.pfx; + + setup_pathspecs(&baseline_opts, opts); + + if (data.opts.baseline_index) { + if ((error = git_iterator_for_index( + &baseline, git_index_owner(data.opts.baseline_index), + data.opts.baseline_index, &baseline_opts)) < 0) + goto cleanup; + } else { + if ((error = git_iterator_for_tree( + &baseline, data.opts.baseline, &baseline_opts)) < 0) + goto cleanup; + } + + /* Should not have case insensitivity mismatch */ + GIT_ASSERT(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline)); + + /* Generate baseline-to-target diff which will include an entry for + * every possible update that might need to be made. + */ + if ((error = git_diff__from_iterators( + &data.diff, data.repo, baseline, target, &diff_opts)) < 0) + goto cleanup; + + /* Loop through diff (and working directory iterator) building a list of + * actions to be taken, plus look for conflicts and send notifications, + * then loop through conflicts. + */ + if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0) + goto cleanup; + + if (data.strategy & GIT_CHECKOUT_DRY_RUN) + goto cleanup; + + data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + + counts[CHECKOUT_ACTION__REMOVE_CONFLICT] + + counts[CHECKOUT_ACTION__UPDATE_BLOB] + + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; + + report_progress(&data, NULL); /* establish 0 baseline */ + + /* To deal with some order dependencies, perform remaining checkout + * in three passes: removes, then update blobs, then update submodules. + */ + if (counts[CHECKOUT_ACTION__REMOVE] > 0 && + (error = checkout_remove_the_old(actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__REMOVE_CONFLICT] > 0 && + (error = checkout_remove_conflicts(&data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && + (error = checkout_create_the_new(actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && + (error = checkout_create_submodules(actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && + (error = checkout_create_conflicts(&data)) < 0) + goto cleanup; + + if (data.index != git_iterator_index(target) && + (error = checkout_extensions_update_index(&data)) < 0) + goto cleanup; + + GIT_ASSERT(data.completed_steps == data.total_steps); + + if (data.opts.perfdata_cb) + data.opts.perfdata_cb(&data.perfdata, data.opts.perfdata_payload); + +cleanup: + if (!error && data.index != NULL && + (data.strategy & CHECKOUT_INDEX_DONT_WRITE_MASK) == 0) + error = git_index_write(data.index); + + git_diff_free(data.diff); + git_iterator_free(workdir); + git_iterator_free(baseline); + git__free(actions); + git__free(counts); + checkout_data_clear(&data); + + return error; +} + +int git_checkout_index( + git_repository *repo, + git_index *index, + const git_checkout_options *opts) +{ + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error, owned = 0; + git_iterator *index_i; + + if (!index && !repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "must provide either repository or index to checkout"); + return -1; + } + + if (index && repo && + git_index_owner(index) && + git_index_owner(index) != repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "index to checkout does not match repository"); + return -1; + } else if(index && repo && !git_index_owner(index)) { + GIT_REFCOUNT_OWN(index, repo); + owned = 1; + } + + if (!repo) + repo = git_index_owner(index); + + if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + return error; + GIT_REFCOUNT_INC(index); + + setup_pathspecs(&iter_opts, opts); + + if (!(error = git_iterator_for_index(&index_i, repo, index, &iter_opts))) + error = git_checkout_iterator(index_i, index, opts); + + if (owned) + GIT_REFCOUNT_OWN(index, NULL); + + git_iterator_free(index_i); + git_index_free(index); + + return error; +} + +int git_checkout_tree( + git_repository *repo, + const git_object *treeish, + const git_checkout_options *opts) +{ + int error; + git_index *index; + git_tree *tree = NULL; + git_iterator *tree_i = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + if (!treeish && !repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "must provide either repository or tree to checkout"); + return -1; + } + if (treeish && repo && git_object_owner(treeish) != repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "object to checkout does not match repository"); + return -1; + } + + if (!repo) + repo = git_object_owner(treeish); + + if (treeish) { + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJECT_TREE) < 0) { + git_error_set( + GIT_ERROR_CHECKOUT, "provided object cannot be peeled to a tree"); + return -1; + } + } + else { + if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) { + if (error != GIT_EUNBORNBRANCH) + git_error_set( + GIT_ERROR_CHECKOUT, + "HEAD could not be peeled to a tree and no treeish given"); + return error; + } + } + + if ((error = git_repository_index(&index, repo)) < 0) + return error; + + setup_pathspecs(&iter_opts, opts); + + if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts))) + error = git_checkout_iterator(tree_i, index, opts); + + git_iterator_free(tree_i); + git_index_free(index); + git_tree_free(tree); + + return error; +} + +int git_checkout_head( + git_repository *repo, + const git_checkout_options *opts) +{ + GIT_ASSERT_ARG(repo); + + return git_checkout_tree(repo, NULL, opts); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_checkout_init_options(git_checkout_options *opts, unsigned int version) +{ + return git_checkout_options_init(opts, version); +} +#endif diff --git a/src/libgit2/checkout.h b/src/libgit2/checkout.h new file mode 100644 index 000000000..517fbf3b1 --- /dev/null +++ b/src/libgit2/checkout.h @@ -0,0 +1,27 @@ +/* + * 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_checkout_h__ +#define INCLUDE_checkout_h__ + +#include "common.h" + +#include "git2/checkout.h" +#include "iterator.h" + +#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12) + +/** + * Update the working directory to match the target iterator. The + * expected baseline value can be passed in via the checkout options + * or else will default to the HEAD commit. + */ +extern int git_checkout_iterator( + git_iterator *target, + git_index *index, + const git_checkout_options *opts); + +#endif diff --git a/src/libgit2/cherrypick.c b/src/libgit2/cherrypick.c new file mode 100644 index 000000000..9ec4962b9 --- /dev/null +++ b/src/libgit2/cherrypick.c @@ -0,0 +1,242 @@ +/* +* 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 "repository.h" +#include "filebuf.h" +#include "merge.h" +#include "vector.h" +#include "index.h" + +#include "git2/types.h" +#include "git2/merge.h" +#include "git2/cherrypick.h" +#include "git2/commit.h" +#include "git2/sys/commit.h" + +#define GIT_CHERRYPICK_FILE_MODE 0666 + +static int write_cherrypick_head( + git_repository *repo, + const char *commit_oidstr) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 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); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int write_merge_msg( + git_repository *repo, + const char *commit_msg) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 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; + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int cherrypick_normalize_opts( + git_repository *repo, + git_cherrypick_options *opts, + const git_cherrypick_options *given, + const char *their_label) +{ + int error = 0; + unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_ALLOW_CONFLICTS; + + GIT_UNUSED(repo); + + if (given != NULL) + memcpy(opts, given, sizeof(git_cherrypick_options)); + else { + git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT; + memcpy(opts, &default_opts, sizeof(git_cherrypick_options)); + } + + if (!opts->checkout_opts.checkout_strategy) + opts->checkout_opts.checkout_strategy = default_checkout_strategy; + + if (!opts->checkout_opts.our_label) + opts->checkout_opts.our_label = "HEAD"; + + if (!opts->checkout_opts.their_label) + opts->checkout_opts.their_label = their_label; + + return error; +} + +static int cherrypick_state_cleanup(git_repository *repo) +{ + const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +static int cherrypick_seterr(git_commit *commit, const char *fmt) +{ + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + git_error_set(GIT_ERROR_CHERRYPICK, fmt, + git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); + + return -1; +} + +int git_cherrypick_commit( + git_index **out, + git_repository *repo, + git_commit *cherrypick_commit, + git_commit *our_commit, + unsigned int mainline, + const git_merge_options *merge_opts) +{ + git_commit *parent_commit = NULL; + git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL; + int parent = 0, error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cherrypick_commit); + GIT_ASSERT_ARG(our_commit); + + if (git_commit_parentcount(cherrypick_commit) > 1) { + if (!mainline) + return cherrypick_seterr(cherrypick_commit, + "mainline branch is not specified but %s is a merge commit"); + + parent = mainline; + } else { + if (mainline) + return cherrypick_seterr(cherrypick_commit, + "mainline branch specified but %s is not a merge commit"); + + parent = git_commit_parentcount(cherrypick_commit); + } + + if (parent && + ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) + goto done; + + if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 || + (error = git_commit_tree(&our_tree, our_commit)) < 0) + goto done; + + error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts); + +done: + git_tree_free(parent_tree); + git_tree_free(our_tree); + git_tree_free(cherrypick_tree); + git_commit_free(parent_commit); + + return error; +} + +int git_cherrypick( + git_repository *repo, + git_commit *commit, + const git_cherrypick_options *given_opts) +{ + git_cherrypick_options opts; + git_reference *our_ref = NULL; + git_commit *our_commit = NULL; + char commit_oidstr[GIT_OID_HEXSZ + 1]; + const char *commit_msg, *commit_summary; + git_str their_label = GIT_STR_INIT; + git_index *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(commit); + + GIT_ERROR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options"); + + if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0) + return error; + + if ((commit_msg = git_commit_message(commit)) == NULL || + (commit_summary = git_commit_summary(commit)) == NULL) { + error = -1; + goto on_error; + } + + git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit)); + + if ((error = write_merge_msg(repo, commit_msg)) < 0 || + (error = git_str_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 || + (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || + (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || + (error = write_cherrypick_head(repo, commit_oidstr)) < 0 || + (error = git_repository_head(&our_ref, repo)) < 0 || + (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || + (error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || + (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || + (error = git_indexwriter_commit(&indexwriter)) < 0) + goto on_error; + + goto done; + +on_error: + cherrypick_state_cleanup(repo); + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_commit_free(our_commit); + git_reference_free(our_ref); + git_str_dispose(&their_label); + + return error; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_cherrypick_init_options( + git_cherrypick_options *opts, unsigned int version) +{ + return git_cherrypick_options_init(opts, version); +} +#endif diff --git a/src/libgit2/clone.c b/src/libgit2/clone.c new file mode 100644 index 000000000..1843875f8 --- /dev/null +++ b/src/libgit2/clone.c @@ -0,0 +1,655 @@ +/* + * 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 "clone.h" + +#include "git2/clone.h" +#include "git2/remote.h" +#include "git2/revparse.h" +#include "git2/branch.h" +#include "git2/config.h" +#include "git2/checkout.h" +#include "git2/commit.h" +#include "git2/tree.h" + +#include "remote.h" +#include "futils.h" +#include "refs.h" +#include "fs_path.h" +#include "repository.h" +#include "odb.h" + +static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link); + +static int create_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *name, + const char *log_message) +{ + git_commit *head_obj = NULL; + git_reference *branch_ref = NULL; + git_str refname = GIT_STR_INIT; + int error; + + /* Find the target commit */ + if ((error = git_commit_lookup(&head_obj, repo, target)) < 0) + return error; + + /* Create the new branch */ + if ((error = git_str_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0) + return error; + + error = git_reference_create(&branch_ref, repo, git_str_cstr(&refname), target, 0, log_message); + git_str_dispose(&refname); + git_commit_free(head_obj); + + if (!error) + *branch = branch_ref; + else + git_reference_free(branch_ref); + + return error; +} + +static int setup_tracking_config( + git_repository *repo, + const char *branch_name, + const char *remote_name, + const char *merge_target) +{ + git_config *cfg; + git_str remote_key = GIT_STR_INIT, merge_key = GIT_STR_INIT; + int error = -1; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + if (git_str_printf(&remote_key, "branch.%s.remote", branch_name) < 0) + goto cleanup; + + if (git_str_printf(&merge_key, "branch.%s.merge", branch_name) < 0) + goto cleanup; + + if (git_config_set_string(cfg, git_str_cstr(&remote_key), remote_name) < 0) + goto cleanup; + + if (git_config_set_string(cfg, git_str_cstr(&merge_key), merge_target) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_str_dispose(&remote_key); + git_str_dispose(&merge_key); + return error; +} + +static int create_tracking_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *branch_name, + const char *log_message) +{ + int error; + + if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0) + return error; + + return setup_tracking_config( + repo, + branch_name, + GIT_REMOTE_ORIGIN, + git_reference_name(*branch)); +} + +static int update_head_to_new_branch( + git_repository *repo, + const git_oid *target, + const char *name, + const char *reflog_message) +{ + git_reference *tracking_branch = NULL; + int error; + + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + name += strlen(GIT_REFS_HEADS_DIR); + + error = create_tracking_branch(&tracking_branch, repo, target, name, + reflog_message); + + if (!error) + error = git_repository_set_head( + repo, git_reference_name(tracking_branch)); + + git_reference_free(tracking_branch); + + /* if it already existed, then the user's refspec created it for us, ignore it' */ + if (error == GIT_EEXISTS) + error = 0; + + return error; +} + +static int update_head_to_default(git_repository *repo) +{ + git_str initialbranch = GIT_STR_INIT; + const char *branch_name; + int error = 0; + + if ((error = git_repository_initialbranch(&initialbranch, repo)) < 0) + goto done; + + if (git__prefixcmp(initialbranch.ptr, GIT_REFS_HEADS_DIR) != 0) { + git_error_set(GIT_ERROR_INVALID, "invalid initial branch '%s'", initialbranch.ptr); + error = -1; + goto done; + } + + branch_name = initialbranch.ptr + strlen(GIT_REFS_HEADS_DIR); + + error = setup_tracking_config(repo, branch_name, GIT_REMOTE_ORIGIN, + initialbranch.ptr); + +done: + git_str_dispose(&initialbranch); + return error; +} + +static int update_remote_head( + git_repository *repo, + git_remote *remote, + git_str *target, + const char *reflog_message) +{ + git_refspec *refspec; + git_reference *remote_head = NULL; + git_str remote_head_name = GIT_STR_INIT; + git_str remote_branch_name = GIT_STR_INIT; + int error; + + /* Determine the remote tracking ref name from the local branch */ + refspec = git_remote__matching_refspec(remote, git_str_cstr(target)); + + if (refspec == NULL) { + git_error_set(GIT_ERROR_NET, "the remote's default branch does not fit the refspec configuration"); + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + if ((error = git_refspec__transform( + &remote_branch_name, + refspec, + git_str_cstr(target))) < 0) + goto cleanup; + + if ((error = git_str_printf(&remote_head_name, + "%s%s/%s", + GIT_REFS_REMOTES_DIR, + git_remote_name(remote), + GIT_HEAD_FILE)) < 0) + goto cleanup; + + error = git_reference_symbolic_create( + &remote_head, + repo, + git_str_cstr(&remote_head_name), + git_str_cstr(&remote_branch_name), + true, + reflog_message); + +cleanup: + git_reference_free(remote_head); + git_str_dispose(&remote_branch_name); + git_str_dispose(&remote_head_name); + return error; +} + +static int update_head_to_remote( + git_repository *repo, + git_remote *remote, + const char *reflog_message) +{ + int error = 0; + size_t refs_len; + const git_remote_head *remote_head, **refs; + const git_oid *remote_head_id; + git_str branch = GIT_STR_INIT; + + if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) + return error; + + /* We cloned an empty repository or one with an unborn HEAD */ + if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE)) + return update_head_to_default(repo); + + /* We know we have HEAD, let's see where it points */ + remote_head = refs[0]; + GIT_ASSERT(remote_head); + + remote_head_id = &remote_head->oid; + + error = git_remote__default_branch(&branch, remote); + if (error == GIT_ENOTFOUND) { + error = git_repository_set_head_detached( + repo, remote_head_id); + goto cleanup; + } + + if ((error = update_remote_head(repo, remote, &branch, reflog_message)) < 0) + goto cleanup; + + error = update_head_to_new_branch( + repo, + remote_head_id, + git_str_cstr(&branch), + reflog_message); + +cleanup: + git_str_dispose(&branch); + + return error; +} + +static int update_head_to_branch( + git_repository *repo, + git_remote *remote, + const char *branch, + const char *reflog_message) +{ + int retcode; + git_str remote_branch_name = GIT_STR_INIT; + git_reference *remote_ref = NULL; + git_str default_branch = GIT_STR_INIT; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(branch); + + if ((retcode = git_str_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", + git_remote_name(remote), branch)) < 0 ) + goto cleanup; + + if ((retcode = git_reference_lookup(&remote_ref, repo, git_str_cstr(&remote_branch_name))) < 0) + goto cleanup; + + if ((retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch, + reflog_message)) < 0) + goto cleanup; + + if ((retcode = git_remote__default_branch(&default_branch, remote)) < 0) + goto cleanup; + + if (!git_remote__matching_refspec(remote, git_str_cstr(&default_branch))) + goto cleanup; + + retcode = update_remote_head(repo, remote, &default_branch, reflog_message); + +cleanup: + git_reference_free(remote_ref); + git_str_dispose(&remote_branch_name); + git_str_dispose(&default_branch); + return retcode; +} + +static int default_repository_create(git_repository **out, const char *path, int bare, void *payload) +{ + GIT_UNUSED(payload); + + return git_repository_init(out, path, bare); +} + +static int default_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload) +{ + GIT_UNUSED(payload); + + return git_remote_create(out, repo, name, url); +} + +/* + * submodules? + */ + +static int create_and_configure_origin( + git_remote **out, + git_repository *repo, + const char *url, + const git_clone_options *options) +{ + int error; + git_remote *origin = NULL; + char buf[GIT_PATH_MAX]; + git_remote_create_cb remote_create = options->remote_cb; + void *payload = options->remote_cb_payload; + + /* If the path exists and is a dir, the url should be the absolute path */ + if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) { + if (p_realpath(url, buf) == NULL) + return -1; + + url = buf; + } + + if (!remote_create) { + remote_create = default_remote_create; + payload = NULL; + } + + if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0) + goto on_error; + + *out = origin; + return 0; + +on_error: + git_remote_free(origin); + return error; +} + +static bool should_checkout( + git_repository *repo, + bool is_bare, + const git_checkout_options *opts) +{ + if (is_bare) + return false; + + if (!opts) + return false; + + if (opts->checkout_strategy == GIT_CHECKOUT_NONE) + return false; + + return !git_repository_head_unborn(repo); +} + +static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) +{ + int error; + + if (branch) + error = update_head_to_branch(repo, remote, branch, reflog_message); + /* Point HEAD to the same ref as the remote's head */ + else + error = update_head_to_remote(repo, remote, reflog_message); + + if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + error = git_checkout_head(repo, co_opts); + + return error; +} + +static int clone_into(git_repository *repo, git_remote *_remote, const git_fetch_options *opts, const git_checkout_options *co_opts, const char *branch) +{ + int error; + git_str reflog_message = GIT_STR_INIT; + git_fetch_options fetch_opts; + git_remote *remote; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(_remote); + + if (!git_repository_is_empty(repo)) { + git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); + return -1; + } + + if ((error = git_remote_dup(&remote, _remote)) < 0) + return error; + + memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); + fetch_opts.update_fetchhead = 0; + fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_str_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); + +cleanup: + git_remote_free(remote); + git_str_dispose(&reflog_message); + + return error; +} + +int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) +{ + git_str fromurl = GIT_STR_INIT; + const char *path = url_or_path; + bool is_url, is_local; + + if (local == GIT_CLONE_NO_LOCAL) + return 0; + + if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) { + if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) { + is_local = -1; + goto done; + } + + path = fromurl.ptr; + } + + is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && + git_fs_path_isdir(path); + +done: + git_str_dispose(&fromurl); + return is_local; +} + +static int git__clone( + git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *_options, + int use_existing) +{ + int error = 0; + git_repository *repo = NULL; + git_remote *origin; + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; + git_repository_create_cb repository_cb; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(local_path); + + if (_options) + memcpy(&options, _options, sizeof(git_clone_options)); + + GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); + + /* Only clone to a new directory or an empty directory */ + if (git_fs_path_exists(local_path) && !use_existing && !git_fs_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; + } + + /* Only remove the root directory on failure if we create it */ + if (git_fs_path_exists(local_path)) + rmdir_flags |= GIT_RMDIR_SKIP_ROOT; + + if (options.repository_cb) + repository_cb = options.repository_cb; + else + repository_cb = default_repository_create; + + if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0) + return error; + + if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { + int clone_local = git_clone__should_clone_local(url, options.local); + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; + + if (clone_local == 1) + error = clone_local_into( + repo, origin, &options.fetch_opts, &options.checkout_opts, + options.checkout_branch, link); + else if (clone_local == 0) + error = clone_into( + repo, origin, &options.fetch_opts, &options.checkout_opts, + options.checkout_branch); + else + error = -1; + + git_remote_free(origin); + } + + if (error != 0) { + git_error_state last_error = {0}; + git_error_state_capture(&last_error, error); + + git_repository_free(repo); + repo = NULL; + + (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); + + git_error_state_restore(&last_error); + } + + *out = repo; + return error; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_clone_init_options(git_clone_options *opts, unsigned int version) +{ + return git_clone_options_init(opts, version); +} +#endif + +static bool can_link(const char *src, const char *dst, int link) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(src); + GIT_UNUSED(dst); + GIT_UNUSED(link); + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link) +{ + int error, flags; + git_repository *src; + git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; + git_str reflog_message = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(remote); + + if (!git_repository_is_empty(repo)) { + git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); + return -1; + } + + /* + * Let's figure out what path we should use for the source + * repo, if it's not rooted, the path should be relative to + * the repository's worktree/gitdir. + */ + if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; + + /* Copy .git/objects/ from the source to the target */ + if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { + git_str_dispose(&src_path); + return error; + } + + if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || + git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { + error = -1; + goto cleanup; + } + + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + + error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE); + + /* + * can_link() doesn't catch all variations, so if we hit an + * error and did want to link, let's try again without trying + * to link. + */ + if (error < 0 && link) { + flags &= ~GIT_CPDIR_LINK_FILES; + error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE); + } + + if (error < 0) + goto cleanup; + + git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_str_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); + +cleanup: + git_str_dispose(&reflog_message); + git_str_dispose(&src_path); + git_str_dispose(&src_odb); + git_str_dispose(&dst_odb); + git_repository_free(src); + return error; +} diff --git a/src/libgit2/clone.h b/src/libgit2/clone.h new file mode 100644 index 000000000..7d73cabd5 --- /dev/null +++ b/src/libgit2/clone.h @@ -0,0 +1,20 @@ +/* + * 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_clone_h__ +#define INCLUDE_clone_h__ + +#include "common.h" + +#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/libgit2/commit.c b/src/libgit2/commit.c new file mode 100644 index 000000000..b137463f3 --- /dev/null +++ b/src/libgit2/commit.c @@ -0,0 +1,1042 @@ +/* + * 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 "commit.h" + +#include "git2/common.h" +#include "git2/object.h" +#include "git2/repository.h" +#include "git2/signature.h" +#include "git2/mailmap.h" +#include "git2/sys/commit.h" + +#include "buf.h" +#include "odb.h" +#include "commit.h" +#include "signature.h" +#include "refs.h" +#include "object.h" +#include "array.h" +#include "oidarray.h" + +void git_commit__free(void *_commit) +{ + git_commit *commit = _commit; + + git_array_clear(commit->parent_ids); + + git_signature_free(commit->author); + git_signature_free(commit->committer); + + git__free(commit->raw_header); + git__free(commit->raw_message); + git__free(commit->message_encoding); + git__free(commit->summary); + git__free(commit->body); + + git__free(commit); +} + +static int git_commit__create_buffer_internal( + git_str *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_array_oid_t *parents) +{ + size_t i = 0; + const git_oid *parent; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(tree); + + git_oid__writebuf(out, "tree ", tree); + + for (i = 0; i < git_array_size(*parents); i++) { + parent = git_array_get(*parents, i); + git_oid__writebuf(out, "parent ", parent); + } + + git_signature__writebuf(out, "author ", author); + git_signature__writebuf(out, "committer ", committer); + + if (message_encoding != NULL) + git_str_printf(out, "encoding %s\n", message_encoding); + + git_str_putc(out, '\n'); + + if (git_str_puts(out, message) < 0) + goto on_error; + + return 0; + +on_error: + git_str_dispose(out); + return -1; +} + +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) +{ + size_t i; + int error; + git_oid *parent_cpy; + const git_oid *parent; + + if (validate && !git_object__is_valid(repo, tree, GIT_OBJECT_TREE)) + return -1; + + i = 0; + while ((parent = parent_cb(i, parent_payload)) != NULL) { + if (validate && !git_object__is_valid(repo, parent, GIT_OBJECT_COMMIT)) { + error = -1; + goto on_error; + } + + parent_cpy = git_array_alloc(*parents); + GIT_ERROR_CHECK_ALLOC(parent_cpy); + + git_oid_cpy(parent_cpy, parent); + i++; + } + + if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) { + git_error_set(GIT_ERROR_OBJECT, "failed to create commit: current tip is not the first parent"); + error = GIT_EMODIFIED; + goto on_error; + } + + return 0; + +on_error: + git_array_clear(*parents); + return error; +} + +static int git_commit__create_internal( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_commit_parent_callback parent_cb, + void *parent_payload, + bool validate) +{ + int error; + git_odb *odb; + git_reference *ref = NULL; + git_str buf = GIT_STR_INIT; + const git_oid *current_id = NULL; + git_array_oid_t parents = GIT_ARRAY_INIT; + + if (update_ref) { + error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + } + git_error_clear(); + + if (ref) + current_id = git_reference_target(ref); + + if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0) + goto cleanup; + + error = git_commit__create_buffer_internal(&buf, author, committer, + message_encoding, message, tree, + &parents); + + if (error < 0) + goto cleanup; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto cleanup; + + if (git_odb__freshen(odb, tree) < 0) + goto cleanup; + + if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0) + goto cleanup; + + + if (update_ref != NULL) { + error = git_reference__update_for_commit( + repo, ref, update_ref, id, "commit"); + goto cleanup; + } + +cleanup: + git_array_clear(parents); + git_reference_free(ref); + git_str_dispose(&buf); + return error; +} + +int git_commit_create_from_callback( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_commit_parent_callback parent_cb, + void *parent_payload) +{ + return git_commit__create_internal( + id, repo, update_ref, author, committer, message_encoding, message, + tree, parent_cb, parent_payload, true); +} + +typedef struct { + size_t total; + va_list args; +} commit_parent_varargs; + +static const git_oid *commit_parent_from_varargs(size_t curr, void *payload) +{ + commit_parent_varargs *data = payload; + const git_commit *commit; + if (curr >= data->total) + return NULL; + commit = va_arg(data->args, const git_commit *); + return commit ? git_commit_id(commit) : NULL; +} + +int git_commit_create_v( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + ...) +{ + int error = 0; + commit_parent_varargs data; + + GIT_ASSERT_ARG(tree); + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + + data.total = parent_count; + va_start(data.args, parent_count); + + error = git_commit__create_internal( + id, repo, update_ref, author, committer, + message_encoding, message, git_tree_id(tree), + commit_parent_from_varargs, &data, false); + + va_end(data.args); + return error; +} + +typedef struct { + size_t total; + const git_oid **parents; +} commit_parent_oids; + +static const git_oid *commit_parent_from_ids(size_t curr, void *payload) +{ + commit_parent_oids *data = payload; + return (curr < data->total) ? data->parents[curr] : NULL; +} + +int git_commit_create_from_ids( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + size_t parent_count, + const git_oid *parents[]) +{ + commit_parent_oids data = { parent_count, parents }; + + return git_commit__create_internal( + id, repo, update_ref, author, committer, + message_encoding, message, tree, + commit_parent_from_ids, &data, true); +} + +typedef struct { + size_t total; + const git_commit **parents; + git_repository *repo; +} commit_parent_data; + +static const git_oid *commit_parent_from_array(size_t curr, void *payload) +{ + commit_parent_data *data = payload; + const git_commit *commit; + if (curr >= data->total) + return NULL; + commit = data->parents[curr]; + if (git_commit_owner(commit) != data->repo) + return NULL; + return git_commit_id(commit); +} + +int git_commit_create( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + commit_parent_data data = { parent_count, parents, repo }; + + GIT_ASSERT_ARG(tree); + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + + return git_commit__create_internal( + id, repo, update_ref, author, committer, + message_encoding, message, git_tree_id(tree), + commit_parent_from_array, &data, false); +} + +static const git_oid *commit_parent_for_amend(size_t curr, void *payload) +{ + const git_commit *commit_to_amend = payload; + if (curr >= git_array_size(commit_to_amend->parent_ids)) + return NULL; + return git_array_get(commit_to_amend->parent_ids, curr); +} + +int git_commit_amend( + git_oid *id, + const git_commit *commit_to_amend, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree) +{ + git_repository *repo; + git_oid tree_id; + git_reference *ref; + int error; + + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(commit_to_amend); + + repo = git_commit_owner(commit_to_amend); + + if (!author) + author = git_commit_author(commit_to_amend); + if (!committer) + committer = git_commit_committer(commit_to_amend); + if (!message_encoding) + message_encoding = git_commit_message_encoding(commit_to_amend); + if (!message) + message = git_commit_message(commit_to_amend); + + if (!tree) { + git_tree *old_tree; + GIT_ERROR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) ); + git_oid_cpy(&tree_id, git_tree_id(old_tree)); + git_tree_free(old_tree); + } else { + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + git_oid_cpy(&tree_id, git_tree_id(tree)); + } + + if (update_ref) { + if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0) + return error; + + if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) { + git_reference_free(ref); + git_error_set(GIT_ERROR_REFERENCE, "commit to amend is not the tip of the given branch"); + return -1; + } + } + + error = git_commit__create_internal( + id, repo, NULL, author, committer, message_encoding, message, + &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false); + + if (!error && update_ref) { + error = git_reference__update_for_commit( + repo, ref, NULL, id, "commit"); + git_reference_free(ref); + } + + return error; +} + +static int commit_parse(git_commit *commit, const char *data, size_t size, unsigned int flags) +{ + 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; + int error; + + GIT_ASSERT_ARG(commit); + GIT_ASSERT_ARG(data); + + buffer = buffer_start; + + /* Allocate for one, which will allow not to realloc 90% of the time */ + git_array_init_to_size(commit->parent_ids, 1); + GIT_ERROR_CHECK_ARRAY(commit->parent_ids); + + /* The tree is always the first field */ + 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! + */ + + while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { + git_oid *new_id = git_array_alloc(commit->parent_ids); + GIT_ERROR_CHECK_ALLOC(new_id); + + git_oid_cpy(new_id, &parent_id); + } + + if (!(flags & GIT_COMMIT_PARSE_QUICK)) { + commit->author = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(commit->author); + + if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < 0) + return error; + } + + /* Some tools create multiple author fields, ignore the extra ones */ + while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) { + if ((error = git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n')) < 0) + return error; + + git__free(dummy_sig.name); + git__free(dummy_sig.email); + } + + /* Always parse the committer; we need the commit time */ + commit->committer = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(commit->committer); + + if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < 0) + return error; + + if (flags & GIT_COMMIT_PARSE_QUICK) + return 0; + + /* Parse add'l header entries */ + while (buffer < buffer_end) { + const char *eoln = buffer; + if (buffer[-1] == '\n' && buffer[0] == '\n') + break; + + while (eoln < buffer_end && *eoln != '\n') + ++eoln; + + if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { + buffer += strlen("encoding "); + + commit->message_encoding = git__strndup(buffer, eoln - buffer); + GIT_ERROR_CHECK_ALLOC(commit->message_encoding); + } + + if (eoln < buffer_end && *eoln == '\n') + ++eoln; + buffer = eoln; + } + + header_len = buffer - buffer_start; + commit->raw_header = git__strndup(buffer_start, header_len); + GIT_ERROR_CHECK_ALLOC(commit->raw_header); + + /* point "buffer" to data after header, +1 for the final LF */ + buffer = buffer_start + header_len + 1; + + /* extract commit message */ + if (buffer <= buffer_end) + commit->raw_message = git__strndup(buffer, buffer_end - buffer); + else + commit->raw_message = git__strdup(""); + GIT_ERROR_CHECK_ALLOC(commit->raw_message); + + return 0; + +bad_buffer: + git_error_set(GIT_ERROR_OBJECT, "failed to parse bad commit object"); + return GIT_EINVALID; +} + +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_ext(_commit, odb_obj, 0); +} + +#define GIT_COMMIT_GETTER(_rvalue, _name, _return, _invalid) \ + _rvalue git_commit_##_name(const git_commit *commit) \ + {\ + GIT_ASSERT_ARG_WITH_RETVAL(commit, _invalid); \ + return _return; \ + } + +GIT_COMMIT_GETTER(const git_signature *, author, commit->author, NULL) +GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer, NULL) +GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message, NULL) +GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding, NULL) +GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header, NULL) +GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time, INT64_MIN) +GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset, -1) +GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids), 0) +GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id, NULL) + +const char *git_commit_message(const git_commit *commit) +{ + const char *message; + + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + message = commit->raw_message; + + /* trim leading newlines from raw message */ + while (*message && *message == '\n') + ++message; + + return message; +} + +const char *git_commit_summary(git_commit *commit) +{ + git_str summary = GIT_STR_INIT; + const char *msg, *space, *next; + bool space_contains_newline = false; + + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + if (!commit->summary) { + for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { + char next_character = msg[0]; + /* stop processing at the end of the first paragraph */ + if (next_character == '\n') { + if (!msg[1]) + break; + if (msg[1] == '\n') + break; + /* stop processing if next line contains only whitespace */ + next = msg + 1; + while (*next && git__isspace_nonlf(*next)) { + ++next; + } + if (!*next || *next == '\n') + break; + } + /* record the beginning of contiguous whitespace runs */ + if (git__isspace(next_character)) { + if(space == NULL) { + space = msg; + space_contains_newline = false; + } + space_contains_newline |= next_character == '\n'; + } + /* the next character is non-space */ + else { + /* process any recorded whitespace */ + if (space) { + if(space_contains_newline) + git_str_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */ + else + git_str_put(&summary, space, (msg - space)); /* otherwise copy it */ + space = NULL; + } + /* copy the next character */ + git_str_putc(&summary, next_character); + } + } + + commit->summary = git_str_detach(&summary); + if (!commit->summary) + commit->summary = git__strdup(""); + } + + return commit->summary; +} + +const char *git_commit_body(git_commit *commit) +{ + const char *msg, *end; + + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + if (!commit->body) { + /* search for end of summary */ + for (msg = git_commit_message(commit); *msg; ++msg) + if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) + break; + + /* trim leading and trailing whitespace */ + for (; *msg; ++msg) + if (!git__isspace(*msg)) + break; + for (end = msg + strlen(msg) - 1; msg <= end; --end) + if (!git__isspace(*end)) + break; + + if (*msg) + commit->body = git__strndup(msg, end - msg + 1); + } + + return commit->body; +} + +int git_commit_tree(git_tree **tree_out, const git_commit *commit) +{ + GIT_ASSERT_ARG(commit); + return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id); +} + +const git_oid *git_commit_parent_id( + const git_commit *commit, unsigned int n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + return git_array_get(commit->parent_ids, n); +} + +int git_commit_parent( + git_commit **parent, const git_commit *commit, unsigned int n) +{ + const git_oid *parent_id; + GIT_ASSERT_ARG(commit); + + parent_id = git_commit_parent_id(commit, n); + if (parent_id == NULL) { + git_error_set(GIT_ERROR_INVALID, "parent %u does not exist", n); + return GIT_ENOTFOUND; + } + + return git_commit_lookup(parent, commit->object.repo, parent_id); +} + +int git_commit_nth_gen_ancestor( + git_commit **ancestor, + const git_commit *commit, + unsigned int n) +{ + git_commit *current, *parent = NULL; + int error; + + GIT_ASSERT_ARG(ancestor); + GIT_ASSERT_ARG(commit); + + if (git_commit_dup(¤t, (git_commit *)commit) < 0) + return -1; + + if (n == 0) { + *ancestor = current; + return 0; + } + + while (n--) { + error = git_commit_parent(&parent, current, 0); + + git_commit_free(current); + + if (error < 0) + return error; + + current = parent; + } + + *ancestor = parent; + return 0; +} + +int git_commit_header_field( + git_buf *out, + const git_commit *commit, + const char *field) +{ + GIT_BUF_WRAP_PRIVATE(out, git_commit__header_field, commit, field); +} + +int git_commit__header_field( + git_str *out, + const git_commit *commit, + const char *field) +{ + const char *eol, *buf = commit->raw_header; + + git_str_clear(out); + + while ((eol = strchr(buf, '\n'))) { + /* We can skip continuations here */ + if (buf[0] == ' ') { + buf = eol + 1; + continue; + } + + /* Skip until we find the field we're after */ + if (git__prefixcmp(buf, field)) { + buf = eol + 1; + continue; + } + + buf += strlen(field); + /* Check that we're not matching a prefix but the field itself */ + if (buf[0] != ' ') { + buf = eol + 1; + continue; + } + + buf++; /* skip the SP */ + + git_str_put(out, buf, eol - buf); + if (git_str_oom(out)) + goto oom; + + /* If the next line starts with SP, it's multi-line, we must continue */ + while (eol[1] == ' ') { + git_str_putc(out, '\n'); + buf = eol + 2; + eol = strchr(buf, '\n'); + if (!eol) + goto malformed; + + git_str_put(out, buf, eol - buf); + } + + if (git_str_oom(out)) + goto oom; + + return 0; + } + + git_error_set(GIT_ERROR_OBJECT, "no such field '%s'", field); + return GIT_ENOTFOUND; + +malformed: + git_error_set(GIT_ERROR_OBJECT, "malformed header"); + return -1; +oom: + git_error_set_oom(); + return -1; +} + +int git_commit_extract_signature( + git_buf *signature_out, + git_buf *signed_data_out, + git_repository *repo, + git_oid *commit_id, + const char *field) +{ + git_str signature = GIT_STR_INIT, signed_data = GIT_STR_INIT; + int error; + + if ((error = git_buf_tostr(&signature, signature_out)) < 0 || + (error = git_buf_tostr(&signed_data, signed_data_out)) < 0 || + (error = git_commit__extract_signature(&signature, &signed_data, repo, commit_id, field)) < 0 || + (error = git_buf_fromstr(signature_out, &signature)) < 0 || + (error = git_buf_fromstr(signed_data_out, &signed_data)) < 0) + goto done; + +done: + git_str_dispose(&signature); + git_str_dispose(&signed_data); + return error; +} + +int git_commit__extract_signature( + git_str *signature, + git_str *signed_data, + git_repository *repo, + git_oid *commit_id, + const char *field) +{ + git_odb_object *obj; + git_odb *odb; + const char *buf; + const char *h, *eol; + int error; + + git_str_clear(signature); + git_str_clear(signed_data); + + if (!field) + field = "gpgsig"; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + if ((error = git_odb_read(&obj, odb, commit_id)) < 0) + return error; + + if (obj->cached.type != GIT_OBJECT_COMMIT) { + git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in the ODB"); + error = GIT_ENOTFOUND; + goto cleanup; + } + + buf = git_odb_object_data(obj); + + while ((h = strchr(buf, '\n')) && h[1] != '\0') { + h++; + if (git__prefixcmp(buf, field)) { + if (git_str_put(signed_data, buf, h - buf) < 0) + return -1; + + buf = h; + continue; + } + + h = buf; + h += strlen(field); + eol = strchr(h, '\n'); + if (h[0] != ' ') { + buf = h; + continue; + } + if (!eol) + goto malformed; + + h++; /* skip the SP */ + + git_str_put(signature, h, eol - h); + if (git_str_oom(signature)) + goto oom; + + /* If the next line starts with SP, it's multi-line, we must continue */ + while (eol[1] == ' ') { + git_str_putc(signature, '\n'); + h = eol + 2; + eol = strchr(h, '\n'); + if (!eol) + goto malformed; + + git_str_put(signature, h, eol - h); + } + + if (git_str_oom(signature)) + goto oom; + + error = git_str_puts(signed_data, eol+1); + git_odb_object_free(obj); + return error; + } + + git_error_set(GIT_ERROR_OBJECT, "this commit is not signed"); + error = GIT_ENOTFOUND; + goto cleanup; + +malformed: + git_error_set(GIT_ERROR_OBJECT, "malformed header"); + error = -1; + goto cleanup; +oom: + git_error_set_oom(); + error = -1; + goto cleanup; + +cleanup: + git_odb_object_free(obj); + git_str_clear(signature); + git_str_clear(signed_data); + return error; +} + +int git_commit_create_buffer( + git_buf *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo, + author, committer, message_encoding, message, + tree, parent_count, parents); +} + +int git_commit__create_buffer( + git_str *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + int error; + commit_parent_data data = { parent_count, parents, repo }; + git_array_oid_t parents_arr = GIT_ARRAY_INIT; + const git_oid *tree_id; + + GIT_ASSERT_ARG(tree); + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + + tree_id = git_tree_id(tree); + + if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0) + return error; + + error = git_commit__create_buffer_internal( + out, author, committer, + message_encoding, message, tree_id, + &parents_arr); + + git_array_clear(parents_arr); + return error; +} + +/** + * Append to 'out' properly marking continuations when there's a newline in 'content' + */ +static int format_header_field(git_str *out, const char *field, const char *content) +{ + const char *lf; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(field); + GIT_ASSERT_ARG(content); + + git_str_puts(out, field); + git_str_putc(out, ' '); + + while ((lf = strchr(content, '\n')) != NULL) { + git_str_put(out, content, lf - content); + git_str_puts(out, "\n "); + content = lf + 1; + } + + git_str_puts(out, content); + git_str_putc(out, '\n'); + + return git_str_oom(out) ? -1 : 0; +} + +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, + const char *commit_content, + const char *signature, + const char *signature_field) +{ + git_odb *odb; + int error = 0; + const char *field; + const char *header_end; + git_str commit = GIT_STR_INIT; + git_commit *parsed; + git_array_oid_t parents = GIT_ARRAY_INIT; + + /* 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 (commit_parse(parsed, commit_content, strlen(commit_content), 0) < 0) { + error = -1; + 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"); + error = -1; + goto cleanup; + } + + /* The header ends after the first LF */ + header_end++; + git_str_put(&commit, commit_content, header_end - commit_content); + + if (signature != NULL) { + field = signature_field ? signature_field : "gpgsig"; + + if ((error = format_header_field(&commit, field, signature)) < 0) + goto cleanup; + } + + git_str_puts(&commit, header_end); + + if (git_str_oom(&commit)) + return -1; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + goto cleanup; + + if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + +cleanup: + git_commit__free(parsed); + git_str_dispose(&commit); + return error; +} + +int git_commit_committer_with_mailmap( + git_signature **out, const git_commit *commit, const git_mailmap *mailmap) +{ + return git_mailmap_resolve_signature(out, mailmap, commit->committer); +} + +int git_commit_author_with_mailmap( + git_signature **out, const git_commit *commit, const git_mailmap *mailmap) +{ + return git_mailmap_resolve_signature(out, mailmap, commit->author); +} diff --git a/src/libgit2/commit.h b/src/libgit2/commit.h new file mode 100644 index 000000000..7a2454e61 --- /dev/null +++ b/src/libgit2/commit.h @@ -0,0 +1,69 @@ +/* + * 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_commit_h__ +#define INCLUDE_commit_h__ + +#include "common.h" + +#include "git2/commit.h" +#include "tree.h" +#include "repository.h" +#include "array.h" + +#include + +struct git_commit { + git_object object; + + git_array_t(git_oid) parent_ids; + git_oid tree_id; + + git_signature *author; + git_signature *committer; + + char *message_encoding; + char *raw_message; + char *raw_header; + + char *summary; + char *body; +}; + +int git_commit__header_field( + git_str *out, + const git_commit *commit, + const char *field); + +int git_commit__extract_signature( + git_str *signature, + git_str *signed_data, + git_repository *repo, + git_oid *commit_id, + const char *field); + +int git_commit__create_buffer( + git_str *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]); + +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/libgit2/commit_graph.c b/src/libgit2/commit_graph.c new file mode 100644 index 000000000..10947acec --- /dev/null +++ b/src/libgit2/commit_graph.c @@ -0,0 +1,1227 @@ +/* + * 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 "commit_graph.h" + +#include "array.h" +#include "buf.h" +#include "filebuf.h" +#include "futils.h" +#include "hash.h" +#include "oidarray.h" +#include "oidmap.h" +#include "pack.h" +#include "repository.h" +#include "revwalk.h" + +#define GIT_COMMIT_GRAPH_MISSING_PARENT 0x70000000 +#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_INFINITY 0xFFFFFFFF + +#define COMMIT_GRAPH_SIGNATURE 0x43475048 /* "CGPH" */ +#define COMMIT_GRAPH_VERSION 1 +#define COMMIT_GRAPH_OBJECT_ID_VERSION 1 +struct git_commit_graph_header { + uint32_t signature; + uint8_t version; + uint8_t object_id_version; + uint8_t chunks; + uint8_t base_graph_files; +}; + +#define COMMIT_GRAPH_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ +#define COMMIT_GRAPH_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ +#define COMMIT_GRAPH_COMMIT_DATA_ID 0x43444154 /* "CDAT" */ +#define COMMIT_GRAPH_EXTRA_EDGE_LIST_ID 0x45444745 /* "EDGE" */ +#define COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID 0x42494458 /* "BIDX" */ +#define COMMIT_GRAPH_BLOOM_FILTER_DATA_ID 0x42444154 /* "BDAT" */ + +struct git_commit_graph_chunk { + off64_t offset; + size_t length; +}; + +typedef git_array_t(size_t) parent_index_array_t; + +struct packed_commit { + size_t index; + git_oid sha1; + git_oid tree_oid; + uint32_t generation; + git_time_t commit_time; + git_array_oid_t parents; + parent_index_array_t parent_indices; +}; + +static void packed_commit_free(struct packed_commit *p) +{ + if (!p) + return; + + git_array_clear(p->parents); + git_array_clear(p->parent_indices); + git__free(p); +} + +static struct packed_commit *packed_commit_new(git_commit *commit) +{ + unsigned int i, parentcount = git_commit_parentcount(commit); + struct packed_commit *p = git__calloc(1, sizeof(struct packed_commit)); + if (!p) + goto cleanup; + + git_array_init_to_size(p->parents, parentcount); + if (parentcount && !p->parents.ptr) + goto cleanup; + + if (git_oid_cpy(&p->sha1, git_commit_id(commit)) < 0) + goto cleanup; + if (git_oid_cpy(&p->tree_oid, git_commit_tree_id(commit)) < 0) + goto cleanup; + p->commit_time = git_commit_time(commit); + + for (i = 0; i < parentcount; ++i) { + git_oid *parent_id = git_array_alloc(p->parents); + if (!parent_id) + goto cleanup; + if (git_oid_cpy(parent_id, git_commit_parent_id(commit, i)) < 0) + goto cleanup; + } + + return p; + +cleanup: + packed_commit_free(p); + return NULL; +} + +typedef int (*commit_graph_write_cb)(const char *buf, size_t size, void *cb_data); + +static int commit_graph_error(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "invalid commit-graph file - %s", message); + return -1; +} + +static int commit_graph_parse_oid_fanout( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_oid_fanout) +{ + uint32_t i, nr; + if (chunk_oid_fanout->offset == 0) + return commit_graph_error("missing OID Fanout chunk"); + if (chunk_oid_fanout->length == 0) + return commit_graph_error("empty OID Fanout chunk"); + if (chunk_oid_fanout->length != 256 * 4) + return commit_graph_error("OID Fanout chunk has wrong length"); + + file->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); + nr = 0; + for (i = 0; i < 256; ++i) { + uint32_t n = ntohl(file->oid_fanout[i]); + if (n < nr) + return commit_graph_error("index is non-monotonic"); + nr = n; + } + file->num_commits = nr; + return 0; +} + +static int commit_graph_parse_oid_lookup( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_oid_lookup) +{ + uint32_t i; + unsigned char *oid, *prev_oid, zero_oid[GIT_OID_RAWSZ] = {0}; + + if (chunk_oid_lookup->offset == 0) + return commit_graph_error("missing OID Lookup chunk"); + if (chunk_oid_lookup->length == 0) + return commit_graph_error("empty OID Lookup chunk"); + if (chunk_oid_lookup->length != file->num_commits * GIT_OID_RAWSZ) + return commit_graph_error("OID Lookup chunk has wrong length"); + + file->oid_lookup = oid = (unsigned char *)(data + chunk_oid_lookup->offset); + prev_oid = zero_oid; + for (i = 0; i < file->num_commits; ++i, oid += GIT_OID_RAWSZ) { + if (git_oid_raw_cmp(prev_oid, oid) >= 0) + return commit_graph_error("OID Lookup index is non-monotonic"); + prev_oid = oid; + } + + return 0; +} + +static int commit_graph_parse_commit_data( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_commit_data) +{ + if (chunk_commit_data->offset == 0) + return commit_graph_error("missing Commit Data chunk"); + if (chunk_commit_data->length == 0) + return commit_graph_error("empty Commit Data chunk"); + if (chunk_commit_data->length != file->num_commits * (GIT_OID_RAWSZ + 16)) + return commit_graph_error("Commit Data chunk has wrong length"); + + file->commit_data = data + chunk_commit_data->offset; + + return 0; +} + +static int commit_graph_parse_extra_edge_list( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_extra_edge_list) +{ + if (chunk_extra_edge_list->length == 0) + return 0; + if (chunk_extra_edge_list->length % 4 != 0) + return commit_graph_error("malformed Extra Edge List chunk"); + + file->extra_edge_list = data + chunk_extra_edge_list->offset; + file->num_extra_edge_list = chunk_extra_edge_list->length / 4; + + return 0; +} + +int git_commit_graph_file_parse( + git_commit_graph_file *file, + const unsigned char *data, + size_t size) +{ + struct git_commit_graph_header *hdr; + const unsigned char *chunk_hdr; + struct git_commit_graph_chunk *last_chunk; + uint32_t i; + off64_t last_chunk_offset, chunk_offset, trailer_offset; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + int error; + struct git_commit_graph_chunk chunk_oid_fanout = {0}, chunk_oid_lookup = {0}, + chunk_commit_data = {0}, chunk_extra_edge_list = {0}, + chunk_unsupported = {0}; + + GIT_ASSERT_ARG(file); + + if (size < sizeof(struct git_commit_graph_header) + GIT_OID_RAWSZ) + return commit_graph_error("commit-graph is too short"); + + hdr = ((struct git_commit_graph_header *)data); + + if (hdr->signature != htonl(COMMIT_GRAPH_SIGNATURE) || hdr->version != COMMIT_GRAPH_VERSION + || hdr->object_id_version != COMMIT_GRAPH_OBJECT_ID_VERSION) { + return commit_graph_error("unsupported commit-graph version"); + } + if (hdr->chunks == 0) + return commit_graph_error("no chunks in commit-graph"); + + /* + * The very first chunk's offset should be after the header, all the chunk + * headers, and a special zero chunk. + */ + last_chunk_offset = sizeof(struct git_commit_graph_header) + (1 + hdr->chunks) * 12; + trailer_offset = size - GIT_OID_RAWSZ; + checksum_size = GIT_HASH_SHA1_SIZE; + + if (trailer_offset < last_chunk_offset) + return commit_graph_error("wrong commit-graph size"); + memcpy(file->checksum, (data + trailer_offset), checksum_size); + + if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) + return commit_graph_error("could not calculate signature"); + if (memcmp(checksum, file->checksum, checksum_size) != 0) + return commit_graph_error("index signature mismatch"); + + chunk_hdr = data + sizeof(struct git_commit_graph_header); + last_chunk = NULL; + for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { + chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32 + | ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))); + if (chunk_offset < last_chunk_offset) + return commit_graph_error("chunks are non-monotonic"); + if (chunk_offset >= trailer_offset) + return commit_graph_error("chunks extend beyond the trailer"); + if (last_chunk != NULL) + last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); + last_chunk_offset = chunk_offset; + + switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) { + case COMMIT_GRAPH_OID_FANOUT_ID: + chunk_oid_fanout.offset = last_chunk_offset; + last_chunk = &chunk_oid_fanout; + break; + + case COMMIT_GRAPH_OID_LOOKUP_ID: + chunk_oid_lookup.offset = last_chunk_offset; + last_chunk = &chunk_oid_lookup; + break; + + case COMMIT_GRAPH_COMMIT_DATA_ID: + chunk_commit_data.offset = last_chunk_offset; + last_chunk = &chunk_commit_data; + break; + + case COMMIT_GRAPH_EXTRA_EDGE_LIST_ID: + chunk_extra_edge_list.offset = last_chunk_offset; + last_chunk = &chunk_extra_edge_list; + break; + + case COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID: + case COMMIT_GRAPH_BLOOM_FILTER_DATA_ID: + chunk_unsupported.offset = last_chunk_offset; + last_chunk = &chunk_unsupported; + break; + + default: + return commit_graph_error("unrecognized chunk ID"); + } + } + last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); + + error = commit_graph_parse_oid_fanout(file, data, &chunk_oid_fanout); + if (error < 0) + return error; + error = commit_graph_parse_oid_lookup(file, data, &chunk_oid_lookup); + if (error < 0) + return error; + error = commit_graph_parse_commit_data(file, data, &chunk_commit_data); + if (error < 0) + return error; + error = commit_graph_parse_extra_edge_list(file, data, &chunk_extra_edge_list); + if (error < 0) + return error; + + return 0; +} + +int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file) +{ + git_commit_graph *cgraph = NULL; + int error = 0; + + GIT_ASSERT_ARG(cgraph_out); + GIT_ASSERT_ARG(objects_dir); + + cgraph = git__calloc(1, sizeof(git_commit_graph)); + GIT_ERROR_CHECK_ALLOC(cgraph); + + error = git_str_joinpath(&cgraph->filename, objects_dir, "info/commit-graph"); + if (error < 0) + goto error; + + if (open_file) { + error = git_commit_graph_file_open(&cgraph->file, git_str_cstr(&cgraph->filename)); + if (error < 0) + goto error; + cgraph->checked = 1; + } + + *cgraph_out = cgraph; + return 0; + +error: + git_commit_graph_free(cgraph); + return error; +} + +int git_commit_graph_open(git_commit_graph **cgraph_out, const char *objects_dir) +{ + return git_commit_graph_new(cgraph_out, objects_dir, true); +} + +int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path) +{ + git_commit_graph_file *file; + git_file fd = -1; + size_t cgraph_size; + struct stat st; + int error; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "commit-graph file not found - '%s'", path); + return GIT_ENOTFOUND; + } + + if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); + return GIT_ENOTFOUND; + } + cgraph_size = (size_t)st.st_size; + + file = git__calloc(1, sizeof(git_commit_graph_file)); + GIT_ERROR_CHECK_ALLOC(file); + + error = git_futils_mmap_ro(&file->graph_map, fd, 0, cgraph_size); + p_close(fd); + if (error < 0) { + git_commit_graph_file_free(file); + return error; + } + + if ((error = git_commit_graph_file_parse(file, file->graph_map.data, cgraph_size)) < 0) { + git_commit_graph_file_free(file); + return error; + } + + *file_out = file; + return 0; +} + +int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph) +{ + if (!cgraph->checked) { + int error = 0; + git_commit_graph_file *result = NULL; + + /* We only check once, no matter the result. */ + cgraph->checked = 1; + + /* Best effort */ + error = git_commit_graph_file_open(&result, git_str_cstr(&cgraph->filename)); + + if (error < 0) + return error; + + cgraph->file = result; + } + if (!cgraph->file) + return GIT_ENOTFOUND; + + *file_out = cgraph->file; + return 0; +} + +void git_commit_graph_refresh(git_commit_graph *cgraph) +{ + if (!cgraph->checked) + return; + + if (cgraph->file + && git_commit_graph_file_needs_refresh(cgraph->file, git_str_cstr(&cgraph->filename))) { + /* We just free the commit graph. The next time it is requested, it will be + * re-loaded. */ + git_commit_graph_file_free(cgraph->file); + cgraph->file = NULL; + } + /* Force a lazy re-check next time it is needed. */ + cgraph->checked = 0; +} + +static int git_commit_graph_entry_get_byindex( + git_commit_graph_entry *e, + const git_commit_graph_file *file, + size_t pos) +{ + const unsigned char *commit_data; + + GIT_ASSERT_ARG(e); + GIT_ASSERT_ARG(file); + + if (pos >= file->num_commits) { + git_error_set(GIT_ERROR_INVALID, "commit index %zu does not exist", pos); + return GIT_ENOTFOUND; + } + + commit_data = file->commit_data + pos * (GIT_OID_RAWSZ + 4 * sizeof(uint32_t)); + git_oid_fromraw(&e->tree_oid, commit_data); + e->parent_indices[0] = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ))); + e->parent_indices[1] = ntohl( + *((uint32_t *)(commit_data + GIT_OID_RAWSZ + sizeof(uint32_t)))); + e->parent_count = (e->parent_indices[0] != GIT_COMMIT_GRAPH_MISSING_PARENT) + + (e->parent_indices[1] != GIT_COMMIT_GRAPH_MISSING_PARENT); + e->generation = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 2 * sizeof(uint32_t)))); + e->commit_time = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 3 * sizeof(uint32_t)))); + + e->commit_time |= (e->generation & UINT64_C(0x3)) << UINT64_C(32); + e->generation >>= 2u; + if (e->parent_indices[1] & 0x80000000u) { + uint32_t extra_edge_list_pos = e->parent_indices[1] & 0x7fffffff; + + /* Make sure we're not being sent out of bounds */ + if (extra_edge_list_pos >= file->num_extra_edge_list) { + git_error_set(GIT_ERROR_INVALID, + "commit %u does not exist", + extra_edge_list_pos); + return GIT_ENOTFOUND; + } + + e->extra_parents_index = extra_edge_list_pos; + while (extra_edge_list_pos < file->num_extra_edge_list + && (ntohl(*( + (uint32_t *)(file->extra_edge_list + + extra_edge_list_pos * sizeof(uint32_t)))) + & 0x80000000u) + == 0) { + extra_edge_list_pos++; + e->parent_count++; + } + } + + git_oid_fromraw(&e->sha1, &file->oid_lookup[pos * GIT_OID_RAWSZ]); + return 0; +} + +bool git_commit_graph_file_needs_refresh(const git_commit_graph_file *file, const char *path) +{ + git_file fd = -1; + struct stat st; + ssize_t bytes_read; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return true; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + return true; + } + + if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size) + || (size_t)st.st_size != file->graph_map.len) { + p_close(fd); + return true; + } + + bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - checksum_size); + p_close(fd); + if (bytes_read != (ssize_t)checksum_size) + return true; + + return (memcmp(checksum, file->checksum, checksum_size) != 0); +} + +int git_commit_graph_entry_find( + git_commit_graph_entry *e, + const git_commit_graph_file *file, + const git_oid *short_oid, + size_t len) +{ + int pos, found = 0; + uint32_t hi, lo; + const unsigned char *current = NULL; + + GIT_ASSERT_ARG(e); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(short_oid); + + hi = ntohl(file->oid_fanout[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(file->oid_fanout[(int)short_oid->id[0] - 1])); + + pos = git_pack__lookup_sha1(file->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = file->oid_lookup + (pos * GIT_OID_RAWSZ); + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = -1 - pos; + if (pos < (int)file->num_commits) { + current = file->oid_lookup + (pos * GIT_OID_RAWSZ); + + if (!git_oid_raw_ncmp(short_oid->id, current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)file->num_commits) { + /* Check for ambiguousity */ + const unsigned char *next = current + GIT_OID_RAWSZ; + + if (!git_oid_raw_ncmp(short_oid->id, next, len)) + found = 2; + } + + if (!found) + return git_odb__error_notfound( + "failed to find offset for commit-graph index entry", short_oid, len); + if (found > 1) + return git_odb__error_ambiguous( + "found multiple offsets for commit-graph index entry"); + + return git_commit_graph_entry_get_byindex(e, file, pos); +} + +int git_commit_graph_entry_parent( + git_commit_graph_entry *parent, + const git_commit_graph_file *file, + const git_commit_graph_entry *entry, + size_t n) +{ + GIT_ASSERT_ARG(parent); + GIT_ASSERT_ARG(file); + + if (n >= entry->parent_count) { + git_error_set(GIT_ERROR_INVALID, "parent index %zu does not exist", n); + return GIT_ENOTFOUND; + } + + if (n == 0 || (n == 1 && entry->parent_count == 2)) + return git_commit_graph_entry_get_byindex(parent, file, entry->parent_indices[n]); + + return git_commit_graph_entry_get_byindex( + parent, + file, + ntohl( + *(uint32_t *)(file->extra_edge_list + + (entry->extra_parents_index + n - 1) + * sizeof(uint32_t))) + & 0x7fffffff); +} + +int git_commit_graph_file_close(git_commit_graph_file *file) +{ + GIT_ASSERT_ARG(file); + + if (file->graph_map.data) + git_futils_mmap_free(&file->graph_map); + + return 0; +} + +void git_commit_graph_free(git_commit_graph *cgraph) +{ + if (!cgraph) + return; + + git_str_dispose(&cgraph->filename); + git_commit_graph_file_free(cgraph->file); + git__free(cgraph); +} + +void git_commit_graph_file_free(git_commit_graph_file *file) +{ + if (!file) + return; + + git_commit_graph_file_close(file); + git__free(file); +} + +static int packed_commit__cmp(const void *a_, const void *b_) +{ + const struct packed_commit *a = a_; + const struct packed_commit *b = b_; + return git_oid_cmp(&a->sha1, &b->sha1); +} + +int git_commit_graph_writer_new(git_commit_graph_writer **out, const char *objects_info_dir) +{ + git_commit_graph_writer *w = git__calloc(1, sizeof(git_commit_graph_writer)); + GIT_ERROR_CHECK_ALLOC(w); + + if (git_str_sets(&w->objects_info_dir, objects_info_dir) < 0) { + git__free(w); + return -1; + } + + if (git_vector_init(&w->commits, 0, packed_commit__cmp) < 0) { + git_str_dispose(&w->objects_info_dir); + git__free(w); + return -1; + } + + *out = w; + return 0; +} + +void git_commit_graph_writer_free(git_commit_graph_writer *w) +{ + struct packed_commit *packed_commit; + size_t i; + + if (!w) + return; + + git_vector_foreach (&w->commits, i, packed_commit) + packed_commit_free(packed_commit); + git_vector_free(&w->commits); + git_str_dispose(&w->objects_info_dir); + git__free(w); +} + +struct object_entry_cb_state { + git_repository *repo; + git_odb *db; + git_vector *commits; +}; + +static int object_entry__cb(const git_oid *id, void *data) +{ + struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; + git_commit *commit = NULL; + struct packed_commit *packed_commit = NULL; + size_t header_len; + git_object_t header_type; + int error = 0; + + error = git_odb_read_header(&header_len, &header_type, state->db, id); + if (error < 0) + return error; + + if (header_type != GIT_OBJECT_COMMIT) + return 0; + + error = git_commit_lookup(&commit, state->repo, id); + if (error < 0) + return error; + + packed_commit = packed_commit_new(commit); + git_commit_free(commit); + GIT_ERROR_CHECK_ALLOC(packed_commit); + + error = git_vector_insert(state->commits, packed_commit); + if (error < 0) { + packed_commit_free(packed_commit); + return error; + } + + return 0; +} + +int git_commit_graph_writer_add_index_file( + git_commit_graph_writer *w, + git_repository *repo, + const char *idx_path) +{ + int error; + struct git_pack_file *p = NULL; + struct object_entry_cb_state state = {0}; + state.repo = repo; + state.commits = &w->commits; + + error = git_repository_odb(&state.db, repo); + if (error < 0) + goto cleanup; + + error = git_mwindow_get_pack(&p, idx_path); + if (error < 0) + goto cleanup; + + error = git_pack_foreach_entry(p, object_entry__cb, &state); + if (error < 0) + goto cleanup; + +cleanup: + if (p) + git_mwindow_put_pack(p); + git_odb_free(state.db); + return error; +} + +int git_commit_graph_writer_add_revwalk(git_commit_graph_writer *w, git_revwalk *walk) +{ + int error; + git_oid id; + git_repository *repo = git_revwalk_repository(walk); + git_commit *commit; + struct packed_commit *packed_commit; + + while ((git_revwalk_next(&id, walk)) == 0) { + error = git_commit_lookup(&commit, repo, &id); + if (error < 0) + return error; + + packed_commit = packed_commit_new(commit); + git_commit_free(commit); + GIT_ERROR_CHECK_ALLOC(packed_commit); + + error = git_vector_insert(&w->commits, packed_commit); + if (error < 0) { + packed_commit_free(packed_commit); + return error; + } + } + + return 0; +} + +enum generation_number_commit_state { + GENERATION_NUMBER_COMMIT_STATE_UNVISITED = 0, + GENERATION_NUMBER_COMMIT_STATE_ADDED = 1, + GENERATION_NUMBER_COMMIT_STATE_EXPANDED = 2, + GENERATION_NUMBER_COMMIT_STATE_VISITED = 3 +}; + +static int compute_generation_numbers(git_vector *commits) +{ + git_array_t(size_t) index_stack = GIT_ARRAY_INIT; + size_t i, j; + size_t *parent_idx; + enum generation_number_commit_state *commit_states = NULL; + struct packed_commit *child_packed_commit; + git_oidmap *packed_commit_map = NULL; + int error = 0; + + /* First populate the parent indices fields */ + error = git_oidmap_new(&packed_commit_map); + if (error < 0) + goto cleanup; + git_vector_foreach (commits, i, child_packed_commit) { + child_packed_commit->index = i; + error = git_oidmap_set( + packed_commit_map, &child_packed_commit->sha1, child_packed_commit); + if (error < 0) + goto cleanup; + } + + git_vector_foreach (commits, i, child_packed_commit) { + size_t parent_i, *parent_idx_ptr; + struct packed_commit *parent_packed_commit; + git_oid *parent_id; + git_array_init_to_size( + child_packed_commit->parent_indices, + git_array_size(child_packed_commit->parents)); + if (git_array_size(child_packed_commit->parents) + && !child_packed_commit->parent_indices.ptr) { + error = -1; + goto cleanup; + } + git_array_foreach (child_packed_commit->parents, parent_i, parent_id) { + parent_packed_commit = git_oidmap_get(packed_commit_map, parent_id); + if (!parent_packed_commit) { + git_error_set(GIT_ERROR_ODB, + "parent commit %s not found in commit graph", + git_oid_tostr_s(parent_id)); + error = GIT_ENOTFOUND; + goto cleanup; + } + parent_idx_ptr = git_array_alloc(child_packed_commit->parent_indices); + if (!parent_idx_ptr) { + error = -1; + goto cleanup; + } + *parent_idx_ptr = parent_packed_commit->index; + } + } + + /* + * We copy all the commits to the stack and then during visitation, + * each node can be added up to two times to the stack. + */ + git_array_init_to_size(index_stack, 3 * git_vector_length(commits)); + if (!index_stack.ptr) { + error = -1; + goto cleanup; + } + + commit_states = (enum generation_number_commit_state *)git__calloc( + git_vector_length(commits), sizeof(enum generation_number_commit_state)); + if (!commit_states) { + error = -1; + goto cleanup; + } + + /* + * Perform a Post-Order traversal so that all parent nodes are fully + * visited before the child node. + */ + git_vector_foreach (commits, i, child_packed_commit) + *(size_t *)git_array_alloc(index_stack) = i; + + while (git_array_size(index_stack)) { + size_t *index_ptr = git_array_pop(index_stack); + i = *index_ptr; + child_packed_commit = git_vector_get(commits, i); + + if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_VISITED) { + /* This commit has already been fully visited. */ + continue; + } + if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_EXPANDED) { + /* All of the commits parents have been visited. */ + child_packed_commit->generation = 0; + git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { + struct packed_commit *parent = git_vector_get(commits, *parent_idx); + if (child_packed_commit->generation < parent->generation) + child_packed_commit->generation = parent->generation; + } + if (child_packed_commit->generation + < GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) { + ++child_packed_commit->generation; + } + commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; + continue; + } + + /* + * This is the first time we see this commit. We need + * to visit all its parents before we can fully visit + * it. + */ + if (git_array_size(child_packed_commit->parent_indices) == 0) { + /* + * Special case: if the commit has no parents, there's + * no need to add it to the stack just to immediately + * remove it. + */ + commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; + child_packed_commit->generation = 1; + continue; + } + + /* + * Add this current commit again so that it is visited + * again once all its children have been visited. + */ + *(size_t *)git_array_alloc(index_stack) = i; + git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { + if (commit_states[*parent_idx] + != GENERATION_NUMBER_COMMIT_STATE_UNVISITED) { + /* This commit has already been considered. */ + continue; + } + + commit_states[*parent_idx] = GENERATION_NUMBER_COMMIT_STATE_ADDED; + *(size_t *)git_array_alloc(index_stack) = *parent_idx; + } + commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_EXPANDED; + } + +cleanup: + git_oidmap_free(packed_commit_map); + git__free(commit_states); + git_array_clear(index_stack); + + return error; +} + +static int write_offset(off64_t offset, commit_graph_write_cb write_cb, void *cb_data) +{ + int error; + uint32_t word; + + word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + + return 0; +} + +static int write_chunk_header( + int chunk_id, + off64_t offset, + commit_graph_write_cb write_cb, + void *cb_data) +{ + uint32_t word = htonl(chunk_id); + int error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + return write_offset(offset, write_cb, cb_data); +} + +static int commit_graph_write_buf(const char *buf, size_t size, void *data) +{ + git_str *b = (git_str *)data; + return git_str_put(b, buf, size); +} + +struct commit_graph_write_hash_context { + commit_graph_write_cb write_cb; + void *cb_data; + git_hash_ctx *ctx; +}; + +static int commit_graph_write_hash(const char *buf, size_t size, void *data) +{ + struct commit_graph_write_hash_context *ctx = data; + int error; + + error = git_hash_update(ctx->ctx, buf, size); + if (error < 0) + return error; + + return ctx->write_cb(buf, size, ctx->cb_data); +} + +static void packed_commit_free_dup(void *packed_commit) +{ + packed_commit_free(packed_commit); +} + +static int commit_graph_write( + git_commit_graph_writer *w, + commit_graph_write_cb write_cb, + void *cb_data) +{ + int error = 0; + size_t i; + struct packed_commit *packed_commit; + struct git_commit_graph_header hdr = {0}; + uint32_t oid_fanout_count; + uint32_t extra_edge_list_count; + uint32_t oid_fanout[256]; + off64_t offset; + git_str oid_lookup = GIT_STR_INIT, commit_data = GIT_STR_INIT, + extra_edge_list = GIT_STR_INIT; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + git_hash_ctx ctx; + struct commit_graph_write_hash_context hash_cb_data = {0}; + + hdr.signature = htonl(COMMIT_GRAPH_SIGNATURE); + hdr.version = COMMIT_GRAPH_VERSION; + hdr.object_id_version = COMMIT_GRAPH_OBJECT_ID_VERSION; + hdr.chunks = 0; + hdr.base_graph_files = 0; + hash_cb_data.write_cb = write_cb; + hash_cb_data.cb_data = cb_data; + hash_cb_data.ctx = &ctx; + + checksum_size = GIT_HASH_SHA1_SIZE; + error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); + if (error < 0) + return error; + cb_data = &hash_cb_data; + write_cb = commit_graph_write_hash; + + /* Sort the commits. */ + git_vector_sort(&w->commits); + git_vector_uniq(&w->commits, packed_commit_free_dup); + error = compute_generation_numbers(&w->commits); + if (error < 0) + goto cleanup; + + /* Fill the OID Fanout table. */ + oid_fanout_count = 0; + for (i = 0; i < 256; i++) { + while (oid_fanout_count < git_vector_length(&w->commits) && + (packed_commit = (struct packed_commit *)git_vector_get(&w->commits, oid_fanout_count)) && + packed_commit->sha1.id[0] <= i) + ++oid_fanout_count; + oid_fanout[i] = htonl(oid_fanout_count); + } + + /* Fill the OID Lookup table. */ + git_vector_foreach (&w->commits, i, packed_commit) { + error = git_str_put(&oid_lookup, + (const char *)&packed_commit->sha1.id, + GIT_OID_RAWSZ); + + if (error < 0) + goto cleanup; + } + + /* Fill the Commit Data and Extra Edge List tables. */ + extra_edge_list_count = 0; + git_vector_foreach (&w->commits, i, packed_commit) { + uint64_t commit_time; + uint32_t generation; + uint32_t word; + size_t *packed_index; + unsigned int parentcount = (unsigned int)git_array_size(packed_commit->parents); + + error = git_str_put(&commit_data, + (const char *)&packed_commit->tree_oid.id, + GIT_OID_RAWSZ); + + if (error < 0) + goto cleanup; + + if (parentcount == 0) { + word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); + } else { + packed_index = git_array_get(packed_commit->parent_indices, 0); + word = htonl((uint32_t)*packed_index); + } + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + + if (parentcount < 2) { + word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); + } else if (parentcount == 2) { + packed_index = git_array_get(packed_commit->parent_indices, 1); + word = htonl((uint32_t)*packed_index); + } else { + word = htonl(0x80000000u | extra_edge_list_count); + } + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + + if (parentcount > 2) { + unsigned int parent_i; + for (parent_i = 1; parent_i < parentcount; ++parent_i) { + packed_index = git_array_get( + packed_commit->parent_indices, parent_i); + word = htonl((uint32_t)(*packed_index | (parent_i + 1 == parentcount ? 0x80000000u : 0))); + + error = git_str_put(&extra_edge_list, + (const char *)&word, + sizeof(word)); + if (error < 0) + goto cleanup; + } + extra_edge_list_count += parentcount - 1; + } + + generation = packed_commit->generation; + commit_time = (uint64_t)packed_commit->commit_time; + if (generation > GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) + generation = GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX; + word = ntohl((uint32_t)((generation << 2) | (((uint32_t)(commit_time >> 32)) & 0x3) )); + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + word = ntohl((uint32_t)(commit_time & 0xfffffffful)); + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + } + + /* Write the header. */ + hdr.chunks = 3; + if (git_str_len(&extra_edge_list) > 0) + hdr.chunks++; + error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); + if (error < 0) + goto cleanup; + + /* Write the chunk headers. */ + offset = sizeof(hdr) + (hdr.chunks + 1) * 12; + error = write_chunk_header(COMMIT_GRAPH_OID_FANOUT_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += sizeof(oid_fanout); + error = write_chunk_header(COMMIT_GRAPH_OID_LOOKUP_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&oid_lookup); + error = write_chunk_header(COMMIT_GRAPH_COMMIT_DATA_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&commit_data); + if (git_str_len(&extra_edge_list) > 0) { + error = write_chunk_header( + COMMIT_GRAPH_EXTRA_EDGE_LIST_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&extra_edge_list); + } + error = write_chunk_header(0, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + + /* Write all the chunks. */ + error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&commit_data), git_str_len(&commit_data), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&extra_edge_list), git_str_len(&extra_edge_list), cb_data); + if (error < 0) + goto cleanup; + + /* Finalize the checksum and write the trailer. */ + error = git_hash_final(checksum, &ctx); + if (error < 0) + goto cleanup; + error = write_cb((char *)checksum, checksum_size, cb_data); + if (error < 0) + goto cleanup; + +cleanup: + git_str_dispose(&oid_lookup); + git_str_dispose(&commit_data); + git_str_dispose(&extra_edge_list); + git_hash_ctx_cleanup(&ctx); + return error; +} + +static int commit_graph_write_filebuf(const char *buf, size_t size, void *data) +{ + git_filebuf *f = (git_filebuf *)data; + return git_filebuf_write(f, buf, size); +} + +int git_commit_graph_writer_options_init( + git_commit_graph_writer_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, + version, + git_commit_graph_writer_options, + GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT); + return 0; +} + +int git_commit_graph_writer_commit( + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts) +{ + int error; + int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; + git_str commit_graph_path = GIT_STR_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + + /* TODO: support options and fill in defaults. */ + GIT_UNUSED(opts); + + error = git_str_joinpath( + &commit_graph_path, git_str_cstr(&w->objects_info_dir), "commit-graph"); + if (error < 0) + return error; + + if (git_repository__fsync_gitdir) + filebuf_flags |= GIT_FILEBUF_FSYNC; + error = git_filebuf_open(&output, git_str_cstr(&commit_graph_path), filebuf_flags, 0644); + git_str_dispose(&commit_graph_path); + if (error < 0) + return error; + + error = commit_graph_write(w, commit_graph_write_filebuf, &output); + if (error < 0) { + git_filebuf_cleanup(&output); + return error; + } + + return git_filebuf_commit(&output); +} + +int git_commit_graph_writer_dump( + git_buf *cgraph, + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts) +{ + GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w, opts); +} + +int git_commit_graph__writer_dump( + git_str *cgraph, + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts) +{ + /* TODO: support options. */ + GIT_UNUSED(opts); + return commit_graph_write(w, commit_graph_write_buf, cgraph); +} diff --git a/src/libgit2/commit_graph.h b/src/libgit2/commit_graph.h new file mode 100644 index 000000000..b78ab8177 --- /dev/null +++ b/src/libgit2/commit_graph.h @@ -0,0 +1,169 @@ +/* + * 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_commit_graph_h__ +#define INCLUDE_commit_graph_h__ + +#include "common.h" + +#include "git2/types.h" +#include "git2/sys/commit_graph.h" + +#include "map.h" +#include "vector.h" +#include "oid.h" +#include "hash.h" + +/** + * A commit-graph file. + * + * This file contains metadata about commits, particularly the generation + * number for each one. This can help speed up graph operations without + * requiring a full graph traversal. + * + * Support for this feature was added in git 2.19. + */ +typedef struct git_commit_graph_file { + git_map graph_map; + + /* The OID Fanout table. */ + const uint32_t *oid_fanout; + /* The total number of commits in the graph. */ + uint32_t num_commits; + + /* The OID Lookup table. */ + unsigned char *oid_lookup; + + /* + * The Commit Data table. Each entry contains the OID of the commit followed + * by two 8-byte fields in network byte order: + * - The indices of the first two parents (32 bits each). + * - The generation number (first 30 bits) and commit time in seconds since + * UNIX epoch (34 bits). + */ + const unsigned char *commit_data; + + /* + * The Extra Edge List table. Each 4-byte entry is a network byte order index + * of one of the i-th (i > 0) parents of commits in the `commit_data` table, + * when the commit has more than 2 parents. + */ + const unsigned char *extra_edge_list; + /* The number of entries in the Extra Edge List table. Each entry is 4 bytes wide. */ + size_t num_extra_edge_list; + + /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ + unsigned char checksum[GIT_HASH_SHA1_SIZE]; +} git_commit_graph_file; + +/** + * An entry in the commit-graph file. Provides a subset of the information that + * can be obtained from the commit header. + */ +typedef struct git_commit_graph_entry { + /* The generation number of the commit within the graph */ + size_t generation; + + /* Time in seconds from UNIX epoch. */ + git_time_t commit_time; + + /* The number of parents of the commit. */ + size_t parent_count; + + /* + * The indices of the parent commits within the Commit Data table. The value + * of `GIT_COMMIT_GRAPH_MISSING_PARENT` indicates that no parent is in that + * position. + */ + size_t parent_indices[2]; + + /* The index within the Extra Edge List of any parent after the first two. */ + size_t extra_parents_index; + + /* The SHA-1 hash of the root tree of the commit. */ + git_oid tree_oid; + + /* The SHA-1 hash of the requested commit. */ + git_oid sha1; +} git_commit_graph_entry; + +/* A wrapper for git_commit_graph_file to enable lazy loading in the ODB. */ +struct git_commit_graph { + /* The path to the commit-graph file. Something like ".git/objects/info/commit-graph". */ + git_str filename; + + /* The underlying commit-graph file. */ + git_commit_graph_file *file; + + /* Whether the commit-graph file was already checked for validity. */ + bool checked; +}; + +/** Create a new commit-graph, optionally opening the underlying file. */ +int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file); + +/** Open and validate a commit-graph file. */ +int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path); + +/* + * Attempt to get the git_commit_graph's commit-graph file. This object is + * still owned by the git_commit_graph. If the repository does not contain a commit graph, + * it will return GIT_ENOTFOUND. + * + * This function is not thread-safe. + */ +int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph); + +/* Marks the commit-graph file as needing a refresh. */ +void git_commit_graph_refresh(git_commit_graph *cgraph); + +/* + * A writer for `commit-graph` files. + */ +struct git_commit_graph_writer { + /* + * The path of the `objects/info` directory where the `commit-graph` will be + * stored. + */ + git_str objects_info_dir; + + /* The list of packed commits. */ + git_vector commits; +}; + +int git_commit_graph__writer_dump( + git_str *cgraph, + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts); + +/* + * Returns whether the git_commit_graph_file needs to be reloaded since the + * contents of the commit-graph file have changed on disk. + */ +bool git_commit_graph_file_needs_refresh( + const git_commit_graph_file *file, const char *path); + +int git_commit_graph_entry_find( + git_commit_graph_entry *e, + const git_commit_graph_file *file, + const git_oid *short_oid, + size_t len); +int git_commit_graph_entry_parent( + git_commit_graph_entry *parent, + const git_commit_graph_file *file, + const git_commit_graph_entry *entry, + size_t n); +int git_commit_graph_file_close(git_commit_graph_file *cgraph); +void git_commit_graph_file_free(git_commit_graph_file *cgraph); + +/* This is exposed for use in the fuzzers. */ +int git_commit_graph_file_parse( + git_commit_graph_file *file, + const unsigned char *data, + size_t size); + +#endif diff --git a/src/libgit2/commit_list.c b/src/libgit2/commit_list.c new file mode 100644 index 000000000..4585508bc --- /dev/null +++ b/src/libgit2/commit_list.c @@ -0,0 +1,208 @@ +/* + * 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 "commit_list.h" + +#include "revwalk.h" +#include "pool.h" +#include "odb.h" +#include "commit.h" + +int git_commit_list_generation_cmp(const void *a, const void *b) +{ + uint32_t generation_a = ((git_commit_list_node *) a)->generation; + uint32_t generation_b = ((git_commit_list_node *) b)->generation; + + if (!generation_a || !generation_b) { + /* Fall back to comparing by timestamps if at least one commit lacks a generation. */ + return git_commit_list_time_cmp(a, b); + } + + if (generation_a < generation_b) + return 1; + if (generation_a > generation_b) + return -1; + + return 0; +} + +int git_commit_list_time_cmp(const void *a, const void *b) +{ + int64_t time_a = ((git_commit_list_node *) a)->time; + int64_t time_b = ((git_commit_list_node *) b)->time; + + if (time_a < time_b) + return 1; + if (time_a > time_b) + return -1; + + return 0; +} + +git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p) +{ + git_commit_list *new_list = git__malloc(sizeof(git_commit_list)); + if (new_list != NULL) { + new_list->item = item; + new_list->next = *list_p; + } + *list_p = new_list; + return new_list; +} + +git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p) +{ + git_commit_list **pp = list_p; + git_commit_list *p; + + while ((p = *pp) != NULL) { + if (git_commit_list_time_cmp(p->item, item) > 0) + break; + + pp = &p->next; + } + + return git_commit_list_insert(item, pp); +} + +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 git_commit_list_node **alloc_parents( + git_revwalk *walk, git_commit_list_node *commit, size_t n_parents) +{ + size_t bytes; + + if (n_parents <= PARENTS_PER_COMMIT) + return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node)); + + if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *))) + return NULL; + + return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes); +} + + +void git_commit_list_free(git_commit_list **list_p) +{ + git_commit_list *list = *list_p; + + if (list == NULL) + return; + + while (list) { + git_commit_list *temp = list; + list = temp->next; + git__free(temp); + } + + *list_p = NULL; +} + +git_commit_list_node *git_commit_list_pop(git_commit_list **stack) +{ + git_commit_list *top = *stack; + git_commit_list_node *item = top ? top->item : NULL; + + if (top) { + *stack = top->next; + git__free(top); + } + return item; +} + +static int commit_quick_parse( + git_revwalk *walk, + git_commit_list_node *node, + git_odb_object *obj) +{ + git_oid *parent_oid; + git_commit *commit; + size_t i; + + commit = git__calloc(1, sizeof(*commit)); + GIT_ERROR_CHECK_ALLOC(commit); + commit->object.repo = walk->repo; + + if (git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK) < 0) { + git__free(commit); + return -1; + } + + 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; + } + + node->generation = 0; + 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); + + git_array_foreach(commit->parent_ids, i, parent_oid) { + node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid); + } + + git_commit__free(commit); + + node->parsed = 1; + + return 0; +} + +int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit) +{ + git_odb_object *obj; + git_commit_graph_file *cgraph_file = NULL; + int error; + + if (commit->parsed) + return 0; + + /* Let's try to use the commit graph first. */ + git_odb__get_commit_graph_file(&cgraph_file, walk->odb); + if (cgraph_file) { + git_commit_graph_entry e; + + error = git_commit_graph_entry_find(&e, cgraph_file, &commit->oid, GIT_OID_RAWSZ); + if (error == 0 && git__is_uint16(e.parent_count)) { + size_t i; + commit->generation = (uint32_t)e.generation; + commit->time = e.commit_time; + commit->out_degree = (uint16_t)e.parent_count; + commit->parents = alloc_parents(walk, commit, commit->out_degree); + GIT_ERROR_CHECK_ALLOC(commit->parents); + + for (i = 0; i < commit->out_degree; ++i) { + git_commit_graph_entry parent; + error = git_commit_graph_entry_parent(&parent, cgraph_file, &e, i); + if (error < 0) + return error; + commit->parents[i] = git_revwalk__commit_lookup(walk, &parent.sha1); + } + commit->parsed = 1; + return 0; + } + } + + if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) + return error; + + if (obj->cached.type != GIT_OBJECT_COMMIT) { + git_error_set(GIT_ERROR_INVALID, "object is no commit object"); + error = -1; + } else + error = commit_quick_parse(walk, commit, obj); + + git_odb_object_free(obj); + return error; +} + diff --git a/src/libgit2/commit_list.h b/src/libgit2/commit_list.h new file mode 100644 index 000000000..aad39f351 --- /dev/null +++ b/src/libgit2/commit_list.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_commit_list_h__ +#define INCLUDE_commit_list_h__ + +#include "common.h" + +#include "git2/oid.h" + +#define PARENT1 (1 << 0) +#define PARENT2 (1 << 1) +#define RESULT (1 << 2) +#define STALE (1 << 3) +#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT) + +#define PARENTS_PER_COMMIT 2 +#define COMMIT_ALLOC \ + (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *)) + +#define FLAG_BITS 4 + +typedef struct git_commit_list_node { + git_oid oid; + int64_t time; + uint32_t generation; + unsigned int seen:1, + uninteresting:1, + topo_delay:1, + parsed:1, + added:1, + flags : FLAG_BITS; + + uint16_t in_degree; + uint16_t out_degree; + + struct git_commit_list_node **parents; +} git_commit_list_node; + +typedef struct git_commit_list { + git_commit_list_node *item; + struct git_commit_list *next; +} git_commit_list; + +git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk); +int git_commit_list_generation_cmp(const void *a, const void *b); +int git_commit_list_time_cmp(const void *a, const void *b); +void git_commit_list_free(git_commit_list **list_p); +git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p); +git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p); +int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit); +git_commit_list_node *git_commit_list_pop(git_commit_list **stack); + +#endif diff --git a/src/libgit2/common.h b/src/libgit2/common.h new file mode 100644 index 000000000..bb9ec5ac1 --- /dev/null +++ b/src/libgit2/common.h @@ -0,0 +1,55 @@ +/* + * 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_common_h__ +#define INCLUDE_common_h__ + +#include "git2_util.h" +#include "errors.h" + +/* +* Include the declarations for deprecated functions; this ensures +* that they're decorated with the proper extern/visibility attributes. +*/ +#include "git2/deprecated.h" + +#include "posix.h" + +/** + * Initialize a structure with a version. + */ +GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version) +{ + memset(structure, 0, len); + *((int*)structure) = version; +} +#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V) + +#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \ + TYPE _tmpl = TPL; \ + GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \ + memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0) + +/** + * Check a versioned structure for validity + */ +GIT_INLINE(int) git_error__check_version(const void *structure, unsigned int expected_max, const char *name) +{ + unsigned int actual; + + if (!structure) + return 0; + + actual = *(const unsigned int*)structure; + if (actual > 0 && actual <= expected_max) + return 0; + + git_error_set(GIT_ERROR_INVALID, "invalid version %d on %s", actual, name); + return -1; +} +#define GIT_ERROR_CHECK_VERSION(S,V,N) if (git_error__check_version(S,V,N) < 0) return -1 + +#endif diff --git a/src/libgit2/config.c b/src/libgit2/config.c new file mode 100644 index 000000000..5c366e221 --- /dev/null +++ b/src/libgit2/config.c @@ -0,0 +1,1573 @@ +/* + * 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 "git2/config.h" +#include "git2/sys/config.h" + +#include "buf.h" +#include "config_backend.h" +#include "regexp.h" +#include "sysdir.h" +#include "transaction.h" +#include "vector.h" +#if GIT_WIN32 +# include +#endif + +#include + +void git_config_entry_free(git_config_entry *entry) +{ + if (!entry) + return; + + entry->free(entry); +} + +typedef struct { + git_refcount rc; + + git_config_backend *backend; + git_config_level_t level; +} backend_internal; + +static void backend_internal_free(backend_internal *internal) +{ + git_config_backend *backend; + + backend = internal->backend; + backend->free(backend); + git__free(internal); +} + +static void config_free(git_config *cfg) +{ + size_t i; + backend_internal *internal; + + for (i = 0; i < cfg->backends.length; ++i) { + internal = git_vector_get(&cfg->backends, i); + GIT_REFCOUNT_DEC(internal, backend_internal_free); + } + + git_vector_free(&cfg->backends); + + git__memzero(cfg, sizeof(*cfg)); + git__free(cfg); +} + +void git_config_free(git_config *cfg) +{ + if (cfg == NULL) + return; + + GIT_REFCOUNT_DEC(cfg, config_free); +} + +static int config_backend_cmp(const void *a, const void *b) +{ + const backend_internal *bk_a = (const backend_internal *)(a); + const backend_internal *bk_b = (const backend_internal *)(b); + + return bk_b->level - bk_a->level; +} + +int git_config_new(git_config **out) +{ + git_config *cfg; + + cfg = git__malloc(sizeof(git_config)); + GIT_ERROR_CHECK_ALLOC(cfg); + + memset(cfg, 0x0, sizeof(git_config)); + + if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { + git__free(cfg); + return -1; + } + + *out = cfg; + GIT_REFCOUNT_INC(cfg); + return 0; +} + +int git_config_add_file_ondisk( + git_config *cfg, + const char *path, + git_config_level_t level, + const git_repository *repo, + int force) +{ + git_config_backend *file = NULL; + struct stat st; + int res; + + GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(path); + + res = p_stat(path, &st); + if (res < 0 && errno != ENOENT && errno != ENOTDIR) { + git_error_set(GIT_ERROR_CONFIG, "failed to stat '%s'", path); + return -1; + } + + if (git_config_backend_from_file(&file, path) < 0) + return -1; + + if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { + /* + * free manually; the file is not owned by the config + * instance yet and will not be freed on cleanup + */ + file->free(file); + return res; + } + + return 0; +} + +int git_config_open_ondisk(git_config **out, const char *path) +{ + int error; + git_config *config; + + *out = NULL; + + if (git_config_new(&config) < 0) + return -1; + + if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)) < 0) + git_config_free(config); + else + *out = config; + + return error; +} + +int git_config_snapshot(git_config **out, git_config *in) +{ + int error = 0; + size_t i; + backend_internal *internal; + git_config *config; + + *out = NULL; + + if (git_config_new(&config) < 0) + return -1; + + git_vector_foreach(&in->backends, i, internal) { + git_config_backend *b; + + if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) + break; + + if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { + b->free(b); + break; + } + } + + if (error < 0) + git_config_free(config); + else + *out = config; + + return error; +} + +static int find_backend_by_level( + backend_internal **out, + const git_config *cfg, + git_config_level_t level) +{ + int pos = -1; + backend_internal *internal; + size_t i; + + /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend + * which has the highest level. As config backends are stored in a vector + * sorted by decreasing order of level, getting the backend at position 0 + * will do the job. + */ + if (level == GIT_CONFIG_HIGHEST_LEVEL) { + pos = 0; + } else { + git_vector_foreach(&cfg->backends, i, internal) { + if (internal->level == level) + pos = (int)i; + } + } + + if (pos == -1) { + git_error_set(GIT_ERROR_CONFIG, + "no configuration exists for the given level '%i'", (int)level); + return GIT_ENOTFOUND; + } + + *out = git_vector_get(&cfg->backends, pos); + + return 0; +} + +static int duplicate_level(void **old_raw, void *new_raw) +{ + backend_internal **old = (backend_internal **)old_raw; + + GIT_UNUSED(new_raw); + + git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); + return GIT_EEXISTS; +} + +static void try_remove_existing_backend( + git_config *cfg, + git_config_level_t level) +{ + int pos = -1; + backend_internal *internal; + size_t i; + + git_vector_foreach(&cfg->backends, i, internal) { + if (internal->level == level) + pos = (int)i; + } + + if (pos == -1) + return; + + internal = git_vector_get(&cfg->backends, pos); + + if (git_vector_remove(&cfg->backends, pos) < 0) + return; + + GIT_REFCOUNT_DEC(internal, backend_internal_free); +} + +static int git_config__add_internal( + git_config *cfg, + backend_internal *internal, + git_config_level_t level, + int force) +{ + int result; + + /* delete existing config backend for level if it exists */ + if (force) + try_remove_existing_backend(cfg, level); + + if ((result = git_vector_insert_sorted(&cfg->backends, + internal, &duplicate_level)) < 0) + return result; + + git_vector_sort(&cfg->backends); + internal->backend->cfg = cfg; + + GIT_REFCOUNT_INC(internal); + + return 0; +} + +int git_config_open_global(git_config **cfg_out, git_config *cfg) +{ + if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) + return 0; + + return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); +} + +int git_config_open_level( + git_config **cfg_out, + const git_config *cfg_parent, + git_config_level_t level) +{ + git_config *cfg; + backend_internal *internal; + int res; + + if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) + return res; + + if ((res = git_config_new(&cfg)) < 0) + return res; + + if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { + git_config_free(cfg); + return res; + } + + *cfg_out = cfg; + + return 0; +} + +int git_config_add_backend( + git_config *cfg, + git_config_backend *backend, + git_config_level_t level, + const git_repository *repo, + int force) +{ + backend_internal *internal; + int result; + + GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(backend); + + GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); + + if ((result = backend->open(backend, level, repo)) < 0) + return result; + + internal = git__malloc(sizeof(backend_internal)); + GIT_ERROR_CHECK_ALLOC(internal); + + memset(internal, 0x0, sizeof(backend_internal)); + + internal->backend = backend; + internal->level = level; + + if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { + git__free(internal); + return result; + } + + return 0; +} + +/* + * Loop over all the variables + */ + +typedef struct { + git_config_iterator parent; + git_config_iterator *current; + const git_config *cfg; + git_regexp regex; + size_t i; +} all_iter; + +static int find_next_backend(size_t *out, const git_config *cfg, size_t i) +{ + backend_internal *internal; + + for (; i > 0; --i) { + internal = git_vector_get(&cfg->backends, i - 1); + if (!internal || !internal->backend) + continue; + + *out = i; + return 0; + } + + return -1; +} + +static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + backend_internal *internal; + git_config_backend *backend; + size_t i; + int error = 0; + + if (iter->current != NULL && + (error = iter->current->next(entry, iter->current)) == 0) { + return 0; + } + + if (error < 0 && error != GIT_ITEROVER) + return error; + + do { + if (find_next_backend(&i, iter->cfg, iter->i) < 0) + return GIT_ITEROVER; + + internal = git_vector_get(&iter->cfg->backends, i - 1); + backend = internal->backend; + iter->i = i - 1; + + if (iter->current) + iter->current->free(iter->current); + + iter->current = NULL; + error = backend->iterator(&iter->current, backend); + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + return error; + + error = iter->current->next(entry, iter->current); + /* If this backend is empty, then keep going */ + if (error == GIT_ITEROVER) + continue; + + return error; + + } while(1); + + return GIT_ITEROVER; +} + +static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) +{ + int error; + all_iter *iter = (all_iter *) _iter; + + /* + * We use the "normal" function to grab the next one across + * backends and then apply the regex + */ + while ((error = all_iter_next(entry, _iter)) == 0) { + /* skip non-matching keys if regexp was provided */ + if (git_regexp_match(&iter->regex, (*entry)->name) != 0) + continue; + + /* and simply return if we like the entry's name */ + return 0; + } + + return error; +} + +static void all_iter_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + if (iter->current) + iter->current->free(iter->current); + + git__free(iter); +} + +static void all_iter_glob_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + git_regexp_dispose(&iter->regex); + all_iter_free(_iter); +} + +int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +{ + all_iter *iter; + + iter = git__calloc(1, sizeof(all_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->parent.free = all_iter_free; + iter->parent.next = all_iter_next; + + iter->i = cfg->backends.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +{ + all_iter *iter; + int result; + + if (regexp == NULL) + return git_config_iterator_new(out, cfg); + + iter = git__calloc(1, sizeof(all_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + if ((result = git_regexp_compile(&iter->regex, regexp, 0)) < 0) { + git__free(iter); + return -1; + } + + iter->parent.next = all_iter_glob_next; + iter->parent.free = all_iter_glob_free; + iter->i = cfg->backends.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + +int git_config_foreach( + const git_config *cfg, git_config_foreach_cb cb, void *payload) +{ + return git_config_foreach_match(cfg, NULL, cb, payload); +} + +int git_config_backend_foreach_match( + git_config_backend *backend, + const char *regexp, + git_config_foreach_cb cb, + void *payload) +{ + git_config_entry *entry; + git_config_iterator *iter; + git_regexp regex; + int error = 0; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(cb); + + if (regexp && git_regexp_compile(®ex, regexp, 0) < 0) + return -1; + + if ((error = backend->iterator(&iter, backend)) < 0) { + iter = NULL; + return -1; + } + + while (!(iter->next(&entry, iter) < 0)) { + /* skip non-matching keys if regexp was provided */ + if (regexp && git_regexp_match(®ex, entry->name) != 0) + continue; + + /* abort iterator on non-zero return value */ + if ((error = cb(entry, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (regexp != NULL) + git_regexp_dispose(®ex); + + iter->free(iter); + + return error; +} + +int git_config_foreach_match( + const git_config *cfg, + const char *regexp, + git_config_foreach_cb cb, + void *payload) +{ + int error; + git_config_iterator *iter; + git_config_entry *entry; + + if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + return error; + + while (!(error = git_config_next(&entry, iter))) { + if ((error = cb(entry, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + git_config_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +/************** + * Setters + **************/ + +typedef enum { + BACKEND_USE_SET, + BACKEND_USE_DELETE +} backend_use; + +static const char *uses[] = { + "set", + "delete" +}; + +static int get_backend_for_use(git_config_backend **out, + git_config *cfg, const char *name, backend_use use) +{ + size_t i; + backend_internal *backend; + + *out = NULL; + + if (git_vector_length(&cfg->backends) == 0) { + git_error_set(GIT_ERROR_CONFIG, + "cannot %s value for '%s' when no config backends exist", + uses[use], name); + return GIT_ENOTFOUND; + } + + git_vector_foreach(&cfg->backends, i, backend) { + if (!backend->backend->readonly) { + *out = backend->backend; + return 0; + } + } + + git_error_set(GIT_ERROR_CONFIG, + "cannot %s value for '%s' when all config backends are readonly", + uses[use], name); + return GIT_ENOTFOUND; +} + +int git_config_delete_entry(git_config *cfg, const char *name) +{ + git_config_backend *backend; + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; + + return backend->del(backend, name); +} + +int git_config_set_int64(git_config *cfg, const char *name, int64_t value) +{ + char str_value[32]; /* All numbers should fit in here */ + p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); + return git_config_set_string(cfg, name, str_value); +} + +int git_config_set_int32(git_config *cfg, const char *name, int32_t value) +{ + return git_config_set_int64(cfg, name, (int64_t)value); +} + +int git_config_set_bool(git_config *cfg, const char *name, int value) +{ + return git_config_set_string(cfg, name, value ? "true" : "false"); +} + +int git_config_set_string(git_config *cfg, const char *name, const char *value) +{ + int error; + git_config_backend *backend; + + if (!value) { + git_error_set(GIT_ERROR_CONFIG, "the value to set cannot be NULL"); + return -1; + } + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) + return GIT_ENOTFOUND; + + error = backend->set(backend, name, value); + + if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) + git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); + + return error; +} + +int git_config__update_entry( + git_config *config, + const char *key, + const char *value, + bool overwrite_existing, + bool only_if_existing) +{ + int error = 0; + git_config_entry *ce = NULL; + + if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0) + return error; + + if (!ce && only_if_existing) /* entry doesn't exist */ + return 0; + if (ce && !overwrite_existing) /* entry would be overwritten */ + return 0; + if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */ + return 0; + if (!value && (!ce || !ce->value)) /* asked to delete absent entry */ + return 0; + + if (!value) + error = git_config_delete_entry(config, key); + else + error = git_config_set_string(config, key, value); + + git_config_entry_free(ce); + return error; +} + +/*********** + * Getters + ***********/ + +static int config_error_notfound(const char *name) +{ + git_error_set(GIT_ERROR_CONFIG, "config value '%s' was not found", name); + return GIT_ENOTFOUND; +} + +enum { + GET_ALL_ERRORS = 0, + GET_NO_MISSING = 1, + GET_NO_ERRORS = 2 +}; + +static int get_entry( + git_config_entry **out, + const git_config *cfg, + const char *name, + bool normalize_name, + int want_errors) +{ + int res = GIT_ENOTFOUND; + const char *key = name; + char *normalized = NULL; + size_t i; + backend_internal *internal; + + *out = NULL; + + if (normalize_name) { + if ((res = git_config__normalize_name(name, &normalized)) < 0) + goto cleanup; + key = normalized; + } + + res = GIT_ENOTFOUND; + git_vector_foreach(&cfg->backends, i, internal) { + if (!internal || !internal->backend) + continue; + + res = internal->backend->get(internal->backend, key, out); + if (res != GIT_ENOTFOUND) + break; + } + + git__free(normalized); + +cleanup: + if (res == GIT_ENOTFOUND) + res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); + else if (res && (want_errors == GET_NO_ERRORS)) { + git_error_clear(); + res = 0; + } + + return res; +} + +int git_config_get_entry( + git_config_entry **out, const git_config *cfg, const char *name) +{ + return get_entry(out, cfg, name, true, GET_ALL_ERRORS); +} + +int git_config__lookup_entry( + git_config_entry **out, + const git_config *cfg, + const char *key, + bool no_errors) +{ + return get_entry( + out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); +} + +int git_config_get_mapped( + int *out, + const git_config *cfg, + const char *name, + const git_configmap *maps, + size_t map_n) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_lookup_map_value(out, maps, map_n, entry->value); + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_parse_int64(out, entry->value); + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_parse_int32(out, entry->value); + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_bool(int *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_parse_bool(out, entry->value); + git_config_entry_free(entry); + + return ret; +} + +static int is_readonly(const git_config *cfg) +{ + size_t i; + backend_internal *internal; + + git_vector_foreach(&cfg->backends, i, internal) { + if (!internal || !internal->backend) + continue; + + if (!internal->backend->readonly) + return 0; + } + + return 1; +} + +static int git_config__parse_path(git_str *out, const char *value) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(value); + + if (value[0] == '~') { + if (value[1] != '\0' && value[1] != '/') { + git_error_set(GIT_ERROR_CONFIG, "retrieving a homedir by name is not supported"); + return -1; + } + + return git_sysdir_expand_global_file(out, value[1] ? &value[2] : NULL); + } + + return git_str_sets(out, value); +} + +int git_config_parse_path(git_buf *out, const char *value) +{ + GIT_BUF_WRAP_PRIVATE(out, git_config__parse_path, value); +} + +int git_config_get_path( + git_buf *out, + const git_config *cfg, + const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); +} + +int git_config__get_path( + git_str *out, + const git_config *cfg, + const char *name) +{ + git_config_entry *entry; + int error; + + if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return error; + + error = git_config__parse_path(out, entry->value); + git_config_entry_free(entry); + + return error; +} + +int git_config_get_string( + const char **out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if (!is_readonly(cfg)) { + git_error_set(GIT_ERROR_CONFIG, "get_string called on a live config object"); + return -1; + } + + ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + *out = !ret ? (entry->value ? entry->value : "") : NULL; + + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_string_buf( + git_buf *out, const git_config *cfg, const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); +} + +int git_config__get_string_buf( + git_str *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + const char *str; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(cfg); + + ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + str = !ret ? (entry->value ? entry->value : "") : NULL; + + if (str) + ret = git_str_puts(out, str); + + git_config_entry_free(entry); + + return ret; +} + +char *git_config__get_string_force( + const git_config *cfg, const char *key, const char *fallback_value) +{ + git_config_entry *entry; + char *ret; + + get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL; + git_config_entry_free(entry); + + return ret; +} + +int git_config__get_bool_force( + const git_config *cfg, const char *key, int fallback_value) +{ + int val = fallback_value; + git_config_entry *entry; + + get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + + if (entry && git_config_parse_bool(&val, entry->value) < 0) + git_error_clear(); + + git_config_entry_free(entry); + return val; +} + +int git_config__get_int_force( + const git_config *cfg, const char *key, int fallback_value) +{ + int32_t val = (int32_t)fallback_value; + git_config_entry *entry; + + get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + + if (entry && git_config_parse_int32(&val, entry->value) < 0) + git_error_clear(); + + git_config_entry_free(entry); + return (int)val; +} + +int git_config_get_multivar_foreach( + const git_config *cfg, const char *name, const char *regexp, + git_config_foreach_cb cb, void *payload) +{ + int err, found; + git_config_iterator *iter; + git_config_entry *entry; + + if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) + return err; + + found = 0; + while ((err = iter->next(&entry, iter)) == 0) { + found = 1; + + if ((err = cb(entry, payload)) != 0) { + git_error_set_after_callback(err); + break; + } + } + + iter->free(iter); + if (err == GIT_ITEROVER) + err = 0; + + if (found == 0 && err == 0) + err = config_error_notfound(name); + + return err; +} + +typedef struct { + git_config_iterator parent; + git_config_iterator *iter; + char *name; + git_regexp regex; + int have_regex; +} multivar_iter; + +static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + int error = 0; + + while ((error = iter->iter->next(entry, iter->iter)) == 0) { + if (git__strcmp(iter->name, (*entry)->name)) + continue; + + if (!iter->have_regex) + return 0; + + if (git_regexp_match(&iter->regex, (*entry)->value) == 0) + return 0; + } + + return error; +} + +static void multivar_iter_free(git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + + iter->iter->free(iter->iter); + + git__free(iter->name); + if (iter->have_regex) + git_regexp_dispose(&iter->regex); + git__free(iter); +} + +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +{ + multivar_iter *iter = NULL; + git_config_iterator *inner = NULL; + int error; + + if ((error = git_config_iterator_new(&inner, cfg)) < 0) + return error; + + iter = git__calloc(1, sizeof(multivar_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + if ((error = git_config__normalize_name(name, &iter->name)) < 0) + goto on_error; + + if (regexp != NULL) { + if ((error = git_regexp_compile(&iter->regex, regexp, 0)) < 0) + goto on_error; + + iter->have_regex = 1; + } + + iter->iter = inner; + iter->parent.free = multivar_iter_free; + iter->parent.next = multivar_iter_next; + + *out = (git_config_iterator *) iter; + + return 0; + +on_error: + + inner->free(inner); + git__free(iter); + return error; +} + +int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) +{ + git_config_backend *backend; + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; + + return backend->set_multivar(backend, name, regexp, value); +} + +int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) +{ + git_config_backend *backend; + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; + + return backend->del_multivar(backend, name, regexp); +} + +int git_config_next(git_config_entry **entry, git_config_iterator *iter) +{ + return iter->next(entry, iter); +} + +void git_config_iterator_free(git_config_iterator *iter) +{ + if (iter == NULL) + return; + + iter->free(iter); +} + +int git_config_find_global(git_buf *path) +{ + GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_GLOBAL); +} + +int git_config__find_global(git_str *path) +{ + return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); +} + +int git_config_find_xdg(git_buf *path) +{ + GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_xdg_file, GIT_CONFIG_FILENAME_XDG); +} + +int git_config__find_xdg(git_str *path) +{ + return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG); +} + +int git_config_find_system(git_buf *path) +{ + GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_system_file, GIT_CONFIG_FILENAME_SYSTEM); +} + +int git_config__find_system(git_str *path) +{ + return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); +} + +int git_config_find_programdata(git_buf *path) +{ + git_str str = GIT_STR_INIT; + int error; + + if ((error = git_buf_tostr(&str, path)) == 0 && + (error = git_config__find_programdata(&str)) == 0) + error = git_buf_fromstr(path, &str); + + git_str_dispose(&str); + return error; +} + +int git_config__find_programdata(git_str *path) +{ + git_fs_path_owner_t owner_level = + GIT_FS_PATH_OWNER_CURRENT_USER | + GIT_FS_PATH_OWNER_ADMINISTRATOR; + bool is_safe; + + if (git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA) < 0 || + git_fs_path_owner_is(&is_safe, path->ptr, owner_level) < 0) + return -1; + + if (!is_safe) { + git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership"); + return -1; + } + + return 0; +} + +int git_config__global_location(git_str *buf) +{ + const git_str *paths; + const char *sep, *start; + + if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0) + return -1; + + /* no paths, so give up */ + if (!paths || !git_str_len(paths)) + return -1; + + /* find unescaped separator or end of string */ + for (sep = start = git_str_cstr(paths); *sep; ++sep) { + if (*sep == GIT_PATH_LIST_SEPARATOR && + (sep <= start || sep[-1] != '\\')) + break; + } + + if (git_str_set(buf, start, (size_t)(sep - start)) < 0) + return -1; + + return git_str_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL); +} + +int git_config_open_default(git_config **out) +{ + int error; + git_config *cfg = NULL; + git_str buf = GIT_STR_INIT; + + if ((error = git_config_new(&cfg)) < 0) + return error; + + if (!git_config__find_global(&buf) || + !git_config__global_location(&buf)) { + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); + } + + if (!error && !git_config__find_xdg(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_XDG, NULL, 0); + + if (!error && !git_config__find_system(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); + + if (!error && !git_config__find_programdata(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); + + git_str_dispose(&buf); + + if (error) { + git_config_free(cfg); + cfg = NULL; + } + + *out = cfg; + + return error; +} + +int git_config_lock(git_transaction **out, git_config *cfg) +{ + int error; + git_config_backend *backend; + backend_internal *internal; + + GIT_ASSERT_ARG(cfg); + + internal = git_vector_get(&cfg->backends, 0); + if (!internal || !internal->backend) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); + return -1; + } + backend = internal->backend; + + if ((error = backend->lock(backend)) < 0) + return error; + + return git_transaction_config_new(out, cfg); +} + +int git_config_unlock(git_config *cfg, int commit) +{ + git_config_backend *backend; + backend_internal *internal; + + GIT_ASSERT_ARG(cfg); + + internal = git_vector_get(&cfg->backends, 0); + if (!internal || !internal->backend) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); + return -1; + } + + backend = internal->backend; + + return backend->unlock(backend, commit); +} + +/*********** + * Parsers + ***********/ + +int git_config_lookup_map_value( + int *out, + const git_configmap *maps, + size_t map_n, + const char *value) +{ + size_t i; + + for (i = 0; i < map_n; ++i) { + const git_configmap *m = maps + i; + + switch (m->type) { + case GIT_CONFIGMAP_FALSE: + case GIT_CONFIGMAP_TRUE: { + int bool_val; + + if (git_config_parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->type) { + *out = m->map_value; + return 0; + } + break; + } + + case GIT_CONFIGMAP_INT32: + if (git_config_parse_int32(out, value) == 0) + return 0; + break; + + case GIT_CONFIGMAP_STRING: + if (value && strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; + } + break; + } + } + + git_error_set(GIT_ERROR_CONFIG, "failed to map '%s'", value); + return -1; +} + +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_configmap *m = &maps[i]; + + if (m->map_value != enum_val) + continue; + + *type_out = m->type; + *str_out = m->str_match; + return 0; + } + + git_error_set(GIT_ERROR_CONFIG, "invalid enum value"); + return GIT_ENOTFOUND; +} + +int git_config_parse_bool(int *out, const char *value) +{ + if (git__parse_bool(out, value) == 0) + return 0; + + if (git_config_parse_int32(out, value) == 0) { + *out = !!(*out); + return 0; + } + + git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a boolean value", value); + return -1; +} + +int git_config_parse_int64(int64_t *out, const char *value) +{ + const char *num_end; + int64_t num; + + if (!value || git__strntol64(&num, value, strlen(value), &num_end, 0) < 0) + goto fail_parse; + + switch (*num_end) { + case 'g': + case 'G': + num *= 1024; + /* fallthrough */ + + case 'm': + case 'M': + num *= 1024; + /* fallthrough */ + + case 'k': + case 'K': + num *= 1024; + + /* check that that there are no more characters after the + * given modifier suffix */ + if (num_end[1] != '\0') + return -1; + + /* fallthrough */ + + case '\0': + *out = num; + return 0; + + default: + goto fail_parse; + } + +fail_parse: + git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as an integer", value ? value : "(null)"); + return -1; +} + +int git_config_parse_int32(int32_t *out, const char *value) +{ + int64_t tmp; + int32_t truncate; + + if (git_config_parse_int64(&tmp, value) < 0) + goto fail_parse; + + truncate = tmp & 0xFFFFFFFF; + if (truncate != tmp) + goto fail_parse; + + *out = truncate; + return 0; + +fail_parse: + git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a 32-bit integer", value ? value : "(null)"); + return -1; +} + +static int normalize_section(char *start, char *end) +{ + char *scan; + + if (start == end) + return GIT_EINVALIDSPEC; + + /* Validate and downcase range */ + for (scan = start; *scan; ++scan) { + if (end && scan >= end) + break; + if (isalnum(*scan)) + *scan = (char)git__tolower(*scan); + else if (*scan != '-' || scan == start) + return GIT_EINVALIDSPEC; + } + + if (scan == start) + return GIT_EINVALIDSPEC; + + return 0; +} + + +/* Take something the user gave us and make it nice for our hash function */ +int git_config__normalize_name(const char *in, char **out) +{ + char *name, *fdot, *ldot; + + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(out); + + name = git__strdup(in); + GIT_ERROR_CHECK_ALLOC(name); + + fdot = strchr(name, '.'); + ldot = strrchr(name, '.'); + + if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) + goto invalid; + + /* Validate and downcase up to first dot and after last dot */ + if (normalize_section(name, fdot) < 0 || + normalize_section(ldot + 1, NULL) < 0) + goto invalid; + + /* If there is a middle range, make sure it doesn't have newlines */ + while (fdot < ldot) + if (*fdot++ == '\n') + goto invalid; + + *out = name; + return 0; + +invalid: + git__free(name); + git_error_set(GIT_ERROR_CONFIG, "invalid config item name '%s'", in); + return GIT_EINVALIDSPEC; +} + +struct rename_data { + git_config *config; + git_str *name; + size_t old_len; +}; + +static int rename_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + int error = 0; + struct rename_data *data = (struct rename_data *)payload; + size_t base_len = git_str_len(data->name); + + if (base_len > 0 && + !(error = git_str_puts(data->name, entry->name + data->old_len))) + { + error = git_config_set_string( + data->config, git_str_cstr(data->name), entry->value); + + git_str_truncate(data->name, base_len); + } + + if (!error) + error = git_config_delete_entry(data->config, entry->name); + + return error; +} + +int git_config_rename_section( + git_repository *repo, + const char *old_section_name, + const char *new_section_name) +{ + git_config *config; + git_str pattern = GIT_STR_INIT, replace = GIT_STR_INIT; + int error = 0; + struct rename_data data; + + git_str_puts_escape_regex(&pattern, old_section_name); + + if ((error = git_str_puts(&pattern, "\\..+")) < 0) + goto cleanup; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + goto cleanup; + + data.config = config; + data.name = &replace; + data.old_len = strlen(old_section_name) + 1; + + if ((error = git_str_join(&replace, '.', new_section_name, "")) < 0) + goto cleanup; + + if (new_section_name != NULL && + (error = normalize_section(replace.ptr, strchr(replace.ptr, '.'))) < 0) + { + git_error_set( + GIT_ERROR_CONFIG, "invalid config section '%s'", new_section_name); + goto cleanup; + } + + error = git_config_foreach_match( + config, git_str_cstr(&pattern), rename_config_entries_cb, &data); + +cleanup: + git_str_dispose(&pattern); + git_str_dispose(&replace); + + return error; +} + +int git_config_init_backend(git_config_backend *backend, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT); + return 0; +} diff --git a/src/libgit2/config.h b/src/libgit2/config.h new file mode 100644 index 000000000..01b84b157 --- /dev/null +++ b/src/libgit2/config.h @@ -0,0 +1,110 @@ +/* + * 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_config_h__ +#define INCLUDE_config_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/config.h" +#include "vector.h" +#include "repository.h" + +#define GIT_CONFIG_FILENAME_PROGRAMDATA "config" +#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" +#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig" +#define GIT_CONFIG_FILENAME_XDG "config" + +#define GIT_CONFIG_FILENAME_INREPO "config" +#define GIT_CONFIG_FILE_MODE 0666 + +struct git_config { + git_refcount rc; + git_vector backends; +}; + +extern int git_config__global_location(git_str *buf); + +extern int git_config__find_global(git_str *path); +extern int git_config__find_xdg(git_str *path); +extern int git_config__find_system(git_str *path); +extern int git_config__find_programdata(git_str *path); + +extern int git_config_rename_section( + git_repository *repo, + const char *old_section_name, /* eg "branch.dummy" */ + const char *new_section_name); /* NULL to drop the old section */ + +extern int git_config__normalize_name(const char *in, char **out); + +/* internal only: does not normalize key and sets out to NULL if not found */ +extern int git_config__lookup_entry( + git_config_entry **out, + const git_config *cfg, + const char *key, + bool no_errors); + +/* internal only: update and/or delete entry string with constraints */ +extern int git_config__update_entry( + git_config *cfg, + const char *key, + const char *value, + bool overwrite_existing, + bool only_if_existing); + +int git_config__get_path( + git_str *out, + const git_config *cfg, + const char *name); + +int git_config__get_string_buf( + git_str *out, const git_config *cfg, const char *name); + +/* + * Lookup functions that cannot fail. These functions look up a config + * value and return a fallback value if the value is missing or if any + * failures occur while trying to access the value. + */ + +extern char *git_config__get_string_force( + const git_config *cfg, const char *key, const char *fallback_value); + +extern int git_config__get_bool_force( + const git_config *cfg, const char *key, int fallback_value); + +extern int git_config__get_int_force( + const git_config *cfg, const char *key, int fallback_value); + +/* API for repository configmap-style lookups from config - not cached, but + * uses configmap value maps and fallbacks + */ +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_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 + * + * Unlocking will allow other writers to update the configuration + * file. Optionally, any changes performed since the lock will be + * applied to the configuration. + * + * @param cfg the configuration + * @param commit boolean which indicates whether to commit any changes + * done since locking + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); + +#endif diff --git a/src/libgit2/config_backend.h b/src/libgit2/config_backend.h new file mode 100644 index 000000000..dbb190514 --- /dev/null +++ b/src/libgit2/config_backend.h @@ -0,0 +1,96 @@ +/* + * 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_config_file_h__ +#define INCLUDE_config_file_h__ + +#include "common.h" + +#include "git2/sys/config.h" +#include "git2/config.h" + +/** + * Create a configuration file backend for ondisk files + * + * These are the normal `.gitconfig` files that Core Git + * processes. Note that you first have to add this file to a + * configuration object before you can query it for configuration + * variables. + * + * @param out the new backend + * @param path where the config file is located + */ +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 + * + * @param out the new backend + * @param cfg the configuration that is to be parsed + * @param len the length of the string pointed to by `cfg` + */ +extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len); + +GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) +{ + return cfg->open(cfg, level, repo); +} + +GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg) +{ + if (cfg) + cfg->free(cfg); +} + +GIT_INLINE(int) git_config_backend_get_string( + git_config_entry **out, git_config_backend *cfg, const char *name) +{ + return cfg->get(cfg, name, out); +} + +GIT_INLINE(int) git_config_backend_set_string( + git_config_backend *cfg, const char *name, const char *value) +{ + return cfg->set(cfg, name, value); +} + +GIT_INLINE(int) git_config_backend_delete( + git_config_backend *cfg, const char *name) +{ + return cfg->del(cfg, name); +} + +GIT_INLINE(int) git_config_backend_foreach( + git_config_backend *cfg, + int (*fn)(const git_config_entry *entry, void *data), + void *data) +{ + return git_config_backend_foreach_match(cfg, NULL, fn, data); +} + +GIT_INLINE(int) git_config_backend_lock(git_config_backend *cfg) +{ + return cfg->lock(cfg); +} + +GIT_INLINE(int) git_config_backend_unlock(git_config_backend *cfg, int success) +{ + return cfg->unlock(cfg, success); +} + +#endif diff --git a/src/libgit2/config_cache.c b/src/libgit2/config_cache.c new file mode 100644 index 000000000..4bb91f52b --- /dev/null +++ b/src/libgit2/config_cache.c @@ -0,0 +1,142 @@ +/* + * 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 "futils.h" +#include "repository.h" +#include "config.h" +#include "git2/config.h" +#include "vector.h" +#include "filter.h" + +struct map_data { + const char *name; + git_configmap *maps; + size_t map_count; + int default_value; +}; + +/* + * core.eol + * Sets the line ending type to use in the working directory for + * files that have the text property set. Alternatives are lf, crlf + * and native, which uses the platform's native line ending. The default + * value is native. See gitattributes(5) for more information on + * end-of-line conversion. + */ +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} +}; + +/* + * core.autocrlf + * Setting this variable to "true" is almost the same as setting + * the text attribute to "auto" on all files except that text files are + * not guaranteed to be normalized: files that contain CRLF in the + * repository will not be touched. Use this setting if you want to have + * CRLF line endings in your working directory even though the repository + * does not have normalized line endings. This variable can be set to input, + * in which case no output conversion is performed. + */ +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_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_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_configmap _configmap_int[] = { + {GIT_CONFIGMAP_INT32, NULL, 0}, +}; + +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", _configmap_int, 1, GIT_ABBREV_DEFAULT }, + {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_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 }, + {"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT }, +}; + +int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) +{ + int error = 0; + struct map_data *data = &_configmaps[(int)item]; + git_config_entry *entry; + + if ((error = git_config__lookup_entry(&entry, config, data->name, false)) < 0) + return error; + + if (!entry) + *out = data->default_value; + else if (data->maps) + error = git_config_lookup_map_value( + out, data->maps, data->map_count, entry->value); + else + error = git_config_parse_bool(out, entry->value); + + git_config_entry_free(entry); + return error; +} + +int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item) +{ + intptr_t value = (intptr_t)git_atomic_load(repo->configmap_cache[(int)item]); + + *out = (int)value; + + if (value == GIT_CONFIGMAP_NOT_CACHED) { + git_config *config; + intptr_t oldval = value; + int error; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0 || + (error = git_config__configmap_lookup(out, config, item)) < 0) + return error; + + value = *out; + git_atomic_compare_and_swap(&repo->configmap_cache[(int)item], (void *)oldval, (void *)value); + } + + return 0; +} + +void git_repository__configmap_lookup_cache_clear(git_repository *repo) +{ + int i; + + for (i = 0; i < GIT_CONFIGMAP_CACHE_MAX; ++i) + repo->configmap_cache[i] = GIT_CONFIGMAP_NOT_CACHED; +} + diff --git a/src/libgit2/config_entries.c b/src/libgit2/config_entries.c new file mode 100644 index 000000000..66aae096d --- /dev/null +++ b/src/libgit2/config_entries.c @@ -0,0 +1,237 @@ +/* + * 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_entries.h" + +typedef struct config_entry_list { + struct config_entry_list *next; + struct config_entry_list *last; + git_config_entry *entry; +} config_entry_list; + +typedef struct { + git_config_entry *entry; + bool multivar; +} config_entry_map_head; + +typedef struct config_entries_iterator { + git_config_iterator parent; + git_config_entries *entries; + config_entry_list *head; +} config_entries_iterator; + +struct git_config_entries { + git_refcount rc; + git_strmap *map; + config_entry_list *list; +}; + +int git_config_entries_new(git_config_entries **out) +{ + git_config_entries *entries; + int error; + + entries = git__calloc(1, sizeof(git_config_entries)); + GIT_ERROR_CHECK_ALLOC(entries); + GIT_REFCOUNT_INC(entries); + + if ((error = git_strmap_new(&entries->map)) < 0) + git__free(entries); + else + *out = entries; + + 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; + config_entry_list *head; + int error; + + if ((error = git_config_entries_new(&result)) < 0) + goto out; + + for (head = entries->list; head; head = head->next) + if ((git_config_entries_dup_entry(result, head->entry)) < 0) + goto out; + + *out = result; + result = NULL; + +out: + git_config_entries_free(result); + return error; +} + +void git_config_entries_incref(git_config_entries *entries) +{ + GIT_REFCOUNT_INC(entries); +} + +static void config_entries_free(git_config_entries *entries) +{ + config_entry_list *list = NULL, *next; + config_entry_map_head *head; + + git_strmap_foreach_value(entries->map, head, + git__free((char *) head->entry->name); git__free(head) + ); + git_strmap_free(entries->map); + + list = entries->list; + while (list != NULL) { + next = list->next; + git__free((char *) list->entry->value); + git__free(list->entry); + git__free(list); + list = next; + } + + git__free(entries); +} + +void git_config_entries_free(git_config_entries *entries) +{ + if (entries) + GIT_REFCOUNT_DEC(entries, config_entries_free); +} + +int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) +{ + config_entry_list *list_head; + config_entry_map_head *map_head; + + if ((map_head = git_strmap_get(entries->map, entry->name)) != NULL) { + map_head->multivar = true; + /* + * 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. + */ + git__free((char *) entry->name); + entry->name = map_head->entry->name; + } else { + map_head = git__calloc(1, sizeof(*map_head)); + if ((git_strmap_set(entries->map, entry->name, map_head)) < 0) + return -1; + } + map_head->entry = entry; + + list_head = git__calloc(1, sizeof(config_entry_list)); + GIT_ERROR_CHECK_ALLOC(list_head); + list_head->entry = entry; + + if (entries->list) + entries->list->last->next = list_head; + else + entries->list = list_head; + entries->list->last = list_head; + + return 0; +} + +int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) +{ + config_entry_map_head *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_map_head *entry; + + if ((entry = git_strmap_get(entries->map, key)) == NULL) + return GIT_ENOTFOUND; + + if (entry->multivar) { + git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); + return -1; + } + + if (entry->entry->include_depth) { + git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included"); + return -1; + } + + *out = entry->entry; + + return 0; +} + +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); +} + +static int config_iterator_next( + git_config_entry **entry, + git_config_iterator *iter) +{ + config_entries_iterator *it = (config_entries_iterator *) iter; + + if (!it->head) + return GIT_ITEROVER; + + *entry = it->head->entry; + it->head = it->head->next; + + return 0; +} + +int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries) +{ + config_entries_iterator *it; + + it = git__calloc(1, sizeof(config_entries_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + it->parent.next = config_iterator_next; + it->parent.free = config_iterator_free; + it->head = entries->list; + it->entries = entries; + + git_config_entries_incref(entries); + *out = &it->parent; + + return 0; +} diff --git a/src/libgit2/config_entries.h b/src/libgit2/config_entries.h new file mode 100644 index 000000000..832379e74 --- /dev/null +++ b/src/libgit2/config_entries.h @@ -0,0 +1,24 @@ +/* + * 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/sys/config.h" +#include "config.h" + +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 */ +int git_config_entries_append(git_config_entries *entries, git_config_entry *entry); +int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key); +int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key); +int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries); diff --git a/src/libgit2/config_file.c b/src/libgit2/config_file.c new file mode 100644 index 000000000..66fcb8ae2 --- /dev/null +++ b/src/libgit2/config_file.c @@ -0,0 +1,1194 @@ +/* + * 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 "git2/config.h" +#include "git2/sys/config.h" + +#include "array.h" +#include "str.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" +#include "hash.h" + +/* Max depth for [include] directives */ +#define MAX_INCLUDE_DEPTH 10 + +typedef struct config_file { + git_futils_filestamp stamp; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + char *path; + git_array_t(struct config_file) includes; +} config_file; + +typedef struct { + git_config_backend parent; + git_mutex values_mutex; + git_config_entries *entries; + const git_repository *repo; + git_config_level_t level; + + git_array_t(git_config_parser) readers; + + bool locked; + git_filebuf locked_buf; + git_str locked_content; + + config_file file; +} config_file_backend; + +typedef struct { + const git_repository *repo; + config_file *file; + git_config_entries *entries; + git_config_level_t level; + unsigned int depth; +} config_file_parse_data; + +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); + +/** + * 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 int config_file_entries_take(git_config_entries **out, config_file_backend *b) +{ + int error; + + if ((error = git_mutex_lock(&b->values_mutex)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + return error; + } + + git_config_entries_incref(b->entries); + *out = b->entries; + + git_mutex_unlock(&b->values_mutex); + + return 0; +} + +static void config_file_clear(config_file *file) +{ + config_file *include; + uint32_t i; + + if (file == NULL) + return; + + git_array_foreach(file->includes, i, include) { + config_file_clear(include); + } + git_array_clear(file->includes); + + git__free(file->path); +} + +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; + + b->level = level; + b->repo = repo; + + if ((res = git_config_entries_new(&b->entries)) < 0) + return res; + + if (!git_fs_path_exists(b->file.path)) + return 0; + + /* + * 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_file_is_modified(int *modified, config_file *file) +{ + config_file *include; + git_str buf = GIT_STR_INIT; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + uint32_t i; + int error = 0; + + *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; + + if ((error = git_hash_buf(checksum, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) + goto out; + + if (memcmp(checksum, file->checksum, GIT_HASH_SHA1_SIZE) != 0) { + *modified = 1; + goto out; + } + +check_includes: + git_array_foreach(file->includes, i, include) { + if ((error = config_file_is_modified(modified, include)) < 0 || *modified) + goto out; + } + +out: + git_str_dispose(&buf); + + return error; +} + +static void config_file_clear_includes(config_file_backend *cfg) +{ + config_file *include; + uint32_t i; + + git_array_foreach(cfg->file.includes, i, include) + config_file_clear(include); + git_array_clear(cfg->file.includes); +} + +static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *old = NULL; + int error; + + if (b->parent.readonly) { + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; + } + + 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; + + config_file_clear_includes(b); + + 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; + + config_file_clear_includes(b); + + 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; + + entries = NULL; +out: + git_config_entries_free(entries); + + return (error == GIT_ENOTFOUND) ? 0 : error; +} + +static void config_file_free(git_config_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->entries); + git_mutex_free(&backend->values_mutex); + git__free(backend); +} + +static int config_file_iterator( + git_config_iterator **iter, + struct git_config_backend *backend) +{ + config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); + git_config_entries *dupped = NULL, *entries = NULL; + int error; + + 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_file_snapshot(git_config_backend **out, git_config_backend *backend) +{ + 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; + int error; + + if ((error = git_config__normalize_name(name, &key)) < 0) + return error; + + 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) { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + } else if ((!existing->value && !value) || + (existing->value && value && !strcmp(existing->value, value))) { + /* don't update if old and new values already match */ + error = 0; + goto out; + } + + /* No early returns due to sanity checks, let's write it out and refresh */ + if (value) { + esc_value = escape_value(value); + GIT_ERROR_CHECK_ALLOC(esc_value); + } + + if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0) + goto out; + +out: + git_config_entries_free(entries); + git__free(esc_value); + git__free(key); + return error; +} + +/* release the map containing the entry as an equivalent to freeing it */ +static void config_file_entry_free(git_config_entry *entry) +{ + git_config_entries *entries = (git_config_entries *) entry->payload; + git_config_entries_free(entries); +} + +/* + * Internal function that actually gets the value in string form + */ +static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) +{ + 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_file_refresh(cfg)) < 0)) + return error; + + 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 = config_file_entry_free; + entry->payload = entries; + *out = entry; + + return 0; +} + +static int config_file_set_multivar( + git_config_backend *cfg, const char *name, const char *regexp, const char *value) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_regexp preg; + int result; + char *key; + + GIT_ASSERT_ARG(regexp); + + if ((result = git_config__normalize_name(name, &key)) < 0) + return result; + + if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) + goto out; + + /* 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); + git_regexp_dispose(&preg); + + return result; +} + +static int config_file_delete(git_config_backend *cfg, const char *name) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry; + char *key = NULL; + int error; + + if ((error = git_config__normalize_name(name, &key)) < 0) + goto out; + + if ((error = config_file_entries_take(&entries, b)) < 0) + goto out; + + /* Check whether we'd be modifying an included or multivar key */ + if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) { + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); + goto out; + } + + if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) + goto out; + +out: + git_config_entries_free(entries); + git__free(key); + return error; +} + +static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry = NULL; + git_regexp preg = GIT_REGEX_INIT; + char *key = NULL; + int result; + + if ((result = git_config__normalize_name(name, &key)) < 0) + goto out; + + 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) + git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); + goto out; + } + + if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) + goto out; + + if ((result = config_file_write(b, name, key, &preg, NULL)) < 0) + goto out; + +out: + git_config_entries_free(entries); + git__free(key); + git_regexp_dispose(&preg); + return result; +} + +static int config_file_lock(git_config_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) + return error; + + error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path); + if (error < 0 && error != GIT_ENOTFOUND) { + git_filebuf_cleanup(&cfg->locked_buf); + return error; + } + + cfg->locked = true; + return 0; + +} + +static int config_file_unlock(git_config_backend *_cfg, int success) +{ + config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); + int error = 0; + + if (success) { + git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size); + error = git_filebuf_commit(&cfg->locked_buf); + } + + git_filebuf_cleanup(&cfg->locked_buf); + git_str_dispose(&cfg->locked_content); + cfg->locked = false; + + return error; +} + +int git_config_backend_from_file(git_config_backend **out, const char *path) +{ + config_file_backend *backend; + + backend = git__calloc(1, sizeof(config_file_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + 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->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; + + return 0; +} + +static int included_path(git_str *out, const char *dir, const char *path) +{ + /* From the user's home */ + if (path[0] == '~' && path[1] == '/') + return git_sysdir_expand_global_file(out, &path[1]); + + return git_fs_path_join_unrooted(out, path, dir, NULL); +} + +/* Escape the values to write them to the file */ +static char *escape_value(const char *ptr) +{ + git_str buf; + size_t len; + const char *esc; + + GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL); + + len = strlen(ptr); + if (!len) + return git__calloc(1, sizeof(char)); + + if (git_str_init(&buf, len) < 0) + return NULL; + + while (*ptr != '\0') { + if ((esc = strchr(git_config_escaped, *ptr)) != NULL) { + git_str_putc(&buf, '\\'); + git_str_putc(&buf, git_config_escapes[esc - git_config_escaped]); + } else { + git_str_putc(&buf, *ptr); + } + ptr++; + } + + if (git_str_oom(&buf)) + return NULL; + + return git_str_detach(&buf); +} + +static int parse_include(config_file_parse_data *parse_data, const char *file) +{ + config_file *include; + git_str path = GIT_STR_INIT; + char *dir; + int result; + + if (!file) + return 0; + + if ((result = git_fs_path_dirname_r(&path, parse_data->file->path)) < 0) + return result; + + dir = git_str_detach(&path); + result = included_path(&path, dir, file); + git__free(dir); + + if (result < 0) + return result; + + 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_str_detach(&path); + + 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(); + result = 0; + } + + return result; +} + +static int do_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *condition, + bool case_insensitive) +{ + git_str pattern = GIT_STR_INIT, gitdir = GIT_STR_INIT; + int error; + + if (condition[0] == '.' && git_fs_path_is_dirsep(condition[1])) { + git_fs_path_dirname_r(&pattern, cfg_file); + git_str_joinpath(&pattern, pattern.ptr, condition + 2); + } else if (condition[0] == '~' && git_fs_path_is_dirsep(condition[1])) + git_sysdir_expand_global_file(&pattern, condition + 1); + else if (!git_fs_path_is_absolute(condition)) + git_str_joinpath(&pattern, "**", condition); + else + git_str_sets(&pattern, condition); + + if (git_fs_path_is_dirsep(condition[strlen(condition) - 1])) + git_str_puts(&pattern, "**"); + + if (git_str_oom(&pattern)) { + error = -1; + goto out; + } + + if ((error = git_repository__item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) + goto out; + + if (git_fs_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) + git_str_truncate(&gitdir, gitdir.size - 1); + + *matches = wildmatch(pattern.ptr, gitdir.ptr, + WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; +out: + git_str_dispose(&pattern); + git_str_dispose(&gitdir); + return error; +} + +static int conditional_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + return do_match_gitdir(matches, repo, cfg_file, value, false); +} + +static int conditional_match_gitdir_i( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + 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_str reference = GIT_STR_INIT, buf = GIT_STR_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_str_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 || + (error = git_futils_readbuffer(&reference, buf.ptr)) < 0) + goto out; + git_str_rtrim(&reference); + + if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) + goto out; + git_str_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); + + if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR))) + goto out; + git_str_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_str_sets(&buf, condition)) < 0) + goto out; + if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]) && + (error = git_str_puts(&buf, "**")) < 0) + goto out; + + *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH; +out: + git_str_dispose(&reference); + git_str_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 }, + { "onbranch:", conditional_match_onbranch } +}; + +static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file) +{ + char *condition; + size_t section_len, i; + int error = 0, matches; + + if (!parse_data->repo || !file) + return 0; + + section_len = strlen(section); + + /* + * We checked that the string starts with `includeIf.` and ends + * in `.path` to get here. Make sure it consists of more. + */ + if (section_len < CONST_STRLEN("includeIf.") + CONST_STRLEN(".path")) + return 0; + + condition = git__substrdup(section + CONST_STRLEN("includeIf."), + section_len - CONST_STRLEN("includeIf.") - CONST_STRLEN(".path")); + + GIT_ERROR_CHECK_ALLOC(condition); + + for (i = 0; i < ARRAY_SIZE(conditions); i++) { + if (git__prefixcmp(condition, conditions[i].prefix)) + continue; + + if ((error = conditions[i].matches(&matches, + parse_data->repo, + parse_data->file->path, + condition + strlen(conditions[i].prefix))) < 0) + break; + + if (matches) + error = parse_include(parse_data, file); + + break; + } + + git__free(condition); + return error; +} + +static int read_on_variable( + git_config_parser *reader, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *data) +{ + config_file_parse_data *parse_data = (config_file_parse_data *)data; + git_str buf = GIT_STR_INIT; + git_config_entry *entry; + const char *c; + int result = 0; + + GIT_UNUSED(reader); + GIT_UNUSED(line); + GIT_UNUSED(line_len); + + if (current_section) { + /* TODO: Once warnings lang, we should likely warn + * here. Git appears to warn in most cases if it sees + * un-namespaced config options. + */ + git_str_puts(&buf, current_section); + git_str_putc(&buf, '.'); + } + + for (c = var_name; *c; c++) + git_str_putc(&buf, git__tolower(*c)); + + if (git_str_oom(&buf)) + return -1; + + entry = git__calloc(1, sizeof(git_config_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->name = git_str_detach(&buf); + entry->value = var_value ? git__strdup(var_value) : NULL; + entry->level = parse_data->level; + entry->include_depth = parse_data->depth; + + if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) + return result; + + result = 0; + + /* Add or append the new config option */ + if (!git__strcmp(entry->name, "include.path")) + result = parse_include(parse_data, entry->value); + else if (!git__prefixcmp(entry->name, "includeif.") && + !git__suffixcmp(entry->name, ".path")) + result = parse_conditional_include(parse_data, entry->name, entry->value); + + return result; +} + +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) +{ + config_file_parse_data parse_data; + git_config_parser reader; + int error; + + if (depth >= MAX_INCLUDE_DEPTH) { + git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached"); + return -1; + } + + /* Initialize the reading position */ + 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') { + error = 0; + goto out; + } + + parse_data.repo = repo; + 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_str contents = GIT_STR_INIT; + struct stat st; + int error; + + if (p_stat(file->path, &st) < 0) { + error = git_fs_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, GIT_HASH_ALGORITHM_SHA1)) < 0) + goto out; + + if ((error = config_file_read_buffer(entries, repo, file, level, depth, + contents.ptr, contents.size)) < 0) + goto out; + +out: + git_str_dispose(&contents); + return error; +} + +static int write_section(git_str *fbuf, const char *key) +{ + int result; + const char *dot; + git_str buf = GIT_STR_INIT; + + /* All of this just for [section "subsection"] */ + dot = strchr(key, '.'); + git_str_putc(&buf, '['); + if (dot == NULL) { + git_str_puts(&buf, key); + } else { + char *escaped; + git_str_put(&buf, key, dot - key); + escaped = escape_value(dot + 1); + GIT_ERROR_CHECK_ALLOC(escaped); + git_str_printf(&buf, " \"%s\"", escaped); + git__free(escaped); + } + git_str_puts(&buf, "]\n"); + + if (git_str_oom(&buf)) + return -1; + + result = git_str_put(fbuf, git_str_cstr(&buf), buf.size); + git_str_dispose(&buf); + + return result; +} + +static const char *quotes_for_value(const char *value) +{ + const char *ptr; + + if (value[0] == ' ' || value[0] == '\0') + return "\""; + + for (ptr = value; *ptr; ++ptr) { + if (*ptr == ';' || *ptr == '#') + return "\""; + } + + if (ptr[-1] == ' ') + return "\""; + + return ""; +} + +struct write_data { + git_str *buf; + git_str buffered_comment; + unsigned int in_section : 1, + preg_replaced : 1; + const char *orig_section; + const char *section; + const char *orig_name; + const char *name; + const git_regexp *preg; + const char *value; +}; + +static int write_line_to(git_str *buf, const char *line, size_t line_len) +{ + int result = git_str_put(buf, line, line_len); + + if (!result && line_len && line[line_len-1] != '\n') + result = git_str_printf(buf, "\n"); + + return result; +} + +static int write_line(struct write_data *write_data, const char *line, size_t line_len) +{ + return write_line_to(write_data->buf, line, line_len); +} + +static int write_value(struct write_data *write_data) +{ + const char *q; + int result; + + q = quotes_for_value(write_data->value); + result = git_str_printf(write_data->buf, + "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q); + + /* If we are updating a single name/value, we're done. Setting `value` + * to `NULL` will prevent us from trying to write it again later (in + * `write_on_section`) if we see the same section repeated. + */ + if (!write_data->preg) + write_data->value = NULL; + + return result; +} + +static int write_on_section( + git_config_parser *reader, + const char *current_section, + const char *line, + size_t line_len, + void *data) +{ + struct write_data *write_data = (struct write_data *)data; + int result = 0; + + GIT_UNUSED(reader); + + /* If we were previously in the correct section (but aren't anymore) + * and haven't written our value (for a simple name/value set, not + * a multivar), then append it to the end of the section before writing + * the new one. + */ + if (write_data->in_section && !write_data->preg && write_data->value) + result = write_value(write_data); + + write_data->in_section = strcmp(current_section, write_data->section) == 0; + + /* + * If there were comments just before this section, dump them as well. + */ + if (!result) { + result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); + git_str_clear(&write_data->buffered_comment); + } + + if (!result) + result = write_line(write_data, line, line_len); + + return result; +} + +static int write_on_variable( + git_config_parser *reader, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *data) +{ + struct write_data *write_data = (struct write_data *)data; + bool has_matched = false; + int error; + + GIT_UNUSED(reader); + GIT_UNUSED(current_section); + + /* + * If there were comments just before this variable, let's dump them as well. + */ + if ((error = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return error; + + git_str_clear(&write_data->buffered_comment); + + /* See if we are to update this name/value pair; first examine name */ + if (write_data->in_section && + strcasecmp(write_data->name, var_name) == 0) + has_matched = true; + + /* If we have a regex to match the value, see if it matches */ + if (has_matched && write_data->preg != NULL) + 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. + */ + if (!has_matched) + return write_line(write_data, line, line_len); + + write_data->preg_replaced = 1; + + /* If value is NULL, we are deleting this value; write nothing. */ + if (!write_data->value) + return 0; + + return write_value(write_data); +} + +static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data) +{ + struct write_data *write_data; + + GIT_UNUSED(reader); + + write_data = (struct write_data *)data; + return write_line_to(&write_data->buffered_comment, line, line_len); +} + +static int write_on_eof( + git_config_parser *reader, const char *current_section, void *data) +{ + struct write_data *write_data = (struct write_data *)data; + int result = 0; + + GIT_UNUSED(reader); + + /* + * If we've buffered comments when reaching EOF, make sure to dump them. + */ + if ((result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return result; + + /* If we are at the EOF and have not written our value (again, for a + * simple name/value set, not a multivar) then we have never seen the + * section in question and should create a new section and write the + * value. + */ + if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { + /* write the section header unless we're already in it */ + if (!current_section || strcmp(current_section, write_data->section)) + result = write_section(write_data->buf, write_data->orig_section); + + if (!result) + result = write_value(write_data); + } + + return result; +} + +/* + * This is pretty much the parsing, except we write out anything we don't have + */ +static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value) + +{ + char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot; + git_str buf = GIT_STR_INIT, contents = GIT_STR_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) { + error = git_str_puts(&contents, git_str_cstr(&cfg->locked_content) == NULL ? "" : git_str_cstr(&cfg->locked_content)); + } else { + 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 */ + error = git_futils_readbuffer(&contents, cfg->file.path); + } + if (error < 0 && error != GIT_ENOTFOUND) + goto done; + + if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0) + goto done; + + ldot = strrchr(key, '.'); + name = ldot + 1; + section = git__strndup(key, ldot - key); + GIT_ERROR_CHECK_ALLOC(section); + + ldot = strrchr(orig_key, '.'); + orig_name = ldot + 1; + orig_section = git__strndup(orig_key, ldot - orig_key); + GIT_ERROR_CHECK_ALLOC(orig_section); + + write_data.buf = &buf; + write_data.orig_section = orig_section; + write_data.section = section; + write_data.orig_name = orig_name; + write_data.name = name; + write_data.preg = preg; + write_data.value = value; + + 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; + /* Update our copy with the modified contents */ + git_str_dispose(&cfg->locked_content); + git_str_attach(&cfg->locked_content, git_str_detach(&buf), len); + } else { + git_filebuf_write(&file, git_str_cstr(&buf), git_str_len(&buf)); + + 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_str_dispose(&write_data.buffered_comment); + git_str_dispose(&buf); + git_str_dispose(&contents); + git_filebuf_cleanup(&file); + git_config_parser_dispose(&parser); + + return error; +} diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c new file mode 100644 index 000000000..560229cf5 --- /dev/null +++ b/src/libgit2/config_mem.c @@ -0,0 +1,220 @@ +/* + * 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_backend.h" +#include "config_parse.h" +#include "config_entries.h" + +typedef struct { + git_config_backend parent; + git_config_entries *entries; + git_str cfg; +} config_memory_backend; + +typedef struct { + git_config_entries *entries; + git_config_level_t level; +} config_memory_parse_data; + +static int config_error_readonly(void) +{ + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; +} + +static int read_variable_cb( + git_config_parser *reader, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *payload) +{ + config_memory_parse_data *parse_data = (config_memory_parse_data *) payload; + git_str buf = GIT_STR_INIT; + git_config_entry *entry; + const char *c; + int result; + + GIT_UNUSED(reader); + GIT_UNUSED(line); + GIT_UNUSED(line_len); + + if (current_section) { + /* TODO: Once warnings land, we should likely warn + * here. Git appears to warn in most cases if it sees + * un-namespaced config options. + */ + git_str_puts(&buf, current_section); + git_str_putc(&buf, '.'); + } + + for (c = var_name; *c; c++) + git_str_putc(&buf, git__tolower(*c)); + + if (git_str_oom(&buf)) + return -1; + + entry = git__calloc(1, sizeof(git_config_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->name = git_str_detach(&buf); + entry->value = var_value ? git__strdup(var_value) : NULL; + entry->level = parse_data->level; + entry->include_depth = 0; + + if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) + return result; + + return result; +} + +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; + int error; + + GIT_UNUSED(repo); + + 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; + + 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) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + return git_config_entries_get(out, memory_backend->entries, key); +} + +static int config_memory_iterator( + git_config_iterator **iter, + git_config_backend *backend) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + git_config_entries *entries; + int error; + + if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0) + goto out; + + if ((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; +} + +static int config_memory_set(git_config_backend *backend, const char *name, const char *value) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + GIT_UNUSED(value); + return config_error_readonly(); +} + +static int config_memory_set_multivar( + git_config_backend *backend, const char *name, const char *regexp, const char *value) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + GIT_UNUSED(value); + return config_error_readonly(); +} + +static int config_memory_delete(git_config_backend *backend, const char *name) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + return config_error_readonly(); +} + +static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + return config_error_readonly(); +} + +static int config_memory_lock(git_config_backend *backend) +{ + GIT_UNUSED(backend); + return config_error_readonly(); +} + +static int config_memory_unlock(git_config_backend *backend, int success) +{ + GIT_UNUSED(backend); + GIT_UNUSED(success); + return config_error_readonly(); +} + +static void config_memory_free(git_config_backend *_backend) +{ + config_memory_backend *backend = (config_memory_backend *)_backend; + + if (backend == NULL) + return; + + git_config_entries_free(backend->entries); + git_str_dispose(&backend->cfg); + git__free(backend); +} + +int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len) +{ + config_memory_backend *backend; + + backend = git__calloc(1, sizeof(config_memory_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + if (git_config_entries_new(&backend->entries) < 0) { + git__free(backend); + return -1; + } + + if (git_str_set(&backend->cfg, cfg, len) < 0) { + git_config_entries_free(backend->entries); + git__free(backend); + return -1; + } + + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + backend->parent.readonly = 1; + backend->parent.open = config_memory_open; + backend->parent.get = config_memory_get; + backend->parent.set = config_memory_set; + backend->parent.set_multivar = config_memory_set_multivar; + backend->parent.del = config_memory_delete; + backend->parent.del_multivar = config_memory_delete_multivar; + backend->parent.iterator = config_memory_iterator; + backend->parent.lock = config_memory_lock; + backend->parent.unlock = config_memory_unlock; + backend->parent.snapshot = git_config_backend_snapshot; + backend->parent.free = config_memory_free; + + *out = (git_config_backend *)backend; + + return 0; +} diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c new file mode 100644 index 000000000..06931368e --- /dev/null +++ b/src/libgit2/config_parse.c @@ -0,0 +1,580 @@ +/* + * 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_parse.h" + +#include + +const char *git_config_escapes = "ntb\"\\"; +const char *git_config_escaped = "\n\t\b\"\\"; + +static void set_parse_error(git_config_parser *reader, int col, const char *error_str) +{ + 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); +} + + +GIT_INLINE(int) config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static int strip_comments(char *line, int in_quotes) +{ + int quote_count = in_quotes, backslash_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ((ptr > line && ptr[-1] != '\\') || ptr == line)) + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && + (quote_count % 2) == 0 && + (backslash_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + + if (ptr[0] == '\\') + backslash_count++; + else + backslash_count = 0; + } + + /* skip any space at the end */ + while (ptr > line && git__isspace(ptr[-1])) { + ptr--; + } + ptr[0] = '\0'; + + return quote_count; +} + + +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; + const char *first_quote, *last_quote; + const char *line_start = line; + git_str buf = GIT_STR_INIT; + size_t quoted_len, alloc_len, base_name_len = strlen(base_name); + + /* Skip any additional whitespace before our section name */ + while (git__isspace(line[pos])) + pos++; + + /* 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"); + goto end_error; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + if (git_str_grow(&buf, alloc_len) < 0 || + git_str_printf(&buf, "%s.", base_name) < 0) + goto end_error; + + rpos = 0; + + line = first_quote; + c = line[++rpos]; + + /* + * At the end of each iteration, whatever is stored in c will be + * added to the string. In case of error, jump to out + */ + do { + + switch (c) { + case 0: + set_parse_error(reader, 0, "unexpected end-of-line in section header"); + goto end_error; + + case '"': + goto end_parse; + + case '\\': + c = line[++rpos]; + + if (c == 0) { + set_parse_error(reader, rpos, "unexpected end-of-line in section header"); + goto end_error; + } + + default: + break; + } + + git_str_putc(&buf, (char)c); + c = line[++rpos]; + } while (line + rpos < last_quote); + +end_parse: + if (git_str_oom(&buf)) + goto end_error; + + if (line[rpos] != '"' || line[rpos + 1] != ']') { + set_parse_error(reader, rpos, "unexpected text after closing quotes"); + git_str_dispose(&buf); + return -1; + } + + *section_name = git_str_detach(&buf); + return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */ + +end_error: + git_str_dispose(&buf); + + return -1; +} + +static int parse_section_header(git_config_parser *reader, char **section_out) +{ + char *name, *name_end; + int name_length, c, pos; + int result; + char *line; + size_t line_len; + + git_parse_advance_ws(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + if (line == NULL) + return -1; + + /* find the end of the variable's name */ + name_end = strrchr(line, ']'); + if (name_end == NULL) { + git__free(line); + set_parse_error(reader, 0, "missing ']' in section header"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); + name = git__malloc(line_len); + GIT_ERROR_CHECK_ALLOC(name); + + name_length = 0; + pos = 0; + + /* Make sure we were given a section header */ + c = line[pos++]; + GIT_ASSERT(c == '['); + + c = line[pos++]; + + do { + if (git__isspace(c)){ + name[name_length] = '\0'; + 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"); + goto fail_parse; + } + + name[name_length++] = (char)git__tolower(c); + + } while ((c = line[pos++]) != ']'); + + if (line[pos - 1] != ']') { + set_parse_error(reader, pos, "unexpected end of file"); + goto fail_parse; + } + + git__free(line); + + name[name_length] = 0; + *section_out = name; + + return pos; + +fail_parse: + git__free(line); + git__free(name); + return -1; +} + +static int skip_bom(git_parse_ctx *parser) +{ + git_str buf = GIT_STR_INIT_CONST(parser->content, parser->content_len); + git_str_bom_t bom; + int bom_offset = git_str_detect_bom(&bom, &buf); + + if (bom == GIT_STR_BOM_UTF8) + git_parse_advance_chars(parser, bom_offset); + + /* TODO: reference implementation is pretty stupid with BoM */ + + return 0; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +/* '\"' -> '"' etc */ +static int unescape_line( + char **out, bool *is_multi, const char *ptr, int quote_count) +{ + char *str, *fixed, *esc; + size_t ptr_len = strlen(ptr), alloc_len; + + *is_multi = false; + + if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || + (str = git__malloc(alloc_len)) == NULL) { + return -1; + } + + fixed = str; + + while (*ptr != '\0') { + if (*ptr == '"') { + quote_count++; + } else if (*ptr != '\\') { + *fixed++ = *ptr; + } else { + /* backslash, check the next char */ + ptr++; + /* if we're at the end, it's a multiline, so keep the backslash */ + if (*ptr == '\0') { + *is_multi = true; + goto done; + } + if ((esc = strchr(git_config_escapes, *ptr)) != NULL) { + *fixed++ = git_config_escaped[esc - git_config_escapes]; + } else { + git__free(str); + git_error_set(GIT_ERROR_CONFIG, "invalid escape at %s", ptr); + return -1; + } + } + ptr++; + } + +done: + *fixed = '\0'; + *out = str; + + return 0; +} + +static int parse_multiline_variable(git_config_parser *reader, git_str *value, int in_quotes, size_t *line_len) +{ + int quote_count; + bool multiline = true; + + while (multiline) { + char *line = NULL, *proc_line = NULL; + int error; + + /* Check that the next line exists */ + git_parse_advance_line(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + GIT_ERROR_CHECK_ALLOC(line); + if (GIT_ADD_SIZET_OVERFLOW(line_len, *line_len, reader->ctx.line_len)) { + error = -1; + goto out; + } + + /* + * We've reached the end of the file, there is no continuation. + * (this is not an error). + */ + if (line[0] == '\0') { + error = 0; + goto out; + } + + /* If it was just a comment, pretend it didn't exist */ + quote_count = strip_comments(line, in_quotes); + if (line[0] == '\0') + goto next; + + if ((error = unescape_line(&proc_line, &multiline, + line, in_quotes)) < 0) + goto out; + + /* Add this line to the multiline var */ + if ((error = git_str_puts(value, proc_line)) < 0) + goto out; + +next: + git__free(line); + git__free(proc_line); + in_quotes = quote_count; + continue; + +out: + git__free(line); + git__free(proc_line); + return error; + } + + return 0; +} + +GIT_INLINE(bool) is_namechar(char c) +{ + return isalnum(c) || c == '-'; +} + +static int parse_name( + char **name, const char **value, git_config_parser *reader, const char *line) +{ + const char *name_end = line, *value_start; + + *name = NULL; + *value = NULL; + + while (*name_end && is_namechar(*name_end)) + name_end++; + + if (line == name_end) { + set_parse_error(reader, 0, "invalid configuration key"); + return -1; + } + + value_start = name_end; + + while (*value_start && git__isspace(*value_start)) + value_start++; + + if (*value_start == '=') { + *value = value_start + 1; + } else if (*value_start) { + set_parse_error(reader, 0, "invalid configuration key"); + return -1; + } + + if ((*name = git__strndup(line, name_end - line)) == NULL) + return -1; + + return 0; +} + +static int parse_variable(git_config_parser *reader, char **var_name, char **var_value, size_t *line_len) +{ + const char *value_start = NULL; + char *line = NULL, *name = NULL, *value = NULL; + int quote_count, error; + bool multiline; + + *var_name = NULL; + *var_value = NULL; + + git_parse_advance_ws(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + GIT_ERROR_CHECK_ALLOC(line); + + quote_count = strip_comments(line, 0); + + if ((error = parse_name(&name, &value_start, reader, line)) < 0) + goto out; + + /* + * Now, let's try to parse the value + */ + if (value_start != NULL) { + while (git__isspace(value_start[0])) + value_start++; + + if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0) + goto out; + + if (multiline) { + git_str multi_value = GIT_STR_INIT; + git_str_attach(&multi_value, value, 0); + value = NULL; + + if (parse_multiline_variable(reader, &multi_value, quote_count % 2, line_len) < 0 || + git_str_oom(&multi_value)) { + error = -1; + git_str_dispose(&multi_value); + goto out; + } + + value = git_str_detach(&multi_value); + } + } + + *var_name = name; + *var_value = value; + name = NULL; + value = NULL; + +out: + git__free(name); + git__free(value); + git__free(line); + 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 *payload) +{ + git_parse_ctx *ctx; + char *current_section = NULL, *var_name = NULL, *var_value = NULL; + int result = 0; + + ctx = &parser->ctx; + + skip_bom(ctx); + + for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) { + const char *line_start; + size_t line_len; + char c; + + restart: + line_start = ctx->line; + line_len = ctx->line_len; + + /* + * Get either first non-whitespace character or, if that does + * not exist, the first whitespace character. This is required + * to preserve whitespaces when writing back the file. + */ + if (git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 && + git_parse_peek(&c, ctx, 0) < 0) + continue; + + switch (c) { + case '[': /* section header, new section begins */ + git__free(current_section); + current_section = NULL; + + result = parse_section_header(parser, ¤t_section); + if (result < 0) + break; + + git_parse_advance_chars(ctx, result); + + if (on_section) + 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, + * run the next loop with the rest of the current line + * instead of moving forward. + */ + + if (!git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE)) + goto restart; + + break; + + case '\n': /* comment or whitespace-only */ + case '\r': + case ' ': + case '\t': + case ';': + case '#': + if (on_comment) { + result = on_comment(parser, line_start, line_len, payload); + } + break; + + default: /* assume variable declaration */ + if ((result = parse_variable(parser, &var_name, &var_value, &line_len)) == 0 && on_variable) { + result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload); + git__free(var_name); + git__free(var_value); + } + + break; + } + + if (result < 0) + goto out; + } + + if (on_eof) + result = on_eof(parser, current_section, payload); + +out: + git__free(current_section); + return result; +} diff --git a/src/libgit2/config_parse.h b/src/libgit2/config_parse.h new file mode 100644 index 000000000..b791d3245 --- /dev/null +++ b/src/libgit2/config_parse.h @@ -0,0 +1,65 @@ +/* + * 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_config_parse_h__ +#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 { + 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 *payload); + +typedef int (*git_config_parser_variable_cb)( + git_config_parser *parser, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *payload); + +typedef int (*git_config_parser_comment_cb)( + git_config_parser *parser, + const char *line, + size_t line_len, + void *payload); + +typedef int (*git_config_parser_eof_cb)( + git_config_parser *parser, + const char *current_section, + 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, + 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 *payload); + +#endif diff --git a/src/libgit2/config_snapshot.c b/src/libgit2/config_snapshot.c new file mode 100644 index 000000000..e295d2f7f --- /dev/null +++ b/src/libgit2/config_snapshot.c @@ -0,0 +1,207 @@ +/* + * 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_backend.h" + +#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/libgit2/crlf.c b/src/libgit2/crlf.c new file mode 100644 index 000000000..1e1f1e845 --- /dev/null +++ b/src/libgit2/crlf.c @@ -0,0 +1,426 @@ +/* + * 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/attr.h" +#include "git2/blob.h" +#include "git2/index.h" +#include "git2/sys/filter.h" + +#include "buf.h" +#include "futils.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" + +typedef enum { + GIT_CRLF_UNDEFINED, + GIT_CRLF_BINARY, + GIT_CRLF_TEXT, + GIT_CRLF_TEXT_INPUT, + GIT_CRLF_TEXT_CRLF, + GIT_CRLF_AUTO, + GIT_CRLF_AUTO_INPUT, + GIT_CRLF_AUTO_CRLF +} git_crlf_t; + +struct crlf_attrs { + int attr_action; /* the .gitattributes setting */ + int crlf_action; /* the core.autocrlf setting */ + + int auto_crlf; + int safe_crlf; + int core_eol; +}; + +struct crlf_filter { + git_filter f; +}; + +static git_crlf_t check_crlf(const char *value) +{ + if (GIT_ATTR_IS_TRUE(value)) + return GIT_CRLF_TEXT; + else if (GIT_ATTR_IS_FALSE(value)) + return GIT_CRLF_BINARY; + else if (GIT_ATTR_IS_UNSPECIFIED(value)) + ; + else if (strcmp(value, "input") == 0) + return GIT_CRLF_TEXT_INPUT; + else if (strcmp(value, "auto") == 0) + return GIT_CRLF_AUTO; + + return GIT_CRLF_UNDEFINED; +} + +static git_configmap_value check_eol(const char *value) +{ + if (GIT_ATTR_IS_UNSPECIFIED(value)) + ; + else if (strcmp(value, "lf") == 0) + return GIT_EOL_LF; + else if (strcmp(value, "crlf") == 0) + return GIT_EOL_CRLF; + + return GIT_EOL_UNSET; +} + +static int has_cr_in_index(const git_filter_source *src) +{ + git_repository *repo = git_filter_source_repo(src); + const char *path = git_filter_source_path(src); + git_index *index; + const git_index_entry *entry; + git_blob *blob; + const void *blobcontent; + git_object_size_t blobsize; + bool found_cr; + + if (!path) + return false; + + if (git_repository_index__weakptr(&index, repo) < 0) { + git_error_clear(); + return false; + } + + if (!(entry = git_index_get_bypath(index, path, 0)) && + !(entry = git_index_get_bypath(index, path, 1))) + return false; + + if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ + return true; + + if (git_blob_lookup(&blob, repo, &entry->id) < 0) + return false; + + blobcontent = git_blob_rawcontent(blob); + blobsize = git_blob_rawsize(blob); + if (!git__is_sizet(blobsize)) + blobsize = (size_t)-1; + + found_cr = (blobcontent != NULL && + blobsize > 0 && + memchr(blobcontent, '\r', (size_t)blobsize) != NULL); + + git_blob_free(blob); + return found_cr; +} + +static int text_eol_is_crlf(struct crlf_attrs *ca) +{ + if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE) + return 1; + else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT) + return 0; + + if (ca->core_eol == GIT_EOL_CRLF) + return 1; + if (ca->core_eol == GIT_EOL_UNSET && GIT_EOL_NATIVE == GIT_EOL_CRLF) + return 1; + + return 0; +} + +static git_configmap_value output_eol(struct crlf_attrs *ca) +{ + switch (ca->crlf_action) { + case GIT_CRLF_BINARY: + return GIT_EOL_UNSET; + case GIT_CRLF_TEXT_CRLF: + return GIT_EOL_CRLF; + case GIT_CRLF_TEXT_INPUT: + return GIT_EOL_LF; + case GIT_CRLF_UNDEFINED: + case GIT_CRLF_AUTO_CRLF: + return GIT_EOL_CRLF; + case GIT_CRLF_AUTO_INPUT: + return GIT_EOL_LF; + case GIT_CRLF_TEXT: + case GIT_CRLF_AUTO: + return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF; + } + + /* TODO: warn when available */ + return ca->core_eol; +} + +GIT_INLINE(int) check_safecrlf( + struct crlf_attrs *ca, + const git_filter_source *src, + git_str_text_stats *stats) +{ + const char *filename = git_filter_source_path(src); + + if (!ca->safe_crlf) + return 0; + + if (output_eol(ca) == GIT_EOL_LF) { + /* + * CRLFs would not be restored by checkout: + * check if we'd remove CRLFs + */ + if (stats->crlf) { + if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { + /* TODO: issue a warning when available */ + } else { + if (filename && *filename) + git_error_set( + GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'", + filename); + else + git_error_set( + GIT_ERROR_FILTER, "CRLF would be replaced by LF"); + + return -1; + } + } + } else if (output_eol(ca) == GIT_EOL_CRLF) { + /* + * CRLFs would be added by checkout: + * check if we have "naked" LFs + */ + if (stats->crlf != stats->lf) { + if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { + /* TODO: issue a warning when available */ + } else { + if (filename && *filename) + git_error_set( + GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'", + filename); + else + git_error_set( + GIT_ERROR_FILTER, "LF would be replaced by CRLF"); + + return -1; + } + } + } + + return 0; +} + +static int crlf_apply_to_odb( + struct crlf_attrs *ca, + git_str *to, + const git_str *from, + const git_filter_source *src) +{ + git_str_text_stats stats; + bool is_binary; + int error; + + /* Binary attribute? Empty file? Nothing to do */ + if (ca->crlf_action == GIT_CRLF_BINARY || from->size == 0) + return GIT_PASSTHROUGH; + + is_binary = git_str_gather_text_stats(&stats, from, false); + + /* Heuristics to see if we can skip the conversion. + * Straight from Core Git. + */ + if (ca->crlf_action == GIT_CRLF_AUTO || + ca->crlf_action == GIT_CRLF_AUTO_INPUT || + ca->crlf_action == GIT_CRLF_AUTO_CRLF) { + + if (is_binary) + return GIT_PASSTHROUGH; + + /* + * If the file in the index has any CR in it, do not convert. + * This is the new safer autocrlf handling. + */ + if (has_cr_in_index(src)) + return GIT_PASSTHROUGH; + } + + if ((error = check_safecrlf(ca, src, &stats)) < 0) + return error; + + /* If there are no CR characters to filter out, then just pass */ + if (!stats.crlf) + return GIT_PASSTHROUGH; + + /* Actually drop the carriage returns */ + return git_str_crlf_to_lf(to, from); +} + +static int crlf_apply_to_workdir( + struct crlf_attrs *ca, + git_str *to, + const git_str *from) +{ + git_str_text_stats stats; + bool is_binary; + + /* Empty file? Nothing to do. */ + if (git_str_len(from) == 0 || output_eol(ca) != GIT_EOL_CRLF) + return GIT_PASSTHROUGH; + + is_binary = git_str_gather_text_stats(&stats, from, false); + + /* If there are no LFs, or all LFs are part of a CRLF, nothing to do */ + if (stats.lf == 0 || stats.lf == stats.crlf) + return GIT_PASSTHROUGH; + + if (ca->crlf_action == GIT_CRLF_AUTO || + ca->crlf_action == GIT_CRLF_AUTO_INPUT || + ca->crlf_action == GIT_CRLF_AUTO_CRLF) { + + /* If we have any existing CR or CRLF line endings, do nothing */ + if (stats.cr > 0) + return GIT_PASSTHROUGH; + + /* Don't filter binary files */ + if (is_binary) + return GIT_PASSTHROUGH; + } + + return git_str_lf_to_crlf(to, from); +} + +static int convert_attrs( + struct crlf_attrs *ca, + const char **attr_values, + const git_filter_source *src) +{ + int error; + + memset(ca, 0, sizeof(struct crlf_attrs)); + + 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 */ + if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) && + ca->safe_crlf == GIT_SAFE_CRLF_FAIL) + ca->safe_crlf = GIT_SAFE_CRLF_WARN; + + if (attr_values) { + /* load the text attribute */ + ca->crlf_action = check_crlf(attr_values[2]); /* text */ + + if (ca->crlf_action == GIT_CRLF_UNDEFINED) + ca->crlf_action = check_crlf(attr_values[0]); /* crlf */ + + if (ca->crlf_action != GIT_CRLF_BINARY) { + /* load the eol attribute */ + int eol_attr = check_eol(attr_values[1]); + + if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_LF) + ca->crlf_action = GIT_CRLF_AUTO_INPUT; + else if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_CRLF) + ca->crlf_action = GIT_CRLF_AUTO_CRLF; + else if (eol_attr == GIT_EOL_LF) + ca->crlf_action = GIT_CRLF_TEXT_INPUT; + else if (eol_attr == GIT_EOL_CRLF) + ca->crlf_action = GIT_CRLF_TEXT_CRLF; + } + + ca->attr_action = ca->crlf_action; + } else { + ca->crlf_action = GIT_CRLF_UNDEFINED; + } + + if (ca->crlf_action == GIT_CRLF_TEXT) + ca->crlf_action = text_eol_is_crlf(ca) ? GIT_CRLF_TEXT_CRLF : GIT_CRLF_TEXT_INPUT; + if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_FALSE) + ca->crlf_action = GIT_CRLF_BINARY; + if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_TRUE) + ca->crlf_action = GIT_CRLF_AUTO_CRLF; + if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_INPUT) + ca->crlf_action = GIT_CRLF_AUTO_INPUT; + + return 0; +} + +static int crlf_check( + git_filter *self, + void **payload, /* points to NULL ptr on entry, may be set */ + const git_filter_source *src, + const char **attr_values) +{ + struct crlf_attrs ca; + + GIT_UNUSED(self); + + convert_attrs(&ca, attr_values, src); + + if (ca.crlf_action == GIT_CRLF_BINARY) + return GIT_PASSTHROUGH; + + *payload = git__malloc(sizeof(ca)); + GIT_ERROR_CHECK_ALLOC(*payload); + memcpy(*payload, &ca, sizeof(ca)); + + return 0; +} + +static int crlf_apply( + git_filter *self, + void **payload, /* may be read and/or set */ + git_str *to, + const git_str *from, + const git_filter_source *src) +{ + int error = 0; + + /* initialize payload in case `check` was bypassed */ + if (!*payload) { + if ((error = crlf_check(self, payload, src, NULL)) < 0) + return error; + } + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + error = crlf_apply_to_workdir(*payload, to, from); + else + error = crlf_apply_to_odb(*payload, to, from, src); + + return error; +} + +static int crlf_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, crlf_apply, NULL, payload, src, next); +} + +static void crlf_cleanup( + git_filter *self, + void *payload) +{ + GIT_UNUSED(self); + git__free(payload); +} + +git_filter *git_crlf_filter_new(void) +{ + struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter)); + if (f == NULL) + return NULL; + + f->f.version = GIT_FILTER_VERSION; + f->f.attributes = "crlf eol text"; + f->f.initialize = NULL; + f->f.shutdown = git_filter_free; + f->f.check = crlf_check; + f->f.stream = crlf_stream; + f->f.cleanup = crlf_cleanup; + + return (git_filter *)f; +} diff --git a/src/libgit2/delta.c b/src/libgit2/delta.c new file mode 100644 index 000000000..2d2c5fa85 --- /dev/null +++ b/src/libgit2/delta.c @@ -0,0 +1,628 @@ +/* + * 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 "delta.h" + +/* maximum hash entry list for the same hash bucket */ +#define HASH_LIMIT 64 + +#define RABIN_SHIFT 23 +#define RABIN_WINDOW 16 + +static const unsigned int T[256] = { + 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344, + 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259, + 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85, + 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2, + 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a, + 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db, + 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753, + 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964, + 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8, + 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5, + 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81, + 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6, + 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e, + 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77, + 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff, + 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8, + 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc, + 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1, + 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d, + 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a, + 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2, + 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02, + 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a, + 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd, + 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61, + 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c, + 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08, + 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f, + 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7, + 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe, + 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76, + 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141, + 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65, + 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78, + 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4, + 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93, + 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b, + 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa, + 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872, + 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645, + 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99, + 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84, + 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811 +}; + +static const unsigned int U[256] = { + 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a, + 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48, + 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511, + 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d, + 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8, + 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe, + 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb, + 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937, + 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e, + 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c, + 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d, + 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1, + 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4, + 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa, + 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef, + 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263, + 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302, + 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000, + 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59, + 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5, + 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90, + 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7, + 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2, + 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e, + 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467, + 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765, + 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604, + 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88, + 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd, + 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3, + 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996, + 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a, + 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b, + 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609, + 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50, + 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc, + 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99, + 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf, + 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa, + 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176, + 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f, + 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d, + 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a +}; + +struct index_entry { + const unsigned char *ptr; + unsigned int val; + struct index_entry *next; +}; + +struct git_delta_index { + unsigned long memsize; + const void *src_buf; + size_t src_size; + unsigned int hash_mask; + struct index_entry *hash[GIT_FLEX_ARRAY]; +}; + +static int lookup_index_alloc( + void **out, unsigned long *out_len, size_t entries, size_t hash_count) +{ + size_t entries_len, hash_len, index_len; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&entries_len, entries, sizeof(struct index_entry)); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&hash_len, hash_count, sizeof(struct index_entry *)); + + GIT_ERROR_CHECK_ALLOC_ADD(&index_len, sizeof(struct git_delta_index), entries_len); + GIT_ERROR_CHECK_ALLOC_ADD(&index_len, index_len, hash_len); + + if (!git__is_ulong(index_len)) { + git_error_set(GIT_ERROR_NOMEMORY, "overly large delta"); + return -1; + } + + *out = git__malloc(index_len); + GIT_ERROR_CHECK_ALLOC(*out); + + *out_len = (unsigned long)index_len; + return 0; +} + +int git_delta_index_init( + git_delta_index **out, const void *buf, size_t bufsize) +{ + unsigned int i, hsize, hmask, entries, prev_val, *hash_count; + const unsigned char *data, *buffer = buf; + struct git_delta_index *index; + struct index_entry *entry, **hash; + void *mem; + unsigned long memsize; + + *out = NULL; + + if (!buf || !bufsize) + return 0; + + /* Determine index hash size. Note that indexing skips the + first byte to allow for optimizing the rabin polynomial + initialization in create_delta(). */ + entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW; + if (bufsize >= 0xffffffffUL) { + /* + * Current delta format can't encode offsets into + * reference buffer with more than 32 bits. + */ + entries = 0xfffffffeU / RABIN_WINDOW; + } + hsize = entries / 4; + for (i = 4; i < 31 && (1u << i) < hsize; i++); + hsize = 1 << i; + hmask = hsize - 1; + + if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0) + return -1; + + index = mem; + mem = index->hash; + hash = mem; + mem = hash + hsize; + entry = mem; + + index->memsize = memsize; + index->src_buf = buf; + index->src_size = bufsize; + index->hash_mask = hmask; + memset(hash, 0, hsize * sizeof(*hash)); + + /* allocate an array to count hash entries */ + hash_count = git__calloc(hsize, sizeof(*hash_count)); + if (!hash_count) { + git__free(index); + return -1; + } + + /* then populate the index */ + prev_val = ~0; + for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW; + data >= buffer; + data -= RABIN_WINDOW) { + unsigned int val = 0; + for (i = 1; i <= RABIN_WINDOW; i++) + val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT]; + if (val == prev_val) { + /* keep the lowest of consecutive identical blocks */ + entry[-1].ptr = data + RABIN_WINDOW; + } else { + prev_val = val; + i = val & hmask; + entry->ptr = data + RABIN_WINDOW; + entry->val = val; + entry->next = hash[i]; + hash[i] = entry++; + hash_count[i]++; + } + } + + /* + * Determine a limit on the number of entries in the same hash + * bucket. This guard us against patological data sets causing + * really bad hash distribution with most entries in the same hash + * bucket that would bring us to O(m*n) computing costs (m and n + * corresponding to reference and target buffer sizes). + * + * Make sure none of the hash buckets has more entries than + * we're willing to test. Otherwise we cull the entry list + * uniformly to still preserve a good repartition across + * the reference buffer. + */ + for (i = 0; i < hsize; i++) { + if (hash_count[i] < HASH_LIMIT) + continue; + + entry = hash[i]; + do { + struct index_entry *keep = entry; + int skip = hash_count[i] / HASH_LIMIT / 2; + do { + entry = entry->next; + } while(--skip && entry); + keep->next = entry; + } while (entry); + } + git__free(hash_count); + + *out = index; + return 0; +} + +void git_delta_index_free(git_delta_index *index) +{ + git__free(index); +} + +size_t git_delta_index_size(git_delta_index *index) +{ + GIT_ASSERT_ARG(index); + + return index->memsize; +} + +/* + * The maximum size for any opcode sequence, including the initial header + * plus rabin window plus biggest copy. + */ +#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7) + +int git_delta_create_from_index( + void **out, + size_t *out_len, + const struct git_delta_index *index, + const void *trg_buf, + size_t trg_size, + size_t max_size) +{ + unsigned int i, bufpos, bufsize, moff, msize, val; + int inscnt; + const unsigned char *ref_data, *ref_top, *data, *top; + unsigned char *buf; + + *out = NULL; + *out_len = 0; + + if (!trg_buf || !trg_size) + return 0; + + if (index->src_size > UINT_MAX || + trg_size > UINT_MAX || + max_size > (UINT_MAX - MAX_OP_SIZE - 1)) { + git_error_set(GIT_ERROR_INVALID, "buffer sizes too large for delta processing"); + return -1; + } + + bufpos = 0; + bufsize = 8192; + if (max_size && bufsize >= max_size) + bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); + buf = git__malloc(bufsize); + GIT_ERROR_CHECK_ALLOC(buf); + + /* store reference buffer size */ + i = (unsigned int)index->src_size; + while (i >= 0x80) { + buf[bufpos++] = i | 0x80; + i >>= 7; + } + buf[bufpos++] = i; + + /* store target buffer size */ + i = (unsigned int)trg_size; + while (i >= 0x80) { + buf[bufpos++] = i | 0x80; + i >>= 7; + } + buf[bufpos++] = i; + + ref_data = index->src_buf; + ref_top = ref_data + index->src_size; + data = trg_buf; + top = (const unsigned char *) trg_buf + trg_size; + + bufpos++; + val = 0; + for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) { + buf[bufpos++] = *data; + val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; + } + inscnt = i; + + moff = 0; + msize = 0; + while (data < top) { + if (msize < 4096) { + struct index_entry *entry; + val ^= U[data[-RABIN_WINDOW]]; + val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; + i = val & index->hash_mask; + for (entry = index->hash[i]; entry; entry = entry->next) { + const unsigned char *ref = entry->ptr; + const unsigned char *src = data; + unsigned int ref_size = (unsigned int)(ref_top - ref); + if (entry->val != val) + continue; + if (ref_size > (unsigned int)(top - src)) + ref_size = (unsigned int)(top - src); + if (ref_size <= msize) + break; + while (ref_size-- && *src++ == *ref) + ref++; + if (msize < (unsigned int)(ref - entry->ptr)) { + /* this is our best match so far */ + msize = (unsigned int)(ref - entry->ptr); + moff = (unsigned int)(entry->ptr - ref_data); + if (msize >= 4096) /* good enough */ + break; + } + } + } + + if (msize < 4) { + if (!inscnt) + bufpos++; + buf[bufpos++] = *data++; + inscnt++; + if (inscnt == 0x7f) { + buf[bufpos - inscnt - 1] = inscnt; + inscnt = 0; + } + msize = 0; + } else { + unsigned int left; + unsigned char *op; + + if (inscnt) { + while (moff && ref_data[moff-1] == data[-1]) { + /* we can match one byte back */ + msize++; + moff--; + data--; + bufpos--; + if (--inscnt) + continue; + bufpos--; /* remove count slot */ + inscnt--; /* make it -1 */ + break; + } + buf[bufpos - inscnt - 1] = inscnt; + inscnt = 0; + } + + /* A copy op is currently limited to 64KB (pack v2) */ + left = (msize < 0x10000) ? 0 : (msize - 0x10000); + msize -= left; + + op = buf + bufpos++; + i = 0x80; + + if (moff & 0x000000ff) + buf[bufpos++] = moff >> 0, i |= 0x01; + if (moff & 0x0000ff00) + buf[bufpos++] = moff >> 8, i |= 0x02; + if (moff & 0x00ff0000) + buf[bufpos++] = moff >> 16, i |= 0x04; + if (moff & 0xff000000) + buf[bufpos++] = moff >> 24, i |= 0x08; + + if (msize & 0x00ff) + buf[bufpos++] = msize >> 0, i |= 0x10; + if (msize & 0xff00) + buf[bufpos++] = msize >> 8, i |= 0x20; + + *op = i; + + data += msize; + moff += msize; + msize = left; + + if (msize < 4096) { + int j; + val = 0; + for (j = -RABIN_WINDOW; j < 0; j++) + val = ((val << 8) | data[j]) + ^ T[val >> RABIN_SHIFT]; + } + } + + if (bufpos >= bufsize - MAX_OP_SIZE) { + void *tmp = buf; + bufsize = bufsize * 3 / 2; + if (max_size && bufsize >= max_size) + bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); + if (max_size && bufpos > max_size) + break; + buf = git__realloc(buf, bufsize); + if (!buf) { + git__free(tmp); + return -1; + } + } + } + + if (inscnt) + buf[bufpos - inscnt - 1] = inscnt; + + if (max_size && bufpos > max_size) { + git_error_set(GIT_ERROR_NOMEMORY, "delta would be larger than maximum size"); + git__free(buf); + return GIT_EBUFS; + } + + *out_len = bufpos; + *out = buf; + return 0; +} + +/* +* Delta application was heavily cribbed from BinaryDelta.java in JGit, which +* itself was heavily cribbed from patch-delta.c in the +* GIT project. The original delta patching code was written by +* Nicolas Pitre . +*/ + +static int hdr_sz( + size_t *size, + const unsigned char **delta, + const unsigned char *end) +{ + const unsigned char *d = *delta; + size_t r = 0; + unsigned int c, shift = 0; + + do { + if (d == end) { + git_error_set(GIT_ERROR_INVALID, "truncated delta"); + return -1; + } + + c = *d++; + r |= (c & 0x7f) << shift; + shift += 7; + } while (c & 0x80); + *delta = d; + *size = r; + return 0; +} + +int git_delta_read_header( + size_t *base_out, + size_t *result_out, + const unsigned char *delta, + size_t delta_len) +{ + const unsigned char *delta_end = delta + delta_len; + if ((hdr_sz(base_out, &delta, delta_end) < 0) || + (hdr_sz(result_out, &delta, delta_end) < 0)) + return -1; + return 0; +} + +#define DELTA_HEADER_BUFFER_LEN 16 +int git_delta_read_header_fromstream( + size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) +{ + static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; + unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; + const unsigned char *delta, *delta_end; + size_t len; + ssize_t read; + + len = read = 0; + while (len < buffer_len) { + read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); + + if (read == 0) + break; + + if (read == GIT_EBUFS) + continue; + + len += read; + } + + delta = buffer; + delta_end = delta + len; + if ((hdr_sz(base_sz, &delta, delta_end) < 0) || + (hdr_sz(res_sz, &delta, delta_end) < 0)) + return -1; + + return 0; +} + +int git_delta_apply( + void **out, + size_t *out_len, + const unsigned char *base, + size_t base_len, + const unsigned char *delta, + size_t delta_len) +{ + const unsigned char *delta_end = delta + delta_len; + size_t base_sz, res_sz, alloc_sz; + unsigned char *res_dp; + + *out = NULL; + *out_len = 0; + + /* + * Check that the base size matches the data we were given; + * if not we would underflow while accessing data from the + * base object, resulting in data corruption or segfault. + */ + if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { + git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); + return -1; + } + + if (hdr_sz(&res_sz, &delta, delta_end) < 0) { + git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); + res_dp = git__malloc(alloc_sz); + GIT_ERROR_CHECK_ALLOC(res_dp); + + res_dp[res_sz] = '\0'; + *out = res_dp; + *out_len = res_sz; + + while (delta < delta_end) { + unsigned char cmd = *delta++; + if (cmd & 0x80) { + /* cmd is a copy instruction; copy from the base. */ + size_t off = 0, len = 0, end; + +#define ADD_DELTA(o, shift) { if (delta < delta_end) (o) |= ((unsigned) *delta++ << shift); else goto fail; } + if (cmd & 0x01) ADD_DELTA(off, 0UL); + if (cmd & 0x02) ADD_DELTA(off, 8UL); + if (cmd & 0x04) ADD_DELTA(off, 16UL); + if (cmd & 0x08) ADD_DELTA(off, 24UL); + + if (cmd & 0x10) ADD_DELTA(len, 0UL); + if (cmd & 0x20) ADD_DELTA(len, 8UL); + if (cmd & 0x40) ADD_DELTA(len, 16UL); + if (!len) len = 0x10000; +#undef ADD_DELTA + + if (GIT_ADD_SIZET_OVERFLOW(&end, off, len) || + base_len < end || res_sz < len) + goto fail; + + memcpy(res_dp, base + off, len); + res_dp += len; + res_sz -= len; + + } else if (cmd) { + /* + * cmd is a literal insert instruction; copy from + * the delta stream itself. + */ + if (delta_end - delta < cmd || res_sz < cmd) + goto fail; + memcpy(res_dp, delta, cmd); + delta += cmd; + res_dp += cmd; + res_sz -= cmd; + + } else { + /* cmd == 0 is reserved for future encodings. */ + goto fail; + } + } + + if (delta != delta_end || res_sz) + goto fail; + return 0; + +fail: + git__free(*out); + + *out = NULL; + *out_len = 0; + + git_error_set(GIT_ERROR_INVALID, "failed to apply delta"); + return -1; +} diff --git a/src/libgit2/delta.h b/src/libgit2/delta.h new file mode 100644 index 000000000..f61987304 --- /dev/null +++ b/src/libgit2/delta.h @@ -0,0 +1,136 @@ +/* + * diff-delta code taken from git.git. See diff-delta.c for details. + * + */ +#ifndef INCLUDE_git_delta_h__ +#define INCLUDE_git_delta_h__ + +#include "common.h" + +#include "pack.h" + +typedef struct git_delta_index git_delta_index; + +/* + * git_delta_index_init: compute index data from given buffer + * + * This returns a pointer to a struct delta_index that should be passed to + * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer + * is returned on failure. The given buffer must not be freed nor altered + * before free_delta_index() is called. The returned pointer must be freed + * using free_delta_index(). + */ +extern int git_delta_index_init( + git_delta_index **out, const void *buf, size_t bufsize); + +/* + * Free the index created by git_delta_index_init() + */ +extern void git_delta_index_free(git_delta_index *index); + +/* + * Returns memory usage of delta index. + */ +extern size_t git_delta_index_size(git_delta_index *index); + +/* + * create_delta: create a delta from given index for the given buffer + * + * This function may be called multiple times with different buffers using + * the same delta_index pointer. If max_delta_size is non-zero and the + * resulting delta is to be larger than max_delta_size then NULL is returned. + * On success, a non-NULL pointer to the buffer with the delta data is + * returned and *delta_size is updated with its size. The returned buffer + * must be freed by the caller. + */ +extern int git_delta_create_from_index( + void **out, + size_t *out_size, + const struct git_delta_index *index, + const void *buf, + size_t bufsize, + size_t max_delta_size); + +/* + * diff_delta: create a delta from source buffer to target buffer + * + * If max_delta_size is non-zero and the resulting delta is to be larger + * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL + * pointer to the buffer with the delta data is returned and *delta_size is + * updated with its size. The returned buffer must be freed by the caller. + */ +GIT_INLINE(int) git_delta( + void **out, size_t *out_len, + const void *src_buf, size_t src_bufsize, + const void *trg_buf, size_t trg_bufsize, + size_t max_delta_size) +{ + git_delta_index *index; + int error = 0; + + *out = NULL; + *out_len = 0; + + if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0) + return error; + + if (index) { + error = git_delta_create_from_index(out, out_len, + index, trg_buf, trg_bufsize, max_delta_size); + + git_delta_index_free(index); + } + + return error; +} + +/* the smallest possible delta size is 4 bytes */ +#define GIT_DELTA_SIZE_MIN 4 + +/** +* Apply a git binary delta to recover the original content. +* The caller is responsible for freeing the returned buffer. +* +* @param out the output buffer +* @param out_len the length of the output buffer +* @param base the base to copy from during copy instructions. +* @param base_len number of bytes available at base. +* @param delta the delta to execute copy/insert instructions from. +* @param delta_len total number of bytes in the delta. +* @return 0 on success or an error code +*/ +extern int git_delta_apply( + void **out, + size_t *out_len, + const unsigned char *base, + size_t base_len, + const unsigned char *delta, + size_t delta_len); + +/** +* Read the header of a git binary delta. +* +* @param base_out pointer to store the base size field. +* @param result_out pointer to store the result size field. +* @param delta the delta to execute copy/insert instructions from. +* @param delta_len total number of bytes in the delta. +* @return 0 on success or an error code +*/ +extern int git_delta_read_header( + size_t *base_out, + size_t *result_out, + const unsigned char *delta, + size_t delta_len); + +/** + * Read the header of a git binary delta + * + * This variant reads just enough from the packfile stream to read the + * delta header. + */ +extern int git_delta_read_header_fromstream( + size_t *base_out, + size_t *result_out, + git_packfile_stream *stream); + +#endif diff --git a/src/libgit2/describe.c b/src/libgit2/describe.c new file mode 100644 index 000000000..1033eac50 --- /dev/null +++ b/src/libgit2/describe.c @@ -0,0 +1,909 @@ +/* + * 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/describe.h" +#include "git2/strarray.h" +#include "git2/diff.h" +#include "git2/status.h" + +#include "buf.h" +#include "commit.h" +#include "commit_list.h" +#include "oidmap.h" +#include "refs.h" +#include "repository.h" +#include "revwalk.h" +#include "tag.h" +#include "vector.h" +#include "wildmatch.h" + +/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ + +struct commit_name { + git_tag *tag; + unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned name_checked:1; + git_oid sha1; + char *path; + + /* Khash workaround. They original key has to still be reachable */ + git_oid peeled; +}; + +static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) +{ + return git_oidmap_get(map, key); +} + +static struct commit_name *find_commit_name( + git_oidmap *names, + const git_oid *peeled) +{ + return (struct commit_name *)(oidmap_value_bykey(names, peeled)); +} + +static int replace_name( + git_tag **tag, + git_repository *repo, + struct commit_name *e, + unsigned int prio, + const git_oid *sha1) +{ + git_time_t e_time = 0, t_time = 0; + + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + git_tag *t = NULL; + + if (!e->tag) { + if (git_tag_lookup(&t, repo, &e->sha1) < 0) + return 1; + e->tag = t; + } + + if (git_tag_lookup(&t, repo, sha1) < 0) + return 0; + + *tag = t; + + if (e->tag->tagger) + e_time = e->tag->tagger->when.time; + + if (t->tagger) + t_time = t->tagger->when.time; + + if (e_time < t_time) + return 1; + } + + return 0; +} + +static int add_to_known_names( + git_repository *repo, + git_oidmap *names, + const char *path, + const git_oid *peeled, + unsigned int prio, + const git_oid *sha1) +{ + struct commit_name *e = find_commit_name(names, peeled); + bool found = (e != NULL); + + git_tag *tag = NULL; + if (replace_name(&tag, repo, e, prio, sha1)) { + if (!found) { + e = git__malloc(sizeof(struct commit_name)); + GIT_ERROR_CHECK_ALLOC(e); + + e->path = NULL; + e->tag = NULL; + } + + if (e->tag) + git_tag_free(e->tag); + e->tag = tag; + e->prio = prio; + e->name_checked = 0; + git_oid_cpy(&e->sha1, sha1); + git__free(e->path); + e->path = git__strdup(path); + git_oid_cpy(&e->peeled, peeled); + + if (!found && git_oidmap_set(names, &e->peeled, e) < 0) + return -1; + } + else + git_tag_free(tag); + + return 0; +} + +static int retrieve_peeled_tag_or_object_oid( + git_oid *peeled_out, + git_oid *ref_target_out, + git_repository *repo, + const char *refname) +{ + git_reference *ref; + git_object *peeled = NULL; + int error; + + if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0) + return error; + + if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0) + goto cleanup; + + git_oid_cpy(ref_target_out, git_reference_target(ref)); + git_oid_cpy(peeled_out, git_object_id(peeled)); + + if (git_oid_cmp(ref_target_out, peeled_out) != 0) + error = 1; /* The reference was pointing to a annotated tag */ + else + error = 0; /* Any other object */ + +cleanup: + git_reference_free(ref); + git_object_free(peeled); + return error; +} + +struct git_describe_result { + int dirty; + int exact_match; + int fallback_to_id; + git_oid commit_id; + git_repository *repo; + struct commit_name *name; + struct possible_tag *tag; +}; + +struct get_name_data +{ + git_describe_options *opts; + git_repository *repo; + git_oidmap *names; + git_describe_result *result; +}; + +static int commit_name_dup(struct commit_name **out, struct commit_name *in) +{ + struct commit_name *name; + + name = git__malloc(sizeof(struct commit_name)); + GIT_ERROR_CHECK_ALLOC(name); + + memcpy(name, in, sizeof(struct commit_name)); + name->tag = NULL; + name->path = NULL; + + if (in->tag && git_tag_dup(&name->tag, in->tag) < 0) + return -1; + + name->path = git__strdup(in->path); + GIT_ERROR_CHECK_ALLOC(name->path); + + *out = name; + return 0; +} + +static int get_name(const char *refname, void *payload) +{ + struct get_name_data *data; + bool is_tag, is_annotated, all; + git_oid peeled, sha1; + unsigned int prio; + int error = 0; + + data = (struct get_name_data *)payload; + is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR); + all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; + + /* Reject anything outside refs/tags/ unless --all */ + if (!all && !is_tag) + return 0; + + /* Accept only tags that match the pattern, if given */ + if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, + refname + strlen(GIT_REFS_TAGS_DIR), 0))) + return 0; + + /* Is it annotated? */ + if ((error = retrieve_peeled_tag_or_object_oid( + &peeled, &sha1, data->repo, refname)) < 0) + return error; + + is_annotated = error; + + /* + * By default, we only use annotated tags, but with --tags + * we fall back to lightweight ones (even without --tags, + * we still remember lightweight ones, only to give hints + * in an error message). --all allows any refs to be used. + */ + if (is_annotated) + prio = 2; + else if (is_tag) + prio = 1; + else + prio = 0; + + add_to_known_names(data->repo, data->names, + all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), + &peeled, prio, &sha1); + return 0; +} + +struct possible_tag { + struct commit_name *name; + int depth; + int found_order; + unsigned flag_within; +}; + +static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in) +{ + struct possible_tag *tag; + int error; + + tag = git__malloc(sizeof(struct possible_tag)); + GIT_ERROR_CHECK_ALLOC(tag); + + memcpy(tag, in, sizeof(struct possible_tag)); + tag->name = NULL; + + if ((error = commit_name_dup(&tag->name, in->name)) < 0) { + git__free(tag); + *out = NULL; + return error; + } + + *out = tag; + return 0; +} + +static int compare_pt(const void *a_, const void *b_) +{ + struct possible_tag *a = (struct possible_tag *)a_; + struct possible_tag *b = (struct possible_tag *)b_; + if (a->depth != b->depth) + return a->depth - b->depth; + if (a->found_order != b->found_order) + return a->found_order - b->found_order; + return 0; +} + +#define SEEN (1u << 0) + +static unsigned long finish_depth_computation( + git_pqueue *list, + git_revwalk *walk, + struct possible_tag *best) +{ + unsigned long seen_commits = 0; + int error, i; + + while (git_pqueue_size(list) > 0) { + git_commit_list_node *c = git_pqueue_pop(list); + seen_commits++; + if (c->flags & best->flag_within) { + size_t index = 0; + while (git_pqueue_size(list) > index) { + git_commit_list_node *i = git_pqueue_get(list, index); + if (!(i->flags & best->flag_within)) + break; + index++; + } + if (index > git_pqueue_size(list)) + break; + } else + best->depth++; + for (i = 0; i < c->out_degree; i++) { + git_commit_list_node *p = c->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + if (!(p->flags & SEEN)) + if ((error = git_pqueue_insert(list, p)) < 0) + return error; + p->flags |= c->flags; + } + } + return seen_commits; +} + +static int display_name(git_str *buf, git_repository *repo, struct commit_name *n) +{ + if (n->prio == 2 && !n->tag) { + if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) { + git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path); + return -1; + } + } + + if (n->tag && !n->name_checked) { + if (!git_tag_name(n->tag)) { + git_error_set(GIT_ERROR_TAG, "annotated tag '%s' has no embedded name", n->path); + return -1; + } + + /* TODO: Cope with warnings + if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) + warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); + */ + + n->name_checked = 1; + } + + if (n->tag) + git_str_printf(buf, "%s", git_tag_name(n->tag)); + else + git_str_printf(buf, "%s", n->path); + + return 0; +} + +static int find_unique_abbrev_size( + int *out, + git_repository *repo, + const git_oid *oid_in, + unsigned int abbreviated_size) +{ + size_t size = abbreviated_size; + git_odb *odb; + git_oid dummy; + int error; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + while (size < GIT_OID_HEXSZ) { + if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) { + *out = (int) size; + return 0; + } + + /* If the error wasn't that it's not unique, then it's a proper error */ + if (error != GIT_EAMBIGUOUS) + return error; + + /* Try again with a larger size */ + size++; + } + + /* If we didn't find any shorter prefix, we have to do the whole thing */ + *out = GIT_OID_HEXSZ; + + return 0; +} + +static int show_suffix( + git_str *buf, + int depth, + git_repository *repo, + const git_oid *id, + unsigned int abbrev_size) +{ + int error, size = 0; + + char hex_oid[GIT_OID_HEXSZ]; + + if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0) + return error; + + git_oid_fmt(hex_oid, id); + + git_str_printf(buf, "-%d-g", depth); + + git_str_put(buf, hex_oid, size); + + return git_str_oom(buf) ? -1 : 0; +} + +#define MAX_CANDIDATES_TAGS FLAG_BITS - 1 + +static int describe_not_found(const git_oid *oid, const char *message_format) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), oid); + + git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str); + return GIT_ENOTFOUND; +} + +static int describe( + struct get_name_data *data, + git_commit *commit) +{ + struct commit_name *n; + struct possible_tag *best; + bool all, tags; + git_revwalk *walk = NULL; + git_pqueue list; + git_commit_list_node *cmit, *gave_up_on = NULL; + git_vector all_matches = GIT_VECTOR_INIT; + unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; + unsigned long seen_commits = 0; /* TODO: Check long */ + unsigned int unannotated_cnt = 0; + int error; + + if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0) + return -1; + + if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0) + goto cleanup; + + all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; + tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS; + + git_oid_cpy(&data->result->commit_id, git_commit_id(commit)); + + n = find_commit_name(data->names, git_commit_id(commit)); + if (n && (tags || all || n->prio == 2)) { + /* + * Exact match to an existing ref. + */ + data->result->exact_match = 1; + if ((error = commit_name_dup(&data->result->name, n)) < 0) + goto cleanup; + + goto cleanup; + } + + if (!data->opts->max_candidates_tags) { + error = describe_not_found( + git_commit_id(commit), + "cannot describe - no tag exactly matches '%s'"); + + goto cleanup; + } + + if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0) + goto cleanup; + + if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL) + goto cleanup; + + if ((error = git_commit_list_parse(walk, cmit)) < 0) + goto cleanup; + + cmit->flags = SEEN; + + if ((error = git_pqueue_insert(&list, cmit)) < 0) + goto cleanup; + + while (git_pqueue_size(&list) > 0) + { + int i; + + git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); + seen_commits++; + + n = find_commit_name(data->names, &c->oid); + + if (n) { + if (!tags && !all && n->prio < 2) { + unannotated_cnt++; + } else if (match_cnt < data->opts->max_candidates_tags) { + struct possible_tag *t = git__malloc(sizeof(struct commit_name)); + GIT_ERROR_CHECK_ALLOC(t); + if ((error = git_vector_insert(&all_matches, t)) < 0) + goto cleanup; + + match_cnt++; + + t->name = n; + t->depth = seen_commits - 1; + t->flag_within = 1u << match_cnt; + t->found_order = match_cnt; + c->flags |= t->flag_within; + if (n->prio == 2) + annotated_cnt++; + } + else { + gave_up_on = c; + break; + } + } + + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = git_vector_get(&all_matches, cur_match); + if (!(c->flags & t->flag_within)) + t->depth++; + } + + if (annotated_cnt && (git_pqueue_size(&list) == 0)) { + /* + if (debug) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), &c->oid); + + fprintf(stderr, "finished search at %s\n", oid_str); + } + */ + break; + } + for (i = 0; i < c->out_degree; i++) { + git_commit_list_node *p = c->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + goto cleanup; + if (!(p->flags & SEEN)) + if ((error = git_pqueue_insert(&list, p)) < 0) + goto cleanup; + p->flags |= c->flags; + + if (data->opts->only_follow_first_parent) + break; + } + } + + if (!match_cnt) { + if (data->opts->show_commit_oid_as_fallback) { + data->result->fallback_to_id = 1; + git_oid_cpy(&data->result->commit_id, &cmit->oid); + + goto cleanup; + } + if (unannotated_cnt) { + error = describe_not_found(git_commit_id(commit), + "cannot describe - " + "no annotated tags can describe '%s'; " + "however, there were unannotated tags."); + goto cleanup; + } + else { + error = describe_not_found(git_commit_id(commit), + "cannot describe - " + "no tags can describe '%s'."); + goto cleanup; + } + } + + git_vector_sort(&all_matches); + + best = (struct possible_tag *)git_vector_get(&all_matches, 0); + + if (gave_up_on) { + if ((error = git_pqueue_insert(&list, gave_up_on)) < 0) + goto cleanup; + seen_commits--; + } + if ((error = finish_depth_computation( + &list, walk, best)) < 0) + goto cleanup; + + seen_commits += error; + if ((error = possible_tag_dup(&data->result->tag, best)) < 0) + goto cleanup; + + /* + { + static const char *prio_names[] = { + "head", "lightweight", "annotated", + }; + + char oid_str[GIT_OID_HEXSZ + 1]; + + if (debug) { + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match); + fprintf(stderr, " %-11s %8d %s\n", + prio_names[t->name->prio], + t->depth, t->name->path); + } + fprintf(stderr, "traversed %lu commits\n", seen_commits); + if (gave_up_on) { + git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid); + fprintf(stderr, + "more than %i tags found; listed %i most recent\n" + "gave up search at %s\n", + data->opts->max_candidates_tags, data->opts->max_candidates_tags, + oid_str); + } + } + } + */ + + git_oid_cpy(&data->result->commit_id, &cmit->oid); + +cleanup: + { + size_t i; + struct possible_tag *match; + git_vector_foreach(&all_matches, i, match) { + git__free(match); + } + } + git_vector_free(&all_matches); + git_pqueue_free(&list); + git_revwalk_free(walk); + return error; +} + +static int normalize_options( + git_describe_options *dst, + const git_describe_options *src) +{ + git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT; + if (!src) src = &default_options; + + *dst = *src; + + if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS) + dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS; + + return 0; +} + +int git_describe_commit( + git_describe_result **result, + git_object *committish, + git_describe_options *opts) +{ + struct get_name_data data; + struct commit_name *name; + git_commit *commit; + int error = -1; + git_describe_options normalized; + + GIT_ASSERT_ARG(result); + GIT_ASSERT_ARG(committish); + + data.result = git__calloc(1, sizeof(git_describe_result)); + GIT_ERROR_CHECK_ALLOC(data.result); + data.result->repo = git_object_owner(committish); + + data.repo = git_object_owner(committish); + + if ((error = normalize_options(&normalized, opts)) < 0) + return error; + + GIT_ERROR_CHECK_VERSION( + &normalized, + GIT_DESCRIBE_OPTIONS_VERSION, + "git_describe_options"); + data.opts = &normalized; + + if ((error = git_oidmap_new(&data.names)) < 0) + return error; + + /** TODO: contains to be implemented */ + + if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + if ((error = git_reference_foreach_name( + git_object_owner(committish), + get_name, &data)) < 0) + goto cleanup; + + if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) { + git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " + "no reference found, cannot describe anything."); + error = -1; + goto cleanup; + } + + if ((error = describe(&data, commit)) < 0) + goto cleanup; + +cleanup: + git_commit_free(commit); + + git_oidmap_foreach_value(data.names, name, { + git_tag_free(name->tag); + git__free(name->path); + git__free(name); + }); + + git_oidmap_free(data.names); + + if (error < 0) + git_describe_result_free(data.result); + else + *result = data.result; + + return error; +} + +int git_describe_workdir( + git_describe_result **out, + git_repository *repo, + git_describe_options *opts) +{ + int error; + git_oid current_id; + git_status_list *status = NULL; + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + git_describe_result *result = NULL; + git_object *commit; + + if ((error = git_reference_name_to_id(¤t_id, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_object_lookup(&commit, repo, ¤t_id, GIT_OBJECT_COMMIT)) < 0) + return error; + + /* The first step is to perform a describe of HEAD, so we can leverage this */ + if ((error = git_describe_commit(&result, commit, opts)) < 0) + goto out; + + if ((error = git_status_list_new(&status, repo, &status_opts)) < 0) + goto out; + + + if (git_status_list_entrycount(status) > 0) + result->dirty = 1; + +out: + git_object_free(commit); + git_status_list_free(status); + + if (error < 0) + git_describe_result_free(result); + else + *out = result; + + return error; +} + +static int normalize_format_options( + git_describe_format_options *dst, + const git_describe_format_options *src) +{ + if (!src) { + git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); + return 0; + } + + memcpy(dst, src, sizeof(git_describe_format_options)); + return 0; +} + +static int git_describe__format( + git_str *out, + const git_describe_result *result, + const git_describe_format_options *given) +{ + int error; + git_repository *repo; + struct commit_name *name; + git_describe_format_options opts; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(result); + + GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options"); + normalize_format_options(&opts, given); + + if (opts.always_use_long_format && opts.abbreviated_size == 0) { + git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " + "'always_use_long_format' is incompatible with a zero" + "'abbreviated_size'"); + return -1; + } + + + repo = result->repo; + + /* If we did find an exact match, then it's the easier method */ + if (result->exact_match) { + name = result->name; + if ((error = display_name(out, repo, name)) < 0) + return error; + + if (opts.always_use_long_format) { + const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id; + if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0) + return error; + } + + if (result->dirty && opts.dirty_suffix) + git_str_puts(out, opts.dirty_suffix); + + return git_str_oom(out) ? -1 : 0; + } + + /* If we didn't find *any* tags, we fall back to the commit's id */ + if (result->fallback_to_id) { + char hex_oid[GIT_OID_HEXSZ + 1] = {0}; + int size = 0; + + if ((error = find_unique_abbrev_size( + &size, repo, &result->commit_id, opts.abbreviated_size)) < 0) + return -1; + + git_oid_fmt(hex_oid, &result->commit_id); + git_str_put(out, hex_oid, size); + + if (result->dirty && opts.dirty_suffix) + git_str_puts(out, opts.dirty_suffix); + + return git_str_oom(out) ? -1 : 0; + } + + /* Lastly, if we found a matching tag, we show that */ + name = result->tag->name; + + if ((error = display_name(out, repo, name)) < 0) + return error; + + if (opts.abbreviated_size) { + if ((error = show_suffix(out, result->tag->depth, repo, + &result->commit_id, opts.abbreviated_size)) < 0) + return error; + } + + if (result->dirty && opts.dirty_suffix) { + git_str_puts(out, opts.dirty_suffix); + } + + return git_str_oom(out) ? -1 : 0; +} + +int git_describe_format( + git_buf *out, + const git_describe_result *result, + const git_describe_format_options *given) +{ + GIT_BUF_WRAP_PRIVATE(out, git_describe__format, result, given); +} + +void git_describe_result_free(git_describe_result *result) +{ + if (result == NULL) + return; + + if (result->name) { + git_tag_free(result->name->tag); + git__free(result->name->path); + git__free(result->name); + } + + if (result->tag) { + git_tag_free(result->tag->name->tag); + git__free(result->tag->name->path); + git__free(result->tag->name); + git__free(result->tag); + } + + git__free(result); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_describe_init_options(git_describe_options *opts, unsigned int version) +{ + return git_describe_options_init(opts, version); +} +#endif + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) +{ + return git_describe_format_options_init(opts, version); +} +#endif diff --git a/src/libgit2/diff.c b/src/libgit2/diff.c new file mode 100644 index 000000000..9840d6050 --- /dev/null +++ b/src/libgit2/diff.c @@ -0,0 +1,389 @@ +/* + * 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 "diff.h" + +#include "common.h" +#include "buf.h" +#include "patch.h" +#include "email.h" +#include "commit.h" +#include "index.h" +#include "diff_generate.h" + +#include "git2/version.h" +#include "git2/email.h" + +struct patch_id_args { + git_hash_ctx ctx; + git_oid result; + int first_file; +}; + +GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) +{ + const char *str = delta->old_file.path; + + if (!str || + delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_RENAMED || + delta->status == GIT_DELTA_COPIED) + str = delta->new_file.path; + + return str; +} + +int git_diff_delta__cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__path(da), diff_delta__path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff_delta__casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__path(da), diff_delta__path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff__entry_cmp(const void *a, const void *b) +{ + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + return strcmp(entry_a->path, entry_b->path); +} + +int git_diff__entry_icmp(const void *a, const void *b) +{ + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + return strcasecmp(entry_a->path, entry_b->path); +} + +void git_diff_free(git_diff *diff) +{ + if (!diff) + return; + + GIT_REFCOUNT_DEC(diff, diff->free_fn); +} + +void git_diff_addref(git_diff *diff) +{ + GIT_REFCOUNT_INC(diff); +} + +size_t git_diff_num_deltas(const git_diff *diff) +{ + GIT_ASSERT_ARG(diff); + return diff->deltas.length; +} + +size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type) +{ + size_t i, count = 0; + const git_diff_delta *delta; + + GIT_ASSERT_ARG(diff); + + git_vector_foreach(&diff->deltas, i, delta) { + count += (delta->status == type); + } + + return count; +} + +const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL); + return git_vector_get(&diff->deltas, idx); +} + +int git_diff_is_sorted_icase(const git_diff *diff) +{ + return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; +} + +int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff) +{ + GIT_ASSERT_ARG(out); + GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); + out->stat_calls = diff->perf.stat_calls; + out->oid_calculations = diff->perf.oid_calculations; + return 0; +} + +int git_diff_foreach( + git_diff *diff, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + int error = 0; + git_diff_delta *delta; + size_t idx; + + GIT_ASSERT_ARG(diff); + + git_vector_foreach(&diff->deltas, idx, delta) { + git_patch *patch; + + /* check flags against patch status */ + if (git_diff_delta__should_skip(&diff->opts, delta)) + continue; + + if ((error = git_patch_from_diff(&patch, diff, idx)) != 0) + break; + + error = git_patch__invoke_callbacks(patch, file_cb, binary_cb, + hunk_cb, data_cb, payload); + git_patch_free(patch); + + if (error) + break; + } + + return error; +} + +#ifndef GIT_DEPRECATE_HARD + +int git_diff_format_email( + git_buf *out, + git_diff *diff, + const git_diff_format_email_options *opts) +{ + git_email_create_options email_create_opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + git_str email = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author); + + GIT_ERROR_CHECK_VERSION(opts, + GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, + "git_format_email_options"); + + /* This is a `git_buf` special case; subsequent calls append. */ + email.ptr = out->ptr; + email.asize = out->reserved; + email.size = out->size; + + out->ptr = git_str__initstr; + out->reserved = 0; + out->size = 0; + + if ((opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) + email_create_opts.subject_prefix = ""; + + error = git_email__append_from_diff(&email, diff, opts->patch_no, + opts->total_patches, opts->id, opts->summary, opts->body, + opts->author, &email_create_opts); + + if (error < 0) + goto done; + + error = git_buf_fromstr(out, &email); + +done: + git_str_dispose(&email); + return error; +} + +int git_diff_commit_as_email( + git_buf *out, + git_repository *repo, + git_commit *commit, + size_t patch_no, + size_t total_patches, + uint32_t flags, + const git_diff_options *diff_opts) +{ + git_diff *diff = NULL; + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + const git_oid *commit_id; + const char *summary, *body; + const git_signature *author; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(commit); + + commit_id = git_commit_id(commit); + summary = git_commit_summary(commit); + body = git_commit_body(commit); + author = git_commit_author(commit); + + if ((flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) + opts.subject_prefix = ""; + + if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) + return error; + + error = git_email_create_from_diff(out, diff, patch_no, total_patches, commit_id, summary, body, author, &opts); + + git_diff_free(diff); + return error; +} + +int git_diff_init_options(git_diff_options *opts, unsigned int version) +{ + return git_diff_options_init(opts, version); +} + +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( + opts, version, git_diff_format_email_options, + GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT); + 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); +} + +#endif + +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_options_init( + git_diff_find_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT); + return 0; +} + +static int flush_hunk(git_oid *result, git_hash_ctx *ctx) +{ + git_oid hash; + unsigned short carry = 0; + int error, i; + + if ((error = git_hash_final(hash.id, ctx)) < 0 || + (error = git_hash_init(ctx)) < 0) + return error; + + for (i = 0; i < GIT_OID_RAWSZ; i++) { + carry += result->id[i] + hash.id[i]; + result->id[i] = (unsigned char)carry; + carry >>= 8; + } + + return 0; +} + +static void strip_spaces(git_str *buf) +{ + char *src = buf->ptr, *dst = buf->ptr; + char c; + size_t len = 0; + + while ((c = *src++) != '\0') { + if (!git__isspace(c)) { + *dst++ = c; + len++; + } + } + + git_str_truncate(buf, len); +} + +static int diff_patchid_print_callback_to_buf( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + struct patch_id_args *args = (struct patch_id_args *) payload; + git_str buf = GIT_STR_INIT; + int error = 0; + + if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL || + line->origin == GIT_DIFF_LINE_ADD_EOFNL || + line->origin == GIT_DIFF_LINE_DEL_EOFNL) + goto out; + + if ((error = git_diff_print_callback__to_buf(delta, hunk, + line, &buf)) < 0) + goto out; + + 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_str_dispose(&buf); + return error; +} + +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); + return 0; +} + +int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts) +{ + struct patch_id_args args; + int error; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options"); + + memset(&args, 0, sizeof(args)); + args.first_file = 1; + if ((error = git_hash_ctx_init(&args.ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) + goto out; + + if ((error = git_diff_print(diff, + GIT_DIFF_FORMAT_PATCH_ID, + diff_patchid_print_callback_to_buf, + &args)) < 0) + goto out; + + if ((error = (flush_hunk(&args.result, &args.ctx))) < 0) + goto out; + + git_oid_cpy(out, &args.result); + +out: + git_hash_ctx_cleanup(&args.ctx); + return error; +} diff --git a/src/libgit2/diff.h b/src/libgit2/diff.h new file mode 100644 index 000000000..2cc35e65b --- /dev/null +++ b/src/libgit2/diff.h @@ -0,0 +1,67 @@ +/* + * 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_diff_h__ +#define INCLUDE_diff_h__ + +#include "common.h" + +#include "git2/diff.h" +#include "git2/patch.h" +#include "git2/sys/diff.h" +#include "git2/oid.h" + +#include "vector.h" +#include "iterator.h" +#include "repository.h" +#include "pool.h" +#include "odb.h" + +#define DIFF_OLD_PREFIX_DEFAULT "a/" +#define DIFF_NEW_PREFIX_DEFAULT "b/" + +typedef enum { + GIT_DIFF_TYPE_UNKNOWN = 0, + GIT_DIFF_TYPE_GENERATED = 1, + GIT_DIFF_TYPE_PARSED = 2 +} git_diff_origin_t; + +struct git_diff { + git_refcount rc; + git_repository *repo; + git_attr_session attrsession; + git_diff_origin_t type; + git_diff_options opts; + git_vector deltas; /* vector of git_diff_delta */ + git_pool pool; + git_iterator_t old_src; + git_iterator_t new_src; + git_diff_perfdata perf; + + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); + int (*pfxcomp)(const char *str, const char *pfx); + int (*entrycomp)(const void *a, const void *b); + + int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx); + void (*free_fn)(git_diff *diff); +}; + +extern int git_diff_delta__format_file_header( + git_str *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + 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); + +extern int git_diff__entry_cmp(const void *a, const void *b); +extern int git_diff__entry_icmp(const void *a, const void *b); + +#endif diff --git a/src/libgit2/diff_driver.c b/src/libgit2/diff_driver.c new file mode 100644 index 000000000..5f25fdb44 --- /dev/null +++ b/src/libgit2/diff_driver.c @@ -0,0 +1,522 @@ +/* + * 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 "diff_driver.h" + +#include "git2/attr.h" + +#include "common.h" +#include "diff.h" +#include "strmap.h" +#include "map.h" +#include "config.h" +#include "regexp.h" +#include "repository.h" + +typedef enum { + DIFF_DRIVER_AUTO = 0, + DIFF_DRIVER_BINARY = 1, + DIFF_DRIVER_TEXT = 2, + DIFF_DRIVER_PATTERNLIST = 3 +} git_diff_driver_t; + +typedef struct { + git_regexp re; + int flags; +} git_diff_driver_pattern; + +enum { + REG_NEGATE = (1 << 15) /* get out of the way of existing flags */ +}; + +/* data for finding function context for a given file type */ +struct git_diff_driver { + git_diff_driver_t type; + uint32_t binary_flags; + uint32_t other_flags; + git_array_t(git_diff_driver_pattern) fn_patterns; + git_regexp word_pattern; + char name[GIT_FLEX_ARRAY]; +}; + +#include "userdiff.h" + +struct git_diff_driver_registry { + git_strmap *drivers; +}; + +#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY) + +static git_diff_driver diff_driver_auto = { DIFF_DRIVER_AUTO, 0, 0 }; +static git_diff_driver diff_driver_binary = { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 }; +static git_diff_driver diff_driver_text = { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 }; + +git_diff_driver_registry *git_diff_driver_registry_new(void) +{ + git_diff_driver_registry *reg = + git__calloc(1, sizeof(git_diff_driver_registry)); + if (!reg) + return NULL; + + if (git_strmap_new(®->drivers) < 0) { + git_diff_driver_registry_free(reg); + return NULL; + } + + return reg; +} + +void git_diff_driver_registry_free(git_diff_driver_registry *reg) +{ + git_diff_driver *drv; + + if (!reg) + return; + + git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv)); + git_strmap_free(reg->drivers); + git__free(reg); +} + +static int diff_driver_add_patterns( + git_diff_driver *drv, const char *regex_str, int regex_flags) +{ + int error = 0; + const char *scan, *end; + git_diff_driver_pattern *pat = NULL; + git_str buf = GIT_STR_INIT; + + for (scan = regex_str; scan; scan = end) { + /* get pattern to fill in */ + if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) { + return -1; + } + + pat->flags = regex_flags; + if (*scan == '!') { + pat->flags |= REG_NEGATE; + ++scan; + } + + if ((end = strchr(scan, '\n')) != NULL) { + error = git_str_set(&buf, scan, end - scan); + end++; + } else { + error = git_str_sets(&buf, scan); + } + if (error < 0) + break; + + if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) { + /* + * TODO: issue a warning + */ + } + } + + if (error && pat != NULL) + (void)git_array_pop(drv->fn_patterns); /* release last item */ + git_str_dispose(&buf); + + /* We want to ignore bad patterns, so return success regardless */ + return 0; +} + +static int diff_driver_xfuncname(const git_config_entry *entry, void *payload) +{ + return diff_driver_add_patterns(payload, entry->value, 0); +} + +static int diff_driver_funcname(const git_config_entry *entry, void *payload) +{ + return diff_driver_add_patterns(payload, entry->value, 0); +} + +static git_diff_driver_registry *git_repository_driver_registry( + git_repository *repo) +{ + git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg; + if (reg) + return reg; + + newreg = git_diff_driver_registry_new(); + if (!newreg) { + git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry"); + return newreg; + } + reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg); + if (!reg) { + reg = newreg; + } else { + /* if we race, free losing allocation */ + git_diff_driver_registry_free(newreg); + } + return reg; +} + +static int diff_driver_alloc( + git_diff_driver **out, size_t *namelen_out, const char *name) +{ + git_diff_driver *driver; + size_t driverlen = sizeof(git_diff_driver), + namelen = strlen(name), + alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + driver = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(driver); + + memcpy(driver->name, name, namelen); + + *out = driver; + + if (namelen_out) + *namelen_out = namelen; + + return 0; +} + +static int git_diff_driver_builtin( + git_diff_driver **out, + git_diff_driver_registry *reg, + const char *driver_name) +{ + 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) { + if (!strcasecmp(driver_name, builtin_defs[idx].name)) { + ddef = &builtin_defs[idx]; + break; + } + } + if (!ddef) + goto done; + + if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0) + goto done; + + drv->type = DIFF_DRIVER_PATTERNLIST; + + if (ddef->fns && + (error = diff_driver_add_patterns( + drv, ddef->fns, ddef->flags)) < 0) + goto done; + + if (ddef->words && + (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0) + goto done; + + if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + goto done; + +done: + if (error && drv) + git_diff_driver_free(drv); + else + *out = drv; + + return error; +} + +static int git_diff_driver_load( + git_diff_driver **out, git_repository *repo, const char *driver_name) +{ + int error = 0; + git_diff_driver_registry *reg; + git_diff_driver *drv; + size_t namelen; + git_config *cfg = NULL; + git_str name = GIT_STR_INIT; + git_config_entry *ce = NULL; + bool found_driver = false; + + if ((reg = git_repository_driver_registry(repo)) == NULL) + return -1; + + if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) { + *out = drv; + return 0; + } + + if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0) + goto done; + + drv->type = DIFF_DRIVER_AUTO; + + /* if you can't read config for repo, just use default driver */ + if (git_repository_config_snapshot(&cfg, repo) < 0) { + git_error_clear(); + goto done; + } + + if ((error = git_str_printf(&name, "diff.%s.binary", driver_name)) < 0) + goto done; + + switch (git_config__get_bool_force(cfg, name.ptr, -1)) { + case true: + /* if diff..binary is true, just return the binary driver */ + *out = &diff_driver_binary; + goto done; + case false: + /* if diff..binary is false, force binary checks off */ + /* but still may have custom function context patterns, etc. */ + drv->binary_flags = GIT_DIFF_FORCE_TEXT; + found_driver = true; + break; + default: + /* diff..binary unspecified or "auto", so just continue */ + break; + } + + /* TODO: warn if diff..command or diff..textconv are set */ + + git_str_truncate(&name, namelen + strlen("diff..")); + if ((error = git_str_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) + goto done; + git_error_clear(); /* no diff..xfuncname, so just continue */ + } + + git_str_truncate(&name, namelen + strlen("diff..")); + if ((error = git_str_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) + goto done; + git_error_clear(); /* no diff..funcname, so just continue */ + } + + /* if we found any patterns, set driver type to use correct callback */ + if (git_array_size(drv->fn_patterns) > 0) { + drv->type = DIFF_DRIVER_PATTERNLIST; + found_driver = true; + } + + git_str_truncate(&name, namelen + strlen("diff..")); + if ((error = git_str_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 = git_regexp_compile(&drv->word_pattern, ce->value, 0))) + found_driver = true; + else { + /* TODO: warn about bad regex instead of failure */ + goto done; + } + + /* TODO: look up diff..algorithm to turn on minimal / patience + * diff in drv->other_flags + */ + + /* if no driver config found at all, fall back on AUTO driver */ + if (!found_driver) + goto done; + + /* store driver in registry */ + if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + goto done; + + *out = drv; + +done: + git_config_entry_free(ce); + git_str_dispose(&name); + git_config_free(cfg); + + if (!*out) { + int error2 = git_diff_driver_builtin(out, reg, driver_name); + if (!error) + error = error2; + } + + if (drv && drv != *out) + git_diff_driver_free(drv); + + return error; +} + +int git_diff_driver_lookup( + git_diff_driver **out, git_repository *repo, + git_attr_session *attrsession, const char *path) +{ + int error = 0; + const char *values[1], *attrs[] = { "diff" }; + + GIT_ASSERT_ARG(out); + *out = NULL; + + if (!repo || !path || !strlen(path)) + /* just use the auto value */; + else if ((error = git_attr_get_many_with_session(values, repo, + attrsession, 0, path, 1, attrs)) < 0) + /* return error below */; + + else if (GIT_ATTR_IS_UNSPECIFIED(values[0])) + /* just use the auto value */; + else if (GIT_ATTR_IS_FALSE(values[0])) + *out = &diff_driver_binary; + else if (GIT_ATTR_IS_TRUE(values[0])) + *out = &diff_driver_text; + + /* otherwise look for driver information in config and build driver */ + else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) { + if (error == GIT_ENOTFOUND) { + error = 0; + git_error_clear(); + } + } + + if (!*out) + *out = &diff_driver_auto; + + return error; +} + +void git_diff_driver_free(git_diff_driver *driver) +{ + git_diff_driver_pattern *pat; + + if (!driver) + return; + + while ((pat = git_array_pop(driver->fn_patterns)) != NULL) + git_regexp_dispose(&pat->re); + git_array_clear(driver->fn_patterns); + + git_regexp_dispose(&driver->word_pattern); + + git__free(driver); +} + +void git_diff_driver_update_options( + uint32_t *option_flags, git_diff_driver *driver) +{ + if ((*option_flags & FORCE_DIFFABLE) == 0) + *option_flags |= driver->binary_flags; + + *option_flags |= driver->other_flags; +} + +int git_diff_driver_content_is_binary( + git_diff_driver *driver, const char *content, size_t content_len) +{ + git_str search = GIT_STR_INIT; + + GIT_UNUSED(driver); + + git_str_attach_notowned(&search, content, + min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL)); + + /* TODO: provide encoding / binary detection callbacks that can + * be UTF-8 aware, etc. For now, instead of trying to be smart, + * let's just use the simple NUL-byte detection that core git uses. + */ + + /* previously was: if (git_str_is_binary(&search)) */ + if (git_str_contains_nul(&search)) + return 1; + + return 0; +} + +static int diff_context_line__simple( + git_diff_driver *driver, git_str *line) +{ + char firstch = line->ptr[0]; + GIT_UNUSED(driver); + return (git__isalpha(firstch) || firstch == '_' || firstch == '$'); +} + +static int diff_context_line__pattern_match( + git_diff_driver *driver, git_str *line) +{ + size_t i, maxi = git_array_size(driver->fn_patterns); + git_regmatch pmatch[2]; + + for (i = 0; i < maxi; ++i) { + git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i); + + 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].start >= 0) ? 1 : 0; + git_str_consume(line, git_str_cstr(line) + pmatch[i].start); + git_str_truncate(line, pmatch[i].end - pmatch[i].start); + git_str_rtrim(line); + + return true; + } + } + + return false; +} + +static long diff_context_find( + const char *line, + long line_len, + char *out, + long out_size, + void *payload) +{ + git_diff_find_context_payload *ctxt = payload; + + if (git_str_set(&ctxt->line, line, (size_t)line_len) < 0) + return -1; + git_str_rtrim(&ctxt->line); + + if (!ctxt->line.size) + return -1; + + if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line)) + return -1; + + if (out_size > (long)ctxt->line.size) + out_size = (long)ctxt->line.size; + memcpy(out, ctxt->line.ptr, (size_t)out_size); + + return out_size; +} + +void git_diff_find_context_init( + git_diff_find_context_fn *findfn_out, + git_diff_find_context_payload *payload_out, + git_diff_driver *driver) +{ + *findfn_out = driver ? diff_context_find : NULL; + + memset(payload_out, 0, sizeof(*payload_out)); + if (driver) { + payload_out->driver = driver; + payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ? + diff_context_line__pattern_match : diff_context_line__simple; + git_str_init(&payload_out->line, 0); + } +} + +void git_diff_find_context_clear(git_diff_find_context_payload *payload) +{ + if (payload) { + git_str_dispose(&payload->line); + payload->driver = NULL; + } +} diff --git a/src/libgit2/diff_driver.h b/src/libgit2/diff_driver.h new file mode 100644 index 000000000..03711e89e --- /dev/null +++ b/src/libgit2/diff_driver.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_diff_driver_h__ +#define INCLUDE_diff_driver_h__ + +#include "common.h" + +#include "attr_file.h" +#include "str.h" + +typedef struct git_diff_driver_registry git_diff_driver_registry; + +git_diff_driver_registry *git_diff_driver_registry_new(void); +void git_diff_driver_registry_free(git_diff_driver_registry *); + +typedef struct git_diff_driver git_diff_driver; + +int git_diff_driver_lookup(git_diff_driver **, git_repository *, + git_attr_session *attrsession, const char *); +void git_diff_driver_free(git_diff_driver *); + +/* diff option flags to force off and on for this driver */ +void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *); + +/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */ +int git_diff_driver_content_is_binary( + git_diff_driver *, const char *content, size_t content_len); + +typedef long (*git_diff_find_context_fn)( + const char *, long, char *, long, void *); + +typedef int (*git_diff_find_context_line)( + git_diff_driver *, git_str *); + +typedef struct { + git_diff_driver *driver; + git_diff_find_context_line match_line; + git_str line; +} git_diff_find_context_payload; + +void git_diff_find_context_init( + git_diff_find_context_fn *findfn_out, + git_diff_find_context_payload *payload_out, + git_diff_driver *driver); + +void git_diff_find_context_clear(git_diff_find_context_payload *); + +#endif diff --git a/src/libgit2/diff_file.c b/src/libgit2/diff_file.c new file mode 100644 index 000000000..c7e9fbeee --- /dev/null +++ b/src/libgit2/diff_file.c @@ -0,0 +1,481 @@ +/* + * 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 "diff_file.h" + +#include "git2/blob.h" +#include "git2/submodule.h" +#include "diff.h" +#include "diff_generate.h" +#include "odb.h" +#include "futils.h" +#include "filter.h" + +#define DIFF_MAX_FILESIZE 0x20000000 + +static bool diff_file_content_binary_by_size(git_diff_file_content *fc) +{ + /* if we have diff opts, check max_size vs file size */ + if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && + fc->opts_max_size > 0 && + fc->file->size > fc->opts_max_size) + fc->file->flags |= GIT_DIFF_FLAG_BINARY; + + return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0); +} + +static void diff_file_content_binary_by_content(git_diff_file_content *fc) +{ + if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) + return; + + switch (git_diff_driver_content_is_binary( + fc->driver, fc->map.data, fc->map.len)) { + case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break; + case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break; + default: break; + } +} + +static int diff_file_content_init_common( + git_diff_file_content *fc, const git_diff_options *opts) +{ + fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL; + + if (opts && opts->max_size >= 0) + fc->opts_max_size = opts->max_size ? + opts->max_size : DIFF_MAX_FILESIZE; + + if (fc->src == GIT_ITERATOR_EMPTY) + fc->src = GIT_ITERATOR_TREE; + + if (!fc->driver && + git_diff_driver_lookup(&fc->driver, fc->repo, + NULL, fc->file->path) < 0) + return -1; + + /* give driver a chance to modify options */ + git_diff_driver_update_options(&fc->opts_flags, fc->driver); + + /* make sure file is conceivable mmap-able */ + 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) { + fc->file->flags &= ~GIT_DIFF_FLAG_BINARY; + fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; + } + /* check if user is forcing binary diff the file */ + else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { + fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY; + fc->file->flags |= GIT_DIFF_FLAG_BINARY; + } + + diff_file_content_binary_by_size(fc); + + if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) { + fc->flags |= GIT_DIFF_FLAG__LOADED; + fc->map.len = 0; + fc->map.data = ""; + } + + if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) + diff_file_content_binary_by_content(fc); + + return 0; +} + +int git_diff_file_content__init_from_diff( + git_diff_file_content *fc, + git_diff *diff, + git_diff_delta *delta, + bool use_old) +{ + bool has_data = true; + + memset(fc, 0, sizeof(*fc)); + fc->repo = diff->repo; + fc->file = use_old ? &delta->old_file : &delta->new_file; + fc->src = use_old ? diff->old_src : diff->new_src; + + if (git_diff_driver_lookup(&fc->driver, fc->repo, + &diff->attrsession, fc->file->path) < 0) + return -1; + + switch (delta->status) { + case GIT_DELTA_ADDED: + has_data = !use_old; break; + case GIT_DELTA_DELETED: + has_data = use_old; break; + case GIT_DELTA_UNTRACKED: + has_data = !use_old && + (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; + break; + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_MODIFIED: + case GIT_DELTA_COPIED: + case GIT_DELTA_RENAMED: + break; + default: + has_data = false; + break; + } + + if (!has_data) + fc->flags |= GIT_DIFF_FLAG__NO_DATA; + + return diff_file_content_init_common(fc, &diff->opts); +} + +int git_diff_file_content__init_from_src( + git_diff_file_content *fc, + git_repository *repo, + const git_diff_options *opts, + const git_diff_file_content_src *src, + git_diff_file *as_file) +{ + memset(fc, 0, sizeof(*fc)); + fc->repo = repo; + fc->file = as_file; + + if (!src->blob && !src->buf) { + fc->flags |= GIT_DIFF_FLAG__NO_DATA; + } else { + fc->flags |= GIT_DIFF_FLAG__LOADED; + fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; + fc->file->mode = GIT_FILEMODE_BLOB; + + if (src->blob) { + git_blob_dup((git_blob **)&fc->blob, (git_blob *) src->blob); + fc->file->size = git_blob_rawsize(src->blob); + git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); + fc->file->id_abbrev = GIT_OID_HEXSZ; + + fc->map.len = (size_t)fc->file->size; + fc->map.data = (char *)git_blob_rawcontent(src->blob); + + fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; + } else { + int error; + if ((error = git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJECT_BLOB)) < 0) + return error; + fc->file->size = src->buflen; + fc->file->id_abbrev = GIT_OID_HEXSZ; + + fc->map.len = src->buflen; + fc->map.data = (char *)src->buf; + } + } + + return diff_file_content_init_common(fc, opts); +} + +static int diff_file_content_commit_to_str( + git_diff_file_content *fc, bool check_status) +{ + char oid[GIT_OID_HEXSZ+1]; + git_str content = GIT_STR_INIT; + const char *status = ""; + + if (check_status) { + int error = 0; + git_submodule *sm = NULL; + unsigned int sm_status = 0; + const git_oid *sm_head; + + if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) { + /* GIT_EEXISTS means a "submodule" that has not been git added */ + if (error == GIT_EEXISTS) { + git_error_clear(); + error = 0; + } + return error; + } + + if ((error = git_submodule_status(&sm_status, fc->repo, fc->file->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) { + git_submodule_free(sm); + return error; + } + + /* update OID if we didn't have it previously */ + if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 && + ((sm_head = git_submodule_wd_id(sm)) != NULL || + (sm_head = git_submodule_head_id(sm)) != NULL)) + { + git_oid_cpy(&fc->file->id, sm_head); + fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; + } + + if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) + status = "-dirty"; + + git_submodule_free(sm); + } + + git_oid_tostr(oid, sizeof(oid), &fc->file->id); + if (git_str_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) + return -1; + + fc->map.len = git_str_len(&content); + fc->map.data = git_str_detach(&content); + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + return 0; +} + +static int diff_file_content_load_blob( + git_diff_file_content *fc, + git_diff_options *opts) +{ + int error = 0; + git_odb_object *odb_obj = NULL; + + if (git_oid_is_zero(&fc->file->id)) + return 0; + + if (fc->file->mode == GIT_FILEMODE_COMMIT) + return diff_file_content_commit_to_str(fc, false); + + /* if we don't know size, try to peek at object header first */ + if (!fc->file->size) { + if ((error = git_diff_file__resolve_zero_size( + fc->file, &odb_obj, fc->repo)) < 0) + return error; + } + + if ((opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && + diff_file_content_binary_by_size(fc)) + return 0; + + if (odb_obj != NULL) { + error = git_object__from_odb_object( + (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJECT_BLOB); + git_odb_object_free(odb_obj); + } else { + error = git_blob_lookup( + (git_blob **)&fc->blob, fc->repo, &fc->file->id); + } + + if (!error) { + fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; + fc->map.data = (void *)git_blob_rawcontent(fc->blob); + fc->map.len = (size_t)git_blob_rawsize(fc->blob); + } + + return error; +} + +static int diff_file_content_load_workdir_symlink_fake( + git_diff_file_content *fc, git_str *path) +{ + git_str target = GIT_STR_INIT; + int error; + + if ((error = git_futils_readbuffer(&target, path->ptr)) < 0) + return error; + + fc->map.len = git_str_len(&target); + fc->map.data = git_str_detach(&target); + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + git_str_dispose(&target); + return error; +} + +static int diff_file_content_load_workdir_symlink( + git_diff_file_content *fc, git_str *path) +{ + ssize_t alloc_len, read_len; + int symlink_supported, error; + + if ((error = git_repository__configmap_lookup( + &symlink_supported, fc->repo, GIT_CONFIGMAP_SYMLINKS)) < 0) + return -1; + + if (!symlink_supported) + return diff_file_content_load_workdir_symlink_fake(fc, path); + + /* link path on disk could be UTF-16, so prepare a buffer that is + * big enough to handle some UTF-8 data expansion + */ + alloc_len = (ssize_t)(fc->file->size * 2) + 1; + + fc->map.data = git__calloc(alloc_len, sizeof(char)); + GIT_ERROR_CHECK_ALLOC(fc->map.data); + + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + read_len = p_readlink(git_str_cstr(path), fc->map.data, alloc_len); + if (read_len < 0) { + git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", fc->file->path); + return -1; + } + + fc->map.len = read_len; + return 0; +} + +static int diff_file_content_load_workdir_file( + git_diff_file_content *fc, + git_str *path, + git_diff_options *diff_opts) +{ + int error = 0; + git_filter_list *fl = NULL; + git_file fd = git_futils_open_ro(git_str_cstr(path)); + git_str raw = GIT_STR_INIT; + git_object_size_t new_file_size = 0; + + if (fd < 0) + return fd; + + error = git_futils_filesize(&new_file_size, fd); + + if (error < 0) + goto cleanup; + + if (!(fc->file->flags & GIT_DIFF_FLAG_VALID_SIZE)) { + fc->file->size = new_file_size; + fc->file->flags |= GIT_DIFF_FLAG_VALID_SIZE; + } else if (fc->file->size != new_file_size) { + git_error_set(GIT_ERROR_FILESYSTEM, "file changed before we could read it"); + error = -1; + goto cleanup; + } + + if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && + diff_file_content_binary_by_size(fc)) + goto cleanup; + + if ((error = git_filter_list_load( + &fl, fc->repo, NULL, fc->file->path, + GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)) < 0) + goto cleanup; + + /* if there are no filters, try to mmap the file */ + if (fl == NULL) { + if (!(error = git_futils_mmap_ro( + &fc->map, fd, 0, (size_t)fc->file->size))) { + fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; + goto cleanup; + } + + /* if mmap failed, fall through to try readbuffer below */ + git_error_clear(); + } + + if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { + git_str out = GIT_STR_INIT; + + error = git_filter_list__convert_buf(&out, fl, &raw); + + if (!error) { + fc->map.len = out.size; + fc->map.data = out.ptr; + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + } + } + +cleanup: + git_filter_list_free(fl); + p_close(fd); + + return error; +} + +static int diff_file_content_load_workdir( + git_diff_file_content *fc, + git_diff_options *diff_opts) +{ + int error = 0; + git_str path = GIT_STR_INIT; + + if (fc->file->mode == GIT_FILEMODE_COMMIT) + return diff_file_content_commit_to_str(fc, true); + + if (fc->file->mode == GIT_FILEMODE_TREE) + return 0; + + if (git_repository_workdir_path(&path, fc->repo, fc->file->path) < 0) + return -1; + + if (S_ISLNK(fc->file->mode)) + error = diff_file_content_load_workdir_symlink(fc, &path); + else + error = diff_file_content_load_workdir_file(fc, &path, diff_opts); + + /* once data is loaded, update OID if we didn't have it previously */ + if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) { + error = git_odb_hash( + &fc->file->id, fc->map.data, fc->map.len, GIT_OBJECT_BLOB); + fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; + } + + git_str_dispose(&path); + return error; +} + +int git_diff_file_content__load( + git_diff_file_content *fc, + git_diff_options *diff_opts) +{ + int error = 0; + + if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) + return 0; + + if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0 && + (diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0) + return 0; + + 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); + if (error) + return error; + + fc->flags |= GIT_DIFF_FLAG__LOADED; + + diff_file_content_binary_by_content(fc); + + return 0; +} + +void git_diff_file_content__unload(git_diff_file_content *fc) +{ + if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) + return; + + if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { + git__free(fc->map.data); + fc->map.data = ""; + fc->map.len = 0; + fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; + } + else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { + git_futils_mmap_free(&fc->map); + fc->map.data = ""; + fc->map.len = 0; + fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; + } + + if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { + git_blob_free((git_blob *)fc->blob); + fc->blob = NULL; + fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; + } + + fc->flags &= ~GIT_DIFF_FLAG__LOADED; +} + +void git_diff_file_content__clear(git_diff_file_content *fc) +{ + git_diff_file_content__unload(fc); + + /* for now, nothing else to do */ +} diff --git a/src/libgit2/diff_file.h b/src/libgit2/diff_file.h new file mode 100644 index 000000000..8d743e821 --- /dev/null +++ b/src/libgit2/diff_file.h @@ -0,0 +1,63 @@ +/* + * 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_diff_file_h__ +#define INCLUDE_diff_file_h__ + +#include "common.h" + +#include "diff.h" +#include "diff_driver.h" +#include "map.h" + +/* expanded information for one side of a delta */ +typedef struct { + git_repository *repo; + git_diff_file *file; + git_diff_driver *driver; + uint32_t flags; + uint32_t opts_flags; + git_object_size_t opts_max_size; + git_iterator_t src; + const git_blob *blob; + git_map map; +} git_diff_file_content; + +extern int git_diff_file_content__init_from_diff( + git_diff_file_content *fc, + git_diff *diff, + git_diff_delta *delta, + bool use_old); + +typedef struct { + const git_blob *blob; + const void *buf; + size_t buflen; + const char *as_path; +} git_diff_file_content_src; + +#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) } +#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) } + +extern int git_diff_file_content__init_from_src( + git_diff_file_content *fc, + git_repository *repo, + const git_diff_options *opts, + const git_diff_file_content_src *src, + git_diff_file *as_file); + +/* this loads the blob/file-on-disk as needed */ +extern int git_diff_file_content__load( + git_diff_file_content *fc, + git_diff_options *diff_opts); + +/* this releases the blob/file-in-memory */ +extern void git_diff_file_content__unload(git_diff_file_content *fc); + +/* this unloads and also releases any other resources */ +extern void git_diff_file_content__clear(git_diff_file_content *fc); + +#endif diff --git a/src/libgit2/diff_generate.c b/src/libgit2/diff_generate.c new file mode 100644 index 000000000..cfaefba66 --- /dev/null +++ b/src/libgit2/diff_generate.c @@ -0,0 +1,1724 @@ +/* + * 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 "diff_generate.h" + +#include "diff.h" +#include "patch_generate.h" +#include "futils.h" +#include "config.h" +#include "attr_file.h" +#include "filter.h" +#include "pathspec.h" +#include "index.h" +#include "odb.h" +#include "submodule.h" + +#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ + (((DIFF)->base.opts.flags & (FLAG)) != 0) +#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ + (((DIFF)->base.opts.flags & (FLAG)) == 0) +#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ + (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ + ((DIFF)->base.opts.flags & ~(FLAG)) + +typedef struct { + struct git_diff base; + + git_vector pathspec; + + uint32_t diffcaps; + bool index_updated; +} git_diff_generated; + +static git_diff_delta *diff_delta__alloc( + git_diff_generated *diff, + git_delta_t status, + const char *path) +{ + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + if (!delta) + return NULL; + + delta->old_file.path = git_pool_strdup(&diff->base.pool, path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + + delta->new_file.path = delta->old_file.path; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + switch (status) { + case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; + case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; + default: break; /* leave other status values alone */ + } + } + delta->status = status; + + return delta; +} + +static int diff_insert_delta( + git_diff_generated *diff, + git_diff_delta *delta, + const char *matched_pathspec) +{ + int error = 0; + + if (diff->base.opts.notify_cb) { + error = diff->base.opts.notify_cb( + &diff->base, delta, matched_pathspec, diff->base.opts.payload); + + if (error) { + git__free(delta); + + if (error > 0) /* positive value means to skip this delta */ + return 0; + else /* negative value means to cancel diff */ + return git_error_set_after_callback_function(error, "git_diff"); + } + } + + if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0) + git__free(delta); + + return error; +} + +static bool diff_pathspec_match( + const char **matched_pathspec, + git_diff_generated *diff, + const git_index_entry *entry) +{ + bool disable_pathspec_match = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); + + /* If we're disabling fnmatch, then the iterator has already applied + * the filters to the files for us and we don't have to do anything. + * However, this only applies to *files* - the iterator will include + * directories that we need to recurse into when not autoexpanding, + * so we still need to apply the pathspec match to directories. + */ + if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && + disable_pathspec_match) { + *matched_pathspec = entry->path; + return true; + } + + return git_pathspec__match( + &diff->pathspec, entry->path, disable_pathspec_match, + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), + matched_pathspec, NULL); +} + +static void diff_delta__flag_known_size(git_diff_file *file) +{ + /* + * If we don't know the ID, that can only come from the workdir + * iterator, which means we *do* know the file size. This is a + * leaky abstraction, but alas. Otherwise, we test against the + * empty blob id. + */ + if (file->size || + !(file->flags & GIT_DIFF_FLAG_VALID_ID) || + git_oid_equal(&file->id, &git_oid__empty_blob_sha1)) + file->flags |= GIT_DIFF_FLAG_VALID_SIZE; +} + +static void diff_delta__flag_known_sizes(git_diff_delta *delta) +{ + diff_delta__flag_known_size(&delta->old_file); + diff_delta__flag_known_size(&delta->new_file); +} + +static int diff_delta__from_one( + git_diff_generated *diff, + git_delta_t status, + const git_index_entry *oitem, + const git_index_entry *nitem) +{ + const git_index_entry *entry = nitem; + bool has_old = false; + git_diff_delta *delta; + const char *matched_pathspec; + + GIT_ASSERT_ARG((oitem != NULL) ^ (nitem != NULL)); + + if (oitem) { + entry = oitem; + has_old = true; + } + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) + has_old = !has_old; + + if ((entry->flags & GIT_INDEX_ENTRY_VALID) != 0) + return 0; + + if (status == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) + return 0; + + if (status == GIT_DELTA_UNTRACKED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) + return 0; + + if (status == GIT_DELTA_UNREADABLE && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) + return 0; + + if (!diff_pathspec_match(&matched_pathspec, diff, entry)) + return 0; + + delta = diff_delta__alloc(diff, status, entry->path); + GIT_ERROR_CHECK_ALLOC(delta); + + /* This fn is just for single-sided diffs */ + GIT_ASSERT(status != GIT_DELTA_MODIFIED); + delta->nfiles = 1; + + if (has_old) { + delta->old_file.mode = entry->mode; + delta->old_file.size = entry->file_size; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + git_oid_cpy(&delta->old_file.id, &entry->id); + delta->old_file.id_abbrev = GIT_OID_HEXSZ; + } else /* ADDED, IGNORED, UNTRACKED */ { + delta->new_file.mode = entry->mode; + delta->new_file.size = entry->file_size; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; + git_oid_cpy(&delta->new_file.id, &entry->id); + delta->new_file.id_abbrev = GIT_OID_HEXSZ; + } + + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + if (has_old || !git_oid_is_zero(&delta->new_file.id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + diff_delta__flag_known_sizes(delta); + + return diff_insert_delta(diff, delta, matched_pathspec); +} + +static int diff_delta__from_two( + git_diff_generated *diff, + git_delta_t status, + const git_index_entry *old_entry, + uint32_t old_mode, + const git_index_entry *new_entry, + uint32_t new_mode, + const git_oid *new_id, + const char *matched_pathspec) +{ + const git_oid *old_id = &old_entry->id; + git_diff_delta *delta; + const char *canonical_path = old_entry->path; + + if (status == GIT_DELTA_UNMODIFIED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) + return 0; + + if (!new_id) + new_id = &new_entry->id; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + uint32_t temp_mode = old_mode; + const git_index_entry *temp_entry = old_entry; + const git_oid *temp_id = old_id; + + old_entry = new_entry; + new_entry = temp_entry; + old_mode = new_mode; + new_mode = temp_mode; + old_id = new_id; + new_id = temp_id; + } + + delta = diff_delta__alloc(diff, status, canonical_path); + GIT_ERROR_CHECK_ALLOC(delta); + delta->nfiles = 2; + + if (!git_index_entry_is_conflict(old_entry)) { + delta->old_file.size = old_entry->file_size; + delta->old_file.mode = old_mode; + git_oid_cpy(&delta->old_file.id, old_id); + delta->old_file.id_abbrev = GIT_OID_HEXSZ; + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | + GIT_DIFF_FLAG_EXISTS; + } + + if (!git_index_entry_is_conflict(new_entry)) { + git_oid_cpy(&delta->new_file.id, new_id); + delta->new_file.id_abbrev = GIT_OID_HEXSZ; + delta->new_file.size = new_entry->file_size; + delta->new_file.mode = new_mode; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; + + if (!git_oid_is_zero(&new_entry->id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + } + + diff_delta__flag_known_sizes(delta); + + return diff_insert_delta(diff, delta, matched_pathspec); +} + +static git_diff_delta *diff_delta__last_for_item( + git_diff_generated *diff, + const git_index_entry *item) +{ + git_diff_delta *delta = git_vector_last(&diff->base.deltas); + if (!delta) + return NULL; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_DELETED: + if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_ADDED: + if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_UNTRACKED: + if (diff->base.strcomp(delta->new_file.path, item->path) == 0 && + git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_MODIFIED: + if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || + (delta->new_file.mode == item->mode && + git_oid__cmp(&delta->new_file.id, &item->id) == 0)) + return delta; + break; + default: + break; + } + + return NULL; +} + +static char *diff_strdup_prefix(git_pool *pool, const char *prefix) +{ + size_t len = strlen(prefix); + + /* append '/' at end if needed */ + if (len > 0 && prefix[len - 1] != '/') + return git_pool_strcat(pool, prefix, "/"); + else + return git_pool_strndup(pool, prefix, len + 1); +} + +GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) +{ + return delta->old_file.path ? + delta->old_file.path : delta->new_file.path; +} + +static int diff_delta_i2w_cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +static int diff_delta_i2w_casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta) +{ + uint32_t flags = opts ? opts->flags : 0; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; + + if (delta->status == GIT_DELTA_IGNORED && + (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNTRACKED && + (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNREADABLE && + (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) + return true; + + return false; +} + + +static const char *diff_mnemonic_prefix( + git_iterator_t type, bool left_side) +{ + const char *pfx = ""; + + switch (type) { + 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; + } + + /* note: without a deeper look at pathspecs, there is no easy way + * to get the (o)bject / (w)ork tree mnemonics working... + */ + + return pfx; +} + +static void diff_set_ignore_case(git_diff *diff, bool ignore_case) +{ + if (!ignore_case) { + diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; + + diff->strcomp = git__strcmp; + diff->strncomp = git__strncmp; + diff->pfxcomp = git__prefixcmp; + diff->entrycomp = git_diff__entry_cmp; + + git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); + } else { + diff->opts.flags |= GIT_DIFF_IGNORE_CASE; + + diff->strcomp = git__strcasecmp; + diff->strncomp = git__strncasecmp; + diff->pfxcomp = git__prefixcmp_icase; + diff->entrycomp = git_diff__entry_icmp; + + git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); + } + + git_vector_sort(&diff->deltas); +} + +static void diff_generated_free(git_diff *d) +{ + git_diff_generated *diff = (git_diff_generated *)d; + + git_attr_session__free(&diff->base.attrsession); + git_vector_free_deep(&diff->base.deltas); + + git_pathspec__vfree(&diff->pathspec); + git_pool_clear(&diff->base.pool); + + git__memzero(diff, sizeof(*diff)); + git__free(diff); +} + +static git_diff_generated *diff_generated_alloc( + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter) +{ + git_diff_generated *diff; + git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; + + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(old_iter, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(new_iter, NULL); + + if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) + return NULL; + + GIT_REFCOUNT_INC(&diff->base); + diff->base.type = GIT_DIFF_TYPE_GENERATED; + diff->base.repo = repo; + diff->base.old_src = old_iter->type; + diff->base.new_src = new_iter->type; + diff->base.patch_fn = git_patch_generated_from_diff; + diff->base.free_fn = diff_generated_free; + git_attr_session__init(&diff->base.attrsession, repo); + memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); + + if (git_pool_init(&diff->base.pool, 1) < 0 || + git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { + git_diff_free(&diff->base); + return NULL; + } + + /* Use case-insensitive compare if either iterator has + * the ignore_case bit set */ + diff_set_ignore_case( + &diff->base, + git_iterator_ignore_case(old_iter) || + git_iterator_ignore_case(new_iter)); + + return diff; +} + +static int diff_generated_apply_options( + git_diff_generated *diff, + const git_diff_options *opts) +{ + git_config *cfg = NULL; + git_repository *repo = diff->base.repo; + git_pool *pool = &diff->base.pool; + int val; + + if (opts) { + /* copy user options (except case sensitivity info from iterators) */ + bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); + memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); + DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); + + /* initialize pathspec from options */ + if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) + return -1; + } + + /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) + diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; + + /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) + diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + /* load config values that affect diff behavior */ + if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) + return val; + + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) + diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; + + 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__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) + diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; + + 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 */ + + /* If not given explicit `opts`, check `diff.xyz` configs */ + if (!opts) { + int context = git_config__get_int_force(cfg, "diff.context", 3); + diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; + + /* add other defaults here */ + } + + /* Reverse src info if diff is reversed */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + 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_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 */ + if (diff->base.opts.ignore_submodules <= 0) { + git_config_entry *entry; + git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); + + if (entry && git_submodule_parse_ignore( + &diff->base.opts.ignore_submodules, entry->value) < 0) + git_error_clear(); + git_config_entry_free(entry); + } + + /* if either prefix is not set, figure out appropriate value */ + if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { + const char *use_old = DIFF_OLD_PREFIX_DEFAULT; + const char *use_new = DIFF_NEW_PREFIX_DEFAULT; + + if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) + use_old = use_new = ""; + else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { + use_old = diff_mnemonic_prefix(diff->base.old_src, true); + use_new = diff_mnemonic_prefix(diff->base.new_src, false); + } + + if (!diff->base.opts.old_prefix) + diff->base.opts.old_prefix = use_old; + if (!diff->base.opts.new_prefix) + diff->base.opts.new_prefix = use_new; + } + + /* strdup prefix from pool so we're not dependent on external data */ + diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); + diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + const char *tmp_prefix = diff->base.opts.old_prefix; + diff->base.opts.old_prefix = diff->base.opts.new_prefix; + diff->base.opts.new_prefix = tmp_prefix; + } + + git_config_free(cfg); + + /* check strdup results for error */ + return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; +} + +int git_diff__oid_for_file( + git_oid *out, + git_diff *diff, + const char *path, + uint16_t mode, + git_object_size_t size) +{ + git_index_entry entry; + + if (size > UINT32_MAX) { + git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); + return -1; + } + + memset(&entry, 0, sizeof(entry)); + entry.mode = mode; + entry.file_size = (uint32_t)size; + entry.path = (char *)path; + + return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); +} + +int git_diff__oid_for_entry( + git_oid *out, + git_diff *d, + const git_index_entry *src, + uint16_t mode, + const git_oid *update_match) +{ + git_diff_generated *diff; + git_str full_path = GIT_STR_INIT; + git_index_entry entry = *src; + git_filter_list *fl = NULL; + int error = 0; + + GIT_ASSERT(d->type == GIT_DIFF_TYPE_GENERATED); + diff = (git_diff_generated *)d; + + memset(out, 0, sizeof(*out)); + + if (git_repository_workdir_path(&full_path, diff->base.repo, entry.path) < 0) + return -1; + + if (!mode) { + struct stat st; + + diff->base.perf.stat_calls++; + + if (p_stat(full_path.ptr, &st) < 0) { + error = git_fs_path_set_error(errno, entry.path, "stat"); + git_str_dispose(&full_path); + return error; + } + + git_index_entry__init_from_stat(&entry, + &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); + } + + /* calculate OID for file if possible */ + if (S_ISGITLINK(mode)) { + git_submodule *sm; + + if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { + const git_oid *sm_oid = git_submodule_wd_id(sm); + if (sm_oid) + git_oid_cpy(out, sm_oid); + git_submodule_free(sm); + } else { + /* if submodule lookup failed probably just in an intermediate + * state where some init hasn't happened, so ignore the error + */ + git_error_clear(); + } + } else if (S_ISLNK(mode)) { + error = git_odb__hashlink(out, full_path.ptr); + diff->base.perf.oid_calculations++; + } else if (!git__is_sizet(entry.file_size)) { + git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", + entry.path); + error = -1; + } else if (!(error = git_filter_list_load(&fl, + diff->base.repo, NULL, entry.path, + GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) + { + int fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) + error = fd; + else { + error = git_odb__hashfd_filtered( + out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl); + p_close(fd); + diff->base.perf.oid_calculations++; + } + + git_filter_list_free(fl); + } + + /* update index for entry if requested */ + if (!error && update_match && git_oid_equal(out, update_match)) { + git_index *idx; + git_index_entry updated_entry; + + memcpy(&updated_entry, &entry, sizeof(git_index_entry)); + updated_entry.mode = mode; + git_oid_cpy(&updated_entry.id, out); + + if (!(error = git_repository_index__weakptr(&idx, + diff->base.repo))) { + error = git_index_add(idx, &updated_entry); + diff->index_updated = true; + } + } + + git_str_dispose(&full_path); + return error; +} + +typedef struct { + git_repository *repo; + git_iterator *old_iter; + git_iterator *new_iter; + const git_index_entry *oitem; + const git_index_entry *nitem; + git_strmap *submodule_cache; + bool submodule_cache_initialized; +} diff_in_progress; + +#define MODE_BITS_MASK 0000777 + +static int maybe_modified_submodule( + git_delta_t *status, + git_oid *found_oid, + git_diff_generated *diff, + diff_in_progress *info) +{ + int error = 0; + git_submodule *sub; + unsigned int sm_status = 0; + git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; + git_strmap *submodule_cache = NULL; + + *status = GIT_DELTA_UNMODIFIED; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || + ign == GIT_SUBMODULE_IGNORE_ALL) + return 0; + + if (diff->base.repo->submodule_cache != NULL) { + submodule_cache = diff->base.repo->submodule_cache; + } else { + if (!info->submodule_cache_initialized) { + info->submodule_cache_initialized = true; + /* + * Try to cache the submodule information to avoid having to parse it for + * every submodule. It is okay if it fails, the cache will still be NULL + * and the submodules will be attempted to be looked up individually. + */ + git_submodule_cache_init(&info->submodule_cache, diff->base.repo); + } + submodule_cache = info->submodule_cache; + } + + if ((error = git_submodule__lookup_with_cache( + &sub, diff->base.repo, info->nitem->path, submodule_cache)) < 0) { + + /* GIT_EEXISTS means dir with .git in it was found - ignore it */ + if (error == GIT_EEXISTS) { + git_error_clear(); + error = 0; + } + return error; + } + + if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) + /* ignore it */; + else if ((error = git_submodule__status( + &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) + /* return error below */; + + /* check IS_WD_UNMODIFIED because this case is only used + * when the new side of the diff is the working directory + */ + else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) + *status = GIT_DELTA_MODIFIED; + + /* now that we have a HEAD OID, check if HEAD moved */ + else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && + !git_oid_equal(&info->oitem->id, found_oid)) + *status = GIT_DELTA_MODIFIED; + + git_submodule_free(sub); + return error; +} + +static int maybe_modified( + git_diff_generated *diff, + diff_in_progress *info) +{ + git_oid noid; + git_delta_t status = GIT_DELTA_MODIFIED; + const git_index_entry *oitem = info->oitem; + 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_WORKDIR); + bool modified_uncertain = false; + const char *matched_pathspec; + int error = 0; + + if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) + return 0; + + memset(&noid, 0, sizeof(noid)); + + /* on platforms with no symlinks, preserve mode of existing symlinks */ + if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && + !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) + nmode = omode; + + /* on platforms with no execmode, just preserve old mode */ + if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && + (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && + new_is_workdir) + nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); + + /* if one side is a conflict, mark the whole delta as conflicted */ + if (git_index_entry_is_conflict(oitem) || + git_index_entry_is_conflict(nitem)) { + status = GIT_DELTA_CONFLICTED; + + /* support "assume unchanged" (poorly, b/c we still stat everything) */ + } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) { + status = GIT_DELTA_UNMODIFIED; + + /* support "skip worktree" index bit */ + } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) { + status = GIT_DELTA_UNMODIFIED; + + /* if basic type of file changed, then split into delete and add */ + } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { + status = GIT_DELTA_TYPECHANGE; + } + + else if (nmode == GIT_FILEMODE_UNREADABLE) { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); + return error; + } + + else { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + return error; + } + + /* 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_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_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); + + status = GIT_DELTA_UNMODIFIED; + + if (S_ISGITLINK(nmode)) { + if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) + return error; + } + + /* if the stat data looks different, then mark modified - this just + * means that the OID will be recalculated below to confirm change + */ + else if (omode != nmode || oitem->file_size != nitem->file_size) { + status = GIT_DELTA_MODIFIED; + modified_uncertain = + (oitem->file_size <= 0 && nitem->file_size > 0); + } + else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || + (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || + oitem->ino != nitem->ino || + oitem->uid != nitem->uid || + oitem->gid != nitem->gid || + git_index_entry_newer_than_index(nitem, index)) + { + status = GIT_DELTA_MODIFIED; + modified_uncertain = true; + } + + /* if mode is GITLINK and submodules are ignored, then skip */ + } else if (S_ISGITLINK(nmode) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { + status = GIT_DELTA_UNMODIFIED; + } + + /* 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_is_zero(&nitem->id)) { + const git_oid *update_check = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? + &oitem->id : NULL; + + if ((error = git_diff__oid_for_entry( + &noid, &diff->base, nitem, nmode, update_check)) < 0) + return error; + + /* if oid matches, then mark unmodified (except submodules, where + * the filesystem content may be modified even if the oid still + * matches between the index and the workdir HEAD) + */ + if (omode == nmode && !S_ISGITLINK(omode) && + git_oid_equal(&oitem->id, &noid)) + status = GIT_DELTA_UNMODIFIED; + } + + /* If we want case changes, then break this into a delete of the old + * and an add of the new so that consumers can act accordingly (eg, + * checkout will update the case on disk.) + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && + strcmp(oitem->path, nitem->path) != 0) { + + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + + return error; + } + + return diff_delta__from_two( + diff, status, oitem, omode, nitem, nmode, + git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); +} + +static bool entry_is_prefixed( + git_diff_generated *diff, + const git_index_entry *item, + const git_index_entry *prefix_item) +{ + size_t pathlen; + + if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) + return false; + + pathlen = strlen(prefix_item->path); + + return (prefix_item->path[pathlen - 1] == '/' || + item->path[pathlen] == '\0' || + item->path[pathlen] == '/'); +} + +static int iterator_current( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance( + const git_index_entry **entry, + git_iterator *iterator) +{ + const git_index_entry *prev_entry = *entry; + int cmp, error; + + /* if we're looking for conflicts, we only want to report + * one conflict for each file, instead of all three sides. + * so if this entry is a conflict for this file, and the + * previous one was a conflict for the same file, skip it. + */ + while ((error = git_iterator_advance(entry, iterator)) == 0) { + if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || + !git_index_entry_is_conflict(prev_entry) || + !git_index_entry_is_conflict(*entry)) + break; + + cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? + strcasecmp(prev_entry->path, (*entry)->path) : + strcmp(prev_entry->path, (*entry)->path); + + if (cmp) + break; + } + + if (error == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_into( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_over( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iterator) +{ + int error = git_iterator_advance_over(entry, status, iterator); + + if (error == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int handle_unmatched_new_item( + git_diff_generated *diff, diff_in_progress *info) +{ + int error = 0; + const git_index_entry *nitem = info->nitem; + git_delta_t delta_type = GIT_DELTA_UNTRACKED; + bool contains_oitem; + + /* check if this is a prefix of the other side */ + contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); + + /* update delta_type if this item is conflicted */ + if (git_index_entry_is_conflict(nitem)) + delta_type = GIT_DELTA_CONFLICTED; + + /* update delta_type if this item is ignored */ + else if (git_iterator_current_is_ignored(info->new_iter)) + delta_type = GIT_DELTA_IGNORED; + + if (nitem->mode == GIT_FILEMODE_TREE) { + bool recurse_into_dir = contains_oitem; + + /* check if user requests recursion into this type of dir */ + recurse_into_dir = contains_oitem || + (delta_type == GIT_DELTA_UNTRACKED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || + (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); + + /* do not advance into directories that contain a .git file */ + if (recurse_into_dir && !contains_oitem) { + git_str *full = NULL; + if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) + return -1; + if (full && git_fs_path_contains(full, DOT_GIT)) { + /* TODO: warning if not a valid git repository */ + recurse_into_dir = false; + } + } + + /* still have to look into untracked directories to match core git - + * with no untracked files, directory is treated as ignored + */ + if (!recurse_into_dir && + delta_type == GIT_DELTA_UNTRACKED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) + { + git_diff_delta *last; + git_iterator_status_t untracked_state; + + /* attempt to insert record for this directory */ + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) + return error; + + /* if delta wasn't created (because of rules), just skip ahead */ + last = diff_delta__last_for_item(diff, nitem); + if (!last) + return iterator_advance(&info->nitem, info->new_iter); + + /* iterate into dir looking for an actual untracked file */ + if ((error = iterator_advance_over( + &info->nitem, &untracked_state, info->new_iter)) < 0) + return error; + + /* if we found nothing that matched our pathlist filter, exclude */ + if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { + git_vector_pop(&diff->base.deltas); + git__free(last); + } + + /* if we found nothing or just ignored items, update the record */ + if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || + untracked_state == GIT_ITERATOR_STATUS_EMPTY) { + last->status = GIT_DELTA_IGNORED; + + /* remove the record if we don't want ignored records */ + if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { + git_vector_pop(&diff->base.deltas); + git__free(last); + } + } + + return 0; + } + + /* try to advance into directory if necessary */ + if (recurse_into_dir) { + error = iterator_advance_into(&info->nitem, info->new_iter); + + /* if directory is empty, can't advance into it, so skip it */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = iterator_advance(&info->nitem, info->new_iter); + } + + return error; + } + } + + else if (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && + git_iterator_current_tree_is_ignored(info->new_iter)) + /* 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_WORKDIR) { + if (delta_type != GIT_DELTA_CONFLICTED) + delta_type = GIT_DELTA_ADDED; + } + + else if (nitem->mode == GIT_FILEMODE_COMMIT) { + /* ignore things that are not actual submodules */ + if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { + git_error_clear(); + delta_type = GIT_DELTA_IGNORED; + + /* if this contains a tracked item, treat as normal TREE */ + if (contains_oitem) { + error = iterator_advance_into(&info->nitem, info->new_iter); + if (error != GIT_ENOTFOUND) + return error; + + git_error_clear(); + return iterator_advance(&info->nitem, info->new_iter); + } + } + } + + else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) + delta_type = GIT_DELTA_UNTRACKED; + else + delta_type = GIT_DELTA_UNREADABLE; + } + + /* Actually create the record for this item if necessary */ + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) + return error; + + /* If user requested TYPECHANGE records, then check for that instead of + * just generating an ADDED/UNTRACKED record + */ + if (delta_type != GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && + contains_oitem) + { + /* this entry was prefixed with a tree - make TYPECHANGE */ + git_diff_delta *last = diff_delta__last_for_item(diff, nitem); + if (last) { + last->status = GIT_DELTA_TYPECHANGE; + last->old_file.mode = GIT_FILEMODE_TREE; + } + } + + return iterator_advance(&info->nitem, info->new_iter); +} + +static int handle_unmatched_old_item( + git_diff_generated *diff, diff_in_progress *info) +{ + git_delta_t delta_type = GIT_DELTA_DELETED; + int error; + + /* update delta_type if this item is conflicted */ + if (git_index_entry_is_conflict(info->oitem)) + delta_type = GIT_DELTA_CONFLICTED; + + if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) + return error; + + /* if we are generating TYPECHANGE records then check for that + * instead of just generating a DELETE record + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && + entry_is_prefixed(diff, info->nitem, info->oitem)) + { + /* this entry has become a tree! convert to TYPECHANGE */ + git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); + if (last) { + last->status = GIT_DELTA_TYPECHANGE; + last->new_file.mode = GIT_FILEMODE_TREE; + } + + /* If new_iter is a workdir iterator, then this situation + * will certainly be followed by a series of untracked items. + * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... + */ + if (S_ISDIR(info->nitem->mode) && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) + return iterator_advance(&info->nitem, info->new_iter); + } + + return iterator_advance(&info->oitem, info->old_iter); +} + +static int handle_matched_item( + git_diff_generated *diff, diff_in_progress *info) +{ + int error = 0; + + if ((error = maybe_modified(diff, info)) < 0) + return error; + + if (!(error = iterator_advance(&info->oitem, info->old_iter))) + error = iterator_advance(&info->nitem, info->new_iter); + + return error; +} + +int git_diff__from_iterators( + git_diff **out, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts) +{ + git_diff_generated *diff; + diff_in_progress info = {0}; + int error = 0; + + *out = NULL; + + diff = diff_generated_alloc(repo, old_iter, new_iter); + GIT_ERROR_CHECK_ALLOC(diff); + + info.repo = repo; + info.old_iter = old_iter; + info.new_iter = new_iter; + + /* make iterators have matching icase behavior */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { + if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || + (error = git_iterator_set_ignore_case(new_iter, true)) < 0) + goto cleanup; + } + + /* finish initialization */ + if ((error = diff_generated_apply_options(diff, opts)) < 0) + goto cleanup; + + if ((error = iterator_current(&info.oitem, old_iter)) < 0 || + (error = iterator_current(&info.nitem, new_iter)) < 0) + goto cleanup; + + /* run iterators building diffs */ + while (!error && (info.oitem || info.nitem)) { + int cmp; + + /* report progress */ + if (opts && opts->progress_cb) { + if ((error = opts->progress_cb(&diff->base, + info.oitem ? info.oitem->path : NULL, + info.nitem ? info.nitem->path : NULL, + opts->payload))) + break; + } + + cmp = info.oitem ? + (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; + + /* create DELETED records for old items not matched in new */ + if (cmp < 0) + error = handle_unmatched_old_item(diff, &info); + + /* create ADDED, TRACKED, or IGNORED records for new items not + * matched in old (and/or descend into directories as needed) + */ + else if (cmp > 0) + error = handle_unmatched_new_item(diff, &info); + + /* otherwise item paths match, so create MODIFIED record + * (or ADDED and DELETED pair if type changed) + */ + else + error = handle_matched_item(diff, &info); + } + + diff->base.perf.stat_calls += + old_iter->stat_calls + new_iter->stat_calls; + +cleanup: + if (!error) + *out = &diff->base; + else + git_diff_free(&diff->base); + if (info.submodule_cache) + git_submodule_cache_free(info.submodule_cache); + + return error; +} + +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, + git_repository *repo, + git_tree *old_tree, + git_tree *new_tree, + const git_diff_options *opts) +{ + 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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + /* for tree to tree diff, be case sensitive even if the index is + * currently case insensitive, unless the user explicitly asked + * for case insensitivity + */ + if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) + iflag = GIT_ITERATOR_IGNORE_CASE; + + 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; + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +static int diff_load_index(git_index **index, git_repository *repo) +{ + int error = git_repository_index__weakptr(index, repo); + + /* reload the repository index when user did not pass one in */ + if (!error && git_index_read(*index, false) < 0) + git_error_clear(); + + return error; +} + +int git_diff_tree_to_index( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + git_index *index, + const git_diff_options *opts) +{ + 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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if (!index && (error = diff_load_index(&index, repo)) < 0) + return error; + + index_ignore_case = index->ignore_case; + + 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 (index_ignore_case) + diff_set_ignore_case(diff, true); + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff_index_to_workdir( + git_diff **out, + git_repository *repo, + 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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if (!index && (error = diff_load_index(&index, repo)) < 0) + return error; + + 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; + + if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) + if ((error = git_index_write(index)) < 0) + goto out; + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff_tree_to_workdir( + git_diff **out, + git_repository *repo, + 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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + 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; + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff_tree_to_workdir_with_index( + git_diff **out, + git_repository *repo, + git_tree *tree, + const git_diff_options *opts) +{ + git_diff *d1 = NULL, *d2 = NULL; + git_index *index = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if ((error = diff_load_index(&index, repo)) < 0) + return error; + + if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && + !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) + error = git_diff_merge(d1, d2); + + git_diff_free(d2); + + if (error) { + git_diff_free(d1); + d1 = NULL; + } + + *out = d1; + return error; +} + +int git_diff_index_to_index( + git_diff **out, + git_repository *repo, + git_index *old_index, + git_index *new_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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(old_index); + GIT_ASSERT_ARG(new_index); + + *out = NULL; + + 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 (old_index->ignore_case || new_index->ignore_case) + diff_set_ignore_case(diff, true); + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff__paired_foreach( + git_diff *head2idx, + git_diff *idx2wd, + int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), + void *payload) +{ + int cmp, error = 0; + git_diff_delta *h2i, *i2w; + size_t i, j, i_max, j_max; + int (*strcomp)(const char *, const char *) = git__strcmp; + bool h2i_icase, i2w_icase, icase_mismatch; + + i_max = head2idx ? head2idx->deltas.length : 0; + j_max = idx2wd ? idx2wd->deltas.length : 0; + if (!i_max && !j_max) + return 0; + + /* At some point, tree-to-index diffs will probably never ignore case, + * even if that isn't true now. Index-to-workdir diffs may or may not + * ignore case, but the index filename for the idx2wd diff should + * still be using the canonical case-preserving name. + * + * Therefore the main thing we need to do here is make sure the diffs + * are traversed in a compatible order. To do this, we temporarily + * resort a mismatched diff to get the order correct. + * + * In order to traverse renames in the index->workdir, we need to + * ensure that we compare the index name on both sides, so we + * always sort by the old name in the i2w list. + */ + h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); + i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); + + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); + + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); + git_vector_sort(&head2idx->deltas); + } + + if (i2w_icase && !icase_mismatch) { + strcomp = git__strcasecmp; + + git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp); + git_vector_sort(&idx2wd->deltas); + } else if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp); + git_vector_sort(&idx2wd->deltas); + } + + for (i = 0, j = 0; i < i_max || j < j_max; ) { + h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; + i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; + + cmp = !i2w ? -1 : !h2i ? 1 : + strcomp(h2i->new_file.path, i2w->old_file.path); + + if (cmp < 0) { + i++; i2w = NULL; + } else if (cmp > 0) { + j++; h2i = NULL; + } else { + i++; j++; + } + + if ((error = cb(h2i, i2w, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + /* restore case-insensitive delta sort */ + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); + git_vector_sort(&head2idx->deltas); + } + + /* restore idx2wd sort by new path */ + if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, + i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); + git_vector_sort(&idx2wd->deltas); + } + + return error; +} + +int git_diff__commit( + git_diff **out, + git_repository *repo, + const git_commit *commit, + const git_diff_options *opts) +{ + git_commit *parent = NULL; + git_diff *commit_diff = NULL; + git_tree *old_tree = NULL, *new_tree = NULL; + size_t parents; + int error = 0; + + *out = NULL; + + if ((parents = git_commit_parentcount(commit)) > 1) { + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + error = -1; + git_error_set(GIT_ERROR_INVALID, "commit %s is a merge commit", + git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); + goto on_error; + } + + if (parents > 0) + if ((error = git_commit_parent(&parent, commit, 0)) < 0 || + (error = git_commit_tree(&old_tree, parent)) < 0) + goto on_error; + + if ((error = git_commit_tree(&new_tree, commit)) < 0 || + (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) + goto on_error; + + *out = commit_diff; + +on_error: + git_tree_free(new_tree); + git_tree_free(old_tree); + git_commit_free(parent); + + return error; +} + diff --git a/src/libgit2/diff_generate.h b/src/libgit2/diff_generate.h new file mode 100644 index 000000000..b782f29c6 --- /dev/null +++ b/src/libgit2/diff_generate.h @@ -0,0 +1,130 @@ +/* + * 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_diff_generate_h__ +#define INCLUDE_diff_generate_h__ + +#include "common.h" + +#include "diff.h" +#include "pool.h" +#include "index.h" + +enum { + GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ + GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ + GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ + GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ + GIT_DIFFCAPS_USE_DEV = (1 << 4) /* use st_dev? */ +}; + +#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) +#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA) + +enum { + GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */ + GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ + GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ + GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ + GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */ + GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */ + + GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */ + GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */ + GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18), + GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19), + GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20) +}; + +#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF) + +#define GIT_DIFF__VERBOSE (1 << 30) + +extern void git_diff_addref(git_diff *diff); + +extern bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta); + +extern int git_diff__from_iterators( + git_diff **diff_ptr, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts); + +extern int git_diff__commit( + git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); + +extern int git_diff__paired_foreach( + git_diff *idx2head, + git_diff *wd2idx, + int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), + void *payload); + +/* Merge two `git_diff`s according to the callback given by `cb`. */ + +typedef git_diff_delta *(*git_diff__merge_cb)( + const git_diff_delta *left, + const git_diff_delta *right, + git_pool *pool); + +extern int git_diff__merge( + git_diff *onto, const git_diff *from, git_diff__merge_cb cb); + +extern git_diff_delta *git_diff__merge_like_cgit( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool); + +/* Duplicate a `git_diff_delta` out of the `git_pool` */ +extern git_diff_delta *git_diff__delta_dup( + const git_diff_delta *d, git_pool *pool); + +extern int git_diff__oid_for_file( + git_oid *out, + git_diff *diff, + const char *path, + uint16_t mode, + git_object_size_t size); + +extern int git_diff__oid_for_entry( + git_oid *out, + git_diff *diff, + const git_index_entry *src, + uint16_t mode, + const git_oid *update_match); + +/* + * Sometimes a git_diff_file will have a zero size; this attempts to + * fill in the size without loading the blob if possible. If that is + * not possible, then it will return the git_odb_object that had to be + * loaded and the caller can use it or dispose of it as needed. + */ +GIT_INLINE(int) git_diff_file__resolve_zero_size( + git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) +{ + int error; + git_odb *odb; + size_t len; + git_object_t type; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + error = git_odb__read_header_or_object( + odb_obj, &len, &type, odb, &file->id); + + git_odb_free(odb); + + if (!error) { + file->size = (git_object_size_t)len; + file->flags |= GIT_DIFF_FLAG_VALID_SIZE; + } + + return error; +} + +#endif diff --git a/src/libgit2/diff_parse.c b/src/libgit2/diff_parse.c new file mode 100644 index 000000000..75e41a544 --- /dev/null +++ b/src/libgit2/diff_parse.c @@ -0,0 +1,107 @@ +/* + * 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 "diff_parse.h" + +#include "diff.h" +#include "patch.h" +#include "patch_parse.h" + +static void diff_parsed_free(git_diff *d) +{ + git_diff_parsed *diff = (git_diff_parsed *)d; + git_patch *patch; + size_t i; + + git_vector_foreach(&diff->patches, i, patch) + git_patch_free(patch); + + git_vector_free(&diff->patches); + + git_vector_free(&diff->base.deltas); + git_pool_clear(&diff->base.pool); + + git__memzero(diff, sizeof(*diff)); + git__free(diff); +} + +static git_diff_parsed *diff_parsed_alloc(void) +{ + git_diff_parsed *diff; + + if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL) + return NULL; + + GIT_REFCOUNT_INC(&diff->base); + diff->base.type = GIT_DIFF_TYPE_PARSED; + diff->base.strcomp = git__strcmp; + diff->base.strncomp = git__strncmp; + diff->base.pfxcomp = git__prefixcmp; + diff->base.entrycomp = git_diff__entry_cmp; + diff->base.patch_fn = git_patch_parsed_from_diff; + diff->base.free_fn = diff_parsed_free; + + if (git_diff_options_init(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { + git__free(diff); + return NULL; + } + + diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE; + + if (git_pool_init(&diff->base.pool, 1) < 0 || + git_vector_init(&diff->patches, 0, NULL) < 0 || + git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { + git_diff_free(&diff->base); + return NULL; + } + + git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp); + + return diff; +} + +int git_diff_from_buffer( + git_diff **out, + const char *content, + size_t content_len) +{ + git_diff_parsed *diff; + git_patch *patch; + git_patch_parse_ctx *ctx = NULL; + int error = 0; + + *out = NULL; + + diff = diff_parsed_alloc(); + GIT_ERROR_CHECK_ALLOC(diff); + + ctx = git_patch_parse_ctx_init(content, content_len, NULL); + GIT_ERROR_CHECK_ALLOC(ctx); + + while (ctx->parse_ctx.remain_len) { + if ((error = git_patch_parse(&patch, ctx)) < 0) + break; + + git_vector_insert(&diff->patches, patch); + git_vector_insert(&diff->base.deltas, patch->delta); + } + + if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) { + git_error_clear(); + error = 0; + } + + git_patch_parse_ctx_free(ctx); + + if (error < 0) + git_diff_free(&diff->base); + else + *out = &diff->base; + + return error; +} + diff --git a/src/libgit2/diff_parse.h b/src/libgit2/diff_parse.h new file mode 100644 index 000000000..876782128 --- /dev/null +++ b/src/libgit2/diff_parse.h @@ -0,0 +1,20 @@ +/* + * 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_diff_parse_h__ +#define INCLUDE_diff_parse_h__ + +#include "common.h" + +#include "diff.h" + +typedef struct { + struct git_diff base; + + git_vector patches; +} git_diff_parsed; + +#endif diff --git a/src/libgit2/diff_print.c b/src/libgit2/diff_print.c new file mode 100644 index 000000000..6c5a2cdc8 --- /dev/null +++ b/src/libgit2/diff_print.c @@ -0,0 +1,830 @@ +/* + * 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 "buf.h" +#include "diff.h" +#include "diff_file.h" +#include "patch_generate.h" +#include "futils.h" +#include "zstream.h" +#include "blob.h" +#include "delta.h" +#include "git2/sys/diff.h" + +typedef struct { + git_diff_format_t format; + git_diff_line_cb print_cb; + void *payload; + + git_str *buf; + git_diff_line line; + + const char *old_prefix; + const char *new_prefix; + uint32_t flags; + int id_strlen; + + int (*strcomp)(const char *, const char *); +} diff_print_info; + +static int diff_print_info_init__common( + diff_print_info *pi, + git_str *out, + git_repository *repo, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) +{ + pi->format = format; + pi->print_cb = cb; + pi->payload = payload; + pi->buf = out; + + if (!pi->id_strlen) { + if (!repo) + pi->id_strlen = GIT_ABBREV_DEFAULT; + else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) + return -1; + } + + if (pi->id_strlen > GIT_OID_HEXSZ) + pi->id_strlen = GIT_OID_HEXSZ; + + memset(&pi->line, 0, sizeof(pi->line)); + pi->line.old_lineno = -1; + pi->line.new_lineno = -1; + pi->line.num_lines = 1; + + return 0; +} + +static int diff_print_info_init_fromdiff( + diff_print_info *pi, + git_str *out, + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) +{ + git_repository *repo = diff ? diff->repo : NULL; + + memset(pi, 0, sizeof(diff_print_info)); + + if (diff) { + pi->flags = diff->opts.flags; + pi->id_strlen = diff->opts.id_abbrev; + pi->old_prefix = diff->opts.old_prefix; + pi->new_prefix = diff->opts.new_prefix; + + pi->strcomp = diff->strcomp; + } + + return diff_print_info_init__common(pi, out, repo, format, cb, payload); +} + +static int diff_print_info_init_frompatch( + diff_print_info *pi, + git_str *out, + git_patch *patch, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) +{ + GIT_ASSERT_ARG(patch); + + memset(pi, 0, sizeof(diff_print_info)); + + pi->flags = patch->diff_opts.flags; + pi->id_strlen = patch->diff_opts.id_abbrev; + pi->old_prefix = patch->diff_opts.old_prefix; + pi->new_prefix = patch->diff_opts.new_prefix; + + return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload); +} + +static char diff_pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ + /* in git, modes are very regular, so we must have 0100755 mode */ + return '*'; + else + return ' '; +} + +char git_diff_status_char(git_delta_t status) +{ + char code; + + switch (status) { + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + case GIT_DELTA_TYPECHANGE: code = 'T'; break; + case GIT_DELTA_UNREADABLE: code = 'X'; break; + default: code = ' '; break; + } + + return code; +} + +static int diff_print_one_name_only( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_str *out = pi->buf; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && + delta->status == GIT_DELTA_UNMODIFIED) + return 0; + + git_str_clear(out); + git_str_puts(out, delta->new_file.path); + git_str_putc(out, '\n'); + if (git_str_oom(out)) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(out); + pi->line.content_len = git_str_len(out); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_one_name_status( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_str *out = pi->buf; + char old_suffix, new_suffix, code = git_diff_status_char(delta->status); + int(*strcomp)(const char *, const char *) = pi->strcomp ? + pi->strcomp : git__strcmp; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') + return 0; + + old_suffix = diff_pick_suffix(delta->old_file.mode); + new_suffix = diff_pick_suffix(delta->new_file.mode); + + git_str_clear(out); + + if (delta->old_file.path != delta->new_file.path && + strcomp(delta->old_file.path,delta->new_file.path) != 0) + git_str_printf(out, "%c\t%s%c %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); + else if (delta->old_file.mode != delta->new_file.mode && + delta->old_file.mode != 0 && delta->new_file.mode != 0) + git_str_printf(out, "%c\t%s%c %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); + else if (old_suffix != ' ') + git_str_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + else + git_str_printf(out, "%c\t%s\n", code, delta->old_file.path); + if (git_str_oom(out)) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(out); + pi->line.content_len = git_str_len(out); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_one_raw( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_str *out = pi->buf; + int id_abbrev; + char code = git_diff_status_char(delta->status); + char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') + return 0; + + git_str_clear(out); + + id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev : + delta->new_file.id_abbrev; + + if (pi->id_strlen > id_abbrev) { + git_error_set(GIT_ERROR_PATCH, + "the patch input contains %d id characters (cannot print %d)", + id_abbrev, pi->id_strlen); + return -1; + } + + git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id); + git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id); + + git_str_printf( + out, (pi->id_strlen <= GIT_OID_HEXSZ) ? + ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c", + delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); + + if (delta->similarity > 0) + git_str_printf(out, "%03u", delta->similarity); + + if (delta->old_file.path != delta->new_file.path) + git_str_printf( + out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); + else + git_str_printf( + out, "\t%s\n", delta->old_file.path ? + delta->old_file.path : delta->new_file.path); + + if (git_str_oom(out)) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(out); + pi->line.content_len = git_str_len(out); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_modes( + git_str *out, const git_diff_delta *delta) +{ + git_str_printf(out, "old mode %o\n", delta->old_file.mode); + git_str_printf(out, "new mode %o\n", delta->new_file.mode); + + return git_str_oom(out) ? -1 : 0; +} + +static int diff_print_oid_range( + git_str *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]; + + if (delta->old_file.mode && + id_strlen > delta->old_file.id_abbrev) { + git_error_set(GIT_ERROR_PATCH, + "the patch input contains %d id characters (cannot print %d)", + delta->old_file.id_abbrev, id_strlen); + return -1; + } + + if ((delta->new_file.mode && + id_strlen > delta->new_file.id_abbrev)) { + git_error_set(GIT_ERROR_PATCH, + "the patch input contains %d id characters (cannot print %d)", + delta->new_file.id_abbrev, id_strlen); + return -1; + } + + git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id); + git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); + + if (delta->old_file.mode == delta->new_file.mode) { + if (print_index) + git_str_printf(out, "index %s..%s %o\n", + start_oid, end_oid, delta->old_file.mode); + } else { + if (delta->old_file.mode == 0) + git_str_printf(out, "new file mode %o\n", delta->new_file.mode); + else if (delta->new_file.mode == 0) + git_str_printf(out, "deleted file mode %o\n", delta->old_file.mode); + else + diff_print_modes(out, delta); + + if (print_index) + git_str_printf(out, "index %s..%s\n", start_oid, end_oid); + } + + return git_str_oom(out) ? -1 : 0; +} + +static int diff_delta_format_path( + git_str *out, const char *prefix, const char *filename) +{ + if (!filename) { + /* don't prefix "/dev/null" */ + return git_str_puts(out, "/dev/null"); + } + + if (git_str_joinpath(out, prefix, filename) < 0) + return -1; + + return git_str_quote(out); +} + +static int diff_delta_format_with_paths( + git_str *out, + const git_diff_delta *delta, + const char *template, + const char *oldpath, + const char *newpath) +{ + if (git_oid_is_zero(&delta->old_file.id)) + oldpath = "/dev/null"; + + if (git_oid_is_zero(&delta->new_file.id)) + newpath = "/dev/null"; + + return git_str_printf(out, template, oldpath, newpath); +} + +static int diff_delta_format_similarity_header( + git_str *out, + const git_diff_delta *delta) +{ + git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; + const char *type; + int error = 0; + + if (delta->similarity > 100) { + git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity); + error = -1; + goto done; + } + + GIT_ASSERT(delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED); + if (delta->status == GIT_DELTA_RENAMED) + type = "rename"; + else + type = "copy"; + + if ((error = git_str_puts(&old_path, delta->old_file.path)) < 0 || + (error = git_str_puts(&new_path, delta->new_file.path)) < 0 || + (error = git_str_quote(&old_path)) < 0 || + (error = git_str_quote(&new_path)) < 0) + goto done; + + git_str_printf(out, + "similarity index %d%%\n" + "%s from %s\n" + "%s to %s\n", + delta->similarity, + type, old_path.ptr, + type, new_path.ptr); + + if (git_str_oom(out)) + error = -1; + +done: + git_str_dispose(&old_path); + git_str_dispose(&new_path); + + return error; +} + +static bool delta_is_unchanged(const git_diff_delta *delta) +{ + 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 || + delta->new_file.mode == GIT_FILEMODE_COMMIT) + return false; + + if (git_oid_equal(&delta->old_file.id, &delta->new_file.id)) + return true; + + return false; +} + +int git_diff_delta__format_file_header( + git_str *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int id_strlen, + bool print_index) +{ + git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; + bool unchanged = delta_is_unchanged(delta); + int error = 0; + + if (!oldpfx) + oldpfx = DIFF_OLD_PREFIX_DEFAULT; + if (!newpfx) + newpfx = DIFF_NEW_PREFIX_DEFAULT; + if (!id_strlen) + id_strlen = GIT_ABBREV_DEFAULT; + + if ((error = diff_delta_format_path( + &old_path, oldpfx, delta->old_file.path)) < 0 || + (error = diff_delta_format_path( + &new_path, newpfx, delta->new_file.path)) < 0) + goto done; + + git_str_clear(out); + + git_str_printf(out, "diff --git %s %s\n", + old_path.ptr, new_path.ptr); + + if (unchanged && delta->old_file.mode != delta->new_file.mode) + diff_print_modes(out, delta); + + if (delta->status == GIT_DELTA_RENAMED || + (delta->status == GIT_DELTA_COPIED && unchanged)) { + if ((error = diff_delta_format_similarity_header(out, delta)) < 0) + goto done; + } + + if (!unchanged) { + if ((error = diff_print_oid_range(out, delta, + id_strlen, print_index)) < 0) + goto done; + + if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) + diff_delta_format_with_paths(out, delta, + "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr); + } + + if (git_str_oom(out)) + error = -1; + +done: + git_str_dispose(&old_path); + git_str_dispose(&new_path); + + return error; +} + +static int format_binary( + diff_print_info *pi, + git_diff_binary_t type, + const char *data, + size_t datalen, + size_t inflatedlen) +{ + const char *typename = type == GIT_DIFF_BINARY_DELTA ? + "delta" : "literal"; + const char *scan, *end; + + git_str_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen); + pi->line.num_lines++; + + for (scan = data, end = data + datalen; scan < end; ) { + size_t chunk_len = end - scan; + if (chunk_len > 52) + chunk_len = 52; + + if (chunk_len <= 26) + git_str_putc(pi->buf, (char)chunk_len + 'A' - 1); + else + git_str_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); + + git_str_encode_base85(pi->buf, scan, chunk_len); + git_str_putc(pi->buf, '\n'); + + if (git_str_oom(pi->buf)) + return -1; + + scan += chunk_len; + pi->line.num_lines++; + } + git_str_putc(pi->buf, '\n'); + + if (git_str_oom(pi->buf)) + return -1; + + return 0; +} + +static int diff_print_patch_file_binary_noshow( + diff_print_info *pi, git_diff_delta *delta, + const char *old_pfx, const char *new_pfx) +{ + git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; + int error; + + if ((error = diff_delta_format_path(&old_path, old_pfx, delta->old_file.path)) < 0 || + (error = diff_delta_format_path(&new_path, new_pfx, delta->new_file.path)) < 0 || + (error = diff_delta_format_with_paths(pi->buf, delta, "Binary files %s and %s differ\n", + old_path.ptr, new_path.ptr)) < 0) + goto done; + + pi->line.num_lines = 1; + +done: + git_str_dispose(&old_path); + git_str_dispose(&new_path); + return error; +} + +static int diff_print_patch_file_binary( + diff_print_info *pi, git_diff_delta *delta, + const char *old_pfx, const char *new_pfx, + const git_diff_binary *binary) +{ + size_t pre_binary_size; + int error; + + if (delta->status == GIT_DELTA_UNMODIFIED) + return 0; + + if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data) + return diff_print_patch_file_binary_noshow( + pi, delta, old_pfx, new_pfx); + + pre_binary_size = pi->buf->size; + git_str_printf(pi->buf, "GIT binary patch\n"); + pi->line.num_lines++; + + if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, + binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || + (error = format_binary(pi, binary->old_file.type, binary->old_file.data, + binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { + if (error == GIT_EBUFS) { + git_error_clear(); + git_str_truncate(pi->buf, pre_binary_size); + + return diff_print_patch_file_binary_noshow( + pi, delta, old_pfx, new_pfx); + } + } + + pi->line.num_lines++; + return error; +} + +static int diff_print_patch_file( + const git_diff_delta *delta, float progress, void *data) +{ + int error; + diff_print_info *pi = data; + const char *oldpfx = + pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; + const char *newpfx = + pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; + + bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || + (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 : + delta->new_file.id_abbrev; + + GIT_UNUSED(progress); + + if (S_ISDIR(delta->new_file.mode) || + delta->status == GIT_DELTA_UNMODIFIED || + delta->status == GIT_DELTA_IGNORED || + delta->status == GIT_DELTA_UNREADABLE || + (delta->status == GIT_DELTA_UNTRACKED && + (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) + return 0; + + if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx, + id_strlen, print_index)) < 0) + return error; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(pi->buf); + pi->line.content_len = git_str_len(pi->buf); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_patch_binary( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *data) +{ + diff_print_info *pi = data; + const char *old_pfx = + pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; + const char *new_pfx = + pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; + int error; + + git_str_clear(pi->buf); + + if ((error = diff_print_patch_file_binary( + pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0) + return error; + + pi->line.origin = GIT_DIFF_LINE_BINARY; + pi->line.content = git_str_cstr(pi->buf); + pi->line.content_len = git_str_len(pi->buf); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_patch_hunk( + const git_diff_delta *d, + const git_diff_hunk *h, + void *data) +{ + diff_print_info *pi = data; + + if (S_ISDIR(d->new_file.mode)) + return 0; + + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; + pi->line.content = h->header; + pi->line.content_len = h->header_len; + + return pi->print_cb(d, h, &pi->line, pi->payload); +} + +static int diff_print_patch_line( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *data) +{ + diff_print_info *pi = data; + + if (S_ISDIR(delta->new_file.mode)) + return 0; + + return pi->print_cb(delta, hunk, line, pi->payload); +} + +/* print a git_diff to an output callback */ +int git_diff_print( + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb print_cb, + void *payload) +{ + int error; + git_str buf = GIT_STR_INIT; + diff_print_info pi; + git_diff_file_cb print_file = NULL; + git_diff_binary_cb print_binary = NULL; + git_diff_hunk_cb print_hunk = NULL; + git_diff_line_cb print_line = NULL; + + switch (format) { + case GIT_DIFF_FORMAT_PATCH: + print_file = diff_print_patch_file; + print_binary = diff_print_patch_binary; + 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; + case GIT_DIFF_FORMAT_RAW: + print_file = diff_print_one_raw; + break; + case GIT_DIFF_FORMAT_NAME_ONLY: + print_file = diff_print_one_name_only; + break; + case GIT_DIFF_FORMAT_NAME_STATUS: + print_file = diff_print_one_name_status; + break; + default: + git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format); + return -1; + } + + if ((error = diff_print_info_init_fromdiff(&pi, &buf, diff, format, print_cb, payload)) < 0) + goto out; + + if ((error = git_diff_foreach(diff, print_file, print_binary, print_hunk, print_line, &pi)) != 0) { + git_error_set_after_callback_function(error, "git_diff_print"); + goto out; + } + +out: + git_str_dispose(&buf); + return error; +} + +int git_diff_print_callback__to_buf( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + git_str *output = payload; + GIT_UNUSED(delta); GIT_UNUSED(hunk); + + if (!output) { + git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided"); + return -1; + } + + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION || + line->origin == GIT_DIFF_LINE_CONTEXT) + git_str_putc(output, line->origin); + + return git_str_put(output, line->content, line->content_len); +} + +int git_diff_print_callback__to_file_handle( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + FILE *fp = payload ? payload : stdout; + int error; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + + if (line->origin == GIT_DIFF_LINE_CONTEXT || + line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) { + while ((error = fputc(line->origin, fp)) == EINTR) + continue; + if (error) { + git_error_set(GIT_ERROR_OS, "could not write status"); + return -1; + } + } + + if (fwrite(line->content, line->content_len, 1, fp) != 1) { + git_error_set(GIT_ERROR_OS, "could not write line"); + return -1; + } + + return 0; +} + +/* print a git_diff to a git_str */ +int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format) +{ + git_str str = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_diff_print(diff, format, git_diff_print_callback__to_buf, &str)) < 0) + goto done; + + error = git_buf_fromstr(out, &str); + +done: + git_str_dispose(&str); + return error; +} + +/* print a git_patch to an output callback */ +int git_patch_print( + git_patch *patch, + git_diff_line_cb print_cb, + void *payload) +{ + git_str temp = GIT_STR_INIT; + diff_print_info pi; + int error; + + GIT_ASSERT_ARG(patch); + GIT_ASSERT_ARG(print_cb); + + if ((error = diff_print_info_init_frompatch(&pi, &temp, patch, + GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0) + goto out; + + if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary, + diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) { + git_error_set_after_callback_function(error, "git_patch_print"); + goto out; + } + +out: + git_str_dispose(&temp); + return error; +} + +/* print a git_patch to a git_str */ +int git_patch_to_buf(git_buf *out, git_patch *patch) +{ + GIT_BUF_WRAP_PRIVATE(out, git_patch__to_buf, patch); +} + +int git_patch__to_buf(git_str *out, git_patch *patch) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(patch); + + return git_patch_print(patch, git_diff_print_callback__to_buf, out); +} diff --git a/src/libgit2/diff_stats.c b/src/libgit2/diff_stats.c new file mode 100644 index 000000000..259939844 --- /dev/null +++ b/src/libgit2/diff_stats.c @@ -0,0 +1,376 @@ +/* + * 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 "diff_stats.h" + +#include "buf.h" +#include "common.h" +#include "vector.h" +#include "diff.h" +#include "patch_generate.h" + +#define DIFF_RENAME_FILE_SEPARATOR " => " +#define STATS_FULL_MIN_SCALE 7 + +typedef struct { + size_t insertions; + size_t deletions; +} diff_file_stats; + +struct git_diff_stats { + git_diff *diff; + diff_file_stats *filestats; + + size_t files_changed; + size_t insertions; + size_t deletions; + size_t renames; + + size_t max_name; + size_t max_filestat; + int max_digits; +}; + +static int digits_for_value(size_t val) +{ + int count = 1; + size_t placevalue = 10; + + while (val >= placevalue) { + ++count; + placevalue *= 10; + } + + return count; +} + +static int diff_file_stats_full_to_buf( + git_str *out, + const git_diff_delta *delta, + const diff_file_stats *filestat, + const git_diff_stats *stats, + size_t width) +{ + const char *old_path = NULL, *new_path = NULL, *adddel_path = NULL; + size_t padding; + git_object_size_t old_size, new_size; + + old_path = delta->old_file.path; + new_path = delta->new_file.path; + old_size = delta->old_file.size; + new_size = delta->new_file.size; + + if (old_path && new_path && strcmp(old_path, new_path) != 0) { + size_t common_dirlen; + int error; + + padding = stats->max_name - strlen(old_path) - strlen(new_path); + + if ((common_dirlen = git_fs_path_common_dirlen(old_path, new_path)) && + common_dirlen <= INT_MAX) { + error = git_str_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}", + (int) common_dirlen, old_path, + old_path + common_dirlen, + new_path + common_dirlen); + } else { + error = git_str_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s", + old_path, new_path); + } + + if (error < 0) + goto on_error; + } else { + adddel_path = new_path ? new_path : old_path; + if (git_str_printf(out, " %s", adddel_path) < 0) + goto on_error; + + padding = stats->max_name - strlen(adddel_path); + + if (stats->renames > 0) + padding += strlen(DIFF_RENAME_FILE_SEPARATOR); + } + + if (git_str_putcn(out, ' ', padding) < 0 || + git_str_puts(out, " | ") < 0) + goto on_error; + + if (delta->flags & GIT_DIFF_FLAG_BINARY) { + if (git_str_printf(out, + "Bin %" PRId64 " -> %" PRId64 " bytes", old_size, new_size) < 0) + goto on_error; + } + else { + if (git_str_printf(out, + "%*" PRIuZ, stats->max_digits, + filestat->insertions + filestat->deletions) < 0) + goto on_error; + + if (filestat->insertions || filestat->deletions) { + if (git_str_putc(out, ' ') < 0) + goto on_error; + + if (!width) { + if (git_str_putcn(out, '+', filestat->insertions) < 0 || + git_str_putcn(out, '-', filestat->deletions) < 0) + goto on_error; + } else { + size_t total = filestat->insertions + filestat->deletions; + size_t full = (total * width + stats->max_filestat / 2) / + stats->max_filestat; + size_t plus = full * filestat->insertions / total; + size_t minus = full - plus; + + if (git_str_putcn(out, '+', max(plus, 1)) < 0 || + git_str_putcn(out, '-', max(minus, 1)) < 0) + goto on_error; + } + } + } + + git_str_putc(out, '\n'); + +on_error: + return (git_str_oom(out) ? -1 : 0); +} + +static int diff_file_stats_number_to_buf( + git_str *out, + const git_diff_delta *delta, + const diff_file_stats *filestats) +{ + int error; + const char *path = delta->new_file.path; + + if (delta->flags & GIT_DIFF_FLAG_BINARY) + error = git_str_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path); + else + error = git_str_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", + filestats->insertions, filestats->deletions, path); + + return error; +} + +static int diff_file_stats_summary_to_buf( + git_str *out, + const git_diff_delta *delta) +{ + if (delta->old_file.mode != delta->new_file.mode) { + if (delta->old_file.mode == 0) { + git_str_printf(out, " create mode %06o %s\n", + delta->new_file.mode, delta->new_file.path); + } + else if (delta->new_file.mode == 0) { + git_str_printf(out, " delete mode %06o %s\n", + delta->old_file.mode, delta->old_file.path); + } + else { + git_str_printf(out, " mode change %06o => %06o %s\n", + delta->old_file.mode, delta->new_file.mode, delta->new_file.path); + } + } + + return 0; +} + +int git_diff_get_stats( + git_diff_stats **out, + git_diff *diff) +{ + size_t i, deltas; + size_t total_insertions = 0, total_deletions = 0; + git_diff_stats *stats = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + + stats = git__calloc(1, sizeof(git_diff_stats)); + GIT_ERROR_CHECK_ALLOC(stats); + + deltas = git_diff_num_deltas(diff); + + stats->filestats = git__calloc(deltas, sizeof(diff_file_stats)); + if (!stats->filestats) { + git__free(stats); + return -1; + } + + stats->diff = diff; + GIT_REFCOUNT_INC(diff); + + for (i = 0; i < deltas && !error; ++i) { + git_patch *patch = NULL; + size_t add = 0, remove = 0, namelen; + const git_diff_delta *delta; + + if ((error = git_patch_from_diff(&patch, diff, i)) < 0) + break; + + /* keep a count of renames because it will affect formatting */ + delta = patch->delta; + + /* TODO ugh */ + namelen = strlen(delta->new_file.path); + if (delta->old_file.path && strcmp(delta->old_file.path, delta->new_file.path) != 0) { + namelen += strlen(delta->old_file.path); + stats->renames++; + } + + /* and, of course, count the line stats */ + error = git_patch_line_stats(NULL, &add, &remove, patch); + + git_patch_free(patch); + + stats->filestats[i].insertions = add; + stats->filestats[i].deletions = remove; + + total_insertions += add; + total_deletions += remove; + + if (stats->max_name < namelen) + stats->max_name = namelen; + if (stats->max_filestat < add + remove) + stats->max_filestat = add + remove; + } + + stats->files_changed = deltas; + stats->insertions = total_insertions; + stats->deletions = total_deletions; + stats->max_digits = digits_for_value(stats->max_filestat + 1); + + if (error < 0) { + git_diff_stats_free(stats); + stats = NULL; + } + + *out = stats; + return error; +} + +size_t git_diff_stats_files_changed( + const git_diff_stats *stats) +{ + GIT_ASSERT_ARG(stats); + + return stats->files_changed; +} + +size_t git_diff_stats_insertions( + const git_diff_stats *stats) +{ + GIT_ASSERT_ARG(stats); + + return stats->insertions; +} + +size_t git_diff_stats_deletions( + const git_diff_stats *stats) +{ + GIT_ASSERT_ARG(stats); + + return stats->deletions; +} + +int git_diff_stats_to_buf( + git_buf *out, + const git_diff_stats *stats, + git_diff_stats_format_t format, + size_t width) +{ + GIT_BUF_WRAP_PRIVATE(out, git_diff__stats_to_buf, stats, format, width); +} + +int git_diff__stats_to_buf( + git_str *out, + const git_diff_stats *stats, + git_diff_stats_format_t format, + size_t width) +{ + int error = 0; + size_t i; + const git_diff_delta *delta; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(stats); + + if (format & GIT_DIFF_STATS_NUMBER) { + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = diff_file_stats_number_to_buf( + out, delta, &stats->filestats[i]); + if (error < 0) + return error; + } + } + + if (format & GIT_DIFF_STATS_FULL) { + if (width > 0) { + if (width > stats->max_name + stats->max_digits + 5) + width -= (stats->max_name + stats->max_digits + 5); + if (width < STATS_FULL_MIN_SCALE) + width = STATS_FULL_MIN_SCALE; + } + if (width > stats->max_filestat) + width = 0; + + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = diff_file_stats_full_to_buf( + out, delta, &stats->filestats[i], stats, width); + if (error < 0) + return error; + } + } + + if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) { + git_str_printf( + out, " %" PRIuZ " file%s changed", + stats->files_changed, stats->files_changed != 1 ? "s" : ""); + + if (stats->insertions || stats->deletions == 0) + git_str_printf( + out, ", %" PRIuZ " insertion%s(+)", + stats->insertions, stats->insertions != 1 ? "s" : ""); + + if (stats->deletions || stats->insertions == 0) + git_str_printf( + out, ", %" PRIuZ " deletion%s(-)", + stats->deletions, stats->deletions != 1 ? "s" : ""); + + git_str_putc(out, '\n'); + + if (git_str_oom(out)) + return -1; + } + + if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) { + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = diff_file_stats_summary_to_buf(out, delta); + if (error < 0) + return error; + } + } + + return error; +} + +void git_diff_stats_free(git_diff_stats *stats) +{ + if (stats == NULL) + return; + + git_diff_free(stats->diff); /* bumped refcount in constructor */ + git__free(stats->filestats); + git__free(stats); +} diff --git a/src/libgit2/diff_stats.h b/src/libgit2/diff_stats.h new file mode 100644 index 000000000..c71862b4e --- /dev/null +++ b/src/libgit2/diff_stats.h @@ -0,0 +1,18 @@ +/* + * 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_diff_stats_h__ +#define INCLUDE_diff_stats_h__ + +#include "common.h" + +int git_diff__stats_to_buf( + git_str *out, + const git_diff_stats *stats, + git_diff_stats_format_t format, + size_t width); + +#endif diff --git a/src/libgit2/diff_tform.c b/src/libgit2/diff_tform.c new file mode 100644 index 000000000..913d649b0 --- /dev/null +++ b/src/libgit2/diff_tform.c @@ -0,0 +1,1121 @@ +/* + * 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 "diff_tform.h" + +#include "git2/config.h" +#include "git2/blob.h" +#include "git2/sys/hashsig.h" + +#include "diff.h" +#include "diff_generate.h" +#include "fs_path.h" +#include "futils.h" +#include "config.h" + +git_diff_delta *git_diff__delta_dup( + const git_diff_delta *d, git_pool *pool) +{ + git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); + if (!delta) + return NULL; + + memcpy(delta, d, sizeof(git_diff_delta)); + GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); + + if (d->old_file.path != NULL) { + delta->old_file.path = git_pool_strdup(pool, d->old_file.path); + if (delta->old_file.path == NULL) + goto fail; + } + + if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) { + delta->new_file.path = git_pool_strdup(pool, d->new_file.path); + if (delta->new_file.path == NULL) + goto fail; + } else { + delta->new_file.path = delta->old_file.path; + } + + return delta; + +fail: + git__free(delta); + return NULL; +} + +git_diff_delta *git_diff__merge_like_cgit( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) +{ + git_diff_delta *dup; + + /* Emulate C git for merging two diffs (a la 'git diff '). + * + * When C git does a diff between the work dir and a tree, it actually + * diffs with the index but uses the workdir contents. This emulates + * those choices so we can emulate the type of diff. + * + * We have three file descriptions here, let's call them: + * f1 = a->old_file + * f2 = a->new_file AND b->old_file + * f3 = b->new_file + */ + + /* If one of the diffs is a conflict, just dup it */ + if (b->status == GIT_DELTA_CONFLICTED) + return git_diff__delta_dup(b, pool); + if (a->status == GIT_DELTA_CONFLICTED) + return git_diff__delta_dup(a, pool); + + /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ + if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) + return git_diff__delta_dup(a, pool); + + /* otherwise, base this diff on the 'b' diff */ + if ((dup = git_diff__delta_dup(b, pool)) == NULL) + return NULL; + + /* If 'a' status is uninteresting, then we're done */ + if (a->status == GIT_DELTA_UNMODIFIED || + a->status == GIT_DELTA_UNTRACKED || + a->status == GIT_DELTA_UNREADABLE) + return dup; + + GIT_ASSERT_WITH_RETVAL(b->status != GIT_DELTA_UNMODIFIED, NULL); + + /* A cgit exception is that the diff of a file that is only in the + * index (i.e. not in HEAD nor workdir) is given as empty. + */ + if (dup->status == GIT_DELTA_DELETED) { + if (a->status == GIT_DELTA_ADDED) { + dup->status = GIT_DELTA_UNMODIFIED; + dup->nfiles = 2; + } + /* else don't overwrite DELETE status */ + } else { + dup->status = a->status; + dup->nfiles = a->nfiles; + } + + git_oid_cpy(&dup->old_file.id, &a->old_file.id); + dup->old_file.mode = a->old_file.mode; + dup->old_file.size = a->old_file.size; + dup->old_file.flags = a->old_file.flags; + + return dup; +} + +int git_diff__merge( + git_diff *onto, const git_diff *from, git_diff__merge_cb cb) +{ + int error = 0; + git_pool onto_pool; + git_vector onto_new; + git_diff_delta *delta; + bool ignore_case, reversed; + unsigned int i, j; + + GIT_ASSERT_ARG(onto); + GIT_ASSERT_ARG(from); + + if (!from->deltas.length) + return 0; + + ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); + reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0); + + if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) || + reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) { + git_error_set(GIT_ERROR_INVALID, + "attempt to merge diffs created with conflicting options"); + return -1; + } + + if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || + git_pool_init(&onto_pool, 1) < 0) + return -1; + + for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { + git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); + const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); + int cmp = !f ? -1 : !o ? 1 : + STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); + + if (cmp < 0) { + delta = git_diff__delta_dup(o, &onto_pool); + i++; + } else if (cmp > 0) { + delta = git_diff__delta_dup(f, &onto_pool); + j++; + } else { + const git_diff_delta *left = reversed ? f : o; + const git_diff_delta *right = reversed ? o : f; + + delta = cb(left, right, &onto_pool); + i++; + j++; + } + + /* the ignore rules for the target may not match the source + * or the result of a merged delta could be skippable... + */ + if (delta && git_diff_delta__should_skip(&onto->opts, delta)) { + git__free(delta); + continue; + } + + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) + break; + } + + if (!error) { + git_vector_swap(&onto->deltas, &onto_new); + git_pool_swap(&onto->pool, &onto_pool); + + if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0) + onto->old_src = from->old_src; + else + onto->new_src = from->new_src; + + /* prefix strings also come from old pool, so recreate those.*/ + onto->opts.old_prefix = + git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); + onto->opts.new_prefix = + git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); + } + + git_vector_free_deep(&onto_new); + git_pool_clear(&onto_pool); + + return error; +} + +int git_diff_merge(git_diff *onto, const git_diff *from) +{ + return git_diff__merge(onto, from, git_diff__merge_like_cgit); +} + +int git_diff_find_similar__hashsig_for_file( + void **out, const git_diff_file *f, const char *path, void *p) +{ + git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; + + GIT_UNUSED(f); + return git_hashsig_create_fromfile((git_hashsig **)out, path, opt); +} + +int git_diff_find_similar__hashsig_for_buf( + void **out, const git_diff_file *f, const char *buf, size_t len, void *p) +{ + git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; + + GIT_UNUSED(f); + return git_hashsig_create((git_hashsig **)out, buf, len, opt); +} + +void git_diff_find_similar__hashsig_free(void *sig, void *payload) +{ + GIT_UNUSED(payload); + git_hashsig_free(sig); +} + +int git_diff_find_similar__calc_similarity( + int *score, void *siga, void *sigb, void *payload) +{ + int error; + + GIT_UNUSED(payload); + error = git_hashsig_compare(siga, sigb); + if (error < 0) + return error; + + *score = error; + return 0; +} + +#define DEFAULT_THRESHOLD 50 +#define DEFAULT_BREAK_REWRITE_THRESHOLD 60 +#define DEFAULT_RENAME_LIMIT 1000 + +static int normalize_find_opts( + git_diff *diff, + git_diff_find_options *opts, + const git_diff_find_options *given) +{ + git_config *cfg = NULL; + git_hashsig_option_t hashsig_opts; + + GIT_ERROR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options"); + + if (diff->repo != NULL && + git_repository_config__weakptr(&cfg, diff->repo) < 0) + return -1; + + if (given) + memcpy(opts, given, sizeof(*opts)); + + if (!given || + (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG) + { + if (cfg) { + char *rule = + git_config__get_string_force(cfg, "diff.renames", "true"); + int boolval; + + if (!git__parse_bool(&boolval, rule) && !boolval) + /* don't set FIND_RENAMES if bool value is false */; + else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy")) + opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + else + opts->flags |= GIT_DIFF_FIND_RENAMES; + + git__free(rule); + } else { + /* set default flag */ + opts->flags |= GIT_DIFF_FIND_RENAMES; + } + } + + /* some flags imply others */ + + if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) { + /* if we are only looking for exact matches, then don't turn + * MODIFIED items into ADD/DELETE pairs because it's too picky + */ + opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES); + + /* similarly, don't look for self-rewrites to split */ + opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + } + + if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES) + opts->flags |= GIT_DIFF_FIND_RENAMES; + + if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED) + opts->flags |= GIT_DIFF_FIND_COPIES; + + if (opts->flags & GIT_DIFF_BREAK_REWRITES) + opts->flags |= GIT_DIFF_FIND_REWRITES; + +#define USE_DEFAULT(X) ((X) == 0 || (X) > 100) + + if (USE_DEFAULT(opts->rename_threshold)) + opts->rename_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->rename_from_rewrite_threshold)) + opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->copy_threshold)) + opts->copy_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->break_rewrite_threshold)) + opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD; + +#undef USE_DEFAULT + + if (!opts->rename_limit) { + if (cfg) { + opts->rename_limit = git_config__get_int_force( + cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT); + } + + if (opts->rename_limit <= 0) + opts->rename_limit = DEFAULT_RENAME_LIMIT; + } + + /* assign the internal metric with whitespace flag as payload */ + if (!opts->metric) { + opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); + GIT_ERROR_CHECK_ALLOC(opts->metric); + + opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; + opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; + opts->metric->free_signature = git_diff_find_similar__hashsig_free; + opts->metric->similarity = git_diff_find_similar__calc_similarity; + + if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE) + hashsig_opts = GIT_HASHSIG_IGNORE_WHITESPACE; + else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE) + hashsig_opts = GIT_HASHSIG_NORMAL; + else + hashsig_opts = GIT_HASHSIG_SMART_WHITESPACE; + hashsig_opts |= GIT_HASHSIG_ALLOW_SMALL_FILES; + opts->metric->payload = (void *)hashsig_opts; + } + + return 0; +} + +static int insert_delete_side_of_split( + git_diff *diff, git_vector *onto, const git_diff_delta *delta) +{ + /* make new record for DELETED side of split */ + git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool); + GIT_ERROR_CHECK_ALLOC(deleted); + + deleted->status = GIT_DELTA_DELETED; + deleted->nfiles = 1; + memset(&deleted->new_file, 0, sizeof(deleted->new_file)); + deleted->new_file.path = deleted->old_file.path; + deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + return git_vector_insert(onto, deleted); +} + +static int apply_splits_and_deletes( + git_diff *diff, size_t expected_size, bool actually_split) +{ + git_vector onto = GIT_VECTOR_INIT; + size_t i; + git_diff_delta *delta; + + if (git_vector_init(&onto, expected_size, diff->deltas._cmp) < 0) + return -1; + + /* build new delta list without TO_DELETE and splitting TO_SPLIT */ + git_vector_foreach(&diff->deltas, i, delta) { + if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) + continue; + + if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) { + delta->similarity = 0; + + if (insert_delete_side_of_split(diff, &onto, delta) < 0) + goto on_error; + + if (diff->new_src == GIT_ITERATOR_WORKDIR) + delta->status = GIT_DELTA_UNTRACKED; + else + delta->status = GIT_DELTA_ADDED; + delta->nfiles = 1; + memset(&delta->old_file, 0, sizeof(delta->old_file)); + delta->old_file.path = delta->new_file.path; + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + } + + /* clean up delta before inserting into new list */ + GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); + + if (delta->status != GIT_DELTA_COPIED && + delta->status != GIT_DELTA_RENAMED && + (delta->status != GIT_DELTA_MODIFIED || actually_split)) + delta->similarity = 0; + + /* insert into new list */ + if (git_vector_insert(&onto, delta) < 0) + goto on_error; + } + + /* cannot return an error past this point */ + + /* free deltas from old list that didn't make it to the new one */ + git_vector_foreach(&diff->deltas, i, delta) { + if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) + git__free(delta); + } + + /* swap new delta list into place */ + git_vector_swap(&diff->deltas, &onto); + git_vector_free(&onto); + git_vector_sort(&diff->deltas); + + return 0; + +on_error: + git_vector_free_deep(&onto); + + return -1; +} + +GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) +{ + git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2); + return (idx & 1) ? &delta->new_file : &delta->old_file; +} + +typedef struct { + size_t idx; + git_iterator_t src; + git_repository *repo; + git_diff_file *file; + git_str data; + git_odb_object *odb_obj; + git_blob *blob; +} similarity_info; + +static int similarity_init( + similarity_info *info, git_diff *diff, size_t file_idx) +{ + info->idx = file_idx; + info->src = (file_idx & 1) ? diff->new_src : diff->old_src; + info->repo = diff->repo; + info->file = similarity_get_file(diff, file_idx); + info->odb_obj = NULL; + info->blob = NULL; + git_str_init(&info->data, 0); + + if ((info->file->flags & GIT_DIFF_FLAG_VALID_SIZE) || + info->src == GIT_ITERATOR_WORKDIR) + return 0; + + return git_diff_file__resolve_zero_size( + info->file, &info->odb_obj, info->repo); +} + +static int similarity_sig( + similarity_info *info, + const git_diff_find_options *opts, + void **cache) +{ + int error = 0; + git_diff_file *file = info->file; + + if (info->src == GIT_ITERATOR_WORKDIR) { + if ((error = git_repository_workdir_path( + &info->data, info->repo, file->path)) < 0) + return error; + + /* if path is not a regular file, just skip this item */ + if (!git_fs_path_isfile(info->data.ptr)) + return 0; + + /* TODO: apply wd-to-odb filters to file data if necessary */ + + error = opts->metric->file_signature( + &cache[info->idx], info->file, + info->data.ptr, opts->metric->payload); + } else { + /* if we didn't initially know the size, we might have an odb_obj + * around from earlier, so convert that, otherwise load the blob now + */ + if (info->odb_obj != NULL) + error = git_object__from_odb_object( + (git_object **)&info->blob, info->repo, + info->odb_obj, GIT_OBJECT_BLOB); + else + error = git_blob_lookup(&info->blob, info->repo, &file->id); + + if (error < 0) { + /* if lookup fails, just skip this item in similarity calc */ + git_error_clear(); + } else { + size_t sz; + + /* index size may not be actual blob size if filtered */ + if (file->size != git_blob_rawsize(info->blob)) + file->size = git_blob_rawsize(info->blob); + + sz = git__is_sizet(file->size) ? (size_t)file->size : (size_t)-1; + + error = opts->metric->buffer_signature( + &cache[info->idx], info->file, + git_blob_rawcontent(info->blob), sz, opts->metric->payload); + } + } + + return error; +} + +static void similarity_unload(similarity_info *info) +{ + if (info->odb_obj) + git_odb_object_free(info->odb_obj); + + if (info->blob) + git_blob_free(info->blob); + else + git_str_dispose(&info->data); +} + +#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) + +/* - score < 0 means files cannot be compared + * - score >= 100 means files are exact match + * - score == 0 means files are completely different + */ +static int similarity_measure( + int *score, + git_diff *diff, + const git_diff_find_options *opts, + void **cache, + size_t a_idx, + size_t b_idx) +{ + git_diff_file *a_file = similarity_get_file(diff, a_idx); + git_diff_file *b_file = similarity_get_file(diff, b_idx); + bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); + int error = 0; + similarity_info a_info, b_info; + + *score = -1; + + /* don't try to compare things that aren't files */ + if (!GIT_MODE_ISBLOB(a_file->mode) || !GIT_MODE_ISBLOB(b_file->mode)) + return 0; + + /* if exact match is requested, force calculation of missing OIDs now */ + if (exact_match) { + 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_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; + } + + /* check OID match as a quick test */ + if (git_oid__cmp(&a_file->id, &b_file->id) == 0) { + *score = 100; + return 0; + } + + /* don't calculate signatures if we are doing exact match */ + if (exact_match) { + *score = 0; + return 0; + } + + memset(&a_info, 0, sizeof(a_info)); + memset(&b_info, 0, sizeof(b_info)); + + /* set up similarity data (will try to update missing file sizes) */ + if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0) + return error; + if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0) + goto cleanup; + + /* check if file sizes are nowhere near each other */ + if (a_file->size > 127 && + b_file->size > 127 && + (a_file->size > (b_file->size << 3) || + b_file->size > (a_file->size << 3))) + goto cleanup; + + /* update signature cache if needed */ + if (!cache[a_idx]) { + if ((error = similarity_sig(&a_info, opts, cache)) < 0) + goto cleanup; + } + if (!cache[b_idx]) { + if ((error = similarity_sig(&b_info, opts, cache)) < 0) + goto cleanup; + } + + /* calculate similarity provided that the metric choose to process + * both the a and b files (some may not if file is too big, etc). + */ + if (cache[a_idx] && cache[b_idx]) + error = opts->metric->similarity( + score, cache[a_idx], cache[b_idx], opts->metric->payload); + +cleanup: + similarity_unload(&a_info); + similarity_unload(&b_info); + + return error; +} + +static int calc_self_similarity( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx, + void **cache) +{ + int error, similarity = -1; + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0) + return 0; + + error = similarity_measure( + &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1); + if (error < 0) + return error; + + if (similarity >= 0) { + delta->similarity = (uint16_t)similarity; + delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY; + } + + return 0; +} + +static bool is_rename_target( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx, + void **cache) +{ + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + /* skip things that aren't plain blobs */ + if (!GIT_MODE_ISBLOB(delta->new_file.mode)) + return false; + + /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as + * targets; maybe include UNTRACKED if requested. + */ + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_DELETED: + case GIT_DELTA_IGNORED: + case GIT_DELTA_CONFLICTED: + return false; + + case GIT_DELTA_MODIFIED: + if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && + !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) + return false; + + if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) + return false; + + if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && + delta->similarity < opts->break_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; + break; + } + if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && + delta->similarity < opts->rename_from_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; + break; + } + + return false; + + case GIT_DELTA_UNTRACKED: + if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) + return false; + break; + + default: /* all other status values should be checked */ + break; + } + + delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET; + return true; +} + +static bool is_rename_source( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx, + void **cache) +{ + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + /* skip things that aren't blobs */ + if (!GIT_MODE_ISBLOB(delta->old_file.mode)) + return false; + + switch (delta->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_IGNORED: + case GIT_DELTA_CONFLICTED: + return false; + + case GIT_DELTA_DELETED: + case GIT_DELTA_TYPECHANGE: + break; + + case GIT_DELTA_UNMODIFIED: + if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)) + return false; + if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED)) + delta->flags |= GIT_DIFF_FLAG__TO_DELETE; + break; + + default: /* MODIFIED, RENAMED, COPIED */ + /* if we're finding copies, this could be a source */ + if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES)) + break; + + /* otherwise, this is only a source if we can split it */ + if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && + !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) + return false; + + if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) + return false; + + if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && + delta->similarity < opts->break_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; + break; + } + + if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && + delta->similarity < opts->rename_from_rewrite_threshold) + break; + + return false; + } + + delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE; + return true; +} + +GIT_INLINE(bool) delta_is_split(git_diff_delta *delta) +{ + return (delta->status == GIT_DELTA_TYPECHANGE || + (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0); +} + +GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) +{ + return (delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_UNTRACKED || + delta->status == GIT_DELTA_UNREADABLE || + delta->status == GIT_DELTA_IGNORED); +} + +GIT_INLINE(void) delta_make_rename( + git_diff_delta *to, const git_diff_delta *from, uint16_t similarity) +{ + to->status = GIT_DELTA_RENAMED; + to->similarity = similarity; + to->nfiles = 2; + memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; +} + +typedef struct { + size_t idx; + uint16_t similarity; +} diff_find_match; + +int git_diff_find_similar( + git_diff *diff, + const git_diff_find_options *given_opts) +{ + size_t s, t; + int error = 0, result; + uint16_t similarity; + git_diff_delta *src, *tgt; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + size_t num_deltas, num_srcs = 0, num_tgts = 0; + size_t tried_srcs = 0, tried_tgts = 0; + size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; + size_t sigcache_size; + void **sigcache = NULL; /* cache of similarity metric file signatures */ + diff_find_match *tgt2src = NULL; + diff_find_match *src2tgt = NULL; + diff_find_match *tgt2src_copy = NULL; + diff_find_match *best_match; + git_diff_file swap; + + GIT_ASSERT_ARG(diff); + + if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) + return error; + + num_deltas = diff->deltas.length; + + /* TODO: maybe abort if deltas.length > rename_limit ??? */ + if (!num_deltas || !git__is_uint32(num_deltas)) + goto cleanup; + + /* No flags set; nothing to do */ + if ((opts.flags & GIT_DIFF_FIND_ALL) == 0) + goto cleanup; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&sigcache_size, num_deltas, 2); + sigcache = git__calloc(sigcache_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(sigcache); + + /* Label rename sources and targets + * + * This will also set self-similarity scores for MODIFIED files and + * mark them for splitting if break-rewrites is enabled + */ + git_vector_foreach(&diff->deltas, t, tgt) { + if (is_rename_source(diff, &opts, t, sigcache)) + ++num_srcs; + + if (is_rename_target(diff, &opts, t, sigcache)) + ++num_tgts; + + if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) + num_rewrites++; + } + + /* if there are no candidate srcs or tgts, we're done */ + if (!num_srcs || !num_tgts) + goto cleanup; + + src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); + GIT_ERROR_CHECK_ALLOC(src2tgt); + tgt2src = git__calloc(num_deltas, sizeof(diff_find_match)); + GIT_ERROR_CHECK_ALLOC(tgt2src); + + if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { + tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match)); + GIT_ERROR_CHECK_ALLOC(tgt2src_copy); + } + + /* + * Find best-fit matches for rename / copy candidates + */ + +find_best_matches: + tried_tgts = num_bumped = 0; + + git_vector_foreach(&diff->deltas, t, tgt) { + /* skip things that are not rename targets */ + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + continue; + + tried_srcs = 0; + + git_vector_foreach(&diff->deltas, s, src) { + /* skip things that are not rename sources */ + if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) + continue; + + /* calculate similarity for this pair and find best match */ + if (s == t) + result = -1; /* don't measure self-similarity here */ + else if ((error = similarity_measure( + &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) + goto cleanup; + + if (result < 0) + continue; + similarity = (uint16_t)result; + + /* is this a better rename? */ + if (tgt2src[t].similarity < similarity && + src2tgt[s].similarity < similarity) + { + /* eject old mapping */ + if (src2tgt[s].similarity > 0) { + tgt2src[src2tgt[s].idx].similarity = 0; + num_bumped++; + } + if (tgt2src[t].similarity > 0) { + src2tgt[tgt2src[t].idx].similarity = 0; + num_bumped++; + } + + /* write new mapping */ + tgt2src[t].idx = s; + tgt2src[t].similarity = similarity; + src2tgt[s].idx = t; + src2tgt[s].similarity = similarity; + } + + /* keep best absolute match for copies */ + if (tgt2src_copy != NULL && + tgt2src_copy[t].similarity < similarity) + { + tgt2src_copy[t].idx = s; + tgt2src_copy[t].similarity = similarity; + } + + if (++tried_srcs >= num_srcs) + break; + + /* cap on maximum targets we'll examine (per "tgt" file) */ + if (tried_srcs > opts.rename_limit) + break; + } + + if (++tried_tgts >= num_tgts) + break; + } + + if (num_bumped > 0) /* try again if we bumped some items */ + goto find_best_matches; + + /* + * Rewrite the diffs with renames / copies + */ + + git_vector_foreach(&diff->deltas, t, tgt) { + /* skip things that are not rename targets */ + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + continue; + + /* check if this delta was the target of a similarity */ + if (tgt2src[t].similarity) + best_match = &tgt2src[t]; + else if (tgt2src_copy && tgt2src_copy[t].similarity) + best_match = &tgt2src_copy[t]; + else + continue; + + s = best_match->idx; + src = GIT_VECTOR_GET(&diff->deltas, s); + + /* possible scenarios: + * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME + * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE + * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME + * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT + * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY + */ + + if (src->status == GIT_DELTA_DELETED) { + + if (delta_is_new_only(tgt)) { + + if (best_match->similarity < opts.rename_threshold) + continue; + + delta_make_rename(tgt, src, best_match->similarity); + + src->flags |= GIT_DIFF_FLAG__TO_DELETE; + num_rewrites++; + } else { + GIT_ASSERT(delta_is_split(tgt)); + + if (best_match->similarity < opts.rename_from_rewrite_threshold) + continue; + + memcpy(&swap, &tgt->old_file, sizeof(swap)); + + delta_make_rename(tgt, src, best_match->similarity); + num_rewrites--; + + GIT_ASSERT(src->status == GIT_DELTA_DELETED); + memcpy(&src->old_file, &swap, sizeof(src->old_file)); + memset(&src->new_file, 0, sizeof(src->new_file)); + src->new_file.path = src->old_file.path; + src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + num_updates++; + + if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = s; + } + } + } + + else if (delta_is_split(src)) { + + if (delta_is_new_only(tgt)) { + + if (best_match->similarity < opts.rename_threshold) + continue; + + delta_make_rename(tgt, src, best_match->similarity); + + 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)); + src->old_file.path = src->new_file.path; + src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + num_rewrites--; + + num_updates++; + } else { + GIT_ASSERT(delta_is_split(src)); + + if (best_match->similarity < opts.rename_from_rewrite_threshold) + continue; + + memcpy(&swap, &tgt->old_file, sizeof(swap)); + + delta_make_rename(tgt, src, best_match->similarity); + num_rewrites--; + num_updates++; + + memcpy(&src->old_file, &swap, sizeof(src->old_file)); + + /* if we've just swapped the new element into the correct + * place, clear the SPLIT and RENAME_TARGET flags + */ + if (tgt2src[s].idx == t && + tgt2src[s].similarity > + opts.rename_from_rewrite_threshold) { + src->status = GIT_DELTA_RENAMED; + src->similarity = tgt2src[s].similarity; + tgt2src[s].similarity = 0; + src->flags &= ~(GIT_DIFF_FLAG__TO_SPLIT | GIT_DIFF_FLAG__IS_RENAME_TARGET); + num_rewrites--; + } + /* otherwise, if we just overwrote a source, update mapping */ + else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = s; + } + + num_updates++; + } + } + + else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { + if (tgt2src_copy[t].similarity < opts.copy_threshold) + continue; + + /* always use best possible source for copy */ + best_match = &tgt2src_copy[t]; + src = GIT_VECTOR_GET(&diff->deltas, best_match->idx); + + if (delta_is_split(tgt)) { + error = insert_delete_side_of_split(diff, &diff->deltas, tgt); + if (error < 0) + goto cleanup; + num_rewrites--; + } + + if (!delta_is_split(tgt) && !delta_is_new_only(tgt)) + continue; + + tgt->status = GIT_DELTA_COPIED; + tgt->similarity = best_match->similarity; + tgt->nfiles = 2; + memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); + tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + + num_updates++; + } + } + + /* + * Actually split and delete entries as needed + */ + + if (num_rewrites > 0 || num_updates > 0) + error = apply_splits_and_deletes( + diff, diff->deltas.length - num_rewrites, + FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && + !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); + +cleanup: + git__free(tgt2src); + git__free(src2tgt); + git__free(tgt2src_copy); + + if (sigcache) { + for (t = 0; t < num_deltas * 2; ++t) { + if (sigcache[t] != NULL) + opts.metric->free_signature(sigcache[t], opts.metric->payload); + } + git__free(sigcache); + } + + if (!given_opts || !given_opts->metric) + git__free(opts.metric); + + return error; +} + +#undef FLAG_SET diff --git a/src/libgit2/diff_tform.h b/src/libgit2/diff_tform.h new file mode 100644 index 000000000..7abb8b3fe --- /dev/null +++ b/src/libgit2/diff_tform.h @@ -0,0 +1,25 @@ +/* + * 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_diff_tform_h__ +#define INCLUDE_diff_tform_h__ + +#include "common.h" + +#include "diff_file.h" + +extern int git_diff_find_similar__hashsig_for_file( + void **out, const git_diff_file *f, const char *path, void *p); + +extern int git_diff_find_similar__hashsig_for_buf( + void **out, const git_diff_file *f, const char *buf, size_t len, void *p); + +extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); + +extern int git_diff_find_similar__calc_similarity( + int *score, void *siga, void *sigb, void *payload); + +#endif diff --git a/src/libgit2/diff_xdiff.c b/src/libgit2/diff_xdiff.c new file mode 100644 index 000000000..5f56c5209 --- /dev/null +++ b/src/libgit2/diff_xdiff.c @@ -0,0 +1,261 @@ +/* + * 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 "diff_xdiff.h" + +#include "git2/errors.h" +#include "diff.h" +#include "diff_driver.h" +#include "patch_generate.h" +#include "utf8.h" + +static int git_xdiff_scan_int(const char **str, int *value) +{ + const char *scan = *str; + int v = 0, digits = 0; + /* find next digit */ + for (scan = *str; *scan && !git__isdigit(*scan); scan++); + /* parse next number */ + for (; git__isdigit(*scan); scan++, digits++) + v = (v * 10) + (*scan - '0'); + *str = scan; + *value = v; + return (digits > 0) ? 0 : -1; +} + +static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) +{ + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (*header != '@') + goto fail; + if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) + goto fail; + if (*header == ',') { + if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) + goto fail; + } else + hunk->old_lines = 1; + if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) + goto fail; + if (*header == ',') { + if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) + goto fail; + } else + hunk->new_lines = 1; + if (hunk->old_start < 0 || hunk->new_start < 0) + goto fail; + + return 0; + +fail: + git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff"); + return -1; +} + +typedef struct { + git_xdiff_output *xo; + git_patch_generated *patch; + git_diff_hunk hunk; + int old_lineno, new_lineno; + mmfile_t xd_old_data, xd_new_data; +} git_xdiff_info; + +static int diff_update_lines( + git_xdiff_info *info, + git_diff_line *line, + const char *content, + size_t content_len) +{ + const char *scan = content, *scan_end = content + content_len; + + for (line->num_lines = 0; scan < scan_end; ++scan) + if (*scan == '\n') + ++line->num_lines; + + line->content = content; + line->content_len = content_len; + + /* expect " "/"-"/"+", then data */ + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_DEL_EOFNL: + line->old_lineno = -1; + line->new_lineno = info->new_lineno; + info->new_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_ADD_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = -1; + info->old_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_CONTEXT_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = info->new_lineno; + info->old_lineno += (int)line->num_lines; + info->new_lineno += (int)line->num_lines; + break; + default: + git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x", + (unsigned int)line->origin); + return -1; + } + + return 0; +} + +static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) +{ + git_xdiff_info *info = priv; + git_patch_generated *patch = info->patch; + const git_diff_delta *delta = patch->base.delta; + git_patch_generated_output *output = &info->xo->output; + git_diff_line line; + size_t buffer_len; + + if (len == 1) { + output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); + if (output->error < 0) + return output->error; + + info->hunk.header_len = bufs[0].size; + if (info->hunk.header_len >= sizeof(info->hunk.header)) + info->hunk.header_len = sizeof(info->hunk.header) - 1; + + /* Sanitize the hunk header in case there is invalid Unicode */ + buffer_len = git_utf8_valid_buf_length(bufs[0].ptr, info->hunk.header_len); + /* Sanitizing the hunk header may delete the newline, so add it back again if there is room */ + if (buffer_len < info->hunk.header_len) { + bufs[0].ptr[buffer_len] = '\n'; + buffer_len += 1; + info->hunk.header_len = buffer_len; + } + + memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); + info->hunk.header[info->hunk.header_len] = '\0'; + + if (output->hunk_cb != NULL && + (output->error = output->hunk_cb( + delta, &info->hunk, output->payload))) + return output->error; + + info->old_lineno = info->hunk.old_start; + info->new_lineno = info->hunk.new_start; + } + + if (len == 2 || len == 3) { + /* expect " "/"-"/"+", then data */ + line.origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + if (line.origin == GIT_DIFF_LINE_ADDITION) + line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; + else if (line.origin == GIT_DIFF_LINE_DELETION) + line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; + else + line.content_offset = -1; + + output->error = diff_update_lines( + info, &line, bufs[1].ptr, bufs[1].size); + + if (!output->error && output->data_cb != NULL) + output->error = output->data_cb( + delta, &info->hunk, &line, output->payload); + } + + if (len == 3 && !output->error) { + /* If we have a '+' and a third buf, then we have added a line + * without a newline and the old code had one, so DEL_EOFNL. + * If we have a '-' and a third buf, then we have removed a line + * with out a newline but added a blank line, so ADD_EOFNL. + */ + line.origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : + GIT_DIFF_LINE_CONTEXT_EOFNL; + + line.content_offset = -1; + + output->error = diff_update_lines( + info, &line, bufs[2].ptr, bufs[2].size); + + if (!output->error && output->data_cb != NULL) + output->error = output->data_cb( + delta, &info->hunk, &line, output->payload); + } + + return output->error; +} + +static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) +{ + git_xdiff_output *xo = (git_xdiff_output *)output; + git_xdiff_info info; + git_diff_find_context_payload findctxt; + + memset(&info, 0, sizeof(info)); + info.patch = patch; + info.xo = xo; + + xo->callback.priv = &info; + + git_diff_find_context_init( + &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); + xo->config.find_func_priv = &findctxt; + + if (xo->config.find_func != NULL) + xo->config.flags |= XDL_EMIT_FUNCNAMES; + else + xo->config.flags &= ~XDL_EMIT_FUNCNAMES; + + /* TODO: check ofile.opts_flags to see if driver-specific per-file + * updates are needed to xo->params.flags + */ + + if (git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch) < 0 || + git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch) < 0) + return -1; + + xdl_diff(&info.xd_old_data, &info.xd_new_data, + &xo->params, &xo->config, &xo->callback); + + git_diff_find_context_clear(&findctxt); + + return xo->output.error; +} + +void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) +{ + uint32_t flags = opts ? opts->flags : 0; + + xo->output.diff_cb = git_xdiff; + + xo->config.ctxlen = opts ? opts->context_lines : 3; + xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0; + + if (flags & GIT_DIFF_IGNORE_WHITESPACE) + xo->params.flags |= XDF_WHITESPACE_FLAGS; + if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) + xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) + xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + if (flags & GIT_DIFF_INDENT_HEURISTIC) + xo->params.flags |= XDF_INDENT_HEURISTIC; + + if (flags & GIT_DIFF_PATIENCE) + xo->params.flags |= XDF_PATIENCE_DIFF; + if (flags & GIT_DIFF_MINIMAL) + xo->params.flags |= XDF_NEED_MINIMAL; + + if (flags & GIT_DIFF_IGNORE_BLANK_LINES) + xo->params.flags |= XDF_IGNORE_BLANK_LINES; + + xo->callback.out_line = git_xdiff_cb; +} diff --git a/src/libgit2/diff_xdiff.h b/src/libgit2/diff_xdiff.h new file mode 100644 index 000000000..9b303e9dc --- /dev/null +++ b/src/libgit2/diff_xdiff.h @@ -0,0 +1,35 @@ +/* + * 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_diff_xdiff_h__ +#define INCLUDE_diff_xdiff_h__ + +#include "common.h" + +#include "diff.h" +#include "xdiff/xdiff.h" +#include "patch_generate.h" + +/* xdiff cannot cope with large files. these files should not be passed to + * xdiff. callers should treat these large files as binary. + */ +#define GIT_XDIFF_MAX_SIZE (INT64_C(1024) * 1024 * 1023) + +/* A git_xdiff_output is a git_patch_generate_output with extra fields + * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb + * field of the output to use xdiff to generate the diffs. + */ +typedef struct { + git_patch_generated_output output; + + xdemitconf_t config; + xpparam_t params; + xdemitcb_t callback; +} git_xdiff_output; + +void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts); + +#endif diff --git a/src/libgit2/email.c b/src/libgit2/email.c new file mode 100644 index 000000000..e19a2928c --- /dev/null +++ b/src/libgit2/email.c @@ -0,0 +1,315 @@ +/* + * 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 "email.h" + +#include "common.h" +#include "buf.h" +#include "diff_generate.h" +#include "diff_stats.h" +#include "patch.h" +#include "date.h" + +#include "git2/email.h" +#include "git2/patch.h" +#include "git2/version.h" + +/* + * Git uses a "magic" timestamp to indicate that an email message + * is from `git format-patch` (or our equivalent). + */ +#define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001" + +GIT_INLINE(int) include_prefix( + size_t patch_count, + git_email_create_options *opts) +{ + return ((!opts->subject_prefix || *opts->subject_prefix) || + (opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || + opts->reroll_number || + (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))); +} + +static int append_prefix( + git_str *out, + size_t patch_idx, + size_t patch_count, + git_email_create_options *opts) +{ + const char *subject_prefix = opts->subject_prefix ? + opts->subject_prefix : "PATCH"; + + git_str_putc(out, '['); + + if (*subject_prefix) + git_str_puts(out, subject_prefix); + + if (opts->reroll_number) { + if (*subject_prefix) + git_str_putc(out, ' '); + + git_str_printf(out, "v%" PRIuZ, opts->reroll_number); + } + + if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || + (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) { + size_t start_number = opts->start_number ? + opts->start_number : 1; + + if (*subject_prefix || opts->reroll_number) + git_str_putc(out, ' '); + + git_str_printf(out, "%" PRIuZ "/%" PRIuZ, + patch_idx + (start_number - 1), + patch_count + (start_number - 1)); + } + + git_str_puts(out, "]"); + + return git_str_oom(out) ? -1 : 0; +} + +static int append_date( + git_str *out, + const git_time *date) +{ + int error; + + if ((error = git_str_printf(out, "Date: ")) == 0 && + (error = git_date_rfc2822_fmt(out, date->time, date->offset)) == 0) + error = git_str_putc(out, '\n'); + + return error; +} + +static int append_subject( + git_str *out, + size_t patch_idx, + size_t patch_count, + const char *summary, + git_email_create_options *opts) +{ + bool prefix = include_prefix(patch_count, opts); + size_t summary_len = summary ? strlen(summary) : 0; + int error; + + if (summary_len) { + const char *nl = strchr(summary, '\n'); + + if (nl) + summary_len = (nl - summary); + } + + if ((error = git_str_puts(out, "Subject: ")) < 0) + return error; + + if (prefix && + (error = append_prefix(out, patch_idx, patch_count, opts)) < 0) + return error; + + if (prefix && summary_len && (error = git_str_putc(out, ' ')) < 0) + return error; + + if (summary_len && + (error = git_str_put(out, summary, summary_len)) < 0) + return error; + + return git_str_putc(out, '\n'); +} + +static int append_header( + git_str *out, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const git_signature *author, + git_email_create_options *opts) +{ + char id[GIT_OID_HEXSZ]; + int error; + + if ((error = git_oid_fmt(id, commit_id)) < 0 || + (error = git_str_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 || + (error = git_str_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 || + (error = append_date(out, &author->when)) < 0 || + (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0) + return error; + + if ((error = git_str_putc(out, '\n')) < 0) + return error; + + return 0; +} + +static int append_body(git_str *out, const char *body) +{ + size_t body_len; + int error; + + if (!body) + return 0; + + body_len = strlen(body); + + if ((error = git_str_puts(out, body)) < 0) + return error; + + if (body_len && body[body_len - 1] != '\n') + error = git_str_putc(out, '\n'); + + return error; +} + +static int append_diffstat(git_str *out, git_diff *diff) +{ + git_diff_stats *stats = NULL; + unsigned int format_flags; + int error; + + format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY; + + if ((error = git_diff_get_stats(&stats, diff)) == 0 && + (error = git_diff__stats_to_buf(out, stats, format_flags, 0)) == 0) + error = git_str_putc(out, '\n'); + + git_diff_stats_free(stats); + return error; +} + +static int append_patches(git_str *out, git_diff *diff) +{ + size_t i, deltas; + int error = 0; + + deltas = git_diff_num_deltas(diff); + + for (i = 0; i < deltas; ++i) { + git_patch *patch = NULL; + + if ((error = git_patch_from_diff(&patch, diff, i)) >= 0) + error = git_patch__to_buf(out, patch); + + git_patch_free(patch); + + if (error < 0) + break; + } + + return error; +} + +int git_email__append_from_diff( + git_str *out, + git_diff *diff, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const char *body, + const git_signature *author, + const git_email_create_options *given_opts) +{ + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count); + GIT_ASSERT_ARG(commit_id); + GIT_ASSERT_ARG(author); + + GIT_ERROR_CHECK_VERSION(given_opts, + GIT_EMAIL_CREATE_OPTIONS_VERSION, + "git_email_create_options"); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_email_create_options)); + + if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 && + (error = append_body(out, body)) == 0 && + (error = git_str_puts(out, "---\n")) == 0 && + (error = append_diffstat(out, diff)) == 0 && + (error = append_patches(out, diff)) == 0) + error = git_str_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n"); + + return error; +} + +int git_email_create_from_diff( + git_buf *out, + git_diff *diff, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const char *body, + const git_signature *author, + const git_email_create_options *given_opts) +{ + git_str email = GIT_STR_INIT; + int error; + + git_buf_tostr(&email, out); + + error = git_email__append_from_diff(&email, diff, patch_idx, + patch_count, commit_id, summary, body, author, + given_opts); + + if (error == 0) + error = git_buf_fromstr(out, &email); + + git_str_dispose(&email); + return error; +} + +int git_email_create_from_commit( + git_buf *out, + git_commit *commit, + const git_email_create_options *given_opts) +{ + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + git_diff *diff = NULL; + git_repository *repo; + git_diff_options *diff_opts; + git_diff_find_options *find_opts; + const git_signature *author; + const char *summary, *body; + const git_oid *commit_id; + int error = -1; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(commit); + + GIT_ERROR_CHECK_VERSION(given_opts, + GIT_EMAIL_CREATE_OPTIONS_VERSION, + "git_email_create_options"); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_email_create_options)); + + repo = git_commit_owner(commit); + author = git_commit_author(commit); + summary = git_commit_summary(commit); + body = git_commit_body(commit); + commit_id = git_commit_id(commit); + diff_opts = &opts.diff_opts; + find_opts = &opts.diff_find_opts; + + if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) + goto done; + + if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 && + (error = git_diff_find_similar(diff, find_opts)) < 0) + goto done; + + error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts); + +done: + git_diff_free(diff); + return error; +} diff --git a/src/libgit2/email.h b/src/libgit2/email.h new file mode 100644 index 000000000..083e56d5c --- /dev/null +++ b/src/libgit2/email.h @@ -0,0 +1,25 @@ +/* + * 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_email_h__ +#define INCLUDE_email_h__ + +#include "common.h" + +#include "git2/email.h" + +extern int git_email__append_from_diff( + git_str *out, + git_diff *diff, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const char *body, + const git_signature *author, + const git_email_create_options *given_opts); + +#endif diff --git a/src/libgit2/errors.c b/src/libgit2/errors.c new file mode 100644 index 000000000..3614b9ce5 --- /dev/null +++ b/src/libgit2/errors.c @@ -0,0 +1,238 @@ +/* + * 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 "threadstate.h" +#include "posix.h" +#include "str.h" +#include "libgit2.h" + +/******************************************** + * New error handling + ********************************************/ + +static git_error g_git_oom_error = { + "Out of memory", + GIT_ERROR_NOMEMORY +}; + +static git_error g_git_uninitialized_error = { + "libgit2 has not been initialized; you must call git_libgit2_init", + GIT_ERROR_INVALID +}; + +static void set_error_from_buffer(int error_class) +{ + git_error *error = &GIT_THREADSTATE->error_t; + git_str *buf = &GIT_THREADSTATE->error_buf; + + error->message = buf->ptr; + error->klass = error_class; + + GIT_THREADSTATE->last_error = error; +} + +static void set_error(int error_class, char *string) +{ + git_str *buf = &GIT_THREADSTATE->error_buf; + + git_str_clear(buf); + if (string) { + git_str_puts(buf, string); + git__free(string); + } + + set_error_from_buffer(error_class); +} + +void git_error_set_oom(void) +{ + GIT_THREADSTATE->last_error = &g_git_oom_error; +} + +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) +{ +#ifdef GIT_WIN32 + DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; +#endif + int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; + git_str *buf = &GIT_THREADSTATE->error_buf; + + git_str_clear(buf); + if (fmt) { + git_str_vprintf(buf, fmt, ap); + if (error_class == GIT_ERROR_OS) + git_str_PUTS(buf, ": "); + } + + if (error_class == GIT_ERROR_OS) { +#ifdef GIT_WIN32 + char * win32_error = git_win32_get_error_message(win32_error_code); + if (win32_error) { + git_str_puts(buf, win32_error); + git__free(win32_error); + + SetLastError(0); + } + else +#endif + if (error_code) + git_str_puts(buf, strerror(error_code)); + + if (error_code) + errno = 0; + } + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +int git_error_set_str(int error_class, const char *string) +{ + git_str *buf = &GIT_THREADSTATE->error_buf; + + GIT_ASSERT_ARG(string); + + git_str_clear(buf); + git_str_puts(buf, string); + + if (git_str_oom(buf)) + return -1; + + set_error_from_buffer(error_class); + return 0; +} + +void git_error_clear(void) +{ + if (GIT_THREADSTATE->last_error != NULL) { + set_error(0, NULL); + GIT_THREADSTATE->last_error = NULL; + } + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif +} + +const git_error *git_error_last(void) +{ + /* If the library is not initialized, return a static error. */ + if (!git_libgit2_init_count()) + return &g_git_uninitialized_error; + + return GIT_THREADSTATE->last_error; +} + +int git_error_state_capture(git_error_state *state, int error_code) +{ + git_error *error = GIT_THREADSTATE->last_error; + git_str *error_buf = &GIT_THREADSTATE->error_buf; + + memset(state, 0, sizeof(git_error_state)); + + if (!error_code) + return 0; + + state->error_code = error_code; + state->oom = (error == &g_git_oom_error); + + if (error) { + state->error_msg.klass = error->klass; + + if (state->oom) + state->error_msg.message = g_git_oom_error.message; + else + state->error_msg.message = git_str_detach(error_buf); + } + + git_error_clear(); + return error_code; +} + +int git_error_state_restore(git_error_state *state) +{ + int ret = 0; + + git_error_clear(); + + if (state && state->error_msg.message) { + if (state->oom) + git_error_set_oom(); + else + set_error(state->error_msg.klass, state->error_msg.message); + + ret = state->error_code; + memset(state, 0, sizeof(git_error_state)); + } + + return ret; +} + +void git_error_state_free(git_error_state *state) +{ + if (!state) + return; + + if (!state->oom) + git__free(state->error_msg.message); + + memset(state, 0, sizeof(git_error_state)); +} + +int git_error_system_last(void) +{ +#ifdef GIT_WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +void git_error_system_set(int code) +{ +#ifdef GIT_WIN32 + SetLastError(code); +#else + errno = code; +#endif +} + +/* Deprecated error values and functions */ + +#ifndef GIT_DEPRECATE_HARD +const git_error *giterr_last(void) +{ + return git_error_last(); +} + +void giterr_clear(void) +{ + git_error_clear(); +} + +void giterr_set_str(int error_class, const char *string) +{ + git_error_set_str(error_class, string); +} + +void giterr_set_oom(void) +{ + git_error_set_oom(); +} +#endif diff --git a/src/libgit2/errors.h b/src/libgit2/errors.h new file mode 100644 index 000000000..772c7bad1 --- /dev/null +++ b/src/libgit2/errors.h @@ -0,0 +1,80 @@ +/* + * 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" + +/* + * `vprintf`-style formatting for the error message for this thread. + */ +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/libgit2/fetch.c b/src/libgit2/fetch.c new file mode 100644 index 000000000..e9f30d9bc --- /dev/null +++ b/src/libgit2/fetch.c @@ -0,0 +1,214 @@ +/* + * 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 "fetch.h" + +#include "git2/oid.h" +#include "git2/refs.h" +#include "git2/revwalk.h" +#include "git2/transport.h" +#include "git2/sys/remote.h" + +#include "remote.h" +#include "refspec.h" +#include "pack.h" +#include "netops.h" +#include "repository.h" +#include "refs.h" + +static int maybe_want(git_remote *remote, git_remote_head *head, git_refspec *tagspec, git_remote_autotag_option_t tagopt) +{ + int match = 0, valid; + + if (git_reference_name_is_valid(&valid, head->name) < 0) + return -1; + + if (!valid) + return 0; + + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { + /* + * If tagopt is --tags, always request tags + * in addition to the remote's refspecs + */ + if (git_refspec_src_matches(tagspec, head->name)) + match = 1; + } + + if (!match && git_remote__matching_refspec(remote, head->name)) + match = 1; + + if (!match) + return 0; + + return git_vector_insert(&remote->refs, head); +} + +static int mark_local(git_remote *remote) +{ + git_remote_head *head; + git_odb *odb; + size_t i; + + if (git_repository_odb__weakptr(&odb, remote->repo) < 0) + return -1; + + git_vector_foreach(&remote->refs, i, head) { + /* If we have the object, mark it so we don't ask for it */ + if (git_odb_exists(odb, &head->oid)) + head->local = 1; + else + remote->need_pack = 1; + } + + return 0; +} + +static int maybe_want_oid(git_remote *remote, git_refspec *spec) +{ + git_remote_head *oid_head; + + oid_head = git__calloc(1, sizeof(git_remote_head)); + GIT_ERROR_CHECK_ALLOC(oid_head); + + git_oid_fromstr(&oid_head->oid, spec->src); + + if (spec->dst) { + oid_head->name = git__strdup(spec->dst); + GIT_ERROR_CHECK_ALLOC(oid_head->name); + } + + if (git_vector_insert(&remote->local_heads, oid_head) < 0 || + git_vector_insert(&remote->refs, oid_head) < 0) + return -1; + + return 0; +} + +static int filter_wants(git_remote *remote, const git_fetch_options *opts) +{ + git_remote_head **heads; + git_refspec tagspec, head, *spec; + int error = 0; + git_odb *odb; + size_t i, heads_len; + unsigned int remote_caps; + unsigned int oid_mask = GIT_REMOTE_CAPABILITY_TIP_OID | + GIT_REMOTE_CAPABILITY_REACHABLE_OID; + git_remote_autotag_option_t tagopt = remote->download_tags; + + if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) + tagopt = opts->download_tags; + + git_vector_clear(&remote->refs); + if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0) + return error; + + /* + * The fetch refspec can be NULL, and what this means is that the + * user didn't specify one. This is fine, as it means that we're + * not interested in any particular branch but just the remote's + * HEAD, which will be stored in FETCH_HEAD after the fetch. + */ + if (remote->active_refspecs.length == 0) { + if ((error = git_refspec__parse(&head, "HEAD", true)) < 0) + goto cleanup; + + error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs); + git_refspec__dispose(&head); + + if (error < 0) + goto cleanup; + } + + if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) + goto cleanup; + + if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0 || + (error = git_remote_capabilities(&remote_caps, remote)) < 0) + goto cleanup; + + /* Handle remote heads */ + for (i = 0; i < heads_len; i++) { + if ((error = maybe_want(remote, heads[i], &tagspec, tagopt)) < 0) + goto cleanup; + } + + /* Handle explicitly specified OID specs */ + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (!git_oid__is_hexstr(spec->src)) + continue; + + if (!(remote_caps & oid_mask)) { + git_error_set(GIT_ERROR_INVALID, "cannot fetch a specific object from the remote repository"); + error = -1; + goto cleanup; + } + + if ((error = maybe_want_oid(remote, spec)) < 0) + goto cleanup; + } + + error = mark_local(remote); + +cleanup: + git_refspec__dispose(&tagspec); + + return error; +} + +/* + * In this first version, we push all our refs in and start sending + * them out. When we get an ACK we hide that commit and continue + * traversing until we're done + */ +int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts) +{ + git_transport *t = remote->transport; + + remote->need_pack = 0; + + if (filter_wants(remote, opts) < 0) + return -1; + + /* Don't try to negotiate when we don't want anything */ + if (!remote->need_pack) + return 0; + + /* + * Now we have everything set up so we can start tell the + * server what we want and what we have. + */ + return t->negotiate_fetch(t, + remote->repo, + (const git_remote_head * const *)remote->refs.contents, + remote->refs.length); +} + +int git_fetch_download_pack(git_remote *remote) +{ + git_transport *t = remote->transport; + + if (!remote->need_pack) + return 0; + + return t->download_pack(t, remote->repo, &remote->stats); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_fetch_init_options(git_fetch_options *opts, unsigned int version) +{ + return git_fetch_options_init(opts, version); +} +#endif diff --git a/src/libgit2/fetch.h b/src/libgit2/fetch.h new file mode 100644 index 000000000..10b6731f0 --- /dev/null +++ b/src/libgit2/fetch.h @@ -0,0 +1,22 @@ +/* + * 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_fetch_h__ +#define INCLUDE_fetch_h__ + +#include "common.h" + +#include "git2/remote.h" + +#include "netops.h" + +int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts); + +int git_fetch_download_pack(git_remote *remote); + +int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); + +#endif diff --git a/src/libgit2/fetchhead.c b/src/libgit2/fetchhead.c new file mode 100644 index 000000000..6511124ef --- /dev/null +++ b/src/libgit2/fetchhead.c @@ -0,0 +1,338 @@ +/* + * 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 "fetchhead.h" + +#include "git2/types.h" +#include "git2/oid.h" + +#include "str.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) +{ + const git_fetchhead_ref *one = (const git_fetchhead_ref *)a; + const git_fetchhead_ref *two = (const git_fetchhead_ref *)b; + + if (one->is_merge && !two->is_merge) + return -1; + if (two->is_merge && !one->is_merge) + return 1; + + if (one->ref_name && two->ref_name) + return strcmp(one->ref_name, two->ref_name); + else if (one->ref_name) + return -1; + else if (two->ref_name) + return 1; + + 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_str buf = GIT_STR_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_str_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, + unsigned int is_merge, + const char *ref_name, + const char *remote_url) +{ + git_fetchhead_ref *fetchhead_ref; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(oid); + + *out = NULL; + + fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref)); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref); + + memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref)); + + git_oid_cpy(&fetchhead_ref->oid, oid); + fetchhead_ref->is_merge = is_merge; + + 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 = sanitized_remote_url(remote_url); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url); + } + + *out = fetchhead_ref; + + return 0; +} + +static int fetchhead_ref_write( + git_filebuf *file, + git_fetchhead_ref *fetchhead_ref) +{ + char oid[GIT_OID_HEXSZ + 1]; + const char *type, *name; + int head = 0; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(fetchhead_ref); + + git_oid_fmt(oid, &fetchhead_ref->oid); + oid[GIT_OID_HEXSZ] = '\0'; + + if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) { + type = "branch "; + name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR); + } else if(git__prefixcmp(fetchhead_ref->ref_name, + GIT_REFS_TAGS_DIR) == 0) { + type = "tag "; + name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR); + } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) { + head = 1; + } else { + type = ""; + name = fetchhead_ref->ref_name; + } + + if (head) + return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url); + + return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n", + oid, + (fetchhead_ref->is_merge) ? "" : "not-for-merge", + type, + name, + fetchhead_ref->remote_url); +} + +int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str path = GIT_STR_INIT; + unsigned int i; + git_fetchhead_ref *fetchhead_ref; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(fetchhead_refs); + + if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) + return -1; + + if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) { + git_str_dispose(&path); + return -1; + } + + git_str_dispose(&path); + + git_vector_sort(fetchhead_refs); + + git_vector_foreach(fetchhead_refs, i, fetchhead_ref) + fetchhead_ref_write(&file, fetchhead_ref); + + return git_filebuf_commit(&file); +} + +static int fetchhead_ref_parse( + git_oid *oid, + unsigned int *is_merge, + git_str *ref_name, + const char **remote_url, + char *line, + size_t line_num) +{ + char *oid_str, *is_merge_str, *desc, *name = NULL; + const char *type = NULL; + int error = 0; + + *remote_url = NULL; + + if (!*line) { + git_error_set(GIT_ERROR_FETCHHEAD, + "empty line in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */ + if ((oid_str = git__strsep(&line, "\t")) == NULL) { + oid_str = line; + line += strlen(line); + + *is_merge = 1; + } + + if (strlen(oid_str) != GIT_OID_HEXSZ) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid object ID in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if (git_oid_fromstr(oid, oid_str) < 0) { + const git_error *oid_err = git_error_last(); + const char *err_msg = oid_err ? oid_err->message : "invalid object ID"; + + git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ, + err_msg, line_num); + return -1; + } + + /* Parse new data from newer git clients */ + if (*line) { + if ((is_merge_str = git__strsep(&line, "\t")) == NULL) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid description data in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if (*is_merge_str == '\0') + *is_merge = 1; + else if (strcmp(is_merge_str, "not-for-merge") == 0) + *is_merge = 0; + else { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if ((desc = line) == NULL) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid description in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if (git__prefixcmp(desc, "branch '") == 0) { + type = GIT_REFS_HEADS_DIR; + name = desc + 8; + } else if (git__prefixcmp(desc, "tag '") == 0) { + type = GIT_REFS_TAGS_DIR; + name = desc + 5; + } else if (git__prefixcmp(desc, "'") == 0) + name = desc + 1; + + if (name) { + if ((desc = strstr(name, "' ")) == NULL || + git__prefixcmp(desc, "' of ") != 0) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid description in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + *desc = '\0'; + desc += 5; + } + + *remote_url = desc; + } + + git_str_clear(ref_name); + + if (type) + git_str_join(ref_name, '/', type, name); + else if(name) + git_str_puts(ref_name, name); + + return error; +} + +int git_repository_fetchhead_foreach(git_repository *repo, + git_repository_fetchhead_foreach_cb cb, + void *payload) +{ + git_str path = GIT_STR_INIT, file = GIT_STR_INIT, name = GIT_STR_INIT; + const char *ref_name; + git_oid oid; + const char *remote_url; + unsigned int is_merge = 0; + char *buffer, *line; + size_t line_num = 0; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cb); + + if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) + return -1; + + if ((error = git_futils_readbuffer(&file, git_str_cstr(&path))) < 0) + goto done; + + buffer = file.ptr; + + while ((line = git__strsep(&buffer, "\n")) != NULL) { + ++line_num; + + if ((error = fetchhead_ref_parse( + &oid, &is_merge, &name, &remote_url, line, line_num)) < 0) + goto done; + + if (git_str_len(&name) > 0) + ref_name = git_str_cstr(&name); + else + ref_name = NULL; + + error = cb(ref_name, remote_url, &oid, is_merge, payload); + if (error) { + git_error_set_after_callback(error); + goto done; + } + } + + if (*buffer) { + git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1); + error = -1; + goto done; + } + +done: + git_str_dispose(&file); + git_str_dispose(&path); + git_str_dispose(&name); + + return error; +} + +void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref) +{ + if (fetchhead_ref == NULL) + return; + + git__free(fetchhead_ref->remote_url); + git__free(fetchhead_ref->ref_name); + git__free(fetchhead_ref); +} + diff --git a/src/libgit2/fetchhead.h b/src/libgit2/fetchhead.h new file mode 100644 index 000000000..9e5171010 --- /dev/null +++ b/src/libgit2/fetchhead.h @@ -0,0 +1,35 @@ +/* + * 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_fetchhead_h__ +#define INCLUDE_fetchhead_h__ + +#include "common.h" + +#include "oid.h" +#include "vector.h" + +typedef struct git_fetchhead_ref { + git_oid oid; + unsigned int is_merge; + char *ref_name; + char *remote_url; +} git_fetchhead_ref; + +int git_fetchhead_ref_create( + git_fetchhead_ref **fetchhead_ref_out, + git_oid *oid, + unsigned int is_merge, + const char *ref_name, + const char *remote_url); + +int git_fetchhead_ref_cmp(const void *a, const void *b); + +int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs); + +void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref); + +#endif diff --git a/src/libgit2/filter.c b/src/libgit2/filter.c new file mode 100644 index 000000000..80a3cae67 --- /dev/null +++ b/src/libgit2/filter.c @@ -0,0 +1,1221 @@ +/* + * 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 "filter.h" + +#include "buf.h" +#include "common.h" +#include "futils.h" +#include "hash.h" +#include "repository.h" +#include "runtime.h" +#include "git2/sys/filter.h" +#include "git2/config.h" +#include "blob.h" +#include "attr_file.h" +#include "array.h" +#include "path.h" + +struct git_filter_source { + git_repository *repo; + const char *path; + git_oid oid; /* zero if unknown (which is likely) */ + uint16_t filemode; /* zero if unknown */ + git_filter_mode_t mode; + git_filter_options options; +}; + +typedef struct { + const char *filter_name; + git_filter *filter; + void *payload; +} git_filter_entry; + +struct git_filter_list { + git_array_t(git_filter_entry) filters; + git_filter_source source; + git_str *temp_buf; + char path[GIT_FLEX_ARRAY]; +}; + +typedef struct { + char *filter_name; + git_filter *filter; + int priority; + int initialized; + size_t nattrs, nmatches; + char *attrdata; + const char *attrs[GIT_FLEX_ARRAY]; +} git_filter_def; + +static int filter_def_priority_cmp(const void *a, const void *b) +{ + int pa = ((const git_filter_def *)a)->priority; + int pb = ((const git_filter_def *)b)->priority; + return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; +} + +struct git_filter_registry { + git_rwlock lock; + git_vector filters; +}; + +static struct git_filter_registry filter_registry; + +static void git_filter_global_shutdown(void); + + +static int filter_def_scan_attrs( + git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str) +{ + const char *start, *scan = attr_str; + int has_eq; + + *nattr = *nmatch = 0; + + if (!scan) + return 0; + + while (*scan) { + while (git__isspace(*scan)) scan++; + + for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) { + if (*scan == '=') + has_eq = 1; + } + + if (scan > start) { + (*nattr)++; + if (has_eq || *start == '-' || *start == '+' || *start == '!') + (*nmatch)++; + + if (has_eq) + git_str_putc(attrs, '='); + git_str_put(attrs, start, scan - start); + git_str_putc(attrs, '\0'); + } + } + + return 0; +} + +static void filter_def_set_attrs(git_filter_def *fdef) +{ + char *scan = fdef->attrdata; + size_t i; + + for (i = 0; i < fdef->nattrs; ++i) { + const char *name, *value; + + switch (*scan) { + case '=': + name = scan + 1; + for (scan++; *scan != '='; scan++) /* find '=' */; + *scan++ = '\0'; + value = scan; + break; + case '-': + name = scan + 1; value = git_attr__false; break; + case '+': + name = scan + 1; value = git_attr__true; break; + case '!': + name = scan + 1; value = git_attr__unset; break; + default: + name = scan; value = NULL; break; + } + + fdef->attrs[i] = name; + fdef->attrs[i + fdef->nattrs] = value; + + scan += strlen(scan) + 1; + } +} + +static int filter_def_name_key_check(const void *key, const void *fdef) +{ + const char *name = + fdef ? ((const git_filter_def *)fdef)->filter_name : NULL; + return name ? git__strcmp(key, name) : -1; +} + +static int filter_def_filter_key_check(const void *key, const void *fdef) +{ + const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; + return (key == filter) ? 0 : -1; +} + +/* Note: callers must lock the registry before calling this function */ +static int filter_registry_insert( + const char *name, git_filter *filter, int priority) +{ + git_filter_def *fdef; + size_t nattr = 0, nmatch = 0, alloc_len; + git_str attrs = GIT_STR_INIT; + + if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0) + return -1; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def)); + + fdef = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(fdef); + + fdef->filter_name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(fdef->filter_name); + + fdef->filter = filter; + fdef->priority = priority; + fdef->nattrs = nattr; + fdef->nmatches = nmatch; + fdef->attrdata = git_str_detach(&attrs); + + filter_def_set_attrs(fdef); + + if (git_vector_insert(&filter_registry.filters, fdef) < 0) { + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + return -1; + } + + git_vector_sort(&filter_registry.filters); + return 0; +} + +int git_filter_global_init(void) +{ + git_filter *crlf = NULL, *ident = NULL; + int error = 0; + + if (git_rwlock_init(&filter_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&filter_registry.filters, 2, + filter_def_priority_cmp)) < 0) + goto done; + + if ((crlf = git_crlf_filter_new()) == NULL || + filter_registry_insert( + GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 || + (ident = git_ident_filter_new()) == NULL || + filter_registry_insert( + GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) + error = -1; + + if (!error) + error = git_runtime_shutdown_register(git_filter_global_shutdown); + +done: + if (error) { + git_filter_free(crlf); + git_filter_free(ident); + } + + return error; +} + +static void git_filter_global_shutdown(void) +{ + size_t pos; + git_filter_def *fdef; + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) + return; + + git_vector_foreach(&filter_registry.filters, pos, fdef) { + if (fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + } + + git_vector_free(&filter_registry.filters); + + git_rwlock_wrunlock(&filter_registry.lock); + git_rwlock_free(&filter_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int filter_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2( + pos, &filter_registry.filters, filter_def_name_key_check, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) +{ + git_filter_def *fdef = NULL; + + if (!filter_registry_find(pos, name)) + fdef = git_vector_get(&filter_registry.filters, *pos); + + return fdef; +} + + +int git_filter_register( + const char *name, git_filter *filter, int priority) +{ + int error; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(filter); + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + if (!filter_registry_find(NULL, name)) { + git_error_set( + GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name); + error = GIT_EEXISTS; + goto done; + } + + error = filter_registry_insert(name, filter, priority); + +done: + git_rwlock_wrunlock(&filter_registry.lock); + return error; +} + +int git_filter_unregister(const char *name) +{ + size_t pos; + git_filter_def *fdef; + int error = 0; + + GIT_ASSERT_ARG(name); + + /* cannot unregister default filters */ + if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { + git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name); + return -1; + } + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { + git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&filter_registry.filters, pos); + + if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + +done: + git_rwlock_wrunlock(&filter_registry.lock); + return error; +} + +static int filter_initialize(git_filter_def *fdef) +{ + int error = 0; + + if (!fdef->initialized && fdef->filter && fdef->filter->initialize) { + if ((error = fdef->filter->initialize(fdef->filter)) < 0) + return error; + } + + fdef->initialized = true; + return 0; +} + +git_filter *git_filter_lookup(const char *name) +{ + size_t pos; + git_filter_def *fdef; + git_filter *filter = NULL; + + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return NULL; + } + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL || + (!fdef->initialized && filter_initialize(fdef) < 0)) + goto done; + + filter = fdef->filter; + +done: + git_rwlock_rdunlock(&filter_registry.lock); + return filter; +} + +void git_filter_free(git_filter *filter) +{ + git__free(filter); +} + +git_repository *git_filter_source_repo(const git_filter_source *src) +{ + return src->repo; +} + +const char *git_filter_source_path(const git_filter_source *src) +{ + return src->path; +} + +uint16_t git_filter_source_filemode(const git_filter_source *src) +{ + return src->filemode; +} + +const git_oid *git_filter_source_id(const git_filter_source *src) +{ + return git_oid_is_zero(&src->oid) ? NULL : &src->oid; +} + +git_filter_mode_t git_filter_source_mode(const git_filter_source *src) +{ + return src->mode; +} + +uint32_t git_filter_source_flags(const git_filter_source *src) +{ + return src->options.flags; +} + +static int filter_list_new( + git_filter_list **out, const git_filter_source *src) +{ + git_filter_list *fl = NULL; + size_t pathlen = src->path ? strlen(src->path) : 0, alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + fl = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(fl); + + if (src->path) + memcpy(fl->path, src->path, pathlen); + fl->source.repo = src->repo; + fl->source.path = fl->path; + fl->source.mode = src->mode; + + memcpy(&fl->source.options, &src->options, sizeof(git_filter_options)); + + *out = fl; + return 0; +} + +static int filter_list_check_attributes( + const char ***out, + git_repository *repo, + git_filter_session *filter_session, + git_filter_def *fdef, + const git_filter_source *src) +{ + const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); + git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT; + size_t i; + int error; + + GIT_ERROR_CHECK_ALLOC(strs); + + if ((src->options.flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) + attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM; + + if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0) + attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD; + + if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { + attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT; + +#ifndef GIT_DEPRECATE_HARD + if (src->options.commit_id) + git_oid_cpy(&attr_opts.attr_commit_id, src->options.commit_id); + else +#endif + git_oid_cpy(&attr_opts.attr_commit_id, &src->options.attr_commit_id); + } + + error = git_attr_get_many_with_session( + strs, repo, filter_session->attr_session, &attr_opts, 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) { + git_error_clear(); + git__free((void *)strs); + return 0; + } + + for (i = 0; !error && i < fdef->nattrs; ++i) { + const char *want = fdef->attrs[fdef->nattrs + i]; + git_attr_value_t want_type, found_type; + + if (!want) + continue; + + want_type = git_attr_value(want); + found_type = git_attr_value(strs[i]); + + if (want_type != found_type) + error = GIT_ENOTFOUND; + else if (want_type == GIT_ATTR_VALUE_STRING && + strcmp(want, strs[i]) && + strcmp(want, "*")) + error = GIT_ENOTFOUND; + } + + if (error) + git__free((void *)strs); + else + *out = strs; + + return error; +} + +int git_filter_list_new( + git_filter_list **out, + git_repository *repo, + git_filter_mode_t mode, + uint32_t flags) +{ + git_filter_source src = { 0 }; + src.repo = repo; + src.path = NULL; + src.mode = mode; + src.options.flags = flags; + return filter_list_new(out, &src); +} + +int git_filter_list__load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + git_filter_session *filter_session) +{ + int error = 0; + git_filter_list *fl = NULL; + git_filter_source src = { 0 }; + git_filter_entry *fe; + size_t idx; + git_filter_def *fdef; + + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + src.repo = repo; + src.path = path; + src.mode = mode; + + memcpy(&src.options, &filter_session->options, sizeof(git_filter_options)); + + if (blob) + git_oid_cpy(&src.oid, git_blob_id(blob)); + + git_vector_foreach(&filter_registry.filters, idx, fdef) { + const char **values = NULL; + void *payload = NULL; + + if (!fdef || !fdef->filter) + continue; + + if (fdef->nattrs > 0) { + error = filter_list_check_attributes( + &values, repo, + filter_session, fdef, &src); + + if (error == GIT_ENOTFOUND) { + error = 0; + continue; + } else if (error < 0) + break; + } + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + break; + + if (fdef->filter->check) + error = fdef->filter->check( + fdef->filter, &payload, &src, values); + + git__free((void *)values); + + if (error == GIT_PASSTHROUGH) + error = 0; + else if (error < 0) + break; + else { + if (!fl) { + if ((error = filter_list_new(&fl, &src)) < 0) + break; + + fl->temp_buf = filter_session->temp_buf; + } + + fe = git_array_alloc(fl->filters); + GIT_ERROR_CHECK_ALLOC(fe); + + fe->filter = fdef->filter; + fe->filter_name = fdef->filter_name; + fe->payload = payload; + } + } + + git_rwlock_rdunlock(&filter_registry.lock); + + if (error && fl != NULL) { + git_array_clear(fl->filters); + git__free(fl); + fl = NULL; + } + + *filters = fl; + return error; +} + +int git_filter_list_load_ext( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + git_filter_options *opts) +{ + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + + if (opts) + memcpy(&filter_session.options, opts, sizeof(git_filter_options)); + + return git_filter_list__load( + filters, repo, blob, path, mode, &filter_session); +} + +int git_filter_list_load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + uint32_t flags) +{ + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + + filter_session.options.flags = flags; + + return git_filter_list__load( + filters, repo, blob, path, mode, &filter_session); +} + +void git_filter_list_free(git_filter_list *fl) +{ + uint32_t i; + + if (!fl) + return; + + for (i = 0; i < git_array_size(fl->filters); ++i) { + git_filter_entry *fe = git_array_get(fl->filters, i); + if (fe->filter->cleanup) + fe->filter->cleanup(fe->filter, fe->payload); + } + + git_array_clear(fl->filters); + git__free(fl); +} + +int git_filter_list_contains( + git_filter_list *fl, + const char *name) +{ + size_t i; + + GIT_ASSERT_ARG(name); + + if (!fl) + return 0; + + for (i = 0; i < fl->filters.size; i++) { + if (strcmp(fl->filters.ptr[i].filter_name, name) == 0) + return 1; + } + + return 0; +} + +int git_filter_list_push( + git_filter_list *fl, git_filter *filter, void *payload) +{ + int error = 0; + size_t pos; + git_filter_def *fdef = NULL; + git_filter_entry *fe; + + GIT_ASSERT_ARG(fl); + GIT_ASSERT_ARG(filter); + + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + if (git_vector_search2( + &pos, &filter_registry.filters, + filter_def_filter_key_check, filter) == 0) + fdef = git_vector_get(&filter_registry.filters, pos); + + git_rwlock_rdunlock(&filter_registry.lock); + + if (fdef == NULL) { + git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter"); + return -1; + } + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + return error; + + fe = git_array_alloc(fl->filters); + GIT_ERROR_CHECK_ALLOC(fe); + fe->filter = filter; + fe->payload = payload; + + return 0; +} + +size_t git_filter_list_length(const git_filter_list *fl) +{ + return fl ? git_array_size(fl->filters) : 0; +} + +struct buf_stream { + git_writestream parent; + git_str *target; + bool complete; +}; + +static int buf_stream_write( + git_writestream *s, const char *buffer, size_t len) +{ + struct buf_stream *buf_stream = (struct buf_stream *)s; + GIT_ASSERT_ARG(buf_stream); + GIT_ASSERT(buf_stream->complete == 0); + + return git_str_put(buf_stream->target, buffer, len); +} + +static int buf_stream_close(git_writestream *s) +{ + struct buf_stream *buf_stream = (struct buf_stream *)s; + GIT_ASSERT_ARG(buf_stream); + + GIT_ASSERT(buf_stream->complete == 0); + buf_stream->complete = 1; + + return 0; +} + +static void buf_stream_free(git_writestream *s) +{ + GIT_UNUSED(s); +} + +static void buf_stream_init(struct buf_stream *writer, git_str *target) +{ + memset(writer, 0, sizeof(struct buf_stream)); + + writer->parent.write = buf_stream_write; + writer->parent.close = buf_stream_close; + writer->parent.free = buf_stream_free; + writer->target = target; + + git_str_clear(target); +} + +int git_filter_list_apply_to_buffer( + git_buf *out, + git_filter_list *filters, + const char *in, + size_t in_len) +{ + GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len); +} + +int git_filter_list__apply_to_buffer( + git_str *out, + git_filter_list *filters, + const char *in, + size_t in_len) +{ + struct buf_stream writer; + int error; + + buf_stream_init(&writer, out); + + if ((error = git_filter_list_stream_buffer(filters, + in, in_len, &writer.parent)) < 0) + return error; + + GIT_ASSERT(writer.complete); + return error; +} + +int git_filter_list__convert_buf( + git_str *out, + git_filter_list *filters, + git_str *in) +{ + int error; + + if (!filters || git_filter_list_length(filters) == 0) { + git_str_swap(out, in); + git_str_dispose(in); + return 0; + } + + error = git_filter_list__apply_to_buffer(out, filters, + in->ptr, in->size); + + if (!error) + git_str_dispose(in); + + return error; +} + +int git_filter_list_apply_to_file( + git_buf *out, + git_filter_list *filters, + git_repository *repo, + const char *path) +{ + GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path); +} + +int git_filter_list__apply_to_file( + git_str *out, + git_filter_list *filters, + git_repository *repo, + const char *path) +{ + struct buf_stream writer; + int error; + + buf_stream_init(&writer, out); + + if ((error = git_filter_list_stream_file( + filters, repo, path, &writer.parent)) < 0) + return error; + + GIT_ASSERT(writer.complete); + return error; +} + +static int buf_from_blob(git_str *out, git_blob *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"); + return -1; + } + + git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize); + return 0; +} + +int git_filter_list_apply_to_blob( + git_buf *out, + git_filter_list *filters, + git_blob *blob) +{ + GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob); +} + +int git_filter_list__apply_to_blob( + git_str *out, + git_filter_list *filters, + git_blob *blob) +{ + struct buf_stream writer; + int error; + + buf_stream_init(&writer, out); + + if ((error = git_filter_list_stream_blob( + filters, blob, &writer.parent)) < 0) + return error; + + GIT_ASSERT(writer.complete); + return error; +} + +struct buffered_stream { + git_writestream parent; + git_filter *filter; + int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *); + int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *); + const git_filter_source *source; + void **payload; + git_str input; + git_str temp_buf; + git_str *output; + git_writestream *target; +}; + +static int buffered_stream_write( + git_writestream *s, const char *buffer, size_t len) +{ + struct buffered_stream *buffered_stream = (struct buffered_stream *)s; + GIT_ASSERT_ARG(buffered_stream); + + return git_str_put(&buffered_stream->input, buffer, len); +} + +#ifndef GIT_DEPRECATE_HARD +# define BUF_TO_STRUCT(b, s) \ + (b)->ptr = (s)->ptr; \ + (b)->size = (s)->size; \ + (b)->reserved = (s)->asize; +# define STRUCT_TO_BUF(s, b) \ + (s)->ptr = (b)->ptr; \ + (s)->size = (b)->size; \ + (s)->asize = (b)->reserved; +#endif + +static int buffered_stream_close(git_writestream *s) +{ + struct buffered_stream *buffered_stream = (struct buffered_stream *)s; + git_str *writebuf; + git_error_state error_state = {0}; + int error; + + GIT_ASSERT_ARG(buffered_stream); + +#ifndef GIT_DEPRECATE_HARD + if (buffered_stream->write_fn == NULL) { + git_buf legacy_output = GIT_BUF_INIT, + legacy_input = GIT_BUF_INIT; + + BUF_TO_STRUCT(&legacy_output, buffered_stream->output); + BUF_TO_STRUCT(&legacy_input, &buffered_stream->input); + + error = buffered_stream->legacy_write_fn( + buffered_stream->filter, + buffered_stream->payload, + &legacy_output, + &legacy_input, + buffered_stream->source); + + STRUCT_TO_BUF(buffered_stream->output, &legacy_output); + STRUCT_TO_BUF(&buffered_stream->input, &legacy_input); + } else +#endif + error = buffered_stream->write_fn( + buffered_stream->filter, + buffered_stream->payload, + buffered_stream->output, + &buffered_stream->input, + buffered_stream->source); + + if (error == GIT_PASSTHROUGH) { + writebuf = &buffered_stream->input; + } else if (error == 0) { + writebuf = buffered_stream->output; + } else { + /* close stream before erroring out taking care + * to preserve the original error */ + git_error_state_capture(&error_state, error); + buffered_stream->target->close(buffered_stream->target); + git_error_state_restore(&error_state); + return error; + } + + if ((error = buffered_stream->target->write( + buffered_stream->target, writebuf->ptr, writebuf->size)) == 0) + error = buffered_stream->target->close(buffered_stream->target); + + return error; +} + +static void buffered_stream_free(git_writestream *s) +{ + struct buffered_stream *buffered_stream = (struct buffered_stream *)s; + + if (buffered_stream) { + git_str_dispose(&buffered_stream->input); + git_str_dispose(&buffered_stream->temp_buf); + git__free(buffered_stream); + } +} + +int git_filter_buffered_stream_new( + git_writestream **out, + git_filter *filter, + int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), + git_str *temp_buf, + void **payload, + const git_filter_source *source, + git_writestream *target) +{ + struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); + GIT_ERROR_CHECK_ALLOC(buffered_stream); + + buffered_stream->parent.write = buffered_stream_write; + buffered_stream->parent.close = buffered_stream_close; + buffered_stream->parent.free = buffered_stream_free; + buffered_stream->filter = filter; + buffered_stream->write_fn = write_fn; + buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; + buffered_stream->payload = payload; + buffered_stream->source = source; + buffered_stream->target = target; + + if (temp_buf) + git_str_clear(temp_buf); + + *out = (git_writestream *)buffered_stream; + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +static int buffered_legacy_stream_new( + git_writestream **out, + git_filter *filter, + int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *), + git_str *temp_buf, + void **payload, + const git_filter_source *source, + git_writestream *target) +{ + struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); + GIT_ERROR_CHECK_ALLOC(buffered_stream); + + buffered_stream->parent.write = buffered_stream_write; + buffered_stream->parent.close = buffered_stream_close; + buffered_stream->parent.free = buffered_stream_free; + buffered_stream->filter = filter; + buffered_stream->legacy_write_fn = legacy_write_fn; + buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; + buffered_stream->payload = payload; + buffered_stream->source = source; + buffered_stream->target = target; + + if (temp_buf) + git_str_clear(temp_buf); + + *out = (git_writestream *)buffered_stream; + return 0; +} +#endif + +static int setup_stream( + git_writestream **out, + git_filter_entry *fe, + git_filter_list *filters, + git_writestream *last_stream) +{ +#ifndef GIT_DEPRECATE_HARD + GIT_ASSERT(fe->filter->stream || fe->filter->apply); + + /* + * If necessary, create a stream that proxies the traditional + * application. + */ + if (!fe->filter->stream) { + /* Create a stream that proxies the one-shot apply */ + return buffered_legacy_stream_new(out, + fe->filter, fe->filter->apply, filters->temp_buf, + &fe->payload, &filters->source, last_stream); + } +#endif + + GIT_ASSERT(fe->filter->stream); + return fe->filter->stream(out, fe->filter, + &fe->payload, &filters->source, last_stream); +} + +static int stream_list_init( + git_writestream **out, + git_vector *streams, + git_filter_list *filters, + git_writestream *target) +{ + git_writestream *last_stream = target; + size_t i; + int error = 0; + + *out = NULL; + + if (!filters) { + *out = target; + return 0; + } + + /* Create filters last to first to get the chaining direction */ + for (i = 0; i < git_array_size(filters->filters); ++i) { + size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ? + git_array_size(filters->filters) - 1 - i : i; + + git_filter_entry *fe = git_array_get(filters->filters, filter_idx); + git_writestream *filter_stream; + + error = setup_stream(&filter_stream, fe, filters, last_stream); + + if (error < 0) + goto out; + + git_vector_insert(streams, filter_stream); + last_stream = filter_stream; + } + +out: + if (error) + last_stream->close(last_stream); + else + *out = last_stream; + + return error; +} + +static void filter_streams_free(git_vector *streams) +{ + git_writestream *stream; + size_t i; + + git_vector_foreach(streams, i, stream) + stream->free(stream); + git_vector_free(streams); +} + +int git_filter_list_stream_file( + git_filter_list *filters, + git_repository *repo, + const char *path, + git_writestream *target) +{ + char buf[GIT_BUFSIZE_FILTERIO]; + git_str abspath = GIT_STR_INIT; + const char *base = repo ? git_repository_workdir(repo) : NULL; + git_vector filter_streams = GIT_VECTOR_INIT; + git_writestream *stream_start; + ssize_t readlen; + int fd = -1, error, initialized = 0; + + if ((error = stream_list_init( + &stream_start, &filter_streams, filters, target)) < 0 || + (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 || + (error = git_path_validate_str_length(repo, &abspath)) < 0) + goto done; + + initialized = 1; + + if ((fd = git_futils_open_ro(abspath.ptr)) < 0) { + error = fd; + goto done; + } + + while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) { + if ((error = stream_start->write(stream_start, buf, readlen)) < 0) + goto done; + } + + if (readlen < 0) + error = -1; + +done: + if (initialized) + error |= stream_start->close(stream_start); + + if (fd >= 0) + p_close(fd); + filter_streams_free(&filter_streams); + git_str_dispose(&abspath); + return error; +} + +int git_filter_list_stream_buffer( + git_filter_list *filters, + const char *buffer, + size_t len, + git_writestream *target) +{ + git_vector filter_streams = GIT_VECTOR_INIT; + git_writestream *stream_start; + int error, initialized = 0; + + if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0) + goto out; + initialized = 1; + + if ((error = stream_start->write(stream_start, buffer, len)) < 0) + goto out; + +out: + if (initialized) + error |= stream_start->close(stream_start); + + filter_streams_free(&filter_streams); + return error; +} + +int git_filter_list_stream_blob( + git_filter_list *filters, + git_blob *blob, + git_writestream *target) +{ + git_str in = GIT_STR_INIT; + + if (buf_from_blob(&in, blob) < 0) + return -1; + + if (filters) + git_oid_cpy(&filters->source.oid, git_blob_id(blob)); + + return git_filter_list_stream_buffer(filters, in.ptr, in.size, target); +} + +int git_filter_init(git_filter *filter, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD + +int git_filter_list_stream_data( + git_filter_list *filters, + git_buf *data, + git_writestream *target) +{ + return git_filter_list_stream_buffer(filters, data->ptr, data->size, target); +} + +int git_filter_list_apply_to_data( + git_buf *tgt, git_filter_list *filters, git_buf *src) +{ + return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size); +} + +#endif diff --git a/src/libgit2/filter.h b/src/libgit2/filter.h new file mode 100644 index 000000000..58cb4b424 --- /dev/null +++ b/src/libgit2/filter.h @@ -0,0 +1,85 @@ +/* + * 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_filter_h__ +#define INCLUDE_filter_h__ + +#include "common.h" + +#include "attr_file.h" +#include "git2/filter.h" +#include "git2/sys/filter.h" + +/* Amount of file to examine for NUL byte when checking binary-ness */ +#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 + +typedef struct { + git_filter_options options; + git_attr_session *attr_session; + git_str *temp_buf; +} git_filter_session; + +#define GIT_FILTER_SESSION_INIT {GIT_FILTER_OPTIONS_INIT, 0} + +extern int git_filter_global_init(void); + +extern void git_filter_free(git_filter *filter); + +extern int git_filter_list__load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + git_filter_session *filter_session); + +int git_filter_list__apply_to_buffer( + git_str *out, + git_filter_list *filters, + const char *in, + size_t in_len); +int git_filter_list__apply_to_file( + git_str *out, + git_filter_list *filters, + git_repository *repo, + const char *path); +int git_filter_list__apply_to_blob( + git_str *out, + git_filter_list *filters, + git_blob *blob); + +/* + * The given input buffer will be converted to the given output buffer. + * The input buffer will be freed (_if_ it was allocated). + */ +extern int git_filter_list__convert_buf( + git_str *out, + git_filter_list *filters, + git_str *in); + +extern int git_filter_list__apply_to_file( + git_str *out, + git_filter_list *filters, + git_repository *repo, + const char *path); + +/* + * Available filters + */ + +extern git_filter *git_crlf_filter_new(void); +extern git_filter *git_ident_filter_new(void); + +extern int git_filter_buffered_stream_new( + git_writestream **out, + git_filter *filter, + int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), + git_str *temp_buf, + void **payload, + const git_filter_source *source, + git_writestream *target); + +#endif diff --git a/src/libgit2/git2.rc b/src/libgit2/git2.rc new file mode 100644 index 000000000..d273afd70 --- /dev/null +++ b/src/libgit2/git2.rc @@ -0,0 +1,59 @@ +#include +#include "../../include/git2/version.h" + +#ifndef LIBGIT2_FILENAME +# ifdef __GNUC__ +# define LIBGIT2_FILENAME git2 +# else +# define LIBGIT2_FILENAME "git2" +# endif +#endif + +#ifndef LIBGIT2_COMMENTS +# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" +#endif + +#ifdef __GNUC__ +# define _STR(x) #x +# define STR(x) _STR(x) +#else +# define STR(x) x +#endif + +#ifdef __GNUC__ +VS_VERSION_INFO VERSIONINFO +#else +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE +#endif + FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0 +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "libgit2 - the Git linkable library\0" + VALUE "FileVersion", LIBGIT2_VERSION "\0" + VALUE "InternalName", STR(LIBGIT2_FILENAME) ".dll\0" + VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0" + VALUE "OriginalFilename", STR(LIBGIT2_FILENAME) ".dll\0" + VALUE "ProductName", "libgit2\0" + VALUE "ProductVersion", LIBGIT2_VERSION "\0" + VALUE "Comments", LIBGIT2_COMMENTS "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/src/libgit2/graph.c b/src/libgit2/graph.c new file mode 100644 index 000000000..35e914f74 --- /dev/null +++ b/src/libgit2/graph.c @@ -0,0 +1,249 @@ +/* + * 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 "revwalk.h" +#include "merge.h" +#include "git2/graph.h" + +static int interesting(git_pqueue *list, git_commit_list *roots) +{ + unsigned int i; + + for (i = 0; i < git_pqueue_size(list); i++) { + git_commit_list_node *commit = git_pqueue_get(list, i); + if ((commit->flags & STALE) == 0) + return 1; + } + + while(roots) { + if ((roots->item->flags & STALE) == 0) + return 1; + roots = roots->next; + } + + return 0; +} + +static int mark_parents(git_revwalk *walk, git_commit_list_node *one, + git_commit_list_node *two) +{ + unsigned int i; + git_commit_list *roots = NULL; + git_pqueue list; + + /* if the commit is repeated, we have a our merge base already */ + if (one == two) { + one->flags |= PARENT1 | PARENT2 | RESULT; + return 0; + } + + if (git_pqueue_init(&list, 0, 2, git_commit_list_generation_cmp) < 0) + return -1; + + if (git_commit_list_parse(walk, one) < 0) + goto on_error; + one->flags |= PARENT1; + if (git_pqueue_insert(&list, one) < 0) + goto on_error; + + if (git_commit_list_parse(walk, two) < 0) + goto on_error; + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) + goto on_error; + + /* as long as there are non-STALE commits */ + while (interesting(&list, roots)) { + git_commit_list_node *commit = git_pqueue_pop(&list); + unsigned int flags; + + if (commit == NULL) + break; + + flags = commit->flags & (PARENT1 | PARENT2 | STALE); + if (flags == (PARENT1 | PARENT2)) { + if (!(commit->flags & RESULT)) + commit->flags |= RESULT; + /* we mark the parents of a merge stale */ + flags |= STALE; + } + + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if ((p->flags & flags) == flags) + continue; + + if (git_commit_list_parse(walk, p) < 0) + goto on_error; + + p->flags |= flags; + if (git_pqueue_insert(&list, p) < 0) + goto on_error; + } + + /* Keep track of root commits, to make sure the path gets marked */ + if (commit->out_degree == 0) { + if (git_commit_list_insert(commit, &roots) == NULL) + goto on_error; + } + } + + git_commit_list_free(&roots); + git_pqueue_free(&list); + return 0; + +on_error: + git_commit_list_free(&roots); + git_pqueue_free(&list); + return -1; +} + + +static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two, + size_t *ahead, size_t *behind) +{ + git_commit_list_node *commit; + git_pqueue pq; + int error = 0, i; + *ahead = 0; + *behind = 0; + + if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0) + return -1; + + if ((error = git_pqueue_insert(&pq, one)) < 0 || + (error = git_pqueue_insert(&pq, two)) < 0) + goto done; + + while ((commit = git_pqueue_pop(&pq)) != NULL) { + if (commit->flags & RESULT || + (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2)) + continue; + else if (commit->flags & PARENT1) + (*ahead)++; + else if (commit->flags & PARENT2) + (*behind)++; + + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if ((error = git_pqueue_insert(&pq, p)) < 0) + goto done; + } + commit->flags |= RESULT; + } + +done: + git_pqueue_free(&pq); + return error; +} + +int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, + const git_oid *local, const git_oid *upstream) +{ + git_revwalk *walk; + git_commit_list_node *commit_u, *commit_l; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + commit_u = git_revwalk__commit_lookup(walk, upstream); + if (commit_u == NULL) + goto on_error; + + commit_l = git_revwalk__commit_lookup(walk, local); + if (commit_l == NULL) + goto on_error; + + if (mark_parents(walk, commit_l, commit_u) < 0) + goto on_error; + if (ahead_behind(commit_l, commit_u, ahead, behind) < 0) + goto on_error; + + git_revwalk_free(walk); + + return 0; + +on_error: + git_revwalk_free(walk); + return -1; +} + +int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor) +{ + if (git_oid_equal(commit, ancestor)) + return 0; + + return git_graph_reachable_from_any(repo, ancestor, commit, 1); +} + +int git_graph_reachable_from_any( + git_repository *repo, + const git_oid *commit_id, + const git_oid descendant_array[], + size_t length) +{ + git_revwalk *walk = NULL; + git_vector list; + git_commit_list *result = NULL; + git_commit_list_node *commit; + size_t i; + uint32_t minimum_generation = 0xffffffff; + int error = 0; + + if (!length) + return 0; + + for (i = 0; i < length; ++i) { + if (git_oid_equal(commit_id, &descendant_array[i])) + return 1; + } + + if ((error = git_vector_init(&list, length + 1, NULL)) < 0) + return error; + + if ((error = git_revwalk_new(&walk, repo)) < 0) + goto done; + + for (i = 0; i < length; i++) { + commit = git_revwalk__commit_lookup(walk, &descendant_array[i]); + if (commit == NULL) { + error = -1; + goto done; + } + + git_vector_insert(&list, commit); + if (minimum_generation > commit->generation) + minimum_generation = commit->generation; + } + + commit = git_revwalk__commit_lookup(walk, commit_id); + if (commit == NULL) { + error = -1; + goto done; + } + + if (minimum_generation > commit->generation) + minimum_generation = commit->generation; + + if ((error = git_merge__bases_many(&result, walk, commit, &list, minimum_generation)) < 0) + goto done; + + if (result) { + error = git_oid_equal(commit_id, &result->item->oid); + } else { + /* No merge-base found, it's not a descendant */ + error = 0; + } + +done: + git_commit_list_free(&result); + git_vector_free(&list); + git_revwalk_free(walk); + return error; +} diff --git a/src/libgit2/hashsig.c b/src/libgit2/hashsig.c new file mode 100644 index 000000000..6b4fb8352 --- /dev/null +++ b/src/libgit2/hashsig.c @@ -0,0 +1,375 @@ +/* + * 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/sys/hashsig.h" +#include "futils.h" +#include "util.h" + +typedef uint32_t hashsig_t; +typedef uint64_t hashsig_state; + +#define HASHSIG_SCALE 100 + +#define HASHSIG_MAX_RUN 80 +#define HASHSIG_HASH_START INT64_C(0x012345678ABCDEF0) +#define HASHSIG_HASH_SHIFT 5 + +#define HASHSIG_HASH_MIX(S,CH) \ + (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH) + +#define HASHSIG_HEAP_SIZE ((1 << 7) - 1) +#define HASHSIG_HEAP_MIN_SIZE 4 + +typedef int (*hashsig_cmp)(const void *a, const void *b, void *); + +typedef struct { + int size, asize; + hashsig_cmp cmp; + hashsig_t values[HASHSIG_HEAP_SIZE]; +} hashsig_heap; + +struct git_hashsig { + hashsig_heap mins; + hashsig_heap maxs; + size_t lines; + git_hashsig_option_t opt; +}; + +#define HEAP_LCHILD_OF(I) (((I)<<1)+1) +#define HEAP_RCHILD_OF(I) (((I)<<1)+2) +#define HEAP_PARENT_OF(I) (((I)-1)>>1) + +static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) +{ + h->size = 0; + h->asize = HASHSIG_HEAP_SIZE; + h->cmp = cmp; +} + +static int hashsig_cmp_max(const void *a, const void *b, void *payload) +{ + hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; + GIT_UNUSED(payload); + return (av < bv) ? -1 : (av > bv) ? 1 : 0; +} + +static int hashsig_cmp_min(const void *a, const void *b, void *payload) +{ + hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; + GIT_UNUSED(payload); + return (av > bv) ? -1 : (av < bv) ? 1 : 0; +} + +static void hashsig_heap_up(hashsig_heap *h, int el) +{ + int parent_el = HEAP_PARENT_OF(el); + + while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) { + hashsig_t t = h->values[el]; + h->values[el] = h->values[parent_el]; + h->values[parent_el] = t; + + el = parent_el; + parent_el = HEAP_PARENT_OF(el); + } +} + +static void hashsig_heap_down(hashsig_heap *h, int el) +{ + hashsig_t v, lv, rv; + + /* 'el < h->size / 2' tests if el is bottom row of heap */ + + while (el < h->size / 2) { + int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel; + + v = h->values[el]; + lv = h->values[lel]; + rv = h->values[rel]; + + if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0) + break; + + swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel; + + h->values[el] = h->values[swapel]; + h->values[swapel] = v; + + el = swapel; + } +} + +static void hashsig_heap_sort(hashsig_heap *h) +{ + /* only need to do this at the end for signature comparison */ + git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL); +} + +static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) +{ + /* if heap is not full, insert new element */ + if (h->size < h->asize) { + h->values[h->size++] = val; + hashsig_heap_up(h, h->size - 1); + } + + /* if heap is full, pop top if new element should replace it */ + else if (h->cmp(&val, &h->values[0], NULL) > 0) { + h->size--; + h->values[0] = h->values[h->size]; + hashsig_heap_down(h, 0); + } + +} + +typedef struct { + int use_ignores; + uint8_t ignore_ch[256]; +} hashsig_in_progress; + +static int hashsig_in_progress_init( + hashsig_in_progress *prog, git_hashsig *sig) +{ + int i; + + /* no more than one can be set */ + GIT_ASSERT(!(sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) || + !(sig->opt & GIT_HASHSIG_SMART_WHITESPACE)); + + if (sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) { + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace_nonlf(i); + prog->use_ignores = 1; + } else if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) { + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace(i); + prog->use_ignores = 1; + } else { + memset(prog, 0, sizeof(*prog)); + } + + return 0; +} + +static int hashsig_add_hashes( + git_hashsig *sig, + const uint8_t *data, + size_t size, + hashsig_in_progress *prog) +{ + const uint8_t *scan = data, *end = data + size; + hashsig_state state = HASHSIG_HASH_START; + int use_ignores = prog->use_ignores, len; + uint8_t ch; + + while (scan < end) { + state = HASHSIG_HASH_START; + + for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) { + ch = *scan; + + if (use_ignores) + for (; scan < end && git__isspace_nonlf(ch); ch = *scan) + ++scan; + else if (sig->opt & + (GIT_HASHSIG_IGNORE_WHITESPACE | GIT_HASHSIG_SMART_WHITESPACE)) + for (; scan < end && ch == '\r'; ch = *scan) + ++scan; + + /* peek at next character to decide what to do next */ + if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) + use_ignores = (ch == '\n'); + + if (scan >= end) + break; + ++scan; + + /* check run terminator */ + if (ch == '\n' || ch == '\0') { + sig->lines++; + break; + } + + ++len; + HASHSIG_HASH_MIX(state, ch); + } + + if (len > 0) { + hashsig_heap_insert(&sig->mins, (hashsig_t)state); + hashsig_heap_insert(&sig->maxs, (hashsig_t)state); + + while (scan < end && (*scan == '\n' || !*scan)) + ++scan; + } + } + + prog->use_ignores = use_ignores; + + return 0; +} + +static int hashsig_finalize_hashes(git_hashsig *sig) +{ + if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE && + !(sig->opt & GIT_HASHSIG_ALLOW_SMALL_FILES)) { + git_error_set(GIT_ERROR_INVALID, + "file too small for similarity signature calculation"); + return GIT_EBUFS; + } + + hashsig_heap_sort(&sig->mins); + hashsig_heap_sort(&sig->maxs); + + return 0; +} + +static git_hashsig *hashsig_alloc(git_hashsig_option_t opts) +{ + git_hashsig *sig = git__calloc(1, sizeof(git_hashsig)); + if (!sig) + return NULL; + + hashsig_heap_init(&sig->mins, hashsig_cmp_min); + hashsig_heap_init(&sig->maxs, hashsig_cmp_max); + sig->opt = opts; + + return sig; +} + +int git_hashsig_create( + git_hashsig **out, + const char *buf, + size_t buflen, + git_hashsig_option_t opts) +{ + int error; + hashsig_in_progress prog; + git_hashsig *sig = hashsig_alloc(opts); + GIT_ERROR_CHECK_ALLOC(sig); + + if ((error = hashsig_in_progress_init(&prog, sig)) < 0) + return error; + + error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog); + + if (!error) + error = hashsig_finalize_hashes(sig); + + if (!error) + *out = sig; + else + git_hashsig_free(sig); + + return error; +} + +int git_hashsig_create_fromfile( + git_hashsig **out, + const char *path, + git_hashsig_option_t opts) +{ + uint8_t buf[0x1000]; + ssize_t buflen = 0; + int error = 0, fd; + hashsig_in_progress prog; + git_hashsig *sig = hashsig_alloc(opts); + GIT_ERROR_CHECK_ALLOC(sig); + + if ((fd = git_futils_open_ro(path)) < 0) { + git__free(sig); + return fd; + } + + if ((error = hashsig_in_progress_init(&prog, sig)) < 0) { + p_close(fd); + return error; + } + + while (!error) { + if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { + if ((error = (int)buflen) < 0) + git_error_set(GIT_ERROR_OS, + "read error on '%s' calculating similarity hashes", path); + break; + } + + error = hashsig_add_hashes(sig, buf, buflen, &prog); + } + + p_close(fd); + + if (!error) + error = hashsig_finalize_hashes(sig); + + if (!error) + *out = sig; + else + git_hashsig_free(sig); + + return error; +} + +void git_hashsig_free(git_hashsig *sig) +{ + git__free(sig); +} + +static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) +{ + int matches = 0, i, j, cmp; + + GIT_ASSERT_WITH_RETVAL(a->cmp == b->cmp, 0); + + /* hash heaps are sorted - just look for overlap vs total */ + + for (i = 0, j = 0; i < a->size && j < b->size; ) { + cmp = a->cmp(&a->values[i], &b->values[j], NULL); + + if (cmp < 0) + ++i; + else if (cmp > 0) + ++j; + else { + ++i; ++j; ++matches; + } + } + + return HASHSIG_SCALE * (matches * 2) / (a->size + b->size); +} + +int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) +{ + /* if we have no elements in either file then each file is either + * empty or blank. if we're ignoring whitespace then the files are + * similar, otherwise they're dissimilar. + */ + if (a->mins.size == 0 && b->mins.size == 0) { + if ((!a->lines && !b->lines) || + (a->opt & GIT_HASHSIG_IGNORE_WHITESPACE)) + return HASHSIG_SCALE; + else + return 0; + } + + /* if we have fewer than the maximum number of elements, then just use + * one array since the two arrays will be the same + */ + if (a->mins.size < HASHSIG_HEAP_SIZE) { + return hashsig_heap_compare(&a->mins, &b->mins); + } else { + int mins, maxs; + + if ((mins = hashsig_heap_compare(&a->mins, &b->mins)) < 0) + return mins; + if ((maxs = hashsig_heap_compare(&a->maxs, &b->maxs)) < 0) + return maxs; + + return (mins + maxs) / 2; + } +} diff --git a/src/libgit2/ident.c b/src/libgit2/ident.c new file mode 100644 index 000000000..53095864e --- /dev/null +++ b/src/libgit2/ident.c @@ -0,0 +1,139 @@ +/* + * 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/sys/filter.h" +#include "filter.h" +#include "str.h" + +static int ident_find_id( + const char **id_start, const char **id_end, const char *start, size_t len) +{ + const char *end = start + len, *found = NULL; + + while (len > 3 && (found = memchr(start, '$', len)) != NULL) { + size_t remaining = (size_t)(end - found) - 1; + if (remaining < 3) + return GIT_ENOTFOUND; + + start = found + 1; + len = remaining; + + if (start[0] == 'I' && start[1] == 'd') + break; + } + + if (len < 3 || !found) + return GIT_ENOTFOUND; + *id_start = found; + + if ((found = memchr(start + 2, '$', len - 2)) == NULL) + return GIT_ENOTFOUND; + + *id_end = found + 1; + return 0; +} + +static int ident_insert_id( + git_str *to, const git_str *from, const git_filter_source *src) +{ + char oid[GIT_OID_HEXSZ+1]; + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + /* replace $Id$ with blob id */ + + if (!git_filter_source_id(src)) + return GIT_PASSTHROUGH; + + git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src)); + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 5 /* "$Id: " */ + GIT_OID_HEXSZ + 2 /* " $" */ + + (size_t)(from_end - id_end); + + if (git_str_grow(to, need_size) < 0) + return -1; + + git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_str_put(to, "$Id: ", 5); + git_str_put(to, oid, GIT_OID_HEXSZ); + git_str_put(to, " $", 2); + git_str_put(to, id_end, (size_t)(from_end - id_end)); + + return git_str_oom(to) ? -1 : 0; +} + +static int ident_remove_id( + git_str *to, const git_str *from) +{ + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 4 /* "$Id$" */ + (size_t)(from_end - id_end); + + if (git_str_grow(to, need_size) < 0) + return -1; + + git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_str_put(to, "$Id$", 4); + git_str_put(to, id_end, (size_t)(from_end - id_end)); + + return git_str_oom(to) ? -1 : 0; +} + +static int ident_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *src) +{ + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* Don't filter binary files */ + if (git_str_is_binary(from)) + return GIT_PASSTHROUGH; + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + return ident_insert_id(to, from, src); + else + return ident_remove_id(to, from); +} + +static int ident_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, ident_apply, NULL, payload, src, next); +} + +git_filter *git_ident_filter_new(void) +{ + git_filter *f = git__calloc(1, sizeof(git_filter)); + if (f == NULL) + return NULL; + + f->version = GIT_FILTER_VERSION; + f->attributes = "+ident"; /* apply to files with ident attribute set */ + f->shutdown = git_filter_free; + f->stream = ident_stream; + + return f; +} diff --git a/src/libgit2/idxmap.c b/src/libgit2/idxmap.c new file mode 100644 index 000000000..bc23608f2 --- /dev/null +++ b/src/libgit2/idxmap.c @@ -0,0 +1,157 @@ +/* + * 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 "idxmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) +__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) + +/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ +static kh_inline khint_t idxentry_hash(const git_index_entry *e) +{ + const char *s = e->path; + khint_t h = (khint_t)git__tolower(*s); + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); + return h + GIT_INDEX_ENTRY_STAGE(e); +} + +#define idxentry_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) +#define idxentry_icase_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) + +__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_new(git_idxmap **out) +{ + *out = kh_init(idx); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +int git_idxmap_icase_new(git_idxmap_icase **out) +{ + *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); +} + +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; +} + +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); + return 0; +} diff --git a/src/libgit2/idxmap.h b/src/libgit2/idxmap.h new file mode 100644 index 000000000..76170ef32 --- /dev/null +++ b/src/libgit2/idxmap.h @@ -0,0 +1,177 @@ +/* + * 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_idxmap_h__ +#define INCLUDE_idxmap_h__ + +#include "common.h" + +#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; + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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/libgit2/ignore.c b/src/libgit2/ignore.c new file mode 100644 index 000000000..cee58d7f1 --- /dev/null +++ b/src/libgit2/ignore.c @@ -0,0 +1,652 @@ +/* + * 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 "ignore.h" + +#include "git2/ignore.h" +#include "common.h" +#include "attrcache.h" +#include "fs_path.h" +#include "config.h" +#include "wildmatch.h" +#include "path.h" + +#define GIT_IGNORE_INTERNAL "[internal]exclude" + +#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n" + +/** + * A negative ignore pattern can negate a positive one without + * wildcards if it is a basename only and equals the basename of + * the positive pattern. Thus + * + * foo/bar + * !bar + * + * would result in foo/bar being unignored again while + * + * moo/foo/bar + * !foo/bar + * + * would do nothing. The reverse also holds true: a positive + * basename pattern can be negated by unignoring the basename in + * subdirectories. Thus + * + * bar + * !foo/bar + * + * would result in foo/bar being unignored again. As with the + * first case, + * + * foo/bar + * !moo/foo/bar + * + * would do nothing, again. + */ +static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) +{ + int (*cmp)(const char *, const char *, size_t); + git_attr_fnmatch *longer, *shorter; + char *p; + + if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 + || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) + return false; + + if (neg->flags & GIT_ATTR_FNMATCH_ICASE) + cmp = git__strncasecmp; + else + cmp = git__strncmp; + + /* If lengths match we need to have an exact match */ + if (rule->length == neg->length) { + return cmp(rule->pattern, neg->pattern, rule->length) == 0; + } else if (rule->length < neg->length) { + shorter = rule; + longer = neg; + } else { + shorter = neg; + longer = rule; + } + + /* Otherwise, we need to check if the shorter + * rule is a basename only (that is, it contains + * no path separator) and, if so, if it + * matches the tail of the longer rule */ + p = longer->pattern + longer->length - shorter->length; + + if (p[-1] != '/') + return false; + if (memchr(shorter->pattern, '/', shorter->length) != NULL) + return false; + + return cmp(p, shorter->pattern, shorter->length) == 0; +} + +/** + * A negative ignore can only unignore a file which is given explicitly before, thus + * + * foo + * !foo/bar + * + * does not unignore 'foo/bar' as it's not in the list. However + * + * foo/ + * !foo/bar + * + * does unignore 'foo/bar', as it is contained within the 'foo/' rule. + */ +static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) +{ + int error = 0, wildmatch_flags, effective_flags; + size_t i; + git_attr_fnmatch *rule; + char *path; + git_str buf = GIT_STR_INIT; + + *out = 0; + + wildmatch_flags = WM_PATHNAME; + if (match->flags & GIT_ATTR_FNMATCH_ICASE) + wildmatch_flags |= WM_CASEFOLD; + + /* path of the file relative to the workdir, so we match the rules in subdirs */ + if (match->containing_dir) { + git_str_puts(&buf, match->containing_dir); + } + if (git_str_puts(&buf, match->pattern) < 0) + return -1; + + path = git_str_detach(&buf); + + git_vector_foreach(rules, i, rule) { + if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) { + if (does_negate_pattern(rule, match)) { + error = 0; + *out = 1; + goto out; + } + else + continue; + } + + git_str_clear(&buf); + if (rule->containing_dir) + git_str_puts(&buf, rule->containing_dir); + git_str_puts(&buf, rule->pattern); + + if (git_str_oom(&buf)) + goto out; + + /* + * if rule isn't for full path we match without PATHNAME flag + * as lines like *.txt should match something like dir/test.txt + * requiring * to also match / + */ + effective_flags = wildmatch_flags; + if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH)) + effective_flags &= ~WM_PATHNAME; + + /* if we found a match, we want to keep this rule */ + if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) { + *out = 1; + error = 0; + goto out; + } + } + + error = 0; + +out: + git__free(path); + git_str_dispose(&buf); + return error; +} + +static int parse_ignore_file( + 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; + + 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 */ + if (attrs->entry && + git_fs_path_root(attrs->entry->path) < 0 && + !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE)) + context = attrs->entry->path; + + if (git_mutex_lock(&attrs->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock ignore file"); + return -1; + } + + while (!error && *scan) { + int valid_rule = 1; + + if (!match && !(match = git__calloc(1, sizeof(*match)))) { + error = -1; + break; + } + + match->flags = + GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; + + if (!(error = git_attr_fnmatch__parse( + match, &attrs->pool, context, &scan))) + { + match->flags |= GIT_ATTR_FNMATCH_IGNORE; + + if (ignore_case) + match->flags |= GIT_ATTR_FNMATCH_ICASE; + + scan = git__next_line(scan); + + /* + * If a negative match doesn't actually do anything, + * throw it away. As we cannot always verify whether a + * rule containing wildcards negates another rule, we + * do not optimize away these rules, though. + * */ + if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE + && !(match->flags & GIT_ATTR_FNMATCH_HASWILD)) + error = does_negate_rule(&valid_rule, &attrs->rules, match); + + if (!error && valid_rule) + error = git_vector_insert(&attrs->rules, match); + } + + if (error != 0 || !valid_rule) { + match->pattern = NULL; + + if (error == GIT_ENOTFOUND) + error = 0; + } else { + match = NULL; /* vector now "owns" the match */ + } + } + + git_mutex_unlock(&attrs->lock); + git__free(match); + + return error; +} + +static int push_ignore_file( + git_ignores *ignores, + git_vector *which_list, + const char *base, + const char *filename) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; + git_attr_file *file = NULL; + int error = 0; + + error = git_attr_cache__get(&file, ignores->repo, NULL, &source, parse_ignore_file, false); + + if (error < 0) + return error; + + if (file != NULL) { + if ((error = git_vector_insert(which_list, file)) < 0) + git_attr_file__free(file); + } + + return error; +} + +static int push_one_ignore(void *payload, const char *path) +{ + git_ignores *ign = payload; + ign->depth++; + return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE); +} + +static int get_internal_ignores(git_attr_file **out, git_repository *repo) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_MEMORY, NULL, GIT_IGNORE_INTERNAL }; + int error; + + if ((error = git_attr_cache__init(repo)) < 0) + return error; + + error = git_attr_cache__get(out, repo, NULL, &source, 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, false); + + return error; +} + +int git_ignore__for_path( + git_repository *repo, + const char *path, + git_ignores *ignores) +{ + int error = 0; + const char *workdir = git_repository_workdir(repo); + git_str infopath = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ignores); + GIT_ASSERT_ARG(path); + + memset(ignores, 0, sizeof(*ignores)); + ignores->repo = repo; + + /* Read the ignore_case flag */ + if ((error = git_repository__configmap_lookup( + &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) + goto cleanup; + + if ((error = git_attr_cache__init(repo)) < 0) + goto cleanup; + + /* given a unrooted path in a non-bare repo, resolve it */ + if (workdir && git_fs_path_root(path) < 0) { + git_str local = GIT_STR_INIT; + + if ((error = git_fs_path_dirname_r(&local, path)) < 0 || + (error = git_fs_path_resolve_relative(&local, 0)) < 0 || + (error = git_fs_path_to_dir(&local)) < 0 || + (error = git_str_joinpath(&ignores->dir, workdir, local.ptr)) < 0 || + (error = git_path_validate_str_length(repo, &ignores->dir)) < 0) { + /* Nothing, we just want to stop on the first error */ + } + + git_str_dispose(&local); + } else { + if (!(error = git_str_joinpath(&ignores->dir, path, ""))) + error = git_path_validate_str_length(NULL, &ignores->dir); + } + + if (error < 0) + goto cleanup; + + if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir)) + ignores->dir_root = strlen(workdir); + + /* set up internals */ + if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0) + goto cleanup; + + /* load .gitignore up the path */ + if (workdir != NULL) { + error = git_fs_path_walk_up( + &ignores->dir, workdir, push_one_ignore, ignores); + 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) + error = push_ignore_file( + ignores, &ignores->ign_global, NULL, + git_repository_attr_cache(repo)->cfg_excl_file); + +cleanup: + git_str_dispose(&infopath); + if (error < 0) + git_ignore__free(ignores); + + return error; +} + +int git_ignore__push_dir(git_ignores *ign, const char *dir) +{ + if (git_str_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) + return -1; + + ign->depth++; + + return push_ignore_file( + ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); +} + +int git_ignore__pop_dir(git_ignores *ign) +{ + if (ign->ign_path.length > 0) { + git_attr_file *file = git_vector_last(&ign->ign_path); + const char *start = file->entry->path, *end; + + /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/") + * - file->path looks something like "a/b/.gitignore + * + * We are popping the last directory off ign->dir. We also want + * to remove the file from the vector if the popped directory + * matches the ignore path. We need to test if the "a/b" part of + * the file key matches the path we are about to pop. + */ + + if ((end = strrchr(start, '/')) != NULL) { + size_t dirlen = (end - start) + 1; + const char *relpath = ign->dir.ptr + ign->dir_root; + size_t pathlen = ign->dir.size - ign->dir_root; + + if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) { + git_vector_pop(&ign->ign_path); + git_attr_file__free(file); + } + } + } + + if (--ign->depth > 0) { + git_str_rtruncate_at_char(&ign->dir, '/'); + git_fs_path_to_dir(&ign->dir); + } + + return 0; +} + +void git_ignore__free(git_ignores *ignores) +{ + unsigned int i; + git_attr_file *file; + + git_attr_file__free(ignores->ign_internal); + + git_vector_foreach(&ignores->ign_path, i, file) { + git_attr_file__free(file); + ignores->ign_path.contents[i] = NULL; + } + git_vector_free(&ignores->ign_path); + + git_vector_foreach(&ignores->ign_global, i, file) { + git_attr_file__free(file); + ignores->ign_global.contents[i] = NULL; + } + git_vector_free(&ignores->ign_global); + + git_str_dispose(&ignores->dir); +} + +static bool ignore_lookup_in_rules( + int *ignored, git_attr_file *file, git_attr_path *path) +{ + size_t j; + git_attr_fnmatch *match; + + git_vector_rforeach(&file->rules, j, match) { + if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && + path->is_dir == GIT_DIR_FLAG_FALSE) + continue; + if (git_attr_fnmatch__match(match, path)) { + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? + GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; + return true; + } + } + + return false; +} + +int git_ignore__lookup( + int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag) +{ + size_t i; + git_attr_file *file; + git_attr_path path; + + *out = GIT_IGNORE_NOTFOUND; + + if (git_attr_path__init( + &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0) + return -1; + + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) + goto cleanup; + + /* 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; + } + + /* last process global ignores */ + git_vector_foreach(&ignores->ign_global, i, file) { + if (ignore_lookup_in_rules(out, file, &path)) + goto cleanup; + } + +cleanup: + git_attr_path__free(&path); + return 0; +} + +int git_ignore_add_rule(git_repository *repo, const char *rules) +{ + int error; + git_attr_file *ign_internal = NULL; + + if ((error = get_internal_ignores(&ign_internal, repo)) < 0) + return error; + + error = parse_ignore_file(repo, ign_internal, rules, false); + git_attr_file__free(ign_internal); + + return error; +} + +int git_ignore_clear_internal_rules(git_repository *repo) +{ + int error; + git_attr_file *ign_internal; + + if ((error = get_internal_ignores(&ign_internal, repo)) < 0) + return error; + + if (!(error = git_attr_file__clear_rules(ign_internal, true))) + error = parse_ignore_file( + repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false); + + git_attr_file__free(ign_internal); + return error; +} + +int git_ignore_path_is_ignored( + int *ignored, + git_repository *repo, + const char *pathname) +{ + int error; + const char *workdir; + git_attr_path path; + git_ignores ignores; + unsigned int i; + git_attr_file *file; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ignored); + GIT_ASSERT_ARG(pathname); + + workdir = git_repository_workdir(repo); + + memset(&path, 0, sizeof(path)); + memset(&ignores, 0, sizeof(ignores)); + + if (!git__suffixcmp(pathname, "/")) + dir_flag = GIT_DIR_FLAG_TRUE; + else if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 || + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) + goto cleanup; + + while (1) { + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) + goto cleanup; + + /* next process files in the path */ + git_vector_foreach(&ignores.ign_path, i, file) { + if (ignore_lookup_in_rules(ignored, file, &path)) + goto cleanup; + } + + /* last process global ignores */ + git_vector_foreach(&ignores.ign_global, i, file) { + if (ignore_lookup_in_rules(ignored, file, &path)) + goto cleanup; + } + + /* move up one directory */ + if (path.basename == path.path) + break; + path.basename[-1] = '\0'; + while (path.basename > path.path && *path.basename != '/') + path.basename--; + if (path.basename > path.path) + path.basename++; + path.is_dir = 1; + + if ((error = git_ignore__pop_dir(&ignores)) < 0) + break; + } + + *ignored = 0; + +cleanup: + git_attr_path__free(&path); + git_ignore__free(&ignores); + return error; +} + +int git_ignore__check_pathspec_for_exact_ignores( + git_repository *repo, + git_vector *vspec, + bool no_fnmatch) +{ + int error = 0; + size_t i; + git_attr_fnmatch *match; + int ignored; + git_str path = GIT_STR_INIT; + const char *filename; + git_index *idx; + + if ((error = git_repository__ensure_not_bare( + repo, "validate pathspec")) < 0 || + (error = git_repository_index(&idx, repo)) < 0) + return error; + + git_vector_foreach(vspec, i, match) { + /* skip wildcard matches (if they are being used) */ + if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 && + !no_fnmatch) + continue; + + filename = match->pattern; + + /* if file is already in the index, it's fine */ + if (git_index_get_bypath(idx, filename, 0) != NULL) + continue; + + if ((error = git_repository_workdir_path(&path, repo, filename)) < 0) + break; + + /* is there a file on disk that matches this exactly? */ + if (!git_fs_path_isfile(path.ptr)) + continue; + + /* is that file ignored? */ + if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0) + break; + + if (ignored) { + git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'", + filename); + error = GIT_EINVALIDSPEC; + break; + } + } + + git_index_free(idx); + git_str_dispose(&path); + + return error; +} diff --git a/src/libgit2/ignore.h b/src/libgit2/ignore.h new file mode 100644 index 000000000..aa5ca62b7 --- /dev/null +++ b/src/libgit2/ignore.h @@ -0,0 +1,65 @@ +/* + * 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_ignore_h__ +#define INCLUDE_ignore_h__ + +#include "common.h" + +#include "repository.h" +#include "vector.h" +#include "attr_file.h" + +#define GIT_IGNORE_FILE ".gitignore" +#define GIT_IGNORE_FILE_INREPO "exclude" +#define GIT_IGNORE_FILE_XDG "ignore" + +/* The git_ignores structure maintains three sets of ignores: + * - internal ignores + * - per directory ignores + * - global ignores (at lower priority than the others) + * As you traverse from one directory to another, you can push and pop + * directories onto git_ignores list efficiently. + */ +typedef struct { + git_repository *repo; + git_str dir; /* current directory reflected in ign_path */ + git_attr_file *ign_internal; + git_vector ign_path; + git_vector ign_global; + size_t dir_root; /* offset in dir to repo root */ + int ignore_case; + int depth; +} git_ignores; + +extern int git_ignore__for_path( + git_repository *repo, const char *path, git_ignores *ign); + +extern int git_ignore__push_dir(git_ignores *ign, const char *dir); + +extern int git_ignore__pop_dir(git_ignores *ign); + +extern void git_ignore__free(git_ignores *ign); + +enum { + GIT_IGNORE_UNCHECKED = -2, + GIT_IGNORE_NOTFOUND = -1, + GIT_IGNORE_FALSE = 0, + GIT_IGNORE_TRUE = 1 +}; + +extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag); + +/* command line Git sometimes generates an error message if given a + * pathspec that contains an exact match to an ignored file (provided + * --force isn't also given). This makes it easy to check it that has + * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored + * exact matches (that are not already present in the index). + */ +extern int git_ignore__check_pathspec_for_exact_ignores( + git_repository *repo, git_vector *pathspec, bool no_fnmatch); + +#endif diff --git a/src/libgit2/index.c b/src/libgit2/index.c new file mode 100644 index 000000000..f44c507d3 --- /dev/null +++ b/src/libgit2/index.c @@ -0,0 +1,3765 @@ +/* + * 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 "index.h" + +#include + +#include "repository.h" +#include "tree.h" +#include "tree-cache.h" +#include "hash.h" +#include "iterator.h" +#include "pathspec.h" +#include "ignore.h" +#include "blob.h" +#include "idxmap.h" +#include "diff.h" +#include "varint.h" +#include "path.h" + +#include "git2/odb.h" +#include "git2/oid.h" +#include "git2/blob.h" +#include "git2/config.h" +#include "git2/sys/index.h" + +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); + +#define minimal_entry_size (offsetof(struct entry_short, path)) + +static const size_t INDEX_HEADER_SIZE = 12; + +static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2; +static const unsigned int INDEX_VERSION_NUMBER_LB = 2; +static const unsigned int INDEX_VERSION_NUMBER_EXT = 3; +static const unsigned int INDEX_VERSION_NUMBER_COMP = 4; +static const unsigned int INDEX_VERSION_NUMBER_UB = 4; + +static const unsigned int INDEX_HEADER_SIG = 0x44495243; +static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; +static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; +static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'}; + +#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx))) + +struct index_header { + uint32_t signature; + uint32_t version; + uint32_t entry_count; +}; + +struct index_extension { + char signature[4]; + uint32_t extension_size; +}; + +struct entry_time { + uint32_t seconds; + uint32_t nanoseconds; +}; + +struct entry_short { + struct entry_time ctime; + struct entry_time mtime; + uint32_t dev; + uint32_t ino; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint32_t file_size; + unsigned char oid[GIT_OID_RAWSZ]; + uint16_t flags; + char path[1]; /* arbitrary length */ +}; + +struct entry_long { + struct entry_time ctime; + struct entry_time mtime; + uint32_t dev; + uint32_t ino; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint32_t file_size; + unsigned char oid[GIT_OID_RAWSZ]; + uint16_t flags; + uint16_t flags_extended; + char path[1]; /* arbitrary length */ +}; + +struct entry_srch_key { + const char *path; + size_t pathlen; + int stage; +}; + +struct entry_internal { + git_index_entry entry; + size_t pathlen; + char path[GIT_FLEX_ARRAY]; +}; + +struct reuc_entry_internal { + git_index_reuc_entry entry; + size_t pathlen; + char path[GIT_FLEX_ARRAY]; +}; + +bool git_index__enforce_unsaved_safety = false; + +/* local declarations */ +static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size); +static int read_header(struct index_header *dest, const void *buffer); + +static int parse_index(git_index *index, const char *buffer, size_t buffer_size); +static bool is_index_extended(git_index *index); +static int write_index(unsigned char checksum[GIT_HASH_SHA1_SIZE], size_t *checksum_size, 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; + const struct entry_internal *entry = array_member; + int cmp; + size_t len1, len2, len; + + len1 = srch_key->pathlen; + len2 = entry->pathlen; + len = len1 < len2 ? len1 : len2; + + cmp = memcmp(srch_key->path, entry->path, len); + if (cmp) + return cmp; + if (len1 < len2) + return -1; + if (len1 > len2) + return 1; + + if (srch_key->stage != GIT_INDEX_STAGE_ANY) + return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); + + return 0; +} + +int git_index_entry_isrch(const void *key, const void *array_member) +{ + const struct entry_srch_key *srch_key = key; + const struct entry_internal *entry = array_member; + int cmp; + size_t len1, len2, len; + + len1 = srch_key->pathlen; + len2 = entry->pathlen; + len = len1 < len2 ? len1 : len2; + + cmp = strncasecmp(srch_key->path, entry->path, len); + + if (cmp) + return cmp; + if (len1 < len2) + return -1; + if (len1 > len2) + return 1; + + if (srch_key->stage != GIT_INDEX_STAGE_ANY) + return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); + + return 0; +} + +static int index_entry_srch_path(const void *path, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcmp((const char *)path, entry->path); +} + +static int index_entry_isrch_path(const void *path, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcasecmp((const char *)path, entry->path); +} + +int git_index_entry_cmp(const void *a, const void *b) +{ + int diff; + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + diff = strcmp(entry_a->path, entry_b->path); + + if (diff == 0) + diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); + + return diff; +} + +int git_index_entry_icmp(const void *a, const void *b) +{ + int diff; + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + diff = strcasecmp(entry_a->path, entry_b->path); + + if (diff == 0) + diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); + + return diff; +} + +static int conflict_name_cmp(const void *a, const void *b) +{ + const git_index_name_entry *name_a = a; + const git_index_name_entry *name_b = b; + + if (name_a->ancestor && !name_b->ancestor) + return 1; + + if (!name_a->ancestor && name_b->ancestor) + return -1; + + if (name_a->ancestor) + return strcmp(name_a->ancestor, name_b->ancestor); + + if (!name_a->ours || !name_b->ours) + return 0; + + return strcmp(name_a->ours, name_b->ours); +} + +/** + * TODO: enable this when resolving case insensitive conflicts + */ +#if 0 +static int conflict_name_icmp(const void *a, const void *b) +{ + const git_index_name_entry *name_a = a; + const git_index_name_entry *name_b = b; + + if (name_a->ancestor && !name_b->ancestor) + return 1; + + if (!name_a->ancestor && name_b->ancestor) + return -1; + + if (name_a->ancestor) + return strcasecmp(name_a->ancestor, name_b->ancestor); + + if (!name_a->ours || !name_b->ours) + return 0; + + return strcasecmp(name_a->ours, name_b->ours); +} +#endif + +static int reuc_srch(const void *key, const void *array_member) +{ + const git_index_reuc_entry *reuc = array_member; + + return strcmp(key, reuc->path); +} + +static int reuc_isrch(const void *key, const void *array_member) +{ + const git_index_reuc_entry *reuc = array_member; + + return strcasecmp(key, reuc->path); +} + +static int reuc_cmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; + + return strcmp(info_a->path, info_b->path); +} + +static int reuc_icmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; + + return strcasecmp(info_a->path, info_b->path); +} + +static void index_entry_reuc_free(git_index_reuc_entry *reuc) +{ + git__free(reuc); +} + +static void index_entry_free(git_index_entry *entry) +{ + if (!entry) + return; + + memset(&entry->id, 0, sizeof(entry->id)); + git__free(entry); +} + +unsigned int git_index__create_mode(unsigned int mode) +{ + if (S_ISLNK(mode)) + return S_IFLNK; + + if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) + return (S_IFLNK | S_IFDIR); + + return S_IFREG | GIT_PERMS_CANONICAL(mode); +} + +static unsigned int index_merge_mode( + git_index *index, git_index_entry *existing, unsigned int mode) +{ + if (index->no_symlinks && S_ISREG(mode) && + existing && S_ISLNK(existing->mode)) + return existing->mode; + + if (index->distrust_filemode && S_ISREG(mode)) + return (existing && S_ISREG(existing->mode)) ? + existing->mode : git_index__create_mode(0666); + + return git_index__create_mode(mode); +} + +GIT_INLINE(int) index_find_in_entries( + size_t *out, git_vector *entries, git_vector_cmp entry_srch, + const char *path, size_t path_len, int stage) +{ + struct entry_srch_key srch_key; + srch_key.path = path; + srch_key.pathlen = !path_len ? strlen(path) : path_len; + srch_key.stage = stage; + return git_vector_bsearch2(out, entries, entry_srch, &srch_key); +} + +GIT_INLINE(int) index_find( + size_t *out, git_index *index, + const char *path, size_t path_len, int stage) +{ + git_vector_sort(&index->entries); + + return index_find_in_entries( + out, &index->entries, index->entries_search, path, path_len, stage); +} + +void git_index__set_ignore_case(git_index *index, bool ignore_case) +{ + index->ignore_case = ignore_case; + + if (ignore_case) { + index->entries_cmp_path = git__strcasecmp_cb; + index->entries_search = git_index_entry_isrch; + index->entries_search_path = index_entry_isrch_path; + index->reuc_search = reuc_isrch; + } else { + index->entries_cmp_path = git__strcmp_cb; + index->entries_search = git_index_entry_srch; + index->entries_search_path = index_entry_srch_path; + index->reuc_search = reuc_srch; + } + + git_vector_set_cmp(&index->entries, + ignore_case ? git_index_entry_icmp : git_index_entry_cmp); + git_vector_sort(&index->entries); + + git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); + git_vector_sort(&index->reuc); +} + +int git_index_open(git_index **index_out, const char *index_path) +{ + git_index *index; + int error = -1; + + GIT_ASSERT_ARG(index_out); + + index = git__calloc(1, sizeof(git_index)); + GIT_ERROR_CHECK_ALLOC(index); + + if (git_pool_init(&index->tree_pool, 1) < 0) + goto fail; + + if (index_path != NULL) { + index->index_file_path = git__strdup(index_path); + if (!index->index_file_path) + goto fail; + + /* Check if index file is stored on disk already */ + if (git_fs_path_exists(index->index_file_path) == true) + index->on_disk = 1; + } + + if (git_vector_init(&index->entries, 32, 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; + index->entries_search = git_index_entry_srch; + index->entries_search_path = index_entry_srch_path; + index->reuc_search = reuc_srch; + index->version = INDEX_VERSION_NUMBER_DEFAULT; + + if (index_path != NULL && (error = git_index_read(index, true)) < 0) + goto fail; + + *index_out = index; + GIT_REFCOUNT_INC(index); + + return 0; + +fail: + git_pool_clear(&index->tree_pool); + git_index_free(index); + return error; +} + +int git_index_new(git_index **out) +{ + return git_index_open(out, NULL); +} + +static void index_free(git_index *index) +{ + /* index iterators increment the refcount of the index, so if we + * get here then there should be no outstanding iterators. + */ + if (git_atomic32_get(&index->readers)) + return; + + git_index_clear(index); + git_idxmap_free(index->entries_map); + git_vector_free(&index->entries); + git_vector_free(&index->names); + git_vector_free(&index->reuc); + git_vector_free(&index->deleted); + + git__free(index->index_file_path); + + git__memzero(index, sizeof(*index)); + git__free(index); +} + +void git_index_free(git_index *index) +{ + if (index == NULL) + return; + + GIT_REFCOUNT_DEC(index, index_free); +} + +/* call with locked index */ +static void index_free_deleted(git_index *index) +{ + int readers = (int)git_atomic32_get(&index->readers); + size_t i; + + if (readers > 0 || !index->deleted.length) + return; + + for (i = 0; i < index->deleted.length; ++i) { + git_index_entry *ie = git_atomic_swap(index->deleted.contents[i], NULL); + index_entry_free(ie); + } + + git_vector_clear(&index->deleted); +} + +/* call with locked index */ +static int index_remove_entry(git_index *index, size_t pos) +{ + int error = 0; + git_index_entry *entry = git_vector_get(&index->entries, pos); + + if (entry != NULL) { + git_tree_cache_invalidate_path(index->tree, entry->path); + index_map_delete(index->entries_map, entry, index->ignore_case); + } + + error = git_vector_remove(&index->entries, pos); + + if (!error) { + if (git_atomic32_get(&index->readers) > 0) { + error = git_vector_insert(&index->deleted, entry); + } else { + index_entry_free(entry); + } + + index->dirty = 1; + } + + return error; +} + +int git_index_clear(git_index *index) +{ + int error = 0; + + GIT_ASSERT_ARG(index); + + index->dirty = 1; + index->tree = NULL; + git_pool_clear(&index->tree_pool); + + 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); + + 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; +} + +static int create_index_error(int error, const char *msg) +{ + git_error_set_str(GIT_ERROR_INDEX, msg); + return error; +} + +int git_index_set_caps(git_index *index, int caps) +{ + unsigned int old_ignore_case; + + GIT_ASSERT_ARG(index); + + old_ignore_case = index->ignore_case; + + if (caps == GIT_INDEX_CAPABILITY_FROM_OWNER) { + git_repository *repo = INDEX_OWNER(index); + int val; + + if (!repo) + return create_index_error( + -1, "cannot access repository to set index caps"); + + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_IGNORECASE)) + index->ignore_case = (val != 0); + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FILEMODE)) + index->distrust_filemode = (val == 0); + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_SYMLINKS)) + index->no_symlinks = (val == 0); + } + else { + index->ignore_case = ((caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); + index->distrust_filemode = ((caps & GIT_INDEX_CAPABILITY_NO_FILEMODE) != 0); + index->no_symlinks = ((caps & GIT_INDEX_CAPABILITY_NO_SYMLINKS) != 0); + } + + if (old_ignore_case != index->ignore_case) { + git_index__set_ignore_case(index, (bool)index->ignore_case); + } + + return 0; +} + +int git_index_caps(const git_index *index) +{ + return ((index->ignore_case ? GIT_INDEX_CAPABILITY_IGNORE_CASE : 0) | + (index->distrust_filemode ? GIT_INDEX_CAPABILITY_NO_FILEMODE : 0) | + (index->no_symlinks ? GIT_INDEX_CAPABILITY_NO_SYMLINKS : 0)); +} + +#ifndef GIT_DEPRECATE_HARD +const git_oid *git_index_checksum(git_index *index) +{ + return (git_oid *)index->checksum; +} +#endif + +/** + * Returns 1 for changed, 0 for not changed and <0 for errors + */ +static int compare_checksum(git_index *index) +{ + int fd; + ssize_t bytes_read; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0) + return fd; + + if (p_lseek(fd, (0 - (ssize_t)checksum_size), SEEK_END) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_OS, "failed to seek to end of file"); + return -1; + } + + bytes_read = p_read(fd, checksum, checksum_size); + p_close(fd); + + if (bytes_read < (ssize_t)checksum_size) + return -1; + + return !!memcmp(checksum, index->checksum, checksum_size); +} + +int git_index_read(git_index *index, int force) +{ + int error = 0, updated; + git_str buffer = GIT_STR_INIT; + git_futils_filestamp stamp = index->stamp; + + if (!index->index_file_path) + return create_index_error(-1, + "failed to read index: The index is in-memory only"); + + index->on_disk = git_fs_path_exists(index->index_file_path); + + if (!index->on_disk) { + if (force && (error = git_index_clear(index)) < 0) + return error; + + index->dirty = 0; + return 0; + } + + if ((updated = git_futils_filestamp_check(&stamp, index->index_file_path) < 0) || + ((updated = compare_checksum(index)) < 0)) { + git_error_set( + GIT_ERROR_INDEX, + "failed to read index: '%s' no longer exists", + index->index_file_path); + return updated; + } + + if (!updated && !force) + return 0; + + error = git_futils_readbuffer(&buffer, index->index_file_path); + if (error < 0) + return error; + + index->tree = NULL; + git_pool_clear(&index->tree_pool); + + error = git_index_clear(index); + + if (!error) + error = parse_index(index, buffer.ptr, buffer.size); + + if (!error) { + git_futils_filestamp_set(&index->stamp, &stamp); + index->dirty = 0; + } + + git_str_dispose(&buffer); + return error; +} + +int git_index_read_safely(git_index *index) +{ + if (git_index__enforce_unsaved_safety && index->dirty) { + git_error_set(GIT_ERROR_INDEX, + "the index has unsaved changes that would be overwritten by this operation"); + return GIT_EINDEXDIRTY; + } + + return git_index_read(index, false); +} + +static bool is_racy_entry(git_index *index, const git_index_entry *entry) +{ + /* Git special-cases submodules in the check */ + if (S_ISGITLINK(entry->mode)) + return false; + + return git_index_entry_newer_than_index(entry, index); +} + +/* + * Force the next diff to take a look at those entries which have the + * same timestamp as the current index. + */ +static int truncate_racily_clean(git_index *index) +{ + size_t i; + int error; + git_index_entry *entry; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_vector paths = GIT_VECTOR_INIT; + git_diff_delta *delta; + + /* Nothing to do if there's no repo to talk about */ + if (!INDEX_OWNER(index)) + return 0; + + /* If there's no workdir, we can't know where to even check */ + if (!git_repository_workdir(INDEX_OWNER(index))) + return 0; + + diff_opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_DISABLE_PATHSPEC_MATCH; + git_vector_foreach(&index->entries, i, entry) { + if ((entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE) == 0 && + is_racy_entry(index, entry)) + git_vector_insert(&paths, (char *)entry->path); + } + + if (paths.length == 0) + goto done; + + diff_opts.pathspec.count = paths.length; + diff_opts.pathspec.strings = (char **)paths.contents; + + if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) + return error; + + git_vector_foreach(&diff->deltas, i, delta) { + entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0); + + /* Ensure that we have a stage 0 for this file (ie, it's not a + * conflict), otherwise smudging it is quite pointless. + */ + if (entry) { + entry->file_size = 0; + index->dirty = 1; + } + } + +done: + git_diff_free(diff); + git_vector_free(&paths); + return 0; +} + +unsigned git_index_version(git_index *index) +{ + GIT_ASSERT_ARG(index); + + return index->version; +} + +int git_index_set_version(git_index *index, unsigned int version) +{ + GIT_ASSERT_ARG(index); + + if (version < INDEX_VERSION_NUMBER_LB || + version > INDEX_VERSION_NUMBER_UB) { + git_error_set(GIT_ERROR_INDEX, "invalid version number"); + return -1; + } + + index->version = version; + + return 0; +} + +int git_index_write(git_index *index) +{ + git_indexwriter writer = GIT_INDEXWRITER_INIT; + int error; + + truncate_racily_clean(index); + + if ((error = git_indexwriter_init(&writer, index)) == 0 && + (error = git_indexwriter_commit(&writer)) == 0) + index->dirty = 0; + + git_indexwriter_cleanup(&writer); + + return error; +} + +const char *git_index_path(const git_index *index) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + return index->index_file_path; +} + +int git_index_write_tree(git_oid *oid, git_index *index) +{ + git_repository *repo; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(index); + + repo = INDEX_OWNER(index); + + if (repo == NULL) + return create_index_error(-1, "Failed to write tree. " + "the index file is not backed up by an existing repository"); + + return git_tree__write_index(oid, index, repo); +} + +int git_index_write_tree_to( + git_oid *oid, git_index *index, git_repository *repo) +{ + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(repo); + + return git_tree__write_index(oid, index, repo); +} + +size_t git_index_entrycount(const git_index *index) +{ + GIT_ASSERT_ARG(index); + + return index->entries.length; +} + +const git_index_entry *git_index_get_byindex( + git_index *index, size_t n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + + git_vector_sort(&index->entries); + return git_vector_get(&index->entries, n); +} + +const git_index_entry *git_index_get_bypath( + git_index *index, const char *path, int stage) +{ + git_index_entry key = {{ 0 }}; + git_index_entry *value; + + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + + key.path = path; + GIT_INDEX_ENTRY_STAGE_SET(&key, stage); + + 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 (!value) { + git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); + return NULL; + } + + return value; +} + +void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st, bool trust_mode) +{ + entry->ctime.seconds = (int32_t)st->st_ctime; + entry->mtime.seconds = (int32_t)st->st_mtime; +#if defined(GIT_USE_NSEC) + entry->mtime.nanoseconds = st->st_mtime_nsec; + entry->ctime.nanoseconds = st->st_ctime_nsec; +#endif + entry->dev = st->st_rdev; + entry->ino = st->st_ino; + entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? + git_index__create_mode(0666) : git_index__create_mode(st->st_mode); + entry->uid = st->st_uid; + entry->gid = st->st_gid; + entry->file_size = (uint32_t)st->st_size; +} + +static void index_entry_adjust_namemask( + git_index_entry *entry, + size_t path_length) +{ + entry->flags &= ~GIT_INDEX_ENTRY_NAMEMASK; + + if (path_length < GIT_INDEX_ENTRY_NAMEMASK) + entry->flags |= path_length & GIT_INDEX_ENTRY_NAMEMASK; + else + entry->flags |= GIT_INDEX_ENTRY_NAMEMASK; +} + +/* When `from_workdir` is true, we will validate the paths to avoid placing + * paths that are invalid for the working directory on the current filesystem + * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This + * function will *always* prevent `.git` and directory traversal `../` from + * being added to the index. + */ +static int index_entry_create( + git_index_entry **out, + git_repository *repo, + const char *path, + struct stat *st, + bool from_workdir) +{ + size_t pathlen = strlen(path), alloclen; + struct entry_internal *entry; + unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; + uint16_t mode = 0; + + /* always reject placing `.git` in the index and directory traversal. + * when requested, disallow platform-specific filenames and upgrade to + * the platform-specific `.git` tests (eg, `git~1`, etc). + */ + if (from_workdir) + path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; + if (st) + mode = st->st_mode; + + if (!git_path_is_valid(repo, path, mode, path_valid_flags)) { + git_error_set(GIT_ERROR_INDEX, "invalid path: '%s'", path); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(struct entry_internal), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + entry = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->pathlen = pathlen; + memcpy(entry->path, path, pathlen); + entry->entry.path = entry->path; + + *out = (git_index_entry *)entry; + return 0; +} + +static int index_entry_init( + git_index_entry **entry_out, + git_index *index, + const char *rel_path) +{ + int error = 0; + git_index_entry *entry = NULL; + git_str path = GIT_STR_INIT; + struct stat st; + git_oid oid; + git_repository *repo; + + if (INDEX_OWNER(index) == NULL) + return create_index_error(-1, + "could not initialize index entry. " + "Index is not backed up by an existing repository."); + + /* + * FIXME: this is duplicated with the work in + * git_blob__create_from_paths. It should accept an optional stat + * structure so we can pass in the one we have to do here. + */ + repo = INDEX_OWNER(index); + if (git_repository__ensure_not_bare(repo, "create blob from file") < 0) + return GIT_EBAREREPO; + + if (git_repository_workdir_path(&path, repo, rel_path) < 0) + return -1; + + error = git_fs_path_lstat(path.ptr, &st); + git_str_dispose(&path); + + if (error < 0) + return error; + + if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, &st, true) < 0) + return -1; + + /* write the blob to disk and get the oid and stat info */ + error = git_blob__create_from_paths( + &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); + + if (error < 0) { + index_entry_free(entry); + return error; + } + + entry->id = oid; + git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); + + *entry_out = (git_index_entry *)entry; + return 0; +} + +static git_index_reuc_entry *reuc_entry_alloc(const char *path) +{ + size_t pathlen = strlen(path), + structlen = sizeof(struct reuc_entry_internal), + alloclen; + struct reuc_entry_internal *entry; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, structlen, pathlen) || + GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1)) + return NULL; + + entry = git__calloc(1, alloclen); + if (!entry) + return NULL; + + entry->pathlen = pathlen; + memcpy(entry->path, path, pathlen); + entry->entry.path = entry->path; + + return (git_index_reuc_entry *)entry; +} + +static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, + const char *path, + int ancestor_mode, const git_oid *ancestor_oid, + int our_mode, const git_oid *our_oid, + int their_mode, const git_oid *their_oid) +{ + git_index_reuc_entry *reuc = NULL; + + GIT_ASSERT_ARG(reuc_out); + GIT_ASSERT_ARG(path); + + *reuc_out = reuc = reuc_entry_alloc(path); + GIT_ERROR_CHECK_ALLOC(reuc); + + if ((reuc->mode[0] = ancestor_mode) > 0) { + GIT_ASSERT(ancestor_oid); + git_oid_cpy(&reuc->oid[0], ancestor_oid); + } + + if ((reuc->mode[1] = our_mode) > 0) { + GIT_ASSERT(our_oid); + git_oid_cpy(&reuc->oid[1], our_oid); + } + + if ((reuc->mode[2] = their_mode) > 0) { + GIT_ASSERT(their_oid); + git_oid_cpy(&reuc->oid[2], their_oid); + } + + return 0; +} + +static void index_entry_cpy( + git_index_entry *tgt, + const git_index_entry *src) +{ + const char *tgt_path = tgt->path; + memcpy(tgt, src, sizeof(*tgt)); + tgt->path = tgt_path; +} + +static int index_entry_dup( + git_index_entry **out, + git_index *index, + const git_index_entry *src) +{ + if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) + return -1; + + index_entry_cpy(*out, src); + return 0; +} + +static void index_entry_cpy_nocache( + git_index_entry *tgt, + const git_index_entry *src) +{ + git_oid_cpy(&tgt->id, &src->id); + tgt->mode = src->mode; + tgt->flags = src->flags; + tgt->flags_extended = (src->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS); +} + +static int index_entry_dup_nocache( + git_index_entry **out, + git_index *index, + const git_index_entry *src) +{ + if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) + return -1; + + index_entry_cpy_nocache(*out, src); + return 0; +} + +static int has_file_name(git_index *index, + const git_index_entry *entry, size_t pos, int ok_to_replace) +{ + size_t len = strlen(entry->path); + int stage = GIT_INDEX_ENTRY_STAGE(entry); + const char *name = entry->path; + + while (pos < index->entries.length) { + struct entry_internal *p = index->entries.contents[pos++]; + + if (len >= p->pathlen) + break; + if (memcmp(name, p->path, len)) + break; + if (GIT_INDEX_ENTRY_STAGE(&p->entry) != stage) + continue; + if (p->path[len] != '/') + continue; + if (!ok_to_replace) + return -1; + + if (index_remove_entry(index, --pos) < 0) + break; + } + return 0; +} + +/* + * Do we have another file with a pathname that is a proper + * subset of the name we're trying to add? + */ +static int has_dir_name(git_index *index, + const git_index_entry *entry, int ok_to_replace) +{ + int stage = GIT_INDEX_ENTRY_STAGE(entry); + const char *name = entry->path; + const char *slash = name + strlen(name); + + for (;;) { + size_t len, pos; + + for (;;) { + if (*--slash == '/') + break; + if (slash <= entry->path) + return 0; + } + len = slash - name; + + if (!index_find(&pos, index, name, len, stage)) { + if (!ok_to_replace) + return -1; + + if (index_remove_entry(index, pos) < 0) + break; + continue; + } + + /* + * Trivial optimization: if we find an entry that + * already matches the sub-directory, then we know + * we're ok, and we can exit. + */ + for (; pos < index->entries.length; ++pos) { + struct entry_internal *p = index->entries.contents[pos]; + + if (p->pathlen <= len || + p->path[len] != '/' || + memcmp(p->path, name, len)) + break; /* not our subdirectory */ + + if (GIT_INDEX_ENTRY_STAGE(&p->entry) == stage) + return 0; + } + } + + return 0; +} + +static int check_file_directory_collision(git_index *index, + git_index_entry *entry, size_t pos, int ok_to_replace) +{ + if (has_file_name(index, entry, pos, ok_to_replace) < 0 || + has_dir_name(index, entry, ok_to_replace) < 0) { + git_error_set(GIT_ERROR_INDEX, + "'%s' appears as both a file and a directory", entry->path); + return -1; + } + + return 0; +} + +static int canonicalize_directory_path( + git_index *index, + git_index_entry *entry, + git_index_entry *existing) +{ + const git_index_entry *match, *best = NULL; + char *search, *sep; + size_t pos, search_len, best_len; + + if (!index->ignore_case) + return 0; + + /* item already exists in the index, simply re-use the existing case */ + if (existing) { + memcpy((char *)entry->path, existing->path, strlen(existing->path)); + return 0; + } + + /* nothing to do */ + if (strchr(entry->path, '/') == NULL) + return 0; + + if ((search = git__strdup(entry->path)) == NULL) + return -1; + + /* starting at the parent directory and descending to the root, find the + * common parent directory. + */ + while (!best && (sep = strrchr(search, '/'))) { + sep[1] = '\0'; + + search_len = strlen(search); + + git_vector_bsearch2( + &pos, &index->entries, index->entries_search_path, search); + + while ((match = git_vector_get(&index->entries, pos))) { + if (GIT_INDEX_ENTRY_STAGE(match) != 0) { + /* conflicts do not contribute to canonical paths */ + } else if (strncmp(search, match->path, search_len) == 0) { + /* prefer an exact match to the input filename */ + best = match; + best_len = search_len; + break; + } else if (strncasecmp(search, match->path, search_len) == 0) { + /* continue walking, there may be a path with an exact + * (case sensitive) match later in the index, but use this + * as the best match until that happens. + */ + if (!best) { + best = match; + best_len = search_len; + } + } else { + break; + } + + pos++; + } + + sep[0] = '\0'; + } + + if (best) + memcpy((char *)entry->path, best->path, best_len); + + git__free(search); + return 0; +} + +static int index_no_dups(void **old, void *new) +{ + const git_index_entry *entry = new; + GIT_UNUSED(old); + git_error_set(GIT_ERROR_INDEX, "'%s' appears multiple times at stage %d", + entry->path, GIT_INDEX_ENTRY_STAGE(entry)); + return GIT_EEXISTS; +} + +static void index_existing_and_best( + git_index_entry **existing, + size_t *existing_position, + git_index_entry **best, + git_index *index, + const git_index_entry *entry) +{ + git_index_entry *e; + size_t pos; + int error; + + error = index_find(&pos, + index, entry->path, 0, GIT_INDEX_ENTRY_STAGE(entry)); + + if (error == 0) { + *existing = index->entries.contents[pos]; + *existing_position = pos; + *best = index->entries.contents[pos]; + return; + } + + *existing = NULL; + *existing_position = 0; + *best = NULL; + + if (GIT_INDEX_ENTRY_STAGE(entry) == 0) { + for (; pos < index->entries.length; pos++) { + int (*strcomp)(const char *a, const char *b) = + index->ignore_case ? git__strcasecmp : git__strcmp; + + e = index->entries.contents[pos]; + + if (strcomp(entry->path, e->path) != 0) + break; + + if (GIT_INDEX_ENTRY_STAGE(e) == GIT_INDEX_STAGE_ANCESTOR) { + *best = e; + continue; + } else { + *best = e; + break; + } + } + } +} + +/* index_insert takes ownership of the new entry - if it can't insert + * it, then it will return an error **and also free the entry**. When + * it replaces an existing entry, it will update the entry_ptr with the + * actual entry in the index (and free the passed in one). + * + * trust_path is whether we use the given path, or whether (on case + * insensitive systems only) we try to canonicalize the given path to + * be within an existing directory. + * + * trust_mode is whether we trust the mode in entry_ptr. + * + * trust_id is whether we trust the id or it should be validated. + */ +static int index_insert( + git_index *index, + git_index_entry **entry_ptr, + int replace, + bool trust_path, + bool trust_mode, + bool trust_id) +{ + git_index_entry *existing, *best, *entry; + size_t path_length, position; + int error; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(entry_ptr); + + entry = *entry_ptr; + + /* Make sure that the path length flag is correct */ + path_length = ((struct entry_internal *)entry)->pathlen; + index_entry_adjust_namemask(entry, path_length); + + /* This entry is now up-to-date and should not be checked for raciness */ + entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; + + git_vector_sort(&index->entries); + + /* + * Look if an entry with this path already exists, either staged, or (if + * this entry is a regular staged item) as the "ours" side of a conflict. + */ + index_existing_and_best(&existing, &position, &best, index, entry); + + /* Update the file mode */ + entry->mode = trust_mode ? + git_index__create_mode(entry->mode) : + index_merge_mode(index, best, entry->mode); + + /* Canonicalize the directory name */ + if (!trust_path && (error = canonicalize_directory_path(index, entry, best)) < 0) + goto out; + + /* Ensure that the given id exists (unless it's a submodule) */ + if (!trust_id && INDEX_OWNER(index) && + (entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) { + + if (!git_object__is_valid(INDEX_OWNER(index), &entry->id, + git_object__type_from_filemode(entry->mode))) { + error = -1; + goto out; + } + } + + /* Look for tree / blob name collisions, removing conflicts if requested */ + if ((error = check_file_directory_collision(index, entry, position, replace)) < 0) + goto out; + + /* + * If we are replacing an existing item, overwrite the existing entry + * and return it in place of the passed in one. + */ + if (existing) { + if (replace) { + index_entry_cpy(existing, entry); + + if (trust_path) + memcpy((char *)existing->path, entry->path, strlen(entry->path)); + } + + index_entry_free(entry); + *entry_ptr = existing; + } else { + /* + * If replace is not requested or no existing entry exists, 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 || + (error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) + goto out; + } + + index->dirty = 1; + +out: + if (error < 0) { + index_entry_free(*entry_ptr); + *entry_ptr = NULL; + } + + return error; +} + +static int index_conflict_to_reuc(git_index *index, const char *path) +{ + const git_index_entry *conflict_entries[3]; + int ancestor_mode, our_mode, their_mode; + git_oid const *ancestor_oid, *our_oid, *their_oid; + int ret; + + if ((ret = git_index_conflict_get(&conflict_entries[0], + &conflict_entries[1], &conflict_entries[2], index, path)) < 0) + return ret; + + ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode; + our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode; + their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode; + + ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->id; + our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->id; + their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->id; + + if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid, + our_mode, our_oid, their_mode, their_oid)) >= 0) + ret = git_index_conflict_remove(index, path); + + return ret; +} + +GIT_INLINE(bool) is_file_or_link(const int filemode) +{ + return (filemode == GIT_FILEMODE_BLOB || + filemode == GIT_FILEMODE_BLOB_EXECUTABLE || + filemode == GIT_FILEMODE_LINK); +} + +GIT_INLINE(bool) valid_filemode(const int filemode) +{ + return (is_file_or_link(filemode) || filemode == GIT_FILEMODE_COMMIT); +} + +int git_index_add_from_buffer( + git_index *index, const git_index_entry *source_entry, + const void *buffer, size_t len) +{ + git_index_entry *entry = NULL; + int error = 0; + git_oid id; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(source_entry && source_entry->path); + + if (INDEX_OWNER(index) == NULL) + return create_index_error(-1, + "could not initialize index entry. " + "Index is not backed up by an existing repository."); + + if (!is_file_or_link(source_entry->mode)) { + git_error_set(GIT_ERROR_INDEX, "invalid filemode"); + 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_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 = (uint32_t)len; + + if ((error = index_insert(index, &entry, 1, true, true, true)) < 0) + return error; + + /* Adding implies conflict was resolved, move conflict entries to REUC */ + if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND) + return error; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} + +static int add_repo_as_submodule(git_index_entry **out, git_index *index, const char *path) +{ + git_repository *sub; + git_str abspath = GIT_STR_INIT; + git_repository *repo = INDEX_OWNER(index); + git_reference *head; + git_index_entry *entry; + struct stat st; + int error; + + if ((error = git_repository_workdir_path(&abspath, repo, path)) < 0) + return error; + + if ((error = p_stat(abspath.ptr, &st)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat repository dir"); + return -1; + } + + if (index_entry_create(&entry, INDEX_OWNER(index), path, &st, true) < 0) + return -1; + + git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); + + if ((error = git_repository_open(&sub, abspath.ptr)) < 0) + return error; + + if ((error = git_repository_head(&head, sub)) < 0) + return error; + + git_oid_cpy(&entry->id, git_reference_target(head)); + entry->mode = GIT_FILEMODE_COMMIT; + + git_reference_free(head); + git_repository_free(sub); + git_str_dispose(&abspath); + + *out = entry; + return 0; +} + +int git_index_add_bypath(git_index *index, const char *path) +{ + git_index_entry *entry = NULL; + int ret; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if ((ret = index_entry_init(&entry, index, path)) == 0) + ret = index_insert(index, &entry, 1, false, false, true); + + /* If we were given a directory, let's see if it's a submodule */ + if (ret < 0 && ret != GIT_EDIRECTORY) + return ret; + + if (ret == GIT_EDIRECTORY) { + git_submodule *sm; + git_error_state err; + + git_error_state_capture(&err, ret); + + ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); + if (ret == GIT_ENOTFOUND) + return git_error_state_restore(&err); + + git_error_state_free(&err); + + /* + * EEXISTS means that there is a repository at that path, but it's not known + * as a submodule. We add its HEAD as an entry and don't register it. + */ + if (ret == GIT_EEXISTS) { + if ((ret = add_repo_as_submodule(&entry, index, path)) < 0) + return ret; + + if ((ret = index_insert(index, &entry, 1, false, false, true)) < 0) + return ret; + } else if (ret < 0) { + return ret; + } else { + ret = git_submodule_add_to_index(sm, false); + git_submodule_free(sm); + return ret; + } + } + + /* Adding implies conflict was resolved, move conflict entries to REUC */ + if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND) + return ret; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} + +int git_index_remove_bypath(git_index *index, const char *path) +{ + int ret; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if (((ret = git_index_remove(index, path, 0)) < 0 && + ret != GIT_ENOTFOUND) || + ((ret = index_conflict_to_reuc(index, path)) < 0 && + ret != GIT_ENOTFOUND)) + return ret; + + if (ret == GIT_ENOTFOUND) + git_error_clear(); + + return 0; +} + +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; + + GIT_ASSERT_ARG(index); + + if (!source_entries->length) + return 0; + + 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 ((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 ((error = git_vector_insert(&index->entries, entry)) < 0) + break; + + if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) + break; + + index->dirty = 1; + } + + if (!error) + git_vector_sort(&index->entries); + + return error; +} + + +int git_index_add(git_index *index, const git_index_entry *source_entry) +{ + git_index_entry *entry = NULL; + int ret; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(source_entry && source_entry->path); + + if (!valid_filemode(source_entry->mode)) { + git_error_set(GIT_ERROR_INDEX, "invalid entry mode"); + return -1; + } + + if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 || + (ret = index_insert(index, &entry, 1, true, true, false)) < 0) + return ret; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} + +int git_index_remove(git_index *index, const char *path, int stage) +{ + int error; + size_t position; + git_index_entry remove_key = {{ 0 }}; + + remove_key.path = path; + GIT_INDEX_ENTRY_STAGE_SET(&remove_key, stage); + + index_map_delete(index->entries_map, &remove_key, index->ignore_case); + + if (index_find(&position, index, path, 0, stage) < 0) { + git_error_set( + GIT_ERROR_INDEX, "index does not contain %s at stage %d", path, stage); + error = GIT_ENOTFOUND; + } else { + error = index_remove_entry(index, position); + } + + return error; +} + +int git_index_remove_directory(git_index *index, const char *dir, int stage) +{ + git_str pfx = GIT_STR_INIT; + int error = 0; + size_t pos; + git_index_entry *entry; + + if (!(error = git_str_sets(&pfx, dir)) && + !(error = git_fs_path_to_dir(&pfx))) + index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY); + + while (!error) { + entry = git_vector_get(&index->entries, pos); + if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0) + break; + + if (GIT_INDEX_ENTRY_STAGE(entry) != stage) { + ++pos; + continue; + } + + error = index_remove_entry(index, pos); + + /* removed entry at 'pos' so we don't need to increment */ + } + + git_str_dispose(&pfx); + + return error; +} + +int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix) +{ + int error = 0; + size_t pos; + const git_index_entry *entry; + + index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY); + entry = git_vector_get(&index->entries, pos); + if (!entry || git__prefixcmp(entry->path, prefix) != 0) + error = GIT_ENOTFOUND; + + if (!error && at_pos) + *at_pos = pos; + + return error; +} + +int git_index__find_pos( + size_t *out, git_index *index, const char *path, size_t path_len, int stage) +{ + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + return index_find(out, index, path, path_len, stage); +} + +int git_index_find(size_t *at_pos, git_index *index, const char *path) +{ + size_t pos; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if (git_vector_bsearch2( + &pos, &index->entries, index->entries_search_path, path) < 0) { + git_error_set(GIT_ERROR_INDEX, "index does not contain %s", path); + return GIT_ENOTFOUND; + } + + /* Since our binary search only looked at path, we may be in the + * middle of a list of stages. + */ + for (; pos > 0; --pos) { + const git_index_entry *prev = git_vector_get(&index->entries, pos - 1); + + if (index->entries_cmp_path(prev->path, path) != 0) + break; + } + + if (at_pos) + *at_pos = pos; + + return 0; +} + +int git_index_conflict_add(git_index *index, + const git_index_entry *ancestor_entry, + const git_index_entry *our_entry, + const git_index_entry *their_entry) +{ + git_index_entry *entries[3] = { 0 }; + unsigned short i; + int ret = 0; + + GIT_ASSERT_ARG(index); + + if ((ancestor_entry && + (ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0) || + (our_entry && + (ret = index_entry_dup(&entries[1], index, our_entry)) < 0) || + (their_entry && + (ret = index_entry_dup(&entries[2], index, their_entry)) < 0)) + goto on_error; + + /* Validate entries */ + for (i = 0; i < 3; i++) { + if (entries[i] && !valid_filemode(entries[i]->mode)) { + git_error_set(GIT_ERROR_INDEX, "invalid filemode for stage %d entry", + i + 1); + ret = -1; + goto on_error; + } + } + + /* Remove existing index entries for each path */ + for (i = 0; i < 3; i++) { + if (entries[i] == NULL) + continue; + + if ((ret = git_index_remove(index, entries[i]->path, 0)) != 0) { + if (ret != GIT_ENOTFOUND) + goto on_error; + + git_error_clear(); + ret = 0; + } + } + + /* Add the conflict entries */ + for (i = 0; i < 3; i++) { + if (entries[i] == NULL) + continue; + + /* Make sure stage is correct */ + GIT_INDEX_ENTRY_STAGE_SET(entries[i], i + 1); + + if ((ret = index_insert(index, &entries[i], 1, true, true, false)) < 0) + goto on_error; + + entries[i] = NULL; /* don't free if later entry fails */ + } + + return 0; + +on_error: + for (i = 0; i < 3; i++) { + if (entries[i] != NULL) + index_entry_free(entries[i]); + } + + return ret; +} + +static int index_conflict__get_byindex( + const git_index_entry **ancestor_out, + const git_index_entry **our_out, + const git_index_entry **their_out, + git_index *index, + size_t n) +{ + const git_index_entry *conflict_entry; + const char *path = NULL; + size_t count; + int stage, len = 0; + + GIT_ASSERT_ARG(ancestor_out); + GIT_ASSERT_ARG(our_out); + GIT_ASSERT_ARG(their_out); + GIT_ASSERT_ARG(index); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + for (count = git_index_entrycount(index); n < count; ++n) { + conflict_entry = git_vector_get(&index->entries, n); + + if (path && index->entries_cmp_path(conflict_entry->path, path) != 0) + break; + + stage = GIT_INDEX_ENTRY_STAGE(conflict_entry); + path = conflict_entry->path; + + switch (stage) { + case 3: + *their_out = conflict_entry; + len++; + break; + case 2: + *our_out = conflict_entry; + len++; + break; + case 1: + *ancestor_out = conflict_entry; + len++; + break; + default: + break; + }; + } + + return len; +} + +int git_index_conflict_get( + const git_index_entry **ancestor_out, + const git_index_entry **our_out, + const git_index_entry **their_out, + git_index *index, + const char *path) +{ + size_t pos; + int len = 0; + + GIT_ASSERT_ARG(ancestor_out); + GIT_ASSERT_ARG(our_out); + GIT_ASSERT_ARG(their_out); + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + if (git_index_find(&pos, index, path) < 0) + return GIT_ENOTFOUND; + + if ((len = index_conflict__get_byindex( + ancestor_out, our_out, their_out, index, pos)) < 0) + return len; + else if (len == 0) + return GIT_ENOTFOUND; + + return 0; +} + +static int index_conflict_remove(git_index *index, const char *path) +{ + size_t pos = 0; + git_index_entry *conflict_entry; + int error = 0; + + if (path != NULL && git_index_find(&pos, index, path) < 0) + return GIT_ENOTFOUND; + + while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) { + + if (path != NULL && + index->entries_cmp_path(conflict_entry->path, path) != 0) + break; + + if (GIT_INDEX_ENTRY_STAGE(conflict_entry) == 0) { + pos++; + continue; + } + + if ((error = index_remove_entry(index, pos)) < 0) + break; + } + + return error; +} + +int git_index_conflict_remove(git_index *index, const char *path) +{ + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + return index_conflict_remove(index, path); +} + +int git_index_conflict_cleanup(git_index *index) +{ + GIT_ASSERT_ARG(index); + return index_conflict_remove(index, NULL); +} + +int git_index_has_conflicts(const git_index *index) +{ + size_t i; + git_index_entry *entry; + + GIT_ASSERT_ARG(index); + + git_vector_foreach(&index->entries, i, entry) { + if (GIT_INDEX_ENTRY_STAGE(entry) > 0) + return 1; + } + + return 0; +} + +int git_index_iterator_new( + git_index_iterator **iterator_out, + git_index *index) +{ + git_index_iterator *it; + int error; + + GIT_ASSERT_ARG(iterator_out); + GIT_ASSERT_ARG(index); + + it = git__calloc(1, sizeof(git_index_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + + if ((error = git_index_snapshot_new(&it->snap, index)) < 0) { + git__free(it); + return error; + } + + it->index = index; + + *iterator_out = it; + return 0; +} + +int git_index_iterator_next( + const git_index_entry **out, + git_index_iterator *it) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(it); + + if (it->cur >= git_vector_length(&it->snap)) + return GIT_ITEROVER; + + *out = (git_index_entry *)git_vector_get(&it->snap, it->cur++); + return 0; +} + +void git_index_iterator_free(git_index_iterator *it) +{ + if (it == NULL) + return; + + git_index_snapshot_release(&it->snap, it->index); + git__free(it); +} + +int git_index_conflict_iterator_new( + git_index_conflict_iterator **iterator_out, + git_index *index) +{ + git_index_conflict_iterator *it = NULL; + + GIT_ASSERT_ARG(iterator_out); + GIT_ASSERT_ARG(index); + + it = git__calloc(1, sizeof(git_index_conflict_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + + it->index = index; + + *iterator_out = it; + return 0; +} + +int git_index_conflict_next( + const git_index_entry **ancestor_out, + const git_index_entry **our_out, + const git_index_entry **their_out, + git_index_conflict_iterator *iterator) +{ + const git_index_entry *entry; + int len; + + GIT_ASSERT_ARG(ancestor_out); + GIT_ASSERT_ARG(our_out); + GIT_ASSERT_ARG(their_out); + GIT_ASSERT_ARG(iterator); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + while (iterator->cur < iterator->index->entries.length) { + entry = git_index_get_byindex(iterator->index, iterator->cur); + + if (git_index_entry_is_conflict(entry)) { + if ((len = index_conflict__get_byindex( + ancestor_out, + our_out, + their_out, + iterator->index, + iterator->cur)) < 0) + return len; + + iterator->cur += len; + return 0; + } + + iterator->cur++; + } + + return GIT_ITEROVER; +} + +void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator) +{ + if (iterator == NULL) + return; + + git__free(iterator); +} + +size_t git_index_name_entrycount(git_index *index) +{ + GIT_ASSERT_ARG(index); + return index->names.length; +} + +const git_index_name_entry *git_index_name_get_byindex( + git_index *index, size_t n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + + git_vector_sort(&index->names); + return git_vector_get(&index->names, n); +} + +static void index_name_entry_free(git_index_name_entry *ne) +{ + if (!ne) + return; + git__free(ne->ancestor); + git__free(ne->ours); + git__free(ne->theirs); + git__free(ne); +} + +int git_index_name_add(git_index *index, + const char *ancestor, const char *ours, const char *theirs) +{ + git_index_name_entry *conflict_name; + + GIT_ASSERT_ARG((ancestor && ours) || (ancestor && theirs) || (ours && theirs)); + + conflict_name = git__calloc(1, sizeof(git_index_name_entry)); + GIT_ERROR_CHECK_ALLOC(conflict_name); + + if ((ancestor && !(conflict_name->ancestor = git__strdup(ancestor))) || + (ours && !(conflict_name->ours = git__strdup(ours))) || + (theirs && !(conflict_name->theirs = git__strdup(theirs))) || + git_vector_insert(&index->names, conflict_name) < 0) + { + index_name_entry_free(conflict_name); + return -1; + } + + index->dirty = 1; + return 0; +} + +int git_index_name_clear(git_index *index) +{ + size_t i; + git_index_name_entry *conflict_name; + + GIT_ASSERT_ARG(index); + + git_vector_foreach(&index->names, i, conflict_name) + index_name_entry_free(conflict_name); + + git_vector_clear(&index->names); + + index->dirty = 1; + + return 0; +} + +size_t git_index_reuc_entrycount(git_index *index) +{ + GIT_ASSERT_ARG(index); + return index->reuc.length; +} + +static int index_reuc_on_dup(void **old, void *new) +{ + index_entry_reuc_free(*old); + *old = new; + return GIT_EEXISTS; +} + +static int index_reuc_insert( + git_index *index, + git_index_reuc_entry *reuc) +{ + int res; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(reuc && reuc->path != NULL); + GIT_ASSERT(git_vector_is_sorted(&index->reuc)); + + res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup); + index->dirty = 1; + + return res == GIT_EEXISTS ? 0 : res; +} + +int git_index_reuc_add(git_index *index, const char *path, + int ancestor_mode, const git_oid *ancestor_oid, + int our_mode, const git_oid *our_oid, + int their_mode, const git_oid *their_oid) +{ + git_index_reuc_entry *reuc = NULL; + int error = 0; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, + ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 || + (error = index_reuc_insert(index, reuc)) < 0) + index_entry_reuc_free(reuc); + + return error; +} + +int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path) +{ + return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path); +} + +const git_index_reuc_entry *git_index_reuc_get_bypath( + git_index *index, const char *path) +{ + size_t pos; + + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(path, NULL); + + if (!index->reuc.length) + return NULL; + + GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); + + if (git_index_reuc_find(&pos, index, path) < 0) + return NULL; + + return git_vector_get(&index->reuc, pos); +} + +const git_index_reuc_entry *git_index_reuc_get_byindex( + git_index *index, size_t n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); + + return git_vector_get(&index->reuc, n); +} + +int git_index_reuc_remove(git_index *index, size_t position) +{ + int error; + git_index_reuc_entry *reuc; + + GIT_ASSERT_ARG(index); + GIT_ASSERT(git_vector_is_sorted(&index->reuc)); + + reuc = git_vector_get(&index->reuc, position); + error = git_vector_remove(&index->reuc, position); + + if (!error) + index_entry_reuc_free(reuc); + + index->dirty = 1; + return error; +} + +int git_index_reuc_clear(git_index *index) +{ + size_t i; + + GIT_ASSERT_ARG(index); + + for (i = 0; i < index->reuc.length; ++i) + index_entry_reuc_free(git_atomic_swap(index->reuc.contents[i], NULL)); + + git_vector_clear(&index->reuc); + + index->dirty = 1; + + return 0; +} + +static int index_error_invalid(const char *message) +{ + git_error_set(GIT_ERROR_INDEX, "invalid data in index - %s", message); + return -1; +} + +static int read_reuc(git_index *index, const char *buffer, size_t size) +{ + const char *endptr; + size_t len; + int i; + + /* If called multiple times, the vector might already be initialized */ + if (index->reuc._alloc_size == 0 && + git_vector_init(&index->reuc, 16, reuc_cmp) < 0) + return -1; + + while (size) { + git_index_reuc_entry *lost; + + len = p_strnlen(buffer, size) + 1; + if (size <= len) + return index_error_invalid("reading reuc entries"); + + lost = reuc_entry_alloc(buffer); + GIT_ERROR_CHECK_ALLOC(lost); + + size -= len; + buffer += len; + + /* read 3 ASCII octal numbers for stage entries */ + for (i = 0; i < 3; i++) { + int64_t tmp; + + if (git__strntol64(&tmp, buffer, size, &endptr, 8) < 0 || + !endptr || endptr == buffer || *endptr || + tmp < 0 || tmp > UINT32_MAX) { + index_entry_reuc_free(lost); + return index_error_invalid("reading reuc entry stage"); + } + + lost->mode[i] = (uint32_t)tmp; + + len = (endptr + 1) - buffer; + if (size <= len) { + index_entry_reuc_free(lost); + return index_error_invalid("reading reuc entry stage"); + } + + size -= len; + buffer += len; + } + + /* read up to 3 OIDs for stage entries */ + for (i = 0; i < 3; i++) { + if (!lost->mode[i]) + continue; + if (size < 20) { + index_entry_reuc_free(lost); + return index_error_invalid("reading reuc entry oid"); + } + + git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); + size -= 20; + buffer += 20; + } + + /* entry was read successfully - insert into reuc vector */ + if (git_vector_insert(&index->reuc, lost) < 0) + return -1; + } + + /* entries are guaranteed to be sorted on-disk */ + git_vector_set_sorted(&index->reuc, true); + + return 0; +} + + +static int read_conflict_names(git_index *index, const char *buffer, size_t size) +{ + size_t len; + + /* This gets called multiple times, the vector might already be initialized */ + if (index->names._alloc_size == 0 && + git_vector_init(&index->names, 16, conflict_name_cmp) < 0) + return -1; + +#define read_conflict_name(ptr) \ + len = p_strnlen(buffer, size) + 1; \ + if (size < len) { \ + index_error_invalid("reading conflict name entries"); \ + goto out_err; \ + } \ + if (len == 1) \ + ptr = NULL; \ + else { \ + ptr = git__malloc(len); \ + GIT_ERROR_CHECK_ALLOC(ptr); \ + memcpy(ptr, buffer, len); \ + } \ + \ + buffer += len; \ + size -= len; + + while (size) { + git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry)); + GIT_ERROR_CHECK_ALLOC(conflict_name); + + read_conflict_name(conflict_name->ancestor); + read_conflict_name(conflict_name->ours); + read_conflict_name(conflict_name->theirs); + + if (git_vector_insert(&index->names, conflict_name) < 0) + goto out_err; + + continue; + +out_err: + git__free(conflict_name->ancestor); + git__free(conflict_name->ours); + git__free(conflict_name->theirs); + git__free(conflict_name); + return -1; + } + +#undef read_conflict_name + + /* entries are guaranteed to be sorted on-disk */ + git_vector_set_sorted(&index->names, true); + + return 0; +} + +static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flags) +{ + if (varint_len) { + if (flags & GIT_INDEX_ENTRY_EXTENDED) + return offsetof(struct entry_long, path) + path_len + 1 + varint_len; + else + return offsetof(struct entry_short, path) + path_len + 1 + varint_len; + } else { +#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) + if (flags & GIT_INDEX_ENTRY_EXTENDED) + return entry_size(struct entry_long, path_len); + else + return entry_size(struct entry_short, path_len); +#undef entry_size + } +} + +static int read_entry( + git_index_entry **out, + size_t *out_size, + git_index *index, + const void *buffer, + size_t buffer_size, + const char *last) +{ + size_t path_length, entry_size; + const char *path_ptr; + struct entry_short source; + git_index_entry entry = {{0}}; + bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP; + char *tmp_path = NULL; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + if (checksum_size + minimal_entry_size > buffer_size) + return -1; + + /* buffer is not guaranteed to be aligned */ + memcpy(&source, buffer, sizeof(struct entry_short)); + + entry.ctime.seconds = (git_time_t)ntohl(source.ctime.seconds); + entry.ctime.nanoseconds = ntohl(source.ctime.nanoseconds); + entry.mtime.seconds = (git_time_t)ntohl(source.mtime.seconds); + entry.mtime.nanoseconds = ntohl(source.mtime.nanoseconds); + entry.dev = ntohl(source.dev); + entry.ino = ntohl(source.ino); + entry.mode = ntohl(source.mode); + entry.uid = ntohl(source.uid); + entry.gid = ntohl(source.gid); + entry.file_size = ntohl(source.file_size); + entry.flags = ntohs(source.flags); + + if (git_oid_fromraw(&entry.id, source.oid) < 0) + return -1; + + if (entry.flags & GIT_INDEX_ENTRY_EXTENDED) { + uint16_t flags_raw; + size_t flags_offset; + + flags_offset = offsetof(struct entry_long, flags_extended); + memcpy(&flags_raw, (const char *) buffer + flags_offset, + sizeof(flags_raw)); + flags_raw = ntohs(flags_raw); + + memcpy(&entry.flags_extended, &flags_raw, sizeof(flags_raw)); + path_ptr = (const char *) buffer + offsetof(struct entry_long, path); + } else + path_ptr = (const char *) buffer + offsetof(struct entry_short, path); + + if (!compressed) { + path_length = entry.flags & GIT_INDEX_ENTRY_NAMEMASK; + + /* if this is a very long string, we must find its + * real length without overflowing */ + if (path_length == 0xFFF) { + const char *path_end; + + path_end = memchr(path_ptr, '\0', buffer_size); + if (path_end == NULL) + return -1; + + path_length = path_end - path_ptr; + } + + entry_size = index_entry_size(path_length, 0, entry.flags); + entry.path = (char *)path_ptr; + } else { + size_t varint_len, last_len, prefix_len, suffix_len, path_len; + uintmax_t strip_len; + + strip_len = git_decode_varint((const unsigned char *)path_ptr, &varint_len); + last_len = strlen(last); + + if (varint_len == 0 || last_len < strip_len) + return index_error_invalid("incorrect prefix length"); + + prefix_len = last_len - (size_t)strip_len; + suffix_len = strlen(path_ptr + varint_len); + + GIT_ERROR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len); + GIT_ERROR_CHECK_ALLOC_ADD(&path_len, path_len, 1); + + if (path_len > GIT_PATH_MAX) + return index_error_invalid("unreasonable path length"); + + tmp_path = git__malloc(path_len); + GIT_ERROR_CHECK_ALLOC(tmp_path); + + memcpy(tmp_path, last, prefix_len); + memcpy(tmp_path + prefix_len, path_ptr + varint_len, suffix_len + 1); + entry_size = index_entry_size(suffix_len, varint_len, entry.flags); + entry.path = tmp_path; + } + + if (entry_size == 0) + return -1; + + if (checksum_size + entry_size > buffer_size) + return -1; + + if (index_entry_dup(out, index, &entry) < 0) { + git__free(tmp_path); + return -1; + } + + git__free(tmp_path); + *out_size = entry_size; + return 0; +} + +static int read_header(struct index_header *dest, const void *buffer) +{ + const struct index_header *source = buffer; + + dest->signature = ntohl(source->signature); + if (dest->signature != INDEX_HEADER_SIG) + return index_error_invalid("incorrect header signature"); + + dest->version = ntohl(source->version); + if (dest->version < INDEX_VERSION_NUMBER_LB || + dest->version > INDEX_VERSION_NUMBER_UB) + return index_error_invalid("incorrect header version"); + + dest->entry_count = ntohl(source->entry_count); + return 0; +} + +static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size) +{ + struct index_extension dest; + size_t total_size; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + /* buffer is not guaranteed to be aligned */ + memcpy(&dest, buffer, sizeof(struct index_extension)); + dest.extension_size = ntohl(dest.extension_size); + + total_size = dest.extension_size + sizeof(struct index_extension); + + if (dest.extension_size > total_size || + buffer_size < total_size || + buffer_size - total_size < checksum_size) { + index_error_invalid("extension is truncated"); + return -1; + } + + /* optional extension */ + if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { + /* tree cache */ + if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { + if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size, &index->tree_pool) < 0) + return -1; + } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { + if (read_reuc(index, buffer + 8, dest.extension_size) < 0) + return -1; + } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) { + if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0) + return -1; + } + /* else, unsupported extension. We cannot parse this, but we can skip + * it by returning `total_size */ + } else { + /* we cannot handle non-ignorable extensions; + * in fact they aren't even defined in the standard */ + git_error_set(GIT_ERROR_INDEX, "unsupported mandatory extension: '%.4s'", dest.signature); + return -1; + } + + *read_len = total_size; + + return 0; +} + +static int parse_index(git_index *index, const char *buffer, size_t buffer_size) +{ + int error = 0; + unsigned int i; + struct index_header header = { 0 }; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + const char *last = NULL; + const char *empty = ""; + +#define seek_forward(_increase) { \ + if (_increase >= buffer_size) { \ + error = index_error_invalid("ran out of data while parsing"); \ + goto done; } \ + buffer += _increase; \ + buffer_size -= _increase;\ +} + + if (buffer_size < INDEX_HEADER_SIZE + checksum_size) + return index_error_invalid("insufficient buffer space"); + + /* Precalculate the SHA1 of the files's contents -- we'll match it to + * the provided SHA1 in the footer */ + git_hash_buf(checksum, buffer, buffer_size - checksum_size, GIT_HASH_ALGORITHM_SHA1); + + /* Parse header */ + if ((error = read_header(&header, buffer)) < 0) + return error; + + index->version = header.version; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = empty; + + seek_forward(INDEX_HEADER_SIZE); + + GIT_ASSERT(!index->entries.length); + + 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 > checksum_size; ++i) { + git_index_entry *entry = NULL; + size_t entry_size; + + if ((error = read_entry(&entry, &entry_size, index, buffer, buffer_size, last)) < 0) { + error = index_error_invalid("invalid entry"); + goto done; + } + + if ((error = git_vector_insert(&index->entries, entry)) < 0) { + index_entry_free(entry); + goto done; + } + + if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) { + index_entry_free(entry); + goto done; + } + error = 0; + + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = entry->path; + + seek_forward(entry_size); + } + + if (i != header.entry_count) { + error = index_error_invalid("header entries changed while parsing"); + goto done; + } + + /* There's still space for some extensions! */ + while (buffer_size > checksum_size) { + size_t extension_size; + + if ((error = read_extension(&extension_size, index, buffer, buffer_size)) < 0) { + goto done; + } + + seek_forward(extension_size); + } + + if (buffer_size != checksum_size) { + error = index_error_invalid( + "buffer size does not match index footer size"); + goto done; + } + + /* 160-bit SHA-1 over the content of the index file before this checksum. */ + if (memcmp(checksum, buffer, checksum_size) != 0) { + error = index_error_invalid( + "calculated checksum does not match expected"); + goto done; + } + + memcpy(index->checksum, checksum, checksum_size); + +#undef seek_forward + + /* Entries are stored case-sensitively on disk, so re-sort now if + * in-memory index is supposed to be case-insensitive + */ + git_vector_set_sorted(&index->entries, !index->ignore_case); + git_vector_sort(&index->entries); + + index->dirty = 0; +done: + return error; +} + +static bool is_index_extended(git_index *index) +{ + size_t i, extended; + git_index_entry *entry; + + extended = 0; + + git_vector_foreach(&index->entries, i, entry) { + entry->flags &= ~GIT_INDEX_ENTRY_EXTENDED; + if (entry->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS) { + extended++; + entry->flags |= GIT_INDEX_ENTRY_EXTENDED; + } + } + + return (extended > 0); +} + +static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char *last) +{ + void *mem = NULL; + struct entry_short ondisk; + size_t path_len, disk_size; + int varint_len = 0; + char *path; + const char *path_start = entry->path; + size_t same_len = 0; + + path_len = ((struct entry_internal *)entry)->pathlen; + + if (last) { + const char *last_c = last; + + while (*path_start == *last_c) { + if (!*path_start || !*last_c) + break; + ++path_start; + ++last_c; + ++same_len; + } + path_len -= same_len; + varint_len = git_encode_varint(NULL, 0, strlen(last) - same_len); + } + + disk_size = index_entry_size(path_len, varint_len, entry->flags); + + if (git_filebuf_reserve(file, &mem, disk_size) < 0) + return -1; + + memset(mem, 0x0, disk_size); + + /** + * Yes, we have to truncate. + * + * The on-disk format for Index entries clearly defines + * the time and size fields to be 4 bytes each -- so even if + * we store these values with 8 bytes on-memory, they must + * be truncated to 4 bytes before writing to disk. + * + * In 2038 I will be either too dead or too rich to care about this + */ + ondisk.ctime.seconds = htonl((uint32_t)entry->ctime.seconds); + ondisk.mtime.seconds = htonl((uint32_t)entry->mtime.seconds); + ondisk.ctime.nanoseconds = htonl(entry->ctime.nanoseconds); + ondisk.mtime.nanoseconds = htonl(entry->mtime.nanoseconds); + ondisk.dev = htonl(entry->dev); + ondisk.ino = htonl(entry->ino); + ondisk.mode = htonl(entry->mode); + ondisk.uid = htonl(entry->uid); + ondisk.gid = htonl(entry->gid); + ondisk.file_size = htonl((uint32_t)entry->file_size); + git_oid_raw_cpy(ondisk.oid, entry->id.id); + ondisk.flags = htons(entry->flags); + + if (entry->flags & GIT_INDEX_ENTRY_EXTENDED) { + const size_t path_offset = offsetof(struct entry_long, path); + struct entry_long ondisk_ext; + memcpy(&ondisk_ext, &ondisk, sizeof(struct entry_short)); + ondisk_ext.flags_extended = htons(entry->flags_extended & + GIT_INDEX_ENTRY_EXTENDED_FLAGS); + memcpy(mem, &ondisk_ext, path_offset); + path = (char *)mem + path_offset; + disk_size -= path_offset; + } else { + const size_t path_offset = offsetof(struct entry_short, path); + memcpy(mem, &ondisk, path_offset); + path = (char *)mem + path_offset; + disk_size -= path_offset; + } + + if (last) { + varint_len = git_encode_varint((unsigned char *) path, + disk_size, strlen(last) - same_len); + GIT_ASSERT(varint_len > 0); + + path += varint_len; + disk_size -= varint_len; + + /* + * If using path compression, we are not allowed + * to have additional trailing NULs. + */ + GIT_ASSERT(disk_size == path_len + 1); + } else { + /* + * If no path compression is used, we do have + * NULs as padding. As such, simply assert that + * we have enough space left to write the path. + */ + GIT_ASSERT(disk_size > path_len); + } + + memcpy(path, path_start, path_len + 1); + + return 0; +} + +static int write_entries(git_index *index, git_filebuf *file) +{ + int error = 0; + size_t i; + git_vector case_sorted = GIT_VECTOR_INIT, *entries = NULL; + git_index_entry *entry; + const char *last = NULL; + + /* If index->entries is sorted case-insensitively, then we need + * to re-sort it case-sensitively before writing */ + if (index->ignore_case) { + if ((error = git_vector_dup(&case_sorted, &index->entries, git_index_entry_cmp)) < 0) + goto done; + + git_vector_sort(&case_sorted); + entries = &case_sorted; + } else { + entries = &index->entries; + } + + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = ""; + + git_vector_foreach(entries, i, entry) { + if ((error = write_disk_entry(file, entry, last)) < 0) + break; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = entry->path; + } + +done: + git_vector_free(&case_sorted); + return error; +} + +static int write_extension(git_filebuf *file, struct index_extension *header, git_str *data) +{ + struct index_extension ondisk; + + memset(&ondisk, 0x0, sizeof(struct index_extension)); + memcpy(&ondisk, header, 4); + ondisk.extension_size = htonl(header->extension_size); + + git_filebuf_write(file, &ondisk, sizeof(struct index_extension)); + return git_filebuf_write(file, data->ptr, data->size); +} + +static int create_name_extension_data(git_str *name_buf, git_index_name_entry *conflict_name) +{ + int error = 0; + + if (conflict_name->ancestor == NULL) + error = git_str_put(name_buf, "\0", 1); + else + error = git_str_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1); + + if (error != 0) + goto on_error; + + if (conflict_name->ours == NULL) + error = git_str_put(name_buf, "\0", 1); + else + error = git_str_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1); + + if (error != 0) + goto on_error; + + if (conflict_name->theirs == NULL) + error = git_str_put(name_buf, "\0", 1); + else + error = git_str_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1); + +on_error: + return error; +} + +static int write_name_extension(git_index *index, git_filebuf *file) +{ + git_str name_buf = GIT_STR_INIT; + git_vector *out = &index->names; + git_index_name_entry *conflict_name; + struct index_extension extension; + size_t i; + int error = 0; + + git_vector_foreach(out, i, conflict_name) { + if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0) + goto done; + } + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4); + extension.extension_size = (uint32_t)name_buf.size; + + error = write_extension(file, &extension, &name_buf); + + git_str_dispose(&name_buf); + +done: + return error; +} + +static int create_reuc_extension_data(git_str *reuc_buf, git_index_reuc_entry *reuc) +{ + int i; + int error = 0; + + if ((error = git_str_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0) + return error; + + for (i = 0; i < 3; i++) { + if ((error = git_str_printf(reuc_buf, "%o", reuc->mode[i])) < 0 || + (error = git_str_put(reuc_buf, "\0", 1)) < 0) + return error; + } + + for (i = 0; i < 3; i++) { + if (reuc->mode[i] && (error = git_str_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0) + return error; + } + + return 0; +} + +static int write_reuc_extension(git_index *index, git_filebuf *file) +{ + git_str reuc_buf = GIT_STR_INIT; + git_vector *out = &index->reuc; + git_index_reuc_entry *reuc; + struct index_extension extension; + size_t i; + int error = 0; + + git_vector_foreach(out, i, reuc) { + if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0) + goto done; + } + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4); + extension.extension_size = (uint32_t)reuc_buf.size; + + error = write_extension(file, &extension, &reuc_buf); + + git_str_dispose(&reuc_buf); + +done: + return error; +} + +static int write_tree_extension(git_index *index, git_filebuf *file) +{ + struct index_extension extension; + git_str buf = GIT_STR_INIT; + int error; + + if (index->tree == NULL) + return 0; + + if ((error = git_tree_cache_write(&buf, index->tree)) < 0) + return error; + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_TREECACHE_SIG, 4); + extension.extension_size = (uint32_t)buf.size; + + error = write_extension(file, &extension, &buf); + + git_str_dispose(&buf); + + return error; +} + +static void clear_uptodate(git_index *index) +{ + git_index_entry *entry; + size_t i; + + git_vector_foreach(&index->entries, i, entry) + entry->flags_extended &= ~GIT_INDEX_ENTRY_UPTODATE; +} + +static int write_index( + unsigned char checksum[GIT_HASH_SHA1_SIZE], + size_t *checksum_size, + git_index *index, + git_filebuf *file) +{ + struct index_header header; + bool is_extended; + uint32_t index_version_number; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(file); + + *checksum_size = GIT_HASH_SHA1_SIZE; + + if (index->version <= INDEX_VERSION_NUMBER_EXT) { + is_extended = is_index_extended(index); + index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB; + } else { + index_version_number = index->version; + } + + header.signature = htonl(INDEX_HEADER_SIG); + header.version = htonl(index_version_number); + header.entry_count = htonl((uint32_t)index->entries.length); + + if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) + return -1; + + if (write_entries(index, file) < 0) + return -1; + + /* write the tree cache extension */ + if (index->tree != NULL && write_tree_extension(index, file) < 0) + return -1; + + /* write the rename conflict extension */ + if (index->names.length > 0 && write_name_extension(index, file) < 0) + return -1; + + /* write the reuc extension */ + if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0) + return -1; + + /* get out the hash for all the contents we've appended to the file */ + git_filebuf_hash(checksum, file); + + /* write it at the end of the file */ + if (git_filebuf_write(file, checksum, *checksum_size) < 0) + return -1; + + /* file entries are no longer up to date */ + clear_uptodate(index); + + return 0; +} + +int git_index_entry_stage(const git_index_entry *entry) +{ + return GIT_INDEX_ENTRY_STAGE(entry); +} + +int git_index_entry_is_conflict(const git_index_entry *entry) +{ + return (GIT_INDEX_ENTRY_STAGE(entry) > 0); +} + +typedef struct read_tree_data { + git_index *index; + git_vector *old_entries; + git_vector *new_entries; + git_vector_cmp entry_cmp; + git_tree_cache *tree; +} read_tree_data; + +static int read_tree_cb( + const char *root, const git_tree_entry *tentry, void *payload) +{ + read_tree_data *data = payload; + git_index_entry *entry = NULL, *old_entry; + git_str path = GIT_STR_INIT; + size_t pos; + + if (git_tree_entry__is_tree(tentry)) + return 0; + + if (git_str_joinpath(&path, root, tentry->filename) < 0) + return -1; + + if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, NULL, false) < 0) + return -1; + + entry->mode = tentry->attr; + git_oid_cpy(&entry->id, git_tree_entry_id(tentry)); + + /* look for corresponding old entry and copy data to new entry */ + if (data->old_entries != NULL && + !index_find_in_entries( + &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) && + (old_entry = git_vector_get(data->old_entries, pos)) != NULL && + entry->mode == old_entry->mode && + git_oid_equal(&entry->id, &old_entry->id)) + { + index_entry_cpy(entry, old_entry); + entry->flags_extended = 0; + } + + index_entry_adjust_namemask(entry, path.size); + git_str_dispose(&path); + + if (git_vector_insert(data->new_entries, entry) < 0) { + index_entry_free(entry); + return -1; + } + + return 0; +} + +int git_index_read_tree(git_index *index, const git_tree *tree) +{ + int error = 0; + git_vector entries = GIT_VECTOR_INIT; + git_idxmap *entries_map; + read_tree_data data; + size_t i; + git_index_entry *e; + + if (git_idxmap_new(&entries_map) < 0) + return -1; + + git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ + + data.index = index; + data.old_entries = &index->entries; + data.new_entries = &entries; + data.entry_cmp = index->entries_search; + + index->tree = NULL; + git_pool_clear(&index->tree_pool); + + git_vector_sort(&index->entries); + + if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) + goto cleanup; + + if ((error = index_map_resize(entries_map, entries.length, index->ignore_case)) < 0) + goto cleanup; + + git_vector_foreach(&entries, i, e) { + 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; + } + } + + error = 0; + + git_vector_sort(&entries); + + if ((error = git_index_clear(index)) < 0) { + /* well, this isn't good */; + } else { + git_vector_swap(&entries, &index->entries); + entries_map = git_atomic_swap(index->entries_map, entries_map); + } + + index->dirty = 1; + +cleanup: + git_vector_free(&entries); + git_idxmap_free(entries_map); + if (error < 0) + return error; + + error = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); + + return error; +} + +static int git_index_read_iterator( + git_index *index, + git_iterator *new_iterator, + size_t new_length_hint) +{ + git_vector new_entries = GIT_VECTOR_INIT, + remove_entries = GIT_VECTOR_INIT; + git_idxmap *new_entries_map = NULL; + git_iterator *index_iterator = NULL; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *old_entry, *new_entry; + git_index_entry *entry; + size_t i; + int error; + + GIT_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_new(&new_entries_map)) < 0) + goto done; + + 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; + + if ((error = git_iterator_for_index(&index_iterator, + git_index_owner(index), index, &opts)) < 0 || + ((error = git_iterator_current(&old_entry, index_iterator)) < 0 && + error != GIT_ITEROVER) || + ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && + error != GIT_ITEROVER)) + goto done; + + while (true) { + git_index_entry + *dup_entry = NULL, + *add_entry = NULL, + *remove_entry = NULL; + int diff; + + error = 0; + + if (old_entry && new_entry) + diff = git_index_entry_cmp(old_entry, new_entry); + else if (!old_entry && new_entry) + diff = 1; + else if (old_entry && !new_entry) + diff = -1; + else + break; + + if (diff < 0) { + remove_entry = (git_index_entry *)old_entry; + } else if (diff > 0) { + dup_entry = (git_index_entry *)new_entry; + } else { + /* Path and stage are equal, if the OID is equal, keep it to + * keep the stat cache data. + */ + if (git_oid_equal(&old_entry->id, &new_entry->id) && + old_entry->mode == new_entry->mode) { + add_entry = (git_index_entry *)old_entry; + } else { + dup_entry = (git_index_entry *)new_entry; + remove_entry = (git_index_entry *)old_entry; + } + } + + if (dup_entry) { + if ((error = index_entry_dup_nocache(&add_entry, index, dup_entry)) < 0) + goto done; + + index_entry_adjust_namemask(add_entry, + ((struct entry_internal *)add_entry)->pathlen); + } + + /* invalidate this path in the tree cache if this is new (to + * invalidate the parent trees) + */ + if (dup_entry && !remove_entry && index->tree) + git_tree_cache_invalidate_path(index->tree, dup_entry->path); + + if (add_entry) { + if ((error = git_vector_insert(&new_entries, add_entry)) == 0) + error = index_map_set(new_entries_map, add_entry, + index->ignore_case); + } + + if (remove_entry && error >= 0) + error = git_vector_insert(&remove_entries, remove_entry); + + if (error < 0) { + git_error_set(GIT_ERROR_INDEX, "failed to insert entry"); + goto done; + } + + if (diff <= 0) { + if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 && + error != GIT_ITEROVER) + goto done; + } + + if (diff >= 0) { + if ((error = git_iterator_advance(&new_entry, new_iterator)) < 0 && + error != GIT_ITEROVER) + goto done; + } + } + + 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_atomic_swap(index->entries_map, new_entries_map); + + git_vector_foreach(&remove_entries, i, entry) { + if (index->tree) + git_tree_cache_invalidate_path(index->tree, entry->path); + + index_entry_free(entry); + } + + clear_uptodate(index); + + index->dirty = 1; + error = 0; + +done: + git_idxmap_free(new_entries_map); + git_vector_free(&new_entries); + git_vector_free(&remove_entries); + git_iterator_free(index_iterator); + return error; +} + +int git_index_read_index( + git_index *index, + const git_index *new_index) +{ + git_iterator *new_iterator = NULL; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; + + if ((error = git_iterator_for_index(&new_iterator, + git_index_owner(new_index), (git_index *)new_index, &opts)) < 0 || + (error = git_index_read_iterator(index, new_iterator, + new_index->entries.length)) < 0) + goto done; + +done: + git_iterator_free(new_iterator); + return error; +} + +git_repository *git_index_owner(const git_index *index) +{ + return INDEX_OWNER(index); +} + +enum { + INDEX_ACTION_NONE = 0, + INDEX_ACTION_UPDATE = 1, + INDEX_ACTION_REMOVE = 2, + INDEX_ACTION_ADDALL = 3 +}; + +int git_index_add_all( + git_index *index, + const git_strarray *paths, + unsigned int flags, + git_index_matched_path_cb cb, + void *payload) +{ + int error; + git_repository *repo; + git_iterator *wditer = NULL; + git_pathspec ps; + bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; + + GIT_ASSERT_ARG(index); + + repo = INDEX_OWNER(index); + if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0) + return error; + + if ((error = git_pathspec__init(&ps, paths)) < 0) + return error; + + /* optionally check that pathspec doesn't mention any ignored files */ + if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 && + (flags & GIT_INDEX_ADD_FORCE) == 0 && + (error = git_ignore__check_pathspec_for_exact_ignores( + repo, &ps.pathspec, no_fnmatch)) < 0) + goto cleanup; + + error = index_apply_to_wd_diff(index, INDEX_ACTION_ADDALL, paths, flags, cb, payload); + + if (error) + git_error_set_after_callback(error); + +cleanup: + git_iterator_free(wditer); + git_pathspec__clear(&ps); + + return error; +} + +struct foreach_diff_data { + git_index *index; + const git_pathspec *pathspec; + unsigned int flags; + git_index_matched_path_cb cb; + void *payload; +}; + +static int apply_each_file(const git_diff_delta *delta, float progress, void *payload) +{ + struct foreach_diff_data *data = payload; + const char *match, *path; + int error = 0; + + GIT_UNUSED(progress); + + path = delta->old_file.path; + + /* We only want those which match the pathspecs */ + if (!git_pathspec__match( + &data->pathspec->pathspec, path, false, (bool)data->index->ignore_case, + &match, NULL)) + return 0; + + if (data->cb) + error = data->cb(path, match, data->payload); + + if (error > 0) /* skip this entry */ + return 0; + if (error < 0) /* actual error */ + return error; + + /* If the workdir item does not exist, remove it from the index. */ + if ((delta->new_file.flags & GIT_DIFF_FLAG_EXISTS) == 0) + error = git_index_remove_bypath(data->index, path); + else + error = git_index_add_bypath(data->index, delta->new_file.path); + + return error; +} + +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) +{ + int error; + git_diff *diff; + git_pathspec ps; + git_repository *repo; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct foreach_diff_data data = { + index, + NULL, + flags, + cb, + payload, + }; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(action == INDEX_ACTION_UPDATE || action == INDEX_ACTION_ADDALL); + + repo = INDEX_OWNER(index); + + if (!repo) { + return create_index_error(-1, + "cannot run update; the index is not backed up by a repository."); + } + + /* + * We do the matching ourselves instead of passing the list to + * diff because we want to tell the callback which one + * matched, which we do not know if we ask diff to filter for us. + */ + if ((error = git_pathspec__init(&ps, paths)) < 0) + return error; + + opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + if (action == INDEX_ACTION_ADDALL) { + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS; + + if (flags == GIT_INDEX_ADD_FORCE) + opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + } + + if ((error = git_diff_index_to_workdir(&diff, repo, index, &opts)) < 0) + goto cleanup; + + data.pathspec = &ps; + error = git_diff_foreach(diff, apply_each_file, NULL, NULL, NULL, &data); + git_diff_free(diff); + + if (error) /* make sure error is set if callback stopped iteration */ + git_error_set_after_callback(error); + +cleanup: + git_pathspec__clear(&ps); + return error; +} + +static int index_apply_to_all( + git_index *index, + int action, + const git_strarray *paths, + git_index_matched_path_cb cb, + void *payload) +{ + int error = 0; + size_t i; + git_pathspec ps; + const char *match; + git_str path = GIT_STR_INIT; + + GIT_ASSERT_ARG(index); + + if ((error = git_pathspec__init(&ps, paths)) < 0) + return error; + + git_vector_sort(&index->entries); + + for (i = 0; !error && i < index->entries.length; ++i) { + git_index_entry *entry = git_vector_get(&index->entries, i); + + /* check if path actually matches */ + if (!git_pathspec__match( + &ps.pathspec, entry->path, false, (bool)index->ignore_case, + &match, NULL)) + continue; + + /* issue notification callback if requested */ + if (cb && (error = cb(entry->path, match, payload)) != 0) { + if (error > 0) { /* return > 0 means skip this one */ + error = 0; + continue; + } + if (error < 0) /* return < 0 means abort */ + break; + } + + /* index manipulation may alter entry, so don't depend on it */ + if ((error = git_str_sets(&path, entry->path)) < 0) + break; + + switch (action) { + case INDEX_ACTION_NONE: + break; + case INDEX_ACTION_UPDATE: + error = git_index_add_bypath(index, path.ptr); + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + + error = git_index_remove_bypath(index, path.ptr); + + if (!error) /* back up foreach if we removed this */ + i--; + } + break; + case INDEX_ACTION_REMOVE: + if (!(error = git_index_remove_bypath(index, path.ptr))) + i--; /* back up foreach if we removed this */ + break; + default: + git_error_set(GIT_ERROR_INVALID, "unknown index action %d", action); + error = -1; + break; + } + } + + git_str_dispose(&path); + git_pathspec__clear(&ps); + + return error; +} + +int git_index_remove_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb cb, + void *payload) +{ + int error = index_apply_to_all( + index, INDEX_ACTION_REMOVE, pathspec, cb, payload); + + if (error) /* make sure error is set if callback stopped iteration */ + git_error_set_after_callback(error); + + return error; +} + +int git_index_update_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb cb, + void *payload) +{ + int error = index_apply_to_wd_diff(index, INDEX_ACTION_UPDATE, pathspec, 0, cb, payload); + if (error) /* make sure error is set if callback stopped iteration */ + git_error_set_after_callback(error); + + return error; +} + +int git_index_snapshot_new(git_vector *snap, git_index *index) +{ + int error; + + GIT_REFCOUNT_INC(index); + + git_atomic32_inc(&index->readers); + git_vector_sort(&index->entries); + + error = git_vector_dup(snap, &index->entries, index->entries._cmp); + + if (error < 0) + git_index_snapshot_release(snap, index); + + return error; +} + +void git_index_snapshot_release(git_vector *snap, git_index *index) +{ + git_vector_free(snap); + + git_atomic32_dec(&index->readers); + + git_index_free(index); +} + +int git_index_snapshot_find( + size_t *out, git_vector *entries, git_vector_cmp entry_srch, + const char *path, size_t path_len, int stage) +{ + return index_find_in_entries(out, entries, entry_srch, path, path_len, stage); +} + +int git_indexwriter_init( + git_indexwriter *writer, + git_index *index) +{ + int error; + + GIT_REFCOUNT_INC(index); + + writer->index = index; + + if (!index->index_file_path) + return create_index_error(-1, + "failed to write index: The index is in-memory only"); + + if ((error = git_filebuf_open( + &writer->file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) { + + if (error == GIT_ELOCKED) + git_error_set(GIT_ERROR_INDEX, "the index is locked; this might be due to a concurrent or crashed process"); + + return error; + } + + writer->should_write = 1; + + return 0; +} + +int git_indexwriter_init_for_operation( + git_indexwriter *writer, + git_repository *repo, + unsigned int *checkout_strategy) +{ + git_index *index; + int error; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_indexwriter_init(writer, index)) < 0) + return error; + + writer->should_write = (*checkout_strategy & GIT_CHECKOUT_DONT_WRITE_INDEX) == 0; + *checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; + + return 0; +} + +int git_indexwriter_commit(git_indexwriter *writer) +{ + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + int error; + + if (!writer->should_write) + return 0; + + git_vector_sort(&writer->index->entries); + git_vector_sort(&writer->index->reuc); + + if ((error = write_index(checksum, &checksum_size, writer->index, &writer->file)) < 0) { + git_indexwriter_cleanup(writer); + return error; + } + + if ((error = git_filebuf_commit(&writer->file)) < 0) + return error; + + if ((error = git_futils_filestamp_check( + &writer->index->stamp, writer->index->index_file_path)) < 0) { + git_error_set(GIT_ERROR_OS, "could not read index timestamp"); + return -1; + } + + writer->index->dirty = 0; + writer->index->on_disk = 1; + memcpy(writer->index->checksum, checksum, checksum_size); + + git_index_free(writer->index); + writer->index = NULL; + + return 0; +} + +void git_indexwriter_cleanup(git_indexwriter *writer) +{ + git_filebuf_cleanup(&writer->file); + + git_index_free(writer->index); + writer->index = NULL; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD +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); +} +#endif diff --git a/src/libgit2/index.h b/src/libgit2/index.h new file mode 100644 index 000000000..71bb096f7 --- /dev/null +++ b/src/libgit2/index.h @@ -0,0 +1,194 @@ +/* + * 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_index_h__ +#define INCLUDE_index_h__ + +#include "common.h" + +#include "futils.h" +#include "filebuf.h" +#include "vector.h" +#include "idxmap.h" +#include "tree-cache.h" +#include "git2/odb.h" +#include "git2/index.h" + +#define GIT_INDEX_FILE "index" +#define GIT_INDEX_FILE_MODE 0666 + +extern bool git_index__enforce_unsaved_safety; + +struct git_index { + git_refcount rc; + + char *index_file_path; + git_futils_filestamp stamp; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + + git_vector entries; + git_idxmap *entries_map; + + git_vector deleted; /* deleted entries if readers > 0 */ + git_atomic32 readers; /* number of active iterators */ + + unsigned int on_disk:1; + unsigned int ignore_case:1; + unsigned int distrust_filemode:1; + unsigned int no_symlinks:1; + unsigned int dirty:1; /* whether we have unsaved changes */ + + git_tree_cache *tree; + git_pool tree_pool; + + git_vector names; + git_vector reuc; + + git_vector_cmp entries_cmp_path; + git_vector_cmp entries_search; + git_vector_cmp entries_search_path; + git_vector_cmp reuc_search; + + unsigned int version; +}; + +struct git_index_iterator { + git_index *index; + git_vector snap; + size_t cur; +}; + +struct git_index_conflict_iterator { + git_index *index; + size_t cur; +}; + +extern void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st, bool trust_mode); + +/* Index entry comparison functions for array sorting */ +extern int git_index_entry_cmp(const void *a, const void *b); +extern int git_index_entry_icmp(const void *a, const void *b); + +/* Index entry search functions for search using a search spec */ +extern int git_index_entry_srch(const void *a, const void *b); +extern int git_index_entry_isrch(const void *a, const void *b); + +/* Index time handling functions */ +GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_time *two) +{ + if (one->seconds != two->seconds) + return false; + +#ifdef GIT_USE_NSEC + if (one->nanoseconds != two->nanoseconds) + return false; +#endif + + return true; +} + +/* + * Test if the given index time is newer than the given existing index entry. + * If the timestamps are exactly equivalent, then the given index time is + * considered "racily newer" than the existing index entry. + */ +GIT_INLINE(bool) git_index_entry_newer_than_index( + const git_index_entry *entry, git_index *index) +{ + /* If we never read the index, we can't have this race either */ + if (!index || index->stamp.mtime.tv_sec == 0) + return false; + + /* If the timestamp is the same or newer than the index, it's racy */ +#if defined(GIT_USE_NSEC) + if ((int32_t)index->stamp.mtime.tv_sec < entry->mtime.seconds) + return true; + else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds) + return false; + else + return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds; +#else + return ((int32_t)index->stamp.mtime.tv_sec) <= entry->mtime.seconds; +#endif +} + +/* Search index for `path`, returning GIT_ENOTFOUND if it does not exist + * (but not setting an error message). + * + * `at_pos` is set to the position where it is or would be inserted. + * Pass `path_len` as strlen of path or 0 to call strlen internally. + */ +extern int git_index__find_pos( + size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage); + +extern int git_index__fill(git_index *index, const git_vector *source_entries); + +extern void git_index__set_ignore_case(git_index *index, bool ignore_case); + +extern unsigned int git_index__create_mode(unsigned int mode); + +GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index) +{ + return &index->stamp; +} + +GIT_INLINE(unsigned char *) git_index__checksum(git_index *index) +{ + return index->checksum; +} + +/* Copy the current entries vector *and* increment the index refcount. + * Call `git_index__release_snapshot` when done. + */ +extern int git_index_snapshot_new(git_vector *snap, git_index *index); +extern void git_index_snapshot_release(git_vector *snap, git_index *index); + +/* Allow searching in a snapshot; entries must already be sorted! */ +extern int git_index_snapshot_find( + size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch, + const char *path, size_t path_len, int stage); + +/* Replace an index with a new index */ +int git_index_read_index(git_index *index, const git_index *new_index); + +GIT_INLINE(int) git_index_is_dirty(git_index *index) +{ + return index->dirty; +} + +extern int git_index_read_safely(git_index *index); + +typedef struct { + git_index *index; + git_filebuf file; + unsigned int should_write:1; +} git_indexwriter; + +#define GIT_INDEXWRITER_INIT { NULL, GIT_FILEBUF_INIT } + +/* Lock the index for eventual writing. */ +extern int git_indexwriter_init(git_indexwriter *writer, git_index *index); + +/* Lock the index for eventual writing by a repository operation: a merge, + * revert, cherry-pick or a rebase. Note that the given checkout strategy + * will be updated for the operation's use so that checkout will not write + * the index. + */ +extern int git_indexwriter_init_for_operation( + git_indexwriter *writer, + git_repository *repo, + unsigned int *checkout_strategy); + +/* Write the index and unlock it. */ +extern int git_indexwriter_commit(git_indexwriter *writer); + +/* Cleanup an index writing session, unlocking the file (if it is still + * locked and freeing any data structures. + */ +extern void git_indexwriter_cleanup(git_indexwriter *writer); + +#endif diff --git a/src/libgit2/indexer.c b/src/libgit2/indexer.c new file mode 100644 index 000000000..afb9ce81a --- /dev/null +++ b/src/libgit2/indexer.c @@ -0,0 +1,1413 @@ +/* + * 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 "indexer.h" + +#include "git2/indexer.h" +#include "git2/object.h" + +#include "commit.h" +#include "tree.h" +#include "tag.h" +#include "pack.h" +#include "mwindow.h" +#include "posix.h" +#include "pack.h" +#include "filebuf.h" +#include "oid.h" +#include "oidarray.h" +#include "oidmap.h" +#include "zstream.h" +#include "object.h" + +size_t git_indexer__max_objects = UINT32_MAX; + +#define UINT31_MAX (0x7FFFFFFF) + +struct entry { + git_oid oid; + uint32_t crc; + uint32_t offset; + uint64_t offset_long; +}; + +struct git_indexer { + unsigned int parsed_header :1, + pack_committed :1, + have_stream :1, + have_delta :1, + do_fsync :1, + do_verify :1; + struct git_pack_header hdr; + struct git_pack_file *pack; + unsigned int mode; + off64_t off; + off64_t entry_start; + git_object_t entry_type; + git_str entry_data; + git_packfile_stream stream; + size_t nr_objects; + git_vector objects; + git_vector deltas; + unsigned int fanout[256]; + git_hash_ctx hash_ctx; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + char name[(GIT_HASH_SHA1_SIZE * 2) + 1]; + git_indexer_progress_cb progress_cb; + void *progress_payload; + char objbuf[8*1024]; + + /* OIDs referenced from pack objects. Used for verification. */ + git_oidmap *expected_oids; + + /* Needed to look up objects which we want to inject to fix a thin pack */ + git_odb *odb; + + /* Fields for calculating the packfile trailer (hash of everything before it) */ + char inbuf[GIT_OID_RAWSZ]; + size_t inbuf_len; + git_hash_ctx trailer; +}; + +struct delta_info { + off64_t delta_off; +}; + +#ifndef GIT_DEPRECATE_HARD +const git_oid *git_indexer_hash(const git_indexer *idx) +{ + return (git_oid *)idx->checksum; +} +#endif + +const char *git_indexer_name(const git_indexer *idx) +{ + return idx->name; +} + +static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) +{ + int error; + git_map map; + + if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) + return error; + + memcpy(hdr, map.data, sizeof(*hdr)); + p_munmap(&map); + + /* Verify we recognize this pack file format. */ + if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { + git_error_set(GIT_ERROR_INDEXER, "wrong pack signature"); + return -1; + } + + if (!pack_version_ok(hdr->hdr_version)) { + git_error_set(GIT_ERROR_INDEXER, "wrong pack version"); + return -1; + } + + return 0; +} + +static int objects_cmp(const void *a, const void *b) +{ + const struct entry *entrya = a; + const struct entry *entryb = b; + + return git_oid__cmp(&entrya->oid, &entryb->oid); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_indexer_init_options(git_indexer_options *opts, unsigned int version) +{ + return git_indexer_options_init(opts, version); +} +#endif + +int git_indexer_new( + git_indexer **out, + const char *prefix, + unsigned int mode, + git_odb *odb, + git_indexer_options *in_opts) +{ + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer *idx; + git_str path = GIT_STR_INIT, tmp_path = GIT_STR_INIT; + static const char suff[] = "/pack"; + int error, fd = -1; + + if (in_opts) + memcpy(&opts, in_opts, sizeof(opts)); + + idx = git__calloc(1, sizeof(git_indexer)); + GIT_ERROR_CHECK_ALLOC(idx); + idx->odb = odb; + idx->progress_cb = opts.progress_cb; + idx->progress_payload = opts.progress_cb_payload; + idx->mode = mode ? mode : GIT_PACK_FILE_MODE; + git_str_init(&idx->entry_data, 0); + + if ((error = git_hash_ctx_init(&idx->hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = git_hash_ctx_init(&idx->trailer, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = git_oidmap_new(&idx->expected_oids)) < 0) + goto cleanup; + + idx->do_verify = opts.verify; + + if (git_repository__fsync_gitdir) + idx->do_fsync = 1; + + error = git_str_joinpath(&path, prefix, suff); + if (error < 0) + goto cleanup; + + fd = git_futils_mktmp(&tmp_path, git_str_cstr(&path), idx->mode); + git_str_dispose(&path); + if (fd < 0) + goto cleanup; + + error = git_packfile_alloc(&idx->pack, git_str_cstr(&tmp_path)); + git_str_dispose(&tmp_path); + + if (error < 0) + goto cleanup; + + idx->pack->mwf.fd = fd; + if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) + goto cleanup; + + *out = idx; + return 0; + +cleanup: + if (fd != -1) + p_close(fd); + + if (git_str_len(&tmp_path) > 0) + p_unlink(git_str_cstr(&tmp_path)); + + if (idx->pack != NULL) + p_unlink(idx->pack->pack_name); + + git_str_dispose(&path); + git_str_dispose(&tmp_path); + git__free(idx); + return -1; +} + +void git_indexer__set_fsync(git_indexer *idx, int do_fsync) +{ + idx->do_fsync = !!do_fsync; +} + +/* Try to store the delta so we can try to resolve it later */ +static int store_delta(git_indexer *idx) +{ + struct delta_info *delta; + + delta = git__calloc(1, sizeof(struct delta_info)); + GIT_ERROR_CHECK_ALLOC(delta); + delta->delta_off = idx->entry_start; + + if (git_vector_insert(&idx->deltas, delta) < 0) + return -1; + + return 0; +} + +static int hash_header(git_hash_ctx *ctx, off64_t len, git_object_t type) +{ + char buffer[64]; + size_t hdrlen; + int error; + + if ((error = git_odb__format_object_header(&hdrlen, + buffer, sizeof(buffer), (size_t)len, type)) < 0) + return error; + + return git_hash_update(ctx, buffer, hdrlen); +} + +static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream) +{ + ssize_t read; + + GIT_ASSERT_ARG(idx); + GIT_ASSERT_ARG(stream); + + do { + if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0) + break; + + if (idx->do_verify) + git_str_put(&idx->entry_data, idx->objbuf, read); + + git_hash_update(&idx->hash_ctx, idx->objbuf, read); + } while (read > 0); + + if (read < 0) + return (int)read; + + return 0; +} + +/* In order to create the packfile stream, we need to skip over the delta base description */ +static int advance_delta_offset(git_indexer *idx, git_object_t type) +{ + git_mwindow *w = NULL; + + GIT_ASSERT_ARG(type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA); + + if (type == GIT_OBJECT_REF_DELTA) { + idx->off += GIT_OID_RAWSZ; + } else { + off64_t base_off; + int error = get_delta_base(&base_off, idx->pack, &w, &idx->off, type, idx->entry_start); + git_mwindow_close(&w); + if (error < 0) + return error; + } + + return 0; +} + +/* Read from the stream and discard any output */ +static int read_object_stream(git_indexer *idx, git_packfile_stream *stream) +{ + ssize_t read; + + GIT_ASSERT_ARG(stream); + + do { + read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf)); + } while (read > 0); + + if (read < 0) + return (int)read; + + return 0; +} + +static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, off64_t start, off64_t size) +{ + void *ptr; + uint32_t crc; + unsigned int left, len; + git_mwindow *w = NULL; + + crc = crc32(0L, Z_NULL, 0); + while (size) { + ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left); + if (ptr == NULL) + return -1; + + len = min(left, (unsigned int)size); + crc = crc32(crc, ptr, len); + size -= len; + start += len; + git_mwindow_close(&w); + } + + *crc_out = htonl(crc); + return 0; +} + +static int add_expected_oid(git_indexer *idx, const git_oid *oid) +{ + /* + * 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 + * not have to expect it. + */ + if ((!idx->odb || !git_odb_exists(idx->odb, 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); + 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; + git_oid *expected; + int error = 0; + + if (obj->type != GIT_OBJECT_BLOB && + obj->type != GIT_OBJECT_TREE && + obj->type != GIT_OBJECT_COMMIT && + obj->type != GIT_OBJECT_TAG) + return 0; + + if (git_object__from_raw(&object, obj->data, obj->len, obj->type) < 0) { + /* + * parse_raw returns EINVALID on invalid data; downgrade + * that to a normal -1 error code. + */ + error = -1; + goto out; + } + + if ((expected = git_oidmap_get(idx->expected_oids, &object->cached.oid)) != NULL) { + git_oidmap_delete(idx->expected_oids, &object->cached.oid); + git__free(expected); + } + + /* + * Check whether this is a known object. If so, we can just continue as + * we assume that the ODB has a complete graph. + */ + if (idx->odb && git_odb_exists(idx->odb, &object->cached.oid)) + return 0; + + switch (obj->type) { + case GIT_OBJECT_TREE: + { + git_tree *tree = (git_tree *) object; + git_tree_entry *entry; + size_t i; + + git_array_foreach(tree->entries, i, entry) + if (add_expected_oid(idx, &entry->oid) < 0) + goto out; + + break; + } + case GIT_OBJECT_COMMIT: + { + git_commit *commit = (git_commit *) object; + git_oid *parent_oid; + size_t i; + + git_array_foreach(commit->parent_ids, i, parent_oid) + if (add_expected_oid(idx, parent_oid) < 0) + goto out; + + if (add_expected_oid(idx, &commit->tree_id) < 0) + goto out; + + break; + } + case GIT_OBJECT_TAG: + { + git_tag *tag = (git_tag *) object; + + if (add_expected_oid(idx, &tag->target) < 0) + goto out; + + break; + } + case GIT_OBJECT_BLOB: + default: + break; + } + +out: + git_object_free(object); + + return error; +} + +static int store_object(git_indexer *idx) +{ + int i, error; + git_oid oid; + struct entry *entry; + off64_t entry_size; + struct git_pack_entry *pentry; + off64_t entry_start = idx->entry_start; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GIT_ERROR_CHECK_ALLOC(pentry); + + if (git_hash_final(oid.id, &idx->hash_ctx)) { + git__free(pentry); + goto on_error; + } + entry_size = idx->off - entry_start; + if (entry_start > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = entry_start; + } else { + entry->offset = (uint32_t)entry_start; + } + + if (idx->do_verify) { + git_rawobj rawobj = { + idx->entry_data.ptr, + idx->entry_data.size, + idx->entry_type + }; + + if ((error = check_object_connectivity(idx, &rawobj)) < 0) + goto on_error; + } + + git_oid_cpy(&pentry->sha1, &oid); + pentry->offset = entry_start; + + 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; + } + + 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); + + if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) + goto on_error; + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + goto on_error; + + for (i = oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; + +on_error: + git__free(entry); + + return -1; +} + +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, off64_t entry_start) +{ + int i; + + if (entry_start > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = entry_start; + } else { + entry->offset = (uint32_t)entry_start; + } + + pentry->offset = entry_start; + + 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; + } + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + return -1; + + for (i = entry->oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; +} + +static int hash_and_save(git_indexer *idx, git_rawobj *obj, off64_t entry_start) +{ + git_oid oid; + size_t entry_size; + struct entry *entry; + struct git_pack_entry *pentry = NULL; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + if (git_odb__hashobj(&oid, obj) < 0) { + git_error_set(GIT_ERROR_INDEXER, "failed to hash object"); + goto on_error; + } + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GIT_ERROR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, &oid); + git_oid_cpy(&entry->oid, &oid); + entry->crc = crc32(0L, Z_NULL, 0); + + entry_size = (size_t)(idx->off - entry_start); + if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) + goto on_error; + + return save_entry(idx, entry, pentry, entry_start); + +on_error: + git__free(pentry); + git__free(entry); + git__free(obj->data); + return -1; +} + +static int do_progress_callback(git_indexer *idx, git_indexer_progress *stats) +{ + if (idx->progress_cb) + return git_error_set_after_callback_function( + idx->progress_cb(stats, idx->progress_payload), + "indexer progress"); + return 0; +} + +/* Hash everything but the last 20B of input */ +static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) +{ + size_t to_expell, to_keep; + + if (size == 0) + return; + + /* Easy case, dump the buffer and the data minus the last 20 bytes */ + if (size >= GIT_OID_RAWSZ) { + git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len); + git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ); + + data += size - GIT_OID_RAWSZ; + memcpy(idx->inbuf, data, GIT_OID_RAWSZ); + idx->inbuf_len = GIT_OID_RAWSZ; + return; + } + + /* We can just append */ + if (idx->inbuf_len + size <= GIT_OID_RAWSZ) { + memcpy(idx->inbuf + idx->inbuf_len, data, size); + idx->inbuf_len += size; + return; + } + + /* We need to partially drain the buffer and then append */ + to_keep = GIT_OID_RAWSZ - size; + to_expell = idx->inbuf_len - to_keep; + + git_hash_update(&idx->trailer, idx->inbuf, to_expell); + + memmove(idx->inbuf, idx->inbuf + to_expell, to_keep); + memcpy(idx->inbuf + to_keep, data, size); + idx->inbuf_len += size - to_expell; +} + +#if defined(NO_MMAP) || !defined(GIT_WIN32) + +static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) +{ + size_t remaining_size = size; + const char *ptr = (const char *)data; + + /* Handle data size larger that ssize_t */ + while (remaining_size > 0) { + ssize_t nb; + HANDLE_EINTR(nb, p_pwrite(idx->pack->mwf.fd, (void *)ptr, + remaining_size, offset)); + if (nb <= 0) + return -1; + + ptr += nb; + offset += nb; + remaining_size -= nb; + } + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + if (write_at(idx, data, idx->pack->mwf.size, size) < 0) { + git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); + return -1; + } + + return 0; +} + +#else + +/* + * Windows may keep different views to a networked file for the mmap- and + * open-accessed versions of a file, so any writes done through + * `write(2)`/`pwrite(2)` may not be reflected on the data that `mmap(2)` is + * able to read. + */ + +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; + off64_t page_start; + unsigned char *map_data; + git_map map; + int error; + + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(size); + + if ((error = git__mmap_alignment(&mmap_alignment)) < 0) + return error; + + /* the offset needs to be at the mmap boundary for the platform */ + page_offset = offset % mmap_alignment; + page_start = offset - page_offset; + + if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) + return error; + + map_data = (unsigned char *)map.data; + memcpy(map_data + page_offset, data, size); + p_munmap(&map); + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + off64_t new_size; + size_t mmap_alignment; + size_t page_offset; + off64_t page_start; + off64_t current_size = idx->pack->mwf.size; + int error; + + if (!size) + return 0; + + if ((error = git__mmap_alignment(&mmap_alignment)) < 0) + return error; + + /* Write a single byte to force the file system to allocate space now or + * report an error, since we can't report errors when writing using mmap. + * Round the size up to the nearest page so that we only need to perform file + * I/O when we add a page, instead of whenever we write even a single byte. */ + new_size = current_size + size; + page_offset = new_size % mmap_alignment; + page_start = new_size - page_offset; + + if (p_pwrite(idx->pack->mwf.fd, data, 1, page_start + mmap_alignment - 1) < 0) { + git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); + return -1; + } + + return write_at(idx, data, idx->pack->mwf.size, size); +} + +#endif + +static int read_stream_object(git_indexer *idx, git_indexer_progress *stats) +{ + git_packfile_stream *stream = &idx->stream; + off64_t entry_start = idx->off; + size_t entry_size; + git_object_t type; + git_mwindow *w = NULL; + int error; + + if (idx->pack->mwf.size <= idx->off + 20) + return GIT_EBUFS; + + if (!idx->have_stream) { + error = git_packfile_unpack_header(&entry_size, &type, idx->pack, &w, &idx->off); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return error; + } + if (error < 0) + return error; + + git_mwindow_close(&w); + idx->entry_start = entry_start; + git_hash_init(&idx->hash_ctx); + git_str_clear(&idx->entry_data); + + if (type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA) { + error = advance_delta_offset(idx, type); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return error; + } + if (error < 0) + return error; + + idx->have_delta = 1; + } else { + idx->have_delta = 0; + + error = hash_header(&idx->hash_ctx, entry_size, type); + if (error < 0) + return error; + } + + idx->have_stream = 1; + idx->entry_type = type; + + error = git_packfile_stream_open(stream, idx->pack, idx->off); + if (error < 0) + return error; + } + + if (idx->have_delta) { + error = read_object_stream(idx, stream); + } else { + error = hash_object_stream(idx, stream); + } + + idx->off = stream->curpos; + if (error == GIT_EBUFS) + return error; + + /* We want to free the stream reasorces no matter what here */ + idx->have_stream = 0; + git_packfile_stream_dispose(stream); + + if (error < 0) + return error; + + if (idx->have_delta) { + error = store_delta(idx); + } else { + error = store_object(idx); + } + + if (error < 0) + return error; + + if (!idx->have_delta) { + stats->indexed_objects++; + } + stats->received_objects++; + + if ((error = do_progress_callback(idx, stats)) != 0) + return error; + + return 0; +} + +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; + git_mwindow_file *mwf = &idx->pack->mwf; + + GIT_ASSERT_ARG(idx); + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(stats); + + if ((error = append_to_pack(idx, data, size)) < 0) + return error; + + hash_partially(idx, data, (int)size); + + /* Make sure we set the new size of the pack */ + idx->pack->mwf.size += size; + + if (!idx->parsed_header) { + unsigned int total_objects; + + if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header)) + return 0; + + if ((error = parse_header(&idx->hdr, idx->pack)) < 0) + return error; + + idx->parsed_header = 1; + idx->nr_objects = ntohl(hdr->hdr_entries); + idx->off = sizeof(struct git_pack_header); + + if (idx->nr_objects <= git_indexer__max_objects) { + total_objects = (unsigned int)idx->nr_objects; + } else { + git_error_set(GIT_ERROR_INDEXER, "too many objects"); + return -1; + } + + 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) + return -1; + + if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0) + return -1; + + stats->received_objects = 0; + stats->local_objects = 0; + stats->total_deltas = 0; + stats->indexed_deltas = 0; + stats->indexed_objects = 0; + stats->total_objects = total_objects; + + if ((error = do_progress_callback(idx, stats)) != 0) + return error; + } + + /* Now that we have data in the pack, let's try to parse it */ + + /* As the file grows any windows we try to use will be out of date */ + if ((error = git_mwindow_free_all(mwf)) < 0) + goto on_error; + + while (stats->indexed_objects < idx->nr_objects) { + if ((error = read_stream_object(idx, stats)) != 0) { + if (error == GIT_EBUFS) + break; + else + goto on_error; + } + } + + return 0; + +on_error: + git_mwindow_free_all(mwf); + return error; +} + +static int index_path(git_str *path, git_indexer *idx, const char *suffix) +{ + const char prefix[] = "pack-"; + size_t slash = (size_t)path->size; + + /* search backwards for '/' */ + while (slash > 0 && path->ptr[slash - 1] != '/') + slash--; + + if (git_str_grow(path, slash + 1 + strlen(prefix) + + GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) + return -1; + + git_str_truncate(path, slash); + git_str_puts(path, prefix); + git_str_puts(path, idx->name); + git_str_puts(path, suffix); + + return git_str_oom(path) ? -1 : 0; +} + +/** + * Rewind the packfile by the trailer, as we might need to fix the + * packfile by injecting objects at the tail and must overwrite it. + */ +static int seek_back_trailer(git_indexer *idx) +{ + idx->pack->mwf.size -= GIT_OID_RAWSZ; + return git_mwindow_free_all(&idx->pack->mwf); +} + +static int inject_object(git_indexer *idx, git_oid *id) +{ + git_odb_object *obj = NULL; + struct entry *entry = NULL; + struct git_pack_entry *pentry = NULL; + unsigned char empty_checksum[GIT_HASH_SHA1_SIZE] = {0}; + unsigned char hdr[64]; + git_str buf = GIT_STR_INIT; + off64_t entry_start; + const void *data; + size_t len, hdr_len; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + int error; + + if ((error = seek_back_trailer(idx)) < 0) + goto cleanup; + + entry_start = idx->pack->mwf.size; + + if ((error = git_odb_read(&obj, idx->odb, id)) < 0) { + git_error_set(GIT_ERROR_INDEXER, "missing delta bases"); + goto cleanup; + } + + data = git_odb_object_data(obj); + len = git_odb_object_size(obj); + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->crc = crc32(0L, Z_NULL, 0); + + /* Write out the object header */ + if ((error = git_packfile__object_header(&hdr_len, hdr, len, git_odb_object_type(obj))) < 0 || + (error = append_to_pack(idx, hdr, hdr_len)) < 0) + goto cleanup; + + idx->pack->mwf.size += hdr_len; + entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); + + if ((error = git_zstream_deflatebuf(&buf, data, len)) < 0) + goto cleanup; + + /* And then the compressed object */ + if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) + goto cleanup; + + idx->pack->mwf.size += buf.size; + entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); + git_str_dispose(&buf); + + /* Write a fake trailer so the pack functions play ball */ + + if ((error = append_to_pack(idx, empty_checksum, checksum_size)) < 0) + goto cleanup; + + idx->pack->mwf.size += GIT_OID_RAWSZ; + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GIT_ERROR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, id); + git_oid_cpy(&entry->oid, id); + idx->off = entry_start + hdr_len + len; + + error = save_entry(idx, entry, pentry, entry_start); + +cleanup: + if (error) { + git__free(entry); + git__free(pentry); + } + + git_odb_object_free(obj); + return error; +} + +static int fix_thin_pack(git_indexer *idx, git_indexer_progress *stats) +{ + int error, found_ref_delta = 0; + unsigned int i; + struct delta_info *delta; + size_t size; + git_object_t type; + git_mwindow *w = NULL; + off64_t curpos = 0; + unsigned char *base_info; + unsigned int left = 0; + git_oid base; + + GIT_ASSERT(git_vector_length(&idx->deltas) > 0); + + if (idx->odb == NULL) { + git_error_set(GIT_ERROR_INDEXER, "cannot fix a thin pack without an ODB"); + return -1; + } + + /* Loop until we find the first REF delta */ + git_vector_foreach(&idx->deltas, i, delta) { + if (!delta) + continue; + + curpos = delta->delta_off; + error = git_packfile_unpack_header(&size, &type, idx->pack, &w, &curpos); + if (error < 0) + return error; + + if (type == GIT_OBJECT_REF_DELTA) { + found_ref_delta = 1; + break; + } + } + + if (!found_ref_delta) { + git_error_set(GIT_ERROR_INDEXER, "no REF_DELTA found, cannot inject object"); + return -1; + } + + /* curpos now points to the base information, which is an OID */ + base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left); + if (base_info == NULL) { + git_error_set(GIT_ERROR_INDEXER, "failed to map delta information"); + return -1; + } + + git_oid_fromraw(&base, base_info); + git_mwindow_close(&w); + + if (has_entry(idx, &base)) + return 0; + + if (inject_object(idx, &base) < 0) + return -1; + + stats->local_objects++; + + return 0; +} + +static int resolve_deltas(git_indexer *idx, git_indexer_progress *stats) +{ + unsigned int i; + int error; + struct delta_info *delta; + int progressed = 0, non_null = 0, progress_cb_result; + + while (idx->deltas.length > 0) { + progressed = 0; + non_null = 0; + git_vector_foreach(&idx->deltas, i, delta) { + git_rawobj obj = {0}; + + if (!delta) + continue; + + non_null = 1; + idx->off = delta->delta_off; + if ((error = git_packfile_unpack(&obj, idx->pack, &idx->off)) < 0) { + if (error == GIT_PASSTHROUGH) { + /* We have not seen the base object, we'll try again later. */ + continue; + } + return -1; + } + + if (idx->do_verify && check_object_connectivity(idx, &obj) < 0) + /* TODO: error? continue? */ + continue; + + if (hash_and_save(idx, &obj, delta->delta_off) < 0) + continue; + + git__free(obj.data); + stats->indexed_objects++; + stats->indexed_deltas++; + progressed = 1; + if ((progress_cb_result = do_progress_callback(idx, stats)) < 0) + return progress_cb_result; + + /* remove from the list */ + git_vector_set(NULL, &idx->deltas, i, NULL); + git__free(delta); + } + + /* if none were actually set, we're done */ + if (!non_null) + break; + + if (!progressed && (fix_thin_pack(idx, stats) < 0)) { + return -1; + } + } + + return 0; +} + +static int update_header_and_rehash(git_indexer *idx, git_indexer_progress *stats) +{ + void *ptr; + size_t chunk = 1024*1024; + off64_t hashed = 0; + git_mwindow *w = NULL; + git_mwindow_file *mwf; + unsigned int left; + + mwf = &idx->pack->mwf; + + git_hash_init(&idx->trailer); + + + /* Update the header to include the number of local objects we injected */ + idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); + if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) + return -1; + + /* + * We now use the same technique as before to determine the + * hash. We keep reading up to the end and let + * hash_partially() keep the existing trailer out of the + * calculation. + */ + if (git_mwindow_free_all(mwf) < 0) + return -1; + + idx->inbuf_len = 0; + while (hashed < mwf->size) { + ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); + if (ptr == NULL) + return -1; + + hash_partially(idx, ptr, left); + hashed += left; + + git_mwindow_close(&w); + } + + return 0; +} + +int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) +{ + git_mwindow *w = NULL; + unsigned int i, long_offsets = 0, left; + int error; + struct git_pack_idx_header hdr; + git_str filename = GIT_STR_INIT; + struct entry *entry; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + git_filebuf index_file = {0}; + void *packfile_trailer; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + bool mismatch; + + if (!idx->parsed_header) { + git_error_set(GIT_ERROR_INDEXER, "incomplete pack header"); + return -1; + } + + /* Test for this before resolve_deltas(), as it plays with idx->off */ + if (idx->off + (ssize_t)checksum_size < idx->pack->mwf.size) { + git_error_set(GIT_ERROR_INDEXER, "unexpected data at the end of the pack"); + return -1; + } + if (idx->off + (ssize_t)checksum_size > idx->pack->mwf.size) { + git_error_set(GIT_ERROR_INDEXER, "missing trailer at the end of the pack"); + return -1; + } + + packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - checksum_size, checksum_size, &left); + if (packfile_trailer == NULL) { + git_mwindow_close(&w); + goto on_error; + } + + /* Compare the packfile trailer as it was sent to us and what we calculated */ + git_hash_final(checksum, &idx->trailer); + mismatch = !!memcmp(checksum, packfile_trailer, checksum_size); + git_mwindow_close(&w); + + if (mismatch) { + git_error_set(GIT_ERROR_INDEXER, "packfile trailer mismatch"); + return -1; + } + + /* Freeze the number of deltas */ + stats->total_deltas = stats->total_objects - stats->indexed_objects; + + if ((error = resolve_deltas(idx, stats)) < 0) + return error; + + if (stats->indexed_objects != stats->total_objects) { + git_error_set(GIT_ERROR_INDEXER, "early EOF"); + return -1; + } + + if (stats->local_objects > 0) { + if (update_header_and_rehash(idx, stats) < 0) + return -1; + + git_hash_final(checksum, &idx->trailer); + write_at(idx, checksum, idx->pack->mwf.size - checksum_size, checksum_size); + } + + /* + * Is the resulting graph fully connected or are we still + * missing some objects? In the second case, we can + * bail out due to an incomplete and thus corrupt + * packfile. + */ + if (git_oidmap_size(idx->expected_oids) > 0) { + git_error_set(GIT_ERROR_INDEXER, "packfile is missing %"PRIuZ" objects", + git_oidmap_size(idx->expected_oids)); + return -1; + } + + git_vector_sort(&idx->objects); + + /* Use the trailer hash as the pack file name to ensure + * files with different contents have different names */ + memcpy(idx->checksum, checksum, checksum_size); + if (git_hash_fmt(idx->name, checksum, checksum_size) < 0) + return -1; + + git_str_sets(&filename, idx->pack->pack_name); + git_str_shorten(&filename, strlen("pack")); + git_str_puts(&filename, "idx"); + if (git_str_oom(&filename)) + return -1; + + if (git_filebuf_open(&index_file, filename.ptr, + GIT_FILEBUF_HASH_CONTENTS | + (idx->do_fsync ? GIT_FILEBUF_FSYNC : 0), + idx->mode) < 0) + goto on_error; + + /* Write out the header */ + hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); + hdr.idx_version = htonl(2); + git_filebuf_write(&index_file, &hdr, sizeof(hdr)); + + /* Write out the fanout table */ + for (i = 0; i < 256; ++i) { + uint32_t n = htonl(idx->fanout[i]); + git_filebuf_write(&index_file, &n, sizeof(n)); + } + + /* Write out the object names (SHA-1 hashes) */ + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&index_file, &entry->oid.id, GIT_OID_RAWSZ); + } + + /* Write out the CRC32 values */ + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t)); + } + + /* Write out the offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t n; + + if (entry->offset == UINT32_MAX) + n = htonl(0x80000000 | long_offsets++); + else + n = htonl(entry->offset); + + git_filebuf_write(&index_file, &n, sizeof(uint32_t)); + } + + /* Write out the long offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t split[2]; + + if (entry->offset != UINT32_MAX) + continue; + + split[0] = htonl(entry->offset_long >> 32); + split[1] = htonl(entry->offset_long & 0xffffffff); + + git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2); + } + + /* Write out the packfile trailer to the index */ + if (git_filebuf_write(&index_file, checksum, checksum_size) < 0) + goto on_error; + + /* Write out the hash of the idx */ + if (git_filebuf_hash(checksum, &index_file) < 0) + goto on_error; + + git_filebuf_write(&index_file, checksum, checksum_size); + + /* Figure out what the final name should be */ + if (index_path(&filename, idx, ".idx") < 0) + goto on_error; + + /* Commit file */ + if (git_filebuf_commit_at(&index_file, filename.ptr) < 0) + goto on_error; + + if (git_mwindow_free_all(&idx->pack->mwf) < 0) + goto on_error; + +#if !defined(NO_MMAP) && defined(GIT_WIN32) + /* + * Some non-Windows remote filesystems fail when truncating files if the + * file permissions change after opening the file (done by p_mkstemp). + * + * Truncation is only needed when mmap is used to undo rounding up to next + * page_size in append_to_pack. + */ + if (p_ftruncate(idx->pack->mwf.fd, idx->pack->mwf.size) < 0) { + git_error_set(GIT_ERROR_OS, "failed to truncate pack file '%s'", idx->pack->pack_name); + return -1; + } +#endif + + if (idx->do_fsync && p_fsync(idx->pack->mwf.fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to fsync packfile"); + goto on_error; + } + + /* We need to close the descriptor here so Windows doesn't choke on commit_at */ + if (p_close(idx->pack->mwf.fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to close packfile"); + goto on_error; + } + + idx->pack->mwf.fd = -1; + + if (index_path(&filename, idx, ".pack") < 0) + goto on_error; + + /* And don't forget to rename the packfile to its new place. */ + if (p_rename(idx->pack->pack_name, git_str_cstr(&filename)) < 0) + goto on_error; + + /* And fsync the parent directory if we're asked to. */ + if (idx->do_fsync && + git_futils_fsync_parent(git_str_cstr(&filename)) < 0) + goto on_error; + + idx->pack_committed = 1; + + git_str_dispose(&filename); + return 0; + +on_error: + git_mwindow_free_all(&idx->pack->mwf); + git_filebuf_cleanup(&index_file); + git_str_dispose(&filename); + return -1; +} + +void git_indexer_free(git_indexer *idx) +{ + const git_oid *key; + git_oid *value; + size_t iter; + + if (idx == NULL) + return; + + if (idx->have_stream) + git_packfile_stream_dispose(&idx->stream); + + git_vector_free_deep(&idx->objects); + + if (idx->pack->idx_cache) { + struct git_pack_entry *pentry; + git_oidmap_foreach_value(idx->pack->idx_cache, pentry, { + git__free(pentry); + }); + + git_oidmap_free(idx->pack->idx_cache); + } + + git_vector_free_deep(&idx->deltas); + + git_packfile_free(idx->pack, !idx->pack_committed); + + 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); + git_str_dispose(&idx->entry_data); + git_oidmap_free(idx->expected_oids); + git__free(idx); +} diff --git a/src/libgit2/indexer.h b/src/libgit2/indexer.h new file mode 100644 index 000000000..8ee6115a6 --- /dev/null +++ b/src/libgit2/indexer.h @@ -0,0 +1,16 @@ +/* + * 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_indexer_h__ +#define INCLUDE_indexer_h__ + +#include "common.h" + +#include "git2/indexer.h" + +extern void git_indexer__set_fsync(git_indexer *idx, int do_fsync); + +#endif diff --git a/src/libgit2/iterator.c b/src/libgit2/iterator.c new file mode 100644 index 000000000..bc6f766ce --- /dev/null +++ b/src/libgit2/iterator.c @@ -0,0 +1,2439 @@ +/* + * 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 "iterator.h" + +#include "tree.h" +#include "index.h" +#include "path.h" + +#define GIT_ITERATOR_FIRST_ACCESS (1 << 15) +#define GIT_ITERATOR_HONOR_IGNORES (1 << 16) +#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17) + +#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) +#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) +#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) +#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS) +#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) +#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) +#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) +#define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) + + +static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) +{ + if (ignore_case) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; + + iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp; + iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp; + iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; + iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; + + git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); +} + +static int iterator_range_init( + git_iterator *iter, const char *start, const char *end) +{ + if (start && *start) { + iter->start = git__strdup(start); + GIT_ERROR_CHECK_ALLOC(iter->start); + + iter->start_len = strlen(iter->start); + } + + if (end && *end) { + iter->end = git__strdup(end); + GIT_ERROR_CHECK_ALLOC(iter->end); + + iter->end_len = strlen(iter->end); + } + + iter->started = (iter->start == NULL); + iter->ended = false; + + return 0; +} + +static void iterator_range_free(git_iterator *iter) +{ + if (iter->start) { + git__free(iter->start); + iter->start = NULL; + iter->start_len = 0; + } + + if (iter->end) { + git__free(iter->end); + iter->end = NULL; + iter->end_len = 0; + } +} + +static int iterator_reset_range( + git_iterator *iter, const char *start, const char *end) +{ + iterator_range_free(iter); + return iterator_range_init(iter, start, end); +} + +static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist) +{ + size_t i; + + if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0) + return -1; + + for (i = 0; i < pathlist->count; i++) { + if (!pathlist->strings[i]) + continue; + + if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0) + return -1; + } + + return 0; +} + +static int iterator_init_common( + git_iterator *iter, + git_repository *repo, + git_index *index, + git_iterator_options *given_opts) +{ + static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator_options *options = given_opts ? given_opts : &default_opts; + bool ignore_case; + int precompose; + int error; + + iter->repo = repo; + iter->index = index; + iter->flags = options->flags; + + if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) { + ignore_case = true; + } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) { + ignore_case = false; + } else if (repo) { + git_index *index; + + if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) + return error; + + ignore_case = !!index->ignore_case; + + if (ignore_case == 1) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + } else { + ignore_case = false; + } + + /* try to look up precompose and set flag if appropriate */ + if (repo && + (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && + (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { + + if (git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) < 0) + git_error_clear(); + else if (precompose) + iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + } + + if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND)) + iter->flags |= GIT_ITERATOR_INCLUDE_TREES; + + if ((error = iterator_range_init(iter, options->start, options->end)) < 0 || + (error = iterator_pathlist_init(iter, &options->pathlist)) < 0) + return error; + + iterator_set_ignore_case(iter, ignore_case); + return 0; +} + +static void iterator_clear(git_iterator *iter) +{ + iter->started = false; + iter->ended = false; + iter->stat_calls = 0; + iter->pathlist_walk_idx = 0; + iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; +} + +GIT_INLINE(bool) iterator_has_started( + git_iterator *iter, const char *path, bool is_submodule) +{ + size_t path_len; + + if (iter->start == NULL || iter->started == true) + return true; + + /* the starting path is generally a prefix - we have started once we + * are prefixed by this path + */ + iter->started = (iter->prefixcomp(path, iter->start) >= 0); + + if (iter->started) + return true; + + path_len = strlen(path); + + /* if, however, we are a submodule, then we support `start` being + * suffixed with a `/` for crazy legacy reasons. match `submod` + * with a start path of `submod/`. + */ + if (is_submodule && iter->start_len && path_len == iter->start_len - 1 && + iter->start[iter->start_len-1] == '/') + return true; + + /* if, however, our current path is a directory, and our starting path + * is _beneath_ that directory, then recurse into the directory (even + * though we have not yet "started") + */ + if (path_len > 0 && path[path_len-1] == '/' && + iter->strncomp(path, iter->start, path_len) == 0) + return true; + + return false; +} + +GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path) +{ + if (iter->end == NULL) + return false; + else if (iter->ended) + return true; + + iter->ended = (iter->prefixcomp(path, iter->end) > 0); + return iter->ended; +} + +/* walker for the index and tree iterator that allows it to walk the sorted + * pathlist entries alongside sorted iterator entries. + */ +static bool iterator_pathlist_next_is(git_iterator *iter, const char *path) +{ + char *p; + size_t path_len, p_len, cmp_len, i; + int cmp; + + if (iter->pathlist.length == 0) + return true; + + git_vector_sort(&iter->pathlist); + + path_len = strlen(path); + + /* for comparison, drop the trailing slash on the current '/' */ + if (path_len && path[path_len-1] == '/') + path_len--; + + for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { + p = iter->pathlist.contents[i]; + p_len = strlen(p); + + if (p_len && p[p_len-1] == '/') + p_len--; + + cmp_len = min(path_len, p_len); + + /* see if the pathlist entry is a prefix of this path */ + cmp = iter->strncomp(p, path, cmp_len); + + /* prefix match - see if there's an exact match, or if we were + * given a path that matches the directory + */ + if (cmp == 0) { + /* if this pathlist entry is not suffixed with a '/' then + * it matches a path that is a file or a directory. + * (eg, pathlist = "foo" and path is "foo" or "foo/" or + * "foo/something") + */ + if (p[cmp_len] == '\0' && + (path[cmp_len] == '\0' || path[cmp_len] == '/')) + return true; + + /* if this pathlist entry _is_ suffixed with a '/' then + * it matches only paths that are directories. + * (eg, pathlist = "foo/" and path is "foo/" or "foo/something") + */ + if (p[cmp_len] == '/' && path[cmp_len] == '/') + return true; + } + + /* this pathlist entry sorts before the given path, try the next */ + else if (cmp < 0) { + iter->pathlist_walk_idx++; + continue; + } + + /* this pathlist sorts after the given path, no match. */ + else if (cmp > 0) { + break; + } + } + + return false; +} + +typedef enum { + ITERATOR_PATHLIST_NONE = 0, + ITERATOR_PATHLIST_IS_FILE = 1, + ITERATOR_PATHLIST_IS_DIR = 2, + ITERATOR_PATHLIST_IS_PARENT = 3, + ITERATOR_PATHLIST_FULL = 4 +} iterator_pathlist_search_t; + +static iterator_pathlist_search_t iterator_pathlist_search( + git_iterator *iter, const char *path, size_t path_len) +{ + const char *p; + size_t idx; + int error; + + if (iter->pathlist.length == 0) + return ITERATOR_PATHLIST_FULL; + + git_vector_sort(&iter->pathlist); + + error = git_vector_bsearch2(&idx, &iter->pathlist, + (git_vector_cmp)iter->strcomp, path); + + /* the given path was found in the pathlist. since the pathlist only + * matches directories when they're suffixed with a '/', analyze the + * path string to determine whether it's a directory or not. + */ + if (error == 0) { + if (path_len && path[path_len-1] == '/') + return ITERATOR_PATHLIST_IS_DIR; + + return ITERATOR_PATHLIST_IS_FILE; + } + + /* at this point, the path we're examining may be a directory (though we + * don't know that yet, since we're avoiding a stat unless it's necessary) + * so walk the pathlist looking for the given path with a '/' after it, + */ + while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { + if (iter->prefixcomp(p, path) != 0) + break; + + /* an exact match would have been matched by the bsearch above */ + GIT_ASSERT_WITH_RETVAL(p[path_len], ITERATOR_PATHLIST_NONE); + + /* is this a literal directory entry (eg `foo/`) or a file beneath */ + if (p[path_len] == '/') { + return (p[path_len+1] == '\0') ? + ITERATOR_PATHLIST_IS_DIR : + ITERATOR_PATHLIST_IS_PARENT; + } + + if (p[path_len] > '/') + break; + + idx++; + } + + return ITERATOR_PATHLIST_NONE; +} + +/* Empty iterator */ + +static int empty_iterator_noop(const git_index_entry **e, git_iterator *i) +{ + GIT_UNUSED(i); + + if (e) + *e = NULL; + + return GIT_ITEROVER; +} + +static int empty_iterator_advance_over( + const git_index_entry **e, + git_iterator_status_t *s, + git_iterator *i) +{ + *s = GIT_ITERATOR_STATUS_EMPTY; + return empty_iterator_noop(e, i); +} + +static int empty_iterator_reset(git_iterator *i) +{ + GIT_UNUSED(i); + return 0; +} + +static void empty_iterator_free(git_iterator *i) +{ + GIT_UNUSED(i); +} + +typedef struct { + git_iterator base; + git_iterator_callbacks cb; +} empty_iterator; + +int git_iterator_for_nothing( + git_iterator **out, + git_iterator_options *options) +{ + empty_iterator *iter; + + static git_iterator_callbacks callbacks = { + empty_iterator_noop, + empty_iterator_noop, + empty_iterator_noop, + empty_iterator_advance_over, + empty_iterator_reset, + empty_iterator_free + }; + + *out = NULL; + + iter = git__calloc(1, sizeof(empty_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_EMPTY; + iter->base.cb = &callbacks; + iter->base.flags = options->flags; + + *out = &iter->base; + return 0; +} + +/* Tree iterator */ + +typedef struct { + git_tree_entry *tree_entry; + const char *parent_path; +} tree_iterator_entry; + +typedef struct { + git_tree *tree; + + /* path to this particular frame (folder) */ + git_str path; + + /* a sorted list of the entries for this frame (folder), these are + * actually pointers to the iterator's entry pool. + */ + git_vector entries; + tree_iterator_entry *current; + + size_t next_idx; + + /* on case insensitive iterations, we also have an array of other + * paths that were case insensitively equal to this one, and their + * tree objects. we have coalesced the tree entries into this frame. + * a child `tree_iterator_entry` will contain a pointer to its actual + * parent path. + */ + git_vector similar_trees; + git_array_t(git_str) similar_paths; +} tree_iterator_frame; + +typedef struct { + git_iterator base; + git_tree *root; + git_array_t(tree_iterator_frame) frames; + + git_index_entry entry; + git_str entry_path; + + /* a pool of entries to reduce the number of allocations */ + git_pool entry_pool; +} tree_iterator; + +GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame( + tree_iterator *iter) +{ + return iter->frames.size > 1 ? + &iter->frames.ptr[iter->frames.size-2] : NULL; +} + +GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame( + tree_iterator *iter) +{ + return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; +} + +GIT_INLINE(int) tree_entry_cmp( + const git_tree_entry *a, const git_tree_entry *b, bool icase) +{ + return git_fs_path_cmp( + a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, + b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, + icase ? git__strncasecmp : git__strncmp); +} + +GIT_INLINE(int) tree_iterator_entry_cmp_icase( + const void *ptr_a, const void *ptr_b) +{ + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; + + return tree_entry_cmp(a->tree_entry, b->tree_entry, true); +} + +static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b) +{ + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; + + int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true); + + /* stabilize the sort order for filenames that are (case insensitively) + * the same by examining the parent path (case sensitively) before + * falling back to a case sensitive sort of the filename. + */ + if (!c && a->parent_path != b->parent_path) + c = git__strcmp(a->parent_path, b->parent_path); + + if (!c) + c = tree_entry_cmp(a->tree_entry, b->tree_entry, false); + + return c; +} + +static int tree_iterator_compute_path( + git_str *out, + tree_iterator_entry *entry) +{ + git_str_clear(out); + + if (entry->parent_path) + git_str_joinpath(out, entry->parent_path, entry->tree_entry->filename); + else + git_str_puts(out, entry->tree_entry->filename); + + if (git_tree_entry__is_tree(entry->tree_entry)) + git_str_putc(out, '/'); + + if (git_str_oom(out)) + return -1; + + return 0; +} + +static int tree_iterator_frame_init( + tree_iterator *iter, + git_tree *tree, + tree_iterator_entry *frame_entry) +{ + tree_iterator_frame *new_frame = NULL; + tree_iterator_entry *new_entry; + git_tree *dup = NULL; + git_tree_entry *tree_entry; + git_vector_cmp cmp; + size_t i; + int error = 0; + + new_frame = git_array_alloc(iter->frames); + GIT_ERROR_CHECK_ALLOC(new_frame); + + if ((error = git_tree_dup(&dup, tree)) < 0) + goto done; + + memset(new_frame, 0x0, sizeof(tree_iterator_frame)); + new_frame->tree = dup; + + if (frame_entry && + (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) + goto done; + + git_array_foreach(dup->entries, i, tree_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; + + if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0) + goto done; + } + + git_vector_set_sorted(&new_frame->entries, + !iterator__ignore_case(&iter->base)); + +done: + if (error < 0) { + git_tree_free(dup); + git_array_pop(iter->frames); + } + + return error; +} + +GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry( + tree_iterator_frame *frame) +{ + return frame->current; +} + +GIT_INLINE(int) tree_iterator_frame_push_neighbors( + tree_iterator *iter, + tree_iterator_frame *parent_frame, + tree_iterator_frame *frame, + const char *filename) +{ + tree_iterator_entry *entry, *new_entry; + git_tree *tree = NULL; + git_tree_entry *tree_entry; + git_str *path; + size_t new_size, i; + int error = 0; + + while (parent_frame->next_idx < parent_frame->entries.length) { + entry = parent_frame->entries.contents[parent_frame->next_idx]; + + if (strcasecmp(filename, entry->tree_entry->filename) != 0) + break; + + if ((error = git_tree_lookup(&tree, + iter->base.repo, &entry->tree_entry->oid)) < 0) + break; + + if (git_vector_insert(&parent_frame->similar_trees, tree) < 0) + break; + + path = git_array_alloc(parent_frame->similar_paths); + GIT_ERROR_CHECK_ALLOC(path); + + memset(path, 0, sizeof(git_str)); + + if ((error = tree_iterator_compute_path(path, entry)) < 0) + break; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, + frame->entries.length, tree->entries.size); + git_vector_size_hint(&frame->entries, new_size); + + git_array_foreach(tree->entries, i, tree_entry) { + new_entry = git_pool_malloc(&iter->entry_pool, 1); + GIT_ERROR_CHECK_ALLOC(new_entry); + + new_entry->tree_entry = tree_entry; + new_entry->parent_path = path->ptr; + + if ((error = git_vector_insert(&frame->entries, new_entry)) < 0) + break; + } + + if (error) + break; + + parent_frame->next_idx++; + } + + return error; +} + +GIT_INLINE(int) tree_iterator_frame_push( + tree_iterator *iter, tree_iterator_entry *entry) +{ + tree_iterator_frame *parent_frame, *frame; + git_tree *tree = NULL; + int error; + + if ((error = git_tree_lookup(&tree, + iter->base.repo, &entry->tree_entry->oid)) < 0 || + (error = tree_iterator_frame_init(iter, tree, entry)) < 0) + goto done; + + parent_frame = tree_iterator_parent_frame(iter); + frame = tree_iterator_current_frame(iter); + + /* if we're case insensitive, then we may have another directory that + * is (case insensitively) equal to this one. coalesce those children + * into this tree. + */ + if (iterator__ignore_case(&iter->base)) + error = tree_iterator_frame_push_neighbors(iter, + parent_frame, frame, entry->tree_entry->filename); + +done: + git_tree_free(tree); + return error; +} + +static int tree_iterator_frame_pop(tree_iterator *iter) +{ + tree_iterator_frame *frame; + git_str *buf = NULL; + git_tree *tree; + size_t i; + + GIT_ASSERT(iter->frames.size); + + frame = git_array_pop(iter->frames); + + git_vector_free(&frame->entries); + git_tree_free(frame->tree); + + do { + buf = git_array_pop(frame->similar_paths); + git_str_dispose(buf); + } while (buf != NULL); + + git_array_clear(frame->similar_paths); + + git_vector_foreach(&frame->similar_trees, i, tree) + git_tree_free(tree); + + git_vector_free(&frame->similar_trees); + + git_str_dispose(&frame->path); + + return 0; +} + +static int tree_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (!iter->frames.size) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = &iter->entry; + return 0; +} + +static void tree_iterator_set_current( + tree_iterator *iter, + tree_iterator_frame *frame, + tree_iterator_entry *entry) +{ + git_tree_entry *tree_entry = entry->tree_entry; + + frame->current = entry; + + memset(&iter->entry, 0x0, sizeof(git_index_entry)); + + iter->entry.mode = tree_entry->attr; + iter->entry.path = iter->entry_path.ptr; + git_oid_cpy(&iter->entry.id, &tree_entry->oid); +} + +static int tree_iterator_advance(const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + /* examine tree entries until we find the next one to return */ + while (true) { + tree_iterator_entry *prev_entry, *entry; + tree_iterator_frame *frame; + bool is_tree; + + if ((frame = tree_iterator_current_frame(iter)) == NULL) { + error = GIT_ITEROVER; + break; + } + + /* no more entries in this frame. pop the frame out */ + if (frame->next_idx == frame->entries.length) { + if ((error = tree_iterator_frame_pop(iter)) < 0) + break; + + continue; + } + + /* we may have coalesced the contents of case-insensitively same-named + * directories, so do the sort now. + */ + if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries)) + git_vector_sort(&frame->entries); + + /* we have more entries in the current frame, that's our next entry */ + prev_entry = tree_iterator_current_entry(frame); + entry = frame->entries.contents[frame->next_idx]; + frame->next_idx++; + + /* we can have collisions when iterating case insensitively. (eg, + * 'A/a' and 'a/A'). squash this one if it's already been seen. + */ + if (iterator__ignore_case(&iter->base) && + prev_entry && + tree_iterator_entry_cmp_icase(prev_entry, entry) == 0) + continue; + + if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0) + break; + + /* if this path is before our start, advance over this entry */ + if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false)) + continue; + + /* if this path is after our end, stop */ + if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) { + error = GIT_ITEROVER; + break; + } + + /* if we have a list of paths we're interested in, examine it */ + if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr)) + continue; + + is_tree = git_tree_entry__is_tree(entry->tree_entry); + + /* if we are *not* including trees then advance over this entry */ + if (is_tree && !iterator__include_trees(iter)) { + + /* if we've found a tree (and are not returning it to the caller) + * and we are autoexpanding, then we want to return the first + * child. push the new directory and advance. + */ + if (iterator__do_autoexpand(iter)) { + if ((error = tree_iterator_frame_push(iter, entry)) < 0) + break; + } + + continue; + } + + tree_iterator_set_current(iter, frame, entry); + + /* if we are autoexpanding, then push this as a new frame, so that + * the next call to `advance` will dive into this directory. + */ + if (is_tree && iterator__do_autoexpand(iter)) + error = tree_iterator_frame_push(iter, entry); + + break; + } + + if (out) + *out = (error == 0) ? &iter->entry : NULL; + + return error; +} + +static int tree_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + tree_iterator_frame *frame; + tree_iterator_entry *prev_entry; + int error; + + if (out) + *out = NULL; + + if ((frame = tree_iterator_current_frame(iter)) == NULL) + return GIT_ITEROVER; + + /* get the last seen entry */ + prev_entry = tree_iterator_current_entry(frame); + + /* it's legal to call advance_into when auto-expand is on. in this case, + * we will have pushed a new (empty) frame on to the stack for this + * new directory. since it's empty, its current_entry should be null. + */ + GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); + + if (prev_entry) { + if (!git_tree_entry__is_tree(prev_entry->tree_entry)) + return 0; + + if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0) + return error; + } + + /* we've advanced into the directory in question, let advance + * find the first entry + */ + return tree_iterator_advance(out, i); +} + +static int tree_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + *status = GIT_ITERATOR_STATUS_NORMAL; + return git_iterator_advance(out, i); +} + +static void tree_iterator_clear(tree_iterator *iter) +{ + while (iter->frames.size) + tree_iterator_frame_pop(iter); + + git_array_clear(iter->frames); + + git_pool_clear(&iter->entry_pool); + git_str_clear(&iter->entry_path); + + iterator_clear(&iter->base); +} + +static int tree_iterator_init(tree_iterator *iter) +{ + int error; + + if ((error = git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry))) < 0 || + (error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0) + return error; + + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + + return 0; +} + +static int tree_iterator_reset(git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + tree_iterator_clear(iter); + return tree_iterator_init(iter); +} + +static void tree_iterator_free(git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + tree_iterator_clear(iter); + + git_tree_free(iter->root); + git_str_dispose(&iter->entry_path); +} + +int git_iterator_for_tree( + git_iterator **out, + git_tree *tree, + git_iterator_options *options) +{ + tree_iterator *iter; + int error; + + static git_iterator_callbacks callbacks = { + tree_iterator_current, + tree_iterator_advance, + tree_iterator_advance_into, + tree_iterator_advance_over, + tree_iterator_reset, + tree_iterator_free + }; + + *out = NULL; + + if (tree == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(tree_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_TREE; + iter->base.cb = &callbacks; + + if ((error = iterator_init_common(&iter->base, + git_tree_owner(tree), NULL, options)) < 0 || + (error = git_tree_dup(&iter->root, tree)) < 0 || + (error = tree_iterator_init(iter)) < 0) + goto on_error; + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} + +int git_iterator_current_tree_entry( + const git_tree_entry **tree_entry, git_iterator *i) +{ + tree_iterator *iter; + tree_iterator_frame *frame; + tree_iterator_entry *entry; + + GIT_ASSERT(i->type == GIT_ITERATOR_TREE); + + iter = (tree_iterator *)i; + + frame = tree_iterator_current_frame(iter); + entry = tree_iterator_current_entry(frame); + + *tree_entry = entry->tree_entry; + return 0; +} + +int git_iterator_current_parent_tree( + const git_tree **parent_tree, git_iterator *i, size_t depth) +{ + tree_iterator *iter; + tree_iterator_frame *frame; + + GIT_ASSERT(i->type == GIT_ITERATOR_TREE); + + iter = (tree_iterator *)i; + + GIT_ASSERT(depth < iter->frames.size); + frame = &iter->frames.ptr[iter->frames.size-depth-1]; + + *parent_tree = frame->tree; + return 0; +} + +/* Filesystem iterator */ + +typedef struct { + struct stat st; + size_t path_len; + iterator_pathlist_search_t match; + git_oid id; + char path[GIT_FLEX_ARRAY]; +} filesystem_iterator_entry; + +typedef struct { + git_vector entries; + git_pool entry_pool; + size_t next_idx; + + size_t path_len; + int is_ignored; +} filesystem_iterator_frame; + +typedef struct { + git_iterator base; + char *root; + size_t root_len; + + unsigned int dirload_flags; + + git_tree *tree; + git_index *index; + git_vector index_snapshot; + + git_array_t(filesystem_iterator_frame) frames; + git_ignores ignores; + + /* info about the current entry */ + git_index_entry entry; + git_str current_path; + int current_is_ignored; + + /* temporary buffer for advance_over */ + git_str tmp_buf; +} filesystem_iterator; + + +GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame( + filesystem_iterator *iter) +{ + return iter->frames.size > 1 ? + &iter->frames.ptr[iter->frames.size-2] : NULL; +} + +GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame( + filesystem_iterator *iter) +{ + return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; +} + +GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry( + filesystem_iterator_frame *frame) +{ + return frame->next_idx == 0 ? + NULL : frame->entries.contents[frame->next_idx-1]; +} + +static int filesystem_iterator_entry_cmp(const void *_a, const void *_b) +{ + const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; + const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; + + return git__strcmp(a->path, b->path); +} + +static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b) +{ + const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; + const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; + + return git__strcasecmp(a->path, b->path); +} + +#define FILESYSTEM_MAX_DEPTH 100 + +/** + * Figure out if an entry is a submodule. + * + * We consider it a submodule if the path is listed as a submodule in + * either the tree or the index. + */ +static int filesystem_iterator_is_submodule( + bool *out, filesystem_iterator *iter, const char *path, size_t path_len) +{ + bool is_submodule = false; + int error; + + *out = false; + + /* first see if this path is a submodule in HEAD */ + if (iter->tree) { + git_tree_entry *entry; + + error = git_tree_entry_bypath(&entry, iter->tree, path); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + is_submodule = (entry->attr == GIT_FILEMODE_COMMIT); + git_tree_entry_free(entry); + } + } + + if (!is_submodule && iter->base.index) { + size_t pos; + + error = git_index_snapshot_find(&pos, + &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + git_index_entry *e = git_vector_get(&iter->index_snapshot, pos); + is_submodule = (e->mode == GIT_FILEMODE_COMMIT); + } + } + + *out = is_submodule; + return 0; +} + +static void filesystem_iterator_frame_push_ignores( + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry, + filesystem_iterator_frame *new_frame) +{ + filesystem_iterator_frame *previous_frame; + const char *path = frame_entry ? frame_entry->path : ""; + + if (!iterator__honor_ignores(&iter->base)) + return; + + if (git_ignore__lookup(&new_frame->is_ignored, + &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) { + git_error_clear(); + new_frame->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* if this is not the top level directory... */ + if (frame_entry) { + const char *relative_path; + + previous_frame = filesystem_iterator_parent_frame(iter); + + /* push new ignores for files in this directory */ + relative_path = frame_entry->path + previous_frame->path_len; + + /* inherit ignored from parent if no rule specified */ + if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND) + new_frame->is_ignored = previous_frame->is_ignored; + + git_ignore__push_dir(&iter->ignores, relative_path); + } +} + +static void filesystem_iterator_frame_pop_ignores( + filesystem_iterator *iter) +{ + if (iterator__honor_ignores(&iter->base)) + git_ignore__pop_dir(&iter->ignores); +} + +GIT_INLINE(bool) filesystem_iterator_examine_path( + bool *is_dir_out, + iterator_pathlist_search_t *match_out, + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry, + const char *path, + size_t path_len) +{ + bool is_dir = 0; + iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL; + + *is_dir_out = false; + *match_out = ITERATOR_PATHLIST_NONE; + + if (iter->base.start_len) { + int cmp = iter->base.strncomp(path, iter->base.start, path_len); + + /* we haven't stat'ed `path` yet, so we don't yet know if it's a + * directory or not. special case if the current path may be a + * directory that matches the start prefix. + */ + if (cmp == 0) { + if (iter->base.start[path_len] == '/') + is_dir = true; + + else if (iter->base.start[path_len] != '\0') + cmp = -1; + } + + if (cmp < 0) + return false; + } + + if (iter->base.end_len) { + int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len); + + if (cmp > 0) + return false; + } + + /* if we have a pathlist that we're limiting to, examine this path now + * to avoid a `stat` if we're not interested in the path. + */ + if (iter->base.pathlist.length) { + /* if our parent was explicitly included, so too are we */ + if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT) + match = ITERATOR_PATHLIST_FULL; + else + match = iterator_pathlist_search(&iter->base, path, path_len); + + if (match == ITERATOR_PATHLIST_NONE) + return false; + + /* Ensure that the pathlist entry lines up with what we expected */ + if (match == ITERATOR_PATHLIST_IS_DIR || + match == ITERATOR_PATHLIST_IS_PARENT) + is_dir = true; + } + + *is_dir_out = is_dir; + *match_out = match; + return true; +} + +GIT_INLINE(bool) filesystem_iterator_is_dot_git( + filesystem_iterator *iter, const char *path, size_t path_len) +{ + size_t len; + + if (!iterator__ignore_dot_git(&iter->base)) + return false; + + if ((len = path_len) < 4) + return false; + + if (path[len - 1] == '/') + len--; + + if (git__tolower(path[len - 1]) != 't' || + git__tolower(path[len - 2]) != 'i' || + git__tolower(path[len - 3]) != 'g' || + git__tolower(path[len - 4]) != '.') + return false; + + return (len == 4 || path[len - 5] == '/'); +} + +static int filesystem_iterator_entry_hash( + filesystem_iterator *iter, + filesystem_iterator_entry *entry) +{ + git_str fullpath = GIT_STR_INIT; + int error; + + if (S_ISDIR(entry->st.st_mode)) { + memset(&entry->id, 0, GIT_OID_RAWSZ); + return 0; + } + + if (iter->base.type == GIT_ITERATOR_WORKDIR) + return git_repository_hashfile(&entry->id, + iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL); + + if (!(error = git_str_joinpath(&fullpath, iter->root, entry->path)) && + !(error = git_path_validate_str_length(iter->base.repo, &fullpath))) + error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJECT_BLOB); + + git_str_dispose(&fullpath); + return error; +} + +static int filesystem_iterator_entry_init( + filesystem_iterator_entry **out, + filesystem_iterator *iter, + filesystem_iterator_frame *frame, + const char *path, + size_t path_len, + struct stat *statbuf, + iterator_pathlist_search_t pathlist_match) +{ + filesystem_iterator_entry *entry; + size_t entry_size; + int error = 0; + + *out = NULL; + + /* Make sure to append two bytes, one for the path's null + * termination, one for a possible trailing '/' for folders. + */ + GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, + sizeof(filesystem_iterator_entry), path_len); + GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, entry_size, 2); + + entry = git_pool_malloc(&frame->entry_pool, entry_size); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->path_len = path_len; + entry->match = pathlist_match; + memcpy(entry->path, path, path_len); + memcpy(&entry->st, statbuf, sizeof(struct stat)); + + /* Suffix directory paths with a '/' */ + if (S_ISDIR(entry->st.st_mode)) + entry->path[entry->path_len++] = '/'; + + entry->path[entry->path_len] = '\0'; + + if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) + error = filesystem_iterator_entry_hash(iter, entry); + + if (!error) + *out = entry; + + return error; +} + +static int filesystem_iterator_frame_push( + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry) +{ + filesystem_iterator_frame *new_frame = NULL; + git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; + git_str root = GIT_STR_INIT; + const char *path; + filesystem_iterator_entry *entry; + struct stat statbuf; + size_t path_len; + int error; + + if (iter->frames.size == FILESYSTEM_MAX_DEPTH) { + git_error_set(GIT_ERROR_REPOSITORY, + "directory nesting too deep (%"PRIuZ")", iter->frames.size); + return -1; + } + + new_frame = git_array_alloc(iter->frames); + GIT_ERROR_CHECK_ALLOC(new_frame); + + memset(new_frame, 0, sizeof(filesystem_iterator_frame)); + + if (frame_entry) + git_str_joinpath(&root, iter->root, frame_entry->path); + else + git_str_puts(&root, iter->root); + + if (git_str_oom(&root) || + git_path_validate_str_length(iter->base.repo, &root) < 0) { + error = -1; + goto done; + } + + new_frame->path_len = frame_entry ? frame_entry->path_len : 0; + + /* Any error here is equivalent to the dir not existing, skip over it */ + if ((error = git_fs_path_diriter_init( + &diriter, root.ptr, iter->dirload_flags)) < 0) { + error = GIT_ENOTFOUND; + goto done; + } + + if ((error = git_vector_init(&new_frame->entries, 64, + iterator__ignore_case(&iter->base) ? + filesystem_iterator_entry_cmp_icase : + filesystem_iterator_entry_cmp)) < 0) + goto done; + + if ((error = git_pool_init(&new_frame->entry_pool, 1)) < 0) + goto done; + + /* check if this directory is ignored */ + filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame); + + while ((error = git_fs_path_diriter_next(&diriter)) == 0) { + iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL; + git_str path_str = GIT_STR_INIT; + bool dir_expected = false; + + if ((error = git_fs_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) + goto done; + + path_str.ptr = (char *)path; + path_str.size = path_len; + + if ((error = git_path_validate_str_length(iter->base.repo, &path_str)) < 0) + goto done; + + GIT_ASSERT(path_len > iter->root_len); + + /* remove the prefix if requested */ + path += iter->root_len; + path_len -= iter->root_len; + + /* examine start / end and the pathlist to see if this path is in it. + * note that since we haven't yet stat'ed the path, we cannot know + * whether it's a directory yet or not, so this can give us an + * expected type (S_IFDIR or S_IFREG) that we should examine) + */ + if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match, + iter, frame_entry, path, path_len)) + continue; + + /* TODO: don't need to stat if assume unchanged for this path and + * we have an index, we can just copy the data out of it. + */ + + if ((error = git_fs_path_diriter_stat(&statbuf, &diriter)) < 0) { + /* file was removed between readdir and lstat */ + if (error == GIT_ENOTFOUND) + continue; + + /* treat the file as unreadable */ + memset(&statbuf, 0, sizeof(statbuf)); + statbuf.st_mode = GIT_FILEMODE_UNREADABLE; + + error = 0; + } + + iter->base.stat_calls++; + + /* Ignore wacky things in the filesystem */ + if (!S_ISDIR(statbuf.st_mode) && + !S_ISREG(statbuf.st_mode) && + !S_ISLNK(statbuf.st_mode) && + statbuf.st_mode != GIT_FILEMODE_UNREADABLE) + continue; + + if (filesystem_iterator_is_dot_git(iter, path, path_len)) + continue; + + /* convert submodules to GITLINK and remove trailing slashes */ + if (S_ISDIR(statbuf.st_mode)) { + bool submodule = false; + + if ((error = filesystem_iterator_is_submodule(&submodule, + iter, path, path_len)) < 0) + goto done; + + if (submodule) + statbuf.st_mode = GIT_FILEMODE_COMMIT; + } + + /* Ensure that the pathlist entry lines up with what we expected */ + else if (dir_expected) + continue; + + if ((error = filesystem_iterator_entry_init(&entry, + iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0) + goto done; + + git_vector_insert(&new_frame->entries, entry); + } + + if (error == GIT_ITEROVER) + error = 0; + + /* sort now that directory suffix is added */ + git_vector_sort(&new_frame->entries); + +done: + if (error < 0) + git_array_pop(iter->frames); + + git_str_dispose(&root); + git_fs_path_diriter_free(&diriter); + return error; +} + +GIT_INLINE(int) filesystem_iterator_frame_pop(filesystem_iterator *iter) +{ + filesystem_iterator_frame *frame; + + GIT_ASSERT(iter->frames.size); + + frame = git_array_pop(iter->frames); + filesystem_iterator_frame_pop_ignores(iter); + + git_pool_clear(&frame->entry_pool); + git_vector_free(&frame->entries); + + return 0; +} + +static void filesystem_iterator_set_current( + filesystem_iterator *iter, + filesystem_iterator_entry *entry) +{ + /* + * Index entries are limited to 32 bit timestamps. We can safely + * cast this since workdir times are only used in the cache; any + * mismatch will cause a hash recomputation which is unfortunate + * but affects only people who set their filetimes to 2038. + * (Same with the file size.) + */ + iter->entry.ctime.seconds = (int32_t)entry->st.st_ctime; + iter->entry.mtime.seconds = (int32_t)entry->st.st_mtime; + +#if defined(GIT_USE_NSEC) + iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; + iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; +#else + iter->entry.ctime.nanoseconds = 0; + iter->entry.mtime.nanoseconds = 0; +#endif + + iter->entry.dev = entry->st.st_dev; + iter->entry.ino = entry->st.st_ino; + iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode); + iter->entry.uid = entry->st.st_uid; + iter->entry.gid = entry->st.st_gid; + iter->entry.file_size = (uint32_t)entry->st.st_size; + + if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) + git_oid_cpy(&iter->entry.id, &entry->id); + + iter->entry.path = entry->path; + + iter->current_is_ignored = GIT_IGNORE_UNCHECKED; +} + +static int filesystem_iterator_current( + const git_index_entry **out, git_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); + + if (!iter->frames.size) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = &iter->entry; + return 0; +} + +static int filesystem_iterator_is_dir( + bool *is_dir, + const filesystem_iterator *iter, + const filesystem_iterator_entry *entry) +{ + struct stat st; + git_str fullpath = GIT_STR_INIT; + int error = 0; + + if (S_ISDIR(entry->st.st_mode)) { + *is_dir = 1; + goto done; + } + + if (!iterator__descend_symlinks(iter) || !S_ISLNK(entry->st.st_mode)) { + *is_dir = 0; + goto done; + } + + if ((error = git_str_joinpath(&fullpath, iter->root, entry->path)) < 0 || + (error = git_path_validate_str_length(iter->base.repo, &fullpath)) < 0 || + (error = p_stat(fullpath.ptr, &st)) < 0) + goto done; + + *is_dir = S_ISDIR(st.st_mode); + +done: + git_str_dispose(&fullpath); + return error; +} + +static int filesystem_iterator_advance( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + bool is_dir; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + /* examine filesystem entries until we find the next one to return */ + while (true) { + filesystem_iterator_frame *frame; + filesystem_iterator_entry *entry; + + if ((frame = filesystem_iterator_current_frame(iter)) == NULL) { + error = GIT_ITEROVER; + break; + } + + /* no more entries in this frame. pop the frame out */ + if (frame->next_idx == frame->entries.length) { + filesystem_iterator_frame_pop(iter); + continue; + } + + /* we have more entries in the current frame, that's our next entry */ + entry = frame->entries.contents[frame->next_idx]; + frame->next_idx++; + + if ((error = filesystem_iterator_is_dir(&is_dir, iter, entry)) < 0) + break; + + if (is_dir) { + if (iterator__do_autoexpand(iter)) { + error = filesystem_iterator_frame_push(iter, entry); + + /* may get GIT_ENOTFOUND due to races or permission problems + * that we want to quietly swallow + */ + if (error == GIT_ENOTFOUND) + continue; + else if (error < 0) + break; + } + + if (!iterator__include_trees(iter)) + continue; + } + + filesystem_iterator_set_current(iter, entry); + break; + } + + if (out) + *out = (error == 0) ? &iter->entry : NULL; + + return error; +} + +static int filesystem_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + filesystem_iterator_frame *frame; + filesystem_iterator_entry *prev_entry; + int error; + + if (out) + *out = NULL; + + if ((frame = filesystem_iterator_current_frame(iter)) == NULL) + return GIT_ITEROVER; + + /* get the last seen entry */ + prev_entry = filesystem_iterator_current_entry(frame); + + /* it's legal to call advance_into when auto-expand is on. in this case, + * we will have pushed a new (empty) frame on to the stack for this + * new directory. since it's empty, its current_entry should be null. + */ + GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); + + if (prev_entry) { + if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT && + !S_ISDIR(prev_entry->st.st_mode)) + return 0; + + if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0) + return error; + } + + /* we've advanced into the directory in question, let advance + * find the first entry + */ + return filesystem_iterator_advance(out, i); +} + +int git_iterator_current_workdir_path(git_str **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + const git_index_entry *entry; + + if (i->type != GIT_ITERATOR_FS && + i->type != GIT_ITERATOR_WORKDIR) { + *out = NULL; + return 0; + } + + git_str_truncate(&iter->current_path, iter->root_len); + + if (git_iterator_current(&entry, i) < 0 || + git_str_puts(&iter->current_path, entry->path) < 0) + return -1; + + *out = &iter->current_path; + return 0; +} + +GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry) +{ +#if defined(GIT_WIN32) && !defined(__MINGW32__) + return (entry && entry->mode) ? + (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) : + GIT_DIR_FLAG_UNKNOWN; +#else + GIT_UNUSED(entry); + return GIT_DIR_FLAG_UNKNOWN; +#endif +} + +static void filesystem_iterator_update_ignored(filesystem_iterator *iter) +{ + filesystem_iterator_frame *frame; + git_dir_flag dir_flag = entry_dir_flag(&iter->entry); + + if (git_ignore__lookup(&iter->current_is_ignored, + &iter->ignores, iter->entry.path, dir_flag) < 0) { + git_error_clear(); + iter->current_is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* use ignore from containing frame stack */ + if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) { + frame = filesystem_iterator_current_frame(iter); + iter->current_is_ignored = frame->is_ignored; + } +} + +GIT_INLINE(bool) filesystem_iterator_current_is_ignored( + filesystem_iterator *iter) +{ + if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED) + filesystem_iterator_update_ignored(iter); + + return (iter->current_is_ignored == GIT_IGNORE_TRUE); +} + +bool git_iterator_current_is_ignored(git_iterator *i) +{ + filesystem_iterator *iter = NULL; + + if (i->type != GIT_ITERATOR_WORKDIR) + return false; + + 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 = GIT_CONTAINER_OF(i, filesystem_iterator, base); + filesystem_iterator_frame *frame; + + if (i->type != GIT_ITERATOR_WORKDIR) + return false; + + frame = filesystem_iterator_current_frame(iter); + return (frame->is_ignored == GIT_IGNORE_TRUE); +} + +static int filesystem_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_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; + const char *base; + int error = 0; + + *out = NULL; + *status = GIT_ITERATOR_STATUS_NORMAL; + + GIT_ASSERT(iterator__has_been_accessed(i)); + + current_frame = filesystem_iterator_current_frame(iter); + GIT_ASSERT(current_frame); + + current_entry = filesystem_iterator_current_entry(current_frame); + GIT_ASSERT(current_entry); + + if ((error = git_iterator_current(&entry, i)) < 0) + return error; + + if (!S_ISDIR(entry->mode)) { + if (filesystem_iterator_current_is_ignored(iter)) + *status = GIT_ITERATOR_STATUS_IGNORED; + + return filesystem_iterator_advance(out, i); + } + + git_str_clear(&iter->tmp_buf); + if ((error = git_str_puts(&iter->tmp_buf, entry->path)) < 0) + return error; + + base = iter->tmp_buf.ptr; + + /* scan inside the directory looking for files. if we find nothing, + * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to + * IGNORED. if we find a real actual item, upgrade all the way to NORMAL + * and then stop. + * + * however, if we're here looking for a pathlist item (but are not + * actually in the pathlist ourselves) then start at FILTERED instead of + * EMPTY. callers then know that this path was not something they asked + * about. + */ + *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ? + GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY; + + while (entry && !iter->base.prefixcomp(entry->path, base)) { + if (filesystem_iterator_current_is_ignored(iter)) { + /* if we found an explicitly ignored item, then update from + * EMPTY to IGNORED + */ + *status = GIT_ITERATOR_STATUS_IGNORED; + } else if (S_ISDIR(entry->mode)) { + error = filesystem_iterator_advance_into(&entry, i); + + if (!error) + continue; + + /* this directory disappeared, ignore it */ + else if (error == GIT_ENOTFOUND) + error = 0; + + /* a real error occurred */ + else + break; + } else { + /* we found a non-ignored item, treat parent as untracked */ + *status = GIT_ITERATOR_STATUS_NORMAL; + break; + } + + if ((error = git_iterator_advance(&entry, i)) < 0) + break; + } + + /* wrap up scan back to base directory */ + while (entry && !iter->base.prefixcomp(entry->path, base)) { + if ((error = git_iterator_advance(&entry, i)) < 0) + break; + } + + if (!error) + *out = entry; + + return error; +} + +static void filesystem_iterator_clear(filesystem_iterator *iter) +{ + while (iter->frames.size) + filesystem_iterator_frame_pop(iter); + + git_array_clear(iter->frames); + git_ignore__free(&iter->ignores); + + git_str_dispose(&iter->tmp_buf); + + iterator_clear(&iter->base); +} + +static int filesystem_iterator_init(filesystem_iterator *iter) +{ + int error; + + if (iterator__honor_ignores(&iter->base) && + (error = git_ignore__for_path(iter->base.repo, + ".gitignore", &iter->ignores)) < 0) + return error; + + if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0) + return error; + + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + + return 0; +} + +static int filesystem_iterator_reset(git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + + filesystem_iterator_clear(iter); + return filesystem_iterator_init(iter); +} + +static void filesystem_iterator_free(git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + git__free(iter->root); + git_str_dispose(&iter->current_path); + git_tree_free(iter->tree); + if (iter->index) + git_index_snapshot_release(&iter->index_snapshot, iter->index); + filesystem_iterator_clear(iter); +} + +static int iterator_for_filesystem( + git_iterator **out, + git_repository *repo, + const char *root, + git_index *index, + git_tree *tree, + git_iterator_t type, + git_iterator_options *options) +{ + filesystem_iterator *iter; + size_t root_len; + int error; + + static git_iterator_callbacks callbacks = { + filesystem_iterator_current, + filesystem_iterator_advance, + filesystem_iterator_advance_into, + filesystem_iterator_advance_over, + filesystem_iterator_reset, + filesystem_iterator_free + }; + + *out = NULL; + + if (root == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(filesystem_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = type; + iter->base.cb = &callbacks; + + root_len = strlen(root); + + iter->root = git__malloc(root_len+2); + GIT_ERROR_CHECK_ALLOC(iter->root); + + memcpy(iter->root, root, root_len); + + if (root_len == 0 || root[root_len-1] != '/') { + iter->root[root_len] = '/'; + root_len++; + } + iter->root[root_len] = '\0'; + iter->root_len = root_len; + + if ((error = git_str_puts(&iter->current_path, iter->root)) < 0) + goto on_error; + + if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0) + goto on_error; + + if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0) + goto on_error; + + if (index && + (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0) + goto on_error; + + iter->index = index; + iter->dirload_flags = + (iterator__ignore_case(&iter->base) ? + GIT_FS_PATH_DIR_IGNORE_CASE : 0) | + (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ? + GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE : 0); + + if ((error = filesystem_iterator_init(iter)) < 0) + goto on_error; + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} + +int git_iterator_for_filesystem( + git_iterator **out, + const char *root, + git_iterator_options *options) +{ + return iterator_for_filesystem(out, + NULL, root, NULL, NULL, GIT_ITERATOR_FS, options); +} + +int git_iterator_for_workdir_ext( + git_iterator **out, + git_repository *repo, + const char *repo_workdir, + git_index *index, + git_tree *tree, + git_iterator_options *given_opts) +{ + git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT; + + if (!repo_workdir) { + if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) + return GIT_EBAREREPO; + + repo_workdir = git_repository_workdir(repo); + } + + /* upgrade to a workdir iterator, adding necessary internal flags */ + if (given_opts) + memcpy(&options, given_opts, sizeof(git_iterator_options)); + + options.flags |= GIT_ITERATOR_HONOR_IGNORES | + GIT_ITERATOR_IGNORE_DOT_GIT; + + return iterator_for_filesystem(out, + repo, repo_workdir, index, tree, GIT_ITERATOR_WORKDIR, &options); +} + + +/* Index iterator */ + + +typedef struct { + git_iterator base; + git_vector entries; + size_t next_idx; + + /* the pseudotree entry */ + git_index_entry tree_entry; + git_str tree_buf; + bool skip_tree; + + const git_index_entry *entry; +} index_iterator; + +static int index_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (iter->entry == NULL) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = iter->entry; + return 0; +} + +static bool index_iterator_create_pseudotree( + const git_index_entry **out, + index_iterator *iter, + const char *path) +{ + const char *prev_path, *relative_path, *dirsep; + size_t common_len; + + prev_path = iter->entry ? iter->entry->path : ""; + + /* determine if the new path is in a different directory from the old */ + common_len = git_fs_path_common_dirlen(prev_path, path); + relative_path = path + common_len; + + if ((dirsep = strchr(relative_path, '/')) == NULL) + return false; + + git_str_clear(&iter->tree_buf); + git_str_put(&iter->tree_buf, path, (dirsep - path) + 1); + + iter->tree_entry.mode = GIT_FILEMODE_TREE; + iter->tree_entry.path = iter->tree_buf.ptr; + + *out = &iter->tree_entry; + return true; +} + +static int index_iterator_skip_pseudotree(index_iterator *iter) +{ + GIT_ASSERT(iterator__has_been_accessed(&iter->base)); + GIT_ASSERT(S_ISDIR(iter->entry->mode)); + + while (true) { + const git_index_entry *next_entry = NULL; + + if (++iter->next_idx >= iter->entries.length) + return GIT_ITEROVER; + + next_entry = iter->entries.contents[iter->next_idx]; + + if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path, + iter->tree_buf.size) != 0) + break; + } + + iter->skip_tree = false; + return 0; +} + +static int index_iterator_advance( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + const git_index_entry *entry = NULL; + bool is_submodule; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + while (true) { + if (iter->next_idx >= iter->entries.length) { + error = GIT_ITEROVER; + break; + } + + /* we were not asked to expand this pseudotree. advance over it. */ + if (iter->skip_tree) { + index_iterator_skip_pseudotree(iter); + continue; + } + + entry = iter->entries.contents[iter->next_idx]; + is_submodule = S_ISGITLINK(entry->mode); + + if (!iterator_has_started(&iter->base, entry->path, is_submodule)) { + iter->next_idx++; + continue; + } + + if (iterator_has_ended(&iter->base, entry->path)) { + error = GIT_ITEROVER; + break; + } + + /* if we have a list of paths we're interested in, examine it */ + if (!iterator_pathlist_next_is(&iter->base, entry->path)) { + iter->next_idx++; + continue; + } + + /* if this is a conflict, skip it unless we're including conflicts */ + if (git_index_entry_is_conflict(entry) && + !iterator__include_conflicts(&iter->base)) { + iter->next_idx++; + continue; + } + + /* we've found what will be our next _file_ entry. but if we are + * returning trees entries, we may need to return a pseudotree + * entry that will contain this. don't advance over this entry, + * though, we still need to return it on the next `advance`. + */ + if (iterator__include_trees(&iter->base) && + index_iterator_create_pseudotree(&entry, iter, entry->path)) { + + /* Note whether this pseudo tree should be expanded or not */ + iter->skip_tree = iterator__dont_autoexpand(&iter->base); + break; + } + + iter->next_idx++; + break; + } + + iter->entry = (error == 0) ? entry : NULL; + + if (out) + *out = iter->entry; + + return error; +} + +static int index_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + + if (! S_ISDIR(iter->tree_entry.mode)) { + if (out) + *out = NULL; + + return 0; + } + + iter->skip_tree = false; + return index_iterator_advance(out, i); +} + +static int index_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + const git_index_entry *entry; + int error; + + if ((error = index_iterator_current(&entry, i)) < 0) + return error; + + if (S_ISDIR(entry->mode)) + index_iterator_skip_pseudotree(iter); + + *status = GIT_ITERATOR_STATUS_NORMAL; + return index_iterator_advance(out, i); +} + +static void index_iterator_clear(index_iterator *iter) +{ + iterator_clear(&iter->base); +} + +static int index_iterator_init(index_iterator *iter) +{ + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + iter->next_idx = 0; + iter->skip_tree = false; + return 0; +} + +static int index_iterator_reset(git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + + index_iterator_clear(iter); + return index_iterator_init(iter); +} + +static void index_iterator_free(git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + + git_index_snapshot_release(&iter->entries, iter->base.index); + git_str_dispose(&iter->tree_buf); +} + +int git_iterator_for_index( + git_iterator **out, + git_repository *repo, + git_index *index, + git_iterator_options *options) +{ + index_iterator *iter; + int error; + + static git_iterator_callbacks callbacks = { + index_iterator_current, + index_iterator_advance, + index_iterator_advance_into, + index_iterator_advance_over, + index_iterator_reset, + index_iterator_free + }; + + *out = NULL; + + if (index == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(index_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_INDEX; + iter->base.cb = &callbacks; + + if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || + (error = git_index_snapshot_new(&iter->entries, index)) < 0 || + (error = index_iterator_init(iter)) < 0) + goto on_error; + + git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ? + git_index_entry_icmp : git_index_entry_cmp); + git_vector_sort(&iter->entries); + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} + + +/* Iterator API */ + +int git_iterator_reset_range( + git_iterator *i, const char *start, const char *end) +{ + if (iterator_reset_range(i, start, end) < 0) + return -1; + + return i->cb->reset(i); +} + +int git_iterator_set_ignore_case(git_iterator *i, bool ignore_case) +{ + GIT_ASSERT(!iterator__has_been_accessed(i)); + iterator_set_ignore_case(i, ignore_case); + return 0; +} + +void git_iterator_free(git_iterator *iter) +{ + if (iter == NULL) + return; + + iter->cb->free(iter); + + git_vector_free(&iter->pathlist); + git__free(iter->start); + git__free(iter->end); + + memset(iter, 0, sizeof(*iter)); + + git__free(iter); +} + +int git_iterator_foreach( + git_iterator *iterator, + git_iterator_foreach_cb cb, + void *data) +{ + const git_index_entry *iterator_item; + int error = 0; + + if ((error = git_iterator_current(&iterator_item, iterator)) < 0) + goto done; + + if ((error = cb(iterator_item, data)) != 0) + goto done; + + while (true) { + if ((error = git_iterator_advance(&iterator_item, iterator)) < 0) + goto done; + + if ((error = cb(iterator_item, data)) != 0) + goto done; + } + +done: + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +int git_iterator_walk( + git_iterator **iterators, + size_t cnt, + git_iterator_walk_cb cb, + void *data) +{ + const git_index_entry **iterator_item; /* next in each iterator */ + const git_index_entry **cur_items; /* current path in each iter */ + const git_index_entry *first_match; + size_t i, j; + int error = 0; + + iterator_item = git__calloc(cnt, sizeof(git_index_entry *)); + cur_items = git__calloc(cnt, sizeof(git_index_entry *)); + + GIT_ERROR_CHECK_ALLOC(iterator_item); + GIT_ERROR_CHECK_ALLOC(cur_items); + + /* Set up the iterators */ + for (i = 0; i < cnt; i++) { + error = git_iterator_current(&iterator_item[i], iterators[i]); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + + while (true) { + for (i = 0; i < cnt; i++) + cur_items[i] = NULL; + + first_match = NULL; + + /* Find the next path(s) to consume from each iterator */ + for (i = 0; i < cnt; i++) { + if (iterator_item[i] == NULL) + continue; + + if (first_match == NULL) { + first_match = iterator_item[i]; + cur_items[i] = iterator_item[i]; + } else { + int path_diff = git_index_entry_cmp(iterator_item[i], first_match); + + if (path_diff < 0) { + /* Found an index entry that sorts before the one we're + * looking at. Forget that we've seen the other and + * look at the other iterators for this path. + */ + for (j = 0; j < i; j++) + cur_items[j] = NULL; + + first_match = iterator_item[i]; + cur_items[i] = iterator_item[i]; + } else if (path_diff == 0) { + cur_items[i] = iterator_item[i]; + } + } + } + + if (first_match == NULL) + break; + + if ((error = cb(cur_items, data)) != 0) + goto done; + + /* Advance each iterator that participated */ + for (i = 0; i < cnt; i++) { + if (cur_items[i] == NULL) + continue; + + error = git_iterator_advance(&iterator_item[i], iterators[i]); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + } + +done: + git__free((git_index_entry **)iterator_item); + git__free((git_index_entry **)cur_items); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} diff --git a/src/libgit2/iterator.h b/src/libgit2/iterator.h new file mode 100644 index 000000000..6bb8489d0 --- /dev/null +++ b/src/libgit2/iterator.h @@ -0,0 +1,322 @@ +/* + * 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_iterator_h__ +#define INCLUDE_iterator_h__ + +#include "common.h" + +#include "git2/index.h" +#include "vector.h" +#include "str.h" +#include "ignore.h" + +typedef struct git_iterator git_iterator; + +typedef enum { + 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 */ + GIT_ITERATOR_IGNORE_CASE = (1u << 0), + /** force case sensitivity for entry sort order */ + GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1), + /** return tree items in addition to blob items */ + GIT_ITERATOR_INCLUDE_TREES = (1u << 2), + /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ + GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), + /** convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), + /** never convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5), + /** include conflicts */ + GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6), + /** descend into symlinked directories */ + GIT_ITERATOR_DESCEND_SYMLINKS = (1u << 7), + /** hash files in workdir or filesystem iterators */ + GIT_ITERATOR_INCLUDE_HASH = (1u << 8) +} git_iterator_flag_t; + +typedef enum { + GIT_ITERATOR_STATUS_NORMAL = 0, + GIT_ITERATOR_STATUS_IGNORED = 1, + GIT_ITERATOR_STATUS_EMPTY = 2, + GIT_ITERATOR_STATUS_FILTERED = 3 +} git_iterator_status_t; + +typedef struct { + const char *start; + const char *end; + + /* paths to include in the iterator (literal). if set, any paths not + * listed here will be excluded from iteration. + */ + git_strarray pathlist; + + /* flags, from above */ + unsigned int flags; +} git_iterator_options; + +#define GIT_ITERATOR_OPTIONS_INIT {0} + +typedef struct { + int (*current)(const git_index_entry **, git_iterator *); + int (*advance)(const git_index_entry **, git_iterator *); + int (*advance_into)(const git_index_entry **, git_iterator *); + int (*advance_over)( + const git_index_entry **, git_iterator_status_t *, git_iterator *); + int (*reset)(git_iterator *); + void (*free)(git_iterator *); +} git_iterator_callbacks; + +struct git_iterator { + git_iterator_t type; + git_iterator_callbacks *cb; + + git_repository *repo; + git_index *index; + + char *start; + size_t start_len; + + char *end; + size_t end_len; + + bool started; + bool ended; + git_vector pathlist; + size_t pathlist_walk_idx; + int (*strcomp)(const char *a, const char *b); + int (*strncomp)(const char *a, const char *b, size_t n); + int (*prefixcomp)(const char *str, const char *prefix); + int (*entry_srch)(const void *key, const void *array_member); + size_t stat_calls; + unsigned int flags; +}; + +extern int git_iterator_for_nothing( + git_iterator **out, + git_iterator_options *options); + +/* tree iterators will match the ignore_case value from the index of the + * repository, unless you override with a non-zero flag value + */ +extern int git_iterator_for_tree( + git_iterator **out, + git_tree *tree, + git_iterator_options *options); + +/* index iterators will take the ignore_case value from the index; the + * ignore_case flags are not used + */ +extern int git_iterator_for_index( + git_iterator **out, + git_repository *repo, + git_index *index, + git_iterator_options *options); + +extern int git_iterator_for_workdir_ext( + git_iterator **out, + git_repository *repo, + const char *repo_workdir, + git_index *index, + git_tree *tree, + git_iterator_options *options); + +/* workdir iterators will match the ignore_case value from the index of the + * repository, unless you override with a non-zero flag value + */ +GIT_INLINE(int) git_iterator_for_workdir( + git_iterator **out, + git_repository *repo, + git_index *index, + git_tree *tree, + git_iterator_options *options) +{ + return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options); +} + +/* for filesystem iterators, you have to explicitly pass in the ignore_case + * behavior that you desire + */ +extern int git_iterator_for_filesystem( + git_iterator **out, + const char *root, + git_iterator_options *options); + +extern void git_iterator_free(git_iterator *iter); + +/* Return a git_index_entry structure for the current value the iterator + * is looking at or NULL if the iterator is at the end. + * + * The entry may noy be fully populated. Tree iterators will only have a + * value mode, OID, and path. Workdir iterators will not have an OID (but + * you can use `git_iterator_current_oid()` to calculate it on demand). + * + * You do not need to free the entry. It is still "owned" by the iterator. + * Once you call `git_iterator_advance()` then the old entry is no longer + * guaranteed to be valid - it may be freed or just overwritten in place. + */ +GIT_INLINE(int) git_iterator_current( + const git_index_entry **entry, git_iterator *iter) +{ + return iter->cb->current(entry, iter); +} + +/** + * Advance to the next item for the iterator. + * + * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If + * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree + * item will skip over all the items under that tree. + */ +GIT_INLINE(int) git_iterator_advance( + const git_index_entry **entry, git_iterator *iter) +{ + return iter->cb->advance(entry, iter); +} + +/** + * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set). + * + * git_iterator_advance() steps through all items being iterated over + * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES), + * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next + * sibling of a tree instead of going to the first child of the tree. In + * that case, use this function to advance to the first child of the tree. + * + * If the current item is not a tree, this is a no-op. + * + * For filesystem and working directory iterators, a tree (i.e. directory) + * can be empty. In that case, this function returns GIT_ENOTFOUND and + * does not advance. That can't happen for tree and index iterators. + */ +GIT_INLINE(int) git_iterator_advance_into( + const git_index_entry **entry, git_iterator *iter) +{ + return iter->cb->advance_into(entry, iter); +} + +/* Advance over a directory and check if it contains no files or just + * ignored files. + * + * In a tree or the index, all directories will contain files, but in the + * working directory it is possible to have an empty directory tree or a + * tree that only contains ignored files. Many Git operations treat these + * cases specially. This advances over a directory (presumably an + * untracked directory) but checks during the scan if there are any files + * and any non-ignored files. + */ +GIT_INLINE(int) git_iterator_advance_over( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iter) +{ + return iter->cb->advance_over(entry, status, iter); +} + +/** + * Go back to the start of the iteration. + */ +GIT_INLINE(int) git_iterator_reset(git_iterator *iter) +{ + return iter->cb->reset(iter); +} + +/** + * Go back to the start of the iteration after updating the `start` and + * `end` pathname boundaries of the iteration. + */ +extern int git_iterator_reset_range( + git_iterator *iter, const char *start, const char *end); + +GIT_INLINE(git_iterator_t) git_iterator_type(git_iterator *iter) +{ + return iter->type; +} + +GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) +{ + return iter->repo; +} + +GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter) +{ + return iter->index; +} + +GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter) +{ + return iter->flags; +} + +GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) +{ + return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); +} + +extern int git_iterator_set_ignore_case( + git_iterator *iter, bool ignore_case); + +extern int git_iterator_current_tree_entry( + const git_tree_entry **entry_out, git_iterator *iter); + +extern int git_iterator_current_parent_tree( + const git_tree **tree_out, git_iterator *iter, size_t depth); + +extern bool git_iterator_current_is_ignored(git_iterator *iter); + +extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); + +/** + * Get full path of the current item from a workdir iterator. This will + * return NULL for a non-workdir iterator. The git_str is still owned by + * the iterator; this is exposed just for efficiency. + */ +extern int git_iterator_current_workdir_path( + git_str **path, git_iterator *iter); + +/** + * Retrieve the index stored in the iterator. + * + * Only implemented for the workdir and index iterators. + */ +extern git_index *git_iterator_index(git_iterator *iter); + +typedef int (*git_iterator_foreach_cb)( + const git_index_entry *entry, + void *data); + +/** + * Walk the given iterator and invoke the callback for each path + * contained in the iterator. + */ +extern int git_iterator_foreach( + git_iterator *iterator, + git_iterator_foreach_cb cb, + void *data); + +typedef int (*git_iterator_walk_cb)( + const git_index_entry **entries, + void *data); + +/** + * Walk the given iterators in lock-step. The given callback will be + * called for each unique path, with the index entry in each iterator + * (or NULL if the given iterator does not contain that path). + */ +extern int git_iterator_walk( + git_iterator **iterators, + size_t cnt, + git_iterator_walk_cb cb, + void *data); + +#endif diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c new file mode 100644 index 000000000..2fda0722e --- /dev/null +++ b/src/libgit2/libgit2.c @@ -0,0 +1,425 @@ +/* + * 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 "libgit2.h" + +#include +#include "alloc.h" +#include "buf.h" +#include "cache.h" +#include "common.h" +#include "filter.h" +#include "hash.h" +#include "index.h" +#include "merge_driver.h" +#include "pool.h" +#include "mwindow.h" +#include "object.h" +#include "odb.h" +#include "rand.h" +#include "refs.h" +#include "runtime.h" +#include "sysdir.h" +#include "thread.h" +#include "threadstate.h" +#include "git2/global.h" +#include "streams/registry.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "transports/smart.h" +#include "transports/http.h" +#include "transports/ssh.h" + +#ifdef GIT_WIN32 +# include "win32/w32_leakcheck.h" +#endif + +/* Declarations for tuneable settings */ +extern size_t git_mwindow__window_size; +extern size_t git_mwindow__mapped_limit; +extern size_t git_mwindow__file_limit; +extern size_t git_indexer__max_objects; +extern bool git_disable_pack_keep_file_checks; +extern int git_odb__packed_priority; +extern int git_odb__loose_priority; + +char *git__user_agent; +char *git__ssl_ciphers; + +static void libgit2_settings_global_shutdown(void) +{ + git__free(git__user_agent); + git__free(git__ssl_ciphers); + git_repository__free_extensions(); +} + +static int git_libgit2_settings_global_init(void) +{ + return git_runtime_shutdown_register(libgit2_settings_global_shutdown); +} + +int git_libgit2_init(void) +{ + static git_runtime_init_fn init_fns[] = { +#ifdef GIT_WIN32 + git_win32_leakcheck_global_init, +#endif + git_allocator_global_init, + git_threadstate_global_init, + git_threads_global_init, + git_rand_global_init, + git_hash_global_init, + git_sysdir_global_init, + git_filter_global_init, + git_merge_driver_global_init, + git_transport_ssh_global_init, + git_stream_registry_global_init, + git_openssl_stream_global_init, + git_mbedtls_stream_global_init, + git_mwindow_global_init, + git_pool_global_init, + git_libgit2_settings_global_init + }; + + return git_runtime_init(init_fns, ARRAY_SIZE(init_fns)); +} + +int git_libgit2_init_count(void) +{ + return git_runtime_init_count(); +} + +int git_libgit2_shutdown(void) +{ + return git_runtime_shutdown(); +} + +int git_libgit2_version(int *major, int *minor, int *rev) +{ + *major = LIBGIT2_VER_MAJOR; + *minor = LIBGIT2_VER_MINOR; + *rev = LIBGIT2_VER_REVISION; + + return 0; +} + +const char *git_libgit2_prerelease(void) +{ + return LIBGIT2_VER_PRERELEASE; +} + +int git_libgit2_features(void) +{ + return 0 +#ifdef GIT_THREADS + | GIT_FEATURE_THREADS +#endif +#ifdef GIT_HTTPS + | GIT_FEATURE_HTTPS +#endif +#if defined(GIT_SSH) + | GIT_FEATURE_SSH +#endif +#if defined(GIT_USE_NSEC) + | GIT_FEATURE_NSEC +#endif + ; +} + +static int config_level_to_sysdir(int *out, int config_level) +{ + switch (config_level) { + case GIT_CONFIG_LEVEL_SYSTEM: + *out = GIT_SYSDIR_SYSTEM; + return 0; + case GIT_CONFIG_LEVEL_XDG: + *out = GIT_SYSDIR_XDG; + return 0; + case GIT_CONFIG_LEVEL_GLOBAL: + *out = GIT_SYSDIR_GLOBAL; + return 0; + case GIT_CONFIG_LEVEL_PROGRAMDATA: + *out = GIT_SYSDIR_PROGRAMDATA; + return 0; + default: + break; + } + + git_error_set( + GIT_ERROR_INVALID, "invalid config path selector %d", config_level); + return -1; +} + +const char *git_libgit2__user_agent(void) +{ + return git__user_agent; +} + +const char *git_libgit2__ssl_ciphers(void) +{ + return git__ssl_ciphers; +} + +int git_libgit2_opts(int key, ...) +{ + int error = 0; + va_list ap; + + va_start(ap, key); + + switch (key) { + case GIT_OPT_SET_MWINDOW_SIZE: + git_mwindow__window_size = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_SIZE: + *(va_arg(ap, size_t *)) = git_mwindow__window_size; + break; + + case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: + git_mwindow__mapped_limit = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: + *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; + break; + + case GIT_OPT_SET_MWINDOW_FILE_LIMIT: + git_mwindow__file_limit = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_FILE_LIMIT: + *(va_arg(ap, size_t *)) = git_mwindow__file_limit; + break; + + case GIT_OPT_GET_SEARCH_PATH: + { + int sysdir = va_arg(ap, int); + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + int level; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = config_level_to_sysdir(&level, sysdir)) < 0 || + (error = git_sysdir_get(&tmp, level)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_SEARCH_PATH: + { + int level; + + if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0) + error = git_sysdir_set(level, va_arg(ap, const char *)); + } + break; + + case GIT_OPT_SET_CACHE_OBJECT_LIMIT: + { + git_object_t type = (git_object_t)va_arg(ap, int); + size_t size = va_arg(ap, size_t); + error = git_cache_set_max_object_size(type, size); + break; + } + + case GIT_OPT_SET_CACHE_MAX_SIZE: + git_cache__max_storage = va_arg(ap, ssize_t); + break; + + case GIT_OPT_ENABLE_CACHING: + git_cache__enabled = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_CACHED_MEMORY: + *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; + *(va_arg(ap, ssize_t *)) = git_cache__max_storage; + break; + + case GIT_OPT_GET_TEMPLATE_PATH: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_TEMPLATE_PATH: + error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *)); + break; + + case GIT_OPT_SET_SSL_CERT_LOCATIONS: +#ifdef GIT_OPENSSL + { + const char *file = va_arg(ap, const char *); + const char *path = va_arg(ap, const char *); + error = git_openssl__set_cert_location(file, path); + } +#elif defined(GIT_MBEDTLS) + { + const char *file = va_arg(ap, const char *); + const char *path = va_arg(ap, const char *); + error = git_mbedtls__set_cert_location(file, path); + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations"); + error = -1; +#endif + break; + case GIT_OPT_SET_USER_AGENT: + git__free(git__user_agent); + git__user_agent = git__strdup(va_arg(ap, const char *)); + if (!git__user_agent) { + git_error_set_oom(); + error = -1; + } + + break; + + case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: + git_object__strict_input_validation = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: + git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_SSL_CIPHERS: +#if (GIT_OPENSSL || GIT_MBEDTLS) + { + git__free(git__ssl_ciphers); + git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); + if (!git__ssl_ciphers) { + git_error_set_oom(); + error = -1; + } + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); + error = -1; +#endif + break; + + case GIT_OPT_GET_USER_AGENT: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_str_puts(&str, git__user_agent)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_ENABLE_OFS_DELTA: + git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_FSYNC_GITDIR: + git_repository__fsync_gitdir = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; +#endif + break; + + case GIT_OPT_SET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + git_win32__createfile_sharemode = va_arg(ap, unsigned long); +#endif + break; + + case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: + git_odb__strict_hash_verification = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_ALLOCATOR: + error = git_allocator_setup(va_arg(ap, git_allocator *)); + break; + + case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: + git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_PACK_MAX_OBJECTS: + git_indexer__max_objects = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_PACK_MAX_OBJECTS: + *(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; + + case GIT_OPT_SET_ODB_PACKED_PRIORITY: + git_odb__packed_priority = va_arg(ap, int); + break; + + case GIT_OPT_SET_ODB_LOOSE_PRIORITY: + git_odb__loose_priority = va_arg(ap, int); + break; + + case GIT_OPT_SET_EXTENSIONS: + { + const char **extensions = va_arg(ap, const char **); + size_t len = va_arg(ap, size_t); + error = git_repository__set_extensions(extensions, len); + } + break; + + case GIT_OPT_GET_EXTENSIONS: + { + git_strarray *out = va_arg(ap, git_strarray *); + char **extensions; + size_t len; + + if ((error = git_repository__extensions(&extensions, &len)) < 0) + break; + + out->strings = extensions; + out->count = len; + } + break; + + case GIT_OPT_GET_OWNER_VALIDATION: + *(va_arg(ap, int *)) = git_repository__validate_ownership; + break; + + case GIT_OPT_SET_OWNER_VALIDATION: + git_repository__validate_ownership = (va_arg(ap, int) != 0); + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid option key"); + error = -1; + } + + va_end(ap); + + return error; +} diff --git a/src/libgit2/libgit2.h b/src/libgit2/libgit2.h new file mode 100644 index 000000000..a898367ae --- /dev/null +++ b/src/libgit2/libgit2.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_libgit2_h__ +#define INCLUDE_libgit2_h__ + +extern int git_libgit2_init_count(void); + +extern const char *git_libgit2__user_agent(void); +extern const char *git_libgit2__ssl_ciphers(void); + +#endif diff --git a/src/libgit2/mailmap.c b/src/libgit2/mailmap.c new file mode 100644 index 000000000..4336fe3e5 --- /dev/null +++ b/src/libgit2/mailmap.c @@ -0,0 +1,500 @@ +/* + * 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 "mailmap.h" + +#include "common.h" +#include "config.h" +#include "fs_path.h" +#include "repository.h" +#include "signature.h" +#include "git2/config.h" +#include "git2/revparse.h" +#include "blob.h" +#include "parse.h" +#include "path.h" + +#define MM_FILE ".mailmap" +#define MM_FILE_CONFIG "mailmap.file" +#define MM_BLOB_CONFIG "mailmap.blob" +#define MM_BLOB_DEFAULT "HEAD:" MM_FILE + +static void mailmap_entry_free(git_mailmap_entry *entry) +{ + if (!entry) + return; + + git__free(entry->real_name); + git__free(entry->real_email); + git__free(entry->replace_name); + git__free(entry->replace_email); + git__free(entry); +} + +/* + * First we sort by replace_email, then replace_name (if present). + * Entries with names are greater than entries without. + */ +static int mailmap_entry_cmp(const void *a_raw, const void *b_raw) +{ + const git_mailmap_entry *a = (const git_mailmap_entry *)a_raw; + const git_mailmap_entry *b = (const git_mailmap_entry *)b_raw; + int cmp; + + GIT_ASSERT_ARG(a && a->replace_email); + GIT_ASSERT_ARG(b && b->replace_email); + + cmp = git__strcmp(a->replace_email, b->replace_email); + if (cmp) + return cmp; + + /* NULL replace_names are less than not-NULL ones */ + if (a->replace_name == NULL || b->replace_name == NULL) + return (int)(a->replace_name != NULL) - (int)(b->replace_name != NULL); + + return git__strcmp(a->replace_name, b->replace_name); +} + +/* Replace the old entry with the new on duplicate. */ +static int mailmap_entry_replace(void **old_raw, void *new_raw) +{ + mailmap_entry_free((git_mailmap_entry *)*old_raw); + *old_raw = new_raw; + return GIT_EEXISTS; +} + +/* Check if we're at the end of line, w/ comments */ +static bool is_eol(git_parse_ctx *ctx) +{ + char c; + return git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 || c == '#'; +} + +static int advance_until( + const char **start, size_t *len, git_parse_ctx *ctx, char needle) +{ + *start = ctx->line; + while (ctx->line_len > 0 && *ctx->line != '#' && *ctx->line != needle) + git_parse_advance_chars(ctx, 1); + + if (ctx->line_len == 0 || *ctx->line == '#') + return -1; /* end of line */ + + *len = ctx->line - *start; + git_parse_advance_chars(ctx, 1); /* advance past needle */ + return 0; +} + +/* + * Parse a single entry from a mailmap file. + * + * The output git_strs will be non-owning, and should be copied before being + * persisted. + */ +static int parse_mailmap_entry( + git_str *real_name, git_str *real_email, + git_str *replace_name, git_str *replace_email, + git_parse_ctx *ctx) +{ + const char *start; + size_t len; + + git_str_clear(real_name); + git_str_clear(real_email); + git_str_clear(replace_name); + git_str_clear(replace_email); + + git_parse_advance_ws(ctx); + if (is_eol(ctx)) + return -1; /* blank line */ + + /* Parse the real name */ + if (advance_until(&start, &len, ctx, '<') < 0) + return -1; + + git_str_attach_notowned(real_name, start, len); + git_str_rtrim(real_name); + + /* + * If this is the last email in the line, this is the email to replace, + * otherwise, it's the real email. + */ + if (advance_until(&start, &len, ctx, '>') < 0) + return -1; + + /* If we aren't at the end of the line, parse a second name and email */ + if (!is_eol(ctx)) { + git_str_attach_notowned(real_email, start, len); + + git_parse_advance_ws(ctx); + if (advance_until(&start, &len, ctx, '<') < 0) + return -1; + git_str_attach_notowned(replace_name, start, len); + git_str_rtrim(replace_name); + + if (advance_until(&start, &len, ctx, '>') < 0) + return -1; + } + + git_str_attach_notowned(replace_email, start, len); + + if (!is_eol(ctx)) + return -1; + + return 0; +} + +int git_mailmap_new(git_mailmap **out) +{ + int error; + git_mailmap *mm = git__calloc(1, sizeof(git_mailmap)); + GIT_ERROR_CHECK_ALLOC(mm); + + error = git_vector_init(&mm->entries, 0, mailmap_entry_cmp); + if (error < 0) { + git__free(mm); + return error; + } + *out = mm; + return 0; +} + +void git_mailmap_free(git_mailmap *mm) +{ + size_t idx; + git_mailmap_entry *entry; + if (!mm) + return; + + git_vector_foreach(&mm->entries, idx, entry) + mailmap_entry_free(entry); + + git_vector_free(&mm->entries); + git__free(mm); +} + +static int mailmap_add_entry_unterminated( + git_mailmap *mm, + const char *real_name, size_t real_name_size, + const char *real_email, size_t real_email_size, + const char *replace_name, size_t replace_name_size, + const char *replace_email, size_t replace_email_size) +{ + int error; + git_mailmap_entry *entry = git__calloc(1, sizeof(git_mailmap_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + GIT_ASSERT_ARG(mm); + GIT_ASSERT_ARG(replace_email && *replace_email); + + if (real_name_size > 0) { + entry->real_name = git__substrdup(real_name, real_name_size); + GIT_ERROR_CHECK_ALLOC(entry->real_name); + } + if (real_email_size > 0) { + entry->real_email = git__substrdup(real_email, real_email_size); + GIT_ERROR_CHECK_ALLOC(entry->real_email); + } + if (replace_name_size > 0) { + entry->replace_name = git__substrdup(replace_name, replace_name_size); + GIT_ERROR_CHECK_ALLOC(entry->replace_name); + } + entry->replace_email = git__substrdup(replace_email, replace_email_size); + GIT_ERROR_CHECK_ALLOC(entry->replace_email); + + error = git_vector_insert_sorted(&mm->entries, entry, mailmap_entry_replace); + if (error == GIT_EEXISTS) + error = GIT_OK; + else if (error < 0) + mailmap_entry_free(entry); + + return error; +} + +int git_mailmap_add_entry( + git_mailmap *mm, const char *real_name, const char *real_email, + const char *replace_name, const char *replace_email) +{ + return mailmap_add_entry_unterminated( + mm, + real_name, real_name ? strlen(real_name) : 0, + real_email, real_email ? strlen(real_email) : 0, + replace_name, replace_name ? strlen(replace_name) : 0, + replace_email, strlen(replace_email)); +} + +static int mailmap_add_buffer(git_mailmap *mm, const char *buf, size_t len) +{ + int error = 0; + git_parse_ctx ctx; + + /* Scratch buffers containing the real parsed names & emails */ + git_str real_name = GIT_STR_INIT; + git_str real_email = GIT_STR_INIT; + git_str replace_name = GIT_STR_INIT; + git_str replace_email = GIT_STR_INIT; + + /* Buffers may not contain '\0's. */ + if (memchr(buf, '\0', len) != NULL) + return -1; + + git_parse_ctx_init(&ctx, buf, len); + + /* Run the parser */ + while (ctx.remain_len > 0) { + error = parse_mailmap_entry( + &real_name, &real_email, &replace_name, &replace_email, &ctx); + if (error < 0) { + error = 0; /* Skip lines which don't contain a valid entry */ + git_parse_advance_line(&ctx); + continue; /* TODO: warn */ + } + + /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */ + error = mailmap_add_entry_unterminated( + mm, real_name.ptr, real_name.size, real_email.ptr, real_email.size, + replace_name.ptr, replace_name.size, replace_email.ptr, replace_email.size); + if (error < 0) + goto cleanup; + + error = 0; + } + +cleanup: + git_str_dispose(&real_name); + git_str_dispose(&real_email); + git_str_dispose(&replace_name); + git_str_dispose(&replace_email); + return error; +} + +int git_mailmap_from_buffer(git_mailmap **out, const char *data, size_t len) +{ + int error = git_mailmap_new(out); + if (error < 0) + return error; + + error = mailmap_add_buffer(*out, data, len); + if (error < 0) { + git_mailmap_free(*out); + *out = NULL; + } + return error; +} + +static int mailmap_add_blob( + git_mailmap *mm, git_repository *repo, const char *rev) +{ + git_object *object = NULL; + git_blob *blob = NULL; + git_str content = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(mm); + GIT_ASSERT_ARG(repo); + + error = git_revparse_single(&object, repo, rev); + if (error < 0) + goto cleanup; + + error = git_object_peel((git_object **)&blob, object, GIT_OBJECT_BLOB); + if (error < 0) + goto cleanup; + + error = git_blob__getbuf(&content, blob); + if (error < 0) + goto cleanup; + + error = mailmap_add_buffer(mm, content.ptr, content.size); + if (error < 0) + goto cleanup; + +cleanup: + git_str_dispose(&content); + git_blob_free(blob); + git_object_free(object); + return error; +} + +static int mailmap_add_file_ondisk( + git_mailmap *mm, const char *path, git_repository *repo) +{ + const char *base = repo ? git_repository_workdir(repo) : NULL; + git_str fullpath = GIT_STR_INIT; + git_str content = GIT_STR_INIT; + int error; + + error = git_fs_path_join_unrooted(&fullpath, path, base, NULL); + if (error < 0) + goto cleanup; + + error = git_path_validate_str_length(repo, &fullpath); + if (error < 0) + goto cleanup; + + error = git_futils_readbuffer(&content, fullpath.ptr); + if (error < 0) + goto cleanup; + + error = mailmap_add_buffer(mm, content.ptr, content.size); + if (error < 0) + goto cleanup; + +cleanup: + git_str_dispose(&fullpath); + git_str_dispose(&content); + return error; +} + +/* NOTE: Only expose with an error return, currently never errors */ +static void mailmap_add_from_repository(git_mailmap *mm, git_repository *repo) +{ + git_config *config = NULL; + git_str rev_buf = GIT_STR_INIT; + git_str path_buf = GIT_STR_INIT; + const char *rev = NULL; + const char *path = NULL; + + /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */ + if (repo->is_bare) + rev = MM_BLOB_DEFAULT; + + /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */ + if (git_repository_config(&config, repo) == 0) { + if (git_config__get_string_buf(&rev_buf, config, MM_BLOB_CONFIG) == 0) + rev = rev_buf.ptr; + if (git_config__get_path(&path_buf, config, MM_FILE_CONFIG) == 0) + path = path_buf.ptr; + } + + /* + * Load mailmap files in order, overriding previous entries with new ones. + * 1. The '.mailmap' file in the repository's workdir root, + * 2. The blob described by the 'mailmap.blob' config (default HEAD:.mailmap), + * 3. The file described by the 'mailmap.file' config. + * + * We ignore errors from these loads, as these files may not exist, or may + * contain invalid information, and we don't want to report that error. + * + * XXX: Warn? + */ + if (!repo->is_bare) + mailmap_add_file_ondisk(mm, MM_FILE, repo); + if (rev != NULL) + mailmap_add_blob(mm, repo, rev); + if (path != NULL) + mailmap_add_file_ondisk(mm, path, repo); + + git_str_dispose(&rev_buf); + git_str_dispose(&path_buf); + git_config_free(config); +} + +int git_mailmap_from_repository(git_mailmap **out, git_repository *repo) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if ((error = git_mailmap_new(out)) < 0) + return error; + + mailmap_add_from_repository(*out, repo); + return 0; +} + +const git_mailmap_entry *git_mailmap_entry_lookup( + const git_mailmap *mm, const char *name, const char *email) +{ + int error; + ssize_t fallback = -1; + size_t idx; + git_mailmap_entry *entry; + + /* The lookup needle we want to use only sets the replace_email. */ + git_mailmap_entry needle = { NULL }; + needle.replace_email = (char *)email; + + GIT_ASSERT_ARG_WITH_RETVAL(email, NULL); + + if (!mm) + return NULL; + + /* + * We want to find the place to start looking. so we do a binary search for + * the "fallback" nameless entry. If we find it, we advance past it and record + * the index. + */ + error = git_vector_bsearch(&idx, (git_vector *)&mm->entries, &needle); + if (error >= 0) + fallback = idx++; + else if (error != GIT_ENOTFOUND) + return NULL; + + /* do a linear search for an exact match */ + for (; idx < git_vector_length(&mm->entries); ++idx) { + entry = git_vector_get(&mm->entries, idx); + + if (git__strcmp(entry->replace_email, email)) + break; /* it's a different email, so we're done looking */ + + /* should be specific */ + GIT_ASSERT_WITH_RETVAL(entry->replace_name, NULL); + if (!name || !git__strcmp(entry->replace_name, name)) + return entry; + } + + if (fallback < 0) + return NULL; /* no fallback */ + return git_vector_get(&mm->entries, fallback); +} + +int git_mailmap_resolve( + const char **real_name, const char **real_email, + const git_mailmap *mailmap, + const char *name, const char *email) +{ + const git_mailmap_entry *entry = NULL; + + GIT_ASSERT(name); + GIT_ASSERT(email); + + *real_name = name; + *real_email = email; + + if ((entry = git_mailmap_entry_lookup(mailmap, name, email))) { + if (entry->real_name) + *real_name = entry->real_name; + if (entry->real_email) + *real_email = entry->real_email; + } + return 0; +} + +int git_mailmap_resolve_signature( + git_signature **out, const git_mailmap *mailmap, const git_signature *sig) +{ + const char *name = NULL; + const char *email = NULL; + int error; + + if (!sig) + return 0; + + error = git_mailmap_resolve(&name, &email, mailmap, sig->name, sig->email); + if (error < 0) + return error; + + error = git_signature_new(out, name, email, sig->when.time, sig->when.offset); + if (error < 0) + return error; + + /* Copy over the sign, as git_signature_new doesn't let you pass it. */ + (*out)->when.sign = sig->when.sign; + return 0; +} diff --git a/src/libgit2/mailmap.h b/src/libgit2/mailmap.h new file mode 100644 index 000000000..2c9736a4a --- /dev/null +++ b/src/libgit2/mailmap.h @@ -0,0 +1,35 @@ +/* + * 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_mailmap_h__ +#define INCLUDE_mailmap_h__ + +#include "git2/mailmap.h" +#include "vector.h" + +/* + * A mailmap is stored as a sorted vector of 'git_mailmap_entry's. These entries + * are sorted first by 'replace_email', and then by 'replace_name'. NULL + * replace_names are ordered first. + * + * Looking up a name and email in the mailmap is done with a binary search. + */ +struct git_mailmap { + git_vector entries; +}; + +/* Single entry parsed from a mailmap */ +typedef struct git_mailmap_entry { + char *real_name; /**< the real name (may be NULL) */ + char *real_email; /**< the real email (may be NULL) */ + char *replace_name; /**< the name to replace (may be NULL) */ + char *replace_email; /**< the email to replace */ +} git_mailmap_entry; + +const git_mailmap_entry *git_mailmap_entry_lookup( + const git_mailmap *mm, const char *name, const char *email); + +#endif diff --git a/src/libgit2/merge.c b/src/libgit2/merge.c new file mode 100644 index 000000000..641b32632 --- /dev/null +++ b/src/libgit2/merge.c @@ -0,0 +1,3435 @@ +/* + * 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 "merge.h" + +#include "posix.h" +#include "str.h" +#include "repository.h" +#include "revwalk.h" +#include "commit_list.h" +#include "fs_path.h" +#include "refs.h" +#include "object.h" +#include "iterator.h" +#include "refs.h" +#include "diff.h" +#include "diff_generate.h" +#include "diff_tform.h" +#include "checkout.h" +#include "tree.h" +#include "blob.h" +#include "oid.h" +#include "index.h" +#include "filebuf.h" +#include "config.h" +#include "oidarray.h" +#include "annotated_commit.h" +#include "commit.h" +#include "oidarray.h" +#include "merge_driver.h" +#include "oidmap.h" +#include "array.h" + +#include "git2/types.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/commit.h" +#include "git2/merge.h" +#include "git2/refs.h" +#include "git2/reset.h" +#include "git2/checkout.h" +#include "git2/signature.h" +#include "git2/config.h" +#include "git2/tree.h" +#include "git2/oidarray.h" +#include "git2/annotated_commit.h" +#include "git2/sys/index.h" +#include "git2/sys/hashsig.h" + +#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) +#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) + + +typedef enum { + TREE_IDX_ANCESTOR = 0, + TREE_IDX_OURS = 1, + TREE_IDX_THEIRS = 2 +} merge_tree_index_t; + +/* Tracks D/F conflicts */ +struct merge_diff_df_data { + const char *df_path; + const char *prev_path; + 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 */ + +static int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk = NULL; + git_vector list; + git_commit_list *result = NULL; + git_commit_list_node *commit; + int error = -1; + unsigned int i; + + if (length < 2) { + git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); + return -1; + } + + if (git_vector_init(&list, length - 1, NULL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + goto on_error; + + for (i = 1; i < length; i++) { + commit = git_revwalk__commit_lookup(walk, &input_array[i]); + if (commit == NULL) + goto on_error; + + git_vector_insert(&list, commit); + } + + commit = git_revwalk__commit_lookup(walk, &input_array[0]); + if (commit == NULL) + goto on_error; + + if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) + goto on_error; + + if (!result) { + git_error_set(GIT_ERROR_MERGE, "no merge base found"); + error = GIT_ENOTFOUND; + goto on_error; + } + + *out = result; + *walk_out = walk; + + git_vector_free(&list); + return 0; + +on_error: + git_vector_free(&list); + git_revwalk_free(walk); + return error; +} + +int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk; + git_commit_list *result = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(input_array); + + if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) + return error; + + git_oid_cpy(out, &result->item->oid); + + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; +} + +int git_merge_bases_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk; + git_commit_list *list, *result = NULL; + int error = 0; + git_array_oid_t array; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(input_array); + + if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) + return error; + + git_array_init(array); + + list = result; + while (list) { + git_oid *id = git_array_alloc(array); + if (id == NULL) { + error = -1; + goto cleanup; + } + + git_oid_cpy(id, &list->item->oid); + list = list->next; + } + + git_oidarray__from_array(out, &array); + +cleanup: + git_commit_list_free(&result); + git_revwalk_free(walk); + + return error; +} + +int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_oid result; + unsigned int i; + int error = -1; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(input_array); + + if (length < 2) { + git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); + return -1; + } + + result = input_array[0]; + for (i = 1; i < length; i++) { + error = git_merge_base(&result, repo, &result, &input_array[i]); + if (error < 0) + return error; + } + + *out = result; + + return 0; +} + +static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two) +{ + git_revwalk *walk; + git_vector list; + git_commit_list *result = NULL; + git_commit_list_node *commit; + void *contents[1]; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + commit = git_revwalk__commit_lookup(walk, two); + if (commit == NULL) + goto on_error; + + /* This is just one value, so we can do it on the stack */ + memset(&list, 0x0, sizeof(git_vector)); + contents[0] = commit; + list.length = 1; + list.contents = contents; + + commit = git_revwalk__commit_lookup(walk, one); + if (commit == NULL) + goto on_error; + + if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) + goto on_error; + + if (!result) { + git_revwalk_free(walk); + git_error_set(GIT_ERROR_MERGE, "no merge base found"); + return GIT_ENOTFOUND; + } + + *out = result; + *walk_out = walk; + + return 0; + +on_error: + git_revwalk_free(walk); + return -1; + +} + +int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two) +{ + int error; + git_revwalk *walk; + git_commit_list *result; + + if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) + return error; + + git_oid_cpy(out, &result->item->oid); + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; +} + +int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two) +{ + int error; + git_revwalk *walk; + git_commit_list *result, *list; + git_array_oid_t array; + + git_array_init(array); + + if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) + return error; + + list = result; + while (list) { + git_oid *id = git_array_alloc(array); + if (id == NULL) + goto on_error; + + git_oid_cpy(id, &list->item->oid); + list = list->next; + } + + git_oidarray__from_array(out, &array); + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; + +on_error: + git_commit_list_free(&result); + git_revwalk_free(walk); + return -1; +} + +static int interesting(git_pqueue *list) +{ + size_t i; + + for (i = 0; i < git_pqueue_size(list); i++) { + git_commit_list_node *commit = git_pqueue_get(list, i); + if ((commit->flags & STALE) == 0) + return 1; + } + + return 0; +} + +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 0; + + commit->flags &= ~mark; + + for (i = 1; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if (git_commit_list_insert(p, plist) == NULL) + return -1; + } + + commit = commit->out_degree ? commit->parents[0] : NULL; + } + + return 0; +} + +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) { + if (git_commit_list_insert(c, &list) == NULL) + return -1; + } + + while (list) + if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) + return -1; + return 0; +} + +static int clear_commit_marks(git_commit_list_node *commit, unsigned int mark) +{ + git_commit_list *list = NULL; + if (git_commit_list_insert(commit, &list) == NULL) + return -1; + while (list) + if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) + return -1; + return 0; +} + +static int paint_down_to_common( + git_commit_list **out, + git_revwalk *walk, + git_commit_list_node *one, + git_vector *twos, + uint32_t minimum_generation) +{ + git_pqueue list; + git_commit_list *result = NULL; + git_commit_list_node *two; + + int error; + unsigned int i; + + if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_generation_cmp) < 0) + return -1; + + one->flags |= PARENT1; + if (git_pqueue_insert(&list, one) < 0) + return -1; + + git_vector_foreach(twos, i, two) { + if (git_commit_list_parse(walk, two) < 0) + return -1; + + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) + return -1; + } + + /* as long as there are non-STALE commits */ + while (interesting(&list)) { + git_commit_list_node *commit = git_pqueue_pop(&list); + int flags; + + if (commit == NULL) + break; + + flags = commit->flags & (PARENT1 | PARENT2 | STALE); + if (flags == (PARENT1 | PARENT2)) { + if (!(commit->flags & RESULT)) { + commit->flags |= RESULT; + if (git_commit_list_insert(commit, &result) == NULL) + return -1; + } + /* we mark the parents of a merge stale */ + flags |= STALE; + } + + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if ((p->flags & flags) == flags) + continue; + if (p->generation < minimum_generation) + continue; + + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + p->flags |= flags; + if (git_pqueue_insert(&list, p) < 0) + return -1; + } + } + + git_pqueue_free(&list); + *out = result; + return 0; +} + +static int remove_redundant(git_revwalk *walk, git_vector *commits, uint32_t minimum_generation) +{ + git_vector work = GIT_VECTOR_INIT; + unsigned char *redundant; + unsigned int *filled_index; + unsigned int i, j; + int error = 0; + + redundant = git__calloc(commits->length, 1); + GIT_ERROR_CHECK_ALLOC(redundant); + filled_index = git__calloc((commits->length - 1), sizeof(unsigned int)); + GIT_ERROR_CHECK_ALLOC(filled_index); + + for (i = 0; i < commits->length; ++i) { + if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0) + goto done; + } + + for (i = 0; i < commits->length; ++i) { + git_commit_list *common = NULL; + git_commit_list_node *commit = commits->contents[i]; + + if (redundant[i]) + continue; + + git_vector_clear(&work); + + for (j = 0; j < commits->length; j++) { + if (i == j || redundant[j]) + continue; + + filled_index[work.length] = j; + if ((error = git_vector_insert(&work, commits->contents[j])) < 0) + goto done; + } + + error = paint_down_to_common(&common, walk, commit, &work, minimum_generation); + if (error < 0) + goto done; + + if (commit->flags & PARENT2) + redundant[i] = 1; + + for (j = 0; j < work.length; j++) { + git_commit_list_node *w = work.contents[j]; + if (w->flags & PARENT1) + redundant[filled_index[j]] = 1; + } + + 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) { + if (redundant[i]) + commits->contents[i] = NULL; + } + +done: + git__free(redundant); + git__free(filled_index); + git_vector_free(&work); + return error; +} + +int git_merge__bases_many( + git_commit_list **out, + git_revwalk *walk, + git_commit_list_node *one, + git_vector *twos, + uint32_t minimum_generation) +{ + int error; + unsigned int i; + git_commit_list_node *two; + git_commit_list *result = NULL, *tmp = NULL; + + /* If there's only the one commit, there can be no merge bases */ + if (twos->length == 0) { + *out = NULL; + return 0; + } + + /* if the commit is repeated, we have a our merge base already */ + git_vector_foreach(twos, i, two) { + if (one == two) + return git_commit_list_insert(one, out) ? 0 : -1; + } + + if (git_commit_list_parse(walk, one) < 0) + return -1; + + error = paint_down_to_common(&result, walk, one, twos, minimum_generation); + if (error < 0) + return error; + + /* filter out any stale commits in the results */ + tmp = result; + result = NULL; + + while (tmp) { + git_commit_list_node *c = git_commit_list_pop(&tmp); + if (!(c->flags & STALE)) + if (git_commit_list_insert_by_date(c, &result) == NULL) + return -1; + } + + /* + * more than one merge base -- see if there are redundant merge + * bases and remove them + */ + if (result && result->next) { + git_vector redundant = GIT_VECTOR_INIT; + + while (result) + git_vector_insert(&redundant, git_commit_list_pop(&result)); + + if ((error = clear_commit_marks(one, ALL_FLAGS)) < 0 || + (error = clear_commit_marks_many(twos, ALL_FLAGS)) < 0 || + (error = remove_redundant(walk, &redundant, minimum_generation)) < 0) { + git_vector_free(&redundant); + return error; + } + + git_vector_foreach(&redundant, i, two) { + if (two != NULL) + git_commit_list_insert_by_date(two, &result); + } + + git_vector_free(&redundant); + } + + *out = result; + return 0; +} + +int git_repository_mergehead_foreach( + git_repository *repo, + git_repository_mergehead_foreach_cb cb, + void *payload) +{ + git_str merge_head_path = GIT_STR_INIT, merge_head_file = GIT_STR_INIT; + char *buffer, *line; + size_t line_num = 1; + git_oid oid; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cb); + + if ((error = git_str_joinpath(&merge_head_path, repo->gitdir, + GIT_MERGE_HEAD_FILE)) < 0) + return error; + + if ((error = git_futils_readbuffer(&merge_head_file, + git_str_cstr(&merge_head_path))) < 0) + goto cleanup; + + buffer = merge_head_file.ptr; + + while ((line = git__strsep(&buffer, "\n")) != NULL) { + if (strlen(line) != GIT_OID_HEXSZ) { + git_error_set(GIT_ERROR_INVALID, "unable to parse OID - invalid length"); + error = -1; + goto cleanup; + } + + if ((error = git_oid_fromstr(&oid, line)) < 0) + goto cleanup; + + if ((error = cb(&oid, payload)) != 0) { + git_error_set_after_callback(error); + goto cleanup; + } + + ++line_num; + } + + if (*buffer) { + git_error_set(GIT_ERROR_MERGE, "no EOL at line %"PRIuZ, line_num); + error = -1; + goto cleanup; + } + +cleanup: + git_str_dispose(&merge_head_path); + git_str_dispose(&merge_head_file); + + return error; +} + +GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b) +{ + int value = 0; + + if (a->path == NULL) + return (b->path == NULL) ? 0 : 1; + + if ((value = a->mode - b->mode) == 0 && + (value = git_oid__cmp(&a->id, &b->id)) == 0) + value = strcmp(a->path, b->path); + + return value; +} + +/* Conflict resolution */ + +static int merge_conflict_resolve_trivial( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict) +{ + int ours_empty, theirs_empty; + int ours_changed, theirs_changed, ours_theirs_differ; + git_index_entry const *result = NULL; + int error = 0; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return 0; + + if (conflict->our_status == GIT_DELTA_RENAMED || + conflict->their_status == GIT_DELTA_RENAMED) + return 0; + + ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); + theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); + + ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); + theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); + ours_theirs_differ = ours_changed && theirs_changed && + index_entry_cmp(&conflict->our_entry, &conflict->their_entry); + + /* + * Note: with only one ancestor, some cases are not distinct: + * + * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge + * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge + * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge + * + * Note that the two cases that take D/F conflicts into account + * specifically do not need to be explicitly tested, as D/F conflicts + * would fail the *empty* test: + * + * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head + * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote + * + * Note that many of these cases need not be explicitly tested, as + * they simply degrade to "all different" cases (eg, 11): + * + * 4: ancest:(empty)^, head:head, remote:remote = result:no merge + * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge + * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge + * 11: ancest:ancest+, head:head, remote:remote = result:no merge + */ + + /* 5ALT: ancest:*, head:head, remote:head = result:head */ + if (ours_changed && !ours_empty && !ours_theirs_differ) + result = &conflict->our_entry; + /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ + else if (ours_changed && ours_empty && theirs_empty) + *resolved = 0; + /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ + else if (ours_empty && !theirs_changed) + *resolved = 0; + /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ + else if (!ours_changed && theirs_empty) + *resolved = 0; + /* 13: ancest:ancest+, head:head, remote:ancest = result:head */ + else if (ours_changed && !theirs_changed) + result = &conflict->our_entry; + /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ + else if (!ours_changed && theirs_changed) + result = &conflict->their_entry; + else + *resolved = 0; + + if (result != NULL && + GIT_MERGE_INDEX_ENTRY_EXISTS(*result) && + (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0) + *resolved = 1; + + /* Note: trivial resolution does not update the REUC. */ + + return error; +} + +static int merge_conflict_resolve_one_removed( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict) +{ + int ours_empty, theirs_empty; + int ours_changed, theirs_changed; + int error = 0; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return 0; + + ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); + theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); + + ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); + theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); + + /* Removed in both */ + if (ours_changed && ours_empty && theirs_empty) + *resolved = 1; + /* Removed in ours */ + else if (ours_empty && !theirs_changed) + *resolved = 1; + /* Removed in theirs */ + else if (!ours_changed && theirs_empty) + *resolved = 1; + + if (*resolved) + git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); + + return error; +} + +static int merge_conflict_resolve_one_renamed( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict) +{ + int ours_renamed, theirs_renamed; + int ours_changed, theirs_changed; + git_index_entry *merged; + int error = 0; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) + return 0; + + ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED); + theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED); + + if (!ours_renamed && !theirs_renamed) + return 0; + + /* Reject one file in a 2->1 conflict */ + if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || + conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return 0; + + ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0) || + (conflict->ancestor_entry.mode != conflict->our_entry.mode); + + theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0) || + (conflict->ancestor_entry.mode != conflict->their_entry.mode); + + /* if both are modified (and not to a common target) require a merge */ + if (ours_changed && theirs_changed && + git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0) + return 0; + + if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL) + return -1; + + if (ours_changed) + memcpy(merged, &conflict->our_entry, sizeof(git_index_entry)); + else + memcpy(merged, &conflict->their_entry, sizeof(git_index_entry)); + + if (ours_renamed) + merged->path = conflict->our_entry.path; + else + merged->path = conflict->their_entry.path; + + *resolved = 1; + + git_vector_insert(&diff_list->staged, merged); + git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); + + return error; +} + +static bool merge_conflict_can_resolve_contents( + const git_merge_diff *conflict) +{ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) + return false; + + /* Reject D/F conflicts */ + if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) + return false; + + /* Reject submodules. */ + if (S_ISGITLINK(conflict->ancestor_entry.mode) || + S_ISGITLINK(conflict->our_entry.mode) || + S_ISGITLINK(conflict->their_entry.mode)) + return false; + + /* Reject link/file conflicts. */ + if ((S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->our_entry.mode)) || + (S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->their_entry.mode))) + return false; + + /* Reject name conflicts */ + if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return false; + + if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && + (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && + strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) + return false; + + return true; +} + +static int merge_conflict_invoke_driver( + git_index_entry **out, + const char *name, + git_merge_driver *driver, + git_merge_diff_list *diff_list, + git_merge_driver_source *src) +{ + git_index_entry *result; + git_buf buf = {0}; + const char *path; + uint32_t mode; + git_odb *odb = NULL; + git_oid oid; + int error; + + *out = NULL; + + if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || + (error = git_repository_odb(&odb, src->repo)) < 0 || + (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJECT_BLOB)) < 0) + goto done; + + result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); + GIT_ERROR_CHECK_ALLOC(result); + + git_oid_cpy(&result->id, &oid); + result->mode = mode; + result->file_size = (uint32_t)buf.size; + + result->path = git_pool_strdup(&diff_list->pool, path); + GIT_ERROR_CHECK_ALLOC(result->path); + + *out = result; + +done: + git_buf_dispose(&buf); + git_odb_free(odb); + + return error; +} + +static int merge_conflict_resolve_contents( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict, + const git_merge_options *merge_opts, + const git_merge_file_options *file_opts) +{ + git_merge_driver_source source = {0}; + git_merge_file_result result = {0}; + git_merge_driver *driver; + git_merge_driver__builtin builtin = {{0}}; + git_index_entry *merge_result; + git_odb *odb = NULL; + const char *name; + bool fallback = false; + int error; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (!merge_conflict_can_resolve_contents(conflict)) + return 0; + + source.repo = diff_list->repo; + source.default_driver = merge_opts->default_driver; + source.file_opts = file_opts; + source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { + /* if the user requested a particular type of resolution (via the + * favor flag) then let that override the gitattributes and use + * the builtin driver. + */ + name = "text"; + builtin.base.apply = git_merge_driver__builtin_apply; + builtin.favor = file_opts->favor; + + driver = &builtin.base; + } else { + /* find the merge driver for this file */ + if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) + goto done; + + if (driver == NULL) + fallback = true; + } + + if (driver) { + error = merge_conflict_invoke_driver(&merge_result, name, driver, + diff_list, &source); + + if (error == GIT_PASSTHROUGH) + fallback = true; + } + + if (fallback) { + error = merge_conflict_invoke_driver(&merge_result, "text", + &git_merge_driver__text.base, diff_list, &source); + } + + if (error < 0) { + if (error == GIT_EMERGECONFLICT) + error = 0; + + goto done; + } + + git_vector_insert(&diff_list->staged, merge_result); + git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); + + *resolved = 1; + +done: + git_merge_file_result_free(&result); + git_odb_free(odb); + + return error; +} + +static int merge_conflict_resolve( + int *out, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict, + const git_merge_options *merge_opts, + const git_merge_file_options *file_opts) +{ + int resolved = 0; + int error = 0; + + *out = 0; + + if ((error = merge_conflict_resolve_trivial( + &resolved, diff_list, conflict)) < 0) + goto done; + + if (!resolved && (error = merge_conflict_resolve_one_removed( + &resolved, diff_list, conflict)) < 0) + goto done; + + if (!resolved && (error = merge_conflict_resolve_one_renamed( + &resolved, diff_list, conflict)) < 0) + goto done; + + if (!resolved && (error = merge_conflict_resolve_contents( + &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) + goto done; + + *out = resolved; + +done: + return error; +} + +/* Rename detection and coalescing */ + +struct merge_diff_similarity { + unsigned char similarity; + size_t other_idx; +}; + +static int index_entry_similarity_calc( + void **out, + git_repository *repo, + git_index_entry *entry, + const git_merge_options *opts) +{ + git_blob *blob; + git_diff_file diff_file = {{{0}}}; + 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) + return error; + + git_oid_cpy(&diff_file.id, &entry->id); + diff_file.path = entry->path; + diff_file.size = entry->file_size; + diff_file.mode = entry->mode; + diff_file.flags = 0; + + blobsize = git_blob_rawsize(blob); + + /* file too big for rename processing */ + if (!git__is_sizet(blobsize)) + return 0; + + 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); + + return error; +} + +static int index_entry_similarity_inexact( + git_repository *repo, + git_index_entry *a, + size_t a_idx, + git_index_entry *b, + size_t b_idx, + void **cache, + const git_merge_options *opts) +{ + int score = 0; + int error = 0; + + if (!GIT_MODE_ISBLOB(a->mode) || !GIT_MODE_ISBLOB(b->mode)) + return 0; + + /* update signature cache if needed */ + 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_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) + return -1; + + /* clip score */ + if (score < 0) + score = 0; + else if (score > 100) + score = 100; + + return score; +} + +/* Tracks deletes by oid for merge_diff_mark_similarity_exact(). This is a +* non-shrinking queue where next_pos is the next position to dequeue. +*/ +typedef struct { + git_array_t(size_t) arr; + size_t next_pos; + size_t first_entry; +} deletes_by_oid_queue; + +static void deletes_by_oid_free(git_oidmap *map) { + deletes_by_oid_queue *queue; + + if (!map) + return; + + git_oidmap_foreach_value(map, queue, { + git_array_clear(queue->arr); + }); + git_oidmap_free(map); +} + +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; + + if ((queue = git_oidmap_get(map, id)) == NULL) { + queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); + GIT_ERROR_CHECK_ALLOC(queue); + + git_array_init(queue->arr); + queue->next_pos = 0; + queue->first_entry = idx; + + if (git_oidmap_set(map, id, queue) < 0) + return -1; + } else { + array_entry = git_array_alloc(queue->arr); + GIT_ERROR_CHECK_ALLOC(array_entry); + *array_entry = idx; + } + + return 0; +} + +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; + + if ((queue = git_oidmap_get(map, id)) == NULL) + return GIT_ENOTFOUND; + + if (queue->next_pos == 0) { + *idx = queue->first_entry; + } else { + array_entry = git_array_get(queue->arr, queue->next_pos - 1); + if (array_entry == NULL) + return GIT_ENOTFOUND; + + *idx = *array_entry; + } + + queue->next_pos++; + return 0; +} + +static int merge_diff_mark_similarity_exact( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs) +{ + size_t i, j; + git_merge_diff *conflict_src, *conflict_tgt; + git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; + int error = 0; + + if (git_oidmap_new(&ours_deletes_by_oid) < 0 || + git_oidmap_new(&theirs_deletes_by_oid) < 0) { + error = -1; + goto done; + } + + /* Build a map of object ids to conflicts */ + git_vector_foreach(&diff_list->conflicts, i, conflict_src) { + /* Items can be the source of a rename iff they have an item in the + * ancestor slot and lack an item in the ours or theirs slot. */ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) + continue; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { + error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + if (error < 0) + goto done; + } + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { + error = deletes_by_oid_enqueue(theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + if (error < 0) + goto done; + } + } + + git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) + continue; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry)) { + if (deletes_by_oid_dequeue(&i, ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { + similarity_ours[i].similarity = 100; + similarity_ours[i].other_idx = j; + + similarity_ours[j].similarity = 100; + similarity_ours[j].other_idx = i; + } + } + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry)) { + if (deletes_by_oid_dequeue(&i, theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { + similarity_theirs[i].similarity = 100; + similarity_theirs[i].other_idx = j; + + similarity_theirs[j].similarity = 100; + similarity_theirs[j].other_idx = i; + } + } + } + +done: + deletes_by_oid_free(ours_deletes_by_oid); + deletes_by_oid_free(theirs_deletes_by_oid); + + return error; +} + +static int merge_diff_mark_similarity_inexact( + git_repository *repo, + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs, + void **cache, + const git_merge_options *opts) +{ + size_t i, j; + git_merge_diff *conflict_src, *conflict_tgt; + int similarity; + + git_vector_foreach(&diff_list->conflicts, i, conflict_src) { + /* Items can be the source of a rename iff they have an item in the + * ancestor slot and lack an item in the ours or theirs slot. */ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) || + (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) && + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry))) + continue; + + git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { + size_t our_idx = diff_list->conflicts.length + j; + size_t their_idx = (diff_list->conflicts.length * 2) + j; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) + continue; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { + similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); + + if (similarity == GIT_EBUFS) + continue; + else if (similarity < 0) + return similarity; + + if (similarity > similarity_ours[i].similarity && + similarity > similarity_ours[j].similarity) { + /* Clear previous best similarity */ + if (similarity_ours[i].similarity > 0) + similarity_ours[similarity_ours[i].other_idx].similarity = 0; + + if (similarity_ours[j].similarity > 0) + similarity_ours[similarity_ours[j].other_idx].similarity = 0; + + similarity_ours[i].similarity = similarity; + similarity_ours[i].other_idx = j; + + similarity_ours[j].similarity = similarity; + similarity_ours[j].other_idx = i; + } + } + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { + similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); + + if (similarity > similarity_theirs[i].similarity && + similarity > similarity_theirs[j].similarity) { + /* Clear previous best similarity */ + if (similarity_theirs[i].similarity > 0) + similarity_theirs[similarity_theirs[i].other_idx].similarity = 0; + + if (similarity_theirs[j].similarity > 0) + similarity_theirs[similarity_theirs[j].other_idx].similarity = 0; + + similarity_theirs[i].similarity = similarity; + similarity_theirs[i].other_idx = j; + + similarity_theirs[j].similarity = similarity; + similarity_theirs[j].other_idx = i; + } + } + } + } + + return 0; +} + +/* + * Rename conflicts: + * + * Ancestor Ours Theirs + * + * 0a A A A No rename + * b A A* A No rename (ours was rewritten) + * c A A A* No rename (theirs rewritten) + * 1a A A B[A] Rename or rename/edit + * b A B[A] A (automergeable) + * 2 A B[A] B[A] Both renamed (automergeable) + * 3a A B[A] Rename/delete + * b A B[A] (same) + * 4a A B[A] B Rename/add [B~ours B~theirs] + * b A B B[A] (same) + * 5 A B[A] C[A] Both renamed ("1 -> 2") + * 6 A C[A] Both renamed ("2 -> 1") + * B C[B] [C~ours C~theirs] (automergeable) + */ +static void merge_diff_mark_rename_conflict( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + bool ours_renamed, + size_t ours_source_idx, + struct merge_diff_similarity *similarity_theirs, + bool theirs_renamed, + size_t theirs_source_idx, + git_merge_diff *target, + const git_merge_options *opts) +{ + git_merge_diff *ours_source = NULL, *theirs_source = NULL; + + if (ours_renamed) + ours_source = diff_list->conflicts.contents[ours_source_idx]; + + if (theirs_renamed) + theirs_source = diff_list->conflicts.contents[theirs_source_idx]; + + /* Detect 2->1 conflicts */ + if (ours_renamed && theirs_renamed) { + /* Both renamed to the same target name. */ + if (ours_source_idx == theirs_source_idx) + ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED; + else { + ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; + theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; + } + } else if (ours_renamed) { + /* If our source was also renamed in theirs, this is a 1->2 */ + if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold) + ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; + + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) { + ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; + target->type = GIT_MERGE_DIFF_RENAMED_ADDED; + } + + else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry)) + ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; + + else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) + ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; + } else if (theirs_renamed) { + /* If their source was also renamed in ours, this is a 1->2 */ + if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold) + theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; + + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) { + theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; + target->type = GIT_MERGE_DIFF_RENAMED_ADDED; + } + + else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry)) + theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; + + else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) + theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; + } +} + +GIT_INLINE(void) merge_diff_coalesce_rename( + git_index_entry *source_entry, + git_delta_t *source_status, + git_index_entry *target_entry, + git_delta_t *target_status) +{ + /* Coalesce the rename target into the rename source. */ + memcpy(source_entry, target_entry, sizeof(git_index_entry)); + *source_status = GIT_DELTA_RENAMED; + + memset(target_entry, 0x0, sizeof(git_index_entry)); + *target_status = GIT_DELTA_UNMODIFIED; +} + +static void merge_diff_list_coalesce_renames( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs, + const git_merge_options *opts) +{ + size_t i; + bool ours_renamed = 0, theirs_renamed = 0; + size_t ours_source_idx = 0, theirs_source_idx = 0; + git_merge_diff *ours_source, *theirs_source, *target; + + for (i = 0; i < diff_list->conflicts.length; i++) { + target = diff_list->conflicts.contents[i]; + + ours_renamed = 0; + theirs_renamed = 0; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) && + similarity_ours[i].similarity >= opts->rename_threshold) { + ours_source_idx = similarity_ours[i].other_idx; + + ours_source = diff_list->conflicts.contents[ours_source_idx]; + + merge_diff_coalesce_rename( + &ours_source->our_entry, + &ours_source->our_status, + &target->our_entry, + &target->our_status); + + similarity_ours[ours_source_idx].similarity = 0; + similarity_ours[i].similarity = 0; + + ours_renamed = 1; + } + + /* insufficient to determine direction */ + if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) && + similarity_theirs[i].similarity >= opts->rename_threshold) { + theirs_source_idx = similarity_theirs[i].other_idx; + + theirs_source = diff_list->conflicts.contents[theirs_source_idx]; + + merge_diff_coalesce_rename( + &theirs_source->their_entry, + &theirs_source->their_status, + &target->their_entry, + &target->their_status); + + similarity_theirs[theirs_source_idx].similarity = 0; + similarity_theirs[i].similarity = 0; + + theirs_renamed = 1; + } + + merge_diff_mark_rename_conflict(diff_list, + similarity_ours, ours_renamed, ours_source_idx, + similarity_theirs, theirs_renamed, theirs_source_idx, + target, opts); + } +} + +static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p) +{ + git_merge_diff *conflict = conflicts->contents[idx]; + + GIT_UNUSED(p); + + return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)); +} + +static void merge_diff_list_count_candidates( + git_merge_diff_list *diff_list, + size_t *src_count, + size_t *tgt_count) +{ + git_merge_diff *entry; + size_t i; + + *src_count = 0; + *tgt_count = 0; + + git_vector_foreach(&diff_list->conflicts, i, entry) { + if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) && + (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) || + !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry))) + (*src_count)++; + else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry)) + (*tgt_count)++; + } +} + +int git_merge_diff_list__find_renames( + git_repository *repo, + git_merge_diff_list *diff_list, + const git_merge_options *opts) +{ + struct merge_diff_similarity *similarity_ours, *similarity_theirs; + void **cache = NULL; + size_t cache_size = 0; + size_t src_count, tgt_count, i; + int error = 0; + + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(opts); + + if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0 || + !diff_list->conflicts.length) + return 0; + + similarity_ours = git__calloc(diff_list->conflicts.length, + sizeof(struct merge_diff_similarity)); + GIT_ERROR_CHECK_ALLOC(similarity_ours); + + similarity_theirs = git__calloc(diff_list->conflicts.length, + sizeof(struct merge_diff_similarity)); + GIT_ERROR_CHECK_ALLOC(similarity_theirs); + + /* Calculate similarity between items that were deleted from the ancestor + * and added in the other branch. + */ + if ((error = merge_diff_mark_similarity_exact(diff_list, similarity_ours, similarity_theirs)) < 0) + goto done; + + if (opts->rename_threshold < 100 && diff_list->conflicts.length <= opts->target_limit) { + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3); + cache = git__calloc(cache_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(cache); + + merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count); + + if (src_count > opts->target_limit || tgt_count > opts->target_limit) { + /* TODO: report! */ + } else { + if ((error = merge_diff_mark_similarity_inexact( + repo, diff_list, similarity_ours, similarity_theirs, cache, opts)) < 0) + goto done; + } + } + + /* For entries that are appropriately similar, merge the new name's entry + * into the old name. + */ + merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts); + + /* And remove any entries that were merged and are now empty. */ + git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL); + +done: + if (cache != NULL) { + for (i = 0; i < cache_size; ++i) { + if (cache[i] != NULL && cache[i] != &cache_invalid_marker) + opts->metric->free_signature(cache[i], opts->metric->payload); + } + + git__free(cache); + } + + git__free(similarity_ours); + git__free(similarity_theirs); + + return error; +} + +/* Directory/file conflict handling */ + +GIT_INLINE(const char *) merge_diff_path( + const git_merge_diff *conflict) +{ + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) + return conflict->ancestor_entry.path; + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry)) + return conflict->our_entry.path; + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) + return conflict->their_entry.path; + + return NULL; +} + +GIT_INLINE(bool) merge_diff_any_side_added_or_modified( + const git_merge_diff *conflict) +{ + if (conflict->our_status == GIT_DELTA_ADDED || + conflict->our_status == GIT_DELTA_MODIFIED || + conflict->their_status == GIT_DELTA_ADDED || + conflict->their_status == GIT_DELTA_MODIFIED) + return true; + + return false; +} + +GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child) +{ + size_t child_len = strlen(child); + size_t parent_len = strlen(parent); + + if (child_len < parent_len || + strncmp(parent, child, parent_len) != 0) + return 0; + + return (child[parent_len] == '/'); +} + +GIT_INLINE(int) merge_diff_detect_df_conflict( + struct merge_diff_df_data *df_data, + git_merge_diff *conflict) +{ + const char *cur_path = merge_diff_path(conflict); + + /* Determine if this is a D/F conflict or the child of one */ + if (df_data->df_path && + path_is_prefixed(df_data->df_path, cur_path)) + conflict->type = GIT_MERGE_DIFF_DF_CHILD; + else if(df_data->df_path) + df_data->df_path = NULL; + else if (df_data->prev_path && + merge_diff_any_side_added_or_modified(df_data->prev_conflict) && + merge_diff_any_side_added_or_modified(conflict) && + path_is_prefixed(df_data->prev_path, cur_path)) { + conflict->type = GIT_MERGE_DIFF_DF_CHILD; + + df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE; + df_data->df_path = df_data->prev_path; + } + + df_data->prev_path = cur_path; + df_data->prev_conflict = conflict; + + return 0; +} + +/* Conflict handling */ + +GIT_INLINE(int) merge_diff_detect_type( + git_merge_diff *conflict) +{ + if (conflict->our_status == GIT_DELTA_ADDED && + conflict->their_status == GIT_DELTA_ADDED) + conflict->type = GIT_MERGE_DIFF_BOTH_ADDED; + else if (conflict->our_status == GIT_DELTA_MODIFIED && + conflict->their_status == GIT_DELTA_MODIFIED) + conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED; + else if (conflict->our_status == GIT_DELTA_DELETED && + conflict->their_status == GIT_DELTA_DELETED) + conflict->type = GIT_MERGE_DIFF_BOTH_DELETED; + else if (conflict->our_status == GIT_DELTA_MODIFIED && + conflict->their_status == GIT_DELTA_DELETED) + conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; + else if (conflict->our_status == GIT_DELTA_DELETED && + conflict->their_status == GIT_DELTA_MODIFIED) + conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; + else + conflict->type = GIT_MERGE_DIFF_NONE; + + return 0; +} + +GIT_INLINE(int) index_entry_dup_pool( + git_index_entry *out, + git_pool *pool, + const git_index_entry *src) +{ + if (src != NULL) { + memcpy(out, src, sizeof(git_index_entry)); + if ((out->path = git_pool_strdup(pool, src->path)) == NULL) + return -1; + } + + return 0; +} + +GIT_INLINE(int) merge_delta_type_from_index_entries( + const git_index_entry *ancestor, + const git_index_entry *other) +{ + if (ancestor == NULL && other == NULL) + return GIT_DELTA_UNMODIFIED; + else if (ancestor == NULL && other != NULL) + return GIT_DELTA_ADDED; + else if (ancestor != NULL && other == NULL) + return GIT_DELTA_DELETED; + else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode)) + return GIT_DELTA_TYPECHANGE; + else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode)) + return GIT_DELTA_TYPECHANGE; + else if (git_oid__cmp(&ancestor->id, &other->id) || + ancestor->mode != other->mode) + return GIT_DELTA_MODIFIED; + + return GIT_DELTA_UNMODIFIED; +} + +static git_merge_diff *merge_diff_from_index_entries( + git_merge_diff_list *diff_list, + const git_index_entry **entries) +{ + git_merge_diff *conflict; + git_pool *pool = &diff_list->pool; + + if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL) + return NULL; + + if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 || + index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 || + index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0) + return NULL; + + conflict->our_status = merge_delta_type_from_index_entries( + entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]); + conflict->their_status = merge_delta_type_from_index_entries( + entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]); + + return conflict; +} + +/* Merge trees */ + +static int merge_diff_list_insert_conflict( + git_merge_diff_list *diff_list, + struct merge_diff_df_data *merge_df_data, + const git_index_entry *tree_items[3]) +{ + git_merge_diff *conflict; + + if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL || + merge_diff_detect_type(conflict) < 0 || + merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 || + git_vector_insert(&diff_list->conflicts, conflict) < 0) + return -1; + + return 0; +} + +static int merge_diff_list_insert_unmodified( + git_merge_diff_list *diff_list, + const git_index_entry *tree_items[3]) +{ + int error = 0; + git_index_entry *entry; + + entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0) + error = git_vector_insert(&diff_list->staged, entry); + + return error; +} + +struct merge_diff_find_data { + git_merge_diff_list *diff_list; + struct merge_diff_df_data df_data; +}; + +static int queue_difference(const git_index_entry **entries, void *data) +{ + struct merge_diff_find_data *find_data = data; + bool item_modified = false; + size_t i; + + if (!entries[0] || !entries[1] || !entries[2]) { + item_modified = true; + } else { + for (i = 1; i < 3; i++) { + if (index_entry_cmp(entries[0], entries[i]) != 0) { + item_modified = true; + break; + } + } + } + + return item_modified ? + merge_diff_list_insert_conflict( + find_data->diff_list, &find_data->df_data, entries) : + merge_diff_list_insert_unmodified(find_data->diff_list, entries); +} + +int git_merge_diff_list__find_differences( + git_merge_diff_list *diff_list, + git_iterator *ancestor_iter, + git_iterator *our_iter, + git_iterator *their_iter) +{ + git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter }; + struct merge_diff_find_data find_data = { diff_list }; + + return git_iterator_walk(iterators, 3, queue_difference, &find_data); +} + +git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) +{ + git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list)); + + if (diff_list == NULL) + return NULL; + + diff_list->repo = repo; + + + if (git_pool_init(&diff_list->pool, 1) < 0 || + git_vector_init(&diff_list->staged, 0, NULL) < 0 || + git_vector_init(&diff_list->conflicts, 0, NULL) < 0 || + git_vector_init(&diff_list->resolved, 0, NULL) < 0) { + git_merge_diff_list__free(diff_list); + return NULL; + } + + return diff_list; +} + +void git_merge_diff_list__free(git_merge_diff_list *diff_list) +{ + if (!diff_list) + return; + + git_vector_free(&diff_list->staged); + git_vector_free(&diff_list->conflicts); + git_vector_free(&diff_list->resolved); + git_pool_clear(&diff_list->pool); + git__free(diff_list); +} + +static int merge_normalize_opts( + git_repository *repo, + git_merge_options *opts, + const git_merge_options *given) +{ + git_config *cfg = NULL; + git_config_entry *entry = NULL; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(opts); + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if (given != NULL) { + memcpy(opts, given, sizeof(git_merge_options)); + } else { + git_merge_options init = GIT_MERGE_OPTIONS_INIT; + memcpy(opts, &init, sizeof(init)); + } + + if ((opts->flags & GIT_MERGE_FIND_RENAMES) && !opts->rename_threshold) + opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; + + if (given && given->default_driver) { + opts->default_driver = git__strdup(given->default_driver); + GIT_ERROR_CHECK_ALLOC(opts->default_driver); + } else { + error = git_config_get_entry(&entry, cfg, "merge.default"); + + if (error == 0) { + opts->default_driver = git__strdup(entry->value); + GIT_ERROR_CHECK_ALLOC(opts->default_driver); + } else if (error == GIT_ENOTFOUND) { + error = 0; + } else { + goto done; + } + } + + if (!opts->target_limit) { + int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); + + if (!limit) + limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); + + opts->target_limit = (limit <= 0) ? + GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit; + } + + /* assign the internal metric with whitespace flag as payload */ + if (!opts->metric) { + opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); + GIT_ERROR_CHECK_ALLOC(opts->metric); + + opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; + opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; + opts->metric->free_signature = git_diff_find_similar__hashsig_free; + opts->metric->similarity = git_diff_find_similar__calc_similarity; + opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; + } + +done: + git_config_entry_free(entry); + return error; +} + + +static int merge_index_insert_reuc( + git_index *index, + size_t idx, + const git_index_entry *entry) +{ + const git_index_reuc_entry *reuc; + int mode[3] = { 0, 0, 0 }; + git_oid const *oid[3] = { NULL, NULL, NULL }; + size_t i; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry)) + return 0; + + if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) { + for (i = 0; i < 3; i++) { + mode[i] = reuc->mode[i]; + oid[i] = &reuc->oid[i]; + } + } + + mode[idx] = entry->mode; + oid[idx] = &entry->id; + + return git_index_reuc_add(index, entry->path, + mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]); +} + +static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list) +{ + int error; + size_t i; + git_merge_diff *conflict; + + /* Add each entry in the resolved conflict to the REUC independently, since + * the paths may differ due to renames. */ + git_vector_foreach(&diff_list->resolved, i, conflict) { + const git_index_entry *ancestor = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + + const git_index_entry *ours = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + + const git_index_entry *theirs = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if (ancestor != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) + return error; + + if (ours != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) + return error; + + if (theirs != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) + return error; + } + + return 0; +} + +static int index_from_diff_list(git_index **out, + git_merge_diff_list *diff_list, bool skip_reuc) +{ + git_index *index; + size_t i; + git_merge_diff *conflict; + int error = 0; + + *out = NULL; + + if ((error = git_index_new(&index)) < 0) + return error; + + if ((error = git_index__fill(index, &diff_list->staged)) < 0) + goto on_error; + + git_vector_foreach(&diff_list->conflicts, i, conflict) { + const git_index_entry *ancestor = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + + const git_index_entry *ours = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + + const git_index_entry *theirs = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0) + goto on_error; + } + + /* Add each rename entry to the rename portion of the index. */ + git_vector_foreach(&diff_list->conflicts, i, conflict) { + const char *ancestor_path, *our_path, *their_path; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) + continue; + + ancestor_path = conflict->ancestor_entry.path; + + our_path = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + conflict->our_entry.path : NULL; + + their_path = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + conflict->their_entry.path : NULL; + + if ((our_path && strcmp(ancestor_path, our_path) != 0) || + (their_path && strcmp(ancestor_path, their_path) != 0)) { + if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0) + goto on_error; + } + } + + if (!skip_reuc) { + if ((error = index_update_reuc(index, diff_list)) < 0) + goto on_error; + } + + *out = index; + return 0; + +on_error: + git_index_free(index); + return error; +} + +static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) +{ + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + + if (given) + return given; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (git_iterator_for_nothing(empty, &opts) < 0) + return NULL; + + return *empty; +} + +int git_merge__iterators( + git_index **out, + git_repository *repo, + git_iterator *ancestor_iter, + git_iterator *our_iter, + git_iterator *theirs_iter, + const git_merge_options *given_opts) +{ + git_iterator *empty_ancestor = NULL, + *empty_ours = NULL, + *empty_theirs = NULL; + git_merge_diff_list *diff_list; + git_merge_options opts; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_diff *conflict; + git_vector changes; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + GIT_ERROR_CHECK_VERSION( + given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options"); + + if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) + return error; + + file_opts.favor = opts.file_favor; + file_opts.flags = opts.file_flags; + + /* use the git-inspired labels when virtual base building */ + if (opts.flags & GIT_MERGE_VIRTUAL_BASE) { + file_opts.ancestor_label = "merged common ancestors"; + file_opts.our_label = "Temporary merge branch 1"; + file_opts.their_label = "Temporary merge branch 2"; + file_opts.flags |= GIT_MERGE_FILE_ACCEPT_CONFLICTS; + file_opts.marker_size = GIT_MERGE_CONFLICT_MARKER_SIZE + 2; + } + + diff_list = git_merge_diff_list__alloc(repo); + GIT_ERROR_CHECK_ALLOC(diff_list); + + ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter); + our_iter = iterator_given_or_empty(&empty_ours, our_iter); + theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter); + + if ((error = git_merge_diff_list__find_differences( + diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 || + (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0) + goto done; + + memcpy(&changes, &diff_list->conflicts, sizeof(git_vector)); + git_vector_clear(&diff_list->conflicts); + + git_vector_foreach(&changes, i, conflict) { + int resolved = 0; + + if ((error = merge_conflict_resolve( + &resolved, diff_list, conflict, &opts, &file_opts)) < 0) + goto done; + + if (!resolved) { + if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) { + git_error_set(GIT_ERROR_MERGE, "merge conflicts exist"); + error = GIT_EMERGECONFLICT; + goto done; + } + + git_vector_insert(&diff_list->conflicts, conflict); + } + } + + error = index_from_diff_list(out, diff_list, + (opts.flags & GIT_MERGE_SKIP_REUC)); + +done: + if (!given_opts || !given_opts->metric) + git__free(opts.metric); + + git__free((char *)opts.default_driver); + + git_merge_diff_list__free(diff_list); + git_iterator_free(empty_ancestor); + git_iterator_free(empty_ours); + git_iterator_free(empty_theirs); + + return error; +} + +int git_merge_trees( + git_index **out, + git_repository *repo, + const git_tree *ancestor_tree, + const git_tree *our_tree, + const git_tree *their_tree, + const git_merge_options *merge_opts) +{ + git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + /* if one side is treesame to the ancestor, take the other side */ + if (ancestor_tree && merge_opts && (merge_opts->flags & GIT_MERGE_SKIP_REUC)) { + const git_tree *result = NULL; + const git_oid *ancestor_tree_id = git_tree_id(ancestor_tree); + + if (our_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(our_tree))) + result = their_tree; + else if (their_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(their_tree))) + result = our_tree; + + if (result) { + if ((error = git_index_new(out)) == 0) + error = git_index_read_tree(*out, result); + + return error; + } + } + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree( + &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) + goto done; + + error = git_merge__iterators( + out, repo, ancestor_iter, our_iter, their_iter, merge_opts); + +done: + git_iterator_free(ancestor_iter); + git_iterator_free(our_iter); + git_iterator_free(their_iter); + + return error; +} + +static int merge_annotated_commits( + git_index **index_out, + git_annotated_commit **base_out, + git_repository *repo, + git_annotated_commit *our_commit, + git_annotated_commit *their_commit, + size_t recursion_level, + const git_merge_options *opts); + +GIT_INLINE(int) insert_head_ids( + git_array_oid_t *ids, + const git_annotated_commit *annotated_commit) +{ + git_oid *id; + size_t i; + + if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) { + id = git_array_alloc(*ids); + GIT_ERROR_CHECK_ALLOC(id); + + git_oid_cpy(id, git_commit_id(annotated_commit->commit)); + } else { + for (i = 0; i < annotated_commit->parents.size; i++) { + id = git_array_alloc(*ids); + GIT_ERROR_CHECK_ALLOC(id); + + git_oid_cpy(id, &annotated_commit->parents.ptr[i]); + } + } + + return 0; +} + +static int create_virtual_base( + git_annotated_commit **out, + git_repository *repo, + git_annotated_commit *one, + git_annotated_commit *two, + const git_merge_options *opts, + size_t recursion_level) +{ + git_annotated_commit *result = NULL; + git_index *index = NULL; + git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; + + /* Conflicts in the merge base creation do not propagate to conflicts + * in the result; the conflicted base will act as the common ancestor. + */ + if (opts) + memcpy(&virtual_opts, opts, sizeof(git_merge_options)); + + virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; + virtual_opts.flags |= GIT_MERGE_VIRTUAL_BASE; + + if ((merge_annotated_commits(&index, NULL, repo, one, two, + recursion_level + 1, &virtual_opts)) < 0) + return -1; + + result = git__calloc(1, sizeof(git_annotated_commit)); + GIT_ERROR_CHECK_ALLOC(result); + result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; + result->index = index; + + if (insert_head_ids(&result->parents, one) < 0 || + insert_head_ids(&result->parents, two) < 0) { + git_annotated_commit_free(result); + return -1; + } + + *out = result; + return 0; +} + +static int compute_base( + git_annotated_commit **out, + git_repository *repo, + const git_annotated_commit *one, + const git_annotated_commit *two, + const git_merge_options *given_opts, + size_t recursion_level) +{ + git_array_oid_t head_ids = GIT_ARRAY_INIT; + git_oidarray bases = {0}; + git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + size_t i, base_count; + int error; + + *out = NULL; + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_merge_options)); + + /* With more than two commits, merge_bases_many finds the base of + * the first commit and a hypothetical merge of the others. Since + * "one" may itself be a virtual commit, which insert_head_ids + * substitutes multiple ancestors for, it needs to be added + * after "two" which is always a single real commit. + */ + if ((error = insert_head_ids(&head_ids, two)) < 0 || + (error = insert_head_ids(&head_ids, one)) < 0 || + (error = git_merge_bases_many(&bases, repo, + head_ids.size, head_ids.ptr)) < 0) + goto done; + + base_count = (opts.flags & GIT_MERGE_NO_RECURSIVE) ? 0 : bases.count; + + if (base_count) + git_oidarray__reverse(&bases); + + if ((error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0) + goto done; + + for (i = 1; i < base_count; i++) { + recursion_level++; + + if (opts.recursion_limit && recursion_level > opts.recursion_limit) + break; + + if ((error = git_annotated_commit_lookup(&other, repo, + &bases.ids[i])) < 0 || + (error = create_virtual_base(&new_base, repo, base, other, &opts, + recursion_level)) < 0) + goto done; + + git_annotated_commit_free(base); + git_annotated_commit_free(other); + + base = new_base; + new_base = NULL; + other = NULL; + } + +done: + if (error == 0) + *out = base; + else + git_annotated_commit_free(base); + + git_annotated_commit_free(other); + git_annotated_commit_free(new_base); + git_oidarray_dispose(&bases); + git_array_clear(head_ids); + return error; +} + +static int iterator_for_annotated_commit( + git_iterator **out, + git_annotated_commit *commit) +{ + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (commit == NULL) { + error = git_iterator_for_nothing(out, &opts); + } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) { + error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts); + } else { + if (!commit->tree && + (error = git_commit_tree(&commit->tree, commit->commit)) < 0) + goto done; + + error = git_iterator_for_tree(out, commit->tree, &opts); + } + +done: + return error; +} + +static int merge_annotated_commits( + git_index **index_out, + git_annotated_commit **base_out, + git_repository *repo, + git_annotated_commit *ours, + git_annotated_commit *theirs, + size_t recursion_level, + const git_merge_options *opts) +{ + git_annotated_commit *base = NULL; + git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; + int error; + + if ((error = compute_base(&base, repo, ours, theirs, opts, + recursion_level)) < 0) { + + if (error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + } + + if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || + (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || + (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 || + (error = git_merge__iterators(index_out, repo, base_iter, our_iter, + their_iter, opts)) < 0) + goto done; + + if (base_out) { + *base_out = base; + base = NULL; + } + +done: + git_annotated_commit_free(base); + git_iterator_free(base_iter); + git_iterator_free(our_iter); + git_iterator_free(their_iter); + return error; +} + + +int git_merge_commits( + git_index **out, + git_repository *repo, + const git_commit *our_commit, + const git_commit *their_commit, + const git_merge_options *opts) +{ + git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL; + int error = 0; + + if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 || + (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0) + goto done; + + error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts); + +done: + git_annotated_commit_free(ours); + git_annotated_commit_free(theirs); + git_annotated_commit_free(base); + return error; +} + +/* Merge setup / cleanup */ + +static int write_merge_head( + git_repository *repo, + const git_annotated_commit *heads[], + size_t heads_len) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(heads); + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 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++) { + if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->id_str)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int write_merge_mode(git_repository *repo) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + GIT_ASSERT_ARG(repo); + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 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) + goto cleanup; + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +struct merge_msg_entry { + const git_annotated_commit *merge_head; + bool written; +}; + +static int msg_entry_is_branch( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0); +} + +static int msg_entry_is_tracking( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0); +} + +static int msg_entry_is_tag( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0); +} + +static int msg_entry_is_remote( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + if (entry->written == 0 && + entry->merge_head->remote_url != NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0) + { + struct merge_msg_entry *existing; + + /* Match only branches from the same remote */ + if (entries->length == 0) + return 1; + + existing = git_vector_get(entries, 0); + + return (git__strcmp(existing->merge_head->remote_url, + entry->merge_head->remote_url) == 0); + } + + return 0; +} + +static int msg_entry_is_oid( + const struct merge_msg_entry *merge_msg_entry) +{ + return (merge_msg_entry->written == 0 && + merge_msg_entry->merge_head->ref_name == NULL && + merge_msg_entry->merge_head->remote_url == NULL); +} + +static int merge_msg_entry_written( + const struct merge_msg_entry *merge_msg_entry) +{ + return (merge_msg_entry->written == 1); +} + +static int merge_msg_entries( + git_vector *v, + const struct merge_msg_entry *entries, + size_t len, + int (*match)(const struct merge_msg_entry *entry, git_vector *entries)) +{ + size_t i; + int matches, total = 0; + + git_vector_clear(v); + + for (i = 0; i < len; i++) { + if ((matches = match(&entries[i], v)) < 0) + return matches; + else if (!matches) + continue; + + git_vector_insert(v, (struct merge_msg_entry *)&entries[i]); + total++; + } + + return total; +} + +static int merge_msg_write_entries( + git_filebuf *file, + git_vector *entries, + const char *item_name, + const char *item_plural_name, + size_t ref_name_skip, + const char *source, + char sep) +{ + struct merge_msg_entry *entry; + size_t i; + int error = 0; + + if (entries->length == 0) + return 0; + + if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0) + goto done; + + if ((error = git_filebuf_printf(file, "%s ", + (entries->length == 1) ? item_name : item_plural_name)) < 0) + goto done; + + git_vector_foreach(entries, i, entry) { + if (i > 0 && + (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0) + goto done; + + if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0) + goto done; + + entry->written = 1; + } + + if (source) + error = git_filebuf_printf(file, " of %s", source); + +done: + return error; +} + +static int merge_msg_write_branches( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep); +} + +static int merge_msg_write_tracking( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "remote-tracking branch", "remote-tracking branches", 0, NULL, sep); +} + +static int merge_msg_write_tags( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep); +} + +static int merge_msg_write_remotes( + git_filebuf *file, + git_vector *entries, + char sep) +{ + const char *source; + + if (entries->length == 0) + return 0; + + source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url; + + return merge_msg_write_entries(file, entries, + "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep); +} + +static int write_merge_msg( + git_repository *repo, + const git_annotated_commit *heads[], + size_t heads_len) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + struct merge_msg_entry *entries; + git_vector matching = GIT_VECTOR_INIT; + size_t i; + char sep = 0; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(heads); + + entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); + GIT_ERROR_CHECK_ALLOC(entries); + + if (git_vector_init(&matching, heads_len, NULL) < 0) { + git__free(entries); + return -1; + } + + for (i = 0; i < heads_len; i++) + entries[i].merge_head = heads[i]; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 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; + + /* + * This is to emulate the format of MERGE_MSG by core git. + * + * Core git will write all the commits specified by OID, in the order + * provided, until the first named branch or tag is reached, at which + * point all branches will be written in the order provided, then all + * tags, then all remote tracking branches and finally all commits that + * were specified by OID that were not already written. + * + * Yes. Really. + */ + for (i = 0; i < heads_len; i++) { + if (!msg_entry_is_oid(&entries[i])) + break; + + if ((error = git_filebuf_printf(&file, + "%scommit '%s'", (i > 0) ? "; " : "", + entries[i].merge_head->id_str)) < 0) + goto cleanup; + + entries[i].written = 1; + } + + if (i) + sep = ';'; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 || + (error = merge_msg_write_branches(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 || + (error = merge_msg_write_tracking(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 || + (error = merge_msg_write_tags(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + /* We should never be called with multiple remote branches, but handle + * it in case we are... */ + while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) { + if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + } + + if (error < 0) + goto cleanup; + + for (i = 0; i < heads_len; i++) { + if (merge_msg_entry_written(&entries[i])) + continue; + + if ((error = git_filebuf_printf(&file, "; commit '%s'", + entries[i].merge_head->id_str)) < 0) + goto cleanup; + } + + if ((error = git_filebuf_printf(&file, "\n")) < 0 || + (error = git_filebuf_commit(&file)) < 0) + goto cleanup; + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + git_vector_free(&matching); + git__free(entries); + + return error; +} + +int git_merge__setup( + git_repository *repo, + const git_annotated_commit *our_head, + const git_annotated_commit *heads[], + size_t heads_len) +{ + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(our_head); + GIT_ASSERT_ARG(heads); + + if ((error = git_repository__set_orig_head(repo, git_annotated_commit_id(our_head))) == 0 && + (error = write_merge_head(repo, heads, heads_len)) == 0 && + (error = write_merge_mode(repo)) == 0) { + error = write_merge_msg(repo, heads, heads_len); + } + + return error; +} + +/* Merge branches */ + +static int merge_ancestor_head( + git_annotated_commit **ancestor_head, + git_repository *repo, + const git_annotated_commit *our_head, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_oid *oids, ancestor_oid; + size_t i, alloc_len; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(our_head); + GIT_ASSERT_ARG(their_heads); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, their_heads_len, 1); + oids = git__calloc(alloc_len, sizeof(git_oid)); + GIT_ERROR_CHECK_ALLOC(oids); + + git_oid_cpy(&oids[0], git_commit_id(our_head->commit)); + + for (i = 0; i < their_heads_len; i++) + git_oid_cpy(&oids[i + 1], git_annotated_commit_id(their_heads[i])); + + if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0) + goto on_error; + + error = git_annotated_commit_lookup(ancestor_head, repo, &ancestor_oid); + +on_error: + git__free(oids); + return error; +} + +static const char *merge_their_label(const char *branchname) +{ + const char *slash; + + if ((slash = strrchr(branchname, '/')) == NULL) + return branchname; + + if (*(slash+1) == '\0') + return "theirs"; + + return slash+1; +} + +static int merge_normalize_checkout_opts( + git_checkout_options *out, + git_repository *repo, + const git_checkout_options *given_checkout_opts, + unsigned int checkout_strategy, + git_annotated_commit *ancestor, + const git_annotated_commit *our_head, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + int error = 0; + + GIT_UNUSED(repo); + + if (given_checkout_opts != NULL) + memcpy(out, given_checkout_opts, sizeof(git_checkout_options)); + else + memcpy(out, &default_checkout_opts, sizeof(git_checkout_options)); + + out->checkout_strategy = checkout_strategy; + + if (!out->ancestor_label) { + if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL) + out->ancestor_label = git_commit_summary(ancestor->commit); + else if (ancestor) + out->ancestor_label = "merged common ancestors"; + else + out->ancestor_label = "empty base"; + } + + if (!out->our_label) { + if (our_head && our_head->ref_name) + out->our_label = our_head->ref_name; + else + out->our_label = "ours"; + } + + if (!out->their_label) { + if (their_heads_len == 1 && their_heads[0]->ref_name) + out->their_label = merge_their_label(their_heads[0]->ref_name); + else if (their_heads_len == 1) + out->their_label = their_heads[0]->id_str; + else + out->their_label = "theirs"; + } + + return error; +} + +static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) +{ + git_tree *head_tree = NULL; + git_index *index_repo = NULL; + git_iterator *iter_repo = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + git_diff *staged_diff_list = NULL, *index_diff_list = NULL; + git_diff_delta *delta; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector staged_paths = GIT_VECTOR_INIT; + size_t i; + int error = 0; + + GIT_UNUSED(merged_paths); + + *conflicts = 0; + + /* No staged changes may exist unless the change staged is identical to + * the result of the merge. This allows one to apply to merge manually, + * then run merge. Any other staged change would be overwritten by + * a reset merge. + */ + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_repository_index(&index_repo, repo)) < 0 || + (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0) + goto done; + + if (staged_diff_list->deltas.length == 0) + goto done; + + git_vector_foreach(&staged_diff_list->deltas, i, delta) { + if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0) + goto done; + } + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + iter_opts.pathlist.strings = (char **)staged_paths.contents; + iter_opts.pathlist.count = staged_paths.length; + + if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || + (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) + goto done; + + *conflicts = index_diff_list->deltas.length; + +done: + git_tree_free(head_tree); + git_index_free(index_repo); + git_iterator_free(iter_repo); + git_iterator_free(iter_new); + git_diff_free(staged_diff_list); + git_diff_free(index_diff_list); + git_vector_free(&staged_paths); + + return error; +} + +static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) +{ + git_diff *wd_diff_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + int error = 0; + + GIT_UNUSED(index_new); + + *conflicts = 0; + + /* We need to have merged at least 1 file for the possibility to exist to + * have conflicts with the workdir. Passing 0 as the pathspec count parameter + * will consider all files in the working directory, that is, we may detect + * a conflict if there were untracked files in the workdir prior to starting + * the merge. This typically happens when cherry-picking a commit whose + * changes have already been applied. + */ + if (merged_paths->length == 0) + return 0; + + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + /* Workdir changes may exist iff they do not conflict with changes that + * will be applied by the merge (including conflicts). Ensure that there + * are no changes in the workdir to these paths. + */ + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = merged_paths->length; + opts.pathspec.strings = (char **)merged_paths->contents; + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; + + if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0) + goto done; + + *conflicts = wd_diff_list->deltas.length; + +done: + git_diff_free(wd_diff_list); + + return error; +} + +int git_merge__check_result(git_repository *repo, git_index *index_new) +{ + git_tree *head_tree = NULL; + git_iterator *iter_head = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + git_diff *merged_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_delta *delta; + git_vector paths = GIT_VECTOR_INIT; + size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts; + const git_index_entry *e; + int error = 0; + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || + (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) + goto done; + + git_vector_foreach(&merged_list->deltas, i, delta) { + if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0) + goto done; + } + + for (i = 0; i < git_index_entrycount(index_new); i++) { + e = git_index_get_byindex(index_new, i); + + if (git_index_entry_is_conflict(e) && + (git_vector_last(&paths) == NULL || + strcmp(git_vector_last(&paths), e->path) != 0)) { + + if ((error = git_vector_insert(&paths, (char *)e->path)) < 0) + goto done; + } + } + + /* Make sure the index and workdir state do not prevent merging */ + if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || + (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) + goto done; + + if ((conflicts = index_conflicts + wd_conflicts) > 0) { + git_error_set(GIT_ERROR_MERGE, "%" PRIuZ " uncommitted change%s would be overwritten by merge", + conflicts, (conflicts != 1) ? "s" : ""); + error = GIT_ECONFLICT; + } + +done: + git_vector_free(&paths); + git_tree_free(head_tree); + git_iterator_free(iter_head); + git_iterator_free(iter_new); + git_diff_free(merged_list); + + return error; +} + +int git_merge__append_conflicts_to_merge_msg( + git_repository *repo, + git_index *index) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + const char *last = NULL; + size_t i; + int error; + + if (!git_index_has_conflicts(index)) + return 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) + goto cleanup; + + git_filebuf_printf(&file, "\n#Conflicts:\n"); + + for (i = 0; i < git_index_entrycount(index); i++) { + const git_index_entry *e = git_index_get_byindex(index, i); + + if (!git_index_entry_is_conflict(e)) + continue; + + if (last == NULL || strcmp(e->path, last) != 0) + git_filebuf_printf(&file, "#\t%s\n", e->path); + + last = e->path; + } + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int merge_state_cleanup(git_repository *repo) +{ + const char *state_files[] = { + GIT_MERGE_HEAD_FILE, + GIT_MERGE_MODE_FILE, + GIT_MERGE_MSG_FILE, + }; + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +static int merge_heads( + git_annotated_commit **ancestor_head_out, + git_annotated_commit **our_head_out, + git_repository *repo, + git_reference *our_ref, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_annotated_commit *ancestor_head = NULL, *our_head = NULL; + int error = 0; + + *ancestor_head_out = NULL; + *our_head_out = NULL; + + if ((error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0) + goto done; + + if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) { + if (error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + error = 0; + } + + *ancestor_head_out = ancestor_head; + *our_head_out = our_head; + +done: + if (error < 0) { + git_annotated_commit_free(ancestor_head); + git_annotated_commit_free(our_head); + } + + return error; +} + +static int merge_preference(git_merge_preference_t *out, git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + *out = GIT_MERGE_PREFERENCE_NONE; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + if (!bool_value) + *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; + } else { + if (strcasecmp(value, "only") == 0) + *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; + } + +done: + git_config_free(config); + return error; +} + +int git_merge_analysis_for_ref( + git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, + git_repository *repo, + git_reference *our_ref, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_annotated_commit *ancestor_head = NULL, *our_head = NULL; + int error = 0; + bool unborn; + + GIT_ASSERT_ARG(analysis_out); + GIT_ASSERT_ARG(preference_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(their_heads && their_heads_len > 0); + + if (their_heads_len != 1) { + git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); + error = -1; + goto done; + } + + *analysis_out = GIT_MERGE_ANALYSIS_NONE; + + if ((error = merge_preference(preference_out, repo)) < 0) + goto done; + + if ((error = git_reference__is_unborn_head(&unborn, our_ref, repo)) < 0) + goto done; + + if (unborn) { + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; + error = 0; + goto done; + } + + if ((error = merge_heads(&ancestor_head, &our_head, repo, our_ref, their_heads, their_heads_len)) < 0) + goto done; + + /* We're up-to-date if we're trying to merge our own common ancestor. */ + if (ancestor_head && git_oid_equal( + git_annotated_commit_id(ancestor_head), git_annotated_commit_id(their_heads[0]))) + *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; + + /* We're fastforwardable if we're our own common ancestor. */ + else if (ancestor_head && git_oid_equal( + git_annotated_commit_id(ancestor_head), git_annotated_commit_id(our_head))) + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; + + /* Otherwise, just a normal merge is possible. */ + else + *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; + +done: + git_annotated_commit_free(ancestor_head); + git_annotated_commit_free(our_head); + return error; +} + +int git_merge_analysis( + git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, + git_repository *repo, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_reference *head_ref = NULL; + int error = 0; + + if ((error = git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE)) < 0) { + git_error_set(GIT_ERROR_MERGE, "failed to lookup HEAD reference"); + return error; + } + + error = git_merge_analysis_for_ref(analysis_out, preference_out, repo, head_ref, their_heads, their_heads_len); + + git_reference_free(head_ref); + + return error; +} + +int git_merge( + git_repository *repo, + const git_annotated_commit **their_heads, + size_t their_heads_len, + const git_merge_options *merge_opts, + const git_checkout_options *given_checkout_opts) +{ + git_reference *our_ref = NULL; + git_checkout_options checkout_opts; + git_annotated_commit *our_head = NULL, *base = NULL; + git_index *repo_index = NULL, *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + unsigned int checkout_strategy; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(their_heads && their_heads_len > 0); + + if (their_heads_len != 1) { + git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); + return -1; + } + + if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) + goto done; + + checkout_strategy = given_checkout_opts ? + given_checkout_opts->checkout_strategy : + GIT_CHECKOUT_SAFE; + + if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, + &checkout_strategy)) < 0) + goto done; + + if ((error = git_repository_index(&repo_index, repo) < 0) || + (error = git_index_read(repo_index, 0) < 0)) + goto done; + + /* Write the merge setup files to the repository. */ + if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 || + (error = git_merge__setup(repo, our_head, their_heads, + their_heads_len)) < 0) + goto done; + + /* TODO: octopus */ + + if ((error = merge_annotated_commits(&index, &base, repo, our_head, + (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0) + goto done; + + /* check out the merge results */ + + if ((error = merge_normalize_checkout_opts(&checkout_opts, repo, + given_checkout_opts, checkout_strategy, + base, our_head, their_heads, their_heads_len)) < 0 || + (error = git_checkout_index(repo, index, &checkout_opts)) < 0) + goto done; + + error = git_indexwriter_commit(&indexwriter); + +done: + if (error < 0) + merge_state_cleanup(repo); + + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_annotated_commit_free(our_head); + git_annotated_commit_free(base); + git_reference_free(our_ref); + git_index_free(repo_index); + + return error; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_merge_init_options(git_merge_options *opts, unsigned int version) +{ + return git_merge_options_init(opts, version); +} +#endif + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) +{ + return git_merge_file_input_init(input, version); +} +#endif + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_merge_file_init_options( + git_merge_file_options *opts, unsigned int version) +{ + return git_merge_file_options_init(opts, version); +} +#endif diff --git a/src/libgit2/merge.h b/src/libgit2/merge.h new file mode 100644 index 000000000..23932905e --- /dev/null +++ b/src/libgit2/merge.h @@ -0,0 +1,204 @@ +/* + * 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_merge_h__ +#define INCLUDE_merge_h__ + +#include "common.h" + +#include "vector.h" +#include "commit_list.h" +#include "pool.h" +#include "iterator.h" + +#include "git2/types.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" + +#define GIT_MERGE_MSG_FILE "MERGE_MSG" +#define GIT_MERGE_MODE_FILE "MERGE_MODE" +#define GIT_MERGE_FILE_MODE 0666 + +#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 +#define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 + +/** Types of changes when files are merged from branch to branch. */ +typedef enum { + /* No conflict - a change only occurs in one branch. */ + GIT_MERGE_DIFF_NONE = 0, + + /* Occurs when a file is modified in both branches. */ + GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0), + + /* Occurs when a file is added in both branches. */ + GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1), + + /* Occurs when a file is deleted in both branches. */ + GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2), + + /* Occurs when a file is modified in one branch and deleted in the other. */ + GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3), + + /* Occurs when a file is renamed in one branch and modified in the other. */ + GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4), + + /* Occurs when a file is renamed in one branch and deleted in the other. */ + GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5), + + /* Occurs when a file is renamed in one branch and a file with the same + * name is added in the other. Eg, A->B and new file B. Core git calls + * this a "rename/delete". */ + GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6), + + /* Occurs when both a file is renamed to the same name in the ours and + * theirs branches. Eg, A->B and A->B in both. Automergeable. */ + GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7), + + /* Occurs when a file is renamed to different names in the ours and theirs + * branches. Eg, A->B and A->C. */ + GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8), + + /* Occurs when two files are renamed to the same name in the ours and + * theirs branches. Eg, A->C and B->C. */ + GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9), + + /* Occurs when an item at a path in one branch is a directory, and an + * item at the same path in a different branch is a file. */ + GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10), + + /* The child of a folder that is in a directory/file conflict. */ + GIT_MERGE_DIFF_DF_CHILD = (1 << 11) +} git_merge_diff_t; + +typedef struct { + git_repository *repo; + git_pool pool; + + /* Vector of git_index_entry that represent the merged items that + * have been staged, either because only one side changed, or because + * the two changes were non-conflicting and mergeable. These items + * will be written as staged entries in the main index. + */ + git_vector staged; + + /* Vector of git_merge_diff entries that represent the conflicts that + * have not been automerged. These items will be written to high-stage + * entries in the main index. + */ + git_vector conflicts; + + /* Vector of git_merge_diff that have been automerged. These items + * will be written to the REUC when the index is produced. + */ + git_vector resolved; +} git_merge_diff_list; + +/** + * Description of changes to one file across three trees. + */ +typedef struct { + git_merge_diff_t type; + + git_index_entry ancestor_entry; + + git_index_entry our_entry; + git_delta_t our_status; + + git_index_entry their_entry; + git_delta_t their_status; + +} git_merge_diff; + +int git_merge__bases_many( + git_commit_list **out, + git_revwalk *walk, + git_commit_list_node *one, + git_vector *twos, + uint32_t minimum_generation); + +/* + * Three-way tree differencing + */ + +git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo); + +int git_merge_diff_list__find_differences( + git_merge_diff_list *merge_diff_list, + git_iterator *ancestor_iterator, + git_iterator *ours_iter, + git_iterator *theirs_iter); + +int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts); + +void git_merge_diff_list__free(git_merge_diff_list *diff_list); + +/* Merge metadata setup */ + +int git_merge__setup( + git_repository *repo, + const git_annotated_commit *our_head, + const git_annotated_commit *heads[], + size_t heads_len); + +int git_merge__iterators( + git_index **out, + git_repository *repo, + git_iterator *ancestor_iter, + git_iterator *our_iter, + git_iterator *their_iter, + const git_merge_options *given_opts); + +int git_merge__check_result(git_repository *repo, git_index *index_new); + +int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); + +/* Merge files */ + +GIT_INLINE(const char *) git_merge_file__best_path( + const char *ancestor, + const char *ours, + const char *theirs) +{ + if (!ancestor) { + if (ours && theirs && strcmp(ours, theirs) == 0) + return ours; + + return NULL; + } + + if (ours && strcmp(ancestor, ours) == 0) + return theirs; + else if(theirs && strcmp(ancestor, theirs) == 0) + return ours; + + return NULL; +} + +GIT_INLINE(uint32_t) git_merge_file__best_mode( + uint32_t ancestor, uint32_t ours, uint32_t theirs) +{ + /* + * If ancestor didn't exist and either ours or theirs is executable, + * assume executable. Otherwise, if any mode changed from the ancestor, + * use that one. + */ + if (!ancestor) { + if (ours == GIT_FILEMODE_BLOB_EXECUTABLE || + theirs == GIT_FILEMODE_BLOB_EXECUTABLE) + return GIT_FILEMODE_BLOB_EXECUTABLE; + + return GIT_FILEMODE_BLOB; + } else if (ours && theirs) { + if (ancestor == ours) + return theirs; + + return ours; + } + + return 0; +} + +#endif diff --git a/src/libgit2/merge_driver.c b/src/libgit2/merge_driver.c new file mode 100644 index 000000000..19b35ac0e --- /dev/null +++ b/src/libgit2/merge_driver.c @@ -0,0 +1,432 @@ +/* + * 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 "merge_driver.h" + +#include "vector.h" +#include "runtime.h" +#include "merge.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" + +static const char *merge_driver_name__text = "text"; +static const char *merge_driver_name__union = "union"; +static const char *merge_driver_name__binary = "binary"; + +struct merge_driver_registry { + git_rwlock lock; + git_vector drivers; +}; + +typedef struct { + git_merge_driver *driver; + int initialized; + char name[GIT_FLEX_ARRAY]; +} git_merge_driver_entry; + +static struct merge_driver_registry merge_driver_registry; + +static void git_merge_driver_global_shutdown(void); + +git_repository *git_merge_driver_source_repo( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->repo; +} + +const git_index_entry *git_merge_driver_source_ancestor( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->ancestor; +} + +const git_index_entry *git_merge_driver_source_ours( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->ours; +} + +const git_index_entry *git_merge_driver_source_theirs( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->theirs; +} + +const git_merge_file_options *git_merge_driver_source_file_options( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->file_opts; +} + +int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + int error; + + GIT_UNUSED(filter_name); + + if (src->file_opts) + memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); + + if (driver->favor) + file_opts.favor = driver->favor; + + if ((error = git_merge_file_from_index(&result, src->repo, + src->ancestor, src->ours, src->theirs, &file_opts)) < 0) + goto done; + + if (!result.automergeable && + !(file_opts.flags & GIT_MERGE_FILE_ACCEPT_CONFLICTS)) { + error = GIT_EMERGECONFLICT; + goto done; + } + + *path_out = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + *mode_out = git_merge_file__best_mode( + src->ancestor ? src->ancestor->mode : 0, + src->ours ? src->ours->mode : 0, + src->theirs ? src->theirs->mode : 0); + + merged_out->ptr = (char *)result.ptr; + merged_out->size = result.len; + merged_out->reserved = 0; + result.ptr = NULL; + +done: + git_merge_file_result_free(&result); + return error; +} + +static int merge_driver_binary_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(self); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_EMERGECONFLICT; +} + +static int merge_driver_entry_cmp(const void *a, const void *b) +{ + const git_merge_driver_entry *entry_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(entry_a->name, entry_b->name); +} + +static int merge_driver_entry_search(const void *a, const void *b) +{ + const char *name_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(name_a, entry_b->name); +} + +git_merge_driver__builtin git_merge_driver__text = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_NORMAL +}; + +git_merge_driver__builtin git_merge_driver__union = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_UNION +}; + +git_merge_driver git_merge_driver__binary = { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + merge_driver_binary_apply +}; + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_insert( + const char *name, git_merge_driver *driver) +{ + git_merge_driver_entry *entry; + + entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); + GIT_ERROR_CHECK_ALLOC(entry); + + strcpy(entry->name, name); + entry->driver = driver; + + return git_vector_insert_sorted( + &merge_driver_registry.drivers, entry, NULL); +} + +int git_merge_driver_global_init(void) +{ + int error; + + if (git_rwlock_init(&merge_driver_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&merge_driver_registry.drivers, 3, + merge_driver_entry_cmp)) < 0) + goto done; + + if ((error = merge_driver_registry_insert( + merge_driver_name__text, &git_merge_driver__text.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__union, &git_merge_driver__union.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__binary, &git_merge_driver__binary)) < 0) + goto done; + + error = git_runtime_shutdown_register(git_merge_driver_global_shutdown); + +done: + if (error < 0) + git_vector_free_deep(&merge_driver_registry.drivers); + + return error; +} + +static void git_merge_driver_global_shutdown(void) +{ + git_merge_driver_entry *entry; + size_t i; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) + return; + + git_vector_foreach(&merge_driver_registry.drivers, i, entry) { + if (entry->driver->shutdown) + entry->driver->shutdown(entry->driver); + + git__free(entry); + } + + git_vector_free(&merge_driver_registry.drivers); + + git_rwlock_wrunlock(&merge_driver_registry.lock); + git_rwlock_free(&merge_driver_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2(pos, &merge_driver_registry.drivers, + merge_driver_entry_search, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_merge_driver_entry *merge_driver_registry_lookup( + size_t *pos, const char *name) +{ + git_merge_driver_entry *entry = NULL; + + if (!merge_driver_registry_find(pos, name)) + entry = git_vector_get(&merge_driver_registry.drivers, *pos); + + return entry; +} + +int git_merge_driver_register(const char *name, git_merge_driver *driver) +{ + int error; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(driver); + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); + return -1; + } + + if (!merge_driver_registry_find(NULL, name)) { + git_error_set(GIT_ERROR_MERGE, "attempt to reregister existing driver '%s'", + name); + error = GIT_EEXISTS; + goto done; + } + + error = merge_driver_registry_insert(name, driver); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +int git_merge_driver_unregister(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error = 0; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); + return -1; + } + + if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { + git_error_set(GIT_ERROR_MERGE, "cannot find merge driver '%s' to unregister", + name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&merge_driver_registry.drivers, pos); + + if (entry->initialized && entry->driver->shutdown) { + entry->driver->shutdown(entry->driver); + entry->initialized = false; + } + + git__free(entry); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +git_merge_driver *git_merge_driver_lookup(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error; + + /* If we've decided the merge driver to use internally - and not + * based on user configuration (in merge_driver_name_for_path) + * then we can use a hardcoded name to compare instead of bothering + * to take a lock and look it up in the vector. + */ + if (name == merge_driver_name__text) + return &git_merge_driver__text.base; + else if (name == merge_driver_name__binary) + return &git_merge_driver__binary; + + if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); + return NULL; + } + + entry = merge_driver_registry_lookup(&pos, name); + + git_rwlock_rdunlock(&merge_driver_registry.lock); + + if (entry == NULL) { + git_error_set(GIT_ERROR_MERGE, "cannot use an unregistered filter"); + return NULL; + } + + if (!entry->initialized) { + if (entry->driver->initialize && + (error = entry->driver->initialize(entry->driver)) < 0) + return NULL; + + entry->initialized = 1; + } + + return entry->driver; +} + +static int merge_driver_name_for_path( + const char **out, + git_repository *repo, + const char *path, + const char *default_driver) +{ + const char *value; + int error; + + *out = NULL; + + if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) + return error; + + /* set: use the built-in 3-way merge driver ("text") */ + if (GIT_ATTR_IS_TRUE(value)) + *out = merge_driver_name__text; + + /* unset: do not merge ("binary") */ + else if (GIT_ATTR_IS_FALSE(value)) + *out = merge_driver_name__binary; + + else if (GIT_ATTR_IS_UNSPECIFIED(value) && default_driver) + *out = default_driver; + + else if (GIT_ATTR_IS_UNSPECIFIED(value)) + *out = merge_driver_name__text; + + else + *out = value; + + return 0; +} + + +GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( + const char *name) +{ + git_merge_driver *driver = git_merge_driver_lookup(name); + + if (driver == NULL) + driver = git_merge_driver_lookup("*"); + + return driver; +} + +int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src) +{ + const char *path, *driver_name; + int error = 0; + + path = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + if ((error = merge_driver_name_for_path( + &driver_name, src->repo, path, src->default_driver)) < 0) + return error; + + *name_out = driver_name; + *driver_out = merge_driver_lookup_with_wildcard(driver_name); + return error; +} + diff --git a/src/libgit2/merge_driver.h b/src/libgit2/merge_driver.h new file mode 100644 index 000000000..6b7da5287 --- /dev/null +++ b/src/libgit2/merge_driver.h @@ -0,0 +1,62 @@ +/* + * 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_merge_driver_h__ +#define INCLUDE_merge_driver_h__ + +#include "common.h" + +#include "git2/merge.h" +#include "git2/index.h" +#include "git2/sys/merge.h" + +struct git_merge_driver_source { + git_repository *repo; + const char *default_driver; + const git_merge_file_options *file_opts; + + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; +}; + +typedef struct git_merge_driver__builtin { + git_merge_driver base; + git_merge_file_favor_t favor; +} git_merge_driver__builtin; + +extern int git_merge_driver_global_init(void); + +extern int git_merge_driver_for_path( + char **name_out, + git_merge_driver **driver_out, + git_repository *repo, + const char *path); + +/* Merge driver configuration */ +extern int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src); + +extern int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src); + +/* Merge driver for text files, performs a standard three-way merge */ +extern git_merge_driver__builtin git_merge_driver__text; + +/* Merge driver for union-style merging */ +extern git_merge_driver__builtin git_merge_driver__union; + +/* Merge driver for unmergeable (binary) files: always produces conflicts */ +extern git_merge_driver git_merge_driver__binary; + +#endif diff --git a/src/libgit2/merge_file.c b/src/libgit2/merge_file.c new file mode 100644 index 000000000..732a047b1 --- /dev/null +++ b/src/libgit2/merge_file.c @@ -0,0 +1,327 @@ +/* + * 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 "repository.h" +#include "posix.h" +#include "futils.h" +#include "index.h" +#include "diff_xdiff.h" +#include "merge.h" + +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/index.h" +#include "git2/merge.h" + +#include "xdiff/xdiff.h" + +/* only examine the first 8000 bytes for binaryness. + * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197 + */ +#define GIT_MERGE_FILE_BINARY_SIZE 8000 + +#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) + +static int merge_file_input_from_index( + git_merge_file_input *input_out, + git_odb_object **odb_object_out, + git_odb *odb, + const git_index_entry *entry) +{ + int error = 0; + + GIT_ASSERT_ARG(input_out); + GIT_ASSERT_ARG(odb_object_out); + GIT_ASSERT_ARG(odb); + GIT_ASSERT_ARG(entry); + + if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0) + goto done; + + input_out->path = entry->path; + input_out->mode = entry->mode; + input_out->ptr = (char *)git_odb_object_data(*odb_object_out); + input_out->size = git_odb_object_size(*odb_object_out); + +done: + return error; +} + +static void merge_file_normalize_opts( + git_merge_file_options *out, + const git_merge_file_options *given_opts) +{ + if (given_opts) + memcpy(out, given_opts, sizeof(git_merge_file_options)); + else { + git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT; + memcpy(out, &default_opts, sizeof(git_merge_file_options)); + } +} + +static int merge_file__xdiff( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + xmparam_t xmparam; + mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0}; + mmbuffer_t mmbuffer; + git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT; + const char *path; + int xdl_result; + int error = 0; + + memset(out, 0x0, sizeof(git_merge_file_result)); + + merge_file_normalize_opts(&options, given_opts); + + memset(&xmparam, 0x0, sizeof(xmparam_t)); + + if (ours->size > LONG_MAX || + theirs->size > LONG_MAX || + (ancestor && ancestor->size > LONG_MAX)) { + git_error_set(GIT_ERROR_MERGE, "failed to merge files"); + error = -1; + goto done; + } + + if (ancestor) { + xmparam.ancestor = (options.ancestor_label) ? + options.ancestor_label : ancestor->path; + ancestor_mmfile.ptr = (char *)ancestor->ptr; + ancestor_mmfile.size = (long)ancestor->size; + } + + xmparam.file1 = (options.our_label) ? + options.our_label : ours->path; + our_mmfile.ptr = (char *)ours->ptr; + our_mmfile.size = (long)ours->size; + + xmparam.file2 = (options.their_label) ? + options.their_label : theirs->path; + their_mmfile.ptr = (char *)theirs->ptr; + their_mmfile.size = (long)theirs->size; + + if (options.favor == GIT_MERGE_FILE_FAVOR_OURS) + xmparam.favor = XDL_MERGE_FAVOR_OURS; + else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS) + xmparam.favor = XDL_MERGE_FAVOR_THEIRS; + else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION) + xmparam.favor = XDL_MERGE_FAVOR_UNION; + + xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ? + XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS; + + if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3) + xmparam.style = XDL_MERGE_DIFF3; + if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3) + xmparam.style = XDL_MERGE_ZEALOUS_DIFF3; + + if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE) + xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE; + if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE) + xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL) + xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + + if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE) + xmparam.xpp.flags |= XDF_PATIENCE_DIFF; + + if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL) + xmparam.xpp.flags |= XDF_NEED_MINIMAL; + + xmparam.marker_size = options.marker_size; + + if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, + &their_mmfile, &xmparam, &mmbuffer)) < 0) { + git_error_set(GIT_ERROR_MERGE, "failed to merge files"); + error = -1; + goto done; + } + + path = git_merge_file__best_path( + ancestor ? ancestor->path : NULL, + ours->path, + theirs->path); + + if (path != NULL && (out->path = git__strdup(path)) == NULL) { + error = -1; + goto done; + } + + out->automergeable = (xdl_result == 0); + out->ptr = (const char *)mmbuffer.ptr; + out->len = mmbuffer.size; + out->mode = git_merge_file__best_mode( + ancestor ? ancestor->mode : 0, + ours->mode, + theirs->mode); + +done: + if (error < 0) + git_merge_file_result_free(out); + + return error; +} + +static bool merge_file__is_binary(const git_merge_file_input *file) +{ + size_t len = file ? file->size : 0; + + if (len > GIT_XDIFF_MAX_SIZE) + return true; + if (len > GIT_MERGE_FILE_BINARY_SIZE) + len = GIT_MERGE_FILE_BINARY_SIZE; + + return len ? (memchr(file->ptr, 0, len) != NULL) : false; +} + +static int merge_file__binary( + git_merge_file_result *out, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + const git_merge_file_input *favored = NULL; + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS) + favored = ours; + else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS) + favored = theirs; + else + goto done; + + if ((out->path = git__strdup(favored->path)) == NULL || + (out->ptr = git__malloc(favored->size)) == NULL) + goto done; + + memcpy((char *)out->ptr, favored->ptr, favored->size); + out->len = favored->size; + out->mode = favored->mode; + out->automergeable = 1; + +done: + return 0; +} + +static int merge_file__from_inputs( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + if (merge_file__is_binary(ancestor) || + merge_file__is_binary(ours) || + merge_file__is_binary(theirs)) + return merge_file__binary(out, ours, theirs, given_opts); + + return merge_file__xdiff(out, ancestor, ours, theirs, given_opts); +} + +static git_merge_file_input *git_merge_file__normalize_inputs( + git_merge_file_input *out, + const git_merge_file_input *given) +{ + memcpy(out, given, sizeof(git_merge_file_input)); + + if (!out->path) + out->path = "file.txt"; + + if (!out->mode) + out->mode = 0100644; + + return out; +} + +int git_merge_file( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *options) +{ + git_merge_file_input inputs[3] = { {0} }; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ours); + GIT_ASSERT_ARG(theirs); + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if (ancestor) + ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor); + + ours = git_merge_file__normalize_inputs(&inputs[1], ours); + theirs = git_merge_file__normalize_inputs(&inputs[2], theirs); + + return merge_file__from_inputs(out, ancestor, ours, theirs, options); +} + +int git_merge_file_from_index( + git_merge_file_result *out, + git_repository *repo, + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs, + const git_merge_file_options *options) +{ + git_merge_file_input *ancestor_ptr = NULL, + ancestor_input = {0}, our_input = {0}, their_input = {0}; + git_odb *odb = NULL; + git_odb_object *odb_object[3] = { 0 }; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ours); + GIT_ASSERT_ARG(theirs); + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if ((error = git_repository_odb(&odb, repo)) < 0) + goto done; + + if (ancestor) { + if ((error = merge_file_input_from_index( + &ancestor_input, &odb_object[0], odb, ancestor)) < 0) + goto done; + + ancestor_ptr = &ancestor_input; + } + + if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 || + (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0) + goto done; + + error = merge_file__from_inputs(out, + ancestor_ptr, &our_input, &their_input, options); + +done: + git_odb_object_free(odb_object[0]); + git_odb_object_free(odb_object[1]); + git_odb_object_free(odb_object[2]); + git_odb_free(odb); + + return error; +} + +void git_merge_file_result_free(git_merge_file_result *result) +{ + if (result == NULL) + return; + + git__free((char *)result->path); + git__free((char *)result->ptr); +} diff --git a/src/libgit2/message.c b/src/libgit2/message.c new file mode 100644 index 000000000..ec0103a33 --- /dev/null +++ b/src/libgit2/message.c @@ -0,0 +1,75 @@ +/* + * 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 "buf.h" + +#include "git2/message.h" + +static size_t line_length_without_trailing_spaces(const char *line, size_t len) +{ + while (len) { + unsigned char c = line[len - 1]; + if (!git__isspace(c)) + break; + len--; + } + + return len; +} + +/* Greatly inspired from git.git "stripspace" */ +/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ +static int git_message__prettify( + git_str *message_out, + const char *message, + int strip_comments, + char comment_char) +{ + const size_t message_len = strlen(message); + + int consecutive_empty_lines = 0; + size_t i, line_length, rtrimmed_line_length; + char *next_newline; + + for (i = 0; i < strlen(message); i += line_length) { + next_newline = memchr(message + i, '\n', message_len - i); + + if (next_newline != NULL) { + line_length = next_newline - (message + i) + 1; + } else { + line_length = message_len - i; + } + + if (strip_comments && line_length && message[i] == comment_char) + continue; + + rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); + + if (!rtrimmed_line_length) { + consecutive_empty_lines++; + continue; + } + + if (consecutive_empty_lines > 0 && message_out->size > 0) + git_str_putc(message_out, '\n'); + + consecutive_empty_lines = 0; + git_str_put(message_out, message + i, rtrimmed_line_length); + git_str_putc(message_out, '\n'); + } + + return git_str_oom(message_out) ? -1 : 0; +} + +int git_message_prettify( + git_buf *message_out, + const char *message, + int strip_comments, + char comment_char) +{ + GIT_BUF_WRAP_PRIVATE(message_out, git_message__prettify, message, strip_comments, comment_char); +} diff --git a/src/libgit2/midx.c b/src/libgit2/midx.c new file mode 100644 index 000000000..67eab9acb --- /dev/null +++ b/src/libgit2/midx.c @@ -0,0 +1,898 @@ +/* + * 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 "midx.h" + +#include "array.h" +#include "buf.h" +#include "filebuf.h" +#include "futils.h" +#include "hash.h" +#include "odb.h" +#include "pack.h" +#include "fs_path.h" +#include "repository.h" +#include "str.h" + +#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ +#define MIDX_VERSION 1 +#define MIDX_OBJECT_ID_VERSION 1 +struct git_midx_header { + uint32_t signature; + uint8_t version; + uint8_t object_id_version; + uint8_t chunks; + uint8_t base_midx_files; + uint32_t packfiles; +}; + +#define MIDX_PACKFILE_NAMES_ID 0x504e414d /* "PNAM" */ +#define MIDX_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ +#define MIDX_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ +#define MIDX_OBJECT_OFFSETS_ID 0x4f4f4646 /* "OOFF" */ +#define MIDX_OBJECT_LARGE_OFFSETS_ID 0x4c4f4646 /* "LOFF" */ + +struct git_midx_chunk { + off64_t offset; + size_t length; +}; + +typedef int (*midx_write_cb)(const char *buf, size_t size, void *cb_data); + +static int midx_error(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "invalid multi-pack-index file - %s", message); + return -1; +} + +static int midx_parse_packfile_names( + git_midx_file *idx, + const unsigned char *data, + uint32_t packfiles, + struct git_midx_chunk *chunk) +{ + int error; + uint32_t i; + char *packfile_name = (char *)(data + chunk->offset); + size_t chunk_size = chunk->length, len; + if (chunk->offset == 0) + return midx_error("missing Packfile Names chunk"); + if (chunk->length == 0) + return midx_error("empty Packfile Names chunk"); + if ((error = git_vector_init(&idx->packfile_names, packfiles, git__strcmp_cb)) < 0) + return error; + for (i = 0; i < packfiles; ++i) { + len = p_strnlen(packfile_name, chunk_size); + if (len == 0) + return midx_error("empty packfile name"); + if (len + 1 > chunk_size) + return midx_error("unterminated packfile name"); + git_vector_insert(&idx->packfile_names, packfile_name); + if (i && strcmp(git_vector_get(&idx->packfile_names, i - 1), packfile_name) >= 0) + return midx_error("packfile names are not sorted"); + if (strlen(packfile_name) <= strlen(".idx") || git__suffixcmp(packfile_name, ".idx") != 0) + return midx_error("non-.idx packfile name"); + if (strchr(packfile_name, '/') != NULL || strchr(packfile_name, '\\') != NULL) + return midx_error("non-local packfile"); + packfile_name += len + 1; + chunk_size -= len + 1; + } + return 0; +} + +static int midx_parse_oid_fanout( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_oid_fanout) +{ + uint32_t i, nr; + if (chunk_oid_fanout->offset == 0) + return midx_error("missing OID Fanout chunk"); + if (chunk_oid_fanout->length == 0) + return midx_error("empty OID Fanout chunk"); + if (chunk_oid_fanout->length != 256 * 4) + return midx_error("OID Fanout chunk has wrong length"); + + idx->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); + nr = 0; + for (i = 0; i < 256; ++i) { + uint32_t n = ntohl(idx->oid_fanout[i]); + if (n < nr) + return midx_error("index is non-monotonic"); + nr = n; + } + idx->num_objects = nr; + return 0; +} + +static int midx_parse_oid_lookup( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_oid_lookup) +{ + uint32_t i; + unsigned char *oid, *prev_oid, zero_oid[GIT_OID_RAWSZ] = {0}; + + if (chunk_oid_lookup->offset == 0) + return midx_error("missing OID Lookup chunk"); + if (chunk_oid_lookup->length == 0) + return midx_error("empty OID Lookup chunk"); + if (chunk_oid_lookup->length != idx->num_objects * GIT_OID_RAWSZ) + return midx_error("OID Lookup chunk has wrong length"); + + idx->oid_lookup = oid = (unsigned char *)(data + chunk_oid_lookup->offset); + prev_oid = zero_oid; + for (i = 0; i < idx->num_objects; ++i, oid += GIT_OID_RAWSZ) { + if (git_oid_raw_cmp(prev_oid, oid) >= 0) + return midx_error("OID Lookup index is non-monotonic"); + prev_oid = oid; + } + + return 0; +} + +static int midx_parse_object_offsets( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_object_offsets) +{ + if (chunk_object_offsets->offset == 0) + return midx_error("missing Object Offsets chunk"); + if (chunk_object_offsets->length == 0) + return midx_error("empty Object Offsets chunk"); + if (chunk_object_offsets->length != idx->num_objects * 8) + return midx_error("Object Offsets chunk has wrong length"); + + idx->object_offsets = data + chunk_object_offsets->offset; + + return 0; +} + +static int midx_parse_object_large_offsets( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_object_large_offsets) +{ + if (chunk_object_large_offsets->length == 0) + return 0; + if (chunk_object_large_offsets->length % 8 != 0) + return midx_error("malformed Object Large Offsets chunk"); + + idx->object_large_offsets = data + chunk_object_large_offsets->offset; + idx->num_object_large_offsets = chunk_object_large_offsets->length / 8; + + return 0; +} + +int git_midx_parse( + git_midx_file *idx, + const unsigned char *data, + size_t size) +{ + struct git_midx_header *hdr; + const unsigned char *chunk_hdr; + struct git_midx_chunk *last_chunk; + uint32_t i; + off64_t last_chunk_offset, chunk_offset, trailer_offset; + size_t checksum_size; + int error; + struct git_midx_chunk chunk_packfile_names = {0}, + chunk_oid_fanout = {0}, + chunk_oid_lookup = {0}, + chunk_object_offsets = {0}, + chunk_object_large_offsets = {0}; + + GIT_ASSERT_ARG(idx); + + if (size < sizeof(struct git_midx_header) + GIT_OID_RAWSZ) + return midx_error("multi-pack index is too short"); + + hdr = ((struct git_midx_header *)data); + + if (hdr->signature != htonl(MIDX_SIGNATURE) || + hdr->version != MIDX_VERSION || + hdr->object_id_version != MIDX_OBJECT_ID_VERSION) { + return midx_error("unsupported multi-pack index version"); + } + if (hdr->chunks == 0) + return midx_error("no chunks in multi-pack index"); + + /* + * The very first chunk's offset should be after the header, all the chunk + * headers, and a special zero chunk. + */ + last_chunk_offset = + sizeof(struct git_midx_header) + + (1 + hdr->chunks) * 12; + + checksum_size = GIT_HASH_SHA1_SIZE; + trailer_offset = size - checksum_size; + + if (trailer_offset < last_chunk_offset) + return midx_error("wrong index size"); + memcpy(idx->checksum, data + trailer_offset, checksum_size); + + chunk_hdr = data + sizeof(struct git_midx_header); + last_chunk = NULL; + for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { + uint32_t chunk_id = ntohl(*((uint32_t *)(chunk_hdr + 0))); + uint64_t high_offset = ((uint64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) & 0xffffffffu; + uint64_t low_offset = ((uint64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))) & 0xffffffffu; + + if (high_offset >= INT32_MAX) + return midx_error("chunk offset out of range"); + chunk_offset = (off64_t)(high_offset << 32 | low_offset); + if (chunk_offset < last_chunk_offset) + return midx_error("chunks are non-monotonic"); + if (chunk_offset >= trailer_offset) + return midx_error("chunks extend beyond the trailer"); + if (last_chunk != NULL) + last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); + last_chunk_offset = chunk_offset; + + switch (chunk_id) { + case MIDX_PACKFILE_NAMES_ID: + chunk_packfile_names.offset = last_chunk_offset; + last_chunk = &chunk_packfile_names; + break; + + case MIDX_OID_FANOUT_ID: + chunk_oid_fanout.offset = last_chunk_offset; + last_chunk = &chunk_oid_fanout; + break; + + case MIDX_OID_LOOKUP_ID: + chunk_oid_lookup.offset = last_chunk_offset; + last_chunk = &chunk_oid_lookup; + break; + + case MIDX_OBJECT_OFFSETS_ID: + chunk_object_offsets.offset = last_chunk_offset; + last_chunk = &chunk_object_offsets; + break; + + case MIDX_OBJECT_LARGE_OFFSETS_ID: + chunk_object_large_offsets.offset = last_chunk_offset; + last_chunk = &chunk_object_large_offsets; + break; + + default: + return midx_error("unrecognized chunk ID"); + } + } + last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); + + error = midx_parse_packfile_names( + idx, data, ntohl(hdr->packfiles), &chunk_packfile_names); + if (error < 0) + return error; + error = midx_parse_oid_fanout(idx, data, &chunk_oid_fanout); + if (error < 0) + return error; + error = midx_parse_oid_lookup(idx, data, &chunk_oid_lookup); + if (error < 0) + return error; + error = midx_parse_object_offsets(idx, data, &chunk_object_offsets); + if (error < 0) + return error; + error = midx_parse_object_large_offsets(idx, data, &chunk_object_large_offsets); + if (error < 0) + return error; + + return 0; +} + +int git_midx_open( + git_midx_file **idx_out, + const char *path) +{ + git_midx_file *idx; + git_file fd = -1; + size_t idx_size; + struct stat st; + int error; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "multi-pack-index file not found - '%s'", path); + return -1; + } + + if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); + return -1; + } + idx_size = (size_t)st.st_size; + + idx = git__calloc(1, sizeof(git_midx_file)); + GIT_ERROR_CHECK_ALLOC(idx); + + error = git_str_sets(&idx->filename, path); + if (error < 0) + return error; + + error = git_futils_mmap_ro(&idx->index_map, fd, 0, idx_size); + p_close(fd); + if (error < 0) { + git_midx_free(idx); + return error; + } + + if ((error = git_midx_parse(idx, idx->index_map.data, idx_size)) < 0) { + git_midx_free(idx); + return error; + } + + *idx_out = idx; + return 0; +} + +bool git_midx_needs_refresh( + const git_midx_file *idx, + const char *path) +{ + git_file fd = -1; + struct stat st; + ssize_t bytes_read; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return true; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + return true; + } + + if (!S_ISREG(st.st_mode) || + !git__is_sizet(st.st_size) || + (size_t)st.st_size != idx->index_map.len) { + p_close(fd); + return true; + } + + checksum_size = GIT_HASH_SHA1_SIZE; + bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - GIT_OID_RAWSZ); + p_close(fd); + + if (bytes_read != (ssize_t)checksum_size) + return true; + + return (memcmp(checksum, idx->checksum, checksum_size) != 0); +} + +int git_midx_entry_find( + git_midx_entry *e, + git_midx_file *idx, + const git_oid *short_oid, + size_t len) +{ + int pos, found = 0; + size_t pack_index; + uint32_t hi, lo; + unsigned char *current = NULL; + const unsigned char *object_offset; + off64_t offset; + + GIT_ASSERT_ARG(idx); + + hi = ntohl(idx->oid_fanout[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(idx->oid_fanout[(int)short_oid->id[0] - 1])); + + pos = git_pack__lookup_sha1(idx->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = idx->oid_lookup + (pos * GIT_OID_RAWSZ); + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = -1 - pos; + if (pos < (int)idx->num_objects) { + current = idx->oid_lookup + (pos * GIT_OID_RAWSZ); + + if (!git_oid_raw_ncmp(short_oid->id, current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)idx->num_objects) { + /* Check for ambiguousity */ + const unsigned char *next = current + GIT_OID_RAWSZ; + + if (!git_oid_raw_ncmp(short_oid->id, next, len)) + found = 2; + } + + if (!found) + return git_odb__error_notfound("failed to find offset for multi-pack index entry", short_oid, len); + if (found > 1) + return git_odb__error_ambiguous("found multiple offsets for multi-pack index entry"); + + object_offset = idx->object_offsets + pos * 8; + offset = ntohl(*((uint32_t *)(object_offset + 4))); + if (idx->object_large_offsets && offset & 0x80000000) { + uint32_t object_large_offsets_pos = (uint32_t) (offset ^ 0x80000000); + const unsigned char *object_large_offsets_index = idx->object_large_offsets; + + /* Make sure we're not being sent out of bounds */ + if (object_large_offsets_pos >= idx->num_object_large_offsets) + return git_odb__error_notfound("invalid index into the object large offsets table", short_oid, len); + + object_large_offsets_index += 8 * object_large_offsets_pos; + + offset = (((uint64_t)ntohl(*((uint32_t *)(object_large_offsets_index + 0)))) << 32) | + ntohl(*((uint32_t *)(object_large_offsets_index + 4))); + } + pack_index = ntohl(*((uint32_t *)(object_offset + 0))); + if (pack_index >= git_vector_length(&idx->packfile_names)) + return midx_error("invalid index into the packfile names table"); + e->pack_index = pack_index; + e->offset = offset; + git_oid_fromraw(&e->sha1, current); + return 0; +} + +int git_midx_foreach_entry( + git_midx_file *idx, + git_odb_foreach_cb cb, + void *data) +{ + git_oid oid; + size_t i; + int error; + + GIT_ASSERT_ARG(idx); + + for (i = 0; i < idx->num_objects; ++i) { + if ((error = git_oid_fromraw(&oid, &idx->oid_lookup[i * GIT_OID_RAWSZ])) < 0) + return error; + + if ((error = cb(&oid, data)) != 0) + return git_error_set_after_callback(error); + } + + return error; +} + +int git_midx_close(git_midx_file *idx) +{ + GIT_ASSERT_ARG(idx); + + if (idx->index_map.data) + git_futils_mmap_free(&idx->index_map); + + git_vector_free(&idx->packfile_names); + + return 0; +} + +void git_midx_free(git_midx_file *idx) +{ + if (!idx) + return; + + git_str_dispose(&idx->filename); + git_midx_close(idx); + git__free(idx); +} + +static int packfile__cmp(const void *a_, const void *b_) +{ + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; + + return strcmp(a->pack_name, b->pack_name); +} + +int git_midx_writer_new( + git_midx_writer **out, + const char *pack_dir) +{ + git_midx_writer *w = git__calloc(1, sizeof(git_midx_writer)); + GIT_ERROR_CHECK_ALLOC(w); + + if (git_str_sets(&w->pack_dir, pack_dir) < 0) { + git__free(w); + return -1; + } + git_fs_path_squash_slashes(&w->pack_dir); + + if (git_vector_init(&w->packs, 0, packfile__cmp) < 0) { + git_str_dispose(&w->pack_dir); + git__free(w); + return -1; + } + + *out = w; + return 0; +} + +void git_midx_writer_free(git_midx_writer *w) +{ + struct git_pack_file *p; + size_t i; + + if (!w) + return; + + git_vector_foreach (&w->packs, i, p) + git_mwindow_put_pack(p); + git_vector_free(&w->packs); + git_str_dispose(&w->pack_dir); + git__free(w); +} + +int git_midx_writer_add( + git_midx_writer *w, + const char *idx_path) +{ + git_str idx_path_buf = GIT_STR_INIT; + int error; + struct git_pack_file *p; + + error = git_fs_path_prettify(&idx_path_buf, idx_path, git_str_cstr(&w->pack_dir)); + if (error < 0) + return error; + + error = git_mwindow_get_pack(&p, git_str_cstr(&idx_path_buf)); + git_str_dispose(&idx_path_buf); + if (error < 0) + return error; + + error = git_vector_insert(&w->packs, p); + if (error < 0) { + git_mwindow_put_pack(p); + return error; + } + + return 0; +} + +typedef git_array_t(git_midx_entry) object_entry_array_t; + +struct object_entry_cb_state { + uint32_t pack_index; + object_entry_array_t *object_entries_array; +}; + +static int object_entry__cb(const git_oid *oid, off64_t offset, void *data) +{ + struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; + + git_midx_entry *entry = git_array_alloc(*state->object_entries_array); + GIT_ERROR_CHECK_ALLOC(entry); + + git_oid_cpy(&entry->sha1, oid); + entry->offset = offset; + entry->pack_index = state->pack_index; + + return 0; +} + +static int object_entry__cmp(const void *a_, const void *b_) +{ + const git_midx_entry *a = (const git_midx_entry *)a_; + const git_midx_entry *b = (const git_midx_entry *)b_; + + return git_oid_cmp(&a->sha1, &b->sha1); +} + +static int write_offset(off64_t offset, midx_write_cb write_cb, void *cb_data) +{ + int error; + uint32_t word; + + word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + + return 0; +} + +static int write_chunk_header(int chunk_id, off64_t offset, midx_write_cb write_cb, void *cb_data) +{ + uint32_t word = htonl(chunk_id); + int error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + return write_offset(offset, write_cb, cb_data); + + return 0; +} + +static int midx_write_buf(const char *buf, size_t size, void *data) +{ + git_str *b = (git_str *)data; + return git_str_put(b, buf, size); +} + +struct midx_write_hash_context { + midx_write_cb write_cb; + void *cb_data; + git_hash_ctx *ctx; +}; + +static int midx_write_hash(const char *buf, size_t size, void *data) +{ + struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data; + int error; + + error = git_hash_update(ctx->ctx, buf, size); + if (error < 0) + return error; + + return ctx->write_cb(buf, size, ctx->cb_data); +} + +static int midx_write( + git_midx_writer *w, + midx_write_cb write_cb, + void *cb_data) +{ + int error = 0; + size_t i; + struct git_pack_file *p; + struct git_midx_header hdr = {0}; + uint32_t oid_fanout_count; + uint32_t object_large_offsets_count; + uint32_t oid_fanout[256]; + off64_t offset; + git_str packfile_names = GIT_STR_INIT, + oid_lookup = GIT_STR_INIT, + object_offsets = GIT_STR_INIT, + object_large_offsets = GIT_STR_INIT; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + git_midx_entry *entry; + object_entry_array_t object_entries_array = GIT_ARRAY_INIT; + git_vector object_entries = GIT_VECTOR_INIT; + git_hash_ctx ctx; + struct midx_write_hash_context hash_cb_data = {0}; + + hdr.signature = htonl(MIDX_SIGNATURE); + hdr.version = MIDX_VERSION; + hdr.object_id_version = MIDX_OBJECT_ID_VERSION; + hdr.base_midx_files = 0; + + hash_cb_data.write_cb = write_cb; + hash_cb_data.cb_data = cb_data; + hash_cb_data.ctx = &ctx; + + checksum_size = GIT_HASH_SHA1_SIZE; + error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); + if (error < 0) + return error; + cb_data = &hash_cb_data; + write_cb = midx_write_hash; + + git_vector_sort(&w->packs); + git_vector_foreach (&w->packs, i, p) { + git_str relative_index = GIT_STR_INIT; + struct object_entry_cb_state state = {0}; + size_t path_len; + + state.pack_index = (uint32_t)i; + state.object_entries_array = &object_entries_array; + + error = git_str_sets(&relative_index, p->pack_name); + if (error < 0) + goto cleanup; + error = git_fs_path_make_relative(&relative_index, git_str_cstr(&w->pack_dir)); + if (error < 0) { + git_str_dispose(&relative_index); + goto cleanup; + } + path_len = git_str_len(&relative_index); + if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(&relative_index), ".pack") != 0) { + git_str_dispose(&relative_index); + git_error_set(GIT_ERROR_INVALID, "invalid packfile name: '%s'", p->pack_name); + error = -1; + goto cleanup; + } + path_len -= strlen(".pack"); + + git_str_put(&packfile_names, git_str_cstr(&relative_index), path_len); + git_str_puts(&packfile_names, ".idx"); + git_str_putc(&packfile_names, '\0'); + git_str_dispose(&relative_index); + + error = git_pack_foreach_entry_offset(p, object_entry__cb, &state); + if (error < 0) + goto cleanup; + } + + /* Sort the object entries. */ + error = git_vector_init(&object_entries, git_array_size(object_entries_array), object_entry__cmp); + if (error < 0) + goto cleanup; + git_array_foreach (object_entries_array, i, entry) { + if ((error = git_vector_set(NULL, &object_entries, i, entry)) < 0) + goto cleanup; + } + git_vector_set_sorted(&object_entries, 0); + git_vector_sort(&object_entries); + git_vector_uniq(&object_entries, NULL); + + /* Pad the packfile names so it is a multiple of four. */ + while (git_str_len(&packfile_names) & 3) + git_str_putc(&packfile_names, '\0'); + + /* Fill the OID Fanout table. */ + oid_fanout_count = 0; + for (i = 0; i < 256; i++) { + while (oid_fanout_count < git_vector_length(&object_entries) && + ((const git_midx_entry *)git_vector_get(&object_entries, oid_fanout_count))->sha1.id[0] <= i) + ++oid_fanout_count; + oid_fanout[i] = htonl(oid_fanout_count); + } + + /* Fill the OID Lookup table. */ + git_vector_foreach (&object_entries, i, entry) { + error = git_str_put(&oid_lookup, (char *)&entry->sha1.id, GIT_OID_RAWSZ); + if (error < 0) + goto cleanup; + } + + /* Fill the Object Offsets and Object Large Offsets tables. */ + object_large_offsets_count = 0; + git_vector_foreach (&object_entries, i, entry) { + uint32_t word; + + word = htonl((uint32_t)entry->pack_index); + error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + if (entry->offset >= 0x80000000l) { + word = htonl(0x80000000u | object_large_offsets_count++); + if ((error = write_offset(entry->offset, midx_write_buf, &object_large_offsets)) < 0) + goto cleanup; + } else { + word = htonl((uint32_t)entry->offset & 0x7fffffffu); + } + + error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + } + + /* Write the header. */ + hdr.packfiles = htonl((uint32_t)git_vector_length(&w->packs)); + hdr.chunks = 4; + if (git_str_len(&object_large_offsets) > 0) + hdr.chunks++; + error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); + if (error < 0) + goto cleanup; + + /* Write the chunk headers. */ + offset = sizeof(hdr) + (hdr.chunks + 1) * 12; + error = write_chunk_header(MIDX_PACKFILE_NAMES_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&packfile_names); + error = write_chunk_header(MIDX_OID_FANOUT_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += sizeof(oid_fanout); + error = write_chunk_header(MIDX_OID_LOOKUP_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&oid_lookup); + error = write_chunk_header(MIDX_OBJECT_OFFSETS_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&object_offsets); + if (git_str_len(&object_large_offsets) > 0) { + error = write_chunk_header(MIDX_OBJECT_LARGE_OFFSETS_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&object_large_offsets); + } + error = write_chunk_header(0, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + + /* Write all the chunks. */ + error = write_cb(git_str_cstr(&packfile_names), git_str_len(&packfile_names), cb_data); + if (error < 0) + goto cleanup; + error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&object_offsets), git_str_len(&object_offsets), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&object_large_offsets), git_str_len(&object_large_offsets), cb_data); + if (error < 0) + goto cleanup; + + /* Finalize the checksum and write the trailer. */ + error = git_hash_final(checksum, &ctx); + if (error < 0) + goto cleanup; + error = write_cb((char *)checksum, checksum_size, cb_data); + if (error < 0) + goto cleanup; + +cleanup: + git_array_clear(object_entries_array); + git_vector_free(&object_entries); + git_str_dispose(&packfile_names); + git_str_dispose(&oid_lookup); + git_str_dispose(&object_offsets); + git_str_dispose(&object_large_offsets); + git_hash_ctx_cleanup(&ctx); + return error; +} + +static int midx_write_filebuf(const char *buf, size_t size, void *data) +{ + git_filebuf *f = (git_filebuf *)data; + return git_filebuf_write(f, buf, size); +} + +int git_midx_writer_commit( + git_midx_writer *w) +{ + int error; + int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; + git_str midx_path = GIT_STR_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + + error = git_str_joinpath(&midx_path, git_str_cstr(&w->pack_dir), "multi-pack-index"); + if (error < 0) + return error; + + if (git_repository__fsync_gitdir) + filebuf_flags |= GIT_FILEBUF_FSYNC; + error = git_filebuf_open(&output, git_str_cstr(&midx_path), filebuf_flags, 0644); + git_str_dispose(&midx_path); + if (error < 0) + return error; + + error = midx_write(w, midx_write_filebuf, &output); + if (error < 0) { + git_filebuf_cleanup(&output); + return error; + } + + return git_filebuf_commit(&output); +} + +int git_midx_writer_dump( + git_buf *midx, + git_midx_writer *w) +{ + git_str str = GIT_STR_INIT; + int error; + + if ((error = git_buf_tostr(&str, midx)) < 0 || + (error = midx_write(w, midx_write_buf, &str)) == 0) + error = git_buf_fromstr(midx, &str); + + git_str_dispose(&str); + return error; +} diff --git a/src/libgit2/midx.h b/src/libgit2/midx.h new file mode 100644 index 000000000..bcb0d9a0a --- /dev/null +++ b/src/libgit2/midx.h @@ -0,0 +1,111 @@ +/* + * 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_midx_h__ +#define INCLUDE_midx_h__ + +#include "common.h" + +#include + +#include "git2/sys/midx.h" + +#include "map.h" +#include "mwindow.h" +#include "odb.h" +#include "oid.h" + +/* + * A multi-pack-index file. + * + * This file contains a merged index for multiple independent .pack files. This + * can help speed up locating objects without requiring a garbage collection + * cycle to create a single .pack file. + * + * Support for this feature was added in git 2.21, and requires the + * `core.multiPackIndex` config option to be set. + */ +typedef struct git_midx_file { + git_map index_map; + + /* The table of Packfile Names. */ + git_vector packfile_names; + + /* The OID Fanout table. */ + const uint32_t *oid_fanout; + /* The total number of objects in the index. */ + uint32_t num_objects; + + /* The OID Lookup table. */ + unsigned char *oid_lookup; + + /* The Object Offsets table. Each entry has two 4-byte fields with the pack index and the offset. */ + const unsigned char *object_offsets; + + /* The Object Large Offsets table. */ + const unsigned char *object_large_offsets; + /* The number of entries in the Object Large Offsets table. Each entry has an 8-byte with an offset */ + size_t num_object_large_offsets; + + /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + + /* something like ".git/objects/pack/multi-pack-index". */ + git_str filename; +} git_midx_file; + +/* + * An entry in the multi-pack-index file. Similar in purpose to git_pack_entry. + */ +typedef struct git_midx_entry { + /* The index within idx->packfile_names where the packfile name can be found. */ + size_t pack_index; + /* The offset within the .pack file where the requested object is found. */ + off64_t offset; + /* The SHA-1 hash of the requested object. */ + git_oid sha1; +} git_midx_entry; + +/* + * A writer for `multi-pack-index` files. + */ +struct git_midx_writer { + /* + * The path of the directory where the .pack/.idx files are stored. The + * `multi-pack-index` file will be written to the same directory. + */ + git_str pack_dir; + + /* The list of `git_pack_file`s. */ + git_vector packs; +}; + +int git_midx_open( + git_midx_file **idx_out, + const char *path); +bool git_midx_needs_refresh( + const git_midx_file *idx, + const char *path); +int git_midx_entry_find( + git_midx_entry *e, + git_midx_file *idx, + const git_oid *short_oid, + size_t len); +int git_midx_foreach_entry( + git_midx_file *idx, + git_odb_foreach_cb cb, + void *data); +int git_midx_close(git_midx_file *idx); +void git_midx_free(git_midx_file *idx); + +/* This is exposed for use in the fuzzers. */ +int git_midx_parse( + git_midx_file *idx, + const unsigned char *data, + size_t size); + +#endif diff --git a/src/libgit2/mwindow.c b/src/libgit2/mwindow.c new file mode 100644 index 000000000..ad649490a --- /dev/null +++ b/src/libgit2/mwindow.c @@ -0,0 +1,541 @@ +/* + * 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 "mwindow.h" + +#include "vector.h" +#include "futils.h" +#include "map.h" +#include "runtime.h" +#include "strmap.h" +#include "pack.h" + +#define DEFAULT_WINDOW_SIZE \ + (sizeof(void*) >= 8 \ + ? 1 * 1024 * 1024 * 1024 \ + : 32 * 1024 * 1024) + +#define DEFAULT_MAPPED_LIMIT \ + ((1024 * 1024) * (sizeof(void*) >= 8 ? UINT64_C(8192) : UINT64_C(256))) + +/* default is unlimited */ +#define DEFAULT_FILE_LIMIT 0 + +size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE; +size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; +size_t git_mwindow__file_limit = DEFAULT_FILE_LIMIT; + +/* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */ +git_mutex git__mwindow_mutex; + +/* Whenever you want to read or modify this, grab `git__mwindow_mutex` */ +git_mwindow_ctl git_mwindow__mem_ctl; + +/* Global list of mwindow files, to open packs once across repos */ +git_strmap *git__pack_cache = NULL; + +static void git_mwindow_global_shutdown(void) +{ + git_strmap *tmp = git__pack_cache; + + git_mutex_free(&git__mwindow_mutex); + + git__pack_cache = NULL; + git_strmap_free(tmp); +} + +int git_mwindow_global_init(void) +{ + int error; + + GIT_ASSERT(!git__pack_cache); + + if ((error = git_mutex_init(&git__mwindow_mutex)) < 0 || + (error = git_strmap_new(&git__pack_cache)) < 0) + return error; + + return git_runtime_shutdown_register(git_mwindow_global_shutdown); +} + +int git_mwindow_get_pack(struct git_pack_file **out, const char *path) +{ + struct git_pack_file *pack; + char *packname; + int error; + + if ((error = git_packfile__name(&packname, path)) < 0) + return error; + + if (git_mutex_lock(&git__mwindow_mutex) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex"); + return -1; + } + + pack = git_strmap_get(git__pack_cache, packname); + git__free(packname); + + if (pack != NULL) { + git_atomic32_inc(&pack->refcount); + git_mutex_unlock(&git__mwindow_mutex); + *out = pack; + return 0; + } + + /* If we didn't find it, we need to create it */ + if ((error = git_packfile_alloc(&pack, path)) < 0) { + git_mutex_unlock(&git__mwindow_mutex); + return error; + } + + git_atomic32_inc(&pack->refcount); + + error = git_strmap_set(git__pack_cache, pack->pack_name, pack); + git_mutex_unlock(&git__mwindow_mutex); + if (error < 0) { + git_packfile_free(pack, false); + return error; + } + + *out = pack; + return 0; +} + +int git_mwindow_put_pack(struct git_pack_file *pack) +{ + int count, error; + struct git_pack_file *pack_to_delete = NULL; + + if ((error = git_mutex_lock(&git__mwindow_mutex)) < 0) + return error; + + /* put before get would be a corrupted state */ + GIT_ASSERT(git__pack_cache); + + /* if we cannot find it, the state is corrupted */ + GIT_ASSERT(git_strmap_exists(git__pack_cache, pack->pack_name)); + + count = git_atomic32_dec(&pack->refcount); + if (count == 0) { + git_strmap_delete(git__pack_cache, pack->pack_name); + pack_to_delete = pack; + } + git_mutex_unlock(&git__mwindow_mutex); + git_packfile_free(pack_to_delete, false); + + return 0; +} + +/* + * Free all the windows in a sequence, typically because we're done + * with the file. Needs to hold the git__mwindow_mutex. + */ +static int git_mwindow_free_all_locked(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + size_t i; + + /* + * Remove these windows from the global list + */ + for (i = 0; i < ctl->windowfiles.length; ++i){ + if (git_vector_get(&ctl->windowfiles, i) == mwf) { + git_vector_remove(&ctl->windowfiles, i); + break; + } + } + + if (ctl->windowfiles.length == 0) { + git_vector_free(&ctl->windowfiles); + ctl->windowfiles.contents = NULL; + } + + while (mwf->windows) { + git_mwindow *w = mwf->windows; + GIT_ASSERT(w->inuse_cnt == 0); + + ctl->mapped -= w->window_map.len; + ctl->open_windows--; + + git_futils_mmap_free(&w->window_map); + + mwf->windows = w->next; + git__free(w); + } + + return 0; +} + +int git_mwindow_free_all(git_mwindow_file *mwf) +{ + int error; + + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return -1; + } + + error = git_mwindow_free_all_locked(mwf); + + git_mutex_unlock(&git__mwindow_mutex); + + return error; +} + +/* + * Check if a window 'win' contains both the address 'offset' and 'extra'. + * + * 'extra' is the size of the hash we're using as we always want to make sure + * that it's contained. + */ +int git_mwindow_contains(git_mwindow *win, off64_t offset, off64_t extra) +{ + off64_t win_off = win->offset; + return win_off <= offset + && (offset + extra) <= (off64_t)(win_off + win->window_map.len); +} + +#define GIT_MWINDOW__LRU -1 +#define GIT_MWINDOW__MRU 1 + +/* + * Find the least- or most-recently-used window in a file that is not currently + * being used. The 'only_unused' flag controls whether the caller requires the + * file to only have unused windows. If '*out_window' is non-null, it is used as + * a starting point for the comparison. + * + * Returns whether such a window was found in the file. + */ +static bool git_mwindow_scan_recently_used( + git_mwindow_file *mwf, + git_mwindow **out_window, + git_mwindow **out_last, + bool only_unused, + int comparison_sign) +{ + git_mwindow *w, *w_last; + git_mwindow *lru_window = NULL, *lru_last = NULL; + bool found = false; + + GIT_ASSERT_ARG(mwf); + GIT_ASSERT_ARG(out_window); + + lru_window = *out_window; + if (out_last) + lru_last = *out_last; + + for (w_last = NULL, w = mwf->windows; w; w_last = w, w = w->next) { + if (w->inuse_cnt) { + if (only_unused) + return false; + /* This window is currently being used. Skip it. */ + continue; + } + + /* + * If the current one is more (or less) recent than the last one, + * store it in the output parameter. If lru_window is NULL, + * it's the first loop, so store it as well. + */ + if (!lru_window || (comparison_sign * w->last_used) > lru_window->last_used) { + lru_window = w; + lru_last = w_last; + found = true; + } + } + + if (!found) + return false; + + *out_window = lru_window; + if (out_last) + *out_last = lru_last; + return true; +} + +/* + * Close the least recently used window (that is currently not being used) out + * of all the files. Called under lock from new_window_locked. + */ +static int git_mwindow_close_lru_window_locked(void) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow_file *cur; + size_t i; + git_mwindow *lru_window = NULL, *lru_last = NULL, **list = NULL; + + git_vector_foreach(&ctl->windowfiles, i, cur) { + if (git_mwindow_scan_recently_used( + cur, &lru_window, &lru_last, false, GIT_MWINDOW__LRU)) { + list = &cur->windows; + } + } + + if (!lru_window) { + git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU"); + return -1; + } + + ctl->mapped -= lru_window->window_map.len; + git_futils_mmap_free(&lru_window->window_map); + + if (lru_last) + lru_last->next = lru_window->next; + else + *list = lru_window->next; + + git__free(lru_window); + ctl->open_windows--; + + return 0; +} + +/* + * Finds the file that does not have any open windows AND whose + * most-recently-used window is the least-recently used one across all + * currently open files. + * + * Called under lock from new_window_locked. + */ +static int git_mwindow_find_lru_file_locked(git_mwindow_file **out) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow_file *lru_file = NULL, *current_file = NULL; + git_mwindow *lru_window = NULL; + size_t i; + + git_vector_foreach(&ctl->windowfiles, i, current_file) { + git_mwindow *mru_window = NULL; + if (!git_mwindow_scan_recently_used( + current_file, &mru_window, NULL, true, GIT_MWINDOW__MRU)) { + continue; + } + if (!lru_window || lru_window->last_used > mru_window->last_used) { + lru_window = mru_window; + lru_file = current_file; + } + } + + if (!lru_file) { + git_error_set(GIT_ERROR_OS, "failed to close memory window file; couldn't find LRU"); + return -1; + } + + *out = lru_file; + return 0; +} + +/* This gets called under lock from git_mwindow_open */ +static git_mwindow *new_window_locked( + git_file fd, + off64_t size, + off64_t offset) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + size_t walign = git_mwindow__window_size / 2; + off64_t len; + git_mwindow *w; + + w = git__calloc(1, sizeof(*w)); + + if (w == NULL) + return NULL; + + w->offset = (offset / walign) * walign; + + len = size - w->offset; + if (len > (off64_t)git_mwindow__window_size) + len = (off64_t)git_mwindow__window_size; + + ctl->mapped += (size_t)len; + + while (git_mwindow__mapped_limit < ctl->mapped && + git_mwindow_close_lru_window_locked() == 0) /* nop */; + + /* + * We treat `mapped_limit` as a soft limit. If we can't find a + * window to close and are above the limit, we still mmap the new + * window. + */ + + if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { + /* + * The first error might be down to memory fragmentation even if + * we're below our soft limits, so free up what we can and try again. + */ + + while (git_mwindow_close_lru_window_locked() == 0) + /* nop */; + + if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { + git__free(w); + return NULL; + } + } + + ctl->mmap_calls++; + ctl->open_windows++; + + if (ctl->mapped > ctl->peak_mapped) + ctl->peak_mapped = ctl->mapped; + + if (ctl->open_windows > ctl->peak_open_windows) + ctl->peak_open_windows = ctl->open_windows; + + return w; +} + +/* + * Open a new window, closing the least recenty used until we have + * enough space. Don't forget to add it to your list + */ +unsigned char *git_mwindow_open( + git_mwindow_file *mwf, + git_mwindow **cursor, + off64_t offset, + size_t extra, + unsigned int *left) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow *w = *cursor; + + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return NULL; + } + + if (!w || !(git_mwindow_contains(w, offset, extra))) { + if (w) { + w->inuse_cnt--; + } + + for (w = mwf->windows; w; w = w->next) { + if (git_mwindow_contains(w, offset, extra)) + break; + } + + /* + * If there isn't a suitable window, we need to create a new + * one. + */ + if (!w) { + w = new_window_locked(mwf->fd, mwf->size, offset); + if (w == NULL) { + git_mutex_unlock(&git__mwindow_mutex); + return NULL; + } + w->next = mwf->windows; + mwf->windows = w; + } + } + + /* If we changed w, store it in the cursor */ + if (w != *cursor) { + w->last_used = ctl->used_ctr++; + w->inuse_cnt++; + *cursor = w; + } + + offset -= w->offset; + + if (left) + *left = (unsigned int)(w->window_map.len - offset); + + git_mutex_unlock(&git__mwindow_mutex); + return (unsigned char *) w->window_map.data + offset; +} + +int git_mwindow_file_register(git_mwindow_file *mwf) +{ + git_vector closed_files = GIT_VECTOR_INIT; + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + int error; + size_t i; + git_mwindow_file *closed_file = NULL; + + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return -1; + } + + if (ctl->windowfiles.length == 0 && + (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < 0) { + git_mutex_unlock(&git__mwindow_mutex); + goto cleanup; + } + + if (git_mwindow__file_limit) { + git_mwindow_file *lru_file; + while (git_mwindow__file_limit <= ctl->windowfiles.length && + git_mwindow_find_lru_file_locked(&lru_file) == 0) { + if ((error = git_vector_insert(&closed_files, lru_file)) < 0) { + /* + * Exceeding the file limit seems preferable to being open to + * data races that can end up corrupting the heap. + */ + break; + } + git_mwindow_free_all_locked(lru_file); + } + } + + error = git_vector_insert(&ctl->windowfiles, mwf); + git_mutex_unlock(&git__mwindow_mutex); + if (error < 0) + goto cleanup; + + /* + * Once we have released the global windowfiles lock, we can close each + * individual file. Before doing so, acquire that file's lock to avoid + * closing a file that is currently being used. + */ + git_vector_foreach(&closed_files, i, closed_file) { + error = git_mutex_lock(&closed_file->lock); + if (error < 0) + continue; + p_close(closed_file->fd); + closed_file->fd = -1; + git_mutex_unlock(&closed_file->lock); + } + +cleanup: + git_vector_free(&closed_files); + return error; +} + +void git_mwindow_file_deregister(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow_file *cur; + size_t i; + + if (git_mutex_lock(&git__mwindow_mutex)) + return; + + git_vector_foreach(&ctl->windowfiles, i, cur) { + if (cur == mwf) { + git_vector_remove(&ctl->windowfiles, i); + git_mutex_unlock(&git__mwindow_mutex); + return; + } + } + git_mutex_unlock(&git__mwindow_mutex); +} + +void git_mwindow_close(git_mwindow **window) +{ + git_mwindow *w = *window; + if (w) { + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return; + } + + w->inuse_cnt--; + git_mutex_unlock(&git__mwindow_mutex); + *window = NULL; + } +} diff --git a/src/libgit2/mwindow.h b/src/libgit2/mwindow.h new file mode 100644 index 000000000..e32ab99d4 --- /dev/null +++ b/src/libgit2/mwindow.h @@ -0,0 +1,54 @@ +/* + * 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_mwindow__ +#define INCLUDE_mwindow__ + +#include "common.h" + +#include "map.h" +#include "vector.h" + +typedef struct git_mwindow { + struct git_mwindow *next; + git_map window_map; + off64_t offset; + size_t last_used; + size_t inuse_cnt; +} git_mwindow; + +typedef struct git_mwindow_file { + git_mutex lock; /* protects updates to fd */ + git_mwindow *windows; + int fd; + off64_t size; +} git_mwindow_file; + +typedef struct git_mwindow_ctl { + size_t mapped; + unsigned int open_windows; + unsigned int mmap_calls; + unsigned int peak_open_windows; + size_t peak_mapped; + size_t used_ctr; + git_vector windowfiles; +} git_mwindow_ctl; + +int git_mwindow_contains(git_mwindow *win, off64_t offset, off64_t extra); +int git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ +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); + +extern int git_mwindow_global_init(void); + +struct git_pack_file; /* just declaration to avoid cyclical includes */ +int git_mwindow_get_pack(struct git_pack_file **out, const char *path); +int git_mwindow_put_pack(struct git_pack_file *pack); + +#endif diff --git a/src/libgit2/netops.c b/src/libgit2/netops.c new file mode 100644 index 000000000..00640c600 --- /dev/null +++ b/src/libgit2/netops.c @@ -0,0 +1,124 @@ +/* + * 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 "netops.h" + +#include +#include "git2/errors.h" + +#include "posix.h" +#include "str.h" +#include "runtime.h" + +int gitno_recv(gitno_buffer *buf) +{ + return buf->recv(buf); +} + +void gitno_buffer_setup_callback( + gitno_buffer *buf, + char *data, + size_t len, + int (*recv)(gitno_buffer *buf), void *cb_data) +{ + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->recv = recv; + buf->cb_data = cb_data; +} + +static int recv_stream(gitno_buffer *buf) +{ + git_stream *io = (git_stream *) buf->cb_data; + size_t readlen = buf->len - buf->offset; + ssize_t ret; + + 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 (int)ret; +} + +void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) +{ + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->recv = recv_stream; + buf->cb_data = st; +} + +/* Consume up to ptr and move the rest of the buffer to the beginning */ +int gitno_consume(gitno_buffer *buf, const char *ptr) +{ + size_t consumed; + + GIT_ASSERT(ptr - buf->data >= 0); + GIT_ASSERT(ptr - buf->data <= (int) buf->len); + + consumed = ptr - buf->data; + + memmove(buf->data, ptr, buf->offset - consumed); + memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); + buf->offset -= consumed; + + return 0; +} + +/* Consume const bytes and move the rest of the buffer to the beginning */ +void gitno_consume_n(gitno_buffer *buf, size_t cons) +{ + memmove(buf->data, buf->data + cons, buf->len - buf->offset); + memset(buf->data + cons, 0x0, buf->len - buf->offset); + buf->offset -= cons; +} + +/* Match host names according to RFC 2818 rules */ +int gitno__match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = git__tolower(*pattern++); + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = git__tolower(*host); + if (c == h) + return gitno__match_host(pattern, host++); + if (h == '.') + return gitno__match_host(pattern, host); + host++; + } + return -1; + } + + if (c != git__tolower(*host++)) + return -1; + } + + return -1; +} diff --git a/src/libgit2/netops.h b/src/libgit2/netops.h new file mode 100644 index 000000000..56f968534 --- /dev/null +++ b/src/libgit2/netops.h @@ -0,0 +1,68 @@ +/* + * 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_netops_h__ +#define INCLUDE_netops_h__ + +#include "common.h" + +#include "posix.h" +#include "stream.h" +#include "net.h" + +#ifdef GIT_OPENSSL +# include "streams/openssl.h" +#endif + +typedef struct gitno_ssl { +#ifdef GIT_OPENSSL + SSL *ssl; +#else + size_t dummy; +#endif +} gitno_ssl; + +/* Represents a socket that may or may not be using SSL */ +typedef struct gitno_socket { + GIT_SOCKET socket; + gitno_ssl ssl; +} gitno_socket; + +typedef struct gitno_buffer { + char *data; + size_t len; + size_t offset; + int (*recv)(struct gitno_buffer *buffer); + void *cb_data; +} gitno_buffer; + +/* Flags to gitno_connect */ +enum { + /* Attempt to create an SSL connection. */ + GITNO_CONNECT_SSL = 1 +}; + +/** + * Check if the name in a cert matches the wanted hostname + * + * Check if a pattern from a certificate matches the hostname we + * wanted to connect to according to RFC2818 rules (which specifies + * HTTP over TLS). Mainly, an asterisk matches anything, but is + * limited to a single url component. + * + * Note that this does not set an error message. It expects the user + * to provide the message for the user. + */ +int gitno__match_host(const char *pattern, const char *host); + +void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len); +void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); +int gitno_recv(gitno_buffer *buf); + +int gitno_consume(gitno_buffer *buf, const char *ptr); +void gitno_consume_n(gitno_buffer *buf, size_t cons); + +#endif diff --git a/src/libgit2/notes.c b/src/libgit2/notes.c new file mode 100644 index 000000000..d1a2b0f64 --- /dev/null +++ b/src/libgit2/notes.c @@ -0,0 +1,809 @@ +/* + * 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 "notes.h" + +#include "buf.h" +#include "refs.h" +#include "config.h" +#include "iterator.h" +#include "signature.h" +#include "blob.h" + +static int note_error_notfound(void) +{ + git_error_set(GIT_ERROR_INVALID, "note could not be found"); + return GIT_ENOTFOUND; +} + +static int find_subtree_in_current_level( + git_tree **out, + git_repository *repo, + git_tree *parent, + const char *annotated_object_sha, + int fanout) +{ + size_t i; + const git_tree_entry *entry; + + *out = NULL; + + if (parent == NULL) + return note_error_notfound(); + + for (i = 0; i < git_tree_entrycount(parent); i++) { + entry = git_tree_entry_byindex(parent, i); + + if (!git__ishex(git_tree_entry_name(entry))) + continue; + + if (S_ISDIR(git_tree_entry_filemode(entry)) + && strlen(git_tree_entry_name(entry)) == 2 + && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2)) + return git_tree_lookup(out, repo, git_tree_entry_id(entry)); + + /* Not a DIR, so do we have an already existing blob? */ + if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout)) + return GIT_EEXISTS; + } + + return note_error_notfound(); +} + +static int find_subtree_r(git_tree **out, git_tree *root, + git_repository *repo, const char *target, int *fanout) +{ + int error; + git_tree *subtree = NULL; + + *out = NULL; + + error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout); + if (error == GIT_EEXISTS) + return git_tree_lookup(out, repo, git_tree_id(root)); + + if (error < 0) + return error; + + *fanout += 2; + error = find_subtree_r(out, subtree, repo, target, fanout); + git_tree_free(subtree); + + return error; +} + +static int find_blob(git_oid *blob, git_tree *tree, const char *target) +{ + size_t i; + const git_tree_entry *entry; + + for (i=0; iid, note_oid); + + if (git_signature_dup(¬e->author, git_commit_author(commit)) < 0 || + git_signature_dup(¬e->committer, git_commit_committer(commit)) < 0) + return -1; + + blobsize = git_blob_rawsize(blob); + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + + note->message = git__strndup(git_blob_rawcontent(blob), (size_t)blobsize); + GIT_ERROR_CHECK_ALLOC(note->message); + + *out = note; + return 0; +} + +static int note_lookup( + git_note **out, + git_repository *repo, + git_commit *commit, + git_tree *tree, + const char *target) +{ + int error, fanout = 0; + git_oid oid; + git_blob *blob = NULL; + git_note *note = NULL; + git_tree *subtree = NULL; + + if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0) + goto cleanup; + + if ((error = find_blob(&oid, subtree, target + fanout)) < 0) + goto cleanup; + + if ((error = git_blob_lookup(&blob, repo, &oid)) < 0) + goto cleanup; + + if ((error = note_new(¬e, &oid, commit, blob)) < 0) + goto cleanup; + + *out = note; + +cleanup: + git_tree_free(subtree); + git_blob_free(blob); + return error; +} + +static int note_remove( + git_oid *notes_commit_out, + git_repository *repo, + const git_signature *author, const git_signature *committer, + const char *notes_ref, git_tree *tree, + const char *target, git_commit **parents) +{ + int error; + git_tree *tree_after_removal = NULL; + git_oid oid; + + if ((error = manipulate_note_in_tree_r( + &tree_after_removal, repo, tree, NULL, target, 0, + remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0) + goto cleanup; + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_RM, + tree_after_removal, + *parents == NULL ? 0 : 1, + (const git_commit **) parents); + + if (error < 0) + goto cleanup; + + if (notes_commit_out) + git_oid_cpy(notes_commit_out, &oid); + +cleanup: + git_tree_free(tree_after_removal); + return error; +} + +static int note_get_default_ref(git_str *out, git_repository *repo) +{ + git_config *cfg; + int error; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + error = git_config__get_string_buf(out, cfg, "core.notesref"); + + if (error == GIT_ENOTFOUND) + error = git_str_puts(out, GIT_NOTES_DEFAULT_REF); + + return error; +} + +static int normalize_namespace(git_str *out, git_repository *repo, const char *notes_ref) +{ + if (notes_ref) + return git_str_puts(out, notes_ref); + + return note_get_default_ref(out, repo); +} + +static int retrieve_note_commit( + git_commit **commit_out, + git_str *notes_ref_out, + git_repository *repo, + const char *notes_ref) +{ + int error; + git_oid oid; + + if ((error = normalize_namespace(notes_ref_out, repo, notes_ref)) < 0) + return error; + + if ((error = git_reference_name_to_id(&oid, repo, notes_ref_out->ptr)) < 0) + return error; + + if (git_commit_lookup(commit_out, repo, &oid) < 0) + return error; + + return 0; +} + +int git_note_commit_read( + git_note **out, + git_repository *repo, + git_commit *notes_commit, + const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + error = note_lookup(out, repo, notes_commit, tree, target); + +cleanup: + git_tree_free(tree); + return error; +} + +int git_note_read(git_note **out, git_repository *repo, + const char *notes_ref_in, const git_oid *oid) +{ + int error; + git_str notes_ref = GIT_STR_INIT; + git_commit *commit = NULL; + + error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); + + if (error < 0) + goto cleanup; + + error = git_note_commit_read(out, repo, commit, oid); + +cleanup: + git_str_dispose(¬es_ref); + git_commit_free(commit); + return error; +} + +int git_note_commit_create( + git_oid *notes_commit_out, + git_oid *notes_blob_out, + git_repository *repo, + git_commit *parent, + const git_signature *author, + const git_signature *committer, + const git_oid *oid, + const char *note, + int allow_note_overwrite) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if (parent != NULL && (error = git_commit_tree(&tree, parent)) < 0) + goto cleanup; + + error = note_write(notes_commit_out, notes_blob_out, repo, author, + committer, NULL, note, tree, target, &parent, allow_note_overwrite); + + if (error < 0) + goto cleanup; + +cleanup: + git_tree_free(tree); + return error; +} + +int git_note_create( + git_oid *out, + git_repository *repo, + const char *notes_ref_in, + const git_signature *author, + const git_signature *committer, + const git_oid *oid, + const char *note, + int allow_note_overwrite) +{ + int error; + git_str notes_ref = GIT_STR_INIT; + git_commit *existing_notes_commit = NULL; + git_reference *ref = NULL; + git_oid notes_blob_oid, notes_commit_oid; + + error = retrieve_note_commit(&existing_notes_commit, ¬es_ref, + repo, notes_ref_in); + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + error = git_note_commit_create(¬es_commit_oid, + ¬es_blob_oid, + repo, existing_notes_commit, author, + committer, oid, note, + allow_note_overwrite); + if (error < 0) + goto cleanup; + + error = git_reference_create(&ref, repo, notes_ref.ptr, + ¬es_commit_oid, 1, NULL); + + if (out != NULL) + git_oid_cpy(out, ¬es_blob_oid); + +cleanup: + git_str_dispose(¬es_ref); + git_commit_free(existing_notes_commit); + git_reference_free(ref); + return error; +} + +int git_note_commit_remove( + git_oid *notes_commit_out, + git_repository *repo, + git_commit *notes_commit, + const git_signature *author, + const git_signature *committer, + const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + error = note_remove(notes_commit_out, + repo, author, committer, NULL, tree, target, ¬es_commit); + +cleanup: + git_tree_free(tree); + return error; +} + +int git_note_remove(git_repository *repo, const char *notes_ref_in, + const git_signature *author, const git_signature *committer, + const git_oid *oid) +{ + int error; + git_str notes_ref_target = GIT_STR_INIT; + git_commit *existing_notes_commit = NULL; + git_oid new_notes_commit; + git_reference *notes_ref = NULL; + + error = retrieve_note_commit(&existing_notes_commit, ¬es_ref_target, + repo, notes_ref_in); + + if (error < 0) + goto cleanup; + + error = git_note_commit_remove(&new_notes_commit, repo, + existing_notes_commit, author, committer, oid); + if (error < 0) + goto cleanup; + + error = git_reference_create(¬es_ref, repo, notes_ref_target.ptr, + &new_notes_commit, 1, NULL); + +cleanup: + git_str_dispose(¬es_ref_target); + git_reference_free(notes_ref); + git_commit_free(existing_notes_commit); + return error; +} + +int git_note_default_ref(git_buf *out, git_repository *repo) +{ + GIT_BUF_WRAP_PRIVATE(out, note_get_default_ref, repo); +} + +const git_signature *git_note_committer(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return note->committer; +} + +const git_signature *git_note_author(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return note->author; +} + +const char *git_note_message(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return note->message; +} + +const git_oid *git_note_id(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return ¬e->id; +} + +void git_note_free(git_note *note) +{ + if (note == NULL) + return; + + git_signature_free(note->committer); + git_signature_free(note->author); + git__free(note->message); + git__free(note); +} + +static int process_entry_path( + const char *entry_path, + git_oid *annotated_object_id) +{ + int error = 0; + size_t i = 0, j = 0, len; + git_str buf = GIT_STR_INIT; + + if ((error = git_str_puts(&buf, entry_path)) < 0) + goto cleanup; + + len = git_str_len(&buf); + + while (i < len) { + if (buf.ptr[i] == '/') { + i++; + continue; + } + + if (git__fromhex(buf.ptr[i]) < 0) { + /* This is not a note entry */ + goto cleanup; + } + + if (i != j) + buf.ptr[j] = buf.ptr[i]; + + i++; + j++; + } + + buf.ptr[j] = '\0'; + buf.size = j; + + if (j != GIT_OID_HEXSZ) { + /* This is not a note entry */ + goto cleanup; + } + + error = git_oid_fromstr(annotated_object_id, buf.ptr); + +cleanup: + git_str_dispose(&buf); + return error; +} + +int git_note_foreach( + git_repository *repo, + const char *notes_ref, + git_note_foreach_cb note_cb, + void *payload) +{ + int error; + git_note_iterator *iter = NULL; + git_oid note_id, annotated_id; + + if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0) + return error; + + while (!(error = git_note_next(¬e_id, &annotated_id, iter))) { + if ((error = note_cb(¬e_id, &annotated_id, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_note_iterator_free(iter); + return error; +} + +void git_note_iterator_free(git_note_iterator *it) +{ + if (it == NULL) + return; + + git_iterator_free(it); +} + +int git_note_commit_iterator_new( + git_note_iterator **it, + git_commit *notes_commit) +{ + int error; + git_tree *tree; + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) + git_iterator_free(*it); + +cleanup: + git_tree_free(tree); + + return error; +} + +int git_note_iterator_new( + git_note_iterator **it, + git_repository *repo, + const char *notes_ref_in) +{ + int error; + git_commit *commit = NULL; + git_str notes_ref = GIT_STR_INIT; + + error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); + if (error < 0) + goto cleanup; + + error = git_note_commit_iterator_new(it, commit); + +cleanup: + git_str_dispose(¬es_ref); + git_commit_free(commit); + + return error; +} + +int git_note_next( + git_oid *note_id, + git_oid *annotated_id, + git_note_iterator *it) +{ + int error; + const git_index_entry *item; + + if ((error = git_iterator_current(&item, it)) < 0) + return error; + + git_oid_cpy(note_id, &item->id); + + if ((error = process_entry_path(item->path, annotated_id)) < 0) + return error; + + if ((error = git_iterator_advance(NULL, it)) < 0 && error != GIT_ITEROVER) + return error; + + return 0; +} diff --git a/src/libgit2/notes.h b/src/libgit2/notes.h new file mode 100644 index 000000000..2168e4595 --- /dev/null +++ b/src/libgit2/notes.h @@ -0,0 +1,32 @@ +/* + * 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_note_h__ +#define INCLUDE_note_h__ + +#include "common.h" + +#include "git2/oid.h" +#include "git2/types.h" + +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" + +#define GIT_NOTES_DEFAULT_MSG_ADD \ + "Notes added by 'git_note_create' from libgit2" + +#define GIT_NOTES_DEFAULT_MSG_RM \ + "Notes removed by 'git_note_remove' from libgit2" + +struct git_note { + git_oid id; + + git_signature *author; + git_signature *committer; + + char *message; +}; + +#endif diff --git a/src/libgit2/object.c b/src/libgit2/object.c new file mode 100644 index 000000000..b828f88f6 --- /dev/null +++ b/src/libgit2/object.c @@ -0,0 +1,602 @@ +/* + * 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 "object.h" + +#include "git2/object.h" + +#include "repository.h" + +#include "buf.h" +#include "commit.h" +#include "hash.h" +#include "tree.h" +#include "blob.h" +#include "oid.h" +#include "tag.h" + +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 */ + size_t size; /* size in bytes of the object structure */ + + int (*parse)(void *self, git_odb_object *obj); + int (*parse_raw)(void *self, const char *data, size_t size); + void (*free)(void *self); +} git_object_def; + +static git_object_def git_objects_table[] = { + /* 0 = GIT_OBJECT__EXT1 */ + { "", 0, NULL, NULL, NULL }, + + /* 1 = GIT_OBJECT_COMMIT */ + { "commit", sizeof(git_commit), git_commit__parse, git_commit__parse_raw, git_commit__free }, + + /* 2 = GIT_OBJECT_TREE */ + { "tree", sizeof(git_tree), git_tree__parse, git_tree__parse_raw, git_tree__free }, + + /* 3 = GIT_OBJECT_BLOB */ + { "blob", sizeof(git_blob), git_blob__parse, git_blob__parse_raw, git_blob__free }, + + /* 4 = GIT_OBJECT_TAG */ + { "tag", sizeof(git_tag), git_tag__parse, git_tag__parse_raw, git_tag__free }, + + /* 5 = GIT_OBJECT__EXT2 */ + { "", 0, NULL, NULL, NULL }, + /* 6 = GIT_OBJECT_OFS_DELTA */ + { "OFS_DELTA", 0, NULL, NULL, NULL }, + /* 7 = GIT_OBJECT_REF_DELTA */ + { "REF_DELTA", 0, NULL, NULL, NULL }, +}; + +int git_object__from_raw( + git_object **object_out, + const char *data, + size_t size, + git_object_t type) +{ + git_object_def *def; + git_object *object; + size_t object_size; + int error; + + GIT_ASSERT_ARG(object_out); + *object_out = NULL; + + /* Validate type match */ + if (type != GIT_OBJECT_BLOB && type != GIT_OBJECT_TREE && type != GIT_OBJECT_COMMIT && type != GIT_OBJECT_TAG) { + git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); + return GIT_ENOTFOUND; + } + + if ((object_size = git_object__size(type)) == 0) { + git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); + return GIT_ENOTFOUND; + } + + /* Allocate and initialize base object */ + object = git__calloc(1, object_size); + GIT_ERROR_CHECK_ALLOC(object); + object->cached.flags = GIT_CACHE_STORE_PARSED; + object->cached.type = type; + if ((error = git_odb_hash(&object->cached.oid, data, size, type)) < 0) + return error; + + /* Parse raw object data */ + def = &git_objects_table[type]; + GIT_ASSERT(def->free && def->parse_raw); + + if ((error = def->parse_raw(object, data, size)) < 0) { + def->free(object); + return error; + } + + git_cached_obj_incref(object); + *object_out = object; + + return 0; +} + +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_object_t type) +{ + int error; + size_t object_size; + git_object_def *def; + git_object *object = NULL; + + GIT_ASSERT_ARG(object_out); + *object_out = NULL; + + /* Validate type match */ + if (type != GIT_OBJECT_ANY && type != odb_obj->cached.type) { + git_error_set(GIT_ERROR_INVALID, + "the requested type does not match the type in the ODB"); + return GIT_ENOTFOUND; + } + + if ((object_size = git_object__size(odb_obj->cached.type)) == 0) { + git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); + return GIT_ENOTFOUND; + } + + /* Allocate and initialize base object */ + object = git__calloc(1, object_size); + GIT_ERROR_CHECK_ALLOC(object); + + git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); + object->cached.type = odb_obj->cached.type; + object->cached.size = odb_obj->cached.size; + object->repo = repo; + + /* Parse raw object data */ + def = &git_objects_table[odb_obj->cached.type]; + GIT_ASSERT(def->free && def->parse); + + if ((error = def->parse(object, odb_obj)) < 0) { + /* + * parse returns EINVALID on invalid data; downgrade + * that to a normal -1 error code. + */ + def->free(object); + return -1; + } + + *object_out = git_cache_store_parsed(&repo->objects, object); + return 0; +} + +void git_object__free(void *obj) +{ + git_object_t type = ((git_object *)obj)->cached.type; + + if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) || + !git_objects_table[type].free) + git__free(obj); + else + git_objects_table[type].free(obj); +} + +int git_object_lookup_prefix( + git_object **object_out, + git_repository *repo, + const git_oid *id, + size_t len, + git_object_t type) +{ + git_object *object = NULL; + git_odb *odb = NULL; + git_odb_object *odb_obj = NULL; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(object_out); + GIT_ASSERT_ARG(id); + + if (len < GIT_OID_MINPREFIXLEN) { + git_error_set(GIT_ERROR_OBJECT, "ambiguous lookup - OID prefix is too short"); + return GIT_EAMBIGUOUS; + } + + error = git_repository_odb__weakptr(&odb, repo); + if (error < 0) + return error; + + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + if (len == GIT_OID_HEXSZ) { + git_cached_obj *cached = NULL; + + /* We want to match the full id : we can first look up in the cache, + * since there is no need to check for non ambiguousity + */ + cached = git_cache_get_any(&repo->objects, id); + if (cached != NULL) { + if (cached->flags == GIT_CACHE_STORE_PARSED) { + object = (git_object *)cached; + + 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 the ODB"); + return GIT_ENOTFOUND; + } + + *object_out = object; + return 0; + } else if (cached->flags == GIT_CACHE_STORE_RAW) { + odb_obj = (git_odb_object *)cached; + } else { + GIT_ASSERT(!"Wrong caching type in the global object cache"); + } + } else { + /* Object was not found in the cache, let's explore the backends. + * We could just use git_odb_read_unique_short_oid, + * it is the same cost for packed and loose object backends, + * but it may be much more costly for sqlite and hiredis. + */ + error = git_odb_read(&odb_obj, odb, id); + } + } else { + git_oid short_oid = {{ 0 }}; + + git_oid__cpy_prefix(&short_oid, id, len); + + /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have + * 2 options : + * - We always search in the cache first. If we find that short oid is + * ambiguous, we can stop. But in all the other cases, we must then + * explore all the backends (to find an object if there was match, + * or to check that oid is not ambiguous if we have found 1 match in + * the cache) + * - We never explore the cache, go right to exploring the backends + * We chose the latter : we explore directly the backends. + */ + error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len); + } + + if (error < 0) + return error; + + GIT_ASSERT(odb_obj); + error = git_object__from_odb_object(object_out, repo, odb_obj, type); + + git_odb_object_free(odb_obj); + + return error; +} + +int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_object_t type) { + return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type); +} + +void git_object_free(git_object *object) +{ + if (object == NULL) + return; + + git_cached_obj_decref(object); +} + +const git_oid *git_object_id(const git_object *obj) +{ + GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); + return &obj->cached.oid; +} + +git_object_t git_object_type(const git_object *obj) +{ + GIT_ASSERT_ARG_WITH_RETVAL(obj, GIT_OBJECT_INVALID); + return obj->cached.type; +} + +git_repository *git_object_owner(const git_object *obj) +{ + GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); + return obj->repo; +} + +const char *git_object_type2string(git_object_t type) +{ + if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) + return ""; + + return git_objects_table[type].str; +} + +git_object_t git_object_string2type(const char *str) +{ + if (!str) + return GIT_OBJECT_INVALID; + + return git_object_stringn2type(str, strlen(str)); +} + +git_object_t git_object_stringn2type(const char *str, size_t len) +{ + size_t i; + + if (!str || !len || !*str) + return GIT_OBJECT_INVALID; + + for (i = 0; i < ARRAY_SIZE(git_objects_table); i++) + if (*git_objects_table[i].str && + !git__prefixncmp(str, len, git_objects_table[i].str)) + return (git_object_t)i; + + return GIT_OBJECT_INVALID; +} + +int git_object_typeisloose(git_object_t type) +{ + if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) + return 0; + + return (git_objects_table[type].size > 0) ? 1 : 0; +} + +size_t git_object__size(git_object_t type) +{ + if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) + return 0; + + return git_objects_table[type].size; +} + +static int dereference_object(git_object **dereferenced, git_object *obj) +{ + git_object_t type = git_object_type(obj); + + switch (type) { + case GIT_OBJECT_COMMIT: + return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); + + case GIT_OBJECT_TAG: + return git_tag_target(dereferenced, (git_tag*)obj); + + case GIT_OBJECT_BLOB: + case GIT_OBJECT_TREE: + return GIT_EPEEL; + + default: + return GIT_EINVALIDSPEC; + } +} + +static int peel_error(int error, const git_oid *oid, git_object_t type) +{ + const char *type_name; + char hex_oid[GIT_OID_HEXSZ + 1]; + + type_name = git_object_type2string(type); + + git_oid_fmt(hex_oid, oid); + hex_oid[GIT_OID_HEXSZ] = '\0'; + + git_error_set(GIT_ERROR_OBJECT, "the git_object of id '%s' can not be " + "successfully peeled into a %s (git_object_t=%i).", hex_oid, type_name, type); + + return error; +} + +static int check_type_combination(git_object_t type, git_object_t target) +{ + if (type == target) + return 0; + + switch (type) { + case GIT_OBJECT_BLOB: + case GIT_OBJECT_TREE: + /* a blob or tree can never be peeled to anything but themselves */ + return GIT_EINVALIDSPEC; + break; + case GIT_OBJECT_COMMIT: + /* a commit can only be peeled to a tree */ + if (target != GIT_OBJECT_TREE && target != GIT_OBJECT_ANY) + return GIT_EINVALIDSPEC; + break; + case GIT_OBJECT_TAG: + /* a tag may point to anything, so we let anything through */ + break; + default: + return GIT_EINVALIDSPEC; + } + + return 0; +} + +int git_object_peel( + git_object **peeled, + const git_object *object, + git_object_t target_type) +{ + git_object *source, *deref = NULL; + int error; + + GIT_ASSERT_ARG(object); + GIT_ASSERT_ARG(peeled); + + GIT_ASSERT_ARG(target_type == GIT_OBJECT_TAG || + target_type == GIT_OBJECT_COMMIT || + target_type == GIT_OBJECT_TREE || + target_type == GIT_OBJECT_BLOB || + target_type == GIT_OBJECT_ANY); + + if ((error = check_type_combination(git_object_type(object), target_type)) < 0) + return peel_error(error, git_object_id(object), target_type); + + if (git_object_type(object) == target_type) + return git_object_dup(peeled, (git_object *)object); + + source = (git_object *)object; + + while (!(error = dereference_object(&deref, source))) { + + if (source != object) + git_object_free(source); + + if (git_object_type(deref) == target_type) { + *peeled = deref; + return 0; + } + + if (target_type == GIT_OBJECT_ANY && + git_object_type(deref) != git_object_type(object)) + { + *peeled = deref; + return 0; + } + + source = deref; + deref = NULL; + } + + if (source != object) + git_object_free(source); + + git_object_free(deref); + + if (error) + error = peel_error(error, git_object_id(object), target_type); + + return error; +} + +int git_object_dup(git_object **dest, git_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + +int git_object_lookup_bypath( + git_object **out, + const git_object *treeish, + const char *path, + git_object_t type) +{ + int error = -1; + git_tree *tree = NULL; + git_tree_entry *entry = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(treeish); + GIT_ASSERT_ARG(path); + + if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJECT_TREE)) < 0 || + (error = git_tree_entry_bypath(&entry, tree, path)) < 0) + { + goto cleanup; + } + + if (type != GIT_OBJECT_ANY && git_tree_entry_type(entry) != type) + { + git_error_set(GIT_ERROR_OBJECT, + "object at path '%s' is not of the asked-for type %d", + path, type); + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + error = git_tree_entry_to_object(out, git_object_owner(treeish), entry); + +cleanup: + git_tree_entry_free(entry); + git_tree_free(tree); + return error; +} + +static int git_object__short_id(git_str *out, const git_object *obj) +{ + git_repository *repo; + int len = GIT_ABBREV_DEFAULT, error; + git_oid id = {{0}}; + git_odb *odb; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(obj); + + repo = git_object_owner(obj); + + if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) + return error; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + while (len < GIT_OID_HEXSZ) { + /* set up short oid */ + memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2); + if (len & 1) + id.id[len / 2] &= 0xf0; + + error = git_odb_exists_prefix(NULL, odb, &id, len); + if (error != GIT_EAMBIGUOUS) + break; + + git_error_clear(); + len++; + } + + if (!error && !(error = git_str_grow(out, len + 1))) { + git_oid_tostr(out->ptr, len + 1, &id); + out->size = len; + } + + git_odb_free(odb); + + return error; +} + +int git_object_short_id(git_buf *out, const git_object *obj) +{ + GIT_BUF_WRAP_PRIVATE(out, git_object__short_id, obj); +} + +bool git_object__is_valid( + git_repository *repo, const git_oid *id, git_object_t expected_type) +{ + git_odb *odb; + git_object_t actual_type; + size_t len; + int error; + + if (!git_object__strict_input_validation) + return true; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0) + return false; + + if (expected_type != GIT_OBJECT_ANY && expected_type != actual_type) { + git_error_set(GIT_ERROR_INVALID, + "the requested type does not match the type in the ODB"); + return false; + } + + return true; +} + +int git_object_rawcontent_is_valid( + int *valid, + const char *buf, + size_t len, + git_object_t type) +{ + git_object *obj = NULL; + int error; + + GIT_ASSERT_ARG(valid); + GIT_ASSERT_ARG(buf); + + /* Blobs are always valid; don't bother parsing. */ + if (type == GIT_OBJECT_BLOB) { + *valid = 1; + return 0; + } + + error = git_object__from_raw(&obj, buf, len, type); + git_object_free(obj); + + if (error == 0) { + *valid = 1; + return 0; + } else if (error == GIT_EINVALID) { + *valid = 0; + return 0; + } + + return error; +} diff --git a/src/libgit2/object.h b/src/libgit2/object.h new file mode 100644 index 000000000..66be57557 --- /dev/null +++ b/src/libgit2/object.h @@ -0,0 +1,71 @@ +/* + * 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_object_h__ +#define INCLUDE_object_h__ + +#include "common.h" + +#include "repository.h" + +#define GIT_OBJECT_SIZE_MAX UINT64_MAX + +extern bool git_object__strict_input_validation; + +/** Base git object for inheritance */ +struct git_object { + git_cached_obj cached; + git_repository *repo; +}; + +/* fully free the object; internal method, DO NOT EXPORT */ +void git_object__free(void *object); + +/* + * Parse object from raw data. Note that the resulting object is + * tied to the lifetime of the data, as some objects simply point + * into it. + */ +int git_object__from_raw( + git_object **object_out, + const char *data, + size_t size, + git_object_t type); + +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_object_t type); + +int git_object__resolve_to_type(git_object **obj, git_object_t type); + +git_object_t git_object_stringn2type(const char *str, size_t len); + +int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); + +void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid); + +bool git_object__is_valid( + git_repository *repo, const git_oid *id, git_object_t expected_type); + +GIT_INLINE(git_object_t) git_object__type_from_filemode(git_filemode_t mode) +{ + switch (mode) { + case GIT_FILEMODE_TREE: + return GIT_OBJECT_TREE; + case GIT_FILEMODE_COMMIT: + return GIT_OBJECT_COMMIT; + case GIT_FILEMODE_BLOB: + case GIT_FILEMODE_BLOB_EXECUTABLE: + case GIT_FILEMODE_LINK: + return GIT_OBJECT_BLOB; + default: + return GIT_OBJECT_INVALID; + } +} + +#endif diff --git a/src/libgit2/object_api.c b/src/libgit2/object_api.c new file mode 100644 index 000000000..d45abd5ce --- /dev/null +++ b/src/libgit2/object_api.c @@ -0,0 +1,148 @@ +/* + * 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/object.h" + +#include "repository.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "tag.h" + +/** + * Commit + */ +int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_COMMIT); +} + +int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_COMMIT); +} + +void git_commit_free(git_commit *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_commit_id(const git_commit *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_commit_owner(const git_commit *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_commit_dup(git_commit **out, git_commit *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + +/** + * Tree + */ +int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TREE); +} + +int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TREE); +} + +void git_tree_free(git_tree *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_tree_id(const git_tree *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_tree_owner(const git_tree *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_tree_dup(git_tree **out, git_tree *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + +/** + * Tag + */ +int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TAG); +} + +int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TAG); +} + +void git_tag_free(git_tag *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_tag_id(const git_tag *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_tag_owner(const git_tag *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_tag_dup(git_tag **out, git_tag *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + +/** + * Blob + */ +int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_BLOB); +} + +int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_BLOB); +} + +void git_blob_free(git_blob *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_blob_id(const git_blob *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_blob_owner(const git_blob *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_blob_dup(git_blob **out, git_blob *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} diff --git a/src/libgit2/odb.c b/src/libgit2/odb.c new file mode 100644 index 000000000..7b98c72ee --- /dev/null +++ b/src/libgit2/odb.c @@ -0,0 +1,1831 @@ +/* + * 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 "odb.h" + +#include +#include "git2/object.h" +#include "git2/sys/odb_backend.h" +#include "futils.h" +#include "hash.h" +#include "delta.h" +#include "filter.h" +#include "repository.h" +#include "blob.h" +#include "oid.h" + +#include "git2/odb_backend.h" +#include "git2/oid.h" +#include "git2/oidarray.h" + +#define GIT_ALTERNATES_FILE "info/alternates" + +#define GIT_ALTERNATES_MAX_DEPTH 5 + +/* + * We work under the assumption that most objects for long-running + * operations will be packed + */ +int git_odb__loose_priority = GIT_ODB_DEFAULT_LOOSE_PRIORITY; +int git_odb__packed_priority = GIT_ODB_DEFAULT_PACKED_PRIORITY; + +bool git_odb__strict_hash_verification = true; + +typedef struct +{ + git_odb_backend *backend; + int priority; + bool is_alternate; + ino_t disk_inode; +} backend_internal; + +static git_cache *odb_cache(git_odb *odb) +{ + git_repository *owner = GIT_REFCOUNT_OWNER(odb); + if (owner != NULL) { + return &owner->objects; + } + + return &odb->own_cache; +} + +static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id); +static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); +static int error_null_oid(int error, const char *message); + +static git_object_t odb_hardcoded_type(const git_oid *id) +{ + if (!git_oid_cmp(id, &git_oid__empty_tree_sha1)) + return GIT_OBJECT_TREE; + + return GIT_OBJECT_INVALID; +} + +static int odb_read_hardcoded(bool *found, git_rawobj *raw, const git_oid *id) +{ + git_object_t type; + + *found = false; + + if ((type = odb_hardcoded_type(id)) == GIT_OBJECT_INVALID) + return 0; + + raw->type = type; + raw->len = 0; + raw->data = git__calloc(1, sizeof(uint8_t)); + GIT_ERROR_CHECK_ALLOC(raw->data); + + *found = true; + return 0; +} + +int git_odb__format_object_header( + size_t *written, + char *hdr, + size_t hdr_size, + git_object_size_t obj_len, + git_object_t obj_type) +{ + const char *type_str = git_object_type2string(obj_type); + int hdr_max = (hdr_size > INT_MAX-2) ? (INT_MAX-2) : (int)hdr_size; + int len; + + len = p_snprintf(hdr, hdr_max, "%s %"PRId64, type_str, (int64_t)obj_len); + + if (len < 0 || len >= hdr_max) { + git_error_set(GIT_ERROR_OS, "object header creation failed"); + return -1; + } + + *written = (size_t)(len + 1); + return 0; +} + +int git_odb__hashobj(git_oid *id, git_rawobj *obj) +{ + git_str_vec vec[2]; + char header[64]; + size_t hdrlen; + int error; + + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(obj); + + if (!git_object_typeisloose(obj->type)) { + git_error_set(GIT_ERROR_INVALID, "invalid object type"); + return -1; + } + + if (!obj->data && obj->len != 0) { + git_error_set(GIT_ERROR_INVALID, "invalid object"); + return -1; + } + + if ((error = git_odb__format_object_header(&hdrlen, + header, sizeof(header), obj->len, obj->type)) < 0) + return error; + + vec[0].data = header; + vec[0].len = hdrlen; + vec[1].data = obj->data; + vec[1].len = obj->len; + + return git_hash_vec(id->id, vec, 2, GIT_HASH_ALGORITHM_SHA1); +} + + +static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source) +{ + git_odb_object *object = git__calloc(1, sizeof(git_odb_object)); + + if (object != NULL) { + git_oid_cpy(&object->cached.oid, oid); + object->cached.type = source->type; + object->cached.size = source->len; + object->buffer = source->data; + } + + return object; +} + +void git_odb_object__free(void *object) +{ + if (object != NULL) { + git__free(((git_odb_object *)object)->buffer); + git__free(object); + } +} + +const git_oid *git_odb_object_id(git_odb_object *object) +{ + return &object->cached.oid; +} + +const void *git_odb_object_data(git_odb_object *object) +{ + return object->buffer; +} + +size_t git_odb_object_size(git_odb_object *object) +{ + return object->cached.size; +} + +git_object_t git_odb_object_type(git_odb_object *object) +{ + return object->cached.type; +} + +int git_odb_object_dup(git_odb_object **dest, git_odb_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + +void git_odb_object_free(git_odb_object *object) +{ + if (object == NULL) + return; + + git_cached_obj_decref(object); +} + +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type) +{ + size_t hdr_len; + char hdr[64], buffer[GIT_BUFSIZE_FILEIO]; + git_hash_ctx ctx; + ssize_t read_len = 0; + int error = 0; + + if (!git_object_typeisloose(type)) { + git_error_set(GIT_ERROR_INVALID, "invalid object type for hash"); + return -1; + } + + if ((error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) + return error; + + if ((error = git_odb__format_object_header(&hdr_len, hdr, + sizeof(hdr), size, type)) < 0) + goto done; + + if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0) + goto done; + + while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { + if ((error = git_hash_update(&ctx, buffer, read_len)) < 0) + goto done; + + size -= read_len; + } + + /* If p_read returned an error code, the read obviously failed. + * If size is not zero, the file was truncated after we originally + * stat'd it, so we consider this a read failure too */ + if (read_len < 0 || size > 0) { + git_error_set(GIT_ERROR_OS, "error reading file for hashing"); + error = -1; + + goto done; + } + + error = git_hash_final(out->id, &ctx); + +done: + git_hash_ctx_cleanup(&ctx); + return error; +} + +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t size, git_object_t type, git_filter_list *fl) +{ + int error; + git_str raw = GIT_STR_INIT; + + if (!fl) + return git_odb__hashfd(out, fd, size, type); + + /* size of data is used in header, so we have to read the whole file + * into memory to apply filters before beginning to calculate the hash + */ + + if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) { + git_str post = GIT_STR_INIT; + + error = git_filter_list__convert_buf(&post, fl, &raw); + + if (!error) + error = git_odb_hash(out, post.ptr, post.size, type); + + git_str_dispose(&post); + } + + return error; +} + +int git_odb__hashlink(git_oid *out, const char *path) +{ + struct stat st; + int size; + int result; + + if (git_fs_path_lstat(path, &st) < 0) + return -1; + + if (!git__is_int(st.st_size) || (int)st.st_size < 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "file size overflow for 32-bit systems"); + return -1; + } + + size = (int)st.st_size; + + if (S_ISLNK(st.st_mode)) { + char *link_data; + int read_len; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, size, 1); + link_data = git__malloc(alloc_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, size); + if (read_len == -1) { + git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", path); + git__free(link_data); + return -1; + } + GIT_ASSERT(read_len <= size); + link_data[read_len] = '\0'; + + result = git_odb_hash(out, link_data, read_len, GIT_OBJECT_BLOB); + git__free(link_data); + } else { + int fd = git_futils_open_ro(path); + if (fd < 0) + return -1; + result = git_odb__hashfd(out, fd, size, GIT_OBJECT_BLOB); + p_close(fd); + } + + return result; +} + +int git_odb_hashfile(git_oid *out, const char *path, git_object_t type) +{ + uint64_t size; + int fd, error = 0; + + if ((fd = git_futils_open_ro(path)) < 0) + return fd; + + 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"); + error = -1; + goto done; + } + + error = git_odb__hashfd(out, fd, (size_t)size, type); + +done: + p_close(fd); + return error; +} + +int git_odb_hash(git_oid *id, const void *data, size_t len, git_object_t type) +{ + git_rawobj raw; + + GIT_ASSERT_ARG(id); + + raw.data = (void *)data; + raw.len = len; + raw.type = type; + + return git_odb__hashobj(id, &raw); +} + +/** + * FAKE WSTREAM + */ + +typedef struct { + git_odb_stream stream; + char *buffer; + size_t size, written; + git_object_t type; +} fake_wstream; + +static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid) +{ + fake_wstream *stream = (fake_wstream *)_stream; + return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type); +} + +static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len) +{ + fake_wstream *stream = (fake_wstream *)_stream; + + GIT_ASSERT(stream->written + len <= stream->size); + + memcpy(stream->buffer + stream->written, data, len); + stream->written += len; + return 0; +} + +static void fake_wstream__free(git_odb_stream *_stream) +{ + fake_wstream *stream = (fake_wstream *)_stream; + + git__free(stream->buffer); + git__free(stream); +} + +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; + + GIT_ERROR_CHECK_BLOBSIZE(size); + blobsize = (size_t)size; + + stream = git__calloc(1, sizeof(fake_wstream)); + GIT_ERROR_CHECK_ALLOC(stream); + + stream->size = blobsize; + stream->type = type; + stream->buffer = git__malloc(blobsize); + if (stream->buffer == NULL) { + git__free(stream); + return -1; + } + + stream->stream.backend = backend; + stream->stream.read = NULL; /* read only */ + stream->stream.write = &fake_wstream__write; + stream->stream.finalize_write = &fake_wstream__fwrite; + stream->stream.free = &fake_wstream__free; + stream->stream.mode = GIT_STREAM_WRONLY; + + *stream_p = (git_odb_stream *)stream; + return 0; +} + +/*********************************************************** + * + * OBJECT DATABASE PUBLIC API + * + * Public calls for the ODB functionality + * + ***********************************************************/ + +static int backend_sort_cmp(const void *a, const void *b) +{ + const backend_internal *backend_a = (const backend_internal *)(a); + const backend_internal *backend_b = (const backend_internal *)(b); + + if (backend_b->priority == backend_a->priority) { + if (backend_a->is_alternate) + return -1; + if (backend_b->is_alternate) + return 1; + return 0; + } + return (backend_b->priority - backend_a->priority); +} + +int git_odb_new(git_odb **out) +{ + git_odb *db = git__calloc(1, sizeof(*db)); + GIT_ERROR_CHECK_ALLOC(db); + + if (git_mutex_init(&db->lock) < 0) { + git__free(db); + return -1; + } + if (git_cache_init(&db->own_cache) < 0) { + git_mutex_free(&db->lock); + git__free(db); + return -1; + } + if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { + git_cache_dispose(&db->own_cache); + git_mutex_free(&db->lock); + git__free(db); + return -1; + } + + *out = db; + GIT_REFCOUNT_INC(db); + return 0; +} + +static int add_backend_internal( + git_odb *odb, git_odb_backend *backend, + int priority, bool is_alternate, ino_t disk_inode) +{ + backend_internal *internal; + + GIT_ASSERT_ARG(odb); + GIT_ASSERT_ARG(backend); + + GIT_ERROR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend"); + + /* Check if the backend is already owned by another ODB */ + GIT_ASSERT(!backend->odb || backend->odb == odb); + + internal = git__malloc(sizeof(backend_internal)); + GIT_ERROR_CHECK_ALLOC(internal); + + internal->backend = backend; + internal->priority = priority; + internal->is_alternate = is_alternate; + internal->disk_inode = disk_inode; + + if (git_mutex_lock(&odb->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return -1; + } + if (git_vector_insert(&odb->backends, internal) < 0) { + git_mutex_unlock(&odb->lock); + git__free(internal); + return -1; + } + git_vector_sort(&odb->backends); + internal->backend->odb = odb; + git_mutex_unlock(&odb->lock); + return 0; +} + +int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority) +{ + return add_backend_internal(odb, backend, priority, false, 0); +} + +int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) +{ + return add_backend_internal(odb, backend, priority, true, 0); +} + +size_t git_odb_num_backends(git_odb *odb) +{ + size_t length; + bool locked = true; + + GIT_ASSERT_ARG(odb); + + if (git_mutex_lock(&odb->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + locked = false; + } + length = odb->backends.length; + if (locked) + git_mutex_unlock(&odb->lock); + return length; +} + +static int git_odb__error_unsupported_in_backend(const char *action) +{ + git_error_set(GIT_ERROR_ODB, + "cannot %s - unsupported in the loaded odb backends", action); + return -1; +} + + +int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos) +{ + backend_internal *internal; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(odb); + + + if ((error = git_mutex_lock(&odb->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + internal = git_vector_get(&odb->backends, pos); + + if (!internal || !internal->backend) { + git_mutex_unlock(&odb->lock); + + git_error_set(GIT_ERROR_ODB, "no ODB backend loaded at index %" PRIuZ, pos); + return GIT_ENOTFOUND; + } + *out = internal->backend; + git_mutex_unlock(&odb->lock); + + return 0; +} + +int git_odb__add_default_backends( + git_odb *db, const char *objects_dir, + bool as_alternates, int alternate_depth) +{ + size_t i = 0; + struct stat st; + ino_t inode; + git_odb_backend *loose, *packed; + + /* TODO: inodes are not really relevant on Win32, so we need to find + * a cross-platform workaround for this */ +#ifdef GIT_WIN32 + GIT_UNUSED(i); + GIT_UNUSED(&st); + + inode = 0; +#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); + return -1; + } + + inode = st.st_ino; + + if (git_mutex_lock(&db->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return -1; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *backend = git_vector_get(&db->backends, i); + if (backend->disk_inode == inode) { + git_mutex_unlock(&db->lock); + return 0; + } + } + git_mutex_unlock(&db->lock); +#endif + + /* add the loose object backend */ + if (git_odb_backend_loose(&loose, objects_dir, -1, db->do_fsync, 0, 0) < 0 || + add_backend_internal(db, loose, git_odb__loose_priority, as_alternates, inode) < 0) + return -1; + + /* add the packed file backend */ + if (git_odb_backend_pack(&packed, objects_dir) < 0 || + add_backend_internal(db, packed, git_odb__packed_priority, as_alternates, inode) < 0) + return -1; + + if (git_mutex_lock(&db->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return -1; + } + if (!db->cgraph && git_commit_graph_new(&db->cgraph, objects_dir, false) < 0) { + git_mutex_unlock(&db->lock); + return -1; + } + git_mutex_unlock(&db->lock); + + return load_alternates(db, objects_dir, alternate_depth); +} + +static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth) +{ + git_str alternates_path = GIT_STR_INIT; + git_str alternates_buf = GIT_STR_INIT; + char *buffer; + const char *alternate; + int result = 0; + + /* Git reports an error, we just ignore anything deeper */ + if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) + return 0; + + if (git_str_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) + return -1; + + if (git_fs_path_exists(alternates_path.ptr) == false) { + git_str_dispose(&alternates_path); + return 0; + } + + if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { + git_str_dispose(&alternates_path); + return -1; + } + + buffer = (char *)alternates_buf.ptr; + + /* add each alternate as a new backend; one alternate per line */ + while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { + if (*alternate == '\0' || *alternate == '#') + continue; + + /* + * Relative path: build based on the current `objects` + * folder. However, relative paths are only allowed in + * the current repository. + */ + if (*alternate == '.' && !alternate_depth) { + if ((result = git_str_joinpath(&alternates_path, objects_dir, alternate)) < 0) + break; + alternate = git_str_cstr(&alternates_path); + } + + if ((result = git_odb__add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0) + break; + } + + git_str_dispose(&alternates_path); + git_str_dispose(&alternates_buf); + + return result; +} + +int git_odb_add_disk_alternate(git_odb *odb, const char *path) +{ + return git_odb__add_default_backends(odb, path, true, 0); +} + +int git_odb_set_commit_graph(git_odb *odb, git_commit_graph *cgraph) +{ + int error = 0; + + GIT_ASSERT_ARG(odb); + + if ((error = git_mutex_lock(&odb->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); + return error; + } + git_commit_graph_free(odb->cgraph); + odb->cgraph = cgraph; + git_mutex_unlock(&odb->lock); + + return error; +} + +int git_odb_open(git_odb **out, const char *objects_dir) +{ + git_odb *db; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(objects_dir); + + *out = NULL; + + if (git_odb_new(&db) < 0) + return -1; + + if (git_odb__add_default_backends(db, objects_dir, 0, 0) < 0) { + git_odb_free(db); + return -1; + } + + *out = db; + return 0; +} + +int git_odb__set_caps(git_odb *odb, int caps) +{ + if (caps == GIT_ODB_CAP_FROM_OWNER) { + git_repository *repo = GIT_REFCOUNT_OWNER(odb); + int val; + + if (!repo) { + git_error_set(GIT_ERROR_ODB, "cannot access repository to set odb caps"); + return -1; + } + + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FSYNCOBJECTFILES)) + odb->do_fsync = !!val; + } + + return 0; +} + +static void odb_free(git_odb *db) +{ + size_t i; + bool locked = true; + + if (git_mutex_lock(&db->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + locked = false; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *backend = internal->backend; + + backend->free(backend); + + git__free(internal); + } + if (locked) + git_mutex_unlock(&db->lock); + + git_commit_graph_free(db->cgraph); + git_vector_free(&db->backends); + git_cache_dispose(&db->own_cache); + git_mutex_free(&db->lock); + + git__memzero(db, sizeof(*db)); + git__free(db); +} + +void git_odb_free(git_odb *db) +{ + if (db == NULL) + return; + + GIT_REFCOUNT_DEC(db, odb_free); +} + +static int odb_exists_1( + git_odb *db, + const git_oid *id, + bool only_refreshed) +{ + size_t i; + bool found = false; + int error; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->exists != NULL) + found = (bool)b->exists(b, id); + } + git_mutex_unlock(&db->lock); + + return (int)found; +} + +int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *db) +{ + int error = 0; + git_commit_graph_file *result = NULL; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); + return error; + } + if (!db->cgraph) { + error = GIT_ENOTFOUND; + goto done; + } + error = git_commit_graph_get_file(&result, db->cgraph); + if (error) + goto done; + *out = result; + +done: + git_mutex_unlock(&db->lock); + return error; +} + +static int odb_freshen_1( + git_odb *db, + const git_oid *id, + bool only_refreshed) +{ + size_t i; + bool found = false; + int error; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->freshen != NULL) + found = !b->freshen(b, id); + else if (b->exists != NULL) + found = b->exists(b, id); + } + git_mutex_unlock(&db->lock); + + return (int)found; +} + +int git_odb__freshen(git_odb *db, const git_oid *id) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + + if (odb_freshen_1(db, id, false)) + return 1; + + if (!git_odb_refresh(db)) + return odb_freshen_1(db, id, true); + + /* Failed to refresh, hence not found */ + return 0; +} + +int git_odb_exists(git_odb *db, const git_oid *id) +{ + return git_odb_exists_ext(db, id, 0); +} + +int git_odb_exists_ext(git_odb *db, const git_oid *id, unsigned int flags) +{ + git_odb_object *object; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + + if (git_oid_is_zero(id)) + return 0; + + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { + git_odb_object_free(object); + return 1; + } + + if (odb_exists_1(db, id, false)) + return 1; + + if (!(flags & GIT_ODB_LOOKUP_NO_REFRESH) && !git_odb_refresh(db)) + return odb_exists_1(db, id, true); + + /* Failed to refresh, hence not found */ + return 0; +} + +static int odb_exists_prefix_1(git_oid *out, git_odb *db, + const git_oid *key, size_t len, bool only_refreshed) +{ + size_t i; + int error = GIT_ENOTFOUND, num_found = 0; + git_oid last_found = {{0}}, found; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ENOTFOUND; + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (!b->exists_prefix) + continue; + + error = b->exists_prefix(&found, b, key, len); + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) + continue; + if (error) { + git_mutex_unlock(&db->lock); + return error; + } + + /* make sure found item doesn't introduce ambiguity */ + if (num_found) { + if (git_oid__cmp(&last_found, &found)) { + git_mutex_unlock(&db->lock); + return git_odb__error_ambiguous("multiple matches for prefix"); + } + } else { + git_oid_cpy(&last_found, &found); + num_found++; + } + } + git_mutex_unlock(&db->lock); + + if (!num_found) + return GIT_ENOTFOUND; + + if (out) + git_oid_cpy(out, &last_found); + + return 0; +} + +int git_odb_exists_prefix( + git_oid *out, git_odb *db, const git_oid *short_id, size_t len) +{ + int error; + git_oid key = {{0}}; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(short_id); + + if (len < GIT_OID_MINPREFIXLEN) + return git_odb__error_ambiguous("prefix length too short"); + + if (len >= GIT_OID_HEXSZ) { + if (git_odb_exists(db, short_id)) { + if (out) + git_oid_cpy(out, short_id); + return 0; + } else { + return git_odb__error_notfound( + "no match for id prefix", short_id, len); + } + } + + git_oid__cpy_prefix(&key, short_id, len); + + error = odb_exists_prefix_1(out, db, &key, len, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_exists_prefix_1(out, db, &key, len, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for id prefix", &key, len); + + return error; +} + +int git_odb_expand_ids( + git_odb *db, + git_odb_expand_id *ids, + size_t count) +{ + size_t i; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(ids); + + for (i = 0; i < count; i++) { + git_odb_expand_id *query = &ids[i]; + int error = GIT_EAMBIGUOUS; + + if (!query->type) + query->type = GIT_OBJECT_ANY; + + /* if we have a short OID, expand it first */ + if (query->length >= GIT_OID_MINPREFIXLEN && query->length < GIT_OID_HEXSZ) { + git_oid actual_id; + + error = odb_exists_prefix_1(&actual_id, db, &query->id, query->length, false); + if (!error) { + git_oid_cpy(&query->id, &actual_id); + query->length = GIT_OID_HEXSZ; + } + } + + /* + * now we ought to have a 40-char OID, either because we've expanded it + * or because the user passed a full OID. Ensure its type is right. + */ + if (query->length >= GIT_OID_HEXSZ) { + git_object_t actual_type; + + error = odb_otype_fast(&actual_type, db, &query->id); + if (!error) { + if (query->type != GIT_OBJECT_ANY && query->type != actual_type) + error = GIT_ENOTFOUND; + else + query->type = actual_type; + } + } + + switch (error) { + /* no errors, so we've successfully expanded the OID */ + case 0: + continue; + + /* the object is missing or ambiguous */ + case GIT_ENOTFOUND: + case GIT_EAMBIGUOUS: + memset(&query->id, 0, sizeof(git_oid)); + query->length = 0; + query->type = 0; + break; + + /* something went very wrong with the ODB; bail hard */ + default: + return error; + } + } + + git_error_clear(); + return 0; +} + +int git_odb_read_header(size_t *len_p, git_object_t *type_p, git_odb *db, const git_oid *id) +{ + int error; + git_odb_object *object = NULL; + + error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); + + if (object) + git_odb_object_free(object); + + return error; +} + +static int odb_read_header_1( + size_t *len_p, git_object_t *type_p, git_odb *db, + const git_oid *id, bool only_refreshed) +{ + size_t i; + git_object_t ht; + bool passthrough = false; + int error; + + if (!only_refreshed && (ht = odb_hardcoded_type(id)) != GIT_OBJECT_INVALID) { + *type_p = ht; + *len_p = 0; + return 0; + } + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (!b->read_header) { + passthrough = true; + continue; + } + + error = b->read_header(len_p, type_p, b, id); + + switch (error) { + case GIT_PASSTHROUGH: + passthrough = true; + break; + case GIT_ENOTFOUND: + break; + default: + git_mutex_unlock(&db->lock); + return error; + } + } + git_mutex_unlock(&db->lock); + + return passthrough ? GIT_PASSTHROUGH : GIT_ENOTFOUND; +} + +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_object_t *type_p, + git_odb *db, const git_oid *id) +{ + int error = GIT_ENOTFOUND; + git_odb_object *object; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(len_p); + GIT_ASSERT_ARG(type_p); + + *out = NULL; + + 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) { + *len_p = object->cached.size; + *type_p = object->cached.type; + *out = object; + return 0; + } + + error = odb_read_header_1(len_p, type_p, db, id, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_read_header_1(len_p, type_p, db, id, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("cannot read header for", id, GIT_OID_HEXSZ); + + /* we found the header; return early */ + if (!error) + return 0; + + if (error == GIT_PASSTHROUGH) { + /* + * no backend has header-reading functionality + * so try using `git_odb_read` instead + */ + error = git_odb_read(&object, db, id); + if (!error) { + *len_p = object->cached.size; + *type_p = object->cached.type; + *out = object; + } + } + + return error; +} + +static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, + bool only_refreshed) +{ + size_t i; + git_rawobj raw; + git_odb_object *object; + git_oid hashed; + bool found = false; + int error = 0; + + if (!only_refreshed) { + if ((error = odb_read_hardcoded(&found, &raw, id)) < 0) + return error; + } + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->read != NULL) { + error = b->read(&raw.data, &raw.len, &raw.type, b, id); + if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND) + continue; + + if (error < 0) { + git_mutex_unlock(&db->lock); + return error; + } + + found = true; + } + } + git_mutex_unlock(&db->lock); + + if (!found) + return GIT_ENOTFOUND; + + if (git_odb__strict_hash_verification) { + if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0) + goto out; + + if (!git_oid_equal(id, &hashed)) { + error = git_odb__error_mismatch(id, &hashed); + goto out; + } + } + + git_error_clear(); + if ((object = odb_object__alloc(id, &raw)) == NULL) { + error = -1; + goto out; + } + + *out = git_cache_store_raw(odb_cache(db), object); + +out: + if (error) + git__free(raw.data); + return error; +} + +int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(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); + if (*out != NULL) + return 0; + + error = odb_read_1(out, db, id, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_read_1(out, db, id, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for id", id, GIT_OID_HEXSZ); + + return error; +} + +static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id) +{ + git_odb_object *object; + size_t _unused; + int error; + + 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) { + *type_p = object->cached.type; + git_odb_object_free(object); + return 0; + } + + error = odb_read_header_1(&_unused, type_p, db, id, false); + + if (error == GIT_PASSTHROUGH) { + error = odb_read_1(&object, db, id, false); + if (!error) + *type_p = object->cached.type; + git_odb_object_free(object); + } + + return error; +} + +static int read_prefix_1(git_odb_object **out, git_odb *db, + const git_oid *key, size_t len, bool only_refreshed) +{ + size_t i; + int error = 0; + git_oid found_full_oid = {{0}}; + git_rawobj raw = {0}; + void *data = NULL; + bool found = false; + git_odb_object *object; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->read_prefix != NULL) { + git_oid full_oid; + error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len); + + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) { + error = 0; + continue; + } + + if (error) { + git_mutex_unlock(&db->lock); + goto out; + } + + git__free(data); + data = raw.data; + + if (found && git_oid__cmp(&full_oid, &found_full_oid)) { + git_str buf = GIT_STR_INIT; + + git_str_printf(&buf, "multiple matches for prefix: %s", + git_oid_tostr_s(&full_oid)); + git_str_printf(&buf, " %s", + git_oid_tostr_s(&found_full_oid)); + + error = git_odb__error_ambiguous(buf.ptr); + git_str_dispose(&buf); + git_mutex_unlock(&db->lock); + goto out; + } + + found_full_oid = full_oid; + found = true; + } + } + git_mutex_unlock(&db->lock); + + if (!found) + return GIT_ENOTFOUND; + + if (git_odb__strict_hash_verification) { + git_oid hash; + + if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0) + goto out; + + if (!git_oid_equal(&found_full_oid, &hash)) { + error = git_odb__error_mismatch(&found_full_oid, &hash); + goto out; + } + } + + if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) { + error = -1; + goto out; + } + + *out = git_cache_store_raw(odb_cache(db), object); + +out: + if (error) + git__free(raw.data); + + return error; +} + +int git_odb_read_prefix( + git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) +{ + git_oid key = {{0}}; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(db); + + if (len < GIT_OID_MINPREFIXLEN) + return git_odb__error_ambiguous("prefix length too short"); + + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + if (len == GIT_OID_HEXSZ) { + *out = git_cache_get_raw(odb_cache(db), short_id); + if (*out != NULL) + return 0; + } + + git_oid__cpy_prefix(&key, short_id, len); + + error = read_prefix_1(out, db, &key, len, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = read_prefix_1(out, db, &key, len, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for prefix", &key, len); + + return error; +} + +int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) +{ + unsigned int i; + git_vector backends = GIT_VECTOR_INIT; + backend_internal *internal; + int error = 0; + + /* Make a copy of the backends vector to invoke the callback without holding the lock. */ + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + goto cleanup; + } + error = git_vector_dup(&backends, &db->backends, NULL); + git_mutex_unlock(&db->lock); + + if (error < 0) + goto cleanup; + + git_vector_foreach(&backends, i, internal) { + git_odb_backend *b = internal->backend; + error = b->foreach(b, cb, payload); + if (error != 0) + goto cleanup; + } + +cleanup: + git_vector_free(&backends); + + return error; +} + +int git_odb_write( + git_oid *oid, git_odb *db, const void *data, size_t len, git_object_t type) +{ + size_t i; + int error; + git_odb_stream *stream; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(db); + + if ((error = git_odb_hash(oid, data, len, type)) < 0) + return error; + + if (git_oid_is_zero(oid)) + return error_null_oid(GIT_EINVALID, "cannot write object"); + + if (git_odb__freshen(db, oid)) + return 0; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0, error = GIT_ERROR; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->write != NULL) + error = b->write(b, oid, data, len, type); + } + git_mutex_unlock(&db->lock); + + if (!error || error == GIT_PASSTHROUGH) + return 0; + + /* if no backends were able to write the object directly, we try a + * streaming write to the backends; just write the whole object into the + * stream in one push + */ + if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) + return error; + + if ((error = stream->write(stream, data, len)) == 0) + error = stream->finalize_write(stream, oid); + + git_odb_stream_free(stream); + return error; +} + +static int hash_header(git_hash_ctx *ctx, git_object_size_t size, git_object_t type) +{ + char header[64]; + size_t hdrlen; + int error; + + if ((error = git_odb__format_object_header(&hdrlen, + header, sizeof(header), size, type)) < 0) + return error; + + return git_hash_update(ctx, header, hdrlen); +} + +int git_odb_open_wstream( + 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; + git_hash_ctx *ctx = NULL; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ERROR; + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writestream != NULL) { + ++writes; + error = b->writestream(stream, b, size, type); + } else if (b->write != NULL) { + ++writes; + error = init_fake_wstream(stream, b, size, type); + } + } + git_mutex_unlock(&db->lock); + + if (error < 0) { + if (error == GIT_PASSTHROUGH) + error = 0; + else if (!writes) + error = git_odb__error_unsupported_in_backend("write object"); + + goto done; + } + + ctx = git__malloc(sizeof(git_hash_ctx)); + GIT_ERROR_CHECK_ALLOC(ctx); + + if ((error = git_hash_ctx_init(ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = hash_header(ctx, size, type)) < 0) + goto done; + + (*stream)->hash_ctx = ctx; + (*stream)->declared_size = size; + (*stream)->received_bytes = 0; + +done: + if (error) + git__free(ctx); + return error; +} + +static int git_odb_stream__invalid_length( + const git_odb_stream *stream, + const char *action) +{ + git_error_set(GIT_ERROR_ODB, + "cannot %s - " + "Invalid length. %"PRId64" was expected. The " + "total size of the received chunks amounts to %"PRId64".", + action, stream->declared_size, stream->received_bytes); + + return -1; +} + +int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) +{ + git_hash_update(stream->hash_ctx, buffer, len); + + stream->received_bytes += len; + + if (stream->received_bytes > stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_write()"); + + return stream->write(stream, buffer, len); +} + +int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) +{ + if (stream->received_bytes != stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_finalize_write()"); + + git_hash_final(out->id, stream->hash_ctx); + + if (git_odb__freshen(stream->backend->odb, out)) + return 0; + + return stream->finalize_write(stream, out); +} + +int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) +{ + return stream->read(stream, buffer, len); +} + +void git_odb_stream_free(git_odb_stream *stream) +{ + if (stream == NULL) + return; + + git_hash_ctx_cleanup(stream->hash_ctx); + git__free(stream->hash_ctx); + stream->free(stream); +} + +int git_odb_open_rstream( + git_odb_stream **stream, + size_t *len, + git_object_t *type, + git_odb *db, + const git_oid *oid) +{ + size_t i, reads = 0; + int error = GIT_ERROR; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ERROR; + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (b->readstream != NULL) { + ++reads; + error = b->readstream(stream, len, type, b, oid); + } + } + git_mutex_unlock(&db->lock); + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !reads) + error = git_odb__error_unsupported_in_backend("read object streamed"); + + return error; +} + +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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ERROR; + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writepack != NULL) { + ++writes; + error = b->writepack(out, b, db, progress_cb, progress_payload); + } + } + git_mutex_unlock(&db->lock); + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !writes) + error = git_odb__error_unsupported_in_backend("write pack"); + + return error; +} + +int git_odb_write_multi_pack_index(git_odb *db) +{ + size_t i, writes = 0; + int error = GIT_ERROR; + + GIT_ASSERT_ARG(db); + + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writemidx != NULL) { + ++writes; + error = b->writemidx(b); + } + } + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !writes) + error = git_odb__error_unsupported_in_backend("write multi-pack-index"); + + return error; +} + +void *git_odb_backend_data_alloc(git_odb_backend *backend, size_t len) +{ + GIT_UNUSED(backend); + return git__malloc(len); +} + +#ifndef GIT_DEPRECATE_HARD +void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) +{ + return git_odb_backend_data_alloc(backend, len); +} +#endif + +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; + int error; + + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (b->refresh != NULL) { + int error = b->refresh(b); + if (error < 0) { + git_mutex_unlock(&db->lock); + return error; + } + } + } + if (db->cgraph) + git_commit_graph_refresh(db->cgraph); + git_mutex_unlock(&db->lock); + + return 0; +} + +int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual) +{ + char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(expected_oid, sizeof(expected_oid), expected); + git_oid_tostr(actual_oid, sizeof(actual_oid), actual); + + git_error_set(GIT_ERROR_ODB, "object hash mismatch - expected %s but got %s", + expected_oid, actual_oid); + + return GIT_EMISMATCH; +} + +int git_odb__error_notfound( + const char *message, const git_oid *oid, size_t oid_len) +{ + if (oid != NULL) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, oid_len+1, oid); + git_error_set(GIT_ERROR_ODB, "object not found - %s (%.*s)", + message, (int) oid_len, oid_str); + } else + git_error_set(GIT_ERROR_ODB, "object not found - %s", message); + + return GIT_ENOTFOUND; +} + +static int error_null_oid(int error, const char *message) +{ + git_error_set(GIT_ERROR_ODB, "odb: %s: null OID cannot exist", message); + return error; +} + +int git_odb__error_ambiguous(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "ambiguous SHA1 prefix - %s", message); + return GIT_EAMBIGUOUS; +} + +int git_odb_init_backend(git_odb_backend *backend, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT); + return 0; +} diff --git a/src/libgit2/odb.h b/src/libgit2/odb.h new file mode 100644 index 000000000..5aa4cc84a --- /dev/null +++ b/src/libgit2/odb.h @@ -0,0 +1,149 @@ +/* + * 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_odb_h__ +#define INCLUDE_odb_h__ + +#include "common.h" + +#include "git2/odb.h" +#include "git2/oid.h" +#include "git2/types.h" +#include "git2/sys/commit_graph.h" + +#include "cache.h" +#include "commit_graph.h" +#include "filter.h" +#include "posix.h" +#include "vector.h" + +#define GIT_OBJECTS_DIR "objects/" +#define GIT_OBJECT_DIR_MODE 0777 +#define GIT_OBJECT_FILE_MODE 0444 + +#define GIT_ODB_DEFAULT_LOOSE_PRIORITY 1 +#define GIT_ODB_DEFAULT_PACKED_PRIORITY 2 + +extern bool git_odb__strict_hash_verification; + +/* DO NOT EXPORT */ +typedef struct { + void *data; /**< Raw, decompressed object data. */ + size_t len; /**< Total number of bytes in data. */ + git_object_t type; /**< Type of this object. */ +} git_rawobj; + +/* EXPORT */ +struct git_odb_object { + git_cached_obj cached; + void *buffer; +}; + +/* EXPORT */ +struct git_odb { + git_refcount rc; + git_mutex lock; /* protects backends */ + git_vector backends; + git_cache own_cache; + git_commit_graph *cgraph; + unsigned int do_fsync :1; +}; + +typedef enum { + GIT_ODB_CAP_FROM_OWNER = -1 +} git_odb_cap_t; + +/* + * Set the capabilities for the object database. + */ +int git_odb__set_caps(git_odb *odb, int caps); + +/* + * Add the default loose and packed backends for a database. + */ +int git_odb__add_default_backends( + git_odb *db, const char *objects_dir, + bool as_alternates, int alternate_depth); + +/* + * Hash a git_rawobj internally. + * The `git_rawobj` is supposed to be previously initialized + */ +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_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, + * but the fd is already open and we have the size of the contents. + * + * Saves us some `stat` calls. + * + * The fd is never closed, not even on error. It must be opened and closed + * by the caller + */ +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type); + +/* + * Hash an open file descriptor applying an array of filters + * Acts just like git_odb__hashfd with the addition of filters... + */ +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t len, git_object_t type, git_filter_list *fl); + +/* + * Hash a `path`, assuming it could be a POSIX symlink: if the path is a + * 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_OBJECT_BLOB` because + * symlinks may only point to blobs. + */ +int git_odb__hashlink(git_oid *out, const char *path); + +/** + * Generate a GIT_EMISMATCH error for the ODB. + */ +int git_odb__error_mismatch( + const git_oid *expected, const git_oid *actual); + +/* + * Generate a GIT_ENOTFOUND error for the ODB. + */ +int git_odb__error_notfound( + const char *message, const git_oid *oid, size_t oid_len); + +/* + * Generate a GIT_EAMBIGUOUS error for the ODB. + */ +int git_odb__error_ambiguous(const char *message); + +/* + * Attempt to read object header or just return whole object if it could + * not be read. + */ +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_object_t *type_p, + git_odb *db, const git_oid *id); + +/* + * Attempt to get the ODB's commit-graph file. This object is still owned by + * the ODB. If the repository does not contain a commit-graph, it will return + * GIT_ENOTFOUND. + */ +int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *odb); + +/* freshen an entry in the object database */ +int git_odb__freshen(git_odb *db, const git_oid *id); + +/* fully free the object; internal method, DO NOT EXPORT */ +void git_odb_object__free(void *object); + +#endif diff --git a/src/libgit2/odb_loose.c b/src/libgit2/odb_loose.c new file mode 100644 index 000000000..463e24fa5 --- /dev/null +++ b/src/libgit2/odb_loose.c @@ -0,0 +1,1182 @@ +/* + * 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 +#include "git2/object.h" +#include "git2/sys/odb_backend.h" +#include "futils.h" +#include "hash.h" +#include "odb.h" +#include "delta.h" +#include "filebuf.h" +#include "object.h" +#include "zstream.h" + +#include "git2/odb_backend.h" +#include "git2/types.h" + +/* maximum possible header length */ +#define MAX_HEADER_LEN 64 + +typedef struct { /* object header data */ + git_object_t type; /* object type */ + size_t size; /* object size */ +} obj_hdr; + +typedef struct { + git_odb_stream stream; + git_filebuf fbuf; +} loose_writestream; + +typedef struct { + git_odb_stream stream; + git_map map; + char start[MAX_HEADER_LEN]; + size_t start_len; + size_t start_read; + git_zstream zstream; +} loose_readstream; + +typedef struct loose_backend { + git_odb_backend parent; + + int object_zlib_level; /** loose object zlib compression level. */ + int fsync_object_files; /** loose object file fsync flag. */ + mode_t object_file_mode; + mode_t object_dir_mode; + + size_t objects_dirlen; + char objects_dir[GIT_FLEX_ARRAY]; +} loose_backend; + +/* State structure for exploring directories, + * in order to locate objects matching a short oid. + */ +typedef struct { + size_t dir_len; + unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ + size_t short_oid_len; + int found; /* number of matching + * objects already found */ + unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of + * the object found */ +} loose_locate_object_state; + + +/*********************************************************** + * + * MISCELLANEOUS HELPER FUNCTIONS + * + ***********************************************************/ + +static int object_file_name( + git_str *name, const loose_backend *be, const git_oid *id) +{ + size_t alloclen; + + /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, be->objects_dirlen, GIT_OID_HEXSZ); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 3); + if (git_str_grow(name, alloclen) < 0) + return -1; + + git_str_set(name, be->objects_dir, be->objects_dirlen); + git_fs_path_to_dir(name); + + /* loose object filename: aa/aaa... (41 bytes) */ + git_oid_pathfmt(name->ptr + name->size, id); + name->size += GIT_OID_HEXSZ + 1; + name->ptr[name->size] = '\0'; + + return 0; +} + +static int object_mkdir(const git_str *name, const loose_backend *be) +{ + return git_futils_mkdir_relative( + name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); +} + +static int parse_header_packlike( + obj_hdr *out, size_t *out_len, const unsigned char *data, size_t len) +{ + unsigned long c; + size_t shift, size, used = 0; + + if (len == 0) + goto on_error; + + c = data[used++]; + out->type = (c >> 4) & 7; + + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + goto on_error; + + if (sizeof(size_t) * 8 <= shift) + goto on_error; + + c = data[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + + out->size = size; + + if (out_len) + *out_len = used; + + return 0; + +on_error: + git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); + return -1; +} + +static int parse_header( + obj_hdr *out, + size_t *out_len, + const unsigned char *_data, + size_t data_len) +{ + const char *data = (char *)_data; + size_t i, typename_len, size_idx, size_len; + int64_t size; + + *out_len = 0; + + /* find the object type name */ + for (i = 0, typename_len = 0; i < data_len; i++, typename_len++) { + if (data[i] == ' ') + break; + } + + if (typename_len == data_len) + goto on_error; + + out->type = git_object_stringn2type(data, typename_len); + + size_idx = typename_len + 1; + for (i = size_idx, size_len = 0; i < data_len; i++, size_len++) { + if (data[i] == '\0') + break; + } + + if (i == data_len) + goto on_error; + + if (git__strntol64(&size, &data[size_idx], size_len, NULL, 10) < 0 || + size < 0) + goto on_error; + + if ((uint64_t)size > SIZE_MAX) { + git_error_set(GIT_ERROR_OBJECT, "object is larger than available memory"); + return -1; + } + + out->size = (size_t)size; + + if (GIT_ADD_SIZET_OVERFLOW(out_len, i, 1)) + goto on_error; + + return 0; + +on_error: + git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); + return -1; +} + +static int is_zlib_compressed_data(unsigned char *data, size_t data_len) +{ + unsigned int w; + + if (data_len < 2) + return 0; + + w = ((unsigned int)(data[0]) << 8) + data[1]; + return (data[0] & 0x8F) == 0x08 && !(w % 31); +} + +/*********************************************************** + * + * ODB OBJECT READING & WRITING + * + * Backend for the public API; read headers and full objects + * from the ODB. Write raw data to the ODB. + * + ***********************************************************/ + + +/* + * At one point, there was a loose object format that was intended to + * mimic the format used in pack-files. This was to allow easy copying + * of loose object data into packs. This format is no longer used, but + * we must still read it. + */ +static int read_loose_packlike(git_rawobj *out, git_str *obj) +{ + git_str body = GIT_STR_INIT; + const unsigned char *obj_data; + obj_hdr hdr; + size_t obj_len, head_len, alloc_size; + int error; + + obj_data = (unsigned char *)obj->ptr; + obj_len = obj->size; + + /* + * read the object header, which is an (uncompressed) + * binary encoding of the object type and size. + */ + if ((error = parse_header_packlike(&hdr, &head_len, obj_data, obj_len)) < 0) + goto done; + + if (!git_object_typeisloose(hdr.type) || head_len > obj_len) { + git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); + error = -1; + goto done; + } + + obj_data += head_len; + obj_len -= head_len; + + /* + * allocate a buffer and inflate the data into it + */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || + git_str_init(&body, alloc_size) < 0) { + error = -1; + goto done; + } + + if ((error = git_zstream_inflatebuf(&body, obj_data, obj_len)) < 0) + goto done; + + out->len = hdr.size; + out->type = hdr.type; + out->data = git_str_detach(&body); + +done: + git_str_dispose(&body); + return error; +} + +static int read_loose_standard(git_rawobj *out, git_str *obj) +{ + git_zstream zstream = GIT_ZSTREAM_INIT; + unsigned char head[MAX_HEADER_LEN], *body = NULL; + size_t decompressed, head_len, body_len, alloc_size; + obj_hdr hdr; + int error; + + if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0 || + (error = git_zstream_set_input(&zstream, git_str_cstr(obj), git_str_len(obj))) < 0) + goto done; + + decompressed = sizeof(head); + + /* + * inflate the initial part of the compressed buffer in order to + * parse the header; read the largest header possible, then push the + * remainder into the body buffer. + */ + if ((error = git_zstream_get_output(head, &decompressed, &zstream)) < 0 || + (error = parse_header(&hdr, &head_len, head, decompressed)) < 0) + goto done; + + if (!git_object_typeisloose(hdr.type)) { + git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); + error = -1; + goto done; + } + + /* + * allocate a buffer and inflate the object data into it + * (including the initial sequence in the head buffer). + */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || + (body = git__calloc(1, alloc_size)) == NULL) { + error = -1; + goto done; + } + + GIT_ASSERT(decompressed >= head_len); + body_len = decompressed - head_len; + + if (body_len) + memcpy(body, head + head_len, body_len); + + decompressed = hdr.size - body_len; + if ((error = git_zstream_get_output(body + body_len, &decompressed, &zstream)) < 0) + goto done; + + if (!git_zstream_done(&zstream)) { + git_error_set(GIT_ERROR_ZLIB, "failed to finish zlib inflation: stream aborted prematurely"); + error = -1; + goto done; + } + + body[hdr.size] = '\0'; + + out->data = body; + out->len = hdr.size; + out->type = hdr.type; + +done: + if (error < 0) + git__free(body); + + git_zstream_free(&zstream); + return error; +} + +static int read_loose(git_rawobj *out, git_str *loc) +{ + int error; + git_str obj = GIT_STR_INIT; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(loc); + + if (git_str_oom(loc)) + return -1; + + out->data = NULL; + out->len = 0; + out->type = GIT_OBJECT_INVALID; + + if ((error = git_futils_readbuffer(&obj, loc->ptr)) < 0) + goto done; + + if (!is_zlib_compressed_data((unsigned char *)obj.ptr, obj.size)) + error = read_loose_packlike(out, &obj); + else + error = read_loose_standard(out, &obj); + +done: + git_str_dispose(&obj); + return error; +} + +static int read_header_loose_packlike( + git_rawobj *out, const unsigned char *data, size_t len) +{ + obj_hdr hdr; + size_t header_len; + int error; + + if ((error = parse_header_packlike(&hdr, &header_len, data, len)) < 0) + return error; + + out->len = hdr.size; + out->type = hdr.type; + + return error; +} + +static int read_header_loose_standard( + git_rawobj *out, const unsigned char *data, size_t len) +{ + git_zstream zs = GIT_ZSTREAM_INIT; + obj_hdr hdr = {0}; + unsigned char inflated[MAX_HEADER_LEN] = {0}; + size_t header_len, inflated_len = sizeof(inflated); + int error; + + if ((error = git_zstream_init(&zs, GIT_ZSTREAM_INFLATE)) < 0 || + (error = git_zstream_set_input(&zs, data, len)) < 0 || + (error = git_zstream_get_output_chunk(inflated, &inflated_len, &zs)) < 0 || + (error = parse_header(&hdr, &header_len, inflated, inflated_len)) < 0) + goto done; + + out->len = hdr.size; + out->type = hdr.type; + +done: + git_zstream_free(&zs); + return error; +} + +static int read_header_loose(git_rawobj *out, git_str *loc) +{ + unsigned char obj[1024]; + ssize_t obj_len; + int fd, error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(loc); + + if (git_str_oom(loc)) + return -1; + + out->data = NULL; + + 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 + error = read_header_loose_standard(out, obj, (size_t)obj_len); + + if (!error && !git_object_typeisloose(out->type)) { + git_error_set(GIT_ERROR_ZLIB, "failed to read loose object header"); + error = -1; + goto done; + } + +done: + if (fd >= 0) + p_close(fd); + return error; +} + +static int locate_object( + git_str *object_location, + loose_backend *backend, + const git_oid *oid) +{ + int error = object_file_name(object_location, backend, oid); + + if (!error && !git_fs_path_exists(object_location->ptr)) + return GIT_ENOTFOUND; + + return error; +} + +/* Explore an entry of a directory and see if it matches a short oid */ +static int fn_locate_object_short_oid(void *state, git_str *pathbuf) { + loose_locate_object_state *sstate = (loose_locate_object_state *)state; + + if (git_str_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { + /* Entry cannot be an object. Continue to next entry */ + return 0; + } + + if (git_fs_path_isdir(pathbuf->ptr) == false) { + /* We are already in the directory matching the 2 first hex characters, + * compare the first ncmp characters of the oids */ + if (!memcmp(sstate->short_oid + 2, + (unsigned char *)pathbuf->ptr + sstate->dir_len, + sstate->short_oid_len - 2)) { + + if (!sstate->found) { + sstate->res_oid[0] = sstate->short_oid[0]; + sstate->res_oid[1] = sstate->short_oid[1]; + memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); + } + sstate->found++; + } + } + + if (sstate->found > 1) + return GIT_EAMBIGUOUS; + + return 0; +} + +/* Locate an object matching a given short oid */ +static int locate_object_short_oid( + git_str *object_location, + git_oid *res_oid, + loose_backend *backend, + const git_oid *short_oid, + size_t len) +{ + char *objects_dir = backend->objects_dir; + size_t dir_len = strlen(objects_dir), alloc_len; + loose_locate_object_state state; + int error; + + /* prealloc memory for OBJ_DIR/xx/xx..38x..xx */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 3); + if (git_str_grow(object_location, alloc_len) < 0) + return -1; + + git_str_set(object_location, objects_dir, dir_len); + git_fs_path_to_dir(object_location); + + /* save adjusted position at end of dir so it can be restored later */ + dir_len = git_str_len(object_location); + + /* Convert raw oid to hex formatted oid */ + git_oid_fmt((char *)state.short_oid, short_oid); + + /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ + if (git_str_put(object_location, (char *)state.short_oid, 3) < 0) + return -1; + object_location->ptr[object_location->size - 1] = '/'; + + /* Check that directory exists */ + if (git_fs_path_isdir(object_location->ptr) == false) + return git_odb__error_notfound("no matching loose object for prefix", + short_oid, len); + + state.dir_len = git_str_len(object_location); + state.short_oid_len = len; + state.found = 0; + + /* Explore directory to find a unique object matching short_oid */ + error = git_fs_path_direach( + object_location, 0, fn_locate_object_short_oid, &state); + if (error < 0 && error != GIT_EAMBIGUOUS) + return error; + + if (!state.found) + return git_odb__error_notfound("no matching loose object for prefix", + short_oid, len); + + if (state.found > 1) + return git_odb__error_ambiguous("multiple matches in loose objects"); + + /* Convert obtained hex formatted oid to raw */ + error = git_oid_fromstr(res_oid, (char *)state.res_oid); + if (error) + return error; + + /* Update the location according to the oid obtained */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + git_str_truncate(object_location, dir_len); + if (git_str_grow(object_location, alloc_len) < 0) + return -1; + + git_oid_pathfmt(object_location->ptr + dir_len, res_oid); + + object_location->size += GIT_OID_HEXSZ + 1; + object_location->ptr[object_location->size] = '\0'; + + return 0; +} + + + + + + + + + +/*********************************************************** + * + * LOOSE BACKEND PUBLIC API + * + * Implement the git_odb_backend API calls + * + ***********************************************************/ + +static int loose_backend__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) +{ + git_str object_path = GIT_STR_INIT; + git_rawobj raw; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + raw.len = 0; + raw.type = GIT_OBJECT_INVALID; + + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + } else if ((error = read_header_loose(&raw, &object_path)) == 0) { + *len_p = raw.len; + *type_p = raw.type; + } + + git_str_dispose(&object_path); + + return error; +} + +static int loose_backend__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) +{ + git_str object_path = GIT_STR_INIT; + git_rawobj raw; + int error = 0; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + } else if ((error = read_loose(&raw, &object_path)) == 0) { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + } + + git_str_dispose(&object_path); + + return error; +} + +static int loose_backend__read_prefix( + git_oid *out_oid, + void **buffer_p, + size_t *len_p, + git_object_t *type_p, + git_odb_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error = 0; + + GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ); + + if (len == GIT_OID_HEXSZ) { + /* We can fall back to regular read method */ + error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) + git_oid_cpy(out_oid, short_oid); + } else { + git_str object_path = GIT_STR_INIT; + git_rawobj raw; + + GIT_ASSERT_ARG(backend && short_oid); + + if ((error = locate_object_short_oid(&object_path, out_oid, + (loose_backend *)backend, short_oid, len)) == 0 && + (error = read_loose(&raw, &object_path)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + } + + git_str_dispose(&object_path); + } + + return error; +} + +static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + git_str object_path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + error = locate_object(&object_path, (loose_backend *)backend, oid); + + git_str_dispose(&object_path); + + return !error; +} + +static int loose_backend__exists_prefix( + git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) +{ + git_str object_path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(short_id); + GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN); + + error = locate_object_short_oid( + &object_path, out, (loose_backend *)backend, short_id, len); + + git_str_dispose(&object_path); + + return error; +} + +struct foreach_state { + size_t dir_len; + git_odb_foreach_cb cb; + void *data; +}; + +GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr) +{ + int v, i = 0; + if (strlen(ptr) != GIT_OID_HEXSZ+1) + return -1; + + if (ptr[2] != '/') { + return -1; + } + + v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]); + if (v < 0) + return -1; + + oid->id[0] = (unsigned char) v; + + ptr += 3; + for (i = 0; i < 38; i += 2) { + v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]); + if (v < 0) + return -1; + + oid->id[1 + i/2] = (unsigned char) v; + } + + return 0; +} + +static int foreach_object_dir_cb(void *_state, git_str *path) +{ + git_oid oid; + struct foreach_state *state = (struct foreach_state *) _state; + + if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0) + return 0; + + return git_error_set_after_callback_function( + state->cb(&oid, state->data), "git_odb_foreach"); +} + +static int foreach_cb(void *_state, git_str *path) +{ + struct foreach_state *state = (struct foreach_state *) _state; + + /* non-dir is some stray file, ignore it */ + if (!git_fs_path_isdir(git_str_cstr(path))) + return 0; + + return git_fs_path_direach(path, 0, foreach_object_dir_cb, state); +} + +static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) +{ + char *objects_dir; + int error; + git_str buf = GIT_STR_INIT; + struct foreach_state state; + loose_backend *backend = (loose_backend *) _backend; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(cb); + + objects_dir = backend->objects_dir; + + git_str_sets(&buf, objects_dir); + git_fs_path_to_dir(&buf); + if (git_str_oom(&buf)) + return -1; + + memset(&state, 0, sizeof(state)); + state.cb = cb; + state.data = data; + state.dir_len = git_str_len(&buf); + + error = git_fs_path_direach(&buf, 0, foreach_cb, &state); + + git_str_dispose(&buf); + + return error; +} + +static int loose_backend__writestream_finalize(git_odb_stream *_stream, const git_oid *oid) +{ + loose_writestream *stream = (loose_writestream *)_stream; + loose_backend *backend = (loose_backend *)_stream->backend; + git_str final_path = GIT_STR_INIT; + int error = 0; + + if (object_file_name(&final_path, backend, oid) < 0 || + object_mkdir(&final_path, backend) < 0) + error = -1; + else + error = git_filebuf_commit_at( + &stream->fbuf, final_path.ptr); + + git_str_dispose(&final_path); + + return error; +} + +static int loose_backend__writestream_write(git_odb_stream *_stream, const char *data, size_t len) +{ + loose_writestream *stream = (loose_writestream *)_stream; + return git_filebuf_write(&stream->fbuf, data, len); +} + +static void loose_backend__writestream_free(git_odb_stream *_stream) +{ + loose_writestream *stream = (loose_writestream *)_stream; + + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); +} + +static int filebuf_flags(loose_backend *backend) +{ + int flags = GIT_FILEBUF_TEMPORARY | + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT); + + if (backend->fsync_object_files || git_repository__fsync_gitdir) + flags |= GIT_FILEBUF_FSYNC; + + return flags; +} + +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; + char hdr[MAX_HEADER_LEN]; + git_str tmp_path = GIT_STR_INIT; + size_t hdrlen; + int error; + + GIT_ASSERT_ARG(_backend); + + backend = (loose_backend *)_backend; + *stream_out = NULL; + + if ((error = git_odb__format_object_header(&hdrlen, + hdr, sizeof(hdr), length, type)) < 0) + return error; + + stream = git__calloc(1, sizeof(loose_writestream)); + GIT_ERROR_CHECK_ALLOC(stream); + + stream->stream.backend = _backend; + stream->stream.read = NULL; /* read only */ + stream->stream.write = &loose_backend__writestream_write; + stream->stream.finalize_write = &loose_backend__writestream_finalize; + stream->stream.free = &loose_backend__writestream_free; + stream->stream.mode = GIT_STREAM_WRONLY; + + if (git_str_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || + git_filebuf_open(&stream->fbuf, tmp_path.ptr, filebuf_flags(backend), + backend->object_file_mode) < 0 || + stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) + { + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); + stream = NULL; + } + git_str_dispose(&tmp_path); + *stream_out = (git_odb_stream *)stream; + + return !stream ? -1 : 0; +} + +static int loose_backend__readstream_read( + git_odb_stream *_stream, + char *buffer, + size_t buffer_len) +{ + loose_readstream *stream = (loose_readstream *)_stream; + 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. + */ + if (start_remain && buffer_len) { + size_t chunk = min(start_remain, buffer_len); + memcpy(buffer, stream->start + stream->start_read, chunk); + + buffer += chunk; + stream->start_read += chunk; + + total += (int)chunk; + buffer_len -= chunk; + } + + if (buffer_len) { + size_t chunk = buffer_len; + + if ((error = git_zstream_get_output(buffer, &chunk, &stream->zstream)) < 0) + return error; + + total += (int)chunk; + } + + return (int)total; +} + +static void loose_backend__readstream_free(git_odb_stream *_stream) +{ + loose_readstream *stream = (loose_readstream *)_stream; + + git_futils_mmap_free(&stream->map); + git_zstream_free(&stream->zstream); + git__free(stream); +} + +static int loose_backend__readstream_packlike( + obj_hdr *hdr, + loose_readstream *stream) +{ + const unsigned char *data; + size_t data_len, head_len; + int error; + + data = stream->map.data; + data_len = stream->map.len; + + /* + * read the object header, which is an (uncompressed) + * binary encoding of the object type and size. + */ + if ((error = parse_header_packlike(hdr, &head_len, data, data_len)) < 0) + return error; + + if (!git_object_typeisloose(hdr->type)) { + git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); + return -1; + } + + return git_zstream_set_input(&stream->zstream, + data + head_len, data_len - head_len); +} + +static int loose_backend__readstream_standard( + obj_hdr *hdr, + loose_readstream *stream) +{ + unsigned char head[MAX_HEADER_LEN]; + size_t init, head_len; + int error; + + if ((error = git_zstream_set_input(&stream->zstream, + stream->map.data, stream->map.len)) < 0) + return error; + + init = sizeof(head); + + /* + * inflate the initial part of the compressed buffer in order to + * parse the header; read the largest header possible, then store + * it in the `start` field of the stream object. + */ + if ((error = git_zstream_get_output(head, &init, &stream->zstream)) < 0 || + (error = parse_header(hdr, &head_len, head, init)) < 0) + return error; + + if (!git_object_typeisloose(hdr->type)) { + git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); + return -1; + } + + if (init > head_len) { + stream->start_len = init - head_len; + memcpy(stream->start, head + head_len, init - head_len); + } + + return 0; +} + +static int loose_backend__readstream( + git_odb_stream **stream_out, + size_t *len_out, + git_object_t *type_out, + git_odb_backend *_backend, + const git_oid *oid) +{ + loose_backend *backend; + loose_readstream *stream = NULL; + git_hash_ctx *hash_ctx = NULL; + git_str object_path = GIT_STR_INIT; + obj_hdr hdr; + int error = 0; + + GIT_ASSERT_ARG(stream_out); + GIT_ASSERT_ARG(len_out); + GIT_ASSERT_ARG(type_out); + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(oid); + + backend = (loose_backend *)_backend; + *stream_out = NULL; + *len_out = 0; + *type_out = GIT_OBJECT_INVALID; + + if (locate_object(&object_path, backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + goto done; + } + + stream = git__calloc(1, sizeof(loose_readstream)); + GIT_ERROR_CHECK_ALLOC(stream); + + hash_ctx = git__malloc(sizeof(git_hash_ctx)); + GIT_ERROR_CHECK_ALLOC(hash_ctx); + + if ((error = git_hash_ctx_init(hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = git_futils_mmap_ro_file(&stream->map, object_path.ptr)) < 0 || + (error = git_zstream_init(&stream->zstream, GIT_ZSTREAM_INFLATE)) < 0) + goto done; + + /* check for a packlike loose object */ + if (!is_zlib_compressed_data(stream->map.data, stream->map.len)) + error = loose_backend__readstream_packlike(&hdr, stream); + else + error = loose_backend__readstream_standard(&hdr, stream); + + if (error < 0) + goto done; + + stream->stream.backend = _backend; + stream->stream.hash_ctx = hash_ctx; + stream->stream.read = &loose_backend__readstream_read; + stream->stream.free = &loose_backend__readstream_free; + + *stream_out = (git_odb_stream *)stream; + *len_out = hdr.size; + *type_out = hdr.type; + +done: + if (error < 0) { + if (stream) { + git_futils_mmap_free(&stream->map); + git_zstream_free(&stream->zstream); + git__free(stream); + } + if (hash_ctx) { + git_hash_ctx_cleanup(hash_ctx); + git__free(hash_ctx); + } + } + + git_str_dispose(&object_path); + return error; +} + +static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) +{ + int error = 0; + git_str final_path = GIT_STR_INIT; + char header[MAX_HEADER_LEN]; + size_t header_len; + git_filebuf fbuf = GIT_FILEBUF_INIT; + loose_backend *backend; + + backend = (loose_backend *)_backend; + + /* prepare the header for the file */ + if ((error = git_odb__format_object_header(&header_len, + header, sizeof(header), len, type)) < 0) + goto cleanup; + + if (git_str_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || + git_filebuf_open(&fbuf, final_path.ptr, filebuf_flags(backend), + backend->object_file_mode) < 0) + { + error = -1; + goto cleanup; + } + + git_filebuf_write(&fbuf, header, header_len); + git_filebuf_write(&fbuf, data, len); + + if (object_file_name(&final_path, backend, oid) < 0 || + object_mkdir(&final_path, backend) < 0 || + git_filebuf_commit_at(&fbuf, final_path.ptr) < 0) + error = -1; + +cleanup: + if (error < 0) + git_filebuf_cleanup(&fbuf); + git_str_dispose(&final_path); + return error; +} + +static int loose_backend__freshen( + git_odb_backend *_backend, + const git_oid *oid) +{ + loose_backend *backend = (loose_backend *)_backend; + git_str path = GIT_STR_INIT; + int error; + + if (object_file_name(&path, backend, oid) < 0) + return -1; + + error = git_futils_touch(path.ptr, NULL); + git_str_dispose(&path); + + return error; +} + +static void loose_backend__free(git_odb_backend *_backend) +{ + git__free(_backend); +} + +int git_odb_backend_loose( + git_odb_backend **backend_out, + const char *objects_dir, + int compression_level, + int do_fsync, + unsigned int dir_mode, + unsigned int file_mode) +{ + loose_backend *backend; + size_t objects_dirlen, alloclen; + + GIT_ASSERT_ARG(backend_out); + GIT_ASSERT_ARG(objects_dir); + + objects_dirlen = strlen(objects_dir); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(loose_backend), objects_dirlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 2); + backend = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + backend->objects_dirlen = objects_dirlen; + memcpy(backend->objects_dir, objects_dir, objects_dirlen); + if (backend->objects_dir[backend->objects_dirlen - 1] != '/') + backend->objects_dir[backend->objects_dirlen++] = '/'; + + if (compression_level < 0) + compression_level = Z_BEST_SPEED; + + if (dir_mode == 0) + dir_mode = GIT_OBJECT_DIR_MODE; + + if (file_mode == 0) + file_mode = GIT_OBJECT_FILE_MODE; + + backend->object_zlib_level = compression_level; + backend->fsync_object_files = do_fsync; + backend->object_dir_mode = dir_mode; + backend->object_file_mode = file_mode; + + backend->parent.read = &loose_backend__read; + backend->parent.write = &loose_backend__write; + backend->parent.read_prefix = &loose_backend__read_prefix; + backend->parent.read_header = &loose_backend__read_header; + backend->parent.writestream = &loose_backend__writestream; + backend->parent.readstream = &loose_backend__readstream; + backend->parent.exists = &loose_backend__exists; + backend->parent.exists_prefix = &loose_backend__exists_prefix; + backend->parent.foreach = &loose_backend__foreach; + backend->parent.freshen = &loose_backend__freshen; + backend->parent.free = &loose_backend__free; + + *backend_out = (git_odb_backend *)backend; + return 0; +} diff --git a/src/libgit2/odb_mempack.c b/src/libgit2/odb_mempack.c new file mode 100644 index 000000000..6f27f45f8 --- /dev/null +++ b/src/libgit2/odb_mempack.c @@ -0,0 +1,189 @@ +/* + * 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 "buf.h" +#include "futils.h" +#include "hash.h" +#include "odb.h" +#include "array.h" +#include "oidmap.h" +#include "pack-objects.h" + +#include "git2/odb_backend.h" +#include "git2/object.h" +#include "git2/types.h" +#include "git2/pack.h" +#include "git2/sys/odb_backend.h" +#include "git2/sys/mempack.h" + +struct memobject { + git_oid oid; + size_t len; + git_object_t type; + char data[GIT_FLEX_ARRAY]; +}; + +struct memory_packer_db { + git_odb_backend parent; + git_oidmap *objects; + git_array_t(struct memobject *) commits; +}; + +static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + struct memobject *obj = NULL; + size_t alloc_len; + + if (git_oidmap_exists(db->objects, oid)) + return 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len); + obj = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(obj); + + memcpy(obj->data, data, len); + git_oid_cpy(&obj->oid, oid); + obj->len = len; + obj->type = type; + + 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); + GIT_ERROR_CHECK_ALLOC(store); + *store = obj; + } + + return 0; +} + +static int impl__exists(git_odb_backend *backend, const git_oid *oid) +{ + struct memory_packer_db *db = (struct memory_packer_db *)backend; + + return git_oidmap_exists(db->objects, 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; + + if ((obj = git_oidmap_get(db->objects, oid)) == NULL) + return GIT_ENOTFOUND; + + *len_p = obj->len; + *type_p = obj->type; + *buffer_p = git__malloc(obj->len); + GIT_ERROR_CHECK_ALLOC(*buffer_p); + + memcpy(*buffer_p, obj->data, obj->len); + return 0; +} + +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; + + if ((obj = git_oidmap_get(db->objects, oid)) == NULL) + return GIT_ENOTFOUND; + + *len_p = obj->len; + *type_p = obj->type; + return 0; +} + +static int git_mempack__dump( + git_str *pack, + git_repository *repo, + git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + git_packbuilder *packbuilder; + uint32_t i; + int err = -1; + + if (git_packbuilder_new(&packbuilder, repo) < 0) + return -1; + + git_packbuilder_set_threads(packbuilder, 0); + + for (i = 0; i < db->commits.size; ++i) { + struct memobject *commit = db->commits.ptr[i]; + + err = git_packbuilder_insert_commit(packbuilder, &commit->oid); + if (err < 0) + goto cleanup; + } + + err = git_packbuilder__write_buf(pack, packbuilder); + +cleanup: + git_packbuilder_free(packbuilder); + return err; +} + +int git_mempack_dump( + git_buf *pack, + git_repository *repo, + git_odb_backend *_backend) +{ + GIT_BUF_WRAP_PRIVATE(pack, git_mempack__dump, repo, _backend); +} + +int git_mempack_reset(git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + struct memobject *object = NULL; + + git_oidmap_foreach_value(db->objects, object, { + git__free(object); + }); + + git_array_clear(db->commits); + + git_oidmap_clear(db->objects); + + return 0; +} + +static void impl__free(git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + + git_mempack_reset(_backend); + git_oidmap_free(db->objects); + git__free(db); +} + +int git_mempack_new(git_odb_backend **out) +{ + struct memory_packer_db *db; + + GIT_ASSERT_ARG(out); + + db = git__calloc(1, sizeof(struct memory_packer_db)); + GIT_ERROR_CHECK_ALLOC(db); + + if (git_oidmap_new(&db->objects) < 0) + return -1; + + db->parent.version = GIT_ODB_BACKEND_VERSION; + db->parent.read = &impl__read; + db->parent.write = &impl__write; + db->parent.read_header = &impl__read_header; + db->parent.exists = &impl__exists; + db->parent.free = &impl__free; + + *out = (git_odb_backend *)db; + return 0; +} diff --git a/src/libgit2/odb_pack.c b/src/libgit2/odb_pack.c new file mode 100644 index 000000000..818cc6125 --- /dev/null +++ b/src/libgit2/odb_pack.c @@ -0,0 +1,921 @@ +/* + * 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 +#include "git2/repository.h" +#include "git2/indexer.h" +#include "git2/sys/odb_backend.h" +#include "delta.h" +#include "futils.h" +#include "hash.h" +#include "midx.h" +#include "mwindow.h" +#include "odb.h" +#include "pack.h" + +#include "git2/odb_backend.h" + +/* re-freshen pack files no more than every 2 seconds */ +#define FRESHEN_FREQUENCY 2 + +struct pack_backend { + git_odb_backend parent; + git_midx_file *midx; + git_vector midx_packs; + git_vector packs; + struct git_pack_file *last_found; + char *pack_folder; +}; + +struct pack_writepack { + struct git_odb_writepack parent; + git_indexer *indexer; +}; + +/** + * The wonderful tale of a Packed Object lookup query + * =================================================== + * A riveting and epic story of epicness and ASCII + * art, presented by yours truly, + * Sir Vicent of Marti + * + * + * Chapter 1: Once upon a time... + * Initialization of the Pack Backend + * -------------------------------------------------- + * + * # git_odb_backend_pack + * | Creates the pack backend structure, initializes the + * | callback pointers to our default read() and exist() methods, + * | and tries to find the `pack` folder, if it exists. ODBs without a `pack` + * | folder are ignored altogether. If there is a `pack` folder, it tries to + * | preload all the known packfiles in the ODB. + * | + * |-# pack_backend__refresh + * | The `multi-pack-index` is loaded if it exists and is valid. + * | Then we run a `dirent` callback through every file in the pack folder, + * | even those present in `multi-pack-index`. The unindexed packfiles are + * | then sorted according to a sorting callback. + * | + * |-# refresh_multi_pack_index + * | Detect the presence of the `multi-pack-index` file. If it needs to be + * | refreshed, frees the old copy and tries to load the new one, together + * | with all the packfiles it indexes. If the process fails, fall back to + * | the old behavior, as if the `multi-pack-index` file was not there. + * | + * |-# packfile_load__cb + * | | This callback is called from `dirent` with every single file + * | | inside the pack folder. We find the packs by actually locating + * | | their index (ends in ".idx"). From that index, we verify that + * | | the corresponding packfile exists and is valid, and if so, we + * | | add it to the pack list. + * | | + * | # git_mwindow_get_pack + * | Make sure that there's a packfile to back this index, and store + * | some very basic information regarding the packfile itself, + * | such as the full path, the size, and the modification time. + * | We don't actually open the packfile to check for internal consistency. + * | + * |-# packfile_sort__cb + * Sort all the preloaded packs according to some specific criteria: + * we prioritize the "newer" packs because it's more likely they + * contain the objects we are looking for, and we prioritize local + * packs over remote ones. + * + * + * + * Chapter 2: To be, or not to be... + * A standard packed `exist` query for an OID + * -------------------------------------------------- + * + * # pack_backend__exists / pack_backend__exists_prefix + * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the + * | packs that have been loaded for our ODB. + * | + * |-# pack_entry_find / pack_entry_find_prefix + * | If there is a multi-pack-index present, search the SHA1 oid in that + * | index first. If it is not found there, iterate through all the unindexed + * | packs that have been preloaded (starting by the pack where the latest + * | object was found) to try to find the OID in one of them. + * | + * |-# git_midx_entry_find + * | Search for the SHA1 oid in the multi-pack-index. See + * | + * | for specifics on the multi-pack-index format and how do we find + * | entries in it. + * | + * |-# git_pack_entry_find + * | Check the index of an individual unindexed pack to see if the SHA1 + * | OID can be found. If we can find the offset to that SHA1 inside of the + * | index, that means the object is contained inside of the packfile and + * | we can stop searching. Before returning, we verify that the + * | packfile behind the index we are searching still exists on disk. + * | + * |-# pack_entry_find_offset + * | Mmap the actual index file to disk if it hasn't been opened + * | yet, and run a binary search through it to find the OID. + * | See + * | for specifics on the Packfile Index format and how do we find + * | entries in it. + * | + * |-# pack_index_open + * | Guess the name of the index based on the full path to the + * | packfile, open it and verify its contents. Only if the index + * | has not been opened already. + * | + * |-# pack_index_check + * Mmap the index file and do a quick run through the header + * to guess the index version (right now we support v1 and v2), + * and to verify that the size of the index makes sense. + * + * + * + * Chapter 3: The neverending story... + * A standard packed `lookup` query for an OID + * -------------------------------------------------- + * + * # pack_backend__read / pack_backend__read_prefix + * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the + * | packs that have been loaded for our ODB. If it does, open the packfile and + * | read from it. + * | + * |-# git_packfile_unpack + * Armed with a packfile and the offset within it, we can finally unpack + * the object pointed at by the SHA1 oid. This involves mmapping part of + * the `.pack` file, and uncompressing the object within it (if it is + * stored in the undelfitied representation), or finding a base object and + * applying some deltas to its uncompressed representation (if it is stored + * in the deltified representation). See + * + * for specifics on the Packfile format and how do we read from it. + * + */ + + +/*********************************************************** + * + * FORWARD DECLARATIONS + * + ***********************************************************/ + +static int packfile_sort__cb(const void *a_, const void *b_); + +static int packfile_load__cb(void *_data, git_str *path); + +static int packfile_byname_search_cmp(const void *path, const void *pack_entry); + +static int pack_entry_find(struct git_pack_entry *e, + struct pack_backend *backend, const git_oid *oid); + +/* Can find the offset of an object given + * a prefix of an identifier. + * Sets GIT_EAMBIGUOUS if short oid is ambiguous. + * This method assumes that len is between + * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. + */ +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + size_t len); + + + +/*********************************************************** + * + * PACK WINDOW MANAGEMENT + * + ***********************************************************/ + +static int packfile_byname_search_cmp(const void *path_, const void *p_) +{ + const git_str *path = (const git_str *)path_; + const struct git_pack_file *p = (const struct git_pack_file *)p_; + + return strncmp(p->pack_name, git_str_cstr(path), git_str_len(path)); +} + +static int packfile_sort__cb(const void *a_, const void *b_) +{ + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; + int st; + + /* + * Local packs tend to contain objects specific to our + * variant of the project than remote ones. In addition, + * remote ones could be on a network mounted filesystem. + * Favor local ones for these reasons. + */ + st = a->pack_local - b->pack_local; + if (st) + return -st; + + /* + * Younger packs tend to contain more recent objects, + * and more recent objects tend to get accessed more + * often. + */ + if (a->mtime < b->mtime) + return 1; + else if (a->mtime == b->mtime) + return 0; + + return -1; +} + + +static int packfile_load__cb(void *data, git_str *path) +{ + struct pack_backend *backend = data; + struct git_pack_file *pack; + const char *path_str = git_str_cstr(path); + git_str index_prefix = GIT_STR_INIT; + size_t cmp_len = git_str_len(path); + int error; + + if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0) + return 0; /* not an index */ + + cmp_len -= strlen(".idx"); + git_str_attach_notowned(&index_prefix, path_str, cmp_len); + + if (git_vector_search2(NULL, &backend->midx_packs, packfile_byname_search_cmp, &index_prefix) == 0) + return 0; + if (git_vector_search2(NULL, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) + return 0; + + error = git_mwindow_get_pack(&pack, path->ptr); + + /* ignore missing .pack file as git does */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + + if (!error) + error = git_vector_insert(&backend->packs, pack); + + return error; + +} + +static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) +{ + struct git_pack_file *last_found = backend->last_found, *p; + git_midx_entry midx_entry; + size_t i; + + if (backend->midx && + git_midx_entry_find(&midx_entry, backend->midx, oid, GIT_OID_HEXSZ) == 0 && + midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { + e->offset = midx_entry.offset; + git_oid_cpy(&e->sha1, &midx_entry.sha1); + e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); + return 0; + } + + if (last_found && + git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) + return 0; + + git_vector_foreach(&backend->packs, i, p) { + if (p == last_found) + continue; + + if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { + backend->last_found = p; + return 0; + } + } + + return git_odb__error_notfound( + "failed to find pack entry", oid, GIT_OID_HEXSZ); +} + +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error; + size_t i; + git_oid found_full_oid = {{0}}; + bool found = false; + struct git_pack_file *last_found = backend->last_found, *p; + git_midx_entry midx_entry; + + if (backend->midx) { + error = git_midx_entry_find(&midx_entry, backend->midx, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error && midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { + e->offset = midx_entry.offset; + git_oid_cpy(&e->sha1, &midx_entry.sha1); + e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + } + } + + if (last_found) { + error = git_pack_entry_find(e, last_found, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) { + if (found && git_oid_cmp(&e->sha1, &found_full_oid)) + return git_odb__error_ambiguous("found multiple pack entries"); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + } + } + + git_vector_foreach(&backend->packs, i, p) { + if (p == last_found) + continue; + + error = git_pack_entry_find(e, p, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) { + if (found && git_oid_cmp(&e->sha1, &found_full_oid)) + return git_odb__error_ambiguous("found multiple pack entries"); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + backend->last_found = p; + } + } + + if (!found) + return git_odb__error_notfound("no matching pack entry for prefix", + short_oid, len); + else + return 0; +} + +/*********************************************************** + * + * MULTI-PACK-INDEX SUPPORT + * + * Functions needed to support the multi-pack-index. + * + ***********************************************************/ + +/* + * Remove the multi-pack-index, and move all midx_packs to packs. + */ +static int remove_multi_pack_index(struct pack_backend *backend) +{ + size_t i, j = git_vector_length(&backend->packs); + struct pack_backend *p; + int error = git_vector_size_hint( + &backend->packs, + j + git_vector_length(&backend->midx_packs)); + if (error < 0) + return error; + + git_vector_foreach(&backend->midx_packs, i, p) + git_vector_set(NULL, &backend->packs, j++, p); + git_vector_clear(&backend->midx_packs); + + git_midx_free(backend->midx); + backend->midx = NULL; + + return 0; +} + +/* + * Loads a single .pack file referred to by the multi-pack-index. These must + * match the order in which they are declared in the multi-pack-index file, + * since these files are referred to by their index. + */ +static int process_multi_pack_index_pack( + struct pack_backend *backend, + size_t i, + const char *packfile_name) +{ + int error; + struct git_pack_file *pack; + size_t found_position; + git_str pack_path = GIT_STR_INIT, index_prefix = GIT_STR_INIT; + + error = git_str_joinpath(&pack_path, backend->pack_folder, packfile_name); + if (error < 0) + return error; + + /* This is ensured by midx_parse_packfile_name() */ + if (git_str_len(&pack_path) <= strlen(".idx") || git__suffixcmp(git_str_cstr(&pack_path), ".idx") != 0) + return git_odb__error_notfound("midx file contained a non-index", NULL, 0); + + git_str_attach_notowned(&index_prefix, git_str_cstr(&pack_path), git_str_len(&pack_path) - strlen(".idx")); + + if (git_vector_search2(&found_position, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) { + /* Pack was found in the packs list. Moving it to the midx_packs list. */ + git_str_dispose(&pack_path); + git_vector_set(NULL, &backend->midx_packs, i, git_vector_get(&backend->packs, found_position)); + git_vector_remove(&backend->packs, found_position); + return 0; + } + + /* Pack was not found. Allocate a new one. */ + error = git_mwindow_get_pack(&pack, git_str_cstr(&pack_path)); + git_str_dispose(&pack_path); + if (error < 0) + return error; + + git_vector_set(NULL, &backend->midx_packs, i, pack); + return 0; +} + +/* + * Reads the multi-pack-index. If this fails for whatever reason, the + * multi-pack-index object is freed, and all the packfiles that are related to + * it are moved to the unindexed packfiles vector. + */ +static int refresh_multi_pack_index(struct pack_backend *backend) +{ + int error; + git_str midx_path = GIT_STR_INIT; + const char *packfile_name; + size_t i; + + error = git_str_joinpath(&midx_path, backend->pack_folder, "multi-pack-index"); + if (error < 0) + return error; + + /* + * Check whether the multi-pack-index has changed. If it has, close any + * old multi-pack-index and move all the packfiles to the unindexed + * packs. This is done to prevent losing any open packfiles in case + * refreshing the new multi-pack-index fails, or the file is deleted. + */ + if (backend->midx) { + if (!git_midx_needs_refresh(backend->midx, git_str_cstr(&midx_path))) { + git_str_dispose(&midx_path); + return 0; + } + error = remove_multi_pack_index(backend); + if (error < 0) { + git_str_dispose(&midx_path); + return error; + } + } + + error = git_midx_open(&backend->midx, git_str_cstr(&midx_path)); + git_str_dispose(&midx_path); + if (error < 0) + return error; + + git_vector_resize_to(&backend->midx_packs, git_vector_length(&backend->midx->packfile_names)); + + git_vector_foreach(&backend->midx->packfile_names, i, packfile_name) { + error = process_multi_pack_index_pack(backend, i, packfile_name); + if (error < 0) { + /* + * Something failed during reading multi-pack-index. + * Restore the state of backend as if the + * multi-pack-index was never there, and move all + * packfiles that have been processed so far to the + * unindexed packs. + */ + git_vector_resize_to(&backend->midx_packs, i); + remove_multi_pack_index(backend); + return error; + } + } + + return 0; +} + +/*********************************************************** + * + * PACKED BACKEND PUBLIC API + * + * Implement the git_odb_backend API calls + * + ***********************************************************/ +static int pack_backend__refresh(git_odb_backend *backend_) +{ + int error; + struct stat st; + git_str path = GIT_STR_INIT; + struct pack_backend *backend = (struct pack_backend *)backend_; + + if (backend->pack_folder == NULL) + return 0; + + if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) + return git_odb__error_notfound("failed to refresh packfiles", NULL, 0); + + if (refresh_multi_pack_index(backend) < 0) { + /* + * It is okay if this fails. We will just not use the + * multi-pack-index in this case. + */ + git_error_clear(); + } + + /* reload all packs */ + git_str_sets(&path, backend->pack_folder); + error = git_fs_path_direach(&path, 0, packfile_load__cb, backend); + + git_str_dispose(&path); + git_vector_sort(&backend->packs); + + return error; +} + +static int pack_backend__read_header( + size_t *len_p, git_object_t *type_p, + struct git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + int error; + + GIT_ASSERT_ARG(len_p); + GIT_ASSERT_ARG(type_p); + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) + return error; + + return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); +} + +static int pack_backend__freshen( + git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + time_t now; + int error; + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) + return error; + + now = time(NULL); + + if (e.p->last_freshen > now - FRESHEN_FREQUENCY) + return 0; + + if ((error = git_futils_touch(e.p->pack_name, &now)) < 0) + return error; + + e.p->last_freshen = now; + return 0; +} + +static int pack_backend__read( + void **buffer_p, size_t *len_p, git_object_t *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + git_rawobj raw = {NULL}; + int error; + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 || + (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0) + return error; + + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + + return 0; +} + +static int pack_backend__read_prefix( + git_oid *out_oid, + void **buffer_p, + size_t *len_p, + git_object_t *type_p, + git_odb_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error = 0; + + if (len < GIT_OID_MINPREFIXLEN) + error = git_odb__error_ambiguous("prefix length too short"); + + else if (len >= GIT_OID_HEXSZ) { + /* We can fall back to regular read method */ + error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) + git_oid_cpy(out_oid, short_oid); + } else { + struct git_pack_entry e; + git_rawobj raw = {NULL}; + + if ((error = pack_entry_find_prefix( + &e, (struct pack_backend *)backend, short_oid, len)) == 0 && + (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + git_oid_cpy(out_oid, &e.sha1); + } + } + + return error; +} + +static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; +} + +static int pack_backend__exists_prefix( + git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) +{ + int error; + struct pack_backend *pb = (struct pack_backend *)backend; + struct git_pack_entry e = {0}; + + error = pack_entry_find_prefix(&e, pb, short_id, len); + git_oid_cpy(out, &e.sha1); + return error; +} + +static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) +{ + int error; + struct git_pack_file *p; + struct pack_backend *backend; + unsigned int i; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(cb); + + backend = (struct pack_backend *)_backend; + + /* Make sure we know about the packfiles */ + if ((error = pack_backend__refresh(_backend)) != 0) + return error; + + if (backend->midx && (error = git_midx_foreach_entry(backend->midx, cb, data)) != 0) + return error; + git_vector_foreach(&backend->packs, i, p) { + if ((error = git_pack_foreach_entry(p, cb, data)) != 0) + return error; + } + + return 0; +} + +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; + + GIT_ASSERT_ARG(writepack); + + return git_indexer_append(writepack->indexer, data, size, stats); +} + +static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_indexer_progress *stats) +{ + struct pack_writepack *writepack = (struct pack_writepack *)_writepack; + + GIT_ASSERT_ARG(writepack); + + return git_indexer_commit(writepack->indexer, stats); +} + +static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) +{ + struct pack_writepack *writepack; + + if (!_writepack) + return; + + writepack = (struct pack_writepack *)_writepack; + + git_indexer_free(writepack->indexer); + git__free(writepack); +} + +static int pack_backend__writepack(struct git_odb_writepack **out, + git_odb_backend *_backend, + git_odb *odb, + git_indexer_progress_cb progress_cb, + void *progress_payload) +{ + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + struct pack_backend *backend; + struct pack_writepack *writepack; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(_backend); + + *out = NULL; + + opts.progress_cb = progress_cb; + opts.progress_cb_payload = progress_payload; + + backend = (struct pack_backend *)_backend; + + writepack = git__calloc(1, sizeof(struct pack_writepack)); + GIT_ERROR_CHECK_ALLOC(writepack); + + if (git_indexer_new(&writepack->indexer, + backend->pack_folder, 0, odb, &opts) < 0) { + git__free(writepack); + return -1; + } + + writepack->parent.backend = _backend; + writepack->parent.append = pack_backend__writepack_append; + writepack->parent.commit = pack_backend__writepack_commit; + writepack->parent.free = pack_backend__writepack_free; + + *out = (git_odb_writepack *)writepack; + + return 0; +} + +static int get_idx_path( + git_str *idx_path, + struct pack_backend *backend, + struct git_pack_file *p) +{ + size_t path_len; + int error; + + error = git_fs_path_prettify(idx_path, p->pack_name, backend->pack_folder); + if (error < 0) + return error; + path_len = git_str_len(idx_path); + if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(idx_path), ".pack") != 0) + return git_odb__error_notfound("packfile does not end in .pack", NULL, 0); + path_len -= strlen(".pack"); + error = git_str_splice(idx_path, path_len, strlen(".pack"), ".idx", strlen(".idx")); + if (error < 0) + return error; + + return 0; +} + +static int pack_backend__writemidx(git_odb_backend *_backend) +{ + struct pack_backend *backend; + git_midx_writer *w = NULL; + struct git_pack_file *p; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(_backend); + + backend = (struct pack_backend *)_backend; + + error = git_midx_writer_new(&w, backend->pack_folder); + if (error < 0) + return error; + + git_vector_foreach(&backend->midx_packs, i, p) { + git_str idx_path = GIT_STR_INIT; + error = get_idx_path(&idx_path, backend, p); + if (error < 0) + goto cleanup; + error = git_midx_writer_add(w, git_str_cstr(&idx_path)); + git_str_dispose(&idx_path); + if (error < 0) + goto cleanup; + } + git_vector_foreach(&backend->packs, i, p) { + git_str idx_path = GIT_STR_INIT; + error = get_idx_path(&idx_path, backend, p); + if (error < 0) + goto cleanup; + error = git_midx_writer_add(w, git_str_cstr(&idx_path)); + git_str_dispose(&idx_path); + if (error < 0) + goto cleanup; + } + + /* + * Invalidate the previous midx before writing the new one. + */ + error = remove_multi_pack_index(backend); + if (error < 0) + goto cleanup; + error = git_midx_writer_commit(w); + if (error < 0) + goto cleanup; + error = refresh_multi_pack_index(backend); + +cleanup: + git_midx_writer_free(w); + return error; +} + +static void pack_backend__free(git_odb_backend *_backend) +{ + struct pack_backend *backend; + struct git_pack_file *p; + size_t i; + + if (!_backend) + return; + + backend = (struct pack_backend *)_backend; + + git_vector_foreach(&backend->midx_packs, i, p) + git_mwindow_put_pack(p); + git_vector_foreach(&backend->packs, i, p) + git_mwindow_put_pack(p); + + git_midx_free(backend->midx); + git_vector_free(&backend->midx_packs); + git_vector_free(&backend->packs); + git__free(backend->pack_folder); + git__free(backend); +} + +static int pack_backend__alloc(struct pack_backend **out, size_t initial_size) +{ + struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + if (git_vector_init(&backend->midx_packs, 0, NULL) < 0) { + git__free(backend); + return -1; + } + if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) { + git_vector_free(&backend->midx_packs); + git__free(backend); + return -1; + } + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + + backend->parent.read = &pack_backend__read; + backend->parent.read_prefix = &pack_backend__read_prefix; + backend->parent.read_header = &pack_backend__read_header; + backend->parent.exists = &pack_backend__exists; + backend->parent.exists_prefix = &pack_backend__exists_prefix; + backend->parent.refresh = &pack_backend__refresh; + backend->parent.foreach = &pack_backend__foreach; + backend->parent.writepack = &pack_backend__writepack; + backend->parent.writemidx = &pack_backend__writemidx; + backend->parent.freshen = &pack_backend__freshen; + backend->parent.free = &pack_backend__free; + + *out = backend; + return 0; +} + +int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) +{ + struct pack_backend *backend = NULL; + struct git_pack_file *packfile = NULL; + + if (pack_backend__alloc(&backend, 1) < 0) + return -1; + + if (git_mwindow_get_pack(&packfile, idx) < 0 || + git_vector_insert(&backend->packs, packfile) < 0) + { + pack_backend__free((git_odb_backend *)backend); + return -1; + } + + *backend_out = (git_odb_backend *)backend; + return 0; +} + +int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) +{ + int error = 0; + struct pack_backend *backend = NULL; + git_str path = GIT_STR_INIT; + + if (pack_backend__alloc(&backend, 8) < 0) + return -1; + + if (!(error = git_str_joinpath(&path, objects_dir, "pack")) && + git_fs_path_isdir(git_str_cstr(&path))) + { + backend->pack_folder = git_str_detach(&path); + error = pack_backend__refresh((git_odb_backend *)backend); + } + + if (error < 0) { + pack_backend__free((git_odb_backend *)backend); + backend = NULL; + } + + *backend_out = (git_odb_backend *)backend; + + git_str_dispose(&path); + + return error; +} diff --git a/src/libgit2/offmap.c b/src/libgit2/offmap.c new file mode 100644 index 000000000..be9eb66d8 --- /dev/null +++ b/src/libgit2/offmap.c @@ -0,0 +1,101 @@ +/* + * 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 "offmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(off, off64_t, void *) + +__KHASH_IMPL(off, static kh_inline, off64_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) + + +int git_offmap_new(git_offmap **out) +{ + *out = kh_init(off); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_offmap_free(git_offmap *map) +{ + kh_destroy(off, map); +} + +void git_offmap_clear(git_offmap *map) +{ + kh_clear(off, map); +} + +size_t git_offmap_size(git_offmap *map) +{ + return kh_size(map); +} + +void *git_offmap_get(git_offmap *map, const off64_t 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_set(git_offmap *map, const off64_t key, void *value) +{ + 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_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_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_value(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/libgit2/offmap.h b/src/libgit2/offmap.h new file mode 100644 index 000000000..81c459b01 --- /dev/null +++ b/src/libgit2/offmap.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_offmap_h__ +#define INCLUDE_offmap_h__ + +#include "common.h" + +#include "git2/types.h" + +/** A map with `off64_t`s as key. */ +typedef struct kh_off_s git_offmap; + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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 = 0; \ + while (git_offmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ + code; \ + } } + +#define git_offmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_offmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/libgit2/oid.c b/src/libgit2/oid.c new file mode 100644 index 000000000..fb92174ab --- /dev/null +++ b/src/libgit2/oid.c @@ -0,0 +1,444 @@ +/* + * 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 "oid.h" + +#include "git2/oid.h" +#include "repository.h" +#include "threadstate.h" +#include +#include + +const git_oid git_oid__empty_blob_sha1 = + {{ 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, 0xd6, 0x43, 0x4b, 0x8b, + 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91 }}; +const git_oid git_oid__empty_tree_sha1 = + {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, + 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }}; + +static char to_hex[] = "0123456789abcdef"; + +static int oid_error_invalid(const char *msg) +{ + git_error_set(GIT_ERROR_INVALID, "unable to parse OID - %s", msg); + return -1; +} + +int git_oid_fromstrn(git_oid *out, const char *str, size_t length) +{ + size_t p; + int v; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(str); + + if (!length) + return oid_error_invalid("too short"); + + if (length > GIT_OID_HEXSZ) + return oid_error_invalid("too long"); + + memset(out->id, 0, GIT_OID_RAWSZ); + + for (p = 0; p < length; p++) { + v = git__fromhex(str[p]); + if (v < 0) + return oid_error_invalid("contains invalid characters"); + + out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4)); + } + + return 0; +} + +int git_oid_fromstrp(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, strlen(str)); +} + +int git_oid_fromstr(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); +} + +GIT_INLINE(char) *fmt_one(char *str, unsigned int val) +{ + *str++ = to_hex[val >> 4]; + *str++ = to_hex[val & 0xf]; + return str; +} + +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 0; + } + if (n > GIT_OID_HEXSZ) { + memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ); + n = GIT_OID_HEXSZ; + } + + max_i = n / 2; + + for (i = 0; i < max_i; i++) + str = fmt_one(str, oid->id[i]); + + if (n & 1) + *str++ = to_hex[oid->id[i] >> 4]; + + return 0; +} + +int git_oid_fmt(char *str, const git_oid *oid) +{ + return git_oid_nfmt(str, GIT_OID_HEXSZ, oid); +} + +int git_oid_pathfmt(char *str, const git_oid *oid) +{ + size_t i; + + str = fmt_one(str, oid->id[0]); + *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) +{ + char *str = GIT_THREADSTATE->oid_fmt; + git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); + return str; +} + +char *git_oid_allocfmt(const git_oid *oid) +{ + char *str = git__malloc(GIT_OID_HEXSZ + 1); + if (!str) + return NULL; + git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); + return str; +} + +char *git_oid_tostr(char *out, size_t n, const git_oid *oid) +{ + if (!out || n == 0) + return ""; + + if (n > GIT_OID_HEXSZ + 1) + n = GIT_OID_HEXSZ + 1; + + git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */ + out[n - 1] = '\0'; + + return out; +} + +int git_oid__parse( + git_oid *oid, const char **buffer_out, + const char *buffer_end, const char *header) +{ + const size_t sha_len = GIT_OID_HEXSZ; + const size_t header_len = strlen(header); + + const char *buffer = *buffer_out; + + if (buffer + (header_len + sha_len + 1) > buffer_end) + return -1; + + if (memcmp(buffer, header, header_len) != 0) + return -1; + + if (buffer[header_len + sha_len] != '\n') + return -1; + + if (git_oid_fromstr(oid, buffer + header_len) < 0) + return -1; + + *buffer_out = buffer + (header_len + sha_len + 1); + + return 0; +} + +void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid) +{ + char hex_oid[GIT_OID_HEXSZ]; + + git_oid_fmt(hex_oid, oid); + git_str_puts(buf, header); + git_str_put(buf, hex_oid, GIT_OID_HEXSZ); + git_str_putc(buf, '\n'); +} + +int git_oid_fromraw(git_oid *out, const unsigned char *raw) +{ + memcpy(out->id, raw, sizeof(out->id)); + return 0; +} + +int git_oid_cpy(git_oid *out, const git_oid *src) +{ + return git_oid_raw_cpy(out->id, src->id); +} + +int git_oid_cmp(const git_oid *a, const git_oid *b) +{ + return git_oid__cmp(a, b); +} + +int git_oid_equal(const git_oid *a, const git_oid *b) +{ + return (git_oid__cmp(a, b) == 0); +} + +int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len) +{ + return git_oid_raw_ncmp(oid_a->id, oid_b->id, len); +} + +int git_oid_strcmp(const git_oid *oid_a, const char *str) +{ + const unsigned char *a; + unsigned char strval; + int hexval; + + for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) { + if ((hexval = git__fromhex(*str++)) < 0) + return -1; + strval = (unsigned char)(hexval << 4); + if (*str) { + if ((hexval = git__fromhex(*str++)) < 0) + return -1; + strval |= hexval; + } + if (*a != strval) + return (*a - strval); + } + + return 0; +} + +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_is_zero(const git_oid *oid_a) +{ + const unsigned char *a = oid_a->id; + unsigned int i; + for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) + if (*a != 0) + return 0; + return 1; +} + +#ifndef GIT_DEPRECATE_HARD +int git_oid_iszero(const git_oid *oid_a) +{ + return git_oid_is_zero(oid_a); +} +#endif + +typedef short node_index; + +typedef union { + const char *tail; + node_index children[16]; +} trie_node; + +struct git_oid_shorten { + trie_node *nodes; + size_t node_count, size; + int min_length, full; +}; + +static int resize_trie(git_oid_shorten *self, size_t new_size) +{ + self->nodes = git__reallocarray(self->nodes, new_size, sizeof(trie_node)); + GIT_ERROR_CHECK_ALLOC(self->nodes); + + if (new_size > self->size) { + memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); + } + + self->size = new_size; + return 0; +} + +static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) +{ + trie_node *node, *leaf; + node_index idx_leaf; + + if (os->node_count >= os->size) { + if (resize_trie(os, os->size * 2) < 0) + return NULL; + } + + idx_leaf = (node_index)os->node_count++; + + if (os->node_count == SHRT_MAX) { + os->full = 1; + return NULL; + } + + node = &os->nodes[idx]; + node->children[push_at] = -idx_leaf; + + leaf = &os->nodes[idx_leaf]; + leaf->tail = oid; + + return node; +} + +git_oid_shorten *git_oid_shorten_new(size_t min_length) +{ + git_oid_shorten *os; + + GIT_ASSERT_ARG_WITH_RETVAL((size_t)((int)min_length) == min_length, NULL); + + os = git__calloc(1, sizeof(git_oid_shorten)); + if (os == NULL) + return NULL; + + if (resize_trie(os, 16) < 0) { + git__free(os); + return NULL; + } + + os->node_count = 1; + os->min_length = (int)min_length; + + return os; +} + +void git_oid_shorten_free(git_oid_shorten *os) +{ + if (os == NULL) + return; + + git__free(os->nodes); + git__free(os); +} + + +/* + * What wizardry is this? + * + * This is just a memory-optimized trie: basically a very fancy + * 16-ary tree, which is used to store the prefixes of the OID + * strings. + * + * Read more: http://en.wikipedia.org/wiki/Trie + * + * Magic that happens in this method: + * + * - Each node in the trie is an union, so it can work both as + * a normal node, or as a leaf. + * + * - Each normal node points to 16 children (one for each possible + * character in the oid). This is *not* stored in an array of + * pointers, because in a 64-bit arch this would be sucking + * 16*sizeof(void*) = 128 bytes of memory per node, which is + * insane. What we do is store Node Indexes, and use these indexes + * to look up each node in the om->index array. These indexes are + * signed shorts, so this limits the amount of unique OIDs that + * fit in the structure to about 20000 (assuming a more or less uniform + * distribution). + * + * - All the nodes in om->index array are stored contiguously in + * memory, and each of them is 32 bytes, so we fit 2x nodes per + * cache line. Convenient for speed. + * + * - To differentiate the leafs from the normal nodes, we store all + * the indexes towards a leaf as a negative index (indexes to normal + * nodes are positives). When we find that one of the children for + * a node has a negative value, that means it's going to be a leaf. + * This reduces the amount of indexes we have by two, but also reduces + * the size of each node by 1-4 bytes (the amount we would need to + * add a `is_leaf` field): this is good because it allows the nodes + * to fit cleanly in cache lines. + * + * - Once we reach an empty children, instead of continuing to insert + * new nodes for each remaining character of the OID, we store a pointer + * to the tail in the leaf; if the leaf is reached again, we turn it + * into a normal node and use the tail to create a new leaf. + * + * This is a pretty good balance between performance and memory usage. + */ +int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) +{ + int i; + bool is_leaf; + node_index idx; + + if (os->full) { + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); + return -1; + } + + if (text_oid == NULL) + return os->min_length; + + idx = 0; + is_leaf = false; + + for (i = 0; i < GIT_OID_HEXSZ; ++i) { + int c = git__fromhex(text_oid[i]); + trie_node *node; + + if (c == -1) { + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - invalid hex value"); + return -1; + } + + node = &os->nodes[idx]; + + if (is_leaf) { + const char *tail; + + tail = node->tail; + node->tail = NULL; + + node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); + if (node == NULL) { + if (os->full) + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); + return -1; + } + } + + if (node->children[c] == 0) { + if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) { + if (os->full) + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); + return -1; + } + break; + } + + idx = node->children[c]; + is_leaf = false; + + if (idx < 0) { + node->children[c] = idx = -idx; + is_leaf = true; + } + } + + if (++i > os->min_length) + os->min_length = i; + + return os->min_length; +} + diff --git a/src/libgit2/oid.h b/src/libgit2/oid.h new file mode 100644 index 000000000..abae9a4b2 --- /dev/null +++ b/src/libgit2/oid.h @@ -0,0 +1,99 @@ +/* + * 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_oid_h__ +#define INCLUDE_oid_h__ + +#include "common.h" + +#include "git2/oid.h" + +extern const git_oid git_oid__empty_blob_sha1; +extern const git_oid git_oid__empty_tree_sha1; + +/** + * Format a git_oid into a newly allocated c-string. + * + * The c-string is owned by the caller and needs to be manually freed. + * + * @param id the oid structure to format + * @return the c-string; NULL if memory is exhausted. Caller must + * deallocate the string with git__free(). + */ +char *git_oid_allocfmt(const git_oid *id); + +GIT_INLINE(int) git_oid_raw_ncmp( + const unsigned char *sha1, + const unsigned char *sha2, + size_t len) +{ + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + while (len > 1) { + if (*sha1 != *sha2) + return 1; + sha1++; + sha2++; + len -= 2; + }; + + if (len) + if ((*sha1 ^ *sha2) & 0xf0) + return 1; + + return 0; +} + +GIT_INLINE(int) git_oid_raw_cmp( + const unsigned char *sha1, + const unsigned char *sha2) +{ + return memcmp(sha1, sha2, GIT_OID_RAWSZ); +} + +GIT_INLINE(int) git_oid_raw_cpy( + unsigned char *dst, + const unsigned char *src) +{ + memcpy(dst, src, GIT_OID_RAWSZ); + return 0; +} + +/* + * Compare two oid structures. + * + * @param a first oid structure. + * @param b second oid structure. + * @return <0, 0, >0 if a < b, a == b, a > b. + */ +GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) +{ + return git_oid_raw_cmp(a->id, b->id); +} + +GIT_INLINE(void) git_oid__cpy_prefix( + git_oid *out, const git_oid *id, size_t len) +{ + memcpy(&out->id, id->id, (len + 1) / 2); + + if (len & 1) + out->id[len / 2] &= 0xF0; +} + +GIT_INLINE(bool) git_oid__is_hexstr(const char *str) +{ + size_t i; + + for (i = 0; str[i] != '\0'; i++) { + if (git__fromhex(str[i]) < 0) + return false; + } + + return (i == GIT_OID_HEXSZ); +} + +#endif diff --git a/src/libgit2/oidarray.c b/src/libgit2/oidarray.c new file mode 100644 index 000000000..583017c4e --- /dev/null +++ b/src/libgit2/oidarray.c @@ -0,0 +1,43 @@ +/* + * 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 "oidarray.h" + +#include "git2/oidarray.h" +#include "array.h" + +void git_oidarray_dispose(git_oidarray *arr) +{ + git__free(arr->ids); +} + +void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array) +{ + arr->count = array->size; + arr->ids = array->ptr; +} + +void git_oidarray__reverse(git_oidarray *arr) +{ + size_t i; + git_oid tmp; + + for (i = 0; i < arr->count / 2; i++) { + git_oid_cpy(&tmp, &arr->ids[i]); + git_oid_cpy(&arr->ids[i], &arr->ids[(arr->count-1)-i]); + git_oid_cpy(&arr->ids[(arr->count-1)-i], &tmp); + } +} + +#ifndef GIT_DEPRECATE_HARD + +void git_oidarray_free(git_oidarray *arr) +{ + git_oidarray_dispose(arr); +} + +#endif diff --git a/src/libgit2/oidarray.h b/src/libgit2/oidarray.h new file mode 100644 index 000000000..eed3a1091 --- /dev/null +++ b/src/libgit2/oidarray.h @@ -0,0 +1,20 @@ +/* + * 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_oidarray_h__ +#define INCLUDE_oidarray_h__ + +#include "common.h" + +#include "git2/oidarray.h" +#include "array.h" + +typedef git_array_t(git_oid) git_array_oid_t; + +extern void git_oidarray__reverse(git_oidarray *arr); +extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array); + +#endif diff --git a/src/libgit2/oidmap.c b/src/libgit2/oidmap.c new file mode 100644 index 000000000..eaf9fa051 --- /dev/null +++ b/src/libgit2/oidmap.c @@ -0,0 +1,107 @@ +/* + * 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 "oidmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(oid, const git_oid *, void *) + +GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) +{ + khint_t h; + memcpy(&h, oid->id, sizeof(khint_t)); + return h; +} + +__KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal) + +int git_oidmap_new(git_oidmap **out) +{ + *out = kh_init(oid); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_oidmap_free(git_oidmap *map) +{ + kh_destroy(oid, map); +} + +void git_oidmap_clear(git_oidmap *map) +{ + kh_clear(oid, map); +} + +size_t git_oidmap_size(git_oidmap *map) +{ + return kh_size(map); +} + +void *git_oidmap_get(git_oidmap *map, const git_oid *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_set(git_oidmap *map, const git_oid *key, void *value) +{ + 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) +{ + return kh_get(oid, map, key) != kh_end(map); +} + +int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_value(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/libgit2/oidmap.h b/src/libgit2/oidmap.h new file mode 100644 index 000000000..b748f727c --- /dev/null +++ b/src/libgit2/oidmap.h @@ -0,0 +1,128 @@ +/* + * 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_oidmap_h__ +#define INCLUDE_oidmap_h__ + +#include "common.h" + +#include "git2/oid.h" + +/** A map with `git_oid`s as key. */ +typedef struct kh_oid_s git_oidmap; + +/** + * 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); + +/** + * 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); + +/** + * 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); + +#define git_oidmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_oidmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/libgit2/pack-objects.c b/src/libgit2/pack-objects.c new file mode 100644 index 000000000..1aa6731b3 --- /dev/null +++ b/src/libgit2/pack-objects.c @@ -0,0 +1,1821 @@ +/* + * 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 "pack-objects.h" + +#include "buf.h" +#include "zstream.h" +#include "delta.h" +#include "iterator.h" +#include "netops.h" +#include "pack.h" +#include "thread.h" +#include "tree.h" +#include "util.h" +#include "revwalk.h" +#include "commit_list.h" + +#include "git2/pack.h" +#include "git2/commit.h" +#include "git2/tag.h" +#include "git2/indexer.h" +#include "git2/config.h" + +struct unpacked { + git_pobject *object; + void *data; + struct git_delta_index *index; + size_t depth; +}; + +struct tree_walk_context { + git_packbuilder *pb; + git_str buf; +}; + +struct pack_write_context { + git_indexer *indexer; + git_indexer_progress *stats; +}; + +struct walk_object { + git_oid id; + unsigned int uninteresting:1, + seen:1; +}; + +#ifdef GIT_THREADS +# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git_mutex_##op(&(pb)->mtx) +#else +# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git__noop() +#endif + +#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock) +#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock) +#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock) +#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock) + +/* The minimal interval between progress updates (in seconds). */ +#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 + +/* Size of the buffer to feed to zlib */ +#define COMPRESS_BUFLEN (1024 * 1024) + +static unsigned name_hash(const char *name) +{ + unsigned c, hash = 0; + + if (!name) + return 0; + + /* + * This effectively just creates a sortable number from the + * last sixteen non-whitespace characters. Last characters + * count "most", so things that end in ".c" sort together. + */ + while ((c = *name++) != 0) { + if (git__isspace(c)) + continue; + hash = (hash >> 2) + (c << 24); + } + return hash; +} + +static int packbuilder_config(git_packbuilder *pb) +{ + git_config *config; + int ret = 0; + int64_t val; + + if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0) + return ret; + +#define config_get(KEY,DST,DFLT) do { \ + ret = git_config_get_int64(&val, config, KEY); \ + if (!ret) { \ + if (!git__is_sizet(val)) { \ + git_error_set(GIT_ERROR_CONFIG, \ + "configuration value '%s' is too large", KEY); \ + ret = -1; \ + goto out; \ + } \ + (DST) = (size_t)val; \ + } else if (ret == GIT_ENOTFOUND) { \ + (DST) = (DFLT); \ + ret = 0; \ + } else if (ret < 0) goto out; } while (0) + + config_get("pack.deltaCacheSize", pb->max_delta_cache_size, + GIT_PACK_DELTA_CACHE_SIZE); + config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size, + GIT_PACK_DELTA_CACHE_LIMIT); + config_get("pack.deltaCacheSize", pb->big_file_threshold, + GIT_PACK_BIG_FILE_THRESHOLD); + config_get("pack.windowMemory", pb->window_memory_limit, 0); + +#undef config_get + +out: + git_config_free(config); + + return ret; +} + +int git_packbuilder_new(git_packbuilder **out, git_repository *repo) +{ + git_packbuilder *pb; + + *out = NULL; + + pb = git__calloc(1, sizeof(*pb)); + GIT_ERROR_CHECK_ALLOC(pb); + + if (git_oidmap_new(&pb->object_ix) < 0 || + git_oidmap_new(&pb->walk_objects) < 0 || + git_pool_init(&pb->object_pool, sizeof(struct walk_object)) < 0) + goto on_error; + + pb->repo = repo; + pb->nr_threads = 1; /* do not spawn any thread by default */ + + if (git_hash_ctx_init(&pb->ctx, GIT_HASH_ALGORITHM_SHA1) < 0 || + git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 || + git_repository_odb(&pb->odb, repo) < 0 || + packbuilder_config(pb) < 0) + goto on_error; + +#ifdef GIT_THREADS + + if (git_mutex_init(&pb->cache_mutex) || + git_mutex_init(&pb->progress_mutex) || + git_cond_init(&pb->progress_cond)) + { + git_error_set(GIT_ERROR_OS, "failed to initialize packbuilder mutex"); + goto on_error; + } + +#endif + + *out = pb; + return 0; + +on_error: + git_packbuilder_free(pb); + return -1; +} + +unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n) +{ + GIT_ASSERT_ARG(pb); + +#ifdef GIT_THREADS + pb->nr_threads = n; +#else + GIT_UNUSED(n); + GIT_ASSERT(pb->nr_threads == 1); +#endif + + return pb->nr_threads; +} + +static int rehash(git_packbuilder *pb) +{ + git_pobject *po; + size_t i; + + git_oidmap_clear(pb->object_ix); + + for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, 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; + int ret; + + GIT_ASSERT_ARG(pb); + GIT_ASSERT_ARG(oid); + + /* If the object already exists in the hash table, then we don't + * have any work to do */ + if (git_oidmap_exists(pb->object_ix, oid)) + return 0; + + if (pb->nr_objects >= pb->nr_alloc) { + GIT_ERROR_CHECK_ALLOC_ADD(&newsize, pb->nr_alloc, 1024); + 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."); + return -1; + } + + pb->nr_alloc = newsize; + + pb->object_list = git__reallocarray(pb->object_list, + pb->nr_alloc, sizeof(*po)); + GIT_ERROR_CHECK_ALLOC(pb->object_list); + + if (rehash(pb) < 0) + return -1; + } + + po = pb->object_list + pb->nr_objects; + memset(po, 0x0, sizeof(*po)); + + if ((ret = git_odb_read_header(&po->size, &po->type, pb->odb, oid)) < 0) + return ret; + + pb->nr_objects++; + git_oid_cpy(&po->id, oid); + po->hash = name_hash(name); + + if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) { + git_error_set_oom(); + return -1; + } + + pb->done = false; + + if (pb->progress_cb) { + double current_time = git__timer(); + double elapsed = current_time - pb->last_progress_report_time; + + if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { + pb->last_progress_report_time = current_time; + + ret = pb->progress_cb( + GIT_PACKBUILDER_ADDING_OBJECTS, + pb->nr_objects, 0, pb->progress_cb_payload); + + if (ret) + return git_error_set_after_callback(ret); + } + } + + return 0; +} + +static int get_delta(void **out, git_odb *odb, git_pobject *po) +{ + git_odb_object *src = NULL, *trg = NULL; + size_t delta_size; + void *delta_buf; + int error; + + *out = NULL; + + if (git_odb_read(&src, odb, &po->delta->id) < 0 || + git_odb_read(&trg, odb, &po->id) < 0) + goto on_error; + + error = git_delta(&delta_buf, &delta_size, + git_odb_object_data(src), git_odb_object_size(src), + git_odb_object_data(trg), git_odb_object_size(trg), + 0); + + if (error < 0 && error != GIT_EBUFS) + goto on_error; + + if (error == GIT_EBUFS || delta_size != po->delta_size) { + git_error_set(GIT_ERROR_INVALID, "delta size changed"); + goto on_error; + } + + *out = delta_buf; + + git_odb_object_free(src); + git_odb_object_free(trg); + return 0; + +on_error: + git_odb_object_free(src); + git_odb_object_free(trg); + return -1; +} + +static int write_object( + git_packbuilder *pb, + git_pobject *po, + int (*write_cb)(void *buf, size_t size, void *cb_data), + void *cb_data) +{ + git_odb_object *obj = NULL; + git_object_t type; + unsigned char hdr[10], *zbuf = NULL; + void *data = NULL; + size_t hdr_len, zbuf_len = COMPRESS_BUFLEN, data_len; + int error; + + /* + * If we have a delta base, let's use the delta to save space. + * Otherwise load the whole object. 'data' ends up pointing to + * whatever data we want to put into the packfile. + */ + if (po->delta) { + if (po->delta_data) + data = po->delta_data; + else if ((error = get_delta(&data, pb->odb, po)) < 0) + goto done; + + data_len = po->delta_size; + type = GIT_OBJECT_REF_DELTA; + } else { + if ((error = git_odb_read(&obj, pb->odb, &po->id)) < 0) + goto done; + + data = (void *)git_odb_object_data(obj); + data_len = git_odb_object_size(obj); + type = git_odb_object_type(obj); + } + + /* Write header */ + if ((error = git_packfile__object_header(&hdr_len, hdr, data_len, type)) < 0 || + (error = write_cb(hdr, hdr_len, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, hdr, hdr_len)) < 0) + goto done; + + if (type == GIT_OBJECT_REF_DELTA) { + if ((error = write_cb(po->delta->id.id, GIT_OID_RAWSZ, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ)) < 0) + goto done; + } + + /* Write data */ + if (po->z_delta_size) { + data_len = po->z_delta_size; + + if ((error = write_cb(data, data_len, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, data, data_len)) < 0) + goto done; + } else { + zbuf = git__malloc(zbuf_len); + GIT_ERROR_CHECK_ALLOC(zbuf); + + git_zstream_reset(&pb->zstream); + + 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 || + (error = write_cb(zbuf, zbuf_len, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, zbuf, zbuf_len)) < 0) + goto done; + + zbuf_len = COMPRESS_BUFLEN; /* reuse buffer */ + } + } + + /* + * If po->delta is true, data is a delta and it is our + * responsibility to free it (otherwise it's a git_object's + * data). We set po->delta_data to NULL in case we got the + * data from there instead of get_delta(). If we didn't, + * there's no harm. + */ + if (po->delta) { + git__free(data); + po->delta_data = NULL; + } + + pb->nr_written++; + +done: + git__free(zbuf); + git_odb_object_free(obj); + return error; +} + +enum write_one_status { + WRITE_ONE_SKIP = -1, /* already written */ + WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */ + WRITE_ONE_WRITTEN = 1, /* normal */ + WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ +}; + +static int write_one( + enum write_one_status *status, + git_packbuilder *pb, + git_pobject *po, + int (*write_cb)(void *buf, size_t size, void *cb_data), + void *cb_data) +{ + int error; + + if (po->recursing) { + *status = WRITE_ONE_RECURSIVE; + return 0; + } else if (po->written) { + *status = WRITE_ONE_SKIP; + return 0; + } + + if (po->delta) { + po->recursing = 1; + + if ((error = write_one(status, pb, po->delta, write_cb, cb_data)) < 0) + return error; + + /* we cannot depend on this one */ + if (*status == WRITE_ONE_RECURSIVE) + po->delta = NULL; + } + + *status = WRITE_ONE_WRITTEN; + po->written = 1; + po->recursing = 0; + + return write_object(pb, po, write_cb, cb_data); +} + +GIT_INLINE(void) add_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) +{ + if (po->filled) + return; + wo[(*endp)++] = po; + po->filled = 1; +} + +static void add_descendants_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) +{ + int add_to_order = 1; + while (po) { + if (add_to_order) { + git_pobject *s; + /* add this node... */ + add_to_write_order(wo, endp, po); + /* all its siblings... */ + for (s = po->delta_sibling; s; s = s->delta_sibling) { + add_to_write_order(wo, endp, s); + } + } + /* drop down a level to add left subtree nodes if possible */ + if (po->delta_child) { + add_to_order = 1; + po = po->delta_child; + } else { + add_to_order = 0; + /* our sibling might have some children, it is next */ + if (po->delta_sibling) { + po = po->delta_sibling; + continue; + } + /* go back to our parent node */ + po = po->delta; + while (po && !po->delta_sibling) { + /* we're on the right side of a subtree, keep + * going up until we can go right again */ + po = po->delta; + } + if (!po) { + /* done- we hit our original root node */ + return; + } + /* pass it off to sibling at this level */ + po = po->delta_sibling; + } + }; +} + +static void add_family_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) +{ + git_pobject *root; + + for (root = po; root->delta; root = root->delta) + ; /* nothing */ + add_descendants_to_write_order(wo, endp, root); +} + +static int cb_tag_foreach(const char *name, git_oid *oid, void *data) +{ + git_packbuilder *pb = data; + git_pobject *po; + + GIT_UNUSED(name); + + if ((po = git_oidmap_get(pb->object_ix, oid)) == NULL) + return 0; + + po->tagged = 1; + + /* TODO: peel objects */ + + return 0; +} + +static int compute_write_order(git_pobject ***out, git_packbuilder *pb) +{ + size_t i, wo_end, last_untagged; + git_pobject **wo; + + *out = NULL; + + if (!pb->nr_objects) + return 0; + + if ((wo = git__mallocarray(pb->nr_objects, sizeof(*wo))) == NULL) + return -1; + + for (i = 0; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + po->tagged = 0; + po->filled = 0; + po->delta_child = NULL; + po->delta_sibling = NULL; + } + + /* + * Fully connect delta_child/delta_sibling network. + * Make sure delta_sibling is sorted in the original + * recency order. + */ + for (i = pb->nr_objects; i > 0;) { + git_pobject *po = &pb->object_list[--i]; + if (!po->delta) + continue; + /* Mark me as the first child */ + po->delta_sibling = po->delta->delta_child; + po->delta->delta_child = po; + } + + /* + * Mark objects that are at the tip of tags. + */ + if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) { + git__free(wo); + return -1; + } + + /* + * Give the objects in the original recency order until + * we see a tagged tip. + */ + for (i = wo_end = 0; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->tagged) + break; + add_to_write_order(wo, &wo_end, po); + } + last_untagged = i; + + /* + * Then fill all the tagged tips. + */ + for (; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->tagged) + add_to_write_order(wo, &wo_end, po); + } + + /* + * And then all remaining commits and tags. + */ + for (i = last_untagged; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->type != GIT_OBJECT_COMMIT && + po->type != GIT_OBJECT_TAG) + continue; + add_to_write_order(wo, &wo_end, po); + } + + /* + * And then all the trees. + */ + for (i = last_untagged; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->type != GIT_OBJECT_TREE) + continue; + add_to_write_order(wo, &wo_end, po); + } + + /* + * Finally all the rest in really tight order + */ + for (i = last_untagged; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (!po->filled) + add_family_to_write_order(wo, &wo_end, po); + } + + if (wo_end != pb->nr_objects) { + git__free(wo); + git_error_set(GIT_ERROR_INVALID, "invalid write order"); + return -1; + } + + *out = wo; + return 0; +} + +static int write_pack(git_packbuilder *pb, + int (*write_cb)(void *buf, size_t size, void *cb_data), + void *cb_data) +{ + git_pobject **write_order; + git_pobject *po; + enum write_one_status status; + struct git_pack_header ph; + git_oid entry_oid; + size_t i = 0; + int error; + + if ((error = compute_write_order(&write_order, pb)) < 0) + return error; + + if (!git__is_uint32(pb->nr_objects)) { + git_error_set(GIT_ERROR_INVALID, "too many objects"); + error = -1; + goto done; + } + + /* Write pack header */ + ph.hdr_signature = htonl(PACK_SIGNATURE); + ph.hdr_version = htonl(PACK_VERSION); + ph.hdr_entries = htonl(pb->nr_objects); + + if ((error = write_cb(&ph, sizeof(ph), cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0) + goto done; + + pb->nr_remaining = pb->nr_objects; + do { + pb->nr_written = 0; + for ( ; i < pb->nr_objects; ++i) { + po = write_order[i]; + + if ((error = write_one(&status, pb, po, write_cb, cb_data)) < 0) + goto done; + } + + pb->nr_remaining -= pb->nr_written; + } while (pb->nr_remaining && i < pb->nr_objects); + + if ((error = git_hash_final(entry_oid.id, &pb->ctx)) < 0) + goto done; + + error = write_cb(entry_oid.id, GIT_OID_RAWSZ, cb_data); + +done: + /* if callback cancelled writing, we must still free delta_data */ + for ( ; i < pb->nr_objects; ++i) { + po = write_order[i]; + if (po->delta_data) { + git__free(po->delta_data); + po->delta_data = NULL; + } + } + + git__free(write_order); + return error; +} + +static int write_pack_buf(void *buf, size_t size, void *data) +{ + git_str *b = (git_str *)data; + return git_str_put(b, buf, size); +} + +static int type_size_sort(const void *_a, const void *_b) +{ + const git_pobject *a = (git_pobject *)_a; + const git_pobject *b = (git_pobject *)_b; + + if (a->type > b->type) + return -1; + if (a->type < b->type) + return 1; + if (a->hash > b->hash) + return -1; + if (a->hash < b->hash) + return 1; + /* + * TODO + * + if (a->preferred_base > b->preferred_base) + return -1; + if (a->preferred_base < b->preferred_base) + return 1; + */ + if (a->size > b->size) + return -1; + if (a->size < b->size) + return 1; + return a < b ? -1 : (a > b); /* newest first */ +} + +static int delta_cacheable( + git_packbuilder *pb, + size_t src_size, + size_t trg_size, + size_t delta_size) +{ + size_t new_size; + + if (git__add_sizet_overflow(&new_size, pb->delta_cache_size, delta_size)) + return 0; + + if (pb->max_delta_cache_size && new_size > pb->max_delta_cache_size) + return 0; + + if (delta_size < pb->cache_max_small_delta_size) + return 1; + + /* cache delta, if objects are large enough compared to delta size */ + if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) + return 1; + + return 0; +} + +static int try_delta(git_packbuilder *pb, struct unpacked *trg, + struct unpacked *src, size_t max_depth, + size_t *mem_usage, int *ret) +{ + git_pobject *trg_object = trg->object; + git_pobject *src_object = src->object; + git_odb_object *obj; + size_t trg_size, src_size, delta_size, sizediff, max_size, sz; + size_t ref_depth; + void *delta_buf; + + /* Don't bother doing diffs between different types */ + if (trg_object->type != src_object->type) { + *ret = -1; + return 0; + } + + *ret = 0; + + /* TODO: support reuse-delta */ + + /* Let's not bust the allowed depth. */ + if (src->depth >= max_depth) + return 0; + + /* Now some size filtering heuristics. */ + trg_size = trg_object->size; + if (!trg_object->delta) { + max_size = trg_size/2 - 20; + ref_depth = 1; + } else { + max_size = trg_object->delta_size; + ref_depth = trg->depth; + } + + max_size = (uint64_t)max_size * (max_depth - src->depth) / + (max_depth - ref_depth + 1); + if (max_size == 0) + return 0; + + src_size = src_object->size; + sizediff = src_size < trg_size ? trg_size - src_size : 0; + if (sizediff >= max_size) + return 0; + if (trg_size < src_size / 32) + return 0; + + /* Load data if not already done */ + if (!trg->data) { + if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0) + return -1; + + sz = git_odb_object_size(obj); + trg->data = git__malloc(sz); + GIT_ERROR_CHECK_ALLOC(trg->data); + memcpy(trg->data, git_odb_object_data(obj), sz); + + git_odb_object_free(obj); + + if (sz != trg_size) { + git_error_set(GIT_ERROR_INVALID, + "inconsistent target object length"); + return -1; + } + + *mem_usage += sz; + } + if (!src->data) { + size_t obj_sz; + + if (git_odb_read(&obj, pb->odb, &src_object->id) < 0 || + !git__is_ulong(obj_sz = git_odb_object_size(obj))) + return -1; + + sz = obj_sz; + src->data = git__malloc(sz); + GIT_ERROR_CHECK_ALLOC(src->data); + memcpy(src->data, git_odb_object_data(obj), sz); + + git_odb_object_free(obj); + + if (sz != src_size) { + git_error_set(GIT_ERROR_INVALID, + "inconsistent source object length"); + return -1; + } + + *mem_usage += sz; + } + if (!src->index) { + if (git_delta_index_init(&src->index, src->data, src_size) < 0) + return 0; /* suboptimal pack - out of memory */ + + *mem_usage += git_delta_index_size(src->index); + } + + if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size, + max_size) < 0) + return 0; + + if (trg_object->delta) { + /* Prefer only shallower same-sized deltas. */ + if (delta_size == trg_object->delta_size && + src->depth + 1 >= trg->depth) { + git__free(delta_buf); + return 0; + } + } + + GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); + + if (trg_object->delta_data) { + git__free(trg_object->delta_data); + GIT_ASSERT(pb->delta_cache_size >= trg_object->delta_size); + pb->delta_cache_size -= trg_object->delta_size; + trg_object->delta_data = NULL; + } + if (delta_cacheable(pb, src_size, trg_size, delta_size)) { + bool overflow = git__add_sizet_overflow( + &pb->delta_cache_size, pb->delta_cache_size, delta_size); + + GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); + + if (overflow) { + git__free(delta_buf); + return -1; + } + + trg_object->delta_data = git__realloc(delta_buf, delta_size); + GIT_ERROR_CHECK_ALLOC(trg_object->delta_data); + } else { + /* create delta when writing the pack */ + GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); + git__free(delta_buf); + } + + trg_object->delta = src_object; + trg_object->delta_size = delta_size; + trg->depth = src->depth + 1; + + *ret = 1; + return 0; +} + +static size_t check_delta_limit(git_pobject *me, size_t n) +{ + git_pobject *child = me->delta_child; + size_t m = n; + + while (child) { + size_t c = check_delta_limit(child, n + 1); + if (m < c) + m = c; + child = child->delta_sibling; + } + return m; +} + +static size_t free_unpacked(struct unpacked *n) +{ + size_t freed_mem = 0; + + if (n->index) { + freed_mem += git_delta_index_size(n->index); + git_delta_index_free(n->index); + } + n->index = NULL; + + if (n->data) { + freed_mem += n->object->size; + git__free(n->data); + n->data = NULL; + } + n->object = NULL; + n->depth = 0; + return freed_mem; +} + +static int report_delta_progress( + git_packbuilder *pb, uint32_t count, bool force) +{ + int ret; + + if (pb->progress_cb) { + double current_time = git__timer(); + double elapsed = current_time - pb->last_progress_report_time; + + if (force || elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { + pb->last_progress_report_time = current_time; + + ret = pb->progress_cb( + GIT_PACKBUILDER_DELTAFICATION, + count, pb->nr_objects, pb->progress_cb_payload); + + if (ret) + return git_error_set_after_callback(ret); + } + } + + return 0; +} + +static int find_deltas(git_packbuilder *pb, git_pobject **list, + size_t *list_size, size_t window, size_t depth) +{ + git_pobject *po; + git_str zbuf = GIT_STR_INIT; + struct unpacked *array; + size_t idx = 0, count = 0; + size_t mem_usage = 0; + size_t i; + int error = -1; + + array = git__calloc(window, sizeof(struct unpacked)); + GIT_ERROR_CHECK_ALLOC(array); + + for (;;) { + struct unpacked *n = array + idx; + size_t max_depth, j, best_base = SIZE_MAX; + + GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); + if (!*list_size) { + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + break; + } + + pb->nr_deltified += 1; + report_delta_progress(pb, pb->nr_deltified, false); + + po = *list++; + (*list_size)--; + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + + mem_usage -= free_unpacked(n); + n->object = po; + + while (pb->window_memory_limit && + mem_usage > pb->window_memory_limit && + count > 1) { + size_t tail = (idx + window - count) % window; + mem_usage -= free_unpacked(array + tail); + count--; + } + + /* + * If the current object is at pack edge, take the depth the + * objects that depend on the current object into account + * otherwise they would become too deep. + */ + max_depth = depth; + if (po->delta_child) { + size_t delta_limit = check_delta_limit(po, 0); + + if (delta_limit > max_depth) + goto next; + + max_depth -= delta_limit; + } + + j = window; + while (--j > 0) { + int ret; + size_t other_idx = idx + j; + struct unpacked *m; + + if (other_idx >= window) + other_idx -= window; + + m = array + other_idx; + if (!m->object) + break; + + if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0) + goto on_error; + if (ret < 0) + break; + else if (ret > 0) + best_base = other_idx; + } + + /* + * If we decided to cache the delta data, then it is best + * to compress it right away. First because we have to do + * it anyway, and doing it here while we're threaded will + * save a lot of time in the non threaded write phase, + * as well as allow for caching more deltas within + * the same cache size limit. + * ... + * But only if not writing to stdout, since in that case + * the network is most likely throttling writes anyway, + * and therefore it is best to go to the write phase ASAP + * instead, as we can afford spending more time compressing + * between writes at that moment. + */ + if (po->delta_data) { + if (git_zstream_deflatebuf(&zbuf, po->delta_data, po->delta_size) < 0) + goto on_error; + + git__free(po->delta_data); + po->delta_data = git__malloc(zbuf.size); + GIT_ERROR_CHECK_ALLOC(po->delta_data); + + memcpy(po->delta_data, zbuf.ptr, zbuf.size); + po->z_delta_size = zbuf.size; + git_str_clear(&zbuf); + + GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); + pb->delta_cache_size -= po->delta_size; + pb->delta_cache_size += po->z_delta_size; + GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); + } + + /* + * If we made n a delta, and if n is already at max + * depth, leaving it in the window is pointless. we + * should evict it first. + */ + if (po->delta && max_depth <= n->depth) + continue; + + /* + * Move the best delta base up in the window, after the + * currently deltified object, to keep it longer. It will + * be the first base object to be attempted next. + */ + if (po->delta) { + struct unpacked swap = array[best_base]; + size_t dist = (window + idx - best_base) % window; + size_t dst = best_base; + while (dist--) { + size_t src = (dst + 1) % window; + array[dst] = array[src]; + dst = src; + } + array[dst] = swap; + } + + next: + idx++; + if (count + 1 < window) + count++; + if (idx >= window) + idx = 0; + } + error = 0; + +on_error: + for (i = 0; i < window; ++i) { + git__free(array[i].index); + git__free(array[i].data); + } + git__free(array); + git_str_dispose(&zbuf); + + return error; +} + +#ifdef GIT_THREADS + +struct thread_params { + git_thread thread; + git_packbuilder *pb; + + git_pobject **list; + + git_cond cond; + git_mutex mutex; + + size_t list_size; + size_t remaining; + + size_t window; + size_t depth; + size_t working; + size_t data_ready; +}; + +static void *threaded_find_deltas(void *arg) +{ + struct thread_params *me = arg; + + while (me->remaining) { + if (find_deltas(me->pb, me->list, &me->remaining, + me->window, me->depth) < 0) { + ; /* TODO */ + } + + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); + me->working = 0; + git_cond_signal(&me->pb->progress_cond); + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_unlock(me->pb) == 0, NULL); + + if (git_mutex_lock(&me->mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); + return NULL; + } + + while (!me->data_ready) + git_cond_wait(&me->cond, &me->mutex); + + /* + * We must not set ->data_ready before we wait on the + * condition because the main thread may have set it to 1 + * before we get here. In order to be sure that new + * work is available if we see 1 in ->data_ready, it + * was initialized to 0 before this thread was spawned + * and we reset it to 0 right away. + */ + me->data_ready = 0; + git_mutex_unlock(&me->mutex); + } + /* leave ->working 1 so that this doesn't get more work assigned */ + return NULL; +} + +static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, + size_t list_size, size_t window, size_t depth) +{ + struct thread_params *p; + size_t i; + int ret, active_threads = 0; + + if (!pb->nr_threads) + pb->nr_threads = git__online_cpus(); + + if (pb->nr_threads <= 1) { + find_deltas(pb, list, &list_size, window, depth); + return 0; + } + + p = git__mallocarray(pb->nr_threads, sizeof(*p)); + GIT_ERROR_CHECK_ALLOC(p); + + /* Partition the work among the threads */ + for (i = 0; i < pb->nr_threads; ++i) { + size_t sub_size = list_size / (pb->nr_threads - i); + + /* don't use too small segments or no deltas will be found */ + if (sub_size < 2*window && i+1 < pb->nr_threads) + sub_size = 0; + + p[i].pb = pb; + p[i].window = window; + p[i].depth = depth; + p[i].working = 1; + p[i].data_ready = 0; + + /* try to split chunks on "path" boundaries */ + while (sub_size && sub_size < list_size && + list[sub_size]->hash && + list[sub_size]->hash == list[sub_size-1]->hash) + sub_size++; + + p[i].list = list; + p[i].list_size = sub_size; + p[i].remaining = sub_size; + + list += sub_size; + list_size -= sub_size; + } + + /* Start work threads */ + for (i = 0; i < pb->nr_threads; ++i) { + if (!p[i].list_size) + continue; + + git_mutex_init(&p[i].mutex); + git_cond_init(&p[i].cond); + + ret = git_thread_create(&p[i].thread, + threaded_find_deltas, &p[i]); + if (ret) { + git_error_set(GIT_ERROR_THREAD, "unable to create thread"); + return -1; + } + active_threads++; + } + + /* + * Now let's wait for work completion. Each time a thread is done + * with its work, we steal half of the remaining work from the + * thread with the largest number of unprocessed objects and give + * it to that newly idle thread. This ensure good load balancing + * until the remaining object list segments are simply too short + * to be worth splitting anymore. + */ + while (active_threads) { + struct thread_params *target = NULL; + struct thread_params *victim = NULL; + size_t sub_size = 0; + + /* Start by locating a thread that has transitioned its + * 'working' flag from 1 -> 0. This indicates that it is + * ready to receive more work using our work-stealing + * algorithm. */ + GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); + for (;;) { + for (i = 0; !target && i < pb->nr_threads; i++) + if (!p[i].working) + target = &p[i]; + if (target) + break; + git_cond_wait(&pb->progress_cond, &pb->progress_mutex); + } + + /* At this point we hold the progress lock and have located + * a thread to receive more work. We still need to locate a + * thread from which to steal work (the victim). */ + for (i = 0; i < pb->nr_threads; i++) + if (p[i].remaining > 2*window && + (!victim || victim->remaining < p[i].remaining)) + victim = &p[i]; + + if (victim) { + sub_size = victim->remaining / 2; + list = victim->list + victim->list_size - sub_size; + while (sub_size && list[0]->hash && + list[0]->hash == list[-1]->hash) { + list++; + sub_size--; + } + if (!sub_size) { + /* + * It is possible for some "paths" to have + * so many objects that no hash boundary + * might be found. Let's just steal the + * exact half in that case. + */ + sub_size = victim->remaining / 2; + list -= sub_size; + } + target->list = list; + victim->list_size -= sub_size; + victim->remaining -= sub_size; + } + target->list_size = sub_size; + target->remaining = sub_size; + target->working = 1; + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + + if (git_mutex_lock(&target->mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); + git__free(p); + return -1; + } + + target->data_ready = 1; + git_cond_signal(&target->cond); + git_mutex_unlock(&target->mutex); + + if (!sub_size) { + git_thread_join(&target->thread, NULL); + git_cond_free(&target->cond); + git_mutex_free(&target->mutex); + active_threads--; + } + } + + git__free(p); + return 0; +} + +#else +#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d) +#endif + +int git_packbuilder__prepare(git_packbuilder *pb) +{ + git_pobject **delta_list; + size_t i, n = 0; + + if (pb->nr_objects == 0 || pb->done) + return 0; /* nothing to do */ + + /* + * Although we do not report progress during deltafication, we + * at least report that we are in the deltafication stage + */ + if (pb->progress_cb) + pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); + + delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list)); + GIT_ERROR_CHECK_ALLOC(delta_list); + + for (i = 0; i < pb->nr_objects; ++i) { + git_pobject *po = pb->object_list + i; + + /* Make sure the item is within our size limits */ + if (po->size < 50 || po->size > pb->big_file_threshold) + continue; + + delta_list[n++] = po; + } + + if (n > 1) { + git__tsort((void **)delta_list, n, type_size_sort); + if (ll_find_deltas(pb, delta_list, n, + GIT_PACK_WINDOW + 1, + GIT_PACK_DEPTH) < 0) { + git__free(delta_list); + return -1; + } + } + + report_delta_progress(pb, pb->nr_objects, true); + + pb->done = true; + git__free(delta_list); + return 0; +} + +#define PREPARE_PACK if (git_packbuilder__prepare(pb) < 0) { return -1; } + +int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) +{ + PREPARE_PACK; + return write_pack(pb, cb, payload); +} + +int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb) +{ + PREPARE_PACK; + + return write_pack(pb, &write_pack_buf, buf); +} + +int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) +{ + GIT_BUF_WRAP_PRIVATE(buf, git_packbuilder__write_buf, pb); +} + +static int write_cb(void *buf, size_t len, void *payload) +{ + struct pack_write_context *ctx = payload; + return git_indexer_append(ctx->indexer, buf, len, ctx->stats); +} + +int git_packbuilder_write( + git_packbuilder *pb, + const char *path, + unsigned int mode, + git_indexer_progress_cb progress_cb, + void *progress_cb_payload) +{ + int error = -1; + git_str object_path = GIT_STR_INIT; + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer *indexer = NULL; + git_indexer_progress stats; + struct pack_write_context ctx; + int t; + + PREPARE_PACK; + + if (path == NULL) { + if ((error = git_repository__item_path(&object_path, pb->repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0) + goto cleanup; + if ((error = git_str_joinpath(&object_path, git_str_cstr(&object_path), "pack")) < 0) + goto cleanup; + path = git_str_cstr(&object_path); + } + + opts.progress_cb = progress_cb; + opts.progress_cb_payload = progress_cb_payload; + + if ((error = git_indexer_new(&indexer, path, mode, pb->odb, &opts)) < 0) + goto cleanup; + + if (!git_repository__configmap_lookup(&t, pb->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) + git_indexer__set_fsync(indexer, 1); + + ctx.indexer = indexer; + ctx.stats = &stats; + + if ((error = git_packbuilder_foreach(pb, write_cb, &ctx)) < 0) + goto cleanup; + + if ((error = git_indexer_commit(indexer, &stats)) < 0) + goto cleanup; + +#ifndef GIT_DEPRECATE_HARD + git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer)); +#endif + + pb->pack_name = git__strdup(git_indexer_name(indexer)); + GIT_ERROR_CHECK_ALLOC(pb->pack_name); + +cleanup: + git_indexer_free(indexer); + git_str_dispose(&object_path); + return error; +} + +#undef PREPARE_PACK + +#ifndef GIT_DEPRECATE_HARD +const git_oid *git_packbuilder_hash(git_packbuilder *pb) +{ + return &pb->pack_oid; +} +#endif + +const char *git_packbuilder_name(git_packbuilder *pb) +{ + return pb->pack_name; +} + + +static int cb_tree_walk( + const char *root, const git_tree_entry *entry, void *payload) +{ + int error; + struct tree_walk_context *ctx = payload; + + /* A commit inside a tree represents a submodule commit and should be skipped. */ + if (git_tree_entry_type(entry) == GIT_OBJECT_COMMIT) + return 0; + + if (!(error = git_str_sets(&ctx->buf, root)) && + !(error = git_str_puts(&ctx->buf, git_tree_entry_name(entry)))) + error = git_packbuilder_insert( + ctx->pb, git_tree_entry_id(entry), git_str_cstr(&ctx->buf)); + + return error; +} + +int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid) +{ + git_commit *commit; + + if (git_commit_lookup(&commit, pb->repo, oid) < 0 || + git_packbuilder_insert(pb, oid, NULL) < 0) + return -1; + + if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0) + return -1; + + git_commit_free(commit); + return 0; +} + +int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + struct tree_walk_context context = { pb, GIT_STR_INIT }; + + if (!(error = git_tree_lookup(&tree, pb->repo, oid)) && + !(error = git_packbuilder_insert(pb, oid, NULL))) + error = git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context); + + git_tree_free(tree); + git_str_dispose(&context.buf); + return error; +} + +int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name) +{ + git_object *obj; + int error; + + GIT_ASSERT_ARG(pb); + GIT_ASSERT_ARG(id); + + if ((error = git_object_lookup(&obj, pb->repo, id, GIT_OBJECT_ANY)) < 0) + return error; + + switch (git_object_type(obj)) { + case GIT_OBJECT_BLOB: + error = git_packbuilder_insert(pb, id, name); + break; + case GIT_OBJECT_TREE: + error = git_packbuilder_insert_tree(pb, id); + break; + case GIT_OBJECT_COMMIT: + error = git_packbuilder_insert_commit(pb, id); + break; + case GIT_OBJECT_TAG: + if ((error = git_packbuilder_insert(pb, id, name)) < 0) + goto cleanup; + error = git_packbuilder_insert_recur(pb, git_tag_target_id((git_tag *) obj), NULL); + break; + + default: + git_error_set(GIT_ERROR_INVALID, "unknown object type"); + error = -1; + } + +cleanup: + git_object_free(obj); + return error; +} + +size_t git_packbuilder_object_count(git_packbuilder *pb) +{ + return pb->nr_objects; +} + +size_t git_packbuilder_written(git_packbuilder *pb) +{ + return pb->nr_written; +} + +static int lookup_walk_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) +{ + struct walk_object *obj; + + obj = git_pool_mallocz(&pb->object_pool, 1); + if (!obj) { + git_error_set_oom(); + return -1; + } + + git_oid_cpy(&obj->id, id); + + *out = obj; + return 0; +} + +static int retrieve_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) +{ + struct walk_object *obj; + int error; + + if ((obj = git_oidmap_get(pb->walk_objects, id)) == NULL) { + if ((error = lookup_walk_object(&obj, pb, id)) < 0) + return error; + + if ((error = git_oidmap_set(pb->walk_objects, &obj->id, obj)) < 0) + return error; + } + + *out = obj; + return 0; +} + +static int mark_blob_uninteresting(git_packbuilder *pb, const git_oid *id) +{ + int error; + struct walk_object *obj; + + if ((error = retrieve_object(&obj, pb, id)) < 0) + return error; + + obj->uninteresting = 1; + + return 0; +} + +static int mark_tree_uninteresting(git_packbuilder *pb, const git_oid *id) +{ + struct walk_object *obj; + git_tree *tree; + int error; + size_t i; + + if ((error = retrieve_object(&obj, pb, id)) < 0) + return error; + + if (obj->uninteresting) + return 0; + + obj->uninteresting = 1; + + if ((error = git_tree_lookup(&tree, pb->repo, id)) < 0) + return error; + + for (i = 0; i < git_tree_entrycount(tree); i++) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); + const git_oid *entry_id = git_tree_entry_id(entry); + switch (git_tree_entry_type(entry)) { + case GIT_OBJECT_TREE: + if ((error = mark_tree_uninteresting(pb, entry_id)) < 0) + goto cleanup; + break; + case GIT_OBJECT_BLOB: + if ((error = mark_blob_uninteresting(pb, entry_id)) < 0) + goto cleanup; + break; + default: + /* it's a submodule or something unknown, we don't want it */ + ; + } + } + +cleanup: + git_tree_free(tree); + return error; +} + +/* + * Mark the edges of the graph uninteresting. Since we start from a + * git_revwalk, the commits are already uninteresting, but we need to + * mark the trees and blobs. + */ +static int mark_edges_uninteresting(git_packbuilder *pb, git_commit_list *commits) +{ + int error; + git_commit_list *list; + git_commit *commit; + + for (list = commits; list; list = list->next) { + if (!list->item->uninteresting) + continue; + + if ((error = git_commit_lookup(&commit, pb->repo, &list->item->oid)) < 0) + return error; + + error = mark_tree_uninteresting(pb, git_commit_tree_id(commit)); + git_commit_free(commit); + + if (error < 0) + return error; + } + + return 0; +} + +static int pack_objects_insert_tree(git_packbuilder *pb, git_tree *tree) +{ + size_t i; + int error; + git_tree *subtree; + struct walk_object *obj; + const char *name; + + if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) + return error; + + if (obj->seen || obj->uninteresting) + return 0; + + obj->seen = 1; + + if ((error = git_packbuilder_insert(pb, &obj->id, NULL))) + return error; + + for (i = 0; i < git_tree_entrycount(tree); i++) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); + const git_oid *entry_id = git_tree_entry_id(entry); + switch (git_tree_entry_type(entry)) { + case GIT_OBJECT_TREE: + if ((error = git_tree_lookup(&subtree, pb->repo, entry_id)) < 0) + return error; + + error = pack_objects_insert_tree(pb, subtree); + git_tree_free(subtree); + + if (error < 0) + return error; + + break; + case GIT_OBJECT_BLOB: + if ((error = retrieve_object(&obj, pb, entry_id)) < 0) + return error; + if (obj->uninteresting) + continue; + name = git_tree_entry_name(entry); + if ((error = git_packbuilder_insert(pb, entry_id, name)) < 0) + return error; + break; + default: + /* it's a submodule or something unknown, we don't want it */ + ; + } + } + + + return error; +} + +static int pack_objects_insert_commit(git_packbuilder *pb, struct walk_object *obj) +{ + int error; + git_commit *commit = NULL; + git_tree *tree = NULL; + + obj->seen = 1; + + if ((error = git_packbuilder_insert(pb, &obj->id, NULL)) < 0) + return error; + + if ((error = git_commit_lookup(&commit, pb->repo, &obj->id)) < 0) + return error; + + if ((error = git_tree_lookup(&tree, pb->repo, git_commit_tree_id(commit))) < 0) + goto cleanup; + + if ((error = pack_objects_insert_tree(pb, tree)) < 0) + goto cleanup; + +cleanup: + git_commit_free(commit); + git_tree_free(tree); + return error; +} + +int git_packbuilder_insert_walk(git_packbuilder *pb, git_revwalk *walk) +{ + int error; + git_oid id; + struct walk_object *obj; + + GIT_ASSERT_ARG(pb); + GIT_ASSERT_ARG(walk); + + if ((error = mark_edges_uninteresting(pb, walk->user_input)) < 0) + return error; + + /* + * TODO: git marks the parents of the edges + * uninteresting. This may provide a speed advantage, but does + * seem to assume the remote does not have a single-commit + * history on the other end. + */ + + /* walk down each tree up to the blobs and insert them, stopping when uninteresting */ + while ((error = git_revwalk_next(&id, walk)) == 0) { + if ((error = retrieve_object(&obj, pb, &id)) < 0) + return error; + + if (obj->seen || obj->uninteresting) + continue; + + if ((error = pack_objects_insert_commit(pb, obj)) < 0) + return error; + } + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload) +{ + if (!pb) + return -1; + + pb->progress_cb = progress_cb; + pb->progress_cb_payload = progress_cb_payload; + + return 0; +} + +void git_packbuilder_free(git_packbuilder *pb) +{ + if (pb == NULL) + return; + +#ifdef GIT_THREADS + + git_mutex_free(&pb->cache_mutex); + git_mutex_free(&pb->progress_mutex); + git_cond_free(&pb->progress_cond); + +#endif + + if (pb->odb) + git_odb_free(pb->odb); + + if (pb->object_ix) + git_oidmap_free(pb->object_ix); + + if (pb->object_list) + git__free(pb->object_list); + + git_oidmap_free(pb->walk_objects); + git_pool_clear(&pb->object_pool); + + git_hash_ctx_cleanup(&pb->ctx); + git_zstream_free(&pb->zstream); + + git__free(pb->pack_name); + + git__free(pb); +} diff --git a/src/libgit2/pack-objects.h b/src/libgit2/pack-objects.h new file mode 100644 index 000000000..2faa3ec7f --- /dev/null +++ b/src/libgit2/pack-objects.h @@ -0,0 +1,106 @@ +/* + * 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_pack_objects_h__ +#define INCLUDE_pack_objects_h__ + +#include "common.h" + +#include "str.h" +#include "hash.h" +#include "oidmap.h" +#include "netops.h" +#include "zstream.h" +#include "pool.h" +#include "indexer.h" + +#include "git2/oid.h" +#include "git2/pack.h" + +#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */ +#define GIT_PACK_DEPTH 50 /* max delta depth */ +#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024) +#define GIT_PACK_DELTA_CACHE_LIMIT 1000 +#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024) + +typedef struct git_pobject { + git_oid id; + git_object_t type; + off64_t offset; + + size_t size; + + unsigned int hash; /* name hint hash */ + + struct git_pobject *delta; /* delta base object */ + struct git_pobject *delta_child; /* deltified objects who bases me */ + struct git_pobject *delta_sibling; /* other deltified objects + * who uses the same base as + * me */ + + void *delta_data; + size_t delta_size; + size_t z_delta_size; + + unsigned int written:1, + recursing:1, + tagged:1, + filled:1; +} git_pobject; + +struct git_packbuilder { + git_repository *repo; /* associated repository */ + git_odb *odb; /* associated object database */ + + git_hash_ctx ctx; + git_zstream zstream; + + uint32_t nr_objects, + nr_deltified, + nr_written, + nr_remaining; + + size_t nr_alloc; + + git_pobject *object_list; + + git_oidmap *object_ix; + + git_oidmap *walk_objects; + git_pool object_pool; + +#ifndef GIT_DEPRECATE_HARD + git_oid pack_oid; /* hash of written pack */ +#endif + char *pack_name; /* name of written pack */ + + /* synchronization objects */ + git_mutex cache_mutex; + git_mutex progress_mutex; + git_cond progress_cond; + + /* configs */ + size_t delta_cache_size; + size_t max_delta_cache_size; + size_t cache_max_small_delta_size; + size_t big_file_threshold; + size_t window_memory_limit; + + unsigned int nr_threads; /* nr of threads to use */ + + git_packbuilder_progress progress_cb; + void *progress_cb_payload; + double last_progress_report_time; /* the time progress was last reported */ + + bool done; +}; + +int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb); +int git_packbuilder__prepare(git_packbuilder *pb); + + +#endif diff --git a/src/libgit2/pack.c b/src/libgit2/pack.c new file mode 100644 index 000000000..16fe378bd --- /dev/null +++ b/src/libgit2/pack.c @@ -0,0 +1,1635 @@ +/* + * 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 "pack.h" + +#include "delta.h" +#include "futils.h" +#include "mwindow.h" +#include "odb.h" +#include "oid.h" +#include "oidarray.h" + +/* Option to bypass checking existence of '.keep' files */ +bool git_disable_pack_keep_file_checks = false; + +static int packfile_open_locked(struct git_pack_file *p); +static off64_t nth_packed_object_offset_locked(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, + off64_t *curpos, + size_t size, + git_object_t type); + +/* Can find the offset of an object given + * a prefix of an identifier. + * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid + * is ambiguous within the pack. + * This method assumes that len is between + * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. + */ +static int pack_entry_find_offset( + off64_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len); + +static int packfile_error(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "invalid pack file - %s", message); + return -1; +} + +/******************** + * Delta base cache + ********************/ + +static git_pack_cache_entry *new_cache_object(git_rawobj *source) +{ + git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry)); + if (!e) + return NULL; + + git_atomic32_inc(&e->refcount); + memcpy(&e->raw, source, sizeof(git_rawobj)); + + return e; +} + +static void free_cache_object(void *o) +{ + git_pack_cache_entry *e = (git_pack_cache_entry *)o; + + if (e != NULL) { + git__free(e->raw.data); + git__free(e); + } +} + +static void cache_free(git_pack_cache *cache) +{ + git_pack_cache_entry *entry; + + if (cache->entries) { + git_offmap_foreach_value(cache->entries, entry, { + free_cache_object(entry); + }); + + git_offmap_free(cache->entries); + cache->entries = NULL; + } +} + +static int cache_init(git_pack_cache *cache) +{ + if (git_offmap_new(&cache->entries) < 0) + return -1; + + cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT; + + if (git_mutex_init(&cache->lock)) { + git_error_set(GIT_ERROR_OS, "failed to initialize pack cache mutex"); + + git__free(cache->entries); + cache->entries = NULL; + + return -1; + } + + return 0; +} + +static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) +{ + git_pack_cache_entry *entry; + + if (git_mutex_lock(&cache->lock) < 0) + return NULL; + + if ((entry = git_offmap_get(cache->entries, offset)) != NULL) { + git_atomic32_inc(&entry->refcount); + entry->last_usage = cache->use_ctr++; + } + git_mutex_unlock(&cache->lock); + + return entry; +} + +/* Run with the cache lock held */ +static void free_lowest_entry(git_pack_cache *cache) +{ + off64_t offset; + git_pack_cache_entry *entry; + + git_offmap_foreach(cache->entries, offset, entry, { + if (entry && git_atomic32_get(&entry->refcount) == 0) { + cache->memory_used -= entry->raw.len; + git_offmap_delete(cache->entries, offset); + free_cache_object(entry); + } + }); +} + +static int cache_add( + git_pack_cache_entry **cached_out, + git_pack_cache *cache, + git_rawobj *base, + off64_t offset) +{ + git_pack_cache_entry *entry; + int exists; + + if (base->len > GIT_PACK_CACHE_SIZE_LIMIT) + return -1; + + entry = new_cache_object(base); + if (entry) { + if (git_mutex_lock(&cache->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock cache"); + git__free(entry); + return -1; + } + /* Add it to the cache if nobody else has */ + exists = git_offmap_exists(cache->entries, offset); + if (!exists) { + while (cache->memory_used + base->len > cache->memory_limit) + free_lowest_entry(cache); + + git_offmap_set(cache->entries, offset, entry); + cache->memory_used += entry->raw.len; + + *cached_out = entry; + } + git_mutex_unlock(&cache->lock); + /* Somebody beat us to adding it into the cache */ + if (exists) { + git__free(entry); + return -1; + } + } + + return 0; +} + +/*********************************************************** + * + * PACK INDEX METHODS + * + ***********************************************************/ + +static void pack_index_free(struct git_pack_file *p) +{ + if (p->oids) { + git__free(p->oids); + p->oids = NULL; + } + if (p->index_map.data) { + git_futils_mmap_free(&p->index_map); + p->index_map.data = NULL; + } +} + +/* Run with the packfile lock held */ +static int pack_index_check_locked(const char *path, struct git_pack_file *p) +{ + struct git_pack_idx_header *hdr; + uint32_t version, nr, i, *index; + void *idx_map; + size_t idx_size; + struct stat st; + int error; + /* TODO: properly open the file without access time using O_NOATIME */ + git_file fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_OS, "unable to stat pack index '%s'", path); + return -1; + } + + if (!S_ISREG(st.st_mode) || + !git__is_sizet(st.st_size) || + (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20) + { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); + return -1; + } + + error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); + + p_close(fd); + + if (error < 0) + return error; + + hdr = idx_map = p->index_map.data; + + if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { + version = ntohl(hdr->idx_version); + + if (version < 2 || version > 2) { + git_futils_mmap_free(&p->index_map); + return packfile_error("unsupported index version"); + } + + } else + version = 1; + + nr = 0; + index = idx_map; + + if (version > 1) + index += 2; /* skip index header */ + + for (i = 0; i < 256; i++) { + uint32_t n = ntohl(index[i]); + if (n < nr) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is non-monotonic"); + } + nr = n; + } + + if (version == 1) { + /* + * Total size: + * - 256 index entries 4 bytes each + * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + */ + if (idx_size != 4*256 + nr * 24 + 20 + 20) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is corrupted"); + } + } else if (version == 2) { + /* + * Minimum size: + * - 8 bytes of header + * - 256 index entries 4 bytes each + * - 20-byte sha1 entry * nr + * - 4-byte crc entry * nr + * - 4-byte offset entry * nr + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + * And after the 4-byte offset table might be a + * variable sized table containing 8-byte entries + * for offsets larger than 2^31. + */ + unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; + unsigned long max_size = min_size; + + if (nr) + max_size += (nr - 1)*8; + + if (idx_size < min_size || idx_size > max_size) { + git_futils_mmap_free(&p->index_map); + return packfile_error("wrong index size"); + } + } + + p->num_objects = nr; + p->index_version = version; + return 0; +} + +/* Run with the packfile lock held */ +static int pack_index_open_locked(struct git_pack_file *p) +{ + int error = 0; + size_t name_len; + git_str idx_name = GIT_STR_INIT; + + if (p->index_version > -1) + goto cleanup; + + /* checked by git_pack_file alloc */ + name_len = strlen(p->pack_name); + GIT_ASSERT(name_len > strlen(".pack")); + + if ((error = git_str_init(&idx_name, name_len)) < 0) + goto cleanup; + + git_str_put(&idx_name, p->pack_name, name_len - strlen(".pack")); + git_str_puts(&idx_name, ".idx"); + if (git_str_oom(&idx_name)) { + error = -1; + goto cleanup; + } + + if (p->index_version == -1) + error = pack_index_check_locked(idx_name.ptr, p); + +cleanup: + git_str_dispose(&idx_name); + + return error; +} + +static unsigned char *pack_window_open( + struct git_pack_file *p, + git_mwindow **w_cursor, + off64_t offset, + unsigned int *left) +{ + unsigned char *pack_data = NULL; + + if (git_mutex_lock(&p->lock) < 0) { + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); + return NULL; + } + if (git_mutex_lock(&p->mwf.lock) < 0) { + git_mutex_unlock(&p->lock); + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); + return NULL; + } + + if (p->mwf.fd == -1 && packfile_open_locked(p) < 0) + goto cleanup; + + /* Since packfiles end in a hash of their content and it's + * pointless to ask for an offset into the middle of that + * hash, and the pack_window_contains function above wouldn't match + * don't allow an offset too close to the end of the file. + * + * Don't allow a negative offset, as that means we've wrapped + * around. + */ + if (offset > (p->mwf.size - 20)) + goto cleanup; + if (offset < 0) + goto cleanup; + + pack_data = git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); + +cleanup: + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + return pack_data; + } + +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", + * then three bits of "type", + * with the high bit being "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type) +{ + unsigned char *hdr_base; + unsigned char c; + + GIT_ASSERT_ARG(type >= GIT_OBJECT_COMMIT && type <= GIT_OBJECT_REF_DELTA); + + /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ + + c = (unsigned char)((type << 4) | (size & 15)); + size >>= 4; + hdr_base = hdr; + + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + } + *hdr++ = c; + + *out = (hdr - hdr_base); + return 0; +} + + +static int packfile_unpack_header1( + unsigned long *usedp, + size_t *sizep, + git_object_t *type, + const unsigned char *buf, + unsigned long len) +{ + unsigned shift; + unsigned long size, c; + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) { + git_error_set(GIT_ERROR_ODB, "buffer too small"); + return GIT_EBUFS; + } + + if (bitsizeof(long) <= shift) { + *usedp = 0; + git_error_set(GIT_ERROR_ODB, "packfile corrupted"); + return -1; + } + + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + + *sizep = (size_t)size; + *usedp = used; + return 0; +} + +int git_packfile_unpack_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos) +{ + unsigned char *base; + unsigned int left; + unsigned long used; + int error; + + if ((error = git_mutex_lock(&p->lock)) < 0) + return error; + if ((error = git_mutex_lock(&p->mwf.lock)) < 0) { + git_mutex_unlock(&p->lock); + return error; + } + + if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { + git_mutex_unlock(&p->lock); + git_mutex_unlock(&p->mwf.lock); + return error; + } + + /* pack_window_open() assures us we have [base, base + 20) available + * as a range that we can look at at. (Its actually the hash + * size that is assured.) With our object header encoding + * the maximum deflated object size is 2^137, which is just + * insane, so we know won't exceed what we have been given. + */ + base = git_mwindow_open(&p->mwf, w_curs, *curpos, 20, &left); + git_mutex_unlock(&p->lock); + git_mutex_unlock(&p->mwf.lock); + if (base == NULL) + return GIT_EBUFS; + + error = packfile_unpack_header1(&used, size_p, type_p, base, left); + git_mwindow_close(w_curs); + if (error == GIT_EBUFS) + return error; + else if (error < 0) + return packfile_error("header length is zero"); + + *curpos += used; + return 0; +} + +int git_packfile_resolve_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + off64_t offset) +{ + git_mwindow *w_curs = NULL; + off64_t curpos = offset; + size_t size; + git_object_t type; + off64_t base_offset; + int error; + + error = git_mutex_lock(&p->lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + error = git_mutex_lock(&p->mwf.lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + git_mutex_unlock(&p->lock); + return error; + } + + if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + return error; + } + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + + error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); + if (error < 0) + return error; + + if (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { + size_t base_size; + git_packfile_stream stream; + + error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, offset); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + + if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0) + return error; + error = git_delta_read_header_fromstream(&base_size, size_p, &stream); + git_packfile_stream_dispose(&stream); + if (error < 0) + return error; + } else { + *size_p = size; + base_offset = 0; + } + + while (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { + curpos = base_offset; + error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); + if (error < 0) + return error; + if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) + break; + + error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, base_offset); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + } + *type_p = type; + + return error; +} + +#define SMALL_STACK_SIZE 64 + +/** + * Generate the chain of dependencies which we need to get to the + * object at `off`. `chain` is used a stack, popping gives the right + * order to apply deltas on. If an object is found in the pack's base + * cache, we stop calculating there. + */ +static int pack_dependency_chain(git_dependency_chain *chain_out, + 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, off64_t obj_offset) +{ + git_dependency_chain chain = GIT_ARRAY_INIT; + git_mwindow *w_curs = NULL; + off64_t curpos = obj_offset, base_offset; + int error = 0, use_heap = 0; + size_t size, elem_pos; + git_object_t type; + + elem_pos = 0; + while (true) { + struct pack_chain_elem *elem; + git_pack_cache_entry *cached = NULL; + + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + *cached_out = cached; + *cached_off = obj_offset; + break; + } + + /* if we run out of space on the small stack, use the array */ + if (elem_pos == SMALL_STACK_SIZE) { + git_array_init_to_size(chain, elem_pos); + GIT_ERROR_CHECK_ARRAY(chain); + memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); + chain.size = elem_pos; + use_heap = 1; + } + + curpos = obj_offset; + if (!use_heap) { + elem = &small_stack[elem_pos]; + } else { + elem = git_array_alloc(chain); + if (!elem) { + error = -1; + goto on_error; + } + } + + elem->base_key = obj_offset; + + error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); + if (error < 0) + goto on_error; + + elem->offset = curpos; + elem->size = size; + elem->type = type; + elem->base_key = obj_offset; + + if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) + break; + + error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); + + if (error < 0) + goto on_error; + + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; + elem_pos++; + } + + + *stack_sz = elem_pos + 1; + *chain_out = chain; + return error; + +on_error: + git_array_clear(chain); + return error; +} + +int git_packfile_unpack( + git_rawobj *obj, + struct git_pack_file *p, + off64_t *obj_offset) +{ + git_mwindow *w_curs = NULL; + off64_t curpos = *obj_offset; + int error, free_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; + struct pack_chain_elem *elem = NULL, *stack; + git_pack_cache_entry *cached = NULL; + struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; + size_t stack_size = 0, elem_pos, alloclen; + git_object_t base_type; + + error = git_mutex_lock(&p->lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + error = git_mutex_lock(&p->mwf.lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + git_mutex_unlock(&p->lock); + return error; + } + + if (p->mwf.fd == -1) + error = packfile_open_locked(p); + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + if (error < 0) + return error; + + /* + * TODO: optionally check the CRC on the packfile + */ + + error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); + if (error < 0) + return error; + + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJECT_INVALID; + + /* let's point to the right stack */ + stack = chain.ptr ? chain.ptr : small_stack; + + elem_pos = stack_size; + if (cached) { + memcpy(obj, &cached->raw, sizeof(git_rawobj)); + base_type = obj->type; + elem_pos--; /* stack_size includes the base, which isn't actually there */ + } else { + elem = &stack[--elem_pos]; + base_type = elem->type; + } + + switch (base_type) { + case GIT_OBJECT_COMMIT: + case GIT_OBJECT_TREE: + case GIT_OBJECT_BLOB: + case GIT_OBJECT_TAG: + if (!cached) { + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + base_type = elem->type; + } + if (error < 0) + goto cleanup; + break; + case GIT_OBJECT_OFS_DELTA: + case GIT_OBJECT_REF_DELTA: + error = packfile_error("dependency chain ends in a delta"); + goto cleanup; + default: + error = packfile_error("invalid packfile type in header"); + goto cleanup; + } + + /* + * Finding the object we want a cached base element is + * problematic, as we need to make sure we don't accidentally + * give the caller the cached object, which it would then feel + * free to free, so we need to copy the data. + */ + if (cached && stack_size == 1) { + void *data = obj->data; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, obj->len, 1); + obj->data = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(obj->data); + + memcpy(obj->data, data, obj->len + 1); + git_atomic32_dec(&cached->refcount); + goto cleanup; + } + + /* we now apply each consecutive delta until we run out */ + while (elem_pos > 0 && !error) { + git_rawobj base, delta; + + /* + * We can now try to add the base to the cache, as + * long as it's not already the cached one. + */ + if (!cached) + free_base = !!cache_add(&cached, &p->bases, obj, elem->base_key); + + elem = &stack[elem_pos - 1]; + curpos = elem->offset; + error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + + if (error < 0) { + /* We have transferred ownership of the data to the cache. */ + obj->data = NULL; + break; + } + + /* the current object becomes the new base, on which we apply the delta */ + base = *obj; + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJECT_INVALID; + + error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len); + obj->type = base_type; + + /* + * We usually don't want to free the base at this + * point, as we put it into the cache in the previous + * iteration. free_base lets us know that we got the + * base object directly from the packfile, so we can free it. + */ + git__free(delta.data); + if (free_base) { + free_base = 0; + git__free(base.data); + } + + if (cached) { + git_atomic32_dec(&cached->refcount); + cached = NULL; + } + + if (error < 0) + break; + + elem_pos--; + } + +cleanup: + if (error < 0) { + git__free(obj->data); + if (cached) + git_atomic32_dec(&cached->refcount); + } + + if (elem) + *obj_offset = curpos; + + git_array_clear(chain); + return error; +} + +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos) +{ + memset(obj, 0, sizeof(git_packfile_stream)); + obj->curpos = curpos; + obj->p = p; + + if (git_zstream_init(&obj->zstream, GIT_ZSTREAM_INFLATE) < 0) { + git_error_set(GIT_ERROR_ZLIB, "failed to init packfile stream"); + return -1; + } + + return 0; +} + +ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len) +{ + unsigned int window_len; + unsigned char *in; + int error; + + if (obj->done) + return 0; + + if ((in = pack_window_open(obj->p, &obj->mw, obj->curpos, &window_len)) == NULL) + return GIT_EBUFS; + + 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; + } + + 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 (!len && !git_zstream_eos(&obj->zstream)) + return GIT_EBUFS; + + return len; + +} + +void git_packfile_stream_dispose(git_packfile_stream *obj) +{ + git_zstream_free(&obj->zstream); +} + +static int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **mwindow, + off64_t *position, + size_t size, + git_object_t type) +{ + git_zstream zstream = GIT_ZSTREAM_INIT; + size_t buffer_len, total = 0; + char *data = NULL; + int error; + + GIT_ERROR_CHECK_ALLOC_ADD(&buffer_len, size, 1); + data = git__calloc(1, buffer_len); + GIT_ERROR_CHECK_ALLOC(data); + + if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0) { + git_error_set(GIT_ERROR_ZLIB, "failed to init zlib stream on unpack"); + goto out; + } + + do { + size_t bytes = buffer_len - total; + unsigned int window_len, consumed; + unsigned char *in; + + if ((in = pack_window_open(p, mwindow, *position, &window_len)) == NULL) { + error = -1; + goto out; + } + + 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; + } + + git_mwindow_close(mwindow); + + consumed = window_len - (unsigned int)zstream.in_len; + + if (!bytes && !consumed) { + git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); + error = -1; + goto out; + } + + *position += consumed; + total += bytes; + } while (!git_zstream_eos(&zstream)); + + if (total != size || !git_zstream_eos(&zstream)) { + git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); + error = -1; + goto out; + } + + obj->type = type; + obj->len = size; + 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 + */ +int get_delta_base( + off64_t *delta_base_out, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos, + git_object_t type, + off64_t delta_obj_offset) +{ + unsigned int left = 0; + unsigned char *base_info; + off64_t base_offset; + git_oid unused; + + GIT_ASSERT_ARG(delta_base_out); + + base_info = pack_window_open(p, w_curs, *curpos, &left); + /* Assumption: the only reason this would fail is because the file is too small */ + if (base_info == NULL) + return GIT_EBUFS; + /* pack_window_open() assured us we have [base_info, base_info + 20) + * as a range that we can look at without walking off the + * end of the mapped window. Its actually the hash size + * that is assured. An OFS_DELTA longer than the hash size + * is stupid, as then a REF_DELTA would be smaller to store. + */ + if (type == GIT_OBJECT_OFS_DELTA) { + unsigned used = 0; + unsigned char c = base_info[used++]; + size_t unsigned_base_offset = c & 127; + while (c & 128) { + if (left <= used) + return GIT_EBUFS; + unsigned_base_offset += 1; + if (!unsigned_base_offset || MSB(unsigned_base_offset, 7)) + return packfile_error("overflow"); + c = base_info[used++]; + unsigned_base_offset = (unsigned_base_offset << 7) + (c & 127); + } + if (unsigned_base_offset == 0 || (size_t)delta_obj_offset <= unsigned_base_offset) + return packfile_error("out of bounds"); + base_offset = delta_obj_offset - unsigned_base_offset; + *curpos += used; + } else if (type == GIT_OBJECT_REF_DELTA) { + git_oid base_oid; + git_oid_fromraw(&base_oid, base_info); + + /* If we have the cooperative cache, search in it first */ + if (p->has_cache) { + struct git_pack_entry *entry; + + if ((entry = git_oidmap_get(p->idx_cache, &base_oid)) != NULL) { + if (entry->offset == 0) + return packfile_error("delta offset is zero"); + + *curpos += 20; + *delta_base_out = entry->offset; + return 0; + } 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 + * progress again in the next loop. + */ + return GIT_PASSTHROUGH; + } + } + + /* The base entry _must_ be in the same pack */ + if (pack_entry_find_offset(&base_offset, &unused, p, &base_oid, GIT_OID_HEXSZ) < 0) + return packfile_error("base entry delta is not in the same pack"); + *curpos += 20; + } else + return packfile_error("unknown object type"); + + if (base_offset == 0) + return packfile_error("delta offset is zero"); + + *delta_base_out = base_offset; + return 0; +} + +/*********************************************************** + * + * PACKFILE METHODS + * + ***********************************************************/ + +void git_packfile_free(struct git_pack_file *p, bool unlink_packfile) +{ + bool locked = true; + + if (!p) + return; + + cache_free(&p->bases); + + if (git_mutex_lock(&p->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile"); + locked = false; + } + if (p->mwf.fd >= 0) { + git_mwindow_free_all(&p->mwf); + p_close(p->mwf.fd); + p->mwf.fd = -1; + } + if (locked) + git_mutex_unlock(&p->lock); + + if (unlink_packfile) + p_unlink(p->pack_name); + + pack_index_free(p); + + git__free(p->bad_object_sha1); + + git_mutex_free(&p->bases.lock); + git_mutex_free(&p->mwf.lock); + git_mutex_free(&p->lock); + git__free(p); +} + +/* Run with the packfile and mwf locks held */ +static int packfile_open_locked(struct git_pack_file *p) +{ + struct stat st; + struct git_pack_header hdr; + unsigned char sha1[GIT_OID_RAWSZ]; + unsigned char *idx_sha1; + + if (pack_index_open_locked(p) < 0) + return git_odb__error_notfound("failed to open packfile", NULL, 0); + + if (p->mwf.fd >= 0) + return 0; + + /* TODO: open with noatime */ + p->mwf.fd = git_futils_open_ro(p->pack_name); + if (p->mwf.fd < 0) + goto cleanup; + + if (p_fstat(p->mwf.fd, &st) < 0) { + git_error_set(GIT_ERROR_OS, "could not stat packfile"); + goto cleanup; + } + + /* If we created the struct before we had the pack we lack size. */ + if (!p->mwf.size) { + if (!S_ISREG(st.st_mode)) + goto cleanup; + p->mwf.size = (off64_t)st.st_size; + } else if (p->mwf.size != st.st_size) + goto cleanup; + +#if 0 + /* We leave these file descriptors open with sliding mmap; + * there is no point keeping them open across exec(), though. + */ + fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); + if (fd_flag < 0) + goto cleanup; + + fd_flag |= FD_CLOEXEC; + if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) + goto cleanup; +#endif + + /* Verify we recognize this pack file format. */ + if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 || + hdr.hdr_signature != htonl(PACK_SIGNATURE) || + !pack_version_ok(hdr.hdr_version)) + goto cleanup; + + /* Verify the pack matches its index. */ + if (p->num_objects != ntohl(hdr.hdr_entries) || + p_pread(p->mwf.fd, sha1, GIT_OID_RAWSZ, p->mwf.size - GIT_OID_RAWSZ) < 0) + goto cleanup; + + idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; + + if (git_oid_raw_cmp(sha1, idx_sha1) != 0) + goto cleanup; + + if (git_mwindow_file_register(&p->mwf) < 0) + goto cleanup; + + return 0; + +cleanup: + git_error_set(GIT_ERROR_OS, "invalid packfile '%s'", p->pack_name); + + if (p->mwf.fd >= 0) + p_close(p->mwf.fd); + p->mwf.fd = -1; + + return -1; +} + +int git_packfile__name(char **out, const char *path) +{ + size_t path_len; + git_str buf = GIT_STR_INIT; + + path_len = strlen(path); + + if (path_len < strlen(".idx")) + return git_odb__error_notfound("invalid packfile path", NULL, 0); + + if (git_str_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0) + return -1; + + *out = git_str_detach(&buf); + return 0; +} + +int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) +{ + struct stat st; + struct git_pack_file *p; + size_t path_len = path ? strlen(path) : 0, alloc_len; + + *pack_out = NULL; + + if (path_len < strlen(".idx")) + return git_odb__error_notfound("invalid packfile path", NULL, 0); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*p), path_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + p = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(p); + + memcpy(p->pack_name, path, path_len + 1); + + /* + * Make sure a corresponding .pack file exists and that + * the index looks sane. + */ + if (git__suffixcmp(path, ".idx") == 0) { + size_t root_len = path_len - strlen(".idx"); + + if (!git_disable_pack_keep_file_checks) { + memcpy(p->pack_name + root_len, ".keep", sizeof(".keep")); + if (git_fs_path_exists(p->pack_name) == true) + p->pack_keep = 1; + } + + memcpy(p->pack_name + root_len, ".pack", sizeof(".pack")); + } + + if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) { + git__free(p); + return git_odb__error_notfound("packfile not found", NULL, 0); + } + + /* ok, it looks sane as far as we can check without + * actually mapping the pack file. + */ + p->mwf.fd = -1; + p->mwf.size = st.st_size; + p->pack_local = 1; + p->mtime = (git_time_t)st.st_mtime; + p->index_version = -1; + + if (git_mutex_init(&p->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to initialize packfile mutex"); + git__free(p); + return -1; + } + + if (git_mutex_init(&p->mwf.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to initialize packfile window mutex"); + git_mutex_free(&p->lock); + git__free(p); + return -1; + } + + if (cache_init(&p->bases) < 0) { + git_mutex_free(&p->mwf.lock); + git_mutex_free(&p->lock); + git__free(p); + return -1; + } + + *pack_out = p; + + return 0; +} + +/*********************************************************** + * + * PACKFILE ENTRY SEARCH INTERNALS + * + ***********************************************************/ + +static off64_t nth_packed_object_offset_locked(struct git_pack_file *p, uint32_t n) +{ + const unsigned char *index, *end; + uint32_t off32; + + index = p->index_map.data; + end = index + p->index_map.len; + index += 4 * 256; + if (p->index_version == 1) + return ntohl(*((uint32_t *)(index + 24 * n))); + + index += 8 + p->num_objects * (20 + 4); + off32 = ntohl(*((uint32_t *)(index + 4 * n))); + if (!(off32 & 0x80000000)) + return off32; + index += p->num_objects * 4 + (off32 & 0x7fffffff) * 8; + + /* Make sure we're not being sent out of bounds */ + if (index >= end - 8) + return -1; + + return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | + ntohl(*((uint32_t *)(index + 4))); +} + +static int git__memcmp4(const void *a, const void *b) { + return memcmp(a, b, 4); +} + +int git_pack_foreach_entry( + struct git_pack_file *p, + git_odb_foreach_cb cb, + void *data) +{ + const unsigned char *index, *current; + uint32_t i; + int error = 0; + git_array_oid_t oids = GIT_ARRAY_INIT; + git_oid *oid; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for git_pack_foreach_entry"); + + if ((error = pack_index_open_locked(p)) < 0) { + git_mutex_unlock(&p->lock); + return error; + } + + if (!p->index_map.data) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); + git_mutex_unlock(&p->lock); + return -1; + } + + index = p->index_map.data; + + if (p->index_version > 1) + index += 8; + + index += 4 * 256; + + if (p->oids == NULL) { + git_vector offsets, oids; + + if ((error = git_vector_init(&oids, p->num_objects, NULL))) { + git_mutex_unlock(&p->lock); + return error; + } + + if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4))) { + git_mutex_unlock(&p->lock); + return error; + } + + if (p->index_version > 1) { + const unsigned char *off = index + 24 * p->num_objects; + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&off[4 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)&index[5 * (current - off)]); + } else { + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&index[24 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)¤t[4]); + } + + git_vector_free(&offsets); + p->oids = (unsigned char **)git_vector_detach(NULL, NULL, &oids); + } + + /* + * We need to copy the OIDs to another array before we + * relinquish the lock to avoid races. We can also take + * this opportunity to put them into normal form. + */ + git_array_init_to_size(oids, p->num_objects); + if (!oids.ptr) { + git_mutex_unlock(&p->lock); + git_array_clear(oids); + GIT_ERROR_CHECK_ARRAY(oids); + } + for (i = 0; i < p->num_objects; i++) { + oid = git_array_alloc(oids); + if (!oid) { + git_mutex_unlock(&p->lock); + git_array_clear(oids); + GIT_ERROR_CHECK_ALLOC(oid); + } + git_oid_fromraw(oid, p->oids[i]); + } + + git_mutex_unlock(&p->lock); + + git_array_foreach(oids, i, oid) { + if ((error = cb(oid, data)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + git_array_clear(oids); + return error; +} + +int git_pack_foreach_entry_offset( + struct git_pack_file *p, + git_pack_foreach_entry_offset_cb cb, + void *data) +{ + const unsigned char *index; + off64_t current_offset; + git_oid current_oid; + uint32_t i; + int error = 0; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for git_pack_foreach_entry_offset"); + + index = p->index_map.data; + if (index == NULL) { + if ((error = pack_index_open_locked(p)) < 0) + goto cleanup; + + if (!p->index_map.data) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); + goto cleanup; + } + + index = p->index_map.data; + } + + if (p->index_version > 1) + index += 8; + + index += 4 * 256; + + /* all offsets should have been validated by pack_index_check_locked */ + if (p->index_version > 1) { + const unsigned char *offsets = index + 24 * p->num_objects; + const unsigned char *large_offset_ptr; + const unsigned char *large_offsets = index + 28 * p->num_objects; + const unsigned char *large_offsets_end = ((const unsigned char *)p->index_map.data) + p->index_map.len - 20; + for (i = 0; i < p->num_objects; i++) { + current_offset = ntohl(*(const uint32_t *)(offsets + 4 * i)); + if (current_offset & 0x80000000) { + large_offset_ptr = large_offsets + (current_offset & 0x7fffffff) * 8; + if (large_offset_ptr >= large_offsets_end) { + error = packfile_error("invalid large offset"); + goto cleanup; + } + current_offset = (((off64_t)ntohl(*((uint32_t *)(large_offset_ptr + 0)))) << 32) | + ntohl(*((uint32_t *)(large_offset_ptr + 4))); + } + + git_oid_fromraw(¤t_oid, (index + 20 * i)); + if ((error = cb(¤t_oid, current_offset, data)) != 0) { + error = git_error_set_after_callback(error); + goto cleanup; + } + } + } else { + for (i = 0; i < p->num_objects; i++) { + current_offset = ntohl(*(const uint32_t *)(index + 24 * i)); + git_oid_fromraw(¤t_oid, (index + 24 * i + 4)); + if ((error = cb(¤t_oid, current_offset, data)) != 0) { + error = git_error_set_after_callback(error); + goto cleanup; + } + } + } + +cleanup: + git_mutex_unlock(&p->lock); + return error; +} + +int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, + unsigned hi, const unsigned char *oid_prefix) +{ + const unsigned char *base = oid_lookup_table; + + while (lo < hi) { + unsigned mi = (lo + hi) / 2; + int cmp = git_oid_raw_cmp(base + mi * stride, oid_prefix); + + if (!cmp) + return mi; + + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } + + return -((int)lo)-1; +} + +static int pack_entry_find_offset( + off64_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len) +{ + const uint32_t *level1_ofs; + const unsigned char *index; + unsigned hi, lo, stride; + int pos, found = 0; + off64_t offset; + const unsigned char *current = 0; + int error = 0; + + *offset_out = 0; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for pack_entry_find_offset"); + + if ((error = pack_index_open_locked(p)) < 0) + goto cleanup; + + if (!p->index_map.data) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); + goto cleanup; + } + + index = p->index_map.data; + level1_ofs = p->index_map.data; + + if (p->index_version > 1) { + level1_ofs += 2; + index += 8; + } + + index += 4 * 256; + hi = ntohl(level1_ofs[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); + + if (p->index_version > 1) { + stride = 20; + } else { + stride = 24; + index += 4; + } + +#ifdef INDEX_DEBUG_LOOKUP + printf("%02x%02x%02x... lo %u hi %u nr %d\n", + short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); +#endif + + pos = git_pack__lookup_sha1(index, stride, lo, hi, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = index + pos * stride; + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = - 1 - pos; + if (pos < (int)p->num_objects) { + current = index + pos * stride; + + if (!git_oid_raw_ncmp(short_oid->id, current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { + /* Check for ambiguousity */ + const unsigned char *next = current + stride; + + if (!git_oid_raw_ncmp(short_oid->id, next, len)) { + found = 2; + } + } + + if (!found) { + error = git_odb__error_notfound("failed to find offset for pack entry", short_oid, len); + goto cleanup; + } + if (found > 1) { + error = git_odb__error_ambiguous("found multiple offsets for pack entry"); + goto cleanup; + } + + if ((offset = nth_packed_object_offset_locked(p, pos)) < 0) { + git_error_set(GIT_ERROR_ODB, "packfile index is corrupt"); + error = -1; + goto cleanup; + } + + *offset_out = offset; + git_oid_fromraw(found_oid, current); + +#ifdef INDEX_DEBUG_LOOKUP + { + unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; + git_oid_fmt(hex_sha1, found_oid); + hex_sha1[GIT_OID_HEXSZ] = '\0'; + printf("found lo=%d %s\n", lo, hex_sha1); + } +#endif + +cleanup: + git_mutex_unlock(&p->lock); + return error; +} + +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len) +{ + off64_t offset; + git_oid found_oid; + int error; + + GIT_ASSERT_ARG(p); + + if (len == GIT_OID_HEXSZ && p->num_bad_objects) { + unsigned i; + for (i = 0; i < p->num_bad_objects; i++) + if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0) + return packfile_error("bad object found in packfile"); + } + + error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); + if (error < 0) + return error; + + error = git_mutex_lock(&p->lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + error = git_mutex_lock(&p->mwf.lock); + if (error < 0) { + git_mutex_unlock(&p->lock); + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + + /* we found a unique entry in the index; + * make sure the packfile backing the index + * still exists on disk */ + if (p->mwf.fd == -1) + error = packfile_open_locked(p); + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + if (error < 0) + return error; + + e->offset = offset; + e->p = p; + + git_oid_cpy(&e->sha1, &found_oid); + return 0; +} diff --git a/src/libgit2/pack.h b/src/libgit2/pack.h new file mode 100644 index 000000000..d90588f79 --- /dev/null +++ b/src/libgit2/pack.h @@ -0,0 +1,200 @@ +/* + * 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_pack_h__ +#define INCLUDE_pack_h__ + +#include "common.h" + +#include "git2/oid.h" + +#include "array.h" +#include "map.h" +#include "mwindow.h" +#include "odb.h" +#include "offmap.h" +#include "oidmap.h" +#include "zstream.h" +#include "oid.h" + +/** + * Function type for callbacks from git_pack_foreach_entry_offset. + */ +typedef int git_pack_foreach_entry_offset_cb( + const git_oid *id, + off64_t offset, + void *payload); + +#define GIT_PACK_FILE_MODE 0444 + +#define PACK_SIGNATURE 0x5041434b /* "PACK" */ +#define PACK_VERSION 2 +#define pack_version_ok(v) ((v) == htonl(2)) +struct git_pack_header { + uint32_t hdr_signature; + uint32_t hdr_version; + uint32_t hdr_entries; +}; + +/* + * The first four bytes of index formats later than version 1 should + * start with this signature, as all older git binaries would find this + * value illegal and abort reading the file. + * + * This is the case because the number of objects in a packfile + * cannot exceed 1,431,660,000 as every object would need at least + * 3 bytes of data and the overall packfile cannot exceed 4 GiB with + * version 1 of the index file due to the offsets limited to 32 bits. + * Clearly the signature exceeds this maximum. + * + * Very old git binaries will also compare the first 4 bytes to the + * next 4 bytes in the index and abort with a "non-monotonic index" + * error if the second 4 byte word is smaller than the first 4 + * byte word. This would be true in the proposed future index + * format as idx_signature would be greater than idx_version. + */ + +#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ + +struct git_pack_idx_header { + uint32_t idx_signature; + uint32_t idx_version; +}; + +typedef struct git_pack_cache_entry { + size_t last_usage; /* enough? */ + git_atomic32 refcount; + git_rawobj raw; +} git_pack_cache_entry; + +struct pack_chain_elem { + 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; + +#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024 +#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */ + +typedef struct { + size_t memory_used; + size_t memory_limit; + size_t use_ctr; + git_mutex lock; + git_offmap *entries; +} git_pack_cache; + +struct git_pack_file { + git_mwindow_file mwf; + git_map index_map; + git_mutex lock; /* protect updates to index_map */ + git_atomic32 refcount; + + uint32_t num_objects; + uint32_t num_bad_objects; + git_oid *bad_object_sha1; /* array of git_oid */ + + int index_version; + git_time_t mtime; + unsigned pack_local:1, pack_keep:1, has_cache:1; + git_oidmap *idx_cache; + unsigned char **oids; + + git_pack_cache bases; /* delta base cache */ + + time_t last_freshen; /* last time the packfile was freshened */ + + /* something like ".git/objects/pack/xxxxx.pack" */ + char pack_name[GIT_FLEX_ARRAY]; /* more */ +}; + +/** + * Return the position where an OID (or a prefix) would be inserted within the + * OID Lookup Table of an .idx file. This performs binary search between the lo + * and hi indices. + * + * The stride parameter is provided because .idx files version 1 store the OIDs + * interleaved with the 4-byte file offsets of the objects within the .pack + * file (stride = 24), whereas files with version 2 store them in a contiguous + * flat array (stride = 20). + */ +int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, + unsigned hi, const unsigned char *oid_prefix); + +struct git_pack_entry { + off64_t offset; + git_oid sha1; + struct git_pack_file *p; +}; + +typedef struct git_packfile_stream { + off64_t curpos; + int done; + git_zstream zstream; + struct git_pack_file *p; + git_mwindow *mw; +} git_packfile_stream; + +int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type); + +int git_packfile__name(char **out, const char *path); + +int git_packfile_unpack_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos); + +int git_packfile_resolve_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + off64_t 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, 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); + +int get_delta_base( + off64_t *delta_base_out, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos, + git_object_t type, + off64_t delta_obj_offset); + +void git_packfile_free(struct git_pack_file *p, bool unlink_packfile); +int git_packfile_alloc(struct git_pack_file **pack_out, const char *path); + +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len); +int git_pack_foreach_entry( + struct git_pack_file *p, + git_odb_foreach_cb cb, + void *data); +/** + * Similar to git_pack_foreach_entry, but: + * - It also provides the offset of the object within the + * packfile. + * - It does not sort the objects in any order. + * - It retains the lock while invoking the callback. + */ +int git_pack_foreach_entry_offset( + struct git_pack_file *p, + git_pack_foreach_entry_offset_cb cb, + void *data); + +#endif diff --git a/src/libgit2/parse.c b/src/libgit2/parse.c new file mode 100644 index 000000000..0a10758bf --- /dev/null +++ b/src/libgit2/parse.c @@ -0,0 +1,134 @@ +/* + * 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 "parse.h" + +int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len) +{ + if (content && content_len) { + ctx->content = content; + ctx->content_len = content_len; + } else { + ctx->content = ""; + ctx->content_len = 0; + } + + ctx->remain = ctx->content; + ctx->remain_len = ctx->content_len; + ctx->line = ctx->remain; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num = 1; + + return 0; +} + +void git_parse_ctx_clear(git_parse_ctx *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->content = ""; +} + +void git_parse_advance_line(git_parse_ctx *ctx) +{ + ctx->line += ctx->line_len; + ctx->remain_len -= ctx->line_len; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num++; +} + +void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt) +{ + ctx->line += char_cnt; + ctx->remain_len -= char_cnt; + ctx->line_len -= char_cnt; +} + +int git_parse_advance_expected( + git_parse_ctx *ctx, + const char *expected, + size_t expected_len) +{ + if (ctx->line_len < expected_len) + return -1; + + if (memcmp(ctx->line, expected, expected_len) != 0) + return -1; + + git_parse_advance_chars(ctx, expected_len); + return 0; +} + +int git_parse_advance_ws(git_parse_ctx *ctx) +{ + int ret = -1; + + while (ctx->line_len > 0 && + ctx->line[0] != '\n' && + git__isspace(ctx->line[0])) { + ctx->line++; + ctx->line_len--; + ctx->remain_len--; + ret = 0; + } + + return ret; +} + +int git_parse_advance_nl(git_parse_ctx *ctx) +{ + if (ctx->line_len != 1 || ctx->line[0] != '\n') + return -1; + + git_parse_advance_line(ctx); + return 0; +} + +int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base) +{ + const char *end; + int ret; + + if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) + return -1; + + if ((ret = git__strntol64(out, ctx->line, ctx->line_len, &end, base)) < 0) + return -1; + + git_parse_advance_chars(ctx, (end - ctx->line)); + 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; + const char *ptr = ctx->line; + + while (remain) { + char c = *ptr; + + if ((flags & GIT_PARSE_PEEK_SKIP_WHITESPACE) && + git__isspace(c)) { + remain--; + ptr++; + continue; + } + + *out = c; + return 0; + } + + return -1; +} diff --git a/src/libgit2/parse.h b/src/libgit2/parse.h new file mode 100644 index 000000000..0ecb7c103 --- /dev/null +++ b/src/libgit2/parse.h @@ -0,0 +1,61 @@ +/* + * 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_parse_h__ +#define INCLUDE_parse_h__ + +#include "common.h" + +typedef struct { + /* Original content buffer */ + const char *content; + size_t content_len; + + /* The remaining (unparsed) buffer */ + const char *remain; + size_t remain_len; + + const char *line; + size_t line_len; + 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_ctx_contains_s(ctx, str) \ + git_parse_ctx_contains(ctx, str, sizeof(str) - 1) + +GIT_INLINE(bool) git_parse_ctx_contains( + git_parse_ctx *ctx, const char *str, size_t len) +{ + return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); +} + +void git_parse_advance_line(git_parse_ctx *ctx); +void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt); +int git_parse_advance_expected( + git_parse_ctx *ctx, + const char *expected, + size_t expected_len); + +#define git_parse_advance_expected_str(ctx, str) \ + git_parse_advance_expected(ctx, str, strlen(str)) + +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) +}; + +int git_parse_peek(char *out, git_parse_ctx *ctx, int flags); + +#endif diff --git a/src/libgit2/patch.c b/src/libgit2/patch.c new file mode 100644 index 000000000..a30546f3c --- /dev/null +++ b/src/libgit2/patch.c @@ -0,0 +1,230 @@ +/* +* 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 "patch.h" + +#include "git2/patch.h" +#include "diff.h" + +int git_patch__invoke_callbacks( + git_patch *patch, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *payload) +{ + int error = 0; + uint32_t i, j; + + if (file_cb) + error = file_cb(patch->delta, 0, payload); + + if (error) + return error; + + if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { + if (binary_cb) + error = binary_cb(patch->delta, &patch->binary, payload); + + return error; + } + + if (!hunk_cb && !line_cb) + return error; + + for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { + git_patch_hunk *h = git_array_get(patch->hunks, i); + + if (hunk_cb) + error = hunk_cb(patch->delta, &h->hunk, payload); + + if (!line_cb) + continue; + + for (j = 0; !error && j < h->line_count; ++j) { + git_diff_line *l = + git_array_get(patch->lines, h->line_start + j); + + error = line_cb(patch->delta, &h->hunk, l, payload); + } + } + + return error; +} + +size_t git_patch_size( + git_patch *patch, + int include_context, + int include_hunk_headers, + int include_file_headers) +{ + size_t out; + + GIT_ASSERT_ARG(patch); + + out = patch->content_size; + + if (!include_context) + out -= patch->context_size; + + if (include_hunk_headers) + out += patch->header_size; + + if (include_file_headers) { + git_str file_header = GIT_STR_INIT; + + if (git_diff_delta__format_file_header( + &file_header, patch->delta, NULL, NULL, 0, true) < 0) + git_error_clear(); + else + out += git_str_len(&file_header); + + git_str_dispose(&file_header); + } + + return out; +} + +int git_patch_line_stats( + size_t *total_ctxt, + size_t *total_adds, + size_t *total_dels, + const git_patch *patch) +{ + size_t totals[3], idx; + + memset(totals, 0, sizeof(totals)); + + for (idx = 0; idx < git_array_size(patch->lines); ++idx) { + git_diff_line *line = git_array_get(patch->lines, idx); + if (!line) + continue; + + switch (line->origin) { + case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; + case GIT_DIFF_LINE_ADDITION: totals[1]++; break; + case GIT_DIFF_LINE_DELETION: totals[2]++; break; + default: + /* diff --stat and --numstat don't count EOFNL marks because + * they will always be paired with a ADDITION or DELETION line. + */ + break; + } + } + + if (total_ctxt) + *total_ctxt = totals[0]; + if (total_adds) + *total_adds = totals[1]; + if (total_dels) + *total_dels = totals[2]; + + return 0; +} + +const git_diff_delta *git_patch_get_delta(const git_patch *patch) +{ + GIT_ASSERT_ARG_WITH_RETVAL(patch, NULL); + return patch->delta; +} + +size_t git_patch_num_hunks(const git_patch *patch) +{ + GIT_ASSERT_ARG(patch); + return git_array_size(patch->hunks); +} + +static int patch_error_outofrange(const char *thing) +{ + git_error_set(GIT_ERROR_INVALID, "patch %s index out of range", thing); + return GIT_ENOTFOUND; +} + +int git_patch_get_hunk( + const git_diff_hunk **out, + size_t *lines_in_hunk, + git_patch *patch, + size_t hunk_idx) +{ + git_patch_hunk *hunk; + GIT_ASSERT_ARG(patch); + + hunk = git_array_get(patch->hunks, hunk_idx); + + if (!hunk) { + if (out) *out = NULL; + if (lines_in_hunk) *lines_in_hunk = 0; + return patch_error_outofrange("hunk"); + } + + if (out) *out = &hunk->hunk; + if (lines_in_hunk) *lines_in_hunk = hunk->line_count; + return 0; +} + +int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) +{ + git_patch_hunk *hunk; + GIT_ASSERT_ARG(patch); + + if (!(hunk = git_array_get(patch->hunks, hunk_idx))) + return patch_error_outofrange("hunk"); + return (int)hunk->line_count; +} + +int git_patch_get_line_in_hunk( + const git_diff_line **out, + git_patch *patch, + size_t hunk_idx, + size_t line_of_hunk) +{ + git_patch_hunk *hunk; + git_diff_line *line; + + GIT_ASSERT_ARG(patch); + + if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { + if (out) *out = NULL; + return patch_error_outofrange("hunk"); + } + + if (line_of_hunk >= hunk->line_count || + !(line = git_array_get( + patch->lines, hunk->line_start + line_of_hunk))) { + if (out) *out = NULL; + return patch_error_outofrange("line"); + } + + if (out) *out = line; + return 0; +} + +git_repository *git_patch_owner(const git_patch *patch) +{ + return patch->repo; +} + +int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + GIT_ASSERT_ARG(diff->patch_fn); + return diff->patch_fn(out, diff, idx); +} + +static void git_patch__free(git_patch *patch) +{ + if (patch->free_fn) + patch->free_fn(patch); +} + +void git_patch_free(git_patch *patch) +{ + if (patch) + GIT_REFCOUNT_DEC(patch, git_patch__free); +} diff --git a/src/libgit2/patch.h b/src/libgit2/patch.h new file mode 100644 index 000000000..1e1471ed6 --- /dev/null +++ b/src/libgit2/patch.h @@ -0,0 +1,69 @@ +/* +* 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_patch_h__ +#define INCLUDE_patch_h__ + +#include "common.h" + +#include "git2/patch.h" +#include "array.h" + +/* cached information about a hunk in a patch */ +typedef struct git_patch_hunk { + git_diff_hunk hunk; + size_t line_start; + size_t line_count; +} git_patch_hunk; + +struct git_patch { + git_refcount rc; + + git_repository *repo; /* may be null */ + + git_diff_options diff_opts; + + git_diff_delta *delta; + git_diff_binary binary; + git_array_t(git_patch_hunk) hunks; + git_array_t(git_diff_line) lines; + + size_t header_size; + size_t content_size; + size_t context_size; + + void (*free_fn)(git_patch *patch); +}; + +extern int git_patch__invoke_callbacks( + git_patch *patch, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *payload); + +extern int git_patch_line_stats( + size_t *total_ctxt, + size_t *total_adds, + size_t *total_dels, + const git_patch *patch); + +/** Options for parsing patch files. */ +typedef struct { + /** + * The length of the prefix (in path segments) for the filenames. + * This prefix will be removed when looking for files. The default is 1. + */ + uint32_t prefix_len; +} git_patch_options; + +#define GIT_PATCH_OPTIONS_INIT { 1 } + +extern int git_patch__to_buf(git_str *out, git_patch *patch); +extern void git_patch_free(git_patch *patch); + +#endif diff --git a/src/libgit2/patch_generate.c b/src/libgit2/patch_generate.c new file mode 100644 index 000000000..bc598fea8 --- /dev/null +++ b/src/libgit2/patch_generate.c @@ -0,0 +1,915 @@ +/* + * 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 "patch_generate.h" + +#include "git2/blob.h" +#include "diff.h" +#include "diff_generate.h" +#include "diff_file.h" +#include "diff_driver.h" +#include "diff_xdiff.h" +#include "delta.h" +#include "zstream.h" +#include "futils.h" + +static void diff_output_init( + git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, + git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); + +static void diff_output_to_patch( + git_patch_generated_output *, git_patch_generated *); + +static void patch_generated_free(git_patch *p) +{ + git_patch_generated *patch = (git_patch_generated *)p; + + git_array_clear(patch->base.lines); + git_array_clear(patch->base.hunks); + + git__free((char *)patch->base.binary.old_file.data); + git__free((char *)patch->base.binary.new_file.data); + + git_diff_file_content__clear(&patch->ofile); + git_diff_file_content__clear(&patch->nfile); + + git_diff_free(patch->diff); /* decrements refcount */ + patch->diff = NULL; + + git_pool_clear(&patch->flattened); + + git__free((char *)patch->base.diff_opts.old_prefix); + git__free((char *)patch->base.diff_opts.new_prefix); + + if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED) + git__free(patch); +} + +static void patch_generated_update_binary(git_patch_generated *patch) +{ + if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) + return; + + if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || + (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + + else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || + patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + + else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && + (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) + patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; +} + +static void patch_generated_init_common(git_patch_generated *patch) +{ + patch->base.free_fn = patch_generated_free; + + patch_generated_update_binary(patch); + + patch->flags |= GIT_PATCH_GENERATED_INITIALIZED; + + if (patch->diff) + git_diff_addref(patch->diff); +} + +static int patch_generated_normalize_options( + git_diff_options *out, + const git_diff_options *opts) +{ + if (opts) { + GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); + memcpy(out, opts, sizeof(git_diff_options)); + } else { + git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT; + memcpy(out, &default_opts, sizeof(git_diff_options)); + } + + out->old_prefix = opts && opts->old_prefix ? + git__strdup(opts->old_prefix) : + git__strdup(DIFF_OLD_PREFIX_DEFAULT); + + out->new_prefix = opts && opts->new_prefix ? + git__strdup(opts->new_prefix) : + git__strdup(DIFF_NEW_PREFIX_DEFAULT); + + GIT_ERROR_CHECK_ALLOC(out->old_prefix); + GIT_ERROR_CHECK_ALLOC(out->new_prefix); + + return 0; +} + +static int patch_generated_init( + git_patch_generated *patch, git_diff *diff, size_t delta_index) +{ + int error = 0; + + memset(patch, 0, sizeof(*patch)); + + patch->diff = diff; + patch->base.repo = diff->repo; + patch->base.delta = git_vector_get(&diff->deltas, delta_index); + patch->delta_index = delta_index; + + if ((error = patch_generated_normalize_options( + &patch->base.diff_opts, &diff->opts)) < 0 || + (error = git_diff_file_content__init_from_diff( + &patch->ofile, diff, patch->base.delta, true)) < 0 || + (error = git_diff_file_content__init_from_diff( + &patch->nfile, diff, patch->base.delta, false)) < 0) + return error; + + patch_generated_init_common(patch); + + return 0; +} + +static int patch_generated_alloc_from_diff( + git_patch_generated **out, git_diff *diff, size_t delta_index) +{ + int error; + git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated)); + GIT_ERROR_CHECK_ALLOC(patch); + + if (!(error = patch_generated_init(patch, diff, delta_index))) { + patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; + GIT_REFCOUNT_INC(&patch->base); + } else { + git__free(patch); + patch = NULL; + } + + *out = patch; + return error; +} + +GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file) +{ + if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) + return false; + + return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; +} + +static bool patch_generated_diffable(git_patch_generated *patch) +{ + size_t olen, nlen; + + if (patch->base.delta->status == GIT_DELTA_UNMODIFIED) + return false; + + /* if we've determined this to be binary (and we are not showing binary + * data) then we have skipped loading the map data. instead, query the + * file data itself. + */ + if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && + (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { + olen = (size_t)patch->ofile.file->size; + nlen = (size_t)patch->nfile.file->size; + } else { + olen = patch->ofile.map.len; + nlen = patch->nfile.map.len; + } + + /* if both sides are empty, files are identical */ + if (!olen && !nlen) + return false; + + /* otherwise, check the file sizes and the oid */ + return (olen != nlen || + !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); +} + +static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output) +{ + int error = 0; + bool incomplete_data; + + if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0) + return 0; + + /* if no hunk and data callbacks and user doesn't care if data looks + * binary, then there is no need to actually load the data + */ + if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 && + output && !output->binary_cb && !output->hunk_cb && !output->data_cb) + return 0; + + incomplete_data = + (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || + (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) && + ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || + (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0)); + + if ((error = git_diff_file_content__load( + &patch->ofile, &patch->base.diff_opts)) < 0 || + (error = git_diff_file_content__load( + &patch->nfile, &patch->base.diff_opts)) < 0 || + should_skip_binary(patch, patch->nfile.file)) + goto cleanup; + + /* if previously missing an oid, and now that we have it the two sides + * are the same (and not submodules), update MODIFIED -> UNMODIFIED + */ + if (incomplete_data && + patch->ofile.file->mode == patch->nfile.file->mode && + patch->ofile.file->mode != GIT_FILEMODE_COMMIT && + git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && + patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ + patch->base.delta->status = GIT_DELTA_UNMODIFIED; + +cleanup: + patch_generated_update_binary(patch); + + if (!error) { + if (patch_generated_diffable(patch)) + patch->flags |= GIT_PATCH_GENERATED_DIFFABLE; + + patch->flags |= GIT_PATCH_GENERATED_LOADED; + } + + return error; +} + +static int patch_generated_invoke_file_callback( + git_patch_generated *patch, git_patch_generated_output *output) +{ + float progress = patch->diff ? + ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; + + if (!output->file_cb) + return 0; + + return git_error_set_after_callback_function( + output->file_cb(patch->base.delta, progress, output->payload), + "git_patch"); +} + +static int create_binary( + git_diff_binary_t *out_type, + char **out_data, + size_t *out_datalen, + size_t *out_inflatedlen, + const char *a_data, + size_t a_datalen, + const char *b_data, + size_t b_datalen) +{ + git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT; + size_t delta_data_len = 0; + int error; + + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen)) + return GIT_EBUFS; + + if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0) + goto done; + + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(deflate.size)) { + error = GIT_EBUFS; + goto done; + } + + if (a_datalen && b_datalen) { + void *delta_data; + + error = git_delta(&delta_data, &delta_data_len, + a_data, a_datalen, + b_data, b_datalen, + deflate.size); + + if (error == 0) { + error = git_zstream_deflatebuf( + &delta, delta_data, delta_data_len); + + git__free(delta_data); + } else if (error == GIT_EBUFS) { + error = 0; + } + + if (error < 0) + goto done; + } + + if (delta.size && delta.size < deflate.size) { + *out_type = GIT_DIFF_BINARY_DELTA; + *out_datalen = delta.size; + *out_data = git_str_detach(&delta); + *out_inflatedlen = delta_data_len; + } else { + *out_type = GIT_DIFF_BINARY_LITERAL; + *out_datalen = deflate.size; + *out_data = git_str_detach(&deflate); + *out_inflatedlen = b_datalen; + } + +done: + git_str_dispose(&deflate); + git_str_dispose(&delta); + + return error; +} + +static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch) +{ + git_diff_binary binary = {0}; + const char *old_data = patch->ofile.map.data; + const char *new_data = patch->nfile.map.data; + size_t old_len = patch->ofile.map.len, + new_len = patch->nfile.map.len; + int error; + + /* Only load contents if the user actually wants to diff + * binary files. */ + if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) { + binary.contains_data = 1; + + /* Create the old->new delta (as the "new" side of the patch), + * and the new->old delta (as the "old" side) + */ + if ((error = create_binary(&binary.old_file.type, + (char **)&binary.old_file.data, + &binary.old_file.datalen, + &binary.old_file.inflatedlen, + new_data, new_len, old_data, old_len)) < 0 || + (error = create_binary(&binary.new_file.type, + (char **)&binary.new_file.data, + &binary.new_file.datalen, + &binary.new_file.inflatedlen, + old_data, old_len, new_data, new_len)) < 0) + return error; + } + + error = git_error_set_after_callback_function( + output->binary_cb(patch->base.delta, &binary, output->payload), + "git_patch"); + + git__free((char *) binary.old_file.data); + git__free((char *) binary.new_file.data); + + return error; +} + +static int patch_generated_create( + git_patch_generated *patch, + git_patch_generated_output *output) +{ + int error = 0; + + if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0) + return 0; + + /* if we are not looking at the binary or text data, don't do the diff */ + if (!output->binary_cb && !output->hunk_cb && !output->data_cb) + return 0; + + if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 && + (error = patch_generated_load(patch, output)) < 0) + return error; + + if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0) + return 0; + + if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { + if (output->binary_cb) + error = diff_binary(output, patch); + } + else { + if (output->diff_cb) + error = output->diff_cb(output, patch); + } + + patch->flags |= GIT_PATCH_GENERATED_DIFFED; + return error; +} + +static int diff_required(git_diff *diff, const char *action) +{ + if (diff) + return 0; + git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action); + return -1; +} + +typedef struct { + git_patch_generated patch; + git_diff_delta delta; + char paths[GIT_FLEX_ARRAY]; +} patch_generated_with_delta; + +static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo) +{ + int error = 0; + git_patch_generated *patch = &pd->patch; + bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); + bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); + + pd->delta.status = has_new ? + (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : + (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); + + if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) + pd->delta.status = GIT_DELTA_UNMODIFIED; + + patch->base.delta = &pd->delta; + + patch_generated_init_common(patch); + + if (pd->delta.status == GIT_DELTA_UNMODIFIED && + !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) { + + /* Even empty patches are flagged as binary, and even though + * there's no difference, we flag this as "containing data" + * (the data is known to be empty, as opposed to wholly unknown). + */ + if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) + patch->base.binary.contains_data = 1; + + return error; + } + + error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo); + + if (!error) + error = patch_generated_create(patch, (git_patch_generated_output *)xo); + + return error; +} + +static int patch_generated_from_sources( + patch_generated_with_delta *pd, + git_xdiff_output *xo, + git_diff_file_content_src *oldsrc, + git_diff_file_content_src *newsrc, + const git_diff_options *opts) +{ + int error = 0; + git_repository *repo = + oldsrc->blob ? git_blob_owner(oldsrc->blob) : + newsrc->blob ? git_blob_owner(newsrc->blob) : NULL; + git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; + git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; + + if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0) + return error; + + if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { + void *tmp = lfile; lfile = rfile; rfile = tmp; + tmp = ldata; ldata = rdata; rdata = tmp; + } + + pd->patch.base.delta = &pd->delta; + + if (!oldsrc->as_path) { + if (newsrc->as_path) + oldsrc->as_path = newsrc->as_path; + else + oldsrc->as_path = newsrc->as_path = "file"; + } + else if (!newsrc->as_path) + newsrc->as_path = oldsrc->as_path; + + lfile->path = oldsrc->as_path; + rfile->path = newsrc->as_path; + + if ((error = git_diff_file_content__init_from_src( + ldata, repo, opts, oldsrc, lfile)) < 0 || + (error = git_diff_file_content__init_from_src( + rdata, repo, opts, newsrc, rfile)) < 0) + return error; + + return diff_single_generate(pd, xo); +} + +static int patch_generated_with_delta_alloc( + patch_generated_with_delta **out, + const char **old_path, + const char **new_path) +{ + patch_generated_with_delta *pd; + size_t old_len = *old_path ? strlen(*old_path) : 0; + size_t new_len = *new_path ? strlen(*new_path) : 0; + size_t alloc_len; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + *out = pd = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(pd); + + pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED; + + if (*old_path) { + memcpy(&pd->paths[0], *old_path, old_len); + *old_path = &pd->paths[0]; + } else if (*new_path) + *old_path = &pd->paths[old_len + 1]; + + if (*new_path) { + memcpy(&pd->paths[old_len + 1], *new_path, new_len); + *new_path = &pd->paths[old_len + 1]; + } else if (*old_path) + *new_path = &pd->paths[0]; + + return 0; +} + +static int diff_from_sources( + git_diff_file_content_src *oldsrc, + git_diff_file_content_src *newsrc, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + int error = 0; + patch_generated_with_delta pd; + git_xdiff_output xo; + + memset(&xo, 0, sizeof(xo)); + diff_output_init( + &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); + git_xdiff_init(&xo, opts); + + memset(&pd, 0, sizeof(pd)); + + error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts); + + git_patch_free(&pd.patch.base); + + return error; +} + +static int patch_from_sources( + git_patch **out, + git_diff_file_content_src *oldsrc, + git_diff_file_content_src *newsrc, + const git_diff_options *opts) +{ + int error = 0; + patch_generated_with_delta *pd; + git_xdiff_output xo; + + GIT_ASSERT_ARG(out); + *out = NULL; + + if ((error = patch_generated_with_delta_alloc( + &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) + return error; + + memset(&xo, 0, sizeof(xo)); + diff_output_to_patch(&xo.output, &pd->patch); + git_xdiff_init(&xo, opts); + + if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts))) + *out = (git_patch *)pd; + else + git_patch_free((git_patch *)pd); + + return error; +} + +int git_diff_blobs( + const git_blob *old_blob, + const char *old_path, + const git_blob *new_blob, + const char *new_path, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); + return diff_from_sources( + &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); +} + +int git_patch_from_blobs( + git_patch **out, + const git_blob *old_blob, + const char *old_path, + const git_blob *new_blob, + const char *new_path, + const git_diff_options *opts) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); + return patch_from_sources(out, &osrc, &nsrc, opts); +} + +int git_diff_blob_to_buffer( + const git_blob *old_blob, + const char *old_path, + const char *buf, + size_t buflen, + const char *buf_path, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); + return diff_from_sources( + &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); +} + +int git_patch_from_blob_and_buffer( + git_patch **out, + const git_blob *old_blob, + const char *old_path, + const void *buf, + size_t buflen, + const char *buf_path, + const git_diff_options *opts) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); + return patch_from_sources(out, &osrc, &nsrc, opts); +} + +int git_diff_buffers( + const void *old_buf, + size_t old_len, + const char *old_path, + const void *new_buf, + size_t new_len, + const char *new_path, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); + return diff_from_sources( + &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); +} + +int git_patch_from_buffers( + git_patch **out, + const void *old_buf, + size_t old_len, + const char *old_path, + const void *new_buf, + size_t new_len, + const char *new_path, + const git_diff_options *opts) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); + return patch_from_sources(out, &osrc, &nsrc, opts); +} + +int git_patch_generated_from_diff( + git_patch **patch_ptr, git_diff *diff, size_t idx) +{ + int error = 0; + git_xdiff_output xo; + git_diff_delta *delta = NULL; + git_patch_generated *patch = NULL; + + if (patch_ptr) *patch_ptr = NULL; + + if (diff_required(diff, "git_patch_from_diff") < 0) + return -1; + + delta = git_vector_get(&diff->deltas, idx); + if (!delta) { + git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff"); + return GIT_ENOTFOUND; + } + + if (git_diff_delta__should_skip(&diff->opts, delta)) + return 0; + + /* don't load the patch data unless we need it for binary check */ + if (!patch_ptr && + ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || + (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) + return 0; + + if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0) + return error; + + memset(&xo, 0, sizeof(xo)); + diff_output_to_patch(&xo.output, patch); + git_xdiff_init(&xo, &diff->opts); + + error = patch_generated_invoke_file_callback(patch, &xo.output); + + if (!error) + error = patch_generated_create(patch, &xo.output); + + if (!error) { + /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ + /* TODO: and unload the file content */ + } + + if (error || !patch_ptr) + git_patch_free(&patch->base); + else + *patch_ptr = &patch->base; + + return error; +} + +git_diff_driver *git_patch_generated_driver(git_patch_generated *patch) +{ + /* ofile driver is representative for whole patch */ + return patch->ofile.driver; +} + +int git_patch_generated_old_data( + char **ptr, long *len, git_patch_generated *patch) +{ + if (patch->ofile.map.len > LONG_MAX || + patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { + git_error_set(GIT_ERROR_INVALID, "files too large for diff"); + return -1; + } + + *ptr = patch->ofile.map.data; + *len = (long)patch->ofile.map.len; + + return 0; +} + +int git_patch_generated_new_data( + char **ptr, long *len, git_patch_generated *patch) +{ + if (patch->ofile.map.len > LONG_MAX || + patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { + git_error_set(GIT_ERROR_INVALID, "files too large for diff"); + return -1; + } + + *ptr = patch->nfile.map.data; + *len = (long)patch->nfile.map.len; + + return 0; +} + +static int patch_generated_file_cb( + const git_diff_delta *delta, + float progress, + void *payload) +{ + GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload); + return 0; +} + +static int patch_generated_binary_cb( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *payload) +{ + git_patch *patch = payload; + + GIT_UNUSED(delta); + + memcpy(&patch->binary, binary, sizeof(git_diff_binary)); + + if (binary->old_file.data) { + patch->binary.old_file.data = git__malloc(binary->old_file.datalen); + GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data); + + memcpy((char *)patch->binary.old_file.data, + binary->old_file.data, binary->old_file.datalen); + } + + if (binary->new_file.data) { + patch->binary.new_file.data = git__malloc(binary->new_file.datalen); + GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data); + + memcpy((char *)patch->binary.new_file.data, + binary->new_file.data, binary->new_file.datalen); + } + + return 0; +} + +static int git_patch_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk_, + void *payload) +{ + git_patch_generated *patch = payload; + git_patch_hunk *hunk; + + GIT_UNUSED(delta); + + hunk = git_array_alloc(patch->base.hunks); + GIT_ERROR_CHECK_ALLOC(hunk); + + memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); + + patch->base.header_size += hunk_->header_len; + + hunk->line_start = git_array_size(patch->base.lines); + hunk->line_count = 0; + + return 0; +} + +static int patch_generated_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk_, + const git_diff_line *line_, + void *payload) +{ + git_patch_generated *patch = payload; + git_patch_hunk *hunk; + git_diff_line *line; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk_); + + hunk = git_array_last(patch->base.hunks); + GIT_ASSERT(hunk); /* programmer error if no hunk is available */ + + line = git_array_alloc(patch->base.lines); + GIT_ERROR_CHECK_ALLOC(line); + + memcpy(line, line_, sizeof(*line)); + + /* do some bookkeeping so we can provide old/new line numbers */ + + patch->base.content_size += line->content_len; + + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) + patch->base.content_size += 1; + else if (line->origin == GIT_DIFF_LINE_CONTEXT) { + patch->base.content_size += 1; + patch->base.context_size += line->content_len + 1; + } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + patch->base.context_size += line->content_len; + + hunk->line_count++; + + return 0; +} + +static void diff_output_init( + git_patch_generated_output *out, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + GIT_UNUSED(opts); + + memset(out, 0, sizeof(*out)); + + out->file_cb = file_cb; + out->binary_cb = binary_cb; + out->hunk_cb = hunk_cb; + out->data_cb = data_cb; + out->payload = payload; +} + +static void diff_output_to_patch( + git_patch_generated_output *out, git_patch_generated *patch) +{ + diff_output_init( + out, + NULL, + patch_generated_file_cb, + patch_generated_binary_cb, + git_patch_hunk_cb, + patch_generated_line_cb, + patch); +} diff --git a/src/libgit2/patch_generate.h b/src/libgit2/patch_generate.h new file mode 100644 index 000000000..56e3e9df4 --- /dev/null +++ b/src/libgit2/patch_generate.h @@ -0,0 +1,69 @@ +/* + * 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_patch_generate_h__ +#define INCLUDE_patch_generate_h__ + +#include "common.h" + +#include "diff.h" +#include "diff_file.h" +#include "patch.h" + +enum { + GIT_PATCH_GENERATED_ALLOCATED = (1 << 0), + GIT_PATCH_GENERATED_INITIALIZED = (1 << 1), + GIT_PATCH_GENERATED_LOADED = (1 << 2), + /* the two sides are different */ + GIT_PATCH_GENERATED_DIFFABLE = (1 << 3), + /* the difference between the two sides has been computed */ + GIT_PATCH_GENERATED_DIFFED = (1 << 4), + GIT_PATCH_GENERATED_FLATTENED = (1 << 5) +}; + +struct git_patch_generated { + struct git_patch base; + + git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ + size_t delta_index; + git_diff_file_content ofile; + git_diff_file_content nfile; + uint32_t flags; + git_pool flattened; +}; + +typedef struct git_patch_generated git_patch_generated; + +extern git_diff_driver *git_patch_generated_driver(git_patch_generated *); + +extern int git_patch_generated_old_data( + char **, long *, git_patch_generated *); +extern int git_patch_generated_new_data( + char **, long *, git_patch_generated *); +extern int git_patch_generated_from_diff( + git_patch **, git_diff *, size_t); + +typedef struct git_patch_generated_output git_patch_generated_output; + +struct git_patch_generated_output { + /* these callbacks are issued with the diff data */ + git_diff_file_cb file_cb; + git_diff_binary_cb binary_cb; + git_diff_hunk_cb hunk_cb; + git_diff_line_cb data_cb; + void *payload; + + /* this records the actual error in cases where it may be obscured */ + int error; + + /* this callback is used to do the diff and drive the other callbacks. + * see diff_xdiff.h for how to use this in practice for now. + */ + int (*diff_cb)(git_patch_generated_output *output, + git_patch_generated *patch); +}; + +#endif diff --git a/src/libgit2/patch_parse.c b/src/libgit2/patch_parse.c new file mode 100644 index 000000000..78cd96252 --- /dev/null +++ b/src/libgit2/patch_parse.c @@ -0,0 +1,1231 @@ +/* + * 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 "patch_parse.h" + +#include "git2/patch.h" +#include "patch.h" +#include "diff_parse.h" +#include "fs_path.h" + +typedef struct { + git_patch base; + + git_patch_parse_ctx *ctx; + + /* the paths from the `diff --git` header, these will be used if this is not + * a rename (and rename paths are specified) or if no `+++`/`---` line specify + * the paths. + */ + char *header_old_path, *header_new_path; + + /* renamed paths are precise and are not prefixed */ + char *rename_old_path, *rename_new_path; + + /* the paths given in `---` and `+++` lines */ + char *old_path, *new_path; + + /* the prefixes from the old/new paths */ + char *old_prefix, *new_prefix; +} git_patch_parsed; + +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, "\""); + size_t len; + + for (len = quoted; len < ctx->parse_ctx.line_len; len++) { + if (!quoted && git__isspace(ctx->parse_ctx.line[len])) + break; + else if (quoted && !inquote && ctx->parse_ctx.line[len] == '"') { + len++; + break; + } + + inquote = (!inquote && ctx->parse_ctx.line[len] == '\\'); + } + + return len; +} + +static int parse_header_path_buf(git_str *path, git_patch_parse_ctx *ctx, size_t path_len) +{ + int error; + + if ((error = git_str_put(path, ctx->parse_ctx.line, path_len)) < 0) + return error; + + git_parse_advance_chars(&ctx->parse_ctx, path_len); + + git_str_rtrim(path); + + if (path->size > 0 && path->ptr[0] == '"' && + (error = git_str_unquote(path)) < 0) + return error; + + git_fs_path_squash_slashes(path); + + 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_str path = GIT_STR_INIT; + int error; + + if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0) + goto out; + *out = git_str_detach(&path); + +out: + git_str_dispose(&path); + return error; +} + +static int parse_header_git_oldpath( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + git_str old_path = GIT_STR_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; + + patch->old_path = git_str_detach(&old_path); + +out: + git_str_dispose(&old_path); + return error; +} + +static int parse_header_git_newpath( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + git_str new_path = GIT_STR_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_str_detach(&new_path); + +out: + git_str_dispose(&new_path); + return error; +} + +static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) +{ + int64_t m; + + if ((git_parse_advance_digit(&m, &ctx->parse_ctx, 8)) < 0) + return git_parse_err("invalid file mode at line %"PRIuZ, ctx->parse_ctx.line_num); + + if (m > UINT16_MAX) + return -1; + + *mode = (uint16_t)m; + + return 0; +} + +static int parse_header_oid( + git_oid *oid, + uint16_t *oid_len, + git_patch_parse_ctx *ctx) +{ + size_t len; + + for (len = 0; len < ctx->parse_ctx.line_len && len < GIT_OID_HEXSZ; len++) { + if (!git__isxdigit(ctx->parse_ctx.line[len])) + break; + } + + if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || + git_oid_fromstrn(oid, ctx->parse_ctx.line, len) < 0) + return git_parse_err("invalid hex formatted object id at line %"PRIuZ, + ctx->parse_ctx.line_num); + + git_parse_advance_chars(&ctx->parse_ctx, len); + + *oid_len = (uint16_t)len; + + return 0; +} + +static int parse_header_git_index( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + char c; + + if (parse_header_oid(&patch->base.delta->old_file.id, + &patch->base.delta->old_file.id_abbrev, ctx) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, "..") < 0 || + parse_header_oid(&patch->base.delta->new_file.id, + &patch->base.delta->new_file.id_abbrev, ctx) < 0) + return -1; + + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ' ') { + uint16_t mode = 0; + + git_parse_advance_chars(&ctx->parse_ctx, 1); + + if (parse_header_mode(&mode, ctx) < 0) + return -1; + + if (!patch->base.delta->new_file.mode) + patch->base.delta->new_file.mode = mode; + + if (!patch->base.delta->old_file.mode) + patch->base.delta->old_file.mode = mode; + } + + return 0; +} + +static int parse_header_git_oldmode( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_mode(&patch->base.delta->old_file.mode, ctx); +} + +static int parse_header_git_newmode( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_mode(&patch->base.delta->new_file.mode, ctx); +} + +static int parse_header_git_deletedfilemode( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git__free((char *)patch->base.delta->new_file.path); + + patch->base.delta->new_file.path = NULL; + patch->base.delta->status = GIT_DELTA_DELETED; + patch->base.delta->nfiles = 1; + + return parse_header_mode(&patch->base.delta->old_file.mode, ctx); +} + +static int parse_header_git_newfilemode( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git__free((char *)patch->base.delta->old_file.path); + + patch->base.delta->old_file.path = NULL; + patch->base.delta->status = GIT_DELTA_ADDED; + patch->base.delta->nfiles = 1; + + return parse_header_mode(&patch->base.delta->new_file.mode, ctx); +} + +static int parse_header_rename( + char **out, + git_patch_parse_ctx *ctx) +{ + git_str path = GIT_STR_INIT; + + if (parse_header_path_buf(&path, ctx, header_path_len(ctx)) < 0) + return -1; + + /* Note: the `rename from` and `rename to` lines include the literal + * filename. They do *not* include the prefix. (Who needs consistency?) + */ + *out = git_str_detach(&path); + return 0; +} + +static int parse_header_renamefrom( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_RENAMED; + return parse_header_rename(&patch->rename_old_path, ctx); +} + +static int parse_header_renameto( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_RENAMED; + return parse_header_rename(&patch->rename_new_path, ctx); +} + +static int parse_header_copyfrom( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_COPIED; + return parse_header_rename(&patch->rename_old_path, ctx); +} + +static int parse_header_copyto( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_COPIED; + return parse_header_rename(&patch->rename_new_path, ctx); +} + +static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) +{ + int64_t val; + + if (git_parse_advance_digit(&val, &ctx->parse_ctx, 10) < 0) + return -1; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "%") < 0) + return -1; + + if (val < 0 || val > 100) + return -1; + + *out = (uint16_t)val; + return 0; +} + +static int parse_header_similarity( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) + return git_parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->parse_ctx.line_num); + + return 0; +} + +static int parse_header_dissimilarity( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + uint16_t dissimilarity; + + if (parse_header_percent(&dissimilarity, ctx) < 0) + return git_parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->parse_ctx.line_num); + + patch->base.delta->similarity = 100 - dissimilarity; + + return 0; +} + +static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_path(&patch->header_old_path, ctx) < 0) + return git_parse_err("corrupt old path in git diff header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + if (git_parse_advance_ws(&ctx->parse_ctx) < 0 || + parse_header_path(&patch->header_new_path, ctx) < 0) + return git_parse_err("corrupt new path in git diff header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + /* + * We cannot expect to be able to always parse paths correctly at this + * point. Due to the possibility of unquoted names, whitespaces in + * filenames and custom prefixes we have to allow that, though, and just + * proceed here. We then hope for the "---" and "+++" lines to fix that + * for us. + */ + if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1) && + !git_parse_ctx_contains(&ctx->parse_ctx, "\r\n", 2)) { + git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); + + git__free(patch->header_old_path); + patch->header_old_path = NULL; + git__free(patch->header_new_path); + patch->header_new_path = NULL; + } + + return 0; +} + +typedef enum { + STATE_START, + + STATE_DIFF, + STATE_FILEMODE, + STATE_MODE, + STATE_INDEX, + STATE_PATH, + + STATE_SIMILARITY, + STATE_RENAME, + STATE_COPY, + + STATE_END +} parse_header_state; + +typedef struct { + const char *str; + parse_header_state expected_state; + parse_header_state next_state; + int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); +} parse_header_transition; + +static const parse_header_transition transitions[] = { + /* Start */ + { "diff --git " , STATE_START, STATE_DIFF, parse_header_start }, + + { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode }, + { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode }, + { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode }, + { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode }, + + { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, + { "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_FILEMODE, STATE_PATH, parse_header_git_oldpath }, + { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, + { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, + { "Binary files " , STATE_INDEX, STATE_END, NULL }, + + { "similarity index " , STATE_END, STATE_SIMILARITY, parse_header_similarity }, + { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity }, + { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity }, + { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, + { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, + { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom }, + { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto }, + { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto }, + { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, + + /* Next patch */ + { "diff --git " , STATE_END, 0, NULL }, + { "@@ -" , STATE_END, 0, NULL }, + { "-- " , STATE_INDEX, 0, NULL }, + { "-- " , STATE_END, 0, NULL }, +}; + +static int parse_header_git( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + size_t i; + int error = 0; + parse_header_state state = STATE_START; + + /* Parse remaining header lines */ + for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { + bool found = false; + + if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') + break; + + for (i = 0; i < ARRAY_SIZE(transitions); i++) { + const parse_header_transition *transition = &transitions[i]; + size_t op_len = strlen(transition->str); + + if (transition->expected_state != state || + git__prefixcmp(ctx->parse_ctx.line, transition->str) != 0) + continue; + + state = transition->next_state; + + /* Do not advance if this is the patch separator */ + if (transition->fn == NULL) + goto done; + + git_parse_advance_chars(&ctx->parse_ctx, op_len); + + if ((error = transition->fn(patch, ctx)) < 0) + goto done; + + git_parse_advance_ws(&ctx->parse_ctx); + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "\n") < 0 || + ctx->parse_ctx.line_len > 0) { + error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + found = true; + break; + } + + if (!found) { + error = git_parse_err("invalid patch header at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto done; + } + } + + if (state != STATE_END) { + error = git_parse_err("unexpected header line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + +done: + return error; +} + +static int parse_int(int *out, git_patch_parse_ctx *ctx) +{ + int64_t num; + + if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) + return -1; + + *out = (int)num; + return 0; +} + +static int parse_hunk_header( + git_patch_hunk *hunk, + git_patch_parse_ctx *ctx) +{ + const char *header_start = ctx->parse_ctx.line; + char c; + + hunk->hunk.old_lines = 1; + hunk->hunk.new_lines = 1; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "@@ -") < 0 || + parse_int(&hunk->hunk.old_start, ctx) < 0) + goto fail; + + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { + if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || + parse_int(&hunk->hunk.old_lines, ctx) < 0) + goto fail; + } + + if (git_parse_advance_expected_str(&ctx->parse_ctx, " +") < 0 || + parse_int(&hunk->hunk.new_start, ctx) < 0) + goto fail; + + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { + if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || + parse_int(&hunk->hunk.new_lines, ctx) < 0) + goto fail; + } + + if (git_parse_advance_expected_str(&ctx->parse_ctx, " @@") < 0) + goto fail; + + git_parse_advance_line(&ctx->parse_ctx); + + if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) + goto fail; + + hunk->hunk.header_len = ctx->parse_ctx.line - header_start; + if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) + return git_parse_err("oversized patch hunk header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); + hunk->hunk.header[hunk->hunk.header_len] = '\0'; + + return 0; + +fail: + git_error_set(GIT_ERROR_PATCH, "invalid patch hunk header at line %"PRIuZ, + ctx->parse_ctx.line_num); + 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, + git_patch_parse_ctx *ctx) +{ + git_diff_line *line; + int error = 0; + + int oldlines = hunk->hunk.old_lines; + int newlines = hunk->hunk.new_lines; + int last_origin = 0; + + for (; + ctx->parse_ctx.remain_len > 1 && + (oldlines || newlines) && + !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; + + 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, + ctx->parse_ctx.line_num); + goto done; + } + + git_parse_peek(&c, &ctx->parse_ctx, 0); + + switch (c) { + case '\n': + prefix = 0; + /* fall through */ + + case ' ': + origin = GIT_DIFF_LINE_CONTEXT; + oldlines--; + newlines--; + break; + + case '-': + origin = GIT_DIFF_LINE_DELETION; + oldlines--; + new_lineno = -1; + break; + + case '+': + origin = GIT_DIFF_LINE_ADDITION; + newlines--; + 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; + } + + 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 - 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; + line->old_lineno = old_lineno; + line->new_lineno = new_lineno; + + hunk->line_count++; + + last_origin = origin; + } + + if (oldlines || newlines) { + error = git_parse_err( + "invalid patch hunk, expected %d old lines and %d new lines", + hunk->hunk.old_lines, hunk->hunk.new_lines); + goto done; + } + + /* + * 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. + */ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && + git_array_size(patch->base.lines) > 0) { + + line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); + + if (line->content_len < 1) { + error = git_parse_err("last line has no trailing newline"); + goto done; + } + + 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); + } + +done: + return error; +} + +static int parse_patch_header( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + int error = 0; + + for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { + /* This line is too short to be a patch header. */ + if (ctx->parse_ctx.line_len < 6) + continue; + + /* This might be a hunk header without a patch header, provide a + * sensible error message. */ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { + size_t line_num = ctx->parse_ctx.line_num; + git_patch_hunk hunk; + + /* If this cannot be parsed as a hunk header, it's just leading + * noise, continue. + */ + if (parse_hunk_header(&hunk, ctx) < 0) { + git_error_clear(); + continue; + } + + error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, + line_num); + goto done; + } + + /* This buffer is too short to contain a patch. */ + if (ctx->parse_ctx.remain_len < ctx->parse_ctx.line_len + 6) + break; + + /* A proper git patch */ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "diff --git ")) { + error = parse_header_git(patch, ctx); + goto done; + } + + error = 0; + continue; + } + + git_error_set(GIT_ERROR_PATCH, "no patch found"); + error = GIT_ENOTFOUND; + +done: + return error; +} + +static int parse_patch_binary_side( + git_diff_binary_file *binary, + git_patch_parse_ctx *ctx) +{ + git_diff_binary_t type = GIT_DIFF_BINARY_NONE; + git_str base85 = GIT_STR_INIT, decoded = GIT_STR_INIT; + int64_t len; + int error = 0; + + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { + type = GIT_DIFF_BINARY_LITERAL; + git_parse_advance_chars(&ctx->parse_ctx, 8); + } else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "delta ")) { + type = GIT_DIFF_BINARY_DELTA; + git_parse_advance_chars(&ctx->parse_ctx, 6); + } else { + error = git_parse_err( + "unknown binary delta type at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + if (git_parse_advance_digit(&len, &ctx->parse_ctx, 10) < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0 || len < 0) { + error = git_parse_err("invalid binary size at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + while (ctx->parse_ctx.line_len) { + char c; + size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; + + git_parse_peek(&c, &ctx->parse_ctx, 0); + + if (c == '\n') + break; + else if (c >= 'A' && c <= 'Z') + decoded_len = c - 'A' + 1; + else if (c >= 'a' && c <= 'z') + decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; + + if (!decoded_len) { + error = git_parse_err("invalid binary length at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + git_parse_advance_chars(&ctx->parse_ctx, 1); + + encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; + + 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; + } + + if ((error = git_str_decode_base85( + &decoded, ctx->parse_ctx.line, encoded_len, decoded_len)) < 0) + goto done; + + if (decoded.size - decoded_orig != decoded_len) { + error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + git_parse_advance_chars(&ctx->parse_ctx, encoded_len); + + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) { + error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + } + + binary->type = type; + binary->inflatedlen = (size_t)len; + binary->datalen = decoded.size; + binary->data = git_str_detach(&decoded); + +done: + git_str_dispose(&base85); + git_str_dispose(&decoded); + return error; +} + +static int parse_patch_binary( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + int error; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "GIT binary patch") < 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); + + /* parse old->new binary diff */ + if ((error = parse_patch_binary_side( + &patch->base.binary.new_file, ctx)) < 0) + return error; + + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary separator at line %"PRIuZ, + ctx->parse_ctx.line_num); + + /* parse new->old binary diff */ + if ((error = parse_patch_binary_side( + &patch->base.binary.old_file, ctx)) < 0) + return error; + + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary patch separator at line %"PRIuZ, + ctx->parse_ctx.line_num); + + patch->base.binary.contains_data = 1; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + return 0; +} + +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, 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; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + return 0; +} + +static int parse_patch_hunks( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git_patch_hunk *hunk; + int error = 0; + + while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { + hunk = git_array_alloc(patch->base.hunks); + GIT_ERROR_CHECK_ALLOC(hunk); + + memset(hunk, 0, sizeof(git_patch_hunk)); + + hunk->line_start = git_array_size(patch->base.lines); + hunk->line_count = 0; + + if ((error = parse_hunk_header(hunk, ctx)) < 0 || + (error = parse_hunk_body(patch, hunk, ctx)) < 0) + goto done; + } + + patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; + +done: + return error; +} + +static int parse_patch_body( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "GIT binary patch")) + return parse_patch_binary(patch, ctx); + else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "Binary files ")) + return parse_patch_binary_nodata(patch, ctx); + else + return parse_patch_hunks(patch, ctx); +} + +static int check_header_names( + const char *one, + const char *two, + const char *old_or_new, + bool two_null) +{ + if (!one || !two) + return 0; + + if (two_null && strcmp(two, "/dev/null") != 0) + return git_parse_err("expected %s path of '/dev/null'", old_or_new); + + else if (!two_null && strcmp(one, two) != 0) + return git_parse_err("mismatched %s path names", old_or_new); + + return 0; +} + +static int check_prefix( + char **out, + size_t *out_len, + git_patch_parsed *patch, + const char *path_start) +{ + const char *path = path_start; + size_t prefix_len = patch->ctx->opts.prefix_len; + size_t remain_len = prefix_len; + + *out = NULL; + *out_len = 0; + + if (prefix_len == 0) + goto done; + + /* leading slashes do not count as part of the prefix in git apply */ + while (*path == '/') + path++; + + while (*path && remain_len) { + if (*path == '/') + remain_len--; + + path++; + } + + if (remain_len || !*path) + return git_parse_err( + "header filename does not contain %"PRIuZ" path components", + prefix_len); + +done: + *out_len = (path - path_start); + *out = git__strndup(path_start, *out_len); + + return (*out == NULL) ? -1 : 0; +} + +static int check_filenames(git_patch_parsed *patch) +{ + const char *prefixed_new, *prefixed_old; + size_t old_prefixlen = 0, new_prefixlen = 0; + bool added = (patch->base.delta->status == GIT_DELTA_ADDED); + bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); + + if (patch->old_path && !patch->new_path) + return git_parse_err("missing new path"); + + if (!patch->old_path && patch->new_path) + return git_parse_err("missing old path"); + + /* Ensure (non-renamed) paths match */ + if (check_header_names(patch->header_old_path, patch->old_path, "old", added) < 0 || + check_header_names(patch->header_new_path, patch->new_path, "new", deleted) < 0) + return -1; + + prefixed_old = (!added && patch->old_path) ? patch->old_path : patch->header_old_path; + prefixed_new = (!deleted && patch->new_path) ? patch->new_path : patch->header_new_path; + + if ((prefixed_old && check_prefix(&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0) || + (prefixed_new && check_prefix(&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)) + return -1; + + /* 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 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 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) + return git_parse_err("git diff header lacks old / new paths"); + + return 0; +} + +static int check_patch(git_patch_parsed *patch) +{ + git_diff_delta *delta = patch->base.delta; + + if (check_filenames(patch) < 0) + return -1; + + if (delta->old_file.path && + delta->status != GIT_DELTA_DELETED && + !delta->new_file.mode) + delta->new_file.mode = delta->old_file.mode; + + if (delta->status == GIT_DELTA_MODIFIED && + !(delta->flags & GIT_DIFF_FLAG_BINARY) && + delta->new_file.mode == delta->old_file.mode && + git_array_size(patch->base.hunks) == 0) + return git_parse_err("patch with no hunks"); + + if (delta->status == GIT_DELTA_ADDED) { + memset(&delta->old_file.id, 0x0, sizeof(git_oid)); + delta->old_file.id_abbrev = 0; + } + + if (delta->status == GIT_DELTA_DELETED) { + memset(&delta->new_file.id, 0x0, sizeof(git_oid)); + delta->new_file.id_abbrev = 0; + } + + return 0; +} + +git_patch_parse_ctx *git_patch_parse_ctx_init( + const char *content, + size_t content_len, + const git_patch_options *opts) +{ + git_patch_parse_ctx *ctx; + git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; + + if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) + return NULL; + + if ((git_parse_ctx_init(&ctx->parse_ctx, content, content_len)) < 0) { + git__free(ctx); + return NULL; + } + + if (opts) + memcpy(&ctx->opts, opts, sizeof(git_patch_options)); + else + memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options)); + + GIT_REFCOUNT_INC(ctx); + return ctx; +} + +static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) +{ + if (!ctx) + return; + + git_parse_ctx_clear(&ctx->parse_ctx); + git__free(ctx); +} + +void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx) +{ + GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free); +} + +int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) +{ + git_diff_parsed *diff = (git_diff_parsed *)d; + git_patch *p; + + if ((p = git_vector_get(&diff->patches, idx)) == NULL) + return -1; + + GIT_REFCOUNT_INC(p); + *out = p; + + return 0; +} + +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; + + git_patch_parse_ctx_free(patch->ctx); + + 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); + + git__free(patch->old_prefix); + git__free(patch->new_prefix); + git__free(patch->header_old_path); + git__free(patch->header_new_path); + git__free(patch->rename_old_path); + git__free(patch->rename_new_path); + git__free(patch->old_path); + git__free(patch->new_path); + git__free(patch); +} + +int git_patch_parse( + git_patch **out, + git_patch_parse_ctx *ctx) +{ + git_patch_parsed *patch; + size_t start, used; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ctx); + + *out = NULL; + + patch = git__calloc(1, sizeof(git_patch_parsed)); + GIT_ERROR_CHECK_ALLOC(patch); + + patch->ctx = ctx; + GIT_REFCOUNT_INC(patch->ctx); + + patch->base.free_fn = patch_parsed__free; + + patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); + GIT_ERROR_CHECK_ALLOC(patch->base.delta); + + patch->base.delta->status = GIT_DELTA_MODIFIED; + patch->base.delta->nfiles = 2; + + start = ctx->parse_ctx.remain_len; + + if ((error = parse_patch_header(patch, ctx)) < 0 || + (error = parse_patch_body(patch, ctx)) < 0 || + (error = check_patch(patch)) < 0) + goto done; + + used = start - ctx->parse_ctx.remain_len; + ctx->parse_ctx.remain += used; + + patch->base.diff_opts.old_prefix = patch->old_prefix; + patch->base.diff_opts.new_prefix = patch->new_prefix; + patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; + + GIT_REFCOUNT_INC(&patch->base); + *out = &patch->base; + +done: + if (error < 0) + patch_parsed__free(&patch->base); + + return error; +} + +int git_patch_from_buffer( + git_patch **out, + const char *content, + size_t content_len, + const git_patch_options *opts) +{ + git_patch_parse_ctx *ctx; + int error; + + ctx = git_patch_parse_ctx_init(content, content_len, opts); + GIT_ERROR_CHECK_ALLOC(ctx); + + error = git_patch_parse(out, ctx); + + git_patch_parse_ctx_free(ctx); + return error; +} + diff --git a/src/libgit2/patch_parse.h b/src/libgit2/patch_parse.h new file mode 100644 index 000000000..140629da8 --- /dev/null +++ b/src/libgit2/patch_parse.h @@ -0,0 +1,51 @@ +/* + * 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_patch_parse_h__ +#define INCLUDE_patch_parse_h__ + +#include "common.h" + +#include "parse.h" +#include "patch.h" + +typedef struct { + git_refcount rc; + + git_patch_options opts; + + git_parse_ctx parse_ctx; +} git_patch_parse_ctx; + +extern git_patch_parse_ctx *git_patch_parse_ctx_init( + const char *content, + size_t content_len, + const git_patch_options *opts); + +extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx); + +/** + * Create a patch for a single file from the contents of a patch buffer. + * + * @param out The patch to be created + * @param contents The contents of a patch file + * @param contents_len The length of the patch file + * @param opts The git_patch_options + * @return 0 on success, <0 on failure. + */ +extern int git_patch_from_buffer( + git_patch **out, + const char *contents, + size_t contents_len, + const git_patch_options *opts); + +extern int git_patch_parse( + git_patch **out, + git_patch_parse_ctx *ctx); + +extern int git_patch_parsed_from_diff(git_patch **, git_diff *, size_t); + +#endif diff --git a/src/libgit2/path.c b/src/libgit2/path.c new file mode 100644 index 000000000..a19340efe --- /dev/null +++ b/src/libgit2/path.c @@ -0,0 +1,375 @@ +/* + * 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 "path.h" + +#include "repository.h" +#include "fs_path.h" +#include "utf8.h" + +typedef struct { + git_repository *repo; + uint16_t file_mode; + unsigned int flags; +} repository_path_validate_data; + +static int32_t next_hfs_char(const char **in, size_t *len) +{ + while (*len) { + uint32_t codepoint; + int cp_len = git_utf8_iterate(&codepoint, *in, *len); + if (cp_len < 0) + return -1; + + (*in) += cp_len; + (*len) -= cp_len; + + /* these code points are ignored completely */ + switch (codepoint) { + case 0x200c: /* ZERO WIDTH NON-JOINER */ + case 0x200d: /* ZERO WIDTH JOINER */ + case 0x200e: /* LEFT-TO-RIGHT MARK */ + case 0x200f: /* RIGHT-TO-LEFT MARK */ + case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ + case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ + case 0x202c: /* POP DIRECTIONAL FORMATTING */ + case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ + case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ + case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ + case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ + case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ + case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ + case 0x206e: /* NATIONAL DIGIT SHAPES */ + case 0x206f: /* NOMINAL DIGIT SHAPES */ + case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ + continue; + } + + /* fold into lowercase -- this will only fold characters in + * the ASCII range, which is perfectly fine, because the + * git folder name can only be composed of ascii characters + */ + return git__tolower((int)codepoint); + } + return 0; /* NULL byte -- end of string */ +} + +static bool validate_dotgit_hfs_generic( + const char *path, + size_t len, + const char *needle, + size_t needle_len) +{ + size_t i; + char c; + + if (next_hfs_char(&path, &len) != '.') + return true; + + for (i = 0; i < needle_len; i++) { + c = next_hfs_char(&path, &len); + if (c != needle[i]) + return true; + } + + if (next_hfs_char(&path, &len) != '\0') + return true; + + return false; +} + +static bool validate_dotgit_hfs(const char *path, size_t len) +{ + return validate_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git")); +} + +GIT_INLINE(bool) validate_dotgit_ntfs( + git_repository *repo, + const char *path, + size_t len) +{ + git_str *reserved = git_repository__reserved_names_win32; + size_t reserved_len = git_repository__reserved_names_win32_len; + size_t start = 0, i; + + if (repo) + git_repository__reserved_names(&reserved, &reserved_len, repo, true); + + for (i = 0; i < reserved_len; i++) { + git_str *r = &reserved[i]; + + if (len >= r->size && + strncasecmp(path, r->ptr, r->size) == 0) { + start = r->size; + break; + } + } + + if (!start) + return true; + + /* + * Reject paths that start with Windows-style directory separators + * (".git\") or NTFS alternate streams (".git:") and could be used + * to write to the ".git" directory on Windows platforms. + */ + if (path[start] == '\\' || path[start] == ':') + return false; + + /* Reject paths like '.git ' or '.git.' */ + for (i = start; i < len; i++) { + if (path[i] != ' ' && path[i] != '.') + return true; + } + + return false; +} + +/* + * Windows paths that end with spaces and/or dots are elided to the + * path without them for backward compatibility. That is to say + * that opening file "foo ", "foo." or even "foo . . ." will all + * map to a filename of "foo". This function identifies spaces and + * dots at the end of a filename, whether the proper end of the + * filename (end of string) or a colon (which would indicate a + * Windows alternate data stream.) + */ +GIT_INLINE(bool) ntfs_end_of_filename(const char *path) +{ + const char *c = path; + + for (;; c++) { + if (*c == '\0' || *c == ':') + return true; + if (*c != ' ' && *c != '.') + return false; + } + + return true; +} + +GIT_INLINE(bool) validate_dotgit_ntfs_generic( + const char *name, + size_t len, + const char *dotgit_name, + size_t dotgit_len, + const char *shortname_pfix) +{ + int i, saw_tilde; + + if (name[0] == '.' && len >= dotgit_len && + !strncasecmp(name + 1, dotgit_name, dotgit_len)) { + return !ntfs_end_of_filename(name + dotgit_len + 1); + } + + /* Detect the basic NTFS shortname with the first six chars */ + if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' && + name[7] >= '1' && name[7] <= '4') + return !ntfs_end_of_filename(name + 8); + + /* Catch fallback names */ + for (i = 0, saw_tilde = 0; i < 8; i++) { + if (name[i] == '\0') { + return true; + } else if (saw_tilde) { + if (name[i] < '0' || name[i] > '9') + return true; + } else if (name[i] == '~') { + if (name[i+1] < '1' || name[i+1] > '9') + return true; + saw_tilde = 1; + } else if (i >= 6) { + return true; + } else if ((unsigned char)name[i] > 127) { + return true; + } else if (git__tolower(name[i]) != shortname_pfix[i]) { + return true; + } + } + + return !ntfs_end_of_filename(name + i); +} + +/* + * Return the length of the common prefix between str and prefix, comparing them + * case-insensitively (must be ASCII to match). + */ +GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix) +{ + size_t count = 0; + + while (len > 0 && tolower(*str) == tolower(*prefix)) { + count++; + str++; + prefix++; + len--; + } + + return count; +} + +static bool validate_repo_component( + const char *component, + size_t len, + void *payload) +{ + repository_path_validate_data *data = (repository_path_validate_data *)payload; + + if (data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) { + if (!validate_dotgit_hfs(component, len)) + return false; + + if (S_ISLNK(data->file_mode) && + git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)) + return false; + } + + if (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) { + if (!validate_dotgit_ntfs(data->repo, component, len)) + return false; + + if (S_ISLNK(data->file_mode) && + git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS)) + return false; + } + + /* don't bother rerunning the `.git` test if we ran the HFS or NTFS + * specific tests, they would have already rejected `.git`. + */ + if ((data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && + (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && + (data->flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) { + if (len >= 4 && + component[0] == '.' && + (component[1] == 'g' || component[1] == 'G') && + (component[2] == 'i' || component[2] == 'I') && + (component[3] == 't' || component[3] == 'T')) { + if (len == 4) + return false; + + if (S_ISLNK(data->file_mode) && + common_prefix_icase(component, len, ".gitmodules") == len) + return false; + } + } + + return true; +} + +GIT_INLINE(unsigned int) dotgit_flags( + git_repository *repo, + unsigned int flags) +{ + int protectHFS = 0, protectNTFS = 1; + int error = 0; + + flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; + +#ifdef __APPLE__ + protectHFS = 1; +#endif + + if (repo && !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__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS); + if (!error && protectNTFS) + flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; + + return flags; +} + +GIT_INLINE(unsigned int) length_flags( + git_repository *repo, + unsigned int flags) +{ +#ifdef GIT_WIN32 + int allow = 0; + + if (repo && + git_repository__configmap_lookup(&allow, repo, GIT_CONFIGMAP_LONGPATHS) < 0) + allow = 0; + + if (allow) + flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; + +#else + GIT_UNUSED(repo); + flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; +#endif + + return flags; +} + +bool git_path_str_is_valid( + git_repository *repo, + const git_str *path, + uint16_t file_mode, + unsigned int flags) +{ + repository_path_validate_data data = {0}; + + /* Upgrade the ".git" checks based on platform */ + if ((flags & GIT_PATH_REJECT_DOT_GIT)) + flags = dotgit_flags(repo, flags); + + /* Update the length checks based on platform */ + if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS)) + flags = length_flags(repo, flags); + + data.repo = repo; + data.file_mode = file_mode; + data.flags = flags; + + return git_fs_path_str_is_valid_ext(path, flags, NULL, validate_repo_component, NULL, &data); +} + +static const struct { + const char *file; + const char *hash; + size_t filelen; +} gitfiles[] = { + { "gitignore", "gi250a", CONST_STRLEN("gitignore") }, + { "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") }, + { "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") } +}; + +extern int git_path_is_gitfile( + const char *path, + size_t pathlen, + git_path_gitfile gitfile, + git_path_fs fs) +{ + const char *file, *hash; + size_t filelen; + + if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) { + git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation"); + return -1; + } + + file = gitfiles[gitfile].file; + filelen = gitfiles[gitfile].filelen; + hash = gitfiles[gitfile].hash; + + switch (fs) { + case GIT_PATH_FS_GENERIC: + return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) || + !validate_dotgit_hfs_generic(path, pathlen, file, filelen); + case GIT_PATH_FS_NTFS: + return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash); + case GIT_PATH_FS_HFS: + return !validate_dotgit_hfs_generic(path, pathlen, file, filelen); + default: + git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation"); + return -1; + } +} + diff --git a/src/libgit2/path.h b/src/libgit2/path.h new file mode 100644 index 000000000..c4a2c4250 --- /dev/null +++ b/src/libgit2/path.h @@ -0,0 +1,68 @@ +/* + * 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_path_h__ +#define INCLUDE_path_h__ + +#include "common.h" + +#include "fs_path.h" +#include + +#define GIT_PATH_REJECT_DOT_GIT (GIT_FS_PATH_REJECT_MAX << 1) +#define GIT_PATH_REJECT_DOT_GIT_LITERAL (GIT_FS_PATH_REJECT_MAX << 2) +#define GIT_PATH_REJECT_DOT_GIT_HFS (GIT_FS_PATH_REJECT_MAX << 3) +#define GIT_PATH_REJECT_DOT_GIT_NTFS (GIT_FS_PATH_REJECT_MAX << 4) + +/* Paths that should never be written into the working directory. */ +#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \ + GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT + +/* Paths that should never be written to the index. */ +#define GIT_PATH_REJECT_INDEX_DEFAULTS \ + GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT + +extern bool git_path_str_is_valid( + git_repository *repo, + const git_str *path, + uint16_t file_mode, + unsigned int flags); + +GIT_INLINE(bool) git_path_is_valid( + git_repository *repo, + const char *path, + uint16_t file_mode, + unsigned int flags) +{ + git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_path_str_is_valid(repo, &str, file_mode, flags); +} + +GIT_INLINE(int) git_path_validate_str_length( + git_repository *repo, + const git_str *path) +{ + if (!git_path_str_is_valid(repo, path, 0, GIT_FS_PATH_REJECT_LONG_PATHS)) { + if (path->size == SIZE_MAX) + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path->ptr); + else + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", (int)path->size, path->ptr); + + return -1; + } + + return 0; +} + +GIT_INLINE(int) git_path_validate_length( + git_repository *repo, + const char *path) +{ + git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_path_validate_str_length(repo, &str); +} + +#endif diff --git a/src/libgit2/pathspec.c b/src/libgit2/pathspec.c new file mode 100644 index 000000000..3e44643c6 --- /dev/null +++ b/src/libgit2/pathspec.c @@ -0,0 +1,722 @@ +/* + * 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 "pathspec.h" + +#include "git2/pathspec.h" +#include "git2/diff.h" +#include "attr_file.h" +#include "iterator.h" +#include "repository.h" +#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) +{ + git_str prefix = GIT_STR_INIT; + const char *scan; + + if (!pathspec || !pathspec->count || + git_str_common_prefix(&prefix, pathspec->strings, pathspec->count) < 0) + return NULL; + + /* diff prefix will only be leading non-wildcards */ + for (scan = prefix.ptr; *scan; ++scan) { + if (git__iswildcard(*scan) && + (scan == prefix.ptr || (*(scan - 1) != '\\'))) + break; + } + git_str_truncate(&prefix, scan - prefix.ptr); + + if (prefix.size <= 0) { + git_str_dispose(&prefix); + return NULL; + } + + git_str_unescape(&prefix); + + return git_str_detach(&prefix); +} + +/* is there anything in the spec that needs to be filtered on */ +bool git_pathspec_is_empty(const git_strarray *pathspec) +{ + size_t i; + + if (pathspec == NULL) + return true; + + for (i = 0; i < pathspec->count; ++i) { + const char *str = pathspec->strings[i]; + + if (str && str[0]) + return false; + } + + return true; +} + +/* build a vector of fnmatch patterns to evaluate efficiently */ +int git_pathspec__vinit( + git_vector *vspec, const git_strarray *strspec, git_pool *strpool) +{ + size_t i; + + memset(vspec, 0, sizeof(*vspec)); + + if (git_pathspec_is_empty(strspec)) + return 0; + + if (git_vector_init(vspec, strspec->count, NULL) < 0) + return -1; + + for (i = 0; i < strspec->count; ++i) { + int ret; + const char *pattern = strspec->strings[i]; + git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); + if (!match) + return -1; + + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; + + ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); + if (ret == GIT_ENOTFOUND) { + git__free(match); + continue; + } else if (ret < 0) { + git__free(match); + return ret; + } + + if (git_vector_insert(vspec, match) < 0) + return -1; + } + + return 0; +} + +/* free data from the pathspec vector */ +void git_pathspec__vfree(git_vector *vspec) +{ + git_vector_free_deep(vspec); +} + +struct pathspec_match_context { + int wildmatch_flags; + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); +}; + +static void pathspec_match_context_init( + struct pathspec_match_context *ctxt, + bool disable_fnmatch, + bool casefold) +{ + if (disable_fnmatch) + ctxt->wildmatch_flags = -1; + else if (casefold) + ctxt->wildmatch_flags = WM_CASEFOLD; + else + ctxt->wildmatch_flags = 0; + + if (casefold) { + ctxt->strcomp = git__strcasecmp; + ctxt->strncomp = git__strncasecmp; + } else { + ctxt->strcomp = git__strcmp; + ctxt->strncomp = git__strncmp; + } +} + +static int pathspec_match_one( + const git_attr_fnmatch *match, + struct pathspec_match_context *ctxt, + const char *path) +{ + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; + + if (result == WM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; + + 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 == WM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + ctxt->strncomp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + /* if we didn't match and this is a negative match, check for exact + * match of filename with leading '!' + */ + if (result == WM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && + *path == '!' && + ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && + (!path[match->length + 1] || path[match->length + 1] == '/')) + return 1; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; + return -1; +} + +static int git_pathspec__match_at( + size_t *matched_at, + const git_vector *vspec, + struct pathspec_match_context *ctxt, + const char *path0, + const char *path1) +{ + int result = GIT_ENOTFOUND; + size_t i = 0; + const git_attr_fnmatch *match; + + git_vector_foreach(vspec, i, match) { + if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) + break; + if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) + break; + } + + *matched_at = i; + return result; +} + +/* match a path against the vectorized pathspec */ +bool git_pathspec__match( + const git_vector *vspec, + const char *path, + bool disable_fnmatch, + bool casefold, + const char **matched_pathspec, + size_t *matched_at) +{ + int result; + size_t pos; + struct pathspec_match_context ctxt; + + if (matched_pathspec) + *matched_pathspec = NULL; + if (matched_at) + *matched_at = GIT_PATHSPEC_NOMATCH; + + if (!vspec || !vspec->length) + return true; + + pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); + + result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); + if (result >= 0) { + if (matched_pathspec) { + const git_attr_fnmatch *match = git_vector_get(vspec, pos); + *matched_pathspec = match->pattern; + } + + if (matched_at) + *matched_at = pos; + } + + return (result > 0); +} + + +int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) +{ + int error = 0; + + memset(ps, 0, sizeof(*ps)); + + ps->prefix = git_pathspec_prefix(paths); + + if ((error = git_pool_init(&ps->pool, 1)) < 0 || + (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) + git_pathspec__clear(ps); + + return error; +} + +void git_pathspec__clear(git_pathspec *ps) +{ + git__free(ps->prefix); + git_pathspec__vfree(&ps->pathspec); + git_pool_clear(&ps->pool); + memset(ps, 0, sizeof(*ps)); +} + +int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) +{ + int error = 0; + git_pathspec *ps = git__malloc(sizeof(git_pathspec)); + GIT_ERROR_CHECK_ALLOC(ps); + + if ((error = git_pathspec__init(ps, pathspec)) < 0) { + git__free(ps); + return error; + } + + GIT_REFCOUNT_INC(ps); + *out = ps; + return 0; +} + +static void pathspec_free(git_pathspec *ps) +{ + git_pathspec__clear(ps); + git__free(ps); +} + +void git_pathspec_free(git_pathspec *ps) +{ + if (!ps) + return; + GIT_REFCOUNT_DEC(ps, pathspec_free); +} + +int git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path) +{ + bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; + bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; + + GIT_ASSERT_ARG(ps); + GIT_ASSERT_ARG(path); + + return (0 != git_pathspec__match( + &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); +} + +static void pathspec_match_free(git_pathspec_match_list *m) +{ + if (!m) + return; + + git_pathspec_free(m->pathspec); + m->pathspec = NULL; + + git_array_clear(m->matches); + git_array_clear(m->failures); + git_pool_clear(&m->pool); + git__free(m); +} + +static git_pathspec_match_list *pathspec_match_alloc( + git_pathspec *ps, int datatype) +{ + git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); + if (!m) + return NULL; + + if (git_pool_init(&m->pool, 1) < 0) + return NULL; + + /* need to keep reference to pathspec and increment refcount because + * failures array stores pointers to the pattern strings of the + * pathspec that had no matches + */ + GIT_REFCOUNT_INC(ps); + m->pathspec = ps; + m->datatype = datatype; + + return m; +} + +GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) +{ + if (!git_bitvec_get(used, pos)) { + git_bitvec_set(used, pos, true); + return 1; + } + + return 0; +} + +static size_t pathspec_mark_remaining( + git_bitvec *used, + git_vector *patterns, + struct pathspec_match_context *ctxt, + size_t start, + const char *path0, + const char *path1) +{ + size_t count = 0; + + if (path1 == path0) + path1 = NULL; + + for (; start < patterns->length; ++start) { + const git_attr_fnmatch *pat = git_vector_get(patterns, start); + + if (git_bitvec_get(used, start)) + continue; + + if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) + count += pathspec_mark_pattern(used, start); + else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) + count += pathspec_mark_pattern(used, start); + } + + return count; +} + +static int pathspec_build_failure_array( + git_pathspec_string_array_t *failures, + git_vector *patterns, + git_bitvec *used, + git_pool *pool) +{ + size_t pos; + char **failed; + const git_attr_fnmatch *pat; + + for (pos = 0; pos < patterns->length; ++pos) { + if (git_bitvec_get(used, pos)) + continue; + + if ((failed = git_array_alloc(*failures)) == NULL) + return -1; + + pat = git_vector_get(patterns, pos); + + if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) + return -1; + } + + return 0; +} + +static int pathspec_match_from_iterator( + git_pathspec_match_list **out, + git_iterator *iter, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + const git_index_entry *entry = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t pos, used_ct = 0, found_files = 0; + git_index *index = NULL; + git_bitvec used_patterns; + char **file; + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); + GIT_ERROR_CHECK_ALLOC(m); + } + + if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) + goto done; + + if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR && + (error = git_repository_index__weakptr( + &index, git_iterator_owner(iter))) < 0) + goto done; + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_iterator_ignore_case(iter)); + + while (!(error = git_iterator_advance(&entry, iter))) { + /* search for match with entry->path */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, entry->path, NULL); + + /* no matches for this path */ + if (result < 0) + continue; + + /* if result was a negative pattern match, then don't list file */ + if (!result) { + used_ct += pathspec_mark_pattern(&used_patterns, pos); + continue; + } + + /* check if path is ignored and untracked */ + if (index != NULL && + git_iterator_current_is_ignored(iter) && + git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + ++found_files; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched path into matches array */ + if ((file = (char **)git_array_alloc(m->matches)) == NULL || + (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { + error = -1; + goto done; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto done; + error = 0; + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { + git_error_set(GIT_ERROR_INVALID, "no matching files were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) +{ + git_iterator_flag_t f = 0; + + if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) + f |= GIT_ITERATOR_IGNORE_CASE; + else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) + f |= GIT_ITERATOR_DONT_IGNORE_CASE; + + return f; +} + +int git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; + + GIT_ASSERT_ARG(repo); + + iter_opts.flags = pathspec_match_iter_flags(flags); + + if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { + error = pathspec_match_from_iterator(out, iter, flags, ps); + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; + + GIT_ASSERT_ARG(index); + + iter_opts.flags = pathspec_match_iter_flags(flags); + + if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) { + error = pathspec_match_from_iterator(out, iter, flags, ps); + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; + + GIT_ASSERT_ARG(tree); + + iter_opts.flags = pathspec_match_iter_flags(flags); + + if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { + error = pathspec_match_from_iterator(out, iter, flags, ps); + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff *diff, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t i, pos, used_ct = 0, found_deltas = 0; + const git_diff_delta *delta, **match; + git_bitvec used_patterns; + + GIT_ASSERT_ARG(diff); + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); + GIT_ERROR_CHECK_ALLOC(m); + } + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_diff_is_sorted_icase(diff)); + + git_vector_foreach(&diff->deltas, i, delta) { + /* search for match with delta */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); + + /* no matches for this path */ + if (result < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + + /* if result was a negative pattern match, then don't list file */ + if (!result) + continue; + + ++found_deltas; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, + delta->old_file.path, delta->new_file.path); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched delta into matches array */ + if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { + error = -1; + goto done; + } else { + *match = delta; + } + } + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { + git_error_set(GIT_ERROR_INVALID, "no matching deltas were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +void git_pathspec_match_list_free(git_pathspec_match_list *m) +{ + if (m) + pathspec_match_free(m); +} + +size_t git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->matches) : 0; +} + +const char *git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const char **)git_array_get(m->matches, pos)); +} + +const git_diff_delta *git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const git_diff_delta **)git_array_get(m->matches, pos)); +} + +size_t git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->failures) : 0; +} + +const char * git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos) +{ + char **entry = m ? git_array_get(m->failures, pos) : NULL; + + return entry ? *entry : NULL; +} diff --git a/src/libgit2/pathspec.h b/src/libgit2/pathspec.h new file mode 100644 index 000000000..0256cb927 --- /dev/null +++ b/src/libgit2/pathspec.h @@ -0,0 +1,76 @@ +/* + * 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_pathspec_h__ +#define INCLUDE_pathspec_h__ + +#include "common.h" + +#include "git2/pathspec.h" +#include "str.h" +#include "vector.h" +#include "pool.h" +#include "array.h" + +/* public compiled pathspec */ +struct git_pathspec { + git_refcount rc; + char *prefix; + git_vector pathspec; + git_pool pool; +}; + +enum { + PATHSPEC_DATATYPE_STRINGS = 0, + PATHSPEC_DATATYPE_DIFF = 1 +}; + +typedef git_array_t(char *) git_pathspec_string_array_t; + +/* public interface to pathspec matching */ +struct git_pathspec_match_list { + git_pathspec *pathspec; + git_array_t(void *) matches; + git_pathspec_string_array_t failures; + git_pool pool; + int datatype; +}; + +/* what is the common non-wildcard prefix for all items in the pathspec */ +extern char *git_pathspec_prefix(const git_strarray *pathspec); + +/* is there anything in the spec that needs to be filtered on */ +extern bool git_pathspec_is_empty(const git_strarray *pathspec); + +/* build a vector of fnmatch patterns to evaluate efficiently */ +extern int git_pathspec__vinit( + git_vector *vspec, const git_strarray *strspec, git_pool *strpool); + +/* free data from the pathspec vector */ +extern void git_pathspec__vfree(git_vector *vspec); + +#define GIT_PATHSPEC_NOMATCH ((size_t)-1) + +/* + * Match a path against the vectorized pathspec. + * The matched pathspec is passed back into the `matched_pathspec` parameter, + * unless it is passed as NULL by the caller. + */ +extern bool git_pathspec__match( + const git_vector *vspec, + const char *path, + bool disable_fnmatch, + bool casefold, + const char **matched_pathspec, + size_t *matched_at); + +/* easy pathspec setup */ + +extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); + +extern void git_pathspec__clear(git_pathspec *ps); + +#endif diff --git a/src/libgit2/proxy.c b/src/libgit2/proxy.c new file mode 100644 index 000000000..ef91ad6ea --- /dev/null +++ b/src/libgit2/proxy.c @@ -0,0 +1,49 @@ +/* + * 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 "proxy.h" + +#include "git2/proxy.h" + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_proxy_init_options(git_proxy_options *opts, unsigned int version) +{ + return git_proxy_options_init(opts, version); +} +#endif + +int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) +{ + if (!src) { + git_proxy_options_init(tgt, GIT_PROXY_OPTIONS_VERSION); + return 0; + } + + memcpy(tgt, src, sizeof(git_proxy_options)); + if (src->url) { + tgt->url = git__strdup(src->url); + GIT_ERROR_CHECK_ALLOC(tgt->url); + } + + return 0; +} + +void git_proxy_options_dispose(git_proxy_options *opts) +{ + if (!opts) + return; + + git__free((char *) opts->url); + opts->url = NULL; +} diff --git a/src/libgit2/proxy.h b/src/libgit2/proxy.h new file mode 100644 index 000000000..7c0ab598d --- /dev/null +++ b/src/libgit2/proxy.h @@ -0,0 +1,17 @@ +/* +* 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_proxy_h__ +#define INCLUDE_proxy_h__ + +#include "common.h" + +#include "git2/proxy.h" + +extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); +extern void git_proxy_options_dispose(git_proxy_options *opts); + +#endif diff --git a/src/libgit2/push.c b/src/libgit2/push.c new file mode 100644 index 000000000..da8aebadd --- /dev/null +++ b/src/libgit2/push.c @@ -0,0 +1,558 @@ +/* + * 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 "push.h" + +#include "git2.h" + +#include "pack.h" +#include "pack-objects.h" +#include "remote.h" +#include "vector.h" +#include "tree.h" + +static int push_spec_rref_cmp(const void *a, const void *b) +{ + const push_spec *push_spec_a = a, *push_spec_b = b; + + return strcmp(push_spec_a->refspec.dst, push_spec_b->refspec.dst); +} + +static int push_status_ref_cmp(const void *a, const void *b) +{ + const push_status *push_status_a = a, *push_status_b = b; + + return strcmp(push_status_a->ref, push_status_b->ref); +} + +int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts) +{ + git_push *p; + + *out = NULL; + + GIT_ERROR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); + + p = git__calloc(1, sizeof(*p)); + GIT_ERROR_CHECK_ALLOC(p); + + p->repo = remote->repo; + p->remote = remote; + p->report_status = 1; + p->pb_parallelism = opts ? opts->pb_parallelism : 1; + + if (opts) { + GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + memcpy(&p->callbacks, &opts->callbacks, sizeof(git_remote_callbacks)); + } + + if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) { + git__free(p); + return -1; + } + + if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) { + git_vector_free(&p->specs); + git__free(p); + return -1; + } + + if (git_vector_init(&p->updates, 0, NULL) < 0) { + git_vector_free(&p->status); + git_vector_free(&p->specs); + git__free(p); + return -1; + } + + *out = p; + return 0; +} + +static void free_refspec(push_spec *spec) +{ + if (spec == NULL) + return; + + git_refspec__dispose(&spec->refspec); + git__free(spec); +} + +static int check_rref(char *ref) +{ + if (git__prefixcmp(ref, "refs/")) { + git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); + return -1; + } + + return 0; +} + +static int check_lref(git_push *push, char *ref) +{ + /* lref must be resolvable to an existing object */ + git_object *obj; + int error = git_revparse_single(&obj, push->repo, ref); + git_object_free(obj); + + if (!error) + return 0; + + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_REFERENCE, + "src refspec '%s' does not match any existing object", ref); + else + git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); + return -1; +} + +static int parse_refspec(git_push *push, push_spec **spec, const char *str) +{ + push_spec *s; + + *spec = NULL; + + s = git__calloc(1, sizeof(*s)); + GIT_ERROR_CHECK_ALLOC(s); + + if (git_refspec__parse(&s->refspec, str, false) < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid refspec %s", str); + goto on_error; + } + + if (s->refspec.src && s->refspec.src[0] != '\0' && + check_lref(push, s->refspec.src) < 0) { + goto on_error; + } + + if (check_rref(s->refspec.dst) < 0) + goto on_error; + + *spec = s; + return 0; + +on_error: + free_refspec(s); + return -1; +} + +int git_push_add_refspec(git_push *push, const char *refspec) +{ + push_spec *spec; + + if (parse_refspec(push, &spec, refspec) < 0 || + git_vector_insert(&push->specs, spec) < 0) + return -1; + + return 0; +} + +int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) +{ + git_str remote_ref_name = GIT_STR_INIT; + size_t i, j; + git_refspec *fetch_spec; + push_spec *push_spec = NULL; + git_reference *remote_ref; + push_status *status; + int error = 0; + + git_vector_foreach(&push->status, i, status) { + int fire_callback = 1; + + /* Skip unsuccessful updates which have non-empty messages */ + if (status->msg) + continue; + + /* Find the corresponding remote ref */ + fetch_spec = git_remote__matching_refspec(push->remote, status->ref); + if (!fetch_spec) + continue; + + /* Clear the buffer which can be dirty from previous iteration */ + git_str_clear(&remote_ref_name); + + if ((error = git_refspec__transform(&remote_ref_name, fetch_spec, status->ref)) < 0) + goto on_error; + + /* Find matching push ref spec */ + git_vector_foreach(&push->specs, j, push_spec) { + if (!strcmp(push_spec->refspec.dst, status->ref)) + break; + } + + /* Could not find the corresponding push ref spec for this push update */ + if (j == push->specs.length) + continue; + + /* Update the remote ref */ + if (git_oid_is_zero(&push_spec->loid)) { + error = git_reference_lookup(&remote_ref, push->remote->repo, git_str_cstr(&remote_ref_name)); + + if (error >= 0) { + error = git_reference_delete(remote_ref); + git_reference_free(remote_ref); + } + } else { + error = git_reference_create(NULL, push->remote->repo, + git_str_cstr(&remote_ref_name), &push_spec->loid, 1, + "update by push"); + } + + if (error < 0) { + if (error != GIT_ENOTFOUND) + goto on_error; + + git_error_clear(); + fire_callback = 0; + } + + if (fire_callback && callbacks && callbacks->update_tips) { + error = callbacks->update_tips(git_str_cstr(&remote_ref_name), + &push_spec->roid, &push_spec->loid, callbacks->payload); + + if (error < 0) + goto on_error; + } + } + + error = 0; + +on_error: + git_str_dispose(&remote_ref_name); + return error; +} + +/** + * Insert all tags until we find a non-tag object, which is returned + * in `out`. + */ +static int enqueue_tag(git_object **out, git_push *push, git_oid *id) +{ + git_object *obj = NULL, *target = NULL; + int error; + + if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJECT_TAG)) < 0) + return error; + + while (git_object_type(obj) == GIT_OBJECT_TAG) { + if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0) + break; + + if ((error = git_tag_target(&target, (git_tag *) obj)) < 0) + break; + + git_object_free(obj); + obj = target; + } + + if (error < 0) + git_object_free(obj); + else + *out = obj; + + return error; +} + +static int queue_objects(git_push *push) +{ + git_remote_head *head; + push_spec *spec; + git_revwalk *rw; + unsigned int i; + int error = -1; + + if (git_revwalk_new(&rw, push->repo) < 0) + return -1; + + git_revwalk_sorting(rw, GIT_SORT_TIME); + + git_vector_foreach(&push->specs, i, spec) { + git_object_t type; + size_t size; + + if (git_oid_is_zero(&spec->loid)) + /* + * Delete reference on remote side; + * nothing to do here. + */ + continue; + + if (git_oid_equal(&spec->loid, &spec->roid)) + continue; /* up-to-date */ + + if ((error = git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid)) < 0) + goto on_error; + + if (type == GIT_OBJECT_TAG) { + git_object *target; + + if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) + goto on_error; + + if (git_object_type(target) == GIT_OBJECT_COMMIT) { + if ((error = git_revwalk_push(rw, git_object_id(target))) < 0) { + git_object_free(target); + goto on_error; + } + } else { + if ((error = git_packbuilder_insert( + push->pb, git_object_id(target), NULL)) < 0) { + git_object_free(target); + goto on_error; + } + } + git_object_free(target); + } else if ((error = git_revwalk_push(rw, &spec->loid)) < 0) + goto on_error; + + if (!spec->refspec.force) { + git_oid base; + + if (git_oid_is_zero(&spec->roid)) + continue; + + if (!git_odb_exists(push->repo->_odb, &spec->roid)) { + git_error_set(GIT_ERROR_REFERENCE, + "cannot push because a reference that you are trying to update on the remote contains commits that are not present locally."); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + error = git_merge_base(&base, push->repo, + &spec->loid, &spec->roid); + + if (error == GIT_ENOTFOUND || + (!error && !git_oid_equal(&base, &spec->roid))) { + git_error_set(GIT_ERROR_REFERENCE, + "cannot push non-fastforwardable reference"); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + if (error < 0) + goto on_error; + } + } + + git_vector_foreach(&push->remote->refs, i, head) { + if (git_oid_is_zero(&head->oid)) + continue; + + 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); + +on_error: + git_revwalk_free(rw); + return error; +} + +static int add_update(git_push *push, push_spec *spec) +{ + git_push_update *u = git__calloc(1, sizeof(git_push_update)); + GIT_ERROR_CHECK_ALLOC(u); + + u->src_refname = git__strdup(spec->refspec.src); + GIT_ERROR_CHECK_ALLOC(u->src_refname); + + u->dst_refname = git__strdup(spec->refspec.dst); + GIT_ERROR_CHECK_ALLOC(u->dst_refname); + + git_oid_cpy(&u->src, &spec->roid); + git_oid_cpy(&u->dst, &spec->loid); + + return git_vector_insert(&push->updates, u); +} + +static int calculate_work(git_push *push) +{ + git_remote_head *head; + push_spec *spec; + unsigned int i, j; + + /* Update local and remote oids*/ + + git_vector_foreach(&push->specs, i, spec) { + if (spec->refspec.src && spec->refspec.src[0]!= '\0') { + /* This is a create or update. Local ref must exist. */ + if (git_reference_name_to_id( + &spec->loid, push->repo, spec->refspec.src) < 0) { + git_error_set(GIT_ERROR_REFERENCE, "no such reference '%s'", spec->refspec.src); + return -1; + } + } + + /* Remote ref may or may not (e.g. during create) already exist. */ + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->refspec.dst, head->name)) { + git_oid_cpy(&spec->roid, &head->oid); + break; + } + } + + if (add_update(push, spec) < 0) + return -1; + } + + return 0; +} + +static int do_push(git_push *push) +{ + int error = 0; + git_transport *transport = push->remote->transport; + git_remote_callbacks *callbacks = &push->callbacks; + + if (!transport->push) { + git_error_set(GIT_ERROR_NET, "remote transport doesn't support push"); + error = -1; + goto on_error; + } + + /* + * A pack-file MUST be sent if either create or update command + * is used, even if the server already has all the necessary + * objects. In this case the client MUST send an empty pack-file. + */ + + if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0) + goto on_error; + + git_packbuilder_set_threads(push->pb, push->pb_parallelism); + + if (callbacks && callbacks->pack_progress) + if ((error = git_packbuilder_set_callbacks(push->pb, callbacks->pack_progress, callbacks->payload)) < 0) + goto on_error; + + if ((error = calculate_work(push)) < 0) + goto on_error; + + if (callbacks && callbacks->push_negotiation && + (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents, + push->updates.length, callbacks->payload)) < 0) + goto on_error; + + if ((error = queue_objects(push)) < 0 || + (error = transport->push(transport, push)) < 0) + goto on_error; + +on_error: + git_packbuilder_free(push->pb); + return error; +} + +static int filter_refs(git_remote *remote) +{ + const git_remote_head **heads; + size_t heads_len, i; + + git_vector_clear(&remote->refs); + + if (git_remote_ls(&heads, &heads_len, remote) < 0) + return -1; + + for (i = 0; i < heads_len; i++) { + if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0) + return -1; + } + + return 0; +} + +int git_push_finish(git_push *push) +{ + int error; + + if (!git_remote_connected(push->remote)) { + git_error_set(GIT_ERROR_NET, "remote is disconnected"); + return -1; + } + + if ((error = filter_refs(push->remote)) < 0 || + (error = do_push(push)) < 0) + return error; + + if (!push->unpack_ok) { + error = -1; + git_error_set(GIT_ERROR_NET, "unpacking the sent packfile failed on the remote"); + } + + return error; +} + +int git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data) +{ + push_status *status; + unsigned int i; + + git_vector_foreach(&push->status, i, status) { + int error = cb(status->ref, status->msg, data); + if (error) + return git_error_set_after_callback(error); + } + + return 0; +} + +void git_push_status_free(push_status *status) +{ + if (status == NULL) + return; + + git__free(status->msg); + git__free(status->ref); + git__free(status); +} + +void git_push_free(git_push *push) +{ + push_spec *spec; + push_status *status; + git_push_update *update; + unsigned int i; + + if (push == NULL) + return; + + git_vector_foreach(&push->specs, i, spec) { + free_refspec(spec); + } + git_vector_free(&push->specs); + + git_vector_foreach(&push->status, i, status) { + git_push_status_free(status); + } + git_vector_free(&push->status); + + git_vector_foreach(&push->updates, i, update) { + git__free(update->src_refname); + git__free(update->dst_refname); + git__free(update); + } + git_vector_free(&push->updates); + + git__free(push); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_push_init_options(git_push_options *opts, unsigned int version) +{ + return git_push_options_init(opts, version); +} +#endif diff --git a/src/libgit2/push.h b/src/libgit2/push.h new file mode 100644 index 000000000..fc72e845e --- /dev/null +++ b/src/libgit2/push.h @@ -0,0 +1,128 @@ +/* + * 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_push_h__ +#define INCLUDE_push_h__ + +#include "common.h" + +#include "git2.h" +#include "refspec.h" +#include "remote.h" + +typedef struct push_spec { + struct git_refspec refspec; + + git_oid loid; + git_oid roid; +} push_spec; + +typedef struct push_status { + bool ok; + + char *ref; + char *msg; +} push_status; + +struct git_push { + git_repository *repo; + git_packbuilder *pb; + git_remote *remote; + git_vector specs; + git_vector updates; + bool report_status; + + /* report-status */ + bool unpack_ok; + git_vector status; + + /* options */ + unsigned pb_parallelism; + git_remote_callbacks callbacks; +}; + +/** + * Free the given push status object + * + * @param status The push status object + */ +void git_push_status_free(push_status *status); + +/** + * Create a new push object + * + * @param out New push object + * @param remote Remote instance + * @param opts Push options or NULL + * + * @return 0 or an error code + */ +int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts); + +/** + * Add a refspec to be pushed + * + * @param push The push object + * @param refspec Refspec string + * + * @return 0 or an error code + */ +int git_push_add_refspec(git_push *push, const char *refspec); + +/** + * Update remote tips after a push + * + * @param push The push object + * @param callbacks the callbacks to use for this connection + * + * @return 0 or an error code + */ +int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks); + +/** + * Perform the push + * + * This function will return an error in case of a protocol error or + * the server being unable to unpack the data we sent. + * + * The return value does not reflect whether the server accepted or + * refused any reference updates. Use `git_push_status_foreach()` in + * order to find out which updates were accepted or rejected. + * + * @param push The push object + * + * @return 0 or an error code + */ +int git_push_finish(git_push *push); + +/** + * Invoke callback `cb' on each status entry + * + * For each of the updated references, we receive a status report in the + * form of `ok refs/heads/master` or `ng refs/heads/master `. + * `msg != NULL` means the reference has not been updated for the given + * reason. + * + * Return a non-zero value from the callback to stop the loop. + * + * @param push The push object + * @param cb The callback to call on each object + * @param data The payload passed to the callback + * + * @return 0 on success, non-zero callback return value, or error code + */ +int git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data); + +/** + * Free the given push object + * + * @param push The push object + */ +void git_push_free(git_push *push); + +#endif diff --git a/src/libgit2/reader.c b/src/libgit2/reader.c new file mode 100644 index 000000000..ba9775240 --- /dev/null +++ b/src/libgit2/reader.c @@ -0,0 +1,269 @@ +/* + * 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 "reader.h" + +#include "futils.h" +#include "blob.h" + +#include "git2/tree.h" +#include "git2/blob.h" +#include "git2/index.h" +#include "git2/repository.h" + +/* tree reader */ + +typedef struct { + git_reader reader; + git_tree *tree; +} tree_reader; + +static int tree_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *_reader, + const char *filename) +{ + tree_reader *reader = (tree_reader *)_reader; + git_tree_entry *tree_entry = NULL; + git_blob *blob = NULL; + git_object_size_t blobsize; + int error; + + if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 || + (error = git_blob_lookup(&blob, git_tree_owner(reader->tree), git_tree_entry_id(tree_entry))) < 0) + goto done; + + blobsize = git_blob_rawsize(blob); + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + + if ((error = git_str_set(out, git_blob_rawcontent(blob), (size_t)blobsize)) < 0) + goto done; + + if (out_id) + git_oid_cpy(out_id, git_tree_entry_id(tree_entry)); + + if (out_filemode) + *out_filemode = git_tree_entry_filemode(tree_entry); + +done: + git_blob_free(blob); + git_tree_entry_free(tree_entry); + return error; +} + +int git_reader_for_tree(git_reader **out, git_tree *tree) +{ + tree_reader *reader; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(tree); + + reader = git__calloc(1, sizeof(tree_reader)); + GIT_ERROR_CHECK_ALLOC(reader); + + reader->reader.read = tree_reader_read; + reader->tree = tree; + + *out = (git_reader *)reader; + return 0; +} + +/* workdir reader */ + +typedef struct { + git_reader reader; + git_repository *repo; + git_index *index; +} workdir_reader; + +static int workdir_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *_reader, + const char *filename) +{ + workdir_reader *reader = (workdir_reader *)_reader; + git_str path = GIT_STR_INIT; + struct stat st; + git_filemode_t filemode; + git_filter_list *filters = NULL; + const git_index_entry *idx_entry; + git_oid id; + int error; + + if ((error = git_repository_workdir_path(&path, reader->repo, filename)) < 0) + goto done; + + if ((error = p_lstat(path.ptr, &st)) < 0) { + if (error == -1 && errno == ENOENT) + error = GIT_ENOTFOUND; + + git_error_set(GIT_ERROR_OS, "could not stat '%s'", path.ptr); + goto done; + } + + filemode = git_futils_canonical_mode(st.st_mode); + + /* + * Patch application - for example - uses the filtered version of + * the working directory data to match git. So we will run the + * workdir -> ODB filter on the contents in this workdir reader. + */ + if ((error = git_filter_list_load(&filters, reader->repo, NULL, filename, + GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT)) < 0) + goto done; + + if ((error = git_filter_list__apply_to_file(out, + filters, reader->repo, path.ptr)) < 0) + goto done; + + if (out_id || reader->index) { + if ((error = git_odb_hash(&id, out->ptr, out->size, GIT_OBJECT_BLOB)) < 0) + goto done; + } + + if (reader->index) { + if (!(idx_entry = git_index_get_bypath(reader->index, filename, 0)) || + filemode != idx_entry->mode || + !git_oid_equal(&id, &idx_entry->id)) { + error = GIT_READER_MISMATCH; + goto done; + } + } + + if (out_id) + git_oid_cpy(out_id, &id); + + if (out_filemode) + *out_filemode = filemode; + +done: + git_filter_list_free(filters); + git_str_dispose(&path); + return error; +} + +int git_reader_for_workdir( + git_reader **out, + git_repository *repo, + bool validate_index) +{ + workdir_reader *reader; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + reader = git__calloc(1, sizeof(workdir_reader)); + GIT_ERROR_CHECK_ALLOC(reader); + + reader->reader.read = workdir_reader_read; + reader->repo = repo; + + if (validate_index && + (error = git_repository_index__weakptr(&reader->index, repo)) < 0) { + git__free(reader); + return error; + } + + *out = (git_reader *)reader; + return 0; +} + +/* index reader */ + +typedef struct { + git_reader reader; + git_repository *repo; + git_index *index; +} index_reader; + +static int index_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *_reader, + const char *filename) +{ + index_reader *reader = (index_reader *)_reader; + const git_index_entry *entry; + git_blob *blob; + int error; + + if ((entry = git_index_get_bypath(reader->index, filename, 0)) == NULL) + return GIT_ENOTFOUND; + + if ((error = git_blob_lookup(&blob, reader->repo, &entry->id)) < 0) + goto done; + + if (out_id) + git_oid_cpy(out_id, &entry->id); + + if (out_filemode) + *out_filemode = entry->mode; + + error = git_blob__getbuf(out, blob); + +done: + git_blob_free(blob); + return error; +} + +int git_reader_for_index( + git_reader **out, + git_repository *repo, + git_index *index) +{ + index_reader *reader; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + reader = git__calloc(1, sizeof(index_reader)); + GIT_ERROR_CHECK_ALLOC(reader); + + reader->reader.read = index_reader_read; + reader->repo = repo; + + if (index) { + reader->index = index; + } else if ((error = git_repository_index__weakptr(&reader->index, repo)) < 0) { + git__free(reader); + return error; + } + + *out = (git_reader *)reader; + return 0; +} + +/* generic */ + +int git_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *reader, + const char *filename) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(reader); + GIT_ASSERT_ARG(filename); + + return reader->read(out, out_id, out_filemode, reader, filename); +} + +void git_reader_free(git_reader *reader) +{ + if (!reader) + return; + + git__free(reader); +} diff --git a/src/libgit2/reader.h b/src/libgit2/reader.h new file mode 100644 index 000000000..b58dc93f6 --- /dev/null +++ b/src/libgit2/reader.h @@ -0,0 +1,107 @@ +/* + * 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_reader_h__ +#define INCLUDE_reader_h__ + +#include "common.h" + +/* Returned when the workdir does not match the index */ +#define GIT_READER_MISMATCH 1 + +typedef struct git_reader git_reader; + +/* + * The `git_reader` structure is a generic interface for reading the + * contents of a file by its name, and implementations are provided + * for reading out of a tree, the index, and the working directory. + * + * Note that the reader implementation is meant to have a short + * lifecycle and does not increase the refcount of the object that + * it's reading. Callers should ensure that they do not use a + * reader after disposing the underlying object that it reads. + */ +struct git_reader { + int (*read)(git_str *out, git_oid *out_oid, git_filemode_t *mode, git_reader *reader, const char *filename); +}; + +/** + * Create a `git_reader` that will allow random access to the given + * tree. Paths requested via `git_reader_read` will be rooted at this + * tree, callers are not expected to recurse through tree lookups. Thus, + * you can request to read `/src/foo.c` and the tree provided to this + * function will be searched to find another tree named `src`, which + * will then be opened to find `foo.c`. + * + * @param out The reader for the given tree + * @param tree The tree object to read + * @return 0 on success, or an error code < 0 + */ +extern int git_reader_for_tree( + git_reader **out, + git_tree *tree); + +/** + * Create a `git_reader` that will allow random access to the given + * index, or the repository's index. + * + * @param out The reader for the given index + * @param repo The repository containing the index + * @param index The index to read, or NULL to use the repository's index + * @return 0 on success, or an error code < 0 + */ +extern int git_reader_for_index( + git_reader **out, + git_repository *repo, + git_index *index); + +/** + * Create a `git_reader` that will allow random access to the given + * repository's working directory. Note that the contents are read + * in repository format, meaning any workdir -> odb filters are + * applied. + * + * If `validate_index` is set to true, reads of files will hash the + * on-disk contents and ensure that the resulting object ID matches + * the repository's index. This ensures that the working directory + * is unmodified from the index contents. + * + * @param out The reader for the given working directory + * @param repo The repository containing the working directory + * @param validate_index If true, the working directory contents will + * be compared to the index contents during read to ensure that + * the working directory is unmodified. + * @return 0 on success, or an error code < 0 + */ +extern int git_reader_for_workdir( + git_reader **out, + git_repository *repo, + bool validate_index); + +/** + * Read the given filename from the reader and populate the given buffer + * with the contents and the given oid with the object ID. + * + * @param out The buffer to populate with the file contents + * @param out_id The oid to populate with the object ID + * @param reader The reader to read + * @param filename The filename to read from the reader + */ +extern int git_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *reader, + const char *filename); + +/** + * Free the given reader and any associated objects. + * + * @param reader The reader to free + */ +extern void git_reader_free(git_reader *reader); + +#endif diff --git a/src/libgit2/rebase.c b/src/libgit2/rebase.c new file mode 100644 index 000000000..5e48f0dfb --- /dev/null +++ b/src/libgit2/rebase.c @@ -0,0 +1,1480 @@ +/* + * 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 "str.h" +#include "repository.h" +#include "posix.h" +#include "filebuf.h" +#include "commit.h" +#include "merge.h" +#include "array.h" +#include "config.h" +#include "annotated_commit.h" +#include "index.h" + +#include +#include +#include +#include +#include +#include +#include + +#define REBASE_APPLY_DIR "rebase-apply" +#define REBASE_MERGE_DIR "rebase-merge" + +#define HEAD_NAME_FILE "head-name" +#define ORIG_HEAD_FILE "orig-head" +#define HEAD_FILE "head" +#define ONTO_FILE "onto" +#define ONTO_NAME_FILE "onto_name" +#define QUIET_FILE "quiet" +#define INTERACTIVE_FILE "interactive" + +#define MSGNUM_FILE "msgnum" +#define END_FILE "end" +#define CMT_FILE_FMT "cmt.%" PRIuZ +#define CURRENT_FILE "current" +#define REWRITTEN_FILE "rewritten" + +#define ORIG_DETACHED_HEAD "detached HEAD" + +#define NOTES_DEFAULT_REF NULL + +#define REBASE_DIR_MODE 0777 +#define REBASE_FILE_MODE 0666 + +typedef enum { + 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_t type; + char *state_path; + + unsigned int head_detached:1, + inmemory:1, + quiet:1, + started:1; + + git_array_t(git_rebase_operation) operations; + size_t current; + + /* Used by in-memory rebase */ + git_index *index; + git_commit *last_commit; + + /* Used by regular (not in-memory) merge-style rebase */ + git_oid orig_head_id; + char *orig_head_name; + + git_oid onto_id; + char *onto_name; +}; + +#define GIT_REBASE_STATE_INIT {0} + +static int rebase_state_type( + git_rebase_t *type_out, + char **path_out, + git_repository *repo) +{ + git_str path = GIT_STR_INIT; + git_str interactive_path = GIT_STR_INIT; + git_rebase_t type = GIT_REBASE_NONE; + + if (git_str_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) + return -1; + + if (git_fs_path_isdir(git_str_cstr(&path))) { + type = GIT_REBASE_APPLY; + goto done; + } + + git_str_clear(&path); + if (git_str_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0) + return -1; + + if (git_fs_path_isdir(git_str_cstr(&path))) { + if (git_str_joinpath(&interactive_path, path.ptr, INTERACTIVE_FILE) < 0) + return -1; + + if (git_fs_path_isfile(interactive_path.ptr)) + type = GIT_REBASE_INTERACTIVE; + else + type = GIT_REBASE_MERGE; + + goto done; + } + +done: + *type_out = type; + + if (type != GIT_REBASE_NONE && path_out) + *path_out = git_str_detach(&path); + + git_str_dispose(&path); + git_str_dispose(&interactive_path); + + return 0; +} + +GIT_INLINE(int) rebase_readfile( + git_str *out, + git_str *state_path, + const char *filename) +{ + size_t state_path_len = state_path->size; + int error; + + git_str_clear(out); + + if ((error = git_str_joinpath(state_path, state_path->ptr, filename)) < 0 || + (error = git_futils_readbuffer(out, state_path->ptr)) < 0) + goto done; + + git_str_rtrim(out); + +done: + git_str_truncate(state_path, state_path_len); + return error; +} + +GIT_INLINE(int) rebase_readint( + size_t *out, git_str *asc_out, git_str *state_path, const char *filename) +{ + int32_t num; + const char *eol; + int error = 0; + + if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) + return error; + + if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { + git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid numeric value", filename); + return -1; + } + + *out = (size_t) num; + + return 0; +} + +GIT_INLINE(int) rebase_readoid( + git_oid *out, git_str *str_out, git_str *state_path, const char *filename) +{ + int error; + + if ((error = rebase_readfile(str_out, state_path, filename)) < 0) + return error; + + if (str_out->size != GIT_OID_HEXSZ || git_oid_fromstr(out, str_out->ptr) < 0) { + git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid object ID", filename); + return -1; + } + + return 0; +} + +static git_rebase_operation *rebase_operation_alloc( + git_rebase *rebase, + git_rebase_operation_t type, + git_oid *id, + const char *exec) +{ + git_rebase_operation *operation; + + GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !id, NULL); + GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !!exec, NULL); + + if ((operation = git_array_alloc(rebase->operations)) == NULL) + return NULL; + + operation->type = type; + git_oid_cpy((git_oid *)&operation->id, id); + operation->exec = exec; + + return operation; +} + +static int rebase_open_merge(git_rebase *rebase) +{ + git_str state_path = GIT_STR_INIT, buf = GIT_STR_INIT, cmt = GIT_STR_INIT; + git_oid id; + git_rebase_operation *operation; + size_t i, msgnum = 0, end; + int error; + + if ((error = git_str_puts(&state_path, rebase->state_path)) < 0) + goto done; + + /* Read 'msgnum' if it exists (otherwise, let msgnum = 0) */ + if ((error = rebase_readint(&msgnum, &buf, &state_path, MSGNUM_FILE)) < 0 && + error != GIT_ENOTFOUND) + goto done; + + if (msgnum) { + rebase->started = 1; + rebase->current = msgnum - 1; + } + + /* Read 'end' */ + if ((error = rebase_readint(&end, &buf, &state_path, END_FILE)) < 0) + goto done; + + /* Read 'current' if it exists */ + if ((error = rebase_readoid(&id, &buf, &state_path, CURRENT_FILE)) < 0 && + error != GIT_ENOTFOUND) + goto done; + + /* Read cmt.* */ + git_array_init_to_size(rebase->operations, end); + GIT_ERROR_CHECK_ARRAY(rebase->operations); + + for (i = 0; i < end; i++) { + git_str_clear(&cmt); + + if ((error = git_str_printf(&cmt, "cmt.%" PRIuZ, (i+1))) < 0 || + (error = rebase_readoid(&id, &buf, &state_path, cmt.ptr)) < 0) + goto done; + + operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); + GIT_ERROR_CHECK_ALLOC(operation); + } + + /* Read 'onto_name' */ + if ((error = rebase_readfile(&buf, &state_path, ONTO_NAME_FILE)) < 0) + goto done; + + rebase->onto_name = git_str_detach(&buf); + +done: + git_str_dispose(&cmt); + git_str_dispose(&state_path); + git_str_dispose(&buf); + + return error; +} + +static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) +{ + git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); + GIT_ERROR_CHECK_ALLOC(rebase); + + *out = NULL; + + if (rebase_opts) + memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); + else + 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); + GIT_ERROR_CHECK_ALLOC(rebase->options.rewrite_notes_ref); + } + + *out = rebase; + + return 0; +} + +static int rebase_check_versions(const git_rebase_options *given_opts) +{ + GIT_ERROR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); + + if (given_opts) + GIT_ERROR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); + + return 0; +} + +int git_rebase_open( + git_rebase **out, + git_repository *repo, + const git_rebase_options *given_opts) +{ + git_rebase *rebase; + git_str path = GIT_STR_INIT, orig_head_name = GIT_STR_INIT, + orig_head_id = GIT_STR_INIT, onto_id = GIT_STR_INIT; + size_t state_path_len; + int error; + + GIT_ASSERT_ARG(repo); + + if ((error = rebase_check_versions(given_opts)) < 0) + return error; + + if (rebase_alloc(&rebase, given_opts) < 0) + return -1; + + rebase->repo = repo; + + if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0) + goto done; + + if (rebase->type == GIT_REBASE_NONE) { + git_error_set(GIT_ERROR_REBASE, "there is no rebase in progress"); + error = GIT_ENOTFOUND; + goto done; + } + + if ((error = git_str_puts(&path, rebase->state_path)) < 0) + goto done; + + state_path_len = git_str_len(&path); + + if ((error = git_str_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 || + (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0) + goto done; + + git_str_rtrim(&orig_head_name); + + if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0) + rebase->head_detached = 1; + + git_str_truncate(&path, state_path_len); + + if ((error = git_str_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0) + goto done; + + if (!git_fs_path_isfile(path.ptr)) { + /* Previous versions of git.git used 'head' here; support that. */ + git_str_truncate(&path, state_path_len); + + if ((error = git_str_joinpath(&path, path.ptr, HEAD_FILE)) < 0) + goto done; + } + + if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0) + goto done; + + git_str_rtrim(&orig_head_id); + + if ((error = git_oid_fromstr(&rebase->orig_head_id, orig_head_id.ptr)) < 0) + goto done; + + git_str_truncate(&path, state_path_len); + + if ((error = git_str_joinpath(&path, path.ptr, ONTO_FILE)) < 0 || + (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0) + goto done; + + git_str_rtrim(&onto_id); + + if ((error = git_oid_fromstr(&rebase->onto_id, onto_id.ptr)) < 0) + goto done; + + if (!rebase->head_detached) + rebase->orig_head_name = git_str_detach(&orig_head_name); + + switch (rebase->type) { + case GIT_REBASE_INTERACTIVE: + git_error_set(GIT_ERROR_REBASE, "interactive rebase is not supported"); + error = -1; + break; + case GIT_REBASE_MERGE: + error = rebase_open_merge(rebase); + break; + case GIT_REBASE_APPLY: + git_error_set(GIT_ERROR_REBASE, "patch application rebase is not supported"); + error = -1; + break; + default: + abort(); + } + +done: + if (error == 0) + *out = rebase; + else + git_rebase_free(rebase); + + git_str_dispose(&path); + git_str_dispose(&orig_head_name); + git_str_dispose(&orig_head_id); + git_str_dispose(&onto_id); + return error; +} + +static int rebase_cleanup(git_rebase *rebase) +{ + if (!rebase || rebase->inmemory) + return 0; + + return git_fs_path_isdir(rebase->state_path) ? + git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) : + 0; +} + +static int rebase_setupfile(git_rebase *rebase, const char *filename, int flags, const char *fmt, ...) +{ + git_str path = GIT_STR_INIT, + contents = GIT_STR_INIT; + va_list ap; + int error; + + va_start(ap, fmt); + git_str_vprintf(&contents, fmt, ap); + va_end(ap); + + if ((error = git_str_joinpath(&path, rebase->state_path, filename)) == 0) + error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE); + + git_str_dispose(&path); + git_str_dispose(&contents); + + return error; +} + +static const char *rebase_onto_name(const git_annotated_commit *onto) +{ + if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0) + return onto->ref_name + 11; + else if (onto->ref_name) + return onto->ref_name; + else + return onto->id_str; +} + +static int rebase_setupfiles_merge(git_rebase *rebase) +{ + git_str commit_filename = GIT_STR_INIT; + char id_str[GIT_OID_HEXSZ]; + git_rebase_operation *operation; + size_t i; + int error = 0; + + if ((error = rebase_setupfile(rebase, END_FILE, 0, "%" PRIuZ "\n", git_array_size(rebase->operations))) < 0 || + (error = rebase_setupfile(rebase, ONTO_NAME_FILE, 0, "%s\n", rebase->onto_name)) < 0) + goto done; + + for (i = 0; i < git_array_size(rebase->operations); i++) { + operation = git_array_get(rebase->operations, i); + + git_str_clear(&commit_filename); + git_str_printf(&commit_filename, CMT_FILE_FMT, i+1); + + git_oid_fmt(id_str, &operation->id); + + if ((error = rebase_setupfile(rebase, commit_filename.ptr, 0, + "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0) + goto done; + } + +done: + git_str_dispose(&commit_filename); + return error; +} + +static int rebase_setupfiles(git_rebase *rebase) +{ + char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ]; + const char *orig_head_name; + + git_oid_fmt(onto, &rebase->onto_id); + git_oid_fmt(orig_head, &rebase->orig_head_id); + + if (p_mkdir(rebase->state_path, REBASE_DIR_MODE) < 0) { + git_error_set(GIT_ERROR_OS, "failed to create rebase directory '%s'", rebase->state_path); + return -1; + } + + orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD : + rebase->orig_head_name; + + if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 || + rebase_setupfile(rebase, HEAD_NAME_FILE, 0, "%s\n", orig_head_name) < 0 || + rebase_setupfile(rebase, ONTO_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 || + rebase_setupfile(rebase, ORIG_HEAD_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 || + rebase_setupfile(rebase, QUIET_FILE, 0, rebase->quiet ? "t\n" : "\n") < 0) + return -1; + + return rebase_setupfiles_merge(rebase); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_rebase_init_options(git_rebase_options *opts, unsigned int version) +{ + return git_rebase_options_init(opts, version); +} +#endif + +static int rebase_ensure_not_in_progress(git_repository *repo) +{ + int error; + git_rebase_t type; + + if ((error = rebase_state_type(&type, NULL, repo)) < 0) + return error; + + if (type != GIT_REBASE_NONE) { + git_error_set(GIT_ERROR_REBASE, "there is an existing rebase in progress"); + return -1; + } + + return 0; +} + +static int rebase_ensure_not_dirty( + git_repository *repo, + bool check_index, + bool check_workdir, + int fail_with) +{ + git_tree *head = NULL; + git_index *index = NULL; + git_diff *diff = NULL; + int error = 0; + + if (check_index) { + if ((error = git_repository_head_tree(&head, repo)) < 0 || + (error = git_repository_index(&index, repo)) < 0 || + (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) + goto done; + + if (git_diff_num_deltas(diff) > 0) { + git_error_set(GIT_ERROR_REBASE, "uncommitted changes exist in index"); + error = fail_with; + goto done; + } + + git_diff_free(diff); + diff = NULL; + } + + if (check_workdir) { + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.ignore_submodules = GIT_SUBMODULE_IGNORE_UNTRACKED; + if ((error = git_diff_index_to_workdir(&diff, repo, index, &diff_opts)) < 0) + goto done; + + if (git_diff_num_deltas(diff) > 0) { + git_error_set(GIT_ERROR_REBASE, "unstaged changes exist in workdir"); + error = fail_with; + goto done; + } + } + +done: + git_diff_free(diff); + git_index_free(index); + git_tree_free(head); + + return error; +} + +static int rebase_init_operations( + git_rebase *rebase, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto) +{ + git_revwalk *revwalk = NULL; + git_commit *commit; + git_oid id; + bool merge; + git_rebase_operation *operation; + int error; + + if (!upstream) + upstream = onto; + + if ((error = git_revwalk_new(&revwalk, rebase->repo)) < 0 || + (error = git_revwalk_push(revwalk, git_annotated_commit_id(branch))) < 0 || + (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0) + goto done; + + git_revwalk_sorting(revwalk, GIT_SORT_REVERSE); + + while ((error = git_revwalk_next(&id, revwalk)) == 0) { + if ((error = git_commit_lookup(&commit, repo, &id)) < 0) + goto done; + + merge = (git_commit_parentcount(commit) > 1); + git_commit_free(commit); + + if (merge) + continue; + + operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); + GIT_ERROR_CHECK_ALLOC(operation); + } + + error = 0; + +done: + git_revwalk_free(revwalk); + return error; +} + +static int rebase_init_merge( + git_rebase *rebase, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto) +{ + git_reference *head_ref = NULL; + git_commit *onto_commit = NULL; + git_str reflog = GIT_STR_INIT; + git_str state_path = GIT_STR_INIT; + int error; + + GIT_UNUSED(upstream); + + if ((error = git_str_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0) + goto done; + + rebase->state_path = git_str_detach(&state_path); + GIT_ERROR_CHECK_ALLOC(rebase->state_path); + + if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) { + rebase->orig_head_name = git__strdup(branch->ref_name); + GIT_ERROR_CHECK_ALLOC(rebase->orig_head_name); + } else { + rebase->head_detached = 1; + } + + rebase->onto_name = git__strdup(rebase_onto_name(onto)); + GIT_ERROR_CHECK_ALLOC(rebase->onto_name); + + rebase->quiet = rebase->options.quiet; + + git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); + git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); + + if ((error = rebase_setupfiles(rebase)) < 0 || + (error = git_str_printf(&reflog, + "rebase: checkout %s", rebase_onto_name(onto))) < 0 || + (error = git_commit_lookup( + &onto_commit, repo, git_annotated_commit_id(onto))) < 0 || + (error = git_checkout_tree(repo, + (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || + (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, + git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) + goto done; + +done: + git_reference_free(head_ref); + git_commit_free(onto_commit); + git_str_dispose(&reflog); + git_str_dispose(&state_path); + + return error; +} + +static int rebase_init_inmemory( + git_rebase *rebase, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto) +{ + GIT_UNUSED(branch); + GIT_UNUSED(upstream); + + return git_commit_lookup( + &rebase->last_commit, repo, git_annotated_commit_id(onto)); +} + +int git_rebase_init( + git_rebase **out, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto, + const git_rebase_options *given_opts) +{ + git_rebase *rebase = NULL; + git_annotated_commit *head_branch = NULL; + git_reference *head_ref = NULL; + bool inmemory = (given_opts && given_opts->inmemory); + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(upstream || onto); + + *out = NULL; + + if (!onto) + onto = upstream; + + if ((error = rebase_check_versions(given_opts)) < 0) + goto done; + + if (!inmemory) { + if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || + (error = rebase_ensure_not_in_progress(repo)) < 0 || + (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0) + goto done; + } + + if (!branch) { + if ((error = git_repository_head(&head_ref, repo)) < 0 || + (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) + goto done; + + branch = head_branch; + } + + if (rebase_alloc(&rebase, given_opts) < 0) + return -1; + + rebase->repo = repo; + rebase->inmemory = inmemory; + rebase->type = GIT_REBASE_MERGE; + + if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) + goto done; + + if (inmemory) + error = rebase_init_inmemory(rebase, repo, branch, upstream, onto); + else + error = rebase_init_merge(rebase, repo, branch ,upstream, onto); + + if (error == 0) + *out = rebase; + +done: + git_reference_free(head_ref); + git_annotated_commit_free(head_branch); + + if (error < 0) { + rebase_cleanup(rebase); + git_rebase_free(rebase); + } + + return error; +} + +static void normalize_checkout_options_for_apply( + git_checkout_options *checkout_opts, + git_rebase *rebase, + git_commit *current_commit) +{ + memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options)); + + if (!checkout_opts->ancestor_label) + checkout_opts->ancestor_label = "ancestor"; + + if (rebase->type == GIT_REBASE_MERGE) { + if (!checkout_opts->our_label) + checkout_opts->our_label = rebase->onto_name; + + if (!checkout_opts->their_label) + checkout_opts->their_label = git_commit_summary(current_commit); + } else { + abort(); + } +} + +GIT_INLINE(int) rebase_movenext(git_rebase *rebase) +{ + size_t next = rebase->started ? rebase->current + 1 : 0; + + if (next == git_array_size(rebase->operations)) + return GIT_ITEROVER; + + rebase->started = 1; + rebase->current = next; + + return 0; +} + +static int rebase_next_merge( + git_rebase_operation **out, + git_rebase *rebase) +{ + git_str path = GIT_STR_INIT; + git_commit *current_commit = NULL, *parent_commit = NULL; + git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; + git_index *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + git_rebase_operation *operation; + git_checkout_options checkout_opts; + char current_idstr[GIT_OID_HEXSZ]; + unsigned int parent_count; + int error; + + *out = NULL; + + operation = git_array_get(rebase->operations, rebase->current); + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0 || + (error = git_repository_head_tree(&head_tree, rebase->repo)) < 0) + goto done; + + if ((parent_count = git_commit_parentcount(current_commit)) > 1) { + git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); + error = -1; + goto done; + } else if (parent_count) { + if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0) + goto done; + } + + git_oid_fmt(current_idstr, &operation->id); + + normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit); + + if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || + (error = rebase_setupfile(rebase, MSGNUM_FILE, 0, "%" PRIuZ "\n", rebase->current+1)) < 0 || + (error = rebase_setupfile(rebase, CURRENT_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 || + (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 || + (error = git_merge__check_result(rebase->repo, index)) < 0 || + (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 || + (error = git_indexwriter_commit(&indexwriter)) < 0) + goto done; + + *out = operation; + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_tree_free(current_tree); + git_tree_free(head_tree); + git_tree_free(parent_tree); + git_commit_free(parent_commit); + git_commit_free(current_commit); + git_str_dispose(&path); + + return error; +} + +static int rebase_next_inmemory( + git_rebase_operation **out, + git_rebase *rebase) +{ + git_commit *current_commit = NULL, *parent_commit = NULL; + git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; + git_rebase_operation *operation; + git_index *index = NULL; + unsigned int parent_count; + int error; + + *out = NULL; + + operation = git_array_get(rebase->operations, rebase->current); + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0) + goto done; + + if ((parent_count = git_commit_parentcount(current_commit)) > 1) { + git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); + error = -1; + goto done; + } else if (parent_count) { + if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0) + goto done; + } + + if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || + (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) + goto done; + + if (!rebase->index) { + rebase->index = index; + index = NULL; + } else { + if ((error = git_index_read_index(rebase->index, index)) < 0) + goto done; + } + + *out = operation; + +done: + git_commit_free(current_commit); + git_commit_free(parent_commit); + git_tree_free(current_tree); + git_tree_free(head_tree); + git_tree_free(parent_tree); + git_index_free(index); + + return error; +} + +int git_rebase_next( + git_rebase_operation **out, + git_rebase *rebase) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(rebase); + + if ((error = rebase_movenext(rebase)) < 0) + return error; + + if (rebase->inmemory) + error = rebase_next_inmemory(out, rebase); + else if (rebase->type == GIT_REBASE_MERGE) + error = rebase_next_merge(out, rebase); + else + abort(); + + return error; +} + +int git_rebase_inmemory_index( + git_index **out, + git_rebase *rebase) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(rebase); + GIT_ASSERT_ARG(rebase->index); + + GIT_REFCOUNT_INC(rebase->index); + *out = rebase->index; + + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +static int create_signed( + git_oid *out, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + git_tree *tree, + size_t parent_count, + const git_commit **parents) +{ + git_str commit_content = GIT_STR_INIT; + git_buf commit_signature = { NULL, 0, 0 }, + signature_field = { NULL, 0, 0 }; + int error; + + git_error_clear(); + + if ((error = git_commit__create_buffer(&commit_content, + rebase->repo, author, committer, message_encoding, + message, tree, parent_count, parents)) < 0) + goto done; + + error = rebase->options.signing_cb(&commit_signature, + &signature_field, commit_content.ptr, + rebase->options.payload); + + if (error) { + if (error != GIT_PASSTHROUGH) + git_error_set_after_callback_function(error, "signing_cb"); + + goto done; + } + + error = git_commit_create_with_signature(out, rebase->repo, + commit_content.ptr, + commit_signature.size > 0 ? commit_signature.ptr : NULL, + signature_field.size > 0 ? signature_field.ptr : NULL); + +done: + git_buf_dispose(&commit_signature); + git_buf_dispose(&signature_field); + git_str_dispose(&commit_content); + return error; +} +#endif + +static int rebase_commit__create( + git_commit **out, + git_rebase *rebase, + git_index *index, + git_commit *parent_commit, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_rebase_operation *operation; + git_commit *current_commit = NULL, *commit = NULL; + git_tree *parent_tree = NULL, *tree = NULL; + git_oid tree_id, commit_id; + int error; + + operation = git_array_get(rebase->operations, rebase->current); + + if (git_index_has_conflicts(index)) { + git_error_set(GIT_ERROR_REBASE, "conflicts have not been resolved"); + error = GIT_EUNMERGED; + goto done; + } + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || + (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 || + (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) + goto done; + + if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) { + git_error_set(GIT_ERROR_REBASE, "this patch has already been applied"); + error = GIT_EAPPLIED; + goto done; + } + + if (!author) + author = git_commit_author(current_commit); + + if (!message) { + message_encoding = git_commit_message_encoding(current_commit); + message = git_commit_message(current_commit); + } + + git_error_clear(); + error = GIT_PASSTHROUGH; + + if (rebase->options.commit_create_cb) { + error = rebase->options.commit_create_cb(&commit_id, + author, committer, message_encoding, message, + tree, 1, (const git_commit **)&parent_commit, + rebase->options.payload); + + git_error_set_after_callback_function(error, + "commit_create_cb"); + } +#ifndef GIT_DEPRECATE_HARD + else if (rebase->options.signing_cb) { + error = create_signed(&commit_id, rebase, author, + committer, message_encoding, message, tree, + 1, (const git_commit **)&parent_commit); + } +#endif + + if (error == GIT_PASSTHROUGH) + error = git_commit_create(&commit_id, rebase->repo, NULL, + author, committer, message_encoding, message, + tree, 1, (const git_commit **)&parent_commit); + + if (error) + goto done; + + if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) + goto done; + + *out = commit; + +done: + if (error < 0) + git_commit_free(commit); + + git_commit_free(current_commit); + git_tree_free(parent_tree); + git_tree_free(tree); + + return error; +} + +static int rebase_commit_merge( + git_oid *commit_id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_rebase_operation *operation; + git_reference *head = NULL; + git_commit *head_commit = NULL, *commit = NULL; + git_index *index = NULL; + char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; + int error; + + operation = git_array_get(rebase->operations, rebase->current); + GIT_ASSERT(operation); + + if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || + (error = git_repository_head(&head, rebase->repo)) < 0 || + (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)) < 0 || + (error = git_repository_index(&index, rebase->repo)) < 0 || + (error = rebase_commit__create(&commit, rebase, index, head_commit, + author, committer, message_encoding, message)) < 0 || + (error = git_reference__update_for_commit( + rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0) + goto done; + + git_oid_fmt(old_idstr, &operation->id); + git_oid_fmt(new_idstr, git_commit_id(commit)); + + if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, + "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0) + goto done; + + git_oid_cpy(commit_id, git_commit_id(commit)); + +done: + git_index_free(index); + git_reference_free(head); + git_commit_free(head_commit); + git_commit_free(commit); + return error; +} + +static int rebase_commit_inmemory( + git_oid *commit_id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_commit *commit = NULL; + int error = 0; + + GIT_ASSERT_ARG(rebase->index); + GIT_ASSERT_ARG(rebase->last_commit); + GIT_ASSERT_ARG(rebase->current < rebase->operations.size); + + if ((error = rebase_commit__create(&commit, rebase, rebase->index, + rebase->last_commit, author, committer, message_encoding, message)) < 0) + goto done; + + git_commit_free(rebase->last_commit); + rebase->last_commit = commit; + + git_oid_cpy(commit_id, git_commit_id(commit)); + +done: + if (error < 0) + git_commit_free(commit); + + return error; +} + +int git_rebase_commit( + git_oid *id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + int error; + + GIT_ASSERT_ARG(rebase); + GIT_ASSERT_ARG(committer); + + if (rebase->inmemory) + error = rebase_commit_inmemory( + id, rebase, author, committer, message_encoding, message); + else if (rebase->type == GIT_REBASE_MERGE) + error = rebase_commit_merge( + id, rebase, author, committer, message_encoding, message); + else + abort(); + + return error; +} + +int git_rebase_abort(git_rebase *rebase) +{ + git_reference *orig_head_ref = NULL; + git_commit *orig_head_commit = NULL; + int error; + + GIT_ASSERT_ARG(rebase); + + if (rebase->inmemory) + return 0; + + error = rebase->head_detached ? + git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE, + &rebase->orig_head_id, 1, "rebase: aborting") : + git_reference_symbolic_create( + &orig_head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, + "rebase: aborting"); + + if (error < 0) + goto done; + + if ((error = git_commit_lookup( + &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 || + (error = git_reset(rebase->repo, (git_object *)orig_head_commit, + GIT_RESET_HARD, &rebase->options.checkout_options)) < 0) + goto done; + + error = rebase_cleanup(rebase); + +done: + git_commit_free(orig_head_commit); + git_reference_free(orig_head_ref); + + return error; +} + +static int notes_ref_lookup(git_str *out, git_rebase *rebase) +{ + git_config *config = NULL; + int do_rewrite, error; + + if (rebase->options.rewrite_notes_ref) { + git_str_attach_notowned(out, + rebase->options.rewrite_notes_ref, + strlen(rebase->options.rewrite_notes_ref)); + return 0; + } + + if ((error = git_repository_config(&config, rebase->repo)) < 0 || + (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) { + + if (error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + do_rewrite = 1; + } + + error = do_rewrite ? + git_config__get_string_buf(out, config, "notes.rewriteref") : + GIT_ENOTFOUND; + +done: + git_config_free(config); + return error; +} + +static int rebase_copy_note( + git_rebase *rebase, + const char *notes_ref, + git_oid *from, + git_oid *to, + const git_signature *committer) +{ + git_note *note = NULL; + git_oid note_id; + git_signature *who = NULL; + int error; + + if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + goto done; + } + + if (!committer) { + if((error = git_signature_default(&who, rebase->repo)) < 0) { + if (error != GIT_ENOTFOUND || + (error = git_signature_now(&who, "unknown", "unknown")) < 0) + goto done; + + git_error_clear(); + } + + committer = who; + } + + error = git_note_create(¬e_id, rebase->repo, notes_ref, + git_note_author(note), committer, to, git_note_message(note), 0); + +done: + git_note_free(note); + git_signature_free(who); + + return error; +} + +static int rebase_copy_notes( + git_rebase *rebase, + const git_signature *committer) +{ + git_str path = GIT_STR_INIT, rewritten = GIT_STR_INIT, notes_ref = GIT_STR_INIT; + char *pair_list, *fromstr, *tostr, *end; + git_oid from, to; + unsigned int linenum = 1; + int error = 0; + + if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + goto done; + } + + if ((error = git_str_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 || + (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0) + goto done; + + pair_list = rewritten.ptr; + + while (*pair_list) { + fromstr = pair_list; + + if ((end = strchr(fromstr, '\n')) == NULL) + goto on_error; + + pair_list = end+1; + *end = '\0'; + + if ((end = strchr(fromstr, ' ')) == NULL) + goto on_error; + + tostr = end+1; + *end = '\0'; + + if (strlen(fromstr) != GIT_OID_HEXSZ || + strlen(tostr) != GIT_OID_HEXSZ || + git_oid_fromstr(&from, fromstr) < 0 || + git_oid_fromstr(&to, tostr) < 0) + goto on_error; + + if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0) + goto done; + + linenum++; + } + + goto done; + +on_error: + git_error_set(GIT_ERROR_REBASE, "invalid rewritten file at line %d", linenum); + error = -1; + +done: + git_str_dispose(&rewritten); + git_str_dispose(&path); + git_str_dispose(¬es_ref); + + return error; +} + +static int return_to_orig_head(git_rebase *rebase) +{ + git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL; + git_commit *terminal_commit = NULL; + git_str branch_msg = GIT_STR_INIT, head_msg = GIT_STR_INIT; + char onto[GIT_OID_HEXSZ]; + int error = 0; + + git_oid_fmt(onto, &rebase->onto_id); + + if ((error = git_str_printf(&branch_msg, + "rebase finished: %s onto %.*s", + rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 && + (error = git_str_printf(&head_msg, + "rebase finished: returning to %s", + rebase->orig_head_name)) == 0 && + (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 && + (error = git_reference_peel((git_object **)&terminal_commit, + terminal_ref, GIT_OBJECT_COMMIT)) == 0 && + (error = git_reference_create_matching(&branch_ref, + rebase->repo, rebase->orig_head_name, + git_commit_id(terminal_commit), 1, + &rebase->orig_head_id, branch_msg.ptr)) == 0) + error = git_reference_symbolic_create(&head_ref, + rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, + head_msg.ptr); + + git_str_dispose(&head_msg); + git_str_dispose(&branch_msg); + git_commit_free(terminal_commit); + git_reference_free(head_ref); + git_reference_free(branch_ref); + git_reference_free(terminal_ref); + + return error; +} + +int git_rebase_finish( + git_rebase *rebase, + const git_signature *signature) +{ + int error = 0; + + GIT_ASSERT_ARG(rebase); + + if (rebase->inmemory) + return 0; + + if (!rebase->head_detached) + error = return_to_orig_head(rebase); + + if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0) + error = rebase_cleanup(rebase); + + return error; +} + +const char *git_rebase_orig_head_name(git_rebase *rebase) { + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + return rebase->orig_head_name; +} + +const git_oid *git_rebase_orig_head_id(git_rebase *rebase) { + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + return &rebase->orig_head_id; +} + +const char *git_rebase_onto_name(git_rebase *rebase) { + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + 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) +{ + GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); + + return git_array_size(rebase->operations); +} + +size_t git_rebase_operation_current(git_rebase *rebase) +{ + GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); + + return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION; +} + +git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + + return git_array_get(rebase->operations, idx); +} + +void git_rebase_free(git_rebase *rebase) +{ + if (rebase == NULL) + return; + + git_index_free(rebase->index); + git_commit_free(rebase->last_commit); + git__free(rebase->onto_name); + git__free(rebase->orig_head_name); + git__free(rebase->state_path); + git_array_clear(rebase->operations); + git__free((char *)rebase->options.rewrite_notes_ref); + git__free(rebase); +} diff --git a/src/libgit2/refdb.c b/src/libgit2/refdb.c new file mode 100644 index 000000000..ed33de92b --- /dev/null +++ b/src/libgit2/refdb.c @@ -0,0 +1,424 @@ +/* + * 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 "refdb.h" + +#include "git2/object.h" +#include "git2/refs.h" +#include "git2/refdb.h" +#include "git2/sys/refdb_backend.h" + +#include "hash.h" +#include "refs.h" +#include "reflog.h" +#include "posix.h" + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +int git_refdb_new(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + db = git__calloc(1, sizeof(*db)); + GIT_ERROR_CHECK_ALLOC(db); + + db->repo = repo; + + *out = db; + GIT_REFCOUNT_INC(db); + return 0; +} + +int git_refdb_open(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + git_refdb_backend *dir; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if (git_refdb_new(&db, repo) < 0) + return -1; + + /* Add the default (filesystem) backend */ + if (git_refdb_backend_fs(&dir, repo) < 0) { + git_refdb_free(db); + return -1; + } + + db->repo = repo; + db->backend = dir; + + *out = db; + return 0; +} + +static void refdb_free_backend(git_refdb *db) +{ + if (db->backend) + db->backend->free(db->backend); +} + +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; + + return 0; +} + +int git_refdb_compress(git_refdb *db) +{ + GIT_ASSERT_ARG(db); + + if (db->backend->compress) + return db->backend->compress(db->backend); + + return 0; +} + +void git_refdb__free(git_refdb *db) +{ + refdb_free_backend(db); + git__memzero(db, sizeof(*db)); + git__free(db); +} + +void git_refdb_free(git_refdb *db) +{ + if (db == NULL) + return; + + GIT_REFCOUNT_DEC(db, git_refdb__free); +} + +int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name) +{ + GIT_ASSERT_ARG(exists); + GIT_ASSERT_ARG(refdb); + GIT_ASSERT_ARG(refdb->backend); + + return refdb->backend->exists(exists, refdb->backend, ref_name); +} + +int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) +{ + git_reference *ref; + int error; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref_name); + + error = db->backend->lookup(&ref, db->backend, ref_name); + if (error < 0) + return error; + + GIT_REFCOUNT_INC(db); + ref->db = db; + + *out = ref; + return 0; +} + +int git_refdb_resolve( + git_reference **out, + git_refdb *db, + const char *ref_name, + int max_nesting) +{ + git_reference *ref = NULL; + int error = 0, nesting; + + *out = NULL; + + if (max_nesting > MAX_NESTING_LEVEL) + max_nesting = MAX_NESTING_LEVEL; + else if (max_nesting < 0) + max_nesting = DEFAULT_NESTING_LEVEL; + + if ((error = git_refdb_lookup(&ref, db, ref_name)) < 0) + goto out; + + for (nesting = 0; nesting < max_nesting; nesting++) { + git_reference *resolved; + + if (ref->type == GIT_REFERENCE_DIRECT) + break; + + if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) { + /* If we found a symbolic reference with a nonexistent target, return it. */ + if (error == GIT_ENOTFOUND) { + error = 0; + *out = ref; + ref = NULL; + } + goto out; + } + + git_reference_free(ref); + ref = resolved; + } + + if (ref->type != GIT_REFERENCE_DIRECT && max_nesting != 0) { + git_error_set(GIT_ERROR_REFERENCE, + "cannot resolve reference (>%u levels deep)", max_nesting); + error = -1; + goto out; + } + + *out = ref; + ref = NULL; +out: + git_reference_free(ref); + return error; +} + +int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob) +{ + int error; + + if (!db->backend || !db->backend->iterator) { + git_error_set(GIT_ERROR_REFERENCE, "this backend doesn't support iterators"); + return -1; + } + + if ((error = db->backend->iterator(out, db->backend, glob)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; +} + +int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter) +{ + int error; + + if ((error = iter->next(out, iter)) < 0) + return error; + + GIT_REFCOUNT_INC(iter->db); + (*out)->db = iter->db; + + return 0; +} + +int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter) +{ + return iter->next_name(out, iter); +} + +void git_refdb_iterator_free(git_reference_iterator *iter) +{ + GIT_REFCOUNT_DEC(iter->db, git_refdb__free); + iter->free(iter); +} + +int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + GIT_REFCOUNT_INC(db); + ref->db = db; + + return db->backend->write(db->backend, ref, force, who, message, old_id, old_target); +} + +int git_refdb_rename( + git_reference **out, + git_refdb *db, + const char *old_name, + const char *new_name, + int force, + const git_signature *who, + const char *message) +{ + int error; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message); + if (error < 0) + return error; + + if (out) { + GIT_REFCOUNT_INC(db); + (*out)->db = db; + } + + return 0; +} + +int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + return db->backend->del(db->backend, ref_name, old_id, old_target); +} + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) +{ + int error; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; +} + +int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref) +{ + int error, logall; + + error = git_repository__configmap_lookup(&logall, db->repo, GIT_CONFIGMAP_LOGALLREFUPDATES); + if (error < 0) + return error; + + /* Defaults to the opposite of the repo being bare */ + if (logall == GIT_LOGALLREFUPDATES_UNSET) + logall = !git_repository_is_bare(db->repo); + + *out = 0; + switch (logall) { + case GIT_LOGALLREFUPDATES_FALSE: + *out = 0; + break; + + case GIT_LOGALLREFUPDATES_TRUE: + /* Only write if it already has a log, + * or if it's under heads/, remotes/ or notes/ + */ + *out = git_refdb_has_log(db, ref->name) || + !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) || + !git__strcmp(ref->name, GIT_HEAD_FILE) || + !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) || + !git__prefixcmp(ref->name, GIT_REFS_NOTES_DIR); + break; + + case GIT_LOGALLREFUPDATES_ALWAYS: + *out = 1; + break; + } + + return 0; +} + +int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref) +{ + git_reference *head = NULL, *resolved = NULL; + const char *name; + int error; + + *out = 0; + + if (ref->type == GIT_REFERENCE_SYMBOLIC) { + error = 0; + goto out; + } + + if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0) + goto out; + + if (git_reference_type(head) == GIT_REFERENCE_DIRECT) + goto out; + + /* Go down the symref chain until we find the branch */ + if ((error = git_refdb_resolve(&resolved, db, git_reference_symbolic_target(head), -1)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + name = git_reference_symbolic_target(head); + } else if (git_reference_type(resolved) == GIT_REFERENCE_SYMBOLIC) { + name = git_reference_symbolic_target(resolved); + } else { + name = git_reference_name(resolved); + } + + if (strcmp(name, ref->name)) + goto out; + + *out = 1; + +out: + git_reference_free(resolved); + git_reference_free(head); + return error; +} + +int git_refdb_has_log(git_refdb *db, const char *refname) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(refname); + + return db->backend->has_log(db->backend, refname); +} + +int git_refdb_ensure_log(git_refdb *db, const char *refname) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(refname); + + return db->backend->ensure_log(db->backend, refname); +} + +int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT); + return 0; +} + +int git_refdb_lock(void **payload, git_refdb *db, const char *refname) +{ + GIT_ASSERT_ARG(payload); + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(refname); + + if (!db->backend->lock) { + git_error_set(GIT_ERROR_REFERENCE, "backend does not support locking"); + return -1; + } + + return db->backend->lock(payload, db->backend, refname); +} + +int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) +{ + GIT_ASSERT_ARG(db); + + return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message); +} diff --git a/src/libgit2/refdb.h b/src/libgit2/refdb.h new file mode 100644 index 000000000..84e19b1c3 --- /dev/null +++ b/src/libgit2/refdb.h @@ -0,0 +1,128 @@ +/* + * 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_refdb_h__ +#define INCLUDE_refdb_h__ + +#include "common.h" + +#include "git2/refdb.h" +#include "repository.h" + +struct git_refdb { + git_refcount rc; + git_repository *repo; + git_refdb_backend *backend; +}; + +void git_refdb__free(git_refdb *db); + +int git_refdb_exists( + int *exists, + git_refdb *refdb, + const char *ref_name); + +int git_refdb_lookup( + git_reference **out, + git_refdb *refdb, + const char *ref_name); + +/** + * Resolve the reference by following symbolic references. + * + * Given a reference name, this function will follow any symbolic references up + * to `max_nesting` deep and return the resolved direct reference. If any of + * the intermediate symbolic references points to a non-existing reference, + * then that symbolic reference is returned instead with an error code of `0`. + * If the given reference is a direct reference already, it is returned + * directly. + * + * If `max_nesting` is `0`, the reference will not be resolved. If it's + * negative, it will be set to the default resolve depth which is `5`. + * + * @param out Pointer to store the result in. + * @param db The refdb to use for resolving the reference. + * @param ref_name The reference name to lookup and resolve. + * @param max_nesting The maximum nesting depth. + * @return `0` on success, a negative error code otherwise. + */ +int git_refdb_resolve( + git_reference **out, + git_refdb *db, + const char *ref_name, + int max_nesting); + +int git_refdb_rename( + git_reference **out, + git_refdb *db, + const char *old_name, + const char *new_name, + int force, + const git_signature *who, + const char *message); + +int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob); +int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter); +int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter); +void git_refdb_iterator_free(git_reference_iterator *iter); + +int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target); +int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target); + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); +int git_refdb_reflog_write(git_reflog *reflog); + +/** + * Determine whether a reflog entry should be created for the given reference. + * + * Whether or not writing to a reference should create a reflog entry is + * dependent on a number of things. Most importantly, there's the + * "core.logAllRefUpdates" setting that controls in which situations a + * reference should get a corresponding reflog entry. The following values for + * it are understood: + * + * - "false": Do not log reference updates. + * + * - "true": Log normal reference updates. This will write entries for + * references in "refs/heads", "refs/remotes", "refs/notes" and + * "HEAD" or if the reference already has a log entry. + * + * - "always": Always create a reflog entry. + * + * If unset, the value will default to "true" for non-bare repositories and + * "false" for bare ones. + * + * @param out pointer to which the result will be written, `1` means a reflog + * entry should be written, `0` means none should be written. + * @param db The refdb to decide this for. + * @param ref The reference one wants to check. + * @return `0` on success, a negative error code otherwise. + */ +int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref); + +/** + * Determine whether a reflog entry should be created for HEAD if creating one + * for the given reference + * + * In case the given reference is being pointed to by HEAD, then creating a + * reflog entry for this reference also requires us to create a corresponding + * reflog entry for HEAD. This function can be used to determine that scenario. + * + * @param out pointer to which the result will be written, `1` means a reflog + * entry should be written, `0` means none should be written. + * @param db The refdb to decide this for. + * @param ref The reference one wants to check. + * @return `0` on success, a negative error code otherwise. + */ +int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref); + +int git_refdb_has_log(git_refdb *db, const char *refname); +int git_refdb_ensure_log(git_refdb *refdb, const char *refname); + +int git_refdb_lock(void **payload, git_refdb *db, const char *refname); +int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); + +#endif diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c new file mode 100644 index 000000000..0f49b16bb --- /dev/null +++ b/src/libgit2/refdb_fs.c @@ -0,0 +1,2468 @@ +/* + * 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 "refs.h" +#include "hash.h" +#include "repository.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 "path.h" + +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +enum { + PACKREF_HAS_PEEL = 1, + PACKREF_WAS_LOOSE = 2, + PACKREF_CANNOT_PEEL = 4, + PACKREF_SHADOWED = 8 +}; + +enum { + PEELING_NONE = 0, + PEELING_STANDARD, + PEELING_FULL +}; + +struct packref { + git_oid oid; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; + +typedef struct refdb_fs_backend { + git_refdb_backend parent; + + git_repository *repo; + /* path to git directory */ + char *gitpath; + /* path to common objects' directory */ + char *commonpath; + + git_sortedcache *refcache; + int peeling_mode; + git_iterator_flag_t iterator_flags; + uint32_t direach_flags; + int fsync; + git_map packed_refs_map; + git_mutex prlock; /* protect packed_refs_map */ + git_futils_filestamp packed_refs_stamp; + bool sorted; +} refdb_fs_backend; + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name); +static char *packed_set_peeling_mode(char *data, size_t data_sz, refdb_fs_backend *backend); + +GIT_INLINE(int) loose_path( + git_str *out, + const char *base, + const char *refname) +{ + if (git_str_joinpath(out, base, refname) < 0) + return -1; + + return git_fs_path_validate_str_length_with_suffix(out, + CONST_STRLEN(".lock")); +} + +GIT_INLINE(int) reflog_path( + git_str *out, + git_repository *repo, + const char *refname) +{ + const char *base; + int error; + + base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir : + repo->commondir; + + if ((error = git_str_joinpath(out, base, GIT_REFLOG_DIR)) < 0) + return error; + + return loose_path(out, out->ptr, refname); +} + +static int packref_cmp(const void *a_, const void *b_) +{ + const struct packref *a = a_, *b = b_; + return strcmp(a->name, b->name); +} + +static int packed_reload(refdb_fs_backend *backend) +{ + int error; + git_str packedrefs = GIT_STR_INIT; + char *scan, *eof, *eol; + + if (!backend->gitpath) + return 0; + + error = git_sortedcache_lockandload(backend->refcache, &packedrefs); + + /* + * If we can't find the packed-refs, clear table and return. + * Any other error just gets passed through. + * If no error, and file wasn't changed, just return. + * Anything else means we need to refresh the packed refs. + */ + if (error <= 0) { + if (error == GIT_ENOTFOUND) { + GIT_UNUSED(git_sortedcache_clear(backend->refcache, true)); + git_error_clear(); + error = 0; + } + return error; + } + + /* At this point, refresh the packed refs from the loaded buffer. */ + + GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); + + scan = packedrefs.ptr; + eof = scan + packedrefs.size; + + scan = packed_set_peeling_mode(scan, packedrefs.size, backend); + if (!scan) + goto parse_failed; + + while (scan < eof && *scan == '#') { + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + scan = eol + 1; + } + + while (scan < eof) { + struct packref *ref; + git_oid oid; + + /* parse " \n" */ + + if (git_oid_fromstr(&oid, scan) < 0) + goto parse_failed; + scan += GIT_OID_HEXSZ; + + if (*scan++ != ' ') + goto parse_failed; + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + *eol = '\0'; + if (eol[-1] == '\r') + eol[-1] = '\0'; + + if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0) + goto parse_failed; + scan = eol + 1; + + git_oid_cpy(&ref->oid, &oid); + + /* look for optional "^\n" */ + + if (*scan == '^') { + if (git_oid_fromstr(&oid, scan + 1) < 0) + goto parse_failed; + scan += GIT_OID_HEXSZ + 1; + + if (scan < eof) { + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + scan = eol + 1; + } + + git_oid_cpy(&ref->peel, &oid); + ref->flags |= PACKREF_HAS_PEEL; + } + else if (backend->peeling_mode == PEELING_FULL || + (backend->peeling_mode == PEELING_STANDARD && + git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) + ref->flags |= PACKREF_CANNOT_PEEL; + } + + git_sortedcache_wunlock(backend->refcache); + git_str_dispose(&packedrefs); + + return 0; + +parse_failed: + git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); + + GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); + git_sortedcache_wunlock(backend->refcache); + git_str_dispose(&packedrefs); + + return -1; +} + +static int loose_parse_oid( + git_oid *oid, const char *filename, git_str *file_content) +{ + const char *str = git_str_cstr(file_content); + + if (git_str_len(file_content) < GIT_OID_HEXSZ) + goto corrupted; + + /* we need to get 40 OID characters from the file */ + if (git_oid_fromstr(oid, str) < 0) + goto corrupted; + + /* If the file is longer than 40 chars, the 41st must be a space */ + str += GIT_OID_HEXSZ; + if (*str == '\0' || git__isspace(*str)) + return 0; + +corrupted: + git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename); + return -1; +} + +static int loose_readbuffer(git_str *buf, const char *base, const char *path) +{ + int error; + + if ((error = loose_path(buf, base, path)) < 0 || + (error = git_futils_readbuffer(buf, buf->ptr)) < 0) + git_str_dispose(buf); + + return error; +} + +static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) +{ + int error = 0; + git_str ref_file = GIT_STR_INIT; + struct packref *ref = NULL; + git_oid oid; + + /* if we fail to load the loose reference, assume someone changed + * the filesystem under us and skip it... + */ + if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) { + git_error_clear(); + goto done; + } + + /* skip symbolic refs */ + if (!git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF)) + goto done; + + /* parse OID from file */ + if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) + goto done; + + if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + goto done; + + if (!(error = git_sortedcache_upsert( + (void **)&ref, backend->refcache, name))) { + + git_oid_cpy(&ref->oid, &oid); + ref->flags = PACKREF_WAS_LOOSE; + } + + git_sortedcache_wunlock(backend->refcache); + +done: + git_str_dispose(&ref_file); + return error; +} + +static int _dirent_loose_load(void *payload, git_str *full_path) +{ + refdb_fs_backend *backend = payload; + const char *file_path; + + if (git__suffixcmp(full_path->ptr, ".lock") == 0) + return 0; + + if (git_fs_path_isdir(full_path->ptr)) { + int error = git_fs_path_direach( + full_path, backend->direach_flags, _dirent_loose_load, backend); + /* Race with the filesystem, ignore it */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + + return error; + } + + file_path = full_path->ptr + strlen(backend->gitpath); + + return loose_lookup_to_packfile(backend, file_path); +} + +/* + * Load all the loose references from the repository + * into the in-memory Packfile, and build a vector with + * all the references so it can be written back to + * disk. + */ +static int packed_loadloose(refdb_fs_backend *backend) +{ + int error; + git_str refs_path = GIT_STR_INIT; + + if (git_str_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0) + return -1; + + /* + * Load all the loose files from disk into the Packfile table. + * This will overwrite any old packed entries with their + * updated loose versions + */ + error = git_fs_path_direach( + &refs_path, backend->direach_flags, _dirent_loose_load, backend); + + git_str_dispose(&refs_path); + + return error; +} + +static int refdb_fs_backend__exists( + int *exists, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_str ref_path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(backend); + + *exists = 0; + + if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0) + goto out; + + if (git_fs_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_str_dispose(&ref_path); + return error; +} + +static const char *loose_parse_symbolic(git_str *file_content) +{ + const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); + const char *refname_start; + + refname_start = (const char *)file_content->ptr; + + if (git_str_len(file_content) < header_len + 1) { + git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file"); + return NULL; + } + + /* + * Assume we have already checked for the header + * before calling this function + */ + refname_start += header_len; + + return refname_start; +} + +/* + * Returns whether a reference is stored per worktree or not. + * Per-worktree references are: + * + * - all pseudorefs, e.g. HEAD and MERGE_HEAD + * - all references stored inside of "refs/bisect/" + */ +static bool is_per_worktree_ref(const char *ref_name) +{ + return git__prefixcmp(ref_name, "refs/") != 0 || + git__prefixcmp(ref_name, "refs/bisect/") == 0; +} + +static int loose_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + git_str ref_file = GIT_STR_INIT; + int error = 0; + const char *ref_dir; + + if (out) + *out = NULL; + + if (is_per_worktree_ref(ref_name)) + ref_dir = backend->gitpath; + else + ref_dir = backend->commonpath; + + if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0) + /* cannot read loose ref file - gah */; + else if (git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF) == 0) { + const char *target; + + git_str_rtrim(&ref_file); + + if (!(target = loose_parse_symbolic(&ref_file))) + error = -1; + else if (out != NULL) + *out = git_reference__alloc_symbolic(ref_name, target); + } else { + git_oid oid; + + if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) && + out != NULL) + *out = git_reference__alloc(ref_name, &oid, NULL); + } + + git_str_dispose(&ref_file); + return error; +} + +static int ref_error_notfound(const char *name) +{ + git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name); + return GIT_ENOTFOUND; +} + +static char *packed_set_peeling_mode( + char *data, + size_t data_sz, + refdb_fs_backend *backend) +{ + static const char *traits_header = "# pack-refs with:"; + char *eol; + backend->peeling_mode = PEELING_NONE; + + if (git__prefixncmp(data, data_sz, traits_header) == 0) { + size_t hdr_sz = strlen(traits_header); + const char *sorted = " sorted "; + const char *peeled = " peeled "; + const char *fully_peeled = " fully-peeled "; + data += hdr_sz; + data_sz -= hdr_sz; + + eol = memchr(data, '\n', data_sz); + + if (!eol) + return NULL; + + if (git__memmem(data, eol - data, fully_peeled, strlen(fully_peeled))) + backend->peeling_mode = PEELING_FULL; + else if (git__memmem(data, eol - data, peeled, strlen(peeled))) + backend->peeling_mode = PEELING_STANDARD; + + backend->sorted = NULL != git__memmem(data, eol - data, sorted, strlen(sorted)); + + return eol + 1; + } + return data; +} + +static void packed_map_free(refdb_fs_backend *backend) +{ + if (backend->packed_refs_map.data) { +#ifdef GIT_WIN32 + git__free(backend->packed_refs_map.data); +#else + git_futils_mmap_free(&backend->packed_refs_map); +#endif + backend->packed_refs_map.data = NULL; + backend->packed_refs_map.len = 0; + git_futils_filestamp_set(&backend->packed_refs_stamp, NULL); + } +} + +static int packed_map_check(refdb_fs_backend *backend) +{ + int error = 0; + git_file fd = -1; + struct stat st; + + if ((error = git_mutex_lock(&backend->prlock)) < 0) + return error; + + if (backend->packed_refs_map.data && + !git_futils_filestamp_check( + &backend->packed_refs_stamp, backend->refcache->path)) { + git_mutex_unlock(&backend->prlock); + return error; + } + packed_map_free(backend); + + fd = git_futils_open_ro(backend->refcache->path); + if (fd < 0) { + git_mutex_unlock(&backend->prlock); + if (fd == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + return fd; + } + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_mutex_unlock(&backend->prlock); + git_error_set(GIT_ERROR_OS, "unable to stat packed-refs '%s'", backend->refcache->path); + return -1; + } + + if (st.st_size == 0) { + p_close(fd); + git_mutex_unlock(&backend->prlock); + return 0; + } + + git_futils_filestamp_set_from_stat(&backend->packed_refs_stamp, &st); + +#ifdef GIT_WIN32 + /* on windows, we copy the entire file into memory rather than using + * mmap() because using mmap() on windows also locks the file and this + * map is long-lived. */ + backend->packed_refs_map.len = (size_t)st.st_size; + backend->packed_refs_map.data = + git__malloc(backend->packed_refs_map.len); + GIT_ERROR_CHECK_ALLOC(backend->packed_refs_map.data); + { + ssize_t bytesread = + p_read(fd, backend->packed_refs_map.data, + backend->packed_refs_map.len); + error = (bytesread == (ssize_t)backend->packed_refs_map.len) ? 0 : -1; + } +#else + error = git_futils_mmap_ro(&backend->packed_refs_map, fd, 0, (size_t)st.st_size); +#endif + p_close(fd); + if (error < 0) { + git_mutex_unlock(&backend->prlock); + return error; + } + + packed_set_peeling_mode( + backend->packed_refs_map.data, backend->packed_refs_map.len, + backend); + + git_mutex_unlock(&backend->prlock); + return error; +} + +/* + * Find beginning of packed-ref record pointed to by p. + * buf - a lower-bound pointer to some memory buffer + * p - an upper-bound pointer to the same memory buffer + */ +static const char *start_of_record(const char *buf, const char *p) +{ + const char *nl = p; + while (true) { + nl = git__memrchr(buf, '\n', nl - buf); + if (!nl) + return buf; + + if (nl[1] == '^' && nl > buf) + --nl; + else + break; + }; + return nl + 1; +} + +/* + * Find end of packed-ref record pointed to by p. + * end - an upper-bound pointer to some memory buffer + * p - a lower-bound pointer to the same memory buffer + */ +static const char *end_of_record(const char *p, const char *end) +{ + while (1) { + size_t sz = end - p; + p = memchr(p, '\n', sz); + if (!p) + return end; + ++p; + if (p < end && p[0] == '^') + ++p; + else + break; + } + return p; +} + +static int +cmp_record_to_refname(const char *rec, size_t data_end, const char *ref_name) +{ + const size_t ref_len = strlen(ref_name); + int cmp_val; + const char *end; + + rec += GIT_OID_HEXSZ + 1; /* + space */ + if (data_end < GIT_OID_HEXSZ + 3) { + /* an incomplete (corrupt) record is treated as less than ref_name */ + return -1; + } + data_end -= GIT_OID_HEXSZ + 1; + + end = memchr(rec, '\n', data_end); + if (end) + data_end = end - rec; + + cmp_val = memcmp(rec, ref_name, min(ref_len, data_end)); + + if (cmp_val == 0 && data_end != ref_len) + return (data_end > ref_len) ? 1 : -1; + return cmp_val; +} + +static int packed_unsorted_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + int error = 0; + struct packref *entry; + + if ((error = packed_reload(backend)) < 0) + return error; + + if (git_sortedcache_rlock(backend->refcache) < 0) + return -1; + + entry = git_sortedcache_lookup(backend->refcache, ref_name); + if (!entry) { + error = ref_error_notfound(ref_name); + } else { + *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel); + if (!*out) + error = -1; + } + + git_sortedcache_runlock(backend->refcache); + + return error; +} + +static int packed_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + int error = 0; + const char *left, *right, *data_end; + + if ((error = packed_map_check(backend)) < 0) + return error; + + if (!backend->sorted) + return packed_unsorted_lookup(out, backend, ref_name); + + left = backend->packed_refs_map.data; + right = data_end = (const char *) backend->packed_refs_map.data + + backend->packed_refs_map.len; + + while (left < right && *left == '#') { + if (!(left = memchr(left, '\n', data_end - left))) + goto parse_failed; + left++; + } + + while (left < right) { + const char *mid, *rec; + int compare; + + mid = left + (right - left) / 2; + rec = start_of_record(left, mid); + compare = cmp_record_to_refname(rec, data_end - rec, ref_name); + + if (compare < 0) { + left = end_of_record(mid, right); + } else if (compare > 0) { + right = rec; + } else { + const char *eol; + git_oid oid, peel, *peel_ptr = NULL; + + if (data_end - rec < GIT_OID_HEXSZ || + git_oid_fromstr(&oid, rec) < 0) { + goto parse_failed; + } + rec += GIT_OID_HEXSZ + 1; + if (!(eol = memchr(rec, '\n', data_end - rec))) { + goto parse_failed; + } + + /* look for optional "^\n" */ + + if (eol + 1 < data_end) { + rec = eol + 1; + + if (*rec == '^') { + rec++; + if (data_end - rec < GIT_OID_HEXSZ || + git_oid_fromstr(&peel, rec) < 0) { + goto parse_failed; + } + peel_ptr = &peel; + } + } + + *out = git_reference__alloc(ref_name, &oid, peel_ptr); + if (!*out) { + return -1; + } + + return 0; + } + } + return ref_error_notfound(ref_name); + +parse_failed: + git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); + return -1; +} + +static int refdb_fs_backend__lookup( + git_reference **out, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + int error; + + GIT_ASSERT_ARG(backend); + + if (!(error = loose_lookup(out, backend, ref_name))) + return 0; + + /* only try to lookup this reference on the packfile if it + * wasn't found on the loose refs; not if there was a critical error */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = packed_lookup(out, backend, ref_name); + } + return error; +} + +typedef struct { + git_reference_iterator parent; + + char *glob; + + git_pool pool; + git_vector loose; + + git_sortedcache *cache; + size_t loose_pos; + size_t packed_pos; +} refdb_fs_iter; + +static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) +{ + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); + + git_vector_free(&iter->loose); + git_pool_clear(&iter->pool); + git_sortedcache_free(iter->cache); + git__free(iter); +} + +static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) +{ + int error = 0; + git_str path = GIT_STR_INIT; + git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry = NULL; + const char *ref_prefix = GIT_REFS_DIR; + size_t ref_prefix_len = strlen(ref_prefix); + + if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ + return 0; + + fsit_opts.flags = backend->iterator_flags; + + if (iter->glob) { + const char *last_sep = NULL; + const char *pos; + for (pos = iter->glob; *pos; ++pos) { + switch (*pos) { + case '?': + case '*': + case '[': + case '\\': + break; + case '/': + last_sep = pos; + /* FALLTHROUGH */ + default: + continue; + } + break; + } + if (last_sep) { + ref_prefix = iter->glob; + ref_prefix_len = (last_sep - ref_prefix) + 1; + } + } + + if ((error = git_str_puts(&path, backend->commonpath)) < 0 || + (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) { + git_str_dispose(&path); + return error; + } + + if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { + git_str_dispose(&path); + return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; + } + + error = git_str_sets(&path, ref_prefix); + + while (!error && !git_iterator_advance(&entry, fsit)) { + const char *ref_name; + char *ref_dup; + + git_str_truncate(&path, ref_prefix_len); + git_str_puts(&path, entry->path); + ref_name = git_str_cstr(&path); + + if (git__suffixcmp(ref_name, ".lock") == 0 || + (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) + continue; + + ref_dup = git_pool_strdup(&iter->pool, ref_name); + if (!ref_dup) + error = -1; + else + error = git_vector_insert(&iter->loose, ref_dup); + } + + git_iterator_free(fsit); + git_str_dispose(&path); + + return error; +} + +static int refdb_fs_backend__iterator_next( + git_reference **out, git_reference_iterator *_iter) +{ + int error = GIT_ITEROVER; + 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) { + ref = git_sortedcache_lookup(iter->cache, path); + if (ref) + ref->flags |= PACKREF_SHADOWED; + + return 0; + } + + git_error_clear(); + } + + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; + + if (ref->flags & PACKREF_SHADOWED) + continue; + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) + continue; + + *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); + error = (*out != NULL) ? 0 : -1; + break; + } + + return error; +} + +static int refdb_fs_backend__iterator_next_name( + const char **out, git_reference_iterator *_iter) +{ + int error = GIT_ITEROVER; + 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; + } + + git_error_clear(); + } + + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; + + if (ref->flags & PACKREF_SHADOWED) + continue; + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) + continue; + + *out = ref->name; + error = 0; + break; + } + + return error; +} + +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; + + GIT_ASSERT_ARG(backend); + + iter = git__calloc(1, sizeof(refdb_fs_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + if ((error = git_pool_init(&iter->pool, 1)) < 0) + goto out; + + if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0) + goto out; + + if (glob != NULL && + (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; + + *out = (git_reference_iterator *)iter; +out: + if (error) + refdb_fs_backend__iterator_free((git_reference_iterator *)iter); + return error; +} + +static bool ref_is_available( + const char *old_ref, const char *new_ref, const char *this_ref) +{ + if (old_ref == NULL || strcmp(old_ref, this_ref)) { + size_t reflen = strlen(this_ref); + size_t newlen = strlen(new_ref); + size_t cmplen = reflen < newlen ? reflen : newlen; + const char *lead = reflen < newlen ? new_ref : this_ref; + + if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') { + return false; + } + } + + return true; +} + +static int reference_path_available( + refdb_fs_backend *backend, + const char *new_ref, + const char *old_ref, + int force) +{ + size_t i; + int error; + + if ((error = packed_reload(backend)) < 0) + return error; + + if (!force) { + int exists; + + if ((error = refdb_fs_backend__exists( + &exists, (git_refdb_backend *)backend, new_ref)) < 0) { + return error; + } + + if (exists) { + git_error_set(GIT_ERROR_REFERENCE, + "failed to write reference '%s': a reference with " + "that name already exists.", new_ref); + return GIT_EEXISTS; + } + } + + if ((error = git_sortedcache_rlock(backend->refcache)) < 0) + return error; + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); + + if (ref && !ref_is_available(old_ref, new_ref, ref->name)) { + git_sortedcache_runlock(backend->refcache); + git_error_set(GIT_ERROR_REFERENCE, + "path to reference '%s' collides with existing one", new_ref); + return -1; + } + } + + git_sortedcache_runlock(backend->refcache); + return 0; +} + +static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name) +{ + int error, filebuf_flags; + git_str ref_path = GIT_STR_INIT; + const char *basedir; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(name); + + if (!git_path_is_valid(backend->repo, name, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { + git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name); + return GIT_EINVALIDSPEC; + } + + if (is_per_worktree_ref(name)) + basedir = backend->gitpath; + else + basedir = backend->commonpath; + + /* Remove a possibly existing empty directory hierarchy + * which name would collide with the reference name + */ + if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0) + return error; + + if ((error = loose_path(&ref_path, basedir, name)) < 0) + return error; + + filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS; + if (backend->fsync) + filebuf_flags |= GIT_FILEBUF_FSYNC; + + error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE); + + if (error == GIT_EDIRECTORY) + git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name); + + git_str_dispose(&ref_path); + return error; +} + +static int loose_commit(git_filebuf *file, const git_reference *ref) +{ + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(ref); + + if (ref->type == GIT_REFERENCE_DIRECT) { + char oid[GIT_OID_HEXSZ + 1]; + git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); + + git_filebuf_printf(file, "%s\n", oid); + } else if (ref->type == GIT_REFERENCE_SYMBOLIC) { + git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic); + } else { + GIT_ASSERT(0); + } + + return git_filebuf_commit(file); +} + +static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname) +{ + int error; + git_filebuf *lock; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + lock = git__calloc(1, sizeof(git_filebuf)); + GIT_ERROR_CHECK_ALLOC(lock); + + if ((error = loose_lock(lock, backend, refname)) < 0) { + git__free(lock); + return error; + } + + *out = lock; + return 0; +} + +static int refdb_fs_backend__write_tail( + git_refdb_backend *_backend, + const git_reference *ref, + git_filebuf *file, + int update_reflog, + const git_oid *old_id, + 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); + +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) +{ + git_filebuf *lock = (git_filebuf *) payload; + int error = 0; + + 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, NULL, NULL, sig, message); + else + git_filebuf_cleanup(lock); + + git__free(lock); + return error; +} + +/* + * Find out what object this reference resolves to. + * + * For references that point to a 'big' tag (e.g. an + * actual tag object on the repository), we need to + * cache on the packfile the OID of the object to + * which that 'big tag' is pointing to. + */ +static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) +{ + git_object *object; + + if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL) + return 0; + + /* + * Find the tagged object in the repository + */ + if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0) + return -1; + + /* + * If the tagged object is a Tag object, we need to resolve it; + * if the ref is actually a 'weak' ref, we don't need to resolve + * anything. + */ + if (git_object_type(object) == GIT_OBJECT_TAG) { + git_tag *tag = (git_tag *)object; + + /* + * Find the object pointed at by this tag + */ + git_oid_cpy(&ref->peel, git_tag_target_id(tag)); + ref->flags |= PACKREF_HAS_PEEL; + + /* + * The reference has now cached the resolved OID, and is + * marked at such. When written to the packfile, it'll be + * accompanied by this resolved oid + */ + } + + git_object_free(object); + return 0; +} + +/* + * Write a single reference into a packfile + */ +static int packed_write_ref(struct packref *ref, git_filebuf *file) +{ + char oid[GIT_OID_HEXSZ + 1]; + git_oid_nfmt(oid, sizeof(oid), &ref->oid); + + /* + * For references that peel to an object in the repo, we must + * write the resulting peel on a separate line, e.g. + * + * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 + * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 + * + * This obviously only applies to tags. + * The required peels have already been loaded into `ref->peel_target`. + */ + if (ref->flags & PACKREF_HAS_PEEL) { + char peel[GIT_OID_HEXSZ + 1]; + git_oid_nfmt(peel, sizeof(peel), &ref->peel); + + if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) + return -1; + } else { + if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) + return -1; + } + + return 0; +} + +/* + * Remove all loose references + * + * Once we have successfully written a packfile, + * all the loose references that were packed must be + * removed from disk. + * + * This is a dangerous method; make sure the packfile + * is well-written, because we are destructing references + * here otherwise. + */ +static int packed_remove_loose(refdb_fs_backend *backend) +{ + size_t i; + git_filebuf lock = GIT_FILEBUF_INIT; + git_str ref_content = GIT_STR_INIT; + int error = 0; + + /* backend->refcache is already locked when this is called */ + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); + git_oid current_id; + + if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) + continue; + + git_filebuf_cleanup(&lock); + + /* We need to stop anybody from updating the ref while we try to do a safe delete */ + error = loose_lock(&lock, backend, ref->name); + /* If someone else is updating it, let them do it */ + if (error == GIT_EEXISTS || error == GIT_ENOTFOUND) + continue; + + if (error < 0) { + git_str_dispose(&ref_content); + git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name); + return error; + } + + error = git_futils_readbuffer(&ref_content, lock.path_original); + /* Someone else beat us to cleaning up the ref, let's simply continue */ + if (error == GIT_ENOTFOUND) + continue; + + /* This became a symref between us packing and trying to delete it, so ignore it */ + if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF)) + continue; + + /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */ + if (loose_parse_oid(¤t_id, lock.path_original, &ref_content) < 0) + continue; + + /* If the ref moved since we packed it, we must not delete it */ + if (!git_oid_equal(¤t_id, &ref->oid)) + continue; + + /* + * if we fail to remove a single file, this is *not* good, + * but we should keep going and remove as many as possible. + * If we fail to remove, the ref is still in the old state, so + * we haven't lost information. + */ + p_unlink(lock.path_original); + } + + git_str_dispose(&ref_content); + git_filebuf_cleanup(&lock); + return 0; +} + +/* + * Write all the contents in the in-memory packfile to disk. + */ +static int packed_write(refdb_fs_backend *backend) +{ + git_sortedcache *refcache = backend->refcache; + git_filebuf pack_file = GIT_FILEBUF_INIT; + int error, open_flags = 0; + size_t i; + + /* take lock and close up packed-refs mmap if open */ + if ((error = git_mutex_lock(&backend->prlock)) < 0) { + return error; + } + + packed_map_free(backend); + + git_mutex_unlock(&backend->prlock); + + /* lock the cache to updates while we do this */ + if ((error = git_sortedcache_wlock(refcache)) < 0) + return error; + + if (backend->fsync) + open_flags = GIT_FILEBUF_FSYNC; + + /* Open the file! */ + if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0) + goto fail; + + /* Packfiles have a header... apparently + * This is in fact not required, but we might as well print it + * just for kicks */ + if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0) + goto fail; + + for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { + struct packref *ref = git_sortedcache_entry(refcache, i); + + GIT_ASSERT_WITH_CLEANUP(ref, { + error = -1; + goto fail; + }); + + if ((error = packed_find_peel(backend, ref)) < 0) + goto fail; + + if ((error = packed_write_ref(ref, &pack_file)) < 0) + goto fail; + } + + /* if we've written all the references properly, we can commit + * the packfile to make the changes effective */ + if ((error = git_filebuf_commit(&pack_file)) < 0) + goto fail; + + /* when and only when the packfile has been properly written, + * we can go ahead and remove the loose refs */ + if ((error = packed_remove_loose(backend)) < 0) + goto fail; + + git_sortedcache_updated(refcache); + git_sortedcache_wunlock(refcache); + + /* we're good now */ + return 0; + +fail: + git_filebuf_cleanup(&pack_file); + git_sortedcache_wunlock(refcache); + + 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 cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name, + const git_oid *old_id, const char *old_target) +{ + int error = 0; + git_reference *old_ref = NULL; + + *cmp = 0; + /* It "matches" if there is no old value to compare against */ + if (!old_id && !old_target) + return 0; + + if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) { + if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id)) + return 0; + goto out; + } + + /* If the types don't match, there's no way the values do */ + if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) { + *cmp = -1; + goto out; + } + if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) { + *cmp = 1; + goto out; + } + + if (old_id && old_ref->type == GIT_REFERENCE_DIRECT) + *cmp = git_oid_cmp(old_id, &old_ref->target.oid); + + if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC) + *cmp = git__strcmp(old_target, old_ref->target.symbolic); + +out: + git_reference_free(old_ref); + + return error; +} + +/* + * The git.git comment regarding this, for your viewing pleasure: + * + * Special hack: If a branch is updated directly and HEAD + * points to it (may happen on the remote side of a push + * for example) then logically the HEAD reflog should be + * updated too. + * A generic solution implies reverse symref information, + * but finding all symrefs pointing to the given branch + * would be rather costly for this rare event (the direct + * update of a branch) to be worth it. So let's cheat and + * check with HEAD only which should cover 99% of all usage + * scenarios (even 100% of the default ones). + */ +static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message) +{ + git_reference *head = NULL; + git_refdb *refdb = NULL; + int error, write_reflog; + git_oid old_id; + + if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 || + (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0) + goto out; + if (!write_reflog) + goto out; + + /* if we can't resolve, we use {0}*40 as old id */ + if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0) + memset(&old_id, 0, sizeof(old_id)); + + if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 || + (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0) + goto out; + +out: + git_reference_free(head); + git_refdb_free(refdb); + return error; +} + +static int refdb_fs_backend__write( + git_refdb_backend *_backend, + const git_reference *ref, + int force, + const git_signature *who, + const char *message, + const git_oid *old_id, + const char *old_target) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_filebuf file = GIT_FILEBUF_INIT; + int error = 0; + + GIT_ASSERT_ARG(backend); + + if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0) + return error; + + /* We need to perform the reflog append and old value check under the ref's lock */ + if ((error = loose_lock(&file, backend, ref->name)) < 0) + return error; + + return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message); +} + +static int refdb_fs_backend__write_tail( + git_refdb_backend *_backend, + const git_reference *ref, + git_filebuf *file, + int update_reflog, + const git_oid *old_id, + const char *old_target, + const git_signature *who, + const char *message) +{ + 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; + + if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0) + goto on_error; + + if (cmp) { + git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); + error = GIT_EMODIFIED; + goto on_error; + } + + if (ref->type == GIT_REFERENCE_SYMBOLIC) + new_target = ref->target.symbolic; + else + new_id = &ref->target.oid; + + error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + /* Don't update if we have the same value */ + if (!error && !cmp) { + error = 0; + goto on_error; /* not really error */ + } + + if (update_reflog) { + git_refdb *refdb; + + if ((error = git_repository_refdb__weakptr(&refdb, backend->repo)) < 0 || + (error = git_refdb_should_write_reflog(&should_write, refdb, ref)) < 0) + goto on_error; + + if (should_write) { + if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0) + goto on_error; + if ((error = maybe_append_head(backend, ref, who, message)) < 0) + goto on_error; + } + } + + return loose_commit(file, ref); + +on_error: + git_filebuf_cleanup(file); + return error; +} + +static int refdb_fs_backend__prune_refs( + refdb_fs_backend *backend, + const char *ref_name, + const char *prefix) +{ + git_str relative_path = GIT_STR_INIT; + git_str base_path = GIT_STR_INIT; + size_t commonlen; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(ref_name); + + if ((error = git_str_sets(&relative_path, ref_name)) < 0) + goto cleanup; + + git_fs_path_squash_slashes(&relative_path); + if ((commonlen = git_fs_path_common_dirlen("refs/heads/", git_str_cstr(&relative_path))) == strlen("refs/heads/") || + (commonlen = git_fs_path_common_dirlen("refs/tags/", git_str_cstr(&relative_path))) == strlen("refs/tags/") || + (commonlen = git_fs_path_common_dirlen("refs/remotes/", git_str_cstr(&relative_path))) == strlen("refs/remotes/")) { + + git_str_truncate(&relative_path, commonlen); + + if (prefix) + error = git_str_join3(&base_path, '/', + backend->commonpath, prefix, + git_str_cstr(&relative_path)); + else + error = git_str_joinpath(&base_path, + backend->commonpath, + git_str_cstr(&relative_path)); + + if (!error) + error = git_path_validate_str_length(NULL, &base_path); + + if (error < 0) + goto cleanup; + + error = git_futils_rmdir_r(ref_name + commonlen, + git_str_cstr(&base_path), + GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT); + + if (error == GIT_ENOTFOUND) + error = 0; + } + +cleanup: + git_str_dispose(&relative_path); + git_str_dispose(&base_path); + return error; +} + +static int refdb_fs_backend__delete( + git_refdb_backend *_backend, + const char *ref_name, + const git_oid *old_id, const char *old_target) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_filebuf file = GIT_FILEBUF_INIT; + int error = 0; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(ref_name); + + if ((error = loose_lock(&file, backend, ref_name)) < 0) + return error; + + if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) { + git_filebuf_cleanup(&file); + return error; + } + + 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_str path = GIT_STR_INIT; + int error = 0; + + if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0) + return error; + + error = p_unlink(path.ptr); + if (error < 0 && errno == ENOENT) + error = GIT_ENOTFOUND; + else if (error != 0) + error = -1; + + git_str_dispose(&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 = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + int error = 0, cmp = 0; + bool packed_deleted = 0; + + error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); + if (error < 0) + goto cleanup; + + if (cmp) { + git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); + error = GIT_EMODIFIED; + goto cleanup; + } + + /* + * 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 (error == 0) + packed_deleted = 1; + + if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (error == GIT_ENOTFOUND) { + error = packed_deleted ? 0 : ref_error_notfound(ref_name); + goto cleanup; + } + +cleanup: + git_filebuf_cleanup(file); + if (error == 0) + error = refdb_fs_backend__prune_refs(backend, ref_name, ""); + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name); + +static int refdb_fs_backend__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) +{ + 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; + + GIT_ASSERT_ARG(backend); + + if ((error = reference_path_available( + backend, new_name, old_name, force)) < 0 || + (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0) + return error; + + if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) { + git_reference_free(old); + return error; + } + + new = git_reference__realloc(&old, new_name); + if (!new) { + git_reference_free(old); + return -1; + } + + if ((error = loose_lock(&file, backend, new->name)) < 0) { + git_reference_free(new); + return error; + } + + /* Try to rename the refog; it's ok if the old doesn't exist */ + error = refdb_reflog_fs__rename(_backend, old_name, new_name); + if (((error == 0) || (error == GIT_ENOTFOUND)) && + ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) { + git_reference_free(new); + git_filebuf_cleanup(&file); + return error; + } + + if (error < 0) { + git_reference_free(new); + git_filebuf_cleanup(&file); + return error; + } + + + if ((error = loose_commit(&file, new)) < 0 || out == NULL) { + git_reference_free(new); + return error; + } + + *out = new; + return 0; +} + +static int refdb_fs_backend__compress(git_refdb_backend *_backend) +{ + int error; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + GIT_ASSERT_ARG(backend); + + if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */ + (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */ + (error = packed_write(backend)) < 0) /* write back to disk */ + return error; + + return 0; +} + +static void refdb_fs_backend__free(git_refdb_backend *_backend) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + if (!backend) + return; + + git_sortedcache_free(backend->refcache); + + git_mutex_lock(&backend->prlock); + packed_map_free(backend); + git_mutex_unlock(&backend->prlock); + git_mutex_free(&backend->prlock); + + git__free(backend->gitpath); + git__free(backend->commonpath); + git__free(backend); +} + +static char *setup_namespace(git_repository *repo, const char *in) +{ + git_str path = GIT_STR_INIT; + char *parts, *start, *end, *out = NULL; + + if (!in) + goto done; + + git_str_puts(&path, in); + + /* if the repo is not namespaced, nothing else to do */ + if (repo->namespace == NULL) { + out = git_str_detach(&path); + goto done; + } + + parts = end = git__strdup(repo->namespace); + if (parts == NULL) + goto done; + + /* + * From `man gitnamespaces`: + * namespaces which include a / will expand to a hierarchy + * of namespaces; for example, GIT_NAMESPACE=foo/bar will store + * refs under refs/namespaces/foo/refs/namespaces/bar/ + */ + while ((start = git__strsep(&end, "/")) != NULL) + git_str_printf(&path, "refs/namespaces/%s/", start); + + git_str_printf(&path, "refs/namespaces/%s/refs", end); + git__free(parts); + + /* Make sure that the folder with the namespace exists */ + if (git_futils_mkdir_relative(git_str_cstr(&path), in, 0777, + GIT_MKDIR_PATH, NULL) < 0) + goto done; + + /* Return root of the namespaced gitpath, i.e. without the trailing 'refs' */ + git_str_rtruncate_at_char(&path, '/'); + git_str_putc(&path, '/'); + out = git_str_detach(&path); + +done: + git_str_dispose(&path); + return out; +} + +static int reflog_alloc(git_reflog **reflog, const char *name) +{ + git_reflog *log; + + *reflog = NULL; + + log = git__calloc(1, sizeof(git_reflog)); + GIT_ERROR_CHECK_ALLOC(log); + + log->ref_name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(log->ref_name); + + if (git_vector_init(&log->entries, 0, NULL) < 0) { + git__free(log->ref_name); + git__free(log); + return -1; + } + + *reflog = log; + + return 0; +} + +static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) +{ + git_parse_ctx parser = GIT_PARSE_CTX_INIT; + + if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0) + return -1; + + 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(*entry->committer)); + GIT_ERROR_CHECK_ALLOC(entry->committer); + + 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; + + sig = parser.line; + while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n') + git_parse_advance_chars(&parser, 1); + + if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0) + goto next; + + if (c == '\t') { + size_t len; + git_parse_advance_chars(&parser, 1); + + len = parser.line_len; + if (parser.line[len - 1] == '\n') + len--; + + entry->msg = git__strndup(parser.line, len); + GIT_ERROR_CHECK_ALLOC(entry->msg); + } + + if ((git_vector_insert(&log->entries, entry)) < 0) { + git_reflog_entry__free(entry); + return -1; + } + + continue; + +next: + git_reflog_entry__free(entry); + } + + return 0; +} + +static int create_new_reflog_file(const char *filepath) +{ + int fd, error; + + if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + return error; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + +static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name) +{ + refdb_fs_backend *backend; + git_repository *repo; + git_str path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(_backend && name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + repo = backend->repo; + + if ((error = reflog_path(&path, repo, name)) < 0) + return error; + + error = create_new_reflog_file(git_str_cstr(&path)); + git_str_dispose(&path); + + return error; +} + +static int has_reflog(git_repository *repo, const char *name) +{ + int ret = 0; + git_str path = GIT_STR_INIT; + + if (reflog_path(&path, repo, name) < 0) + goto cleanup; + + ret = git_fs_path_isfile(git_str_cstr(&path)); + +cleanup: + git_str_dispose(&path); + return ret; +} + +static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name) +{ + refdb_fs_backend *backend; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + return has_reflog(backend->repo, name); +} + +static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) +{ + int error = -1; + git_str log_path = GIT_STR_INIT; + git_str log_file = GIT_STR_INIT; + git_reflog *log = NULL; + git_repository *repo; + refdb_fs_backend *backend; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + repo = backend->repo; + + if (reflog_alloc(&log, name) < 0) + return -1; + + if (reflog_path(&log_path, repo, name) < 0) + goto cleanup; + + error = git_futils_readbuffer(&log_file, git_str_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_str_cstr(&log_path))) < 0)) + goto cleanup; + + if ((error = reflog_parse(log, + git_str_cstr(&log_file), git_str_len(&log_file))) < 0) + goto cleanup; + + *out = log; + goto success; + +cleanup: + git_reflog_free(log); + +success: + git_str_dispose(&log_file); + git_str_dispose(&log_path); + + return error; +} + +static int serialize_reflog_entry( + git_str *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) +{ + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; + + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); + + git_str_clear(buf); + + git_str_puts(buf, raw_old); + git_str_putc(buf, ' '); + git_str_puts(buf, raw_new); + + git_signature__writebuf(buf, " ", committer); + + /* drop trailing LF */ + git_str_rtrim(buf); + + if (msg) { + size_t i; + + git_str_putc(buf, '\t'); + git_str_puts(buf, msg); + + for (i = 0; i < buf->size - 2; i++) + if (buf->ptr[i] == '\n') + buf->ptr[i] = ' '; + git_str_rtrim(buf); + } + + git_str_putc(buf, '\n'); + + return git_str_oom(buf); +} + +static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname) +{ + git_repository *repo; + git_str log_path = GIT_STR_INIT; + int error; + + repo = backend->repo; + + if (!git_path_is_valid(backend->repo, refname, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { + git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname); + return GIT_EINVALIDSPEC; + } + + if (reflog_path(&log_path, repo, refname) < 0) + return -1; + + if (!git_fs_path_isfile(git_str_cstr(&log_path))) { + git_error_set(GIT_ERROR_INVALID, + "log file for reference '%s' doesn't exist", refname); + error = -1; + goto cleanup; + } + + error = git_filebuf_open(file, git_str_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE); + +cleanup: + git_str_dispose(&log_path); + + return error; +} + +static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) +{ + int error = -1; + unsigned int i; + git_reflog_entry *entry; + refdb_fs_backend *backend; + git_str log = GIT_STR_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(reflog); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0) + return -1; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_str_dispose(&log); + + return error; +} + +/* Append to the reflog, must be called under reference lock */ +static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message) +{ + int error, is_symbolic, open_flags; + git_oid old_id = {{0}}, new_id = {{0}}; + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + git_repository *repo = backend->repo; + + is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC; + + /* "normal" symbolic updates do not write */ + if (is_symbolic && + strcmp(ref->name, GIT_HEAD_FILE) && + !(old && new)) + return 0; + + /* From here on is_symbolic also means that it's HEAD */ + + if (old) { + git_oid_cpy(&old_id, old); + } else { + error = git_reference_name_to_id(&old_id, repo, ref->name); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + } + + if (new) { + git_oid_cpy(&new_id, new); + } else { + if (!is_symbolic) { + git_oid_cpy(&new_id, git_reference_target(ref)); + } else { + error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref)); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + /* detaching HEAD does not create an entry */ + if (error == GIT_ENOTFOUND) + return 0; + + git_error_clear(); + } + } + + if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0) + goto cleanup; + + if ((error = reflog_path(&path, repo, ref->name)) < 0) + goto cleanup; + + if (((error = git_futils_mkpath2file(git_str_cstr(&path), 0777)) < 0) && + (error != GIT_EEXISTS)) { + goto cleanup; + } + + /* If the new branch matches part of the namespace of a previously deleted branch, + * there maybe an obsolete/unused directory (or directory hierarchy) in the way. + */ + if (git_fs_path_isdir(git_str_cstr(&path))) { + if ((error = git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + } else if (git_fs_path_isdir(git_str_cstr(&path))) { + git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder", + ref->name); + error = GIT_EDIRECTORY; + } + + if (error != 0) + goto cleanup; + } + + open_flags = O_WRONLY | O_CREAT | O_APPEND; + + if (backend->fsync) + open_flags |= O_FSYNC; + + error = git_futils_writebuffer(&buf, git_str_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE); + +cleanup: + git_str_dispose(&buf); + git_str_dispose(&path); + + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) +{ + int error = 0, fd; + git_str old_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT; + git_str temp_path = GIT_STR_INIT; + git_str normalized = GIT_STR_INIT; + git_repository *repo; + refdb_fs_backend *backend; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(old_name); + GIT_ASSERT_ARG(new_name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + repo = backend->repo; + + if ((error = git_reference__normalize_name( + &normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0) + return error; + + if (git_str_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0) + return -1; + + if ((error = loose_path(&old_path, git_str_cstr(&temp_path), old_name)) < 0) + return error; + + if ((error = loose_path(&new_path, git_str_cstr(&temp_path), git_str_cstr(&normalized))) < 0) + return error; + + if (!git_fs_path_exists(git_str_cstr(&old_path))) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + /* + * Move the reflog to a temporary place. This two-phase renaming is required + * in order to cope with funny renaming use cases when one tries to move a reference + * to a partially colliding namespace: + * - a/b -> a/b/c + * - a/b/c/d -> a/b/c + */ + if ((error = loose_path(&temp_path, git_str_cstr(&temp_path), "temp_reflog")) < 0) + return error; + + if ((fd = git_futils_mktmp(&temp_path, git_str_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) { + error = -1; + goto cleanup; + } + + p_close(fd); + + if (p_rename(git_str_cstr(&old_path), git_str_cstr(&temp_path)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); + error = -1; + goto cleanup; + } + + if (git_fs_path_isdir(git_str_cstr(&new_path)) && + (git_futils_rmdir_r(git_str_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { + error = -1; + goto cleanup; + } + + if (git_futils_mkpath2file(git_str_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { + error = -1; + goto cleanup; + } + + if (p_rename(git_str_cstr(&temp_path), git_str_cstr(&new_path)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); + error = -1; + } + +cleanup: + git_str_dispose(&temp_path); + git_str_dispose(&old_path); + git_str_dispose(&new_path); + git_str_dispose(&normalized); + + return error; +} + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_str path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(name); + + if ((error = reflog_path(&path, backend->repo, name)) < 0) + goto out; + + if (!git_fs_path_exists(path.ptr)) + goto out; + + if ((error = p_unlink(path.ptr)) < 0) + goto out; + + error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR); + +out: + git_str_dispose(&path); + + return error; +} + +int git_refdb_backend_fs( + git_refdb_backend **backend_out, + git_repository *repository) +{ + int t = 0; + git_str gitpath = GIT_STR_INIT; + refdb_fs_backend *backend; + + backend = git__calloc(1, sizeof(refdb_fs_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + if (git_mutex_init(&backend->prlock) < 0) { + git__free(backend); + return -1; + } + + + if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0) + goto fail; + + backend->repo = repository; + + if (repository->gitdir) { + backend->gitpath = setup_namespace(repository, repository->gitdir); + + if (backend->gitpath == NULL) + goto fail; + } + + if (repository->commondir) { + backend->commonpath = setup_namespace(repository, repository->commondir); + + if (backend->commonpath == NULL) + goto fail; + } + + if (git_str_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 || + git_sortedcache_new( + &backend->refcache, offsetof(struct packref, name), + NULL, NULL, packref_cmp, git_str_cstr(&gitpath)) < 0) + goto fail; + + git_str_dispose(&gitpath); + + if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) { + backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; + backend->direach_flags |= GIT_FS_PATH_DIR_IGNORE_CASE; + } + if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) { + backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + backend->direach_flags |= GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE; + } + 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; + + backend->parent.exists = &refdb_fs_backend__exists; + backend->parent.lookup = &refdb_fs_backend__lookup; + backend->parent.iterator = &refdb_fs_backend__iterator; + backend->parent.write = &refdb_fs_backend__write; + backend->parent.del = &refdb_fs_backend__delete; + backend->parent.rename = &refdb_fs_backend__rename; + backend->parent.compress = &refdb_fs_backend__compress; + backend->parent.lock = &refdb_fs_backend__lock; + backend->parent.unlock = &refdb_fs_backend__unlock; + backend->parent.has_log = &refdb_reflog_fs__has_log; + backend->parent.ensure_log = &refdb_reflog_fs__ensure_log; + backend->parent.free = &refdb_fs_backend__free; + backend->parent.reflog_read = &refdb_reflog_fs__read; + backend->parent.reflog_write = &refdb_reflog_fs__write; + backend->parent.reflog_rename = &refdb_reflog_fs__rename; + backend->parent.reflog_delete = &refdb_reflog_fs__delete; + + *backend_out = (git_refdb_backend *)backend; + return 0; + +fail: + git_mutex_free(&backend->prlock); + git_str_dispose(&gitpath); + git__free(backend->gitpath); + git__free(backend->commonpath); + git__free(backend); + return -1; +} diff --git a/src/libgit2/reflog.c b/src/libgit2/reflog.c new file mode 100644 index 000000000..1e9c0d4f1 --- /dev/null +++ b/src/libgit2/reflog.c @@ -0,0 +1,232 @@ +/* + * 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 "reflog.h" + +#include "repository.h" +#include "filebuf.h" +#include "signature.h" +#include "refdb.h" + +#include "git2/sys/refdb_backend.h" +#include "git2/sys/reflog.h" + +void git_reflog_entry__free(git_reflog_entry *entry) +{ + git_signature_free(entry->committer); + + git__free(entry->msg); + git__free(entry); +} + +void git_reflog_free(git_reflog *reflog) +{ + size_t i; + git_reflog_entry *entry; + + if (reflog == NULL) + return; + + if (reflog->db) + GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); + + for (i=0; i < reflog->entries.length; i++) { + entry = git_vector_get(&reflog->entries, i); + + git_reflog_entry__free(entry); + } + + git_vector_free(&reflog->entries); + git__free(reflog->ref_name); + git__free(reflog); +} + +int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) +{ + git_refdb *refdb; + int error; + + GIT_ASSERT_ARG(reflog); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + return git_refdb_reflog_read(reflog, refdb, name); +} + +int git_reflog_write(git_reflog *reflog) +{ + git_refdb *db; + + GIT_ASSERT_ARG(reflog); + GIT_ASSERT_ARG(reflog->db); + + db = reflog->db; + return db->backend->reflog_write(db->backend, reflog); +} + +int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) +{ + const git_reflog_entry *previous; + git_reflog_entry *entry; + + GIT_ASSERT_ARG(reflog); + GIT_ASSERT_ARG(new_oid); + GIT_ASSERT_ARG(committer); + + entry = git__calloc(1, sizeof(git_reflog_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + if ((git_signature_dup(&entry->committer, committer)) < 0) + goto cleanup; + + if (msg != NULL) { + size_t i, msglen = strlen(msg); + + if ((entry->msg = git__strndup(msg, msglen)) == NULL) + goto cleanup; + + /* + * 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); + + if (previous == NULL) + git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); + else + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + git_oid_cpy(&entry->oid_cur, new_oid); + + if (git_vector_insert(&reflog->entries, entry) < 0) + goto cleanup; + + return 0; + +cleanup: + git_reflog_entry__free(entry); + return -1; +} + +int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) +{ + git_refdb *refdb; + int error; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; + + return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); +} + +int git_reflog_delete(git_repository *repo, const char *name) +{ + git_refdb *refdb; + int error; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; + + return refdb->backend->reflog_delete(refdb->backend, name); +} + +size_t git_reflog_entrycount(git_reflog *reflog) +{ + GIT_ASSERT_ARG_WITH_RETVAL(reflog, 0); + return reflog->entries.length; +} + +const git_reflog_entry *git_reflog_entry_byindex(const git_reflog *reflog, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(reflog, NULL); + + if (idx >= reflog->entries.length) + return NULL; + + return git_vector_get( + &reflog->entries, reflog_inverse_index(idx, reflog->entries.length)); +} + +const git_oid *git_reflog_entry_id_old(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return &entry->oid_old; +} + +const git_oid *git_reflog_entry_id_new(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return &entry->oid_cur; +} + +const git_signature *git_reflog_entry_committer(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->committer; +} + +const char *git_reflog_entry_message(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->msg; +} + +int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry) +{ + size_t entrycount; + git_reflog_entry *entry, *previous; + + entrycount = git_reflog_entrycount(reflog); + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + + if (entry == NULL) { + git_error_set(GIT_ERROR_REFERENCE, "no reflog entry at index %"PRIuZ, idx); + return GIT_ENOTFOUND; + } + + git_reflog_entry__free(entry); + + if (git_vector_remove( + &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) + return -1; + + if (!rewrite_previous_entry) + return 0; + + /* No need to rewrite anything when removing the most recent entry */ + if (idx == 0) + return 0; + + /* Have the latest entry just been dropped? */ + if (entrycount == 1) + return 0; + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); + + /* If the oldest entry has just been removed... */ + if (idx == entrycount - 1) { + /* ...clear the oid_old member of the "new" oldest entry */ + if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) + return -1; + + return 0; + } + + previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + return 0; +} diff --git a/src/libgit2/reflog.h b/src/libgit2/reflog.h new file mode 100644 index 000000000..8c3895952 --- /dev/null +++ b/src/libgit2/reflog.h @@ -0,0 +1,41 @@ +/* + * 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_reflog_h__ +#define INCLUDE_reflog_h__ + +#include "common.h" + +#include "git2/reflog.h" +#include "vector.h" + +#define GIT_REFLOG_DIR "logs/" +#define GIT_REFLOG_DIR_MODE 0777 +#define GIT_REFLOG_FILE_MODE 0666 + +#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) + +struct git_reflog_entry { + git_oid oid_old; + git_oid oid_cur; + + git_signature *committer; + + char *msg; +}; + +struct git_reflog { + git_refdb *db; + char *ref_name; + git_vector entries; +}; + +GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) +{ + return (total - 1) - idx; +} + +#endif diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c new file mode 100644 index 000000000..5c875b95b --- /dev/null +++ b/src/libgit2/refs.c @@ -0,0 +1,1395 @@ +/* + * 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 "refs.h" + +#include "hash.h" +#include "repository.h" +#include "futils.h" +#include "filebuf.h" +#include "pack.h" +#include "reflog.h" +#include "refdb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool git_reference__enable_symbolic_ref_target_validation = true; + +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; + +static git_reference *alloc_ref(const char *name) +{ + git_reference *ref = NULL; + size_t namelen = strlen(name), reflen; + + if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && + !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && + (ref = git__calloc(1, reflen)) != NULL) + memcpy(ref->name, name, namelen + 1); + + return ref; +} + +git_reference *git_reference__alloc_symbolic( + const char *name, const char *target) +{ + git_reference *ref; + + GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(target, NULL); + + ref = alloc_ref(name); + if (!ref) + return NULL; + + ref->type = GIT_REFERENCE_SYMBOLIC; + + if ((ref->target.symbolic = git__strdup(target)) == NULL) { + git__free(ref); + return NULL; + } + + return ref; +} + +git_reference *git_reference__alloc( + const char *name, + const git_oid *oid, + const git_oid *peel) +{ + git_reference *ref; + + GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(oid, NULL); + + ref = alloc_ref(name); + if (!ref) + return NULL; + + ref->type = GIT_REFERENCE_DIRECT; + git_oid_cpy(&ref->target.oid, oid); + + if (peel != NULL) + git_oid_cpy(&ref->peel, peel); + + return ref; +} + +git_reference *git_reference__realloc( + git_reference **ptr_to_ref, const char *name) +{ + size_t namelen, reflen; + git_reference *rewrite = NULL; + + GIT_ASSERT_ARG_WITH_RETVAL(ptr_to_ref, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); + + namelen = strlen(name); + + if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && + !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && + (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL) + memcpy(rewrite->name, name, namelen + 1); + + *ptr_to_ref = NULL; + + return rewrite; +} + +int git_reference_dup(git_reference **dest, git_reference *source) +{ + if (source->type == GIT_REFERENCE_SYMBOLIC) + *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic); + else + *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel); + + GIT_ERROR_CHECK_ALLOC(*dest); + + (*dest)->db = source->db; + GIT_REFCOUNT_INC((*dest)->db); + + return 0; +} + +void git_reference_free(git_reference *reference) +{ + if (reference == NULL) + return; + + if (reference->type == GIT_REFERENCE_SYMBOLIC) + git__free(reference->target.symbolic); + + if (reference->db) + GIT_REFCOUNT_DEC(reference->db, git_refdb__free); + + git__free(reference); +} + +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 + old_target = ref->target.symbolic; + + return git_refdb_delete(ref->db, ref->name, old_id, old_target); +} + +int git_reference_remove(git_repository *repo, const char *name) +{ + git_refdb *db; + int error; + + if ((error = git_repository_refdb__weakptr(&db, repo)) < 0) + return error; + + return git_refdb_delete(db, name, NULL, NULL); +} + +int git_reference_lookup(git_reference **ref_out, + git_repository *repo, const char *name) +{ + return git_reference_lookup_resolved(ref_out, repo, name, 0); +} + +int git_reference_name_to_id( + git_oid *out, git_repository *repo, const char *name) +{ + int error; + git_reference *ref; + + if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) + return error; + + git_oid_cpy(out, git_reference_target(ref)); + git_reference_free(ref); + return 0; +} + +static int reference_normalize_for_repo( + git_refname_t out, + git_repository *repo, + const char *name, + bool validate) +{ + int precompose; + unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL; + + if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) && + precompose) + flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE; + + if (!validate) + flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE; + + return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); +} + +int git_reference_lookup_resolved( + git_reference **ref_out, + git_repository *repo, + const char *name, + int max_nesting) +{ + git_refname_t normalized; + git_refdb *refdb; + int error = 0; + + GIT_ASSERT_ARG(ref_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 || + (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 || + (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0) + return error; + + /* + * The resolved reference may be a symbolic reference in case its + * target doesn't exist. If the user asked us to resolve (e.g. + * `max_nesting != 0`), then we need to return an error in case we got + * a symbolic reference back. + */ + if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) { + git_reference_free(*ref_out); + *ref_out = NULL; + return GIT_ENOTFOUND; + } + + return 0; +} + +int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname) +{ + int error = 0, i, valid; + bool fallbackmode = true, foundvalid = false; + git_reference *ref; + git_str refnamebuf = GIT_STR_INIT, name = GIT_STR_INIT; + + static const char *formatters[] = { + "%s", + GIT_REFS_DIR "%s", + GIT_REFS_TAGS_DIR "%s", + GIT_REFS_HEADS_DIR "%s", + GIT_REFS_REMOTES_DIR "%s", + GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE, + NULL + }; + + if (*refname) + git_str_puts(&name, refname); + else { + git_str_puts(&name, GIT_HEAD_FILE); + fallbackmode = false; + } + + for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) { + + git_str_clear(&refnamebuf); + + if ((error = git_str_printf(&refnamebuf, formatters[i], git_str_cstr(&name))) < 0 || + (error = git_reference_name_is_valid(&valid, git_str_cstr(&refnamebuf))) < 0) + goto cleanup; + + if (!valid) { + error = GIT_EINVALIDSPEC; + continue; + } + foundvalid = true; + + error = git_reference_lookup_resolved(&ref, repo, git_str_cstr(&refnamebuf), -1); + + if (!error) { + *out = ref; + error = 0; + goto cleanup; + } + + if (error != GIT_ENOTFOUND) + goto cleanup; + } + +cleanup: + if (error && !foundvalid) { + /* never found a valid reference name */ + git_error_set(GIT_ERROR_REFERENCE, + "could not use '%s' as valid reference name", git_str_cstr(&name)); + } + + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname); + + git_str_dispose(&name); + git_str_dispose(&refnamebuf); + return error; +} + +/** + * Getters + */ +git_reference_t git_reference_type(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return ref->type; +} + +const char *git_reference_name(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + return ref->name; +} + +git_repository *git_reference_owner(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + return ref->db->repo; +} + +const git_oid *git_reference_target(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + + if (ref->type != GIT_REFERENCE_DIRECT) + return NULL; + + return &ref->target.oid; +} + +const git_oid *git_reference_target_peel(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + + if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel)) + return NULL; + + return &ref->peel; +} + +const char *git_reference_symbolic_target(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + + if (ref->type != GIT_REFERENCE_SYMBOLIC) + return NULL; + + return ref->target.symbolic; +} + +static int reference__create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *oid, + const char *symbolic, + int force, + const git_signature *signature, + const char *log_message, + const git_oid *old_id, + const char *old_target) +{ + git_refname_t normalized; + git_refdb *refdb; + git_reference *ref = NULL; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(symbolic || signature); + + if (ref_out) + *ref_out = NULL; + + error = reference_normalize_for_repo(normalized, repo, name, true); + if (error < 0) + return error; + + error = git_repository_refdb__weakptr(&refdb, repo); + if (error < 0) + return error; + + if (oid != NULL) { + GIT_ASSERT(symbolic == NULL); + + if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) { + git_error_set(GIT_ERROR_REFERENCE, + "target OID for the reference doesn't exist on the repository"); + return -1; + } + + ref = git_reference__alloc(normalized, oid, NULL); + } else { + git_refname_t normalized_target; + + error = reference_normalize_for_repo(normalized_target, repo, + symbolic, git_reference__enable_symbolic_ref_target_validation); + + if (error < 0) + return error; + + ref = git_reference__alloc_symbolic(normalized, normalized_target); + } + + GIT_ERROR_CHECK_ALLOC(ref); + + if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) { + git_reference_free(ref); + return error; + } + + if (ref_out == NULL) + git_reference_free(ref); + else + *ref_out = ref; + + return 0; +} + +static int refs_configured_ident(git_signature **out, const git_repository *repo) +{ + if (repo->ident_name && repo->ident_email) + return git_signature_now(out, repo->ident_name, repo->ident_email); + + /* if not configured let us fall-through to the next method */ + return -1; +} + +int git_reference__log_signature(git_signature **out, git_repository *repo) +{ + int error; + git_signature *who; + + if(((error = refs_configured_ident(&who, repo)) < 0) && + ((error = git_signature_default(&who, repo)) < 0) && + ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) + return error; + + *out = who; + return 0; +} + +int git_reference_create_matching( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force, + const git_oid *old_id, + const char *log_message) + +{ + int error; + git_signature *who = NULL; + + GIT_ASSERT_ARG(id); + + if ((error = git_reference__log_signature(&who, repo)) < 0) + return error; + + error = reference__create( + ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL); + + git_signature_free(who); + return error; +} + +int git_reference_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force, + const char *log_message) +{ + return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message); +} + +int git_reference_symbolic_create_matching( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force, + const char *old_target, + const char *log_message) +{ + int error; + git_signature *who = NULL; + + GIT_ASSERT_ARG(target); + + if ((error = git_reference__log_signature(&who, repo)) < 0) + return error; + + error = reference__create( + ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target); + + git_signature_free(who); + return error; +} + +int git_reference_symbolic_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force, + const char *log_message) +{ + return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message); +} + +static int ensure_is_an_updatable_direct_reference(git_reference *ref) +{ + if (ref->type == GIT_REFERENCE_DIRECT) + return 0; + + git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference"); + return -1; +} + +int git_reference_set_target( + git_reference **out, + git_reference *ref, + const git_oid *id, + const char *log_message) +{ + int error; + git_repository *repo; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + GIT_ASSERT_ARG(id); + + repo = ref->db->repo; + + if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) + return error; + + return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message); +} + +static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) +{ + if (ref->type == GIT_REFERENCE_SYMBOLIC) + return 0; + + git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference"); + return -1; +} + +int git_reference_symbolic_set_target( + git_reference **out, + git_reference *ref, + const char *target, + const char *log_message) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + GIT_ASSERT_ARG(target); + + if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0) + return error; + + return git_reference_symbolic_create_matching( + out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message); +} + +typedef struct { + const char *old_name; + git_refname_t new_name; +} refs_update_head_payload; + +static int refs_update_head(git_repository *worktree, void *_payload) +{ + refs_update_head_payload *payload = (refs_update_head_payload *)_payload; + git_reference *head = NULL, *updated = NULL; + int error; + + if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) + goto out; + + if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC || + git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0) + goto out; + + /* Update HEAD if it was pointing to the reference being renamed */ + if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) { + git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference"); + goto out; + } + +out: + git_reference_free(updated); + git_reference_free(head); + return error; +} + +int git_reference_rename( + git_reference **out, + git_reference *ref, + const char *new_name, + int force, + const char *log_message) +{ + refs_update_head_payload payload; + git_signature *signature = NULL; + git_repository *repo; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + + repo = git_reference_owner(ref); + + if ((error = git_reference__log_signature(&signature, repo)) < 0 || + (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 || + (error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0) + goto out; + + payload.old_name = ref->name; + + /* We may have to update any HEAD that was pointing to the renamed reference. */ + if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0) + goto out; + +out: + git_signature_free(signature); + return error; +} + +int git_reference_resolve(git_reference **ref_out, const git_reference *ref) +{ + switch (git_reference_type(ref)) { + case GIT_REFERENCE_DIRECT: + return git_reference_lookup(ref_out, ref->db->repo, ref->name); + + case GIT_REFERENCE_SYMBOLIC: + return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1); + + default: + git_error_set(GIT_ERROR_REFERENCE, "invalid reference"); + return -1; + } +} + +int git_reference_foreach( + git_repository *repo, + git_reference_foreach_cb callback, + void *payload) +{ + git_reference_iterator *iter; + git_reference *ref; + int error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + return error; + + while (!(error = git_reference_next(&ref, iter))) { + if ((error = callback(ref, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_reference_iterator_free(iter); + return error; +} + +int git_reference_foreach_name( + git_repository *repo, + git_reference_foreach_name_cb callback, + void *payload) +{ + git_reference_iterator *iter; + const char *refname; + int error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + return error; + + while (!(error = git_reference_next_name(&refname, iter))) { + if ((error = callback(refname, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_reference_iterator_free(iter); + return error; +} + +int git_reference_foreach_glob( + git_repository *repo, + const char *glob, + git_reference_foreach_name_cb callback, + void *payload) +{ + git_reference_iterator *iter; + const char *refname; + int error; + + if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) + return error; + + while (!(error = git_reference_next_name(&refname, iter))) { + if ((error = callback(refname, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_reference_iterator_free(iter); + return error; +} + +int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo) +{ + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + + return git_refdb_iterator(out, refdb, NULL); +} + +int git_reference_iterator_glob_new( + git_reference_iterator **out, git_repository *repo, const char *glob) +{ + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + + return git_refdb_iterator(out, refdb, glob); +} + +int git_reference_next(git_reference **out, git_reference_iterator *iter) +{ + return git_refdb_iterator_next(out, iter); +} + +int git_reference_next_name(const char **out, git_reference_iterator *iter) +{ + return git_refdb_iterator_next_name(out, iter); +} + +void git_reference_iterator_free(git_reference_iterator *iter) +{ + if (iter == NULL) + return; + + git_refdb_iterator_free(iter); +} + +static int cb__reflist_add(const char *ref, void *data) +{ + char *name = git__strdup(ref); + GIT_ERROR_CHECK_ALLOC(name); + return git_vector_insert((git_vector *)data, name); +} + +int git_reference_list( + git_strarray *array, + git_repository *repo) +{ + git_vector ref_list; + + GIT_ASSERT_ARG(array); + GIT_ASSERT_ARG(repo); + + array->strings = NULL; + array->count = 0; + + if (git_vector_init(&ref_list, 8, NULL) < 0) + return -1; + + if (git_reference_foreach_name( + repo, &cb__reflist_add, (void *)&ref_list) < 0) { + git_vector_free(&ref_list); + return -1; + } + + array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list); + + return 0; +} + +static int is_valid_ref_char(char ch) +{ + if ((unsigned) ch <= ' ') + return 0; + + switch (ch) { + case '~': + case '^': + case ':': + case '\\': + case '?': + case '[': + return 0; + default: + return 1; + } +} + +static int ensure_segment_validity(const char *name, char may_contain_glob) +{ + const char *current = name; + char prev = '\0'; + const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); + int segment_len; + + if (*current == '.') + return -1; /* Refname starts with "." */ + + for (current = name; ; current++) { + if (*current == '\0' || *current == '/') + break; + + if (!is_valid_ref_char(*current)) + return -1; /* Illegal character in refname */ + + if (prev == '.' && *current == '.') + return -1; /* Refname contains ".." */ + + if (prev == '@' && *current == '{') + return -1; /* Refname contains "@{" */ + + if (*current == '*') { + if (!may_contain_glob) + return -1; + may_contain_glob = 0; + } + + prev = *current; + } + + segment_len = (int)(current - name); + + /* A refname component can not end with ".lock" */ + if (segment_len >= lock_len && + !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) + return -1; + + return segment_len; +} + +static bool is_all_caps_and_underscore(const char *name, size_t len) +{ + size_t i; + char c; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(len > 0); + + for (i = 0; i < len; i++) + { + c = name[i]; + if ((c < 'A' || c > 'Z') && c != '_') + return false; + } + + if (*name == '_' || name[len - 1] == '_') + return false; + + return true; +} + +/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */ +int git_reference__normalize_name( + git_str *buf, + const char *name, + unsigned int flags) +{ + const char *current; + int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; + unsigned int process_flags; + bool normalize = (buf != NULL); + bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + + GIT_ASSERT_ARG(name); + + process_flags = flags; + current = (char *)name; + + if (validate && *current == '/') + goto cleanup; + + if (normalize) + git_str_clear(buf); + +#ifdef GIT_USE_ICONV + if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) { + size_t namelen = strlen(current); + if ((error = git_fs_path_iconv_init_precompose(&ic)) < 0 || + (error = git_fs_path_iconv(&ic, ¤t, &namelen)) < 0) + goto cleanup; + error = GIT_EINVALIDSPEC; + } +#endif + + if (!validate) { + git_str_sets(buf, current); + + error = git_str_oom(buf) ? -1 : 0; + goto cleanup; + } + + while (true) { + 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_str_len(buf); + + git_str_joinpath(buf, git_str_cstr(buf), current); + git_str_truncate(buf, + cur_len + segment_len + (segments_count ? 1 : 0)); + + if (git_str_oom(buf)) { + error = -1; + goto cleanup; + } + } + + segments_count++; + } + + /* No empty segment is allowed when not normalizing */ + if (segment_len == 0 && !normalize) + goto cleanup; + + if (current[segment_len] == '\0') + break; + + current += segment_len + 1; + } + + /* A refname can not be empty */ + if (segment_len == 0 && segments_count == 0) + goto cleanup; + + /* A refname can not end with "." */ + if (current[segment_len - 1] == '.') + goto cleanup; + + /* A refname can not end with "/" */ + if (current[segment_len - 1] == '/') + goto cleanup; + + if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) + goto cleanup; + + if ((segments_count == 1 ) && + !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) && + !(is_all_caps_and_underscore(name, (size_t)segment_len) || + ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) + goto cleanup; + + if ((segments_count > 1) + && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) + goto cleanup; + + error = 0; + +cleanup: + if (error == GIT_EINVALIDSPEC) + git_error_set( + GIT_ERROR_REFERENCE, + "the given reference name '%s' is not valid", name); + + if (error && normalize) + git_str_dispose(buf); + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif + + return error; +} + +int git_reference_normalize_name( + char *buffer_out, + size_t buffer_size, + const char *name, + unsigned int flags) +{ + git_str buf = GIT_STR_INIT; + int error; + + if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) + goto cleanup; + + if (git_str_len(&buf) > buffer_size - 1) { + git_error_set( + GIT_ERROR_REFERENCE, + "the provided buffer is too short to hold the normalization of '%s'", name); + error = GIT_EBUFS; + goto cleanup; + } + + if ((error = git_str_copy_cstr(buffer_out, buffer_size, &buf)) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_str_dispose(&buf); + return error; +} + +#define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC) + +int git_reference_cmp( + const git_reference *ref1, + const git_reference *ref2) +{ + git_reference_t type1, type2; + + GIT_ASSERT_ARG(ref1); + GIT_ASSERT_ARG(ref2); + + type1 = git_reference_type(ref1); + type2 = git_reference_type(ref2); + + /* let's put symbolic refs before OIDs */ + if (type1 != type2) + return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1; + + if (type1 == GIT_REFERENCE_SYMBOLIC) + return strcmp(ref1->target.symbolic, ref2->target.symbolic); + + return git_oid__cmp(&ref1->target.oid, &ref2->target.oid); +} + +/* + * Starting with the reference given by `ref_name`, follows symbolic + * references until a direct reference is found and updated the OID + * on that direct reference to `oid`. + */ +int git_reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid, + const git_signature *sig, + const char *log_message) +{ + git_reference *ref = NULL, *ref2 = NULL; + git_signature *who = NULL; + git_refdb *refdb = NULL; + const git_signature *to_use; + int error = 0; + + if (!sig && (error = git_reference__log_signature(&who, repo)) < 0) + goto out; + + to_use = sig ? sig : who; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + goto out; + + if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use, + log_message, NULL, NULL); + } + goto out; + } + + /* In case the resolved reference is symbolic, then it's a dangling symref. */ + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use, + log_message, NULL, NULL); + } else { + error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use, + log_message, &ref->target.oid, NULL); + } + +out: + git_reference_free(ref2); + git_reference_free(ref); + git_signature_free(who); + return error; +} + +static const char *commit_type(const git_commit *commit) +{ + unsigned int count = git_commit_parentcount(commit); + + if (count >= 2) + return " (merge)"; + else if (count == 0) + return " (initial)"; + else + return ""; +} + +int git_reference__update_for_commit( + git_repository *repo, + git_reference *ref, + const char *ref_name, + const git_oid *id, + const char *operation) +{ + git_reference *ref_new = NULL; + git_commit *commit = NULL; + git_str reflog_msg = GIT_STR_INIT; + const git_signature *who; + int error; + + if ((error = git_commit_lookup(&commit, repo, id)) < 0 || + (error = git_str_printf(&reflog_msg, "%s%s: %s", + operation ? operation : "commit", + commit_type(commit), + git_commit_summary(commit))) < 0) + goto done; + + who = git_commit_committer(commit); + + if (ref) { + if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) + return error; + + error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who, + git_str_cstr(&reflog_msg), &ref->target.oid, NULL); + } + else + error = git_reference__update_terminal( + repo, ref_name, id, who, git_str_cstr(&reflog_msg)); + +done: + git_reference_free(ref_new); + git_str_dispose(&reflog_msg); + git_commit_free(commit); + return error; +} + +int git_reference_has_log(git_repository *repo, const char *refname) +{ + int error; + git_refdb *refdb; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + return git_refdb_has_log(refdb, refname); +} + +int git_reference_ensure_log(git_repository *repo, const char *refname) +{ + int error; + git_refdb *refdb; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + return git_refdb_ensure_log(refdb, refname); +} + +int git_reference__is_branch(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0; +} + +int git_reference_is_branch(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_branch(ref->name); +} + +int git_reference__is_remote(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0; +} + +int git_reference_is_remote(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_remote(ref->name); +} + +int git_reference__is_tag(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0; +} + +int git_reference_is_tag(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_tag(ref->name); +} + +int git_reference__is_note(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0; +} + +int git_reference_is_note(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_note(ref->name); +} + +static int peel_error(int error, const git_reference *ref, const char *msg) +{ + git_error_set( + GIT_ERROR_INVALID, + "the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); + return error; +} + +int git_reference_peel( + git_object **peeled, + const git_reference *ref, + git_object_t target_type) +{ + const git_reference *resolved = NULL; + git_reference *allocated = NULL; + git_object *target = NULL; + int error; + + GIT_ASSERT_ARG(ref); + + if (ref->type == GIT_REFERENCE_DIRECT) { + resolved = ref; + } else { + if ((error = git_reference_resolve(&allocated, ref)) < 0) + return peel_error(error, ref, "Cannot resolve reference"); + + resolved = allocated; + } + + /* + * If we try to peel an object to a tag, we cannot use + * the fully peeled object, as that will always resolve + * 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_is_zero(&resolved->peel)) { + error = git_object_lookup(&target, + git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY); + } else { + error = git_object_lookup(&target, + git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY); + } + + if (error < 0) { + peel_error(error, ref, "Cannot retrieve reference target"); + goto cleanup; + } + + if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG) + error = git_object_dup(peeled, target); + else + error = git_object_peel(peeled, target, target_type); + +cleanup: + git_object_free(target); + git_reference_free(allocated); + + return error; +} + +int git_reference__name_is_valid( + int *valid, + const char *refname, + unsigned int flags) +{ + int error; + + GIT_ASSERT(valid && refname); + + *valid = 0; + + error = git_reference__normalize_name(NULL, refname, flags); + + if (!error) + *valid = 1; + else if (error == GIT_EINVALIDSPEC) + error = 0; + + return error; +} + +int git_reference_name_is_valid(int *valid, const char *refname) +{ + return git_reference__name_is_valid(valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); +} + +const char *git_reference__shorthand(const char *name) +{ + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + return name + strlen(GIT_REFS_HEADS_DIR); + else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + return name + strlen(GIT_REFS_TAGS_DIR); + else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR)) + return name + strlen(GIT_REFS_REMOTES_DIR); + else if (!git__prefixcmp(name, GIT_REFS_DIR)) + return name + strlen(GIT_REFS_DIR); + + /* No shorthands are available, so just return the name. */ + return name; +} + +const char *git_reference_shorthand(const git_reference *ref) +{ + return git_reference__shorthand(ref->name); +} + +int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo) +{ + int error; + git_reference *tmp_ref; + + GIT_ASSERT_ARG(unborn); + GIT_ASSERT_ARG(ref); + GIT_ASSERT_ARG(repo); + + if (ref->type == GIT_REFERENCE_DIRECT) { + *unborn = 0; + return 0; + } + + error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1); + git_reference_free(tmp_ref); + + if (error != 0 && error != GIT_ENOTFOUND) + return error; + else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0) + *unborn = true; + else + *unborn = false; + + return 0; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD + +int git_reference_is_valid_name(const char *refname) +{ + int valid = 0; + + git_reference__name_is_valid(&valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); + + return valid; +} + +#endif diff --git a/src/libgit2/refs.h b/src/libgit2/refs.h new file mode 100644 index 000000000..cb888bf8f --- /dev/null +++ b/src/libgit2/refs.h @@ -0,0 +1,130 @@ +/* + * 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_refs_h__ +#define INCLUDE_refs_h__ + +#include "common.h" + +#include "git2/oid.h" +#include "git2/refs.h" +#include "git2/refdb.h" +#include "strmap.h" +#include "str.h" +#include "oid.h" + +extern bool git_reference__enable_symbolic_ref_target_validation; + +#define GIT_REFS_DIR "refs/" +#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" +#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" +#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/" +#define GIT_REFS_NOTES_DIR GIT_REFS_DIR "notes/" +#define GIT_REFS_DIR_MODE 0777 +#define GIT_REFS_FILE_MODE 0666 + +#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF" + +#define GIT_SYMREF "ref: " +#define GIT_PACKEDREFS_FILE "packed-refs" +#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled sorted " +#define GIT_PACKEDREFS_FILE_MODE 0666 + +#define GIT_HEAD_FILE "HEAD" +#define GIT_ORIG_HEAD_FILE "ORIG_HEAD" +#define GIT_FETCH_HEAD_FILE "FETCH_HEAD" +#define GIT_MERGE_HEAD_FILE "MERGE_HEAD" +#define GIT_REVERT_HEAD_FILE "REVERT_HEAD" +#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD" +#define GIT_BISECT_LOG_FILE "BISECT_LOG" +#define GIT_REBASE_MERGE_DIR "rebase-merge/" +#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive" +#define GIT_REBASE_APPLY_DIR "rebase-apply/" +#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing" +#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" + +#define GIT_SEQUENCER_DIR "sequencer/" +#define GIT_SEQUENCER_HEAD_FILE GIT_SEQUENCER_DIR "head" +#define GIT_SEQUENCER_OPTIONS_FILE GIT_SEQUENCER_DIR "options" +#define GIT_SEQUENCER_TODO_FILE GIT_SEQUENCER_DIR "todo" + +#define GIT_STASH_FILE "stash" +#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE + +#define GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE (1u << 16) +#define GIT_REFERENCE_FORMAT__VALIDATION_DISABLE (1u << 15) + +#define GIT_REFNAME_MAX 1024 + +typedef char git_refname_t[GIT_REFNAME_MAX]; + +struct git_reference { + git_refdb *db; + git_reference_t type; + + union { + git_oid oid; + char *symbolic; + } target; + + git_oid peel; + char name[GIT_FLEX_ARRAY]; +}; + +/** + * 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_str *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); +int git_reference__name_is_valid(int *valid, const char *name, unsigned int flags); +int git_reference__is_branch(const char *ref_name); +int git_reference__is_remote(const char *ref_name); +int git_reference__is_tag(const char *ref_name); +int git_reference__is_note(const char *ref_name); +const char *git_reference__shorthand(const char *name); + +/** + * Lookup a reference by name and try to resolve to an OID. + * + * You can control how many dereferences this will attempt to resolve the + * reference with the `max_deref` parameter, or pass -1 to use a sane + * default. If you pass 0 for `max_deref`, this will not attempt to resolve + * the reference. For any value of `max_deref` other than 0, not + * successfully resolving the reference will be reported as an error. + + * The generated reference must be freed by the user. + * + * @param reference_out Pointer to the looked-up reference + * @param repo The repository to look up the reference + * @param name The long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) + * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value + * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref + */ +int git_reference_lookup_resolved( + git_reference **reference_out, + git_repository *repo, + const char *name, + int max_deref); + +int git_reference__log_signature(git_signature **out, git_repository *repo); + +/** Update a reference after a commit. */ +int git_reference__update_for_commit( + git_repository *repo, + git_reference *ref, + const char *ref_name, + const git_oid *id, + const char *operation); + +int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo); + +#endif diff --git a/src/libgit2/refspec.c b/src/libgit2/refspec.c new file mode 100644 index 000000000..f0a0c2bfb --- /dev/null +++ b/src/libgit2/refspec.c @@ -0,0 +1,420 @@ +/* + * 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 "refspec.h" + +#include "buf.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) +{ + /* Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 */ + + size_t llen; + int is_glob = 0; + const char *lhs, *rhs; + int valid = 0; + unsigned int flags; + + GIT_ASSERT_ARG(refspec); + GIT_ASSERT_ARG(input); + + memset(refspec, 0x0, sizeof(git_refspec)); + refspec->push = !is_fetch; + + lhs = input; + if (*lhs == '+') { + refspec->force = 1; + lhs++; + } + + rhs = strrchr(lhs, ':'); + + /* + * Before going on, special case ":" (or "+:") as a refspec + * for matching refs. + */ + if (!is_fetch && rhs == lhs && rhs[1] == '\0') { + refspec->matching = 1; + refspec->string = git__strdup(input); + GIT_ERROR_CHECK_ALLOC(refspec->string); + refspec->src = git__strdup(""); + GIT_ERROR_CHECK_ALLOC(refspec->src); + refspec->dst = git__strdup(""); + GIT_ERROR_CHECK_ALLOC(refspec->dst); + return 0; + } + + if (rhs) { + size_t rlen = strlen(++rhs); + if (rlen || !is_fetch) { + is_glob = (1 <= rlen && strchr(rhs, '*')); + refspec->dst = git__strndup(rhs, rlen); + } + } + + llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); + if (1 <= llen && memchr(lhs, '*', llen)) { + if ((rhs && !is_glob) || (!rhs && is_fetch)) + goto invalid; + is_glob = 1; + } else if (rhs && is_glob) + goto invalid; + + refspec->pattern = is_glob; + refspec->src = git__strndup(lhs, llen); + flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | + GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND | + (is_glob ? GIT_REFERENCE_FORMAT_REFSPEC_PATTERN : 0); + + if (is_fetch) { + /* + * LHS + * - empty is allowed; it means HEAD. + * - otherwise it must be a valid looking ref. + */ + if (!*refspec->src) + ; /* empty is ok */ + else if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + + /* + * RHS + * - missing is ok, and is same as empty. + * - empty is ok; it means not to store. + * - otherwise it must be a valid looking ref. + */ + if (!refspec->dst) + ; /* ok */ + else if (!*refspec->dst) + ; /* ok */ + else if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } else { + /* + * LHS + * - empty is allowed; it means delete. + * - when wildcarded, it must be a valid looking ref. + * - otherwise, it must be an extended SHA-1, but + * there is no existing way to validate this. + */ + if (!*refspec->src) + ; /* empty is ok */ + else if (is_glob) { + if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } + else { + ; /* anything goes, for now */ + } + + /* + * RHS + * - missing is allowed, but LHS then must be a + * valid looking ref. + * - empty is not allowed. + * - otherwise it must be a valid looking ref. + */ + if (!refspec->dst) { + if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } else if (!*refspec->dst) { + goto invalid; + } else { + if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } + + /* if the RHS is empty, then it's a copy of the LHS */ + if (!refspec->dst) { + refspec->dst = git__strdup(refspec->src); + GIT_ERROR_CHECK_ALLOC(refspec->dst); + } + } + + refspec->string = git__strdup(input); + GIT_ERROR_CHECK_ALLOC(refspec->string); + + return 0; + +invalid: + git_error_set(GIT_ERROR_INVALID, + "'%s' is not a valid refspec.", input); + git_refspec__dispose(refspec); + return GIT_EINVALIDSPEC; + +on_error: + git_refspec__dispose(refspec); + return -1; +} + +void git_refspec__dispose(git_refspec *refspec) +{ + if (refspec == NULL) + return; + + git__free(refspec->src); + git__free(refspec->dst); + git__free(refspec->string); + + memset(refspec, 0x0, sizeof(git_refspec)); +} + +int git_refspec_parse(git_refspec **out_refspec, const char *input, int is_fetch) +{ + git_refspec *refspec; + GIT_ASSERT_ARG(out_refspec); + GIT_ASSERT_ARG(input); + + *out_refspec = NULL; + + refspec = git__malloc(sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(refspec); + + if (git_refspec__parse(refspec, input, !!is_fetch) != 0) { + git__free(refspec); + return -1; + } + + *out_refspec = refspec; + return 0; +} + +void git_refspec_free(git_refspec *refspec) +{ + git_refspec__dispose(refspec); + git__free(refspec); +} + +const char *git_refspec_src(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->src; +} + +const char *git_refspec_dst(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->dst; +} + +const char *git_refspec_string(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->string; +} + +int git_refspec_force(const git_refspec *refspec) +{ + GIT_ASSERT_ARG(refspec); + + return refspec->force; +} + +int git_refspec_src_matches(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->src == NULL) + return false; + + return (wildmatch(refspec->src, refname, 0) == 0); +} + +int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->dst == NULL) + return false; + + return (wildmatch(refspec->dst, refname, 0) == 0); +} + +static int refspec_transform( + git_str *out, const char *from, const char *to, const char *name) +{ + const char *from_star, *to_star; + size_t replacement_len, star_offset; + + git_str_clear(out); + + /* + * There are two parts to each side of a refspec, the bit + * before the star and the bit after it. The star can be in + * the middle of the pattern, so we need to look at each bit + * individually. + */ + from_star = strchr(from, '*'); + to_star = strchr(to, '*'); + + GIT_ASSERT(from_star && to_star); + + /* star offset, both in 'from' and in 'name' */ + star_offset = from_star - from; + + /* the first half is copied over */ + git_str_put(out, to, to_star - to); + + /* + * 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_str_put(out, name + star_offset, replacement_len); + + return git_str_puts(out, to_star + 1); +} + +int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_refspec__transform, spec, name); +} + +int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(name); + + if (!git_refspec_src_matches(spec, name)) { + git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the source", name); + return -1; + } + + if (!spec->pattern) + return git_str_puts(out, spec->dst ? spec->dst : ""); + + return refspec_transform(out, spec->src, spec->dst, name); +} + +int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_refspec__rtransform, spec, name); +} + +int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(name); + + if (!git_refspec_dst_matches(spec, name)) { + git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the destination", name); + return -1; + } + + if (!spec->pattern) + return git_str_puts(out, spec->src); + + return refspec_transform(out, spec->dst, spec->src, name); +} + +int git_refspec__serialize(git_str *out, const git_refspec *refspec) +{ + if (refspec->force) + git_str_putc(out, '+'); + + git_str_printf(out, "%s:%s", + refspec->src != NULL ? refspec->src : "", + refspec->dst != NULL ? refspec->dst : ""); + + return git_str_oom(out) == false; +} + +int git_refspec_is_wildcard(const git_refspec *spec) +{ + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(spec->src); + + return (spec->src[strlen(spec->src) - 1] == '*'); +} + +git_direction git_refspec_direction(const git_refspec *spec) +{ + GIT_ASSERT_ARG(spec); + + return spec->push; +} + +int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) +{ + git_str buf = GIT_STR_INIT; + size_t j, pos; + git_remote_head key; + git_refspec *cur; + + const char *formatters[] = { + GIT_REFS_DIR "%s", + GIT_REFS_TAGS_DIR "%s", + GIT_REFS_HEADS_DIR "%s", + NULL + }; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(refs); + + cur = git__calloc(1, sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(cur); + + cur->force = spec->force; + cur->push = spec->push; + cur->pattern = spec->pattern; + cur->matching = spec->matching; + cur->string = git__strdup(spec->string); + + /* shorthand on the lhs */ + if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { + for (j = 0; formatters[j]; j++) { + git_str_clear(&buf); + git_str_printf(&buf, formatters[j], spec->src); + GIT_ERROR_CHECK_ALLOC_STR(&buf); + + key.name = (char *) git_str_cstr(&buf); + if (!git_vector_search(&pos, refs, &key)) { + /* we found something to match the shorthand, set src to that */ + cur->src = git_str_detach(&buf); + } + } + } + + /* No shorthands found, copy over the name */ + if (cur->src == NULL && spec->src != NULL) { + cur->src = git__strdup(spec->src); + GIT_ERROR_CHECK_ALLOC(cur->src); + } + + if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { + /* if it starts with "remotes" then we just prepend "refs/" */ + if (!git__prefixcmp(spec->dst, "remotes/")) { + git_str_puts(&buf, GIT_REFS_DIR); + } else { + git_str_puts(&buf, GIT_REFS_HEADS_DIR); + } + + git_str_puts(&buf, spec->dst); + GIT_ERROR_CHECK_ALLOC_STR(&buf); + + cur->dst = git_str_detach(&buf); + } + + git_str_dispose(&buf); + + if (cur->dst == NULL && spec->dst != NULL) { + cur->dst = git__strdup(spec->dst); + GIT_ERROR_CHECK_ALLOC(cur->dst); + } + + return git_vector_insert(out, cur); +} diff --git a/src/libgit2/refspec.h b/src/libgit2/refspec.h new file mode 100644 index 000000000..bf4f7fcfb --- /dev/null +++ b/src/libgit2/refspec.h @@ -0,0 +1,54 @@ +/* + * 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_refspec_h__ +#define INCLUDE_refspec_h__ + +#include "common.h" + +#include "git2/refspec.h" +#include "str.h" +#include "vector.h" + +struct git_refspec { + char *string; + char *src; + char *dst; + unsigned int force :1, + push : 1, + pattern :1, + matching :1; +}; + +#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*" + +int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name); +int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name); + +int git_refspec__parse( + struct git_refspec *refspec, + const char *str, + bool is_fetch); + +void git_refspec__dispose(git_refspec *refspec); + +int git_refspec__serialize(git_str *out, const git_refspec *refspec); + +/** + * Determines if a refspec is a wildcard refspec. + * + * @param spec the refspec + * @return 1 if the refspec is a wildcard, 0 otherwise + */ +int git_refspec_is_wildcard(const git_refspec *spec); + +/** + * DWIM `spec` with `refs` existing on the remote, append the dwim'ed + * result in `out`. + */ +int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs); + +#endif diff --git a/src/libgit2/remote.c b/src/libgit2/remote.c new file mode 100644 index 000000000..3d0593712 --- /dev/null +++ b/src/libgit2/remote.c @@ -0,0 +1,3087 @@ +/* + * 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 "remote.h" + +#include "buf.h" +#include "branch.h" +#include "config.h" +#include "repository.h" +#include "fetch.h" +#include "refs.h" +#include "refspec.h" +#include "fetchhead.h" +#include "push.h" +#include "proxy.h" + +#include "git2/config.h" +#include "git2/types.h" +#include "git2/oid.h" +#include "git2/net.h" + +#define CONFIG_URL_FMT "remote.%s.url" +#define CONFIG_PUSHURL_FMT "remote.%s.pushurl" +#define CONFIG_FETCH_FMT "remote.%s.fetch" +#define CONFIG_PUSH_FMT "remote.%s.push" +#define CONFIG_TAGOPT_FMT "remote.%s.tagopt" + +static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); +static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name); +static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty); + +static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch) +{ + git_refspec *spec; + + spec = git__calloc(1, sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(spec); + + if (git_refspec__parse(spec, string, is_fetch) < 0) { + git__free(spec); + return -1; + } + + spec->push = !is_fetch; + if (git_vector_insert(vector, spec) < 0) { + git_refspec__dispose(spec); + git__free(spec); + return -1; + } + + return 0; +} + +static int add_refspec(git_remote *remote, const char *string, bool is_fetch) +{ + return add_refspec_to(&remote->refspecs, string, is_fetch); +} + +static int download_tags_value(git_remote *remote, git_config *cfg) +{ + git_config_entry *ce; + git_str buf = GIT_STR_INIT; + int error; + + if (git_str_printf(&buf, "remote.%s.tagopt", remote->name) < 0) + return -1; + + error = git_config__lookup_entry(&ce, cfg, git_str_cstr(&buf), false); + git_str_dispose(&buf); + + if (!error && ce && ce->value) { + if (!strcmp(ce->value, "--no-tags")) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; + else if (!strcmp(ce->value, "--tags")) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + } + + git_config_entry_free(ce); + return error; +} + +static int ensure_remote_name_is_valid(const char *name) +{ + int valid, error; + + error = git_remote_name_is_valid(&valid, name); + + if (!error && !valid) { + git_error_set( + GIT_ERROR_CONFIG, + "'%s' is not a valid remote name.", name ? name : "(null)"); + error = GIT_EINVALIDSPEC; + } + + return error; +} + +static int write_add_refspec(git_repository *repo, const char *name, const char *refspec, bool fetch) +{ + git_config *cfg; + git_str var = GIT_STR_INIT; + git_refspec spec; + const char *fmt; + int error; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + fmt = fetch ? CONFIG_FETCH_FMT : CONFIG_PUSH_FMT; + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if ((error = git_refspec__parse(&spec, refspec, fetch)) < 0) + return error; + + git_refspec__dispose(&spec); + + if ((error = git_str_printf(&var, fmt, name)) < 0) + return error; + + /* + * "$^" is an unmatchable regexp: it will not match anything at all, so + * all values will be considered new and we will not replace any + * present value. + */ + if ((error = git_config_set_multivar(cfg, var.ptr, "$^", refspec)) < 0) { + goto cleanup; + } + +cleanup: + git_str_dispose(&var); + return 0; +} + +static int canonicalize_url(git_str *out, const char *in) +{ + if (in == NULL || strlen(in) == 0) { + git_error_set(GIT_ERROR_INVALID, "cannot set empty URL"); + return GIT_EINVALIDSPEC; + } + +#ifdef GIT_WIN32 + /* Given a UNC path like \\server\path, we need to convert this + * to //server/path for compatibility with core git. + */ + if (in[0] == '\\' && in[1] == '\\' && + (git__isalpha(in[2]) || git__isdigit(in[2]))) { + const char *c; + for (c = in; *c; c++) + git_str_putc(out, *c == '\\' ? '/' : *c); + + return git_str_oom(out) ? -1 : 0; + } +#endif + + return git_str_puts(out, in); +} + +static int default_fetchspec_for_name(git_str *buf, const char *name) +{ + if (git_str_printf(buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) + return -1; + + return 0; +} + +static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) +{ + int error; + git_remote *remote; + + error = git_remote_lookup(&remote, repo, name); + + if (error == GIT_ENOTFOUND) + return 0; + + if (error < 0) + return error; + + git_remote_free(remote); + + git_error_set(GIT_ERROR_CONFIG, "remote '%s' already exists", name); + + return GIT_EEXISTS; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version) +{ + return git_remote_create_options_init(opts, version); +} +#endif + +int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts) +{ + git_remote *remote = NULL; + git_config *config_ro = NULL, *config_rw; + git_str canonical_url = GIT_STR_INIT; + git_str var = GIT_STR_INIT; + git_str specbuf = GIT_STR_INIT; + const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + int error = -1; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(url); + + if (!opts) { + opts = &dummy_opts; + } + + GIT_ERROR_CHECK_VERSION(opts, GIT_REMOTE_CREATE_OPTIONS_VERSION, "git_remote_create_options"); + + if (opts->name != NULL) { + if ((error = ensure_remote_name_is_valid(opts->name)) < 0) + return error; + + if (opts->repository && + (error = ensure_remote_doesnot_exist(opts->repository, opts->name)) < 0) + return error; + } + + if (opts->repository) { + if ((error = git_repository_config_snapshot(&config_ro, opts->repository)) < 0) + goto on_error; + } + + remote = git__calloc(1, sizeof(git_remote)); + GIT_ERROR_CHECK_ALLOC(remote); + + remote->repo = opts->repository; + + if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 || + (error = canonicalize_url(&canonical_url, url)) < 0) + goto on_error; + + if (opts->repository && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF)) { + if ((error = apply_insteadof(&remote->url, config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH, true)) < 0 || + (error = apply_insteadof(&remote->pushurl, config_ro, canonical_url.ptr, GIT_DIRECTION_PUSH, false)) < 0) + goto on_error; + } else { + remote->url = git__strdup(canonical_url.ptr); + GIT_ERROR_CHECK_ALLOC(remote->url); + } + + if (opts->name != NULL) { + remote->name = git__strdup(opts->name); + GIT_ERROR_CHECK_ALLOC(remote->name); + + if (opts->repository && + ((error = git_str_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 || + (error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 || + (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)) + goto on_error; + } + + if (opts->fetchspec != NULL || + (opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))) { + const char *fetch = NULL; + if (opts->fetchspec) { + fetch = opts->fetchspec; + } else { + if ((error = default_fetchspec_for_name(&specbuf, opts->name)) < 0) + goto on_error; + + fetch = git_str_cstr(&specbuf); + } + + if ((error = add_refspec(remote, fetch, true)) < 0) + goto on_error; + + /* only write for named remotes with a repository */ + if (opts->repository && opts->name && + ((error = write_add_refspec(opts->repository, opts->name, fetch, true)) < 0 || + (error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0)) + goto on_error; + + /* Move the data over to where the matching functions can find them */ + if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) + goto on_error; + } + + /* A remote without a name doesn't download tags */ + if (!opts->name) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; + else + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + + + git_str_dispose(&var); + + *out = remote; + error = 0; + +on_error: + if (error) + git_remote_free(remote); + + git_config_free(config_ro); + git_str_dispose(&specbuf); + git_str_dispose(&canonical_url); + git_str_dispose(&var); + return error; +} + +int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url) +{ + git_str buf = GIT_STR_INIT; + int error; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + /* Those 2 tests are duplicated here because of backward-compatibility */ + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if (canonicalize_url(&buf, url) < 0) + return GIT_ERROR; + + git_str_clear(&buf); + + opts.repository = repo; + opts.name = name; + + error = git_remote_create_with_opts(out, url, &opts); + + git_str_dispose(&buf); + + return error; +} + +int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) +{ + int error; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + opts.repository = repo; + opts.name = name; + opts.fetchspec = fetch; + opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; + + return git_remote_create_with_opts(out, url, &opts); +} + +int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url) +{ + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.repository = repo; + + return git_remote_create_with_opts(out, url, &opts); +} + +int git_remote_create_detached(git_remote **out, const char *url) +{ + return git_remote_create_with_opts(out, url, NULL); +} + +int git_remote_dup(git_remote **dest, git_remote *source) +{ + size_t i; + int error = 0; + git_refspec *spec; + git_remote *remote = git__calloc(1, sizeof(git_remote)); + GIT_ERROR_CHECK_ALLOC(remote); + + if (source->name != NULL) { + remote->name = git__strdup(source->name); + GIT_ERROR_CHECK_ALLOC(remote->name); + } + + if (source->url != NULL) { + remote->url = git__strdup(source->url); + GIT_ERROR_CHECK_ALLOC(remote->url); + } + + if (source->pushurl != NULL) { + remote->pushurl = git__strdup(source->pushurl); + GIT_ERROR_CHECK_ALLOC(remote->pushurl); + } + + remote->repo = source->repo; + remote->download_tags = source->download_tags; + remote->prune_refs = source->prune_refs; + + if (git_vector_init(&remote->refs, 32, NULL) < 0 || + git_vector_init(&remote->refspecs, 2, NULL) < 0 || + git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { + error = -1; + goto cleanup; + } + + git_vector_foreach(&source->refspecs, i, spec) { + if ((error = add_refspec(remote, spec->string, !spec->push)) < 0) + goto cleanup; + } + + *dest = remote; + +cleanup: + + if (error < 0) + git__free(remote); + + return error; +} + +struct refspec_cb_data { + git_remote *remote; + int fetch; +}; + +static int refspec_cb(const git_config_entry *entry, void *payload) +{ + struct refspec_cb_data *data = (struct refspec_cb_data *)payload; + return add_refspec(data->remote, entry->value, data->fetch); +} + +static int get_optional_config( + bool *found, git_config *config, git_str *buf, + git_config_foreach_cb cb, void *payload) +{ + int error = 0; + const char *key = git_str_cstr(buf); + + if (git_str_oom(buf)) + return -1; + + if (cb != NULL) + error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); + else + error = git_config_get_string(payload, config, key); + + if (found) + *found = !error; + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + return error; +} + +int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) +{ + git_remote *remote = NULL; + git_str buf = GIT_STR_INIT; + const char *val; + int error = 0; + git_config *config; + struct refspec_cb_data data = { NULL }; + bool optional_setting_found = false, found; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + return error; + + remote = git__calloc(1, sizeof(git_remote)); + GIT_ERROR_CHECK_ALLOC(remote); + + remote->name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(remote->name); + + if (git_vector_init(&remote->refs, 32, NULL) < 0 || + git_vector_init(&remote->refspecs, 2, NULL) < 0 || + git_vector_init(&remote->passive_refspecs, 2, NULL) < 0 || + git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { + error = -1; + goto cleanup; + } + + if ((error = git_str_printf(&buf, "remote.%s.url", name)) < 0) + goto cleanup; + + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) + goto cleanup; + + optional_setting_found |= found; + + remote->repo = repo; + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + + if (found && strlen(val) > 0) { + if ((error = apply_insteadof(&remote->url, config, val, GIT_DIRECTION_FETCH, true)) < 0 || + (error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_PUSH, false)) < 0) + goto cleanup; + } + + val = NULL; + git_str_clear(&buf); + git_str_printf(&buf, "remote.%s.pushurl", name); + + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) + goto cleanup; + + optional_setting_found |= found; + + if (!optional_setting_found) { + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_CONFIG, "remote '%s' does not exist", name); + goto cleanup; + } + + if (found && strlen(val) > 0) { + if (remote->pushurl) + git__free(remote->pushurl); + + if ((error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_FETCH, true)) < 0) + goto cleanup; + } + + data.remote = remote; + data.fetch = true; + + git_str_clear(&buf); + git_str_printf(&buf, "remote.%s.fetch", name); + + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) + goto cleanup; + + data.fetch = false; + git_str_clear(&buf); + git_str_printf(&buf, "remote.%s.push", name); + + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) + goto cleanup; + + if ((error = download_tags_value(remote, config)) < 0) + goto cleanup; + + if ((error = lookup_remote_prune_config(remote, config, name)) < 0) + goto cleanup; + + /* Move the data over to where the matching functions can find them */ + if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) + goto cleanup; + + *out = remote; + +cleanup: + git_config_free(config); + git_str_dispose(&buf); + + if (error < 0) + git_remote_free(remote); + + return error; +} + +static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name) +{ + git_str buf = GIT_STR_INIT; + int error = 0; + + git_str_printf(&buf, "remote.%s.prune", name); + + if ((error = git_config_get_bool(&remote->prune_refs, config, git_str_cstr(&buf))) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + + if ((error = git_config_get_bool(&remote->prune_refs, config, "fetch.prune")) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + } + } + } + + git_str_dispose(&buf); + return error; +} + +const char *git_remote_name(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->name; +} + +git_repository *git_remote_owner(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->repo; +} + +const char *git_remote_url(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->url; +} + +int git_remote_set_instance_url(git_remote *remote, const char *url) +{ + char *tmp; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(url); + + if ((tmp = git__strdup(url)) == NULL) + return -1; + + git__free(remote->url); + remote->url = tmp; + + return 0; +} + +static int set_url(git_repository *repo, const char *remote, const char *pattern, const char *url) +{ + git_config *cfg; + git_str buf = GIT_STR_INIT, canonical_url = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(remote); + + if ((error = ensure_remote_name_is_valid(remote)) < 0) + return error; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if ((error = git_str_printf(&buf, pattern, remote)) < 0) + return error; + + if (url) { + if ((error = canonicalize_url(&canonical_url, url)) < 0) + goto cleanup; + + error = git_config_set_string(cfg, buf.ptr, url); + } else { + error = git_config_delete_entry(cfg, buf.ptr); + } + +cleanup: + git_str_dispose(&canonical_url); + git_str_dispose(&buf); + + return error; +} + +int git_remote_set_url(git_repository *repo, const char *remote, const char *url) +{ + return set_url(repo, remote, CONFIG_URL_FMT, url); +} + +const char *git_remote_pushurl(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->pushurl; +} + +int git_remote_set_instance_pushurl(git_remote *remote, const char *url) +{ + char *tmp; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(url); + + if ((tmp = git__strdup(url)) == NULL) + return -1; + + git__free(remote->pushurl); + remote->pushurl = tmp; + + return 0; +} + +int git_remote_set_pushurl(git_repository *repo, const char *remote, const char *url) +{ + return set_url(repo, remote, CONFIG_PUSHURL_FMT, url); +} + +static int resolve_url( + git_str *resolved_url, + const char *url, + int direction, + const git_remote_callbacks *callbacks) +{ +#ifdef GIT_DEPRECATE_HARD + GIT_UNUSED(direction); + GIT_UNUSED(callbacks); +#else + git_buf buf = GIT_BUF_INIT; + int error; + + if (callbacks && callbacks->resolve_url) { + error = callbacks->resolve_url(&buf, url, direction, callbacks->payload); + + if (error != GIT_PASSTHROUGH) { + git_error_set_after_callback_function(error, "git_resolve_url_cb"); + + git_str_set(resolved_url, buf.ptr, buf.size); + git_buf_dispose(&buf); + + return error; + } + } +#endif + + return git_str_sets(resolved_url, url); +} + +int git_remote__urlfordirection( + git_str *url_out, + struct git_remote *remote, + int direction, + const git_remote_callbacks *callbacks) +{ + const char *url = NULL; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + + if (callbacks && callbacks->remote_ready) { + int status = callbacks->remote_ready(remote, direction, callbacks->payload); + + if (status != 0 && status != GIT_PASSTHROUGH) { + git_error_set_after_callback_function(status, "git_remote_ready_cb"); + return status; + } + } + + if (direction == GIT_DIRECTION_FETCH) + url = remote->url; + else if (direction == GIT_DIRECTION_PUSH) + url = 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 resolve_url(url_out, url, direction, callbacks); +} + +int git_remote_connect_options_init( + git_remote_connect_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_remote_connect_options, GIT_REMOTE_CONNECT_OPTIONS_INIT); + return 0; +} + +int git_remote_connect_options_dup( + git_remote_connect_options *dst, + const git_remote_connect_options *src) +{ + memcpy(dst, src, sizeof(git_remote_connect_options)); + + if (git_proxy_options_dup(&dst->proxy_opts, &src->proxy_opts) < 0 || + git_strarray_copy(&dst->custom_headers, &src->custom_headers) < 0) + return -1; + + return 0; +} + +void git_remote_connect_options_dispose(git_remote_connect_options *opts) +{ + if (!opts) + return; + + git_strarray_dispose(&opts->custom_headers); + git_proxy_options_dispose(&opts->proxy_opts); +} + +static size_t http_header_name_length(const char *http_header) +{ + const char *colon = strchr(http_header, ':'); + if (!colon) + return 0; + return colon - http_header; +} + +static bool is_malformed_http_header(const char *http_header) +{ + const char *c; + size_t name_len; + + /* Disallow \r and \n */ + if ((c = strchr(http_header, '\r')) != NULL) + return true; + if ((c = strchr(http_header, '\n')) != NULL) + return true; + + /* Require a header name followed by : */ + if ((name_len = http_header_name_length(http_header)) < 1) + return true; + + return false; +} + +static char *forbidden_custom_headers[] = { + "User-Agent", + "Host", + "Accept", + "Content-Type", + "Transfer-Encoding", + "Content-Length", +}; + +static bool is_forbidden_custom_header(const char *custom_header) +{ + unsigned long i; + 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++) + if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) + return true; + + return false; +} + +static int validate_custom_headers(const git_strarray *custom_headers) +{ + size_t i; + + if (!custom_headers) + return 0; + + for (i = 0; i < custom_headers->count; i++) { + if (is_malformed_http_header(custom_headers->strings[i])) { + git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); + return -1; + } + + if (is_forbidden_custom_header(custom_headers->strings[i])) { + git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); + return -1; + } + } + + return 0; +} + +static int lookup_redirect_config( + git_remote_redirect_t *out, + git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + if (!repo) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + return 0; + } + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "http.followRedirects")) < 0) { + if (error == GIT_ENOTFOUND) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + *out = bool_value ? GIT_REMOTE_REDIRECT_ALL : + GIT_REMOTE_REDIRECT_NONE; + } else if (strcasecmp(value, "initial") == 0) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + } else { + git_error_set(GIT_ERROR_CONFIG, "invalid configuration setting '%s' for 'http.followRedirects'", value); + error = -1; + } + +done: + git_config_free(config); + return error; +} + +int git_remote_connect_options_normalize( + git_remote_connect_options *dst, + git_repository *repo, + const git_remote_connect_options *src) +{ + git_remote_connect_options_dispose(dst); + git_remote_connect_options_init(dst, GIT_REMOTE_CONNECT_OPTIONS_VERSION); + + if (src) { + GIT_ERROR_CHECK_VERSION(src, GIT_REMOTE_CONNECT_OPTIONS_VERSION, "git_remote_connect_options"); + GIT_ERROR_CHECK_VERSION(&src->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + GIT_ERROR_CHECK_VERSION(&src->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + + if (validate_custom_headers(&src->custom_headers) < 0 || + git_remote_connect_options_dup(dst, src) < 0) + return -1; + } + + if (dst->follow_redirects == 0) { + if (lookup_redirect_config(&dst->follow_redirects, repo) < 0) + return -1; + } + + return 0; +} + +int git_remote_connect_ext( + git_remote *remote, + git_direction direction, + const git_remote_connect_options *given_opts) +{ + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + git_str url = GIT_STR_INIT; + git_transport *t; + int error; + + GIT_ASSERT_ARG(remote); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_remote_connect_options)); + + GIT_ERROR_CHECK_VERSION(&opts.callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + GIT_ERROR_CHECK_VERSION(&opts.proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + + t = remote->transport; + + if ((error = git_remote__urlfordirection(&url, remote, direction, &opts.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 && opts.callbacks.transport && + (error = opts.callbacks.transport(&t, remote, opts.callbacks.payload)) < 0) + 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.ptr)) < 0) + goto on_error; + + if ((error = t->connect(t, url.ptr, direction, &opts)) != 0) + goto on_error; + + remote->transport = t; + + git_str_dispose(&url); + + return 0; + +on_error: + if (t) + t->free(t); + + git_str_dispose(&url); + + if (t == remote->transport) + remote->transport = NULL; + + return error; +} + +int git_remote_connect( + git_remote *remote, + git_direction direction, + const git_remote_callbacks *callbacks, + const git_proxy_options *proxy, + const git_strarray *custom_headers) +{ + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + if (callbacks) + memcpy(&opts.callbacks, callbacks, sizeof(git_remote_callbacks)); + + if (proxy) + memcpy(&opts.proxy_opts, proxy, sizeof(git_proxy_options)); + + if (custom_headers) + memcpy(&opts.custom_headers, custom_headers, sizeof(git_strarray)); + + return git_remote_connect_ext(remote, direction, &opts); +} + +int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (!remote->transport) { + git_error_set(GIT_ERROR_NET, "this remote has never connected"); + return -1; + } + + return remote->transport->ls(out, size, remote->transport); +} + +int git_remote_capabilities(unsigned int *out, git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + *out = 0; + + if (!remote->transport) { + git_error_set(GIT_ERROR_NET, "this remote has never connected"); + return -1; + } + + return remote->transport->capabilities(out, remote->transport); +} + +static int lookup_config(char **out, git_config *cfg, const char *name) +{ + git_config_entry *ce = NULL; + int error; + + if ((error = git_config__lookup_entry(&ce, cfg, name, false)) < 0) + return error; + + if (ce && ce->value) { + *out = git__strdup(ce->value); + GIT_ERROR_CHECK_ALLOC(*out); + } else { + error = GIT_ENOTFOUND; + } + + git_config_entry_free(ce); + return error; +} + +static void url_config_trim(git_net_url *url) +{ + size_t len = strlen(url->path); + + if (url->path[len - 1] == '/') { + len--; + } else { + while (len && url->path[len - 1] != '/') + len--; + } + + url->path[len] = '\0'; +} + +static int http_proxy_config(char **out, git_remote *remote, git_net_url *url) +{ + git_config *cfg = NULL; + git_str buf = GIT_STR_INIT; + git_net_url lookup_url = GIT_NET_URL_INIT; + int error; + + if ((error = git_net_url_dup(&lookup_url, url)) < 0) + goto done; + + if (remote->repo) { + if ((error = git_repository_config(&cfg, remote->repo)) < 0) + goto done; + } else { + if ((error = git_config_open_default(&cfg)) < 0) + goto done; + } + + /* remote..proxy config setting */ + if (remote->name && remote->name[0]) { + git_str_clear(&buf); + + if ((error = git_str_printf(&buf, "remote.%s.proxy", remote->name)) < 0 || + (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) + goto done; + } + + while (true) { + git_str_clear(&buf); + + if ((error = git_str_puts(&buf, "http.")) < 0 || + (error = git_net_url_fmt(&buf, &lookup_url)) < 0 || + (error = git_str_puts(&buf, ".proxy")) < 0 || + (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) + goto done; + + if (! lookup_url.path[0]) + break; + + url_config_trim(&lookup_url); + } + + git_str_clear(&buf); + + error = lookup_config(out, cfg, "http.proxy"); + +done: + git_config_free(cfg); + git_str_dispose(&buf); + git_net_url_dispose(&lookup_url); + return error; +} + +static int http_proxy_env(char **out, git_remote *remote, git_net_url *url) +{ + git_str proxy_env = GIT_STR_INIT, no_proxy_env = GIT_STR_INIT; + bool use_ssl = (strcmp(url->scheme, "https") == 0); + int error; + + GIT_UNUSED(remote); + + /* http_proxy / https_proxy environment variables */ + error = git__getenv(&proxy_env, use_ssl ? "https_proxy" : "http_proxy"); + + /* try uppercase environment variables */ + if (error == GIT_ENOTFOUND) + error = git__getenv(&proxy_env, use_ssl ? "HTTPS_PROXY" : "HTTP_PROXY"); + + if (error) + goto done; + + /* no_proxy/NO_PROXY environment variables */ + error = git__getenv(&no_proxy_env, "no_proxy"); + + if (error == GIT_ENOTFOUND) + error = git__getenv(&no_proxy_env, "NO_PROXY"); + + if (error && error != GIT_ENOTFOUND) + goto done; + + if (!git_net_url_matches_pattern_list(url, no_proxy_env.ptr)) + *out = git_str_detach(&proxy_env); + else + error = GIT_ENOTFOUND; + +done: + git_str_dispose(&proxy_env); + git_str_dispose(&no_proxy_env); + return error; +} + +int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(remote); + + *out = NULL; + + /* + * Go through the possible sources for proxy configuration, + * Examine the various git config options first, then + * consult environment variables. + */ + if ((error = http_proxy_config(out, remote, url)) != GIT_ENOTFOUND || + (error = http_proxy_env(out, remote, url)) != GIT_ENOTFOUND) + return error; + + return 0; +} + +/* DWIM `refspecs` based on `refs` and append the output to `out` */ +static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs) +{ + size_t i; + git_refspec *spec; + + git_vector_foreach(refspecs, i, spec) { + if (git_refspec__dwim_one(out, spec, refs) < 0) + return -1; + } + + return 0; +} + +static void free_refspecs(git_vector *vec) +{ + size_t i; + git_refspec *spec; + + git_vector_foreach(vec, i, spec) { + git_refspec__dispose(spec); + git__free(spec); + } + + git_vector_clear(vec); +} + +static int remote_head_cmp(const void *_a, const void *_b) +{ + const git_remote_head *a = (git_remote_head *) _a; + const git_remote_head *b = (git_remote_head *) _b; + + return git__strcmp_cb(a->name, b->name); +} + +static int ls_to_vector(git_vector *out, git_remote *remote) +{ + git_remote_head **heads; + size_t heads_len, i; + + if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) + return -1; + + if (git_vector_init(out, heads_len, remote_head_cmp) < 0) + return -1; + + for (i = 0; i < heads_len; i++) { + if (git_vector_insert(out, heads[i]) < 0) + return -1; + } + + return 0; +} + +#define copy_opts(out, in) \ + if (in) { \ + (out)->callbacks = (in)->callbacks; \ + (out)->proxy_opts = (in)->proxy_opts; \ + (out)->custom_headers = (in)->custom_headers; \ + (out)->follow_redirects = (in)->follow_redirects; \ + } + +GIT_INLINE(int) connect_opts_from_fetch_opts( + git_remote_connect_options *out, + git_remote *remote, + const git_fetch_options *fetch_opts) +{ + git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; + copy_opts(&tmp, fetch_opts); + return git_remote_connect_options_normalize(out, remote->repo, &tmp); +} + +static int connect_or_reset_options( + git_remote *remote, + int direction, + git_remote_connect_options *opts) +{ + if (!git_remote_connected(remote)) { + return git_remote_connect_ext(remote, direction, opts); + } else { + return remote->transport->set_connect_opts(remote->transport, opts); + } +} + +/* Download from an already connected remote. */ +static int git_remote__download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts) +{ + git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; + size_t i; + int error; + + if (ls_to_vector(&refs, remote) < 0) + return -1; + + if ((error = git_vector_init(&specs, 0, NULL)) < 0) + goto on_error; + + remote->passed_refspecs = 0; + if (!refspecs || !refspecs->count) { + to_active = &remote->refspecs; + } else { + for (i = 0; i < refspecs->count; i++) { + if ((error = add_refspec_to(&specs, refspecs->strings[i], true)) < 0) + goto on_error; + } + + to_active = &specs; + remote->passed_refspecs = 1; + } + + free_refspecs(&remote->passive_refspecs); + if ((error = dwim_refspecs(&remote->passive_refspecs, &remote->refspecs, &refs)) < 0) + goto on_error; + + free_refspecs(&remote->active_refspecs); + error = dwim_refspecs(&remote->active_refspecs, to_active, &refs); + + git_vector_free(&refs); + free_refspecs(&specs); + git_vector_free(&specs); + + if (error < 0) + goto on_error; + + if (remote->push) { + git_push_free(remote->push); + remote->push = NULL; + } + + if ((error = git_fetch_negotiate(remote, opts)) < 0) + goto on_error; + + error = git_fetch_download_pack(remote); + +on_error: + git_vector_free(&refs); + free_refspecs(&specs); + git_vector_free(&specs); + return error; +} + +int git_remote_download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) + return error; + + return git_remote__download(remote, refspecs, opts); +} + +int git_remote_fetch( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts, + const char *reflog_message) +{ + int error, update_fetchhead = 1; + git_remote_autotag_option_t tagopt = remote->download_tags; + bool prune = false; + git_str reflog_msg_buf = GIT_STR_INIT; + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) + return error; + + if (opts) { + update_fetchhead = opts->update_fetchhead; + tagopt = opts->download_tags; + } + + /* Connect and download everything */ + error = git_remote__download(remote, refspecs, opts); + + /* We don't need to be connected anymore */ + git_remote_disconnect(remote); + + /* If the download failed, return the error */ + if (error != 0) + goto done; + + /* Default reflog message */ + if (reflog_message) + git_str_sets(&reflog_msg_buf, reflog_message); + else { + git_str_printf(&reflog_msg_buf, "fetch %s", + remote->name ? remote->name : remote->url); + } + + /* Create "remote/foo" branches for all remote branches */ + error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); + git_str_dispose(&reflog_msg_buf); + if (error < 0) + goto done; + + if (opts && opts->prune == GIT_FETCH_PRUNE) + prune = true; + else if (opts && opts->prune == GIT_FETCH_PRUNE_UNSPECIFIED && remote->prune_refs) + prune = true; + else if (opts && opts->prune == GIT_FETCH_NO_PRUNE) + prune = false; + else + prune = remote->prune_refs; + + if (prune) + error = git_remote_prune(remote, &connect_opts.callbacks); + +done: + git_remote_connect_options_dispose(&connect_opts); + return error; +} + +static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) +{ + unsigned int i; + git_remote_head *remote_ref; + + GIT_ASSERT_ARG(update_heads); + GIT_ASSERT_ARG(fetchspec_src); + + *out = NULL; + + git_vector_foreach(update_heads, i, remote_ref) { + if (strcmp(remote_ref->name, fetchspec_src) == 0) { + *out = remote_ref; + break; + } + } + + return 0; +} + +static int ref_to_update(int *update, git_str *remote_name, git_remote *remote, git_refspec *spec, const char *ref_name) +{ + int error = 0; + git_repository *repo; + git_str upstream_remote = GIT_STR_INIT; + git_str upstream_name = GIT_STR_INIT; + + repo = git_remote_owner(remote); + + if ((!git_reference__is_branch(ref_name)) || + !git_remote_name(remote) || + (error = git_branch__upstream_remote(&upstream_remote, repo, ref_name) < 0) || + git__strcmp(git_remote_name(remote), git_str_cstr(&upstream_remote)) || + (error = git_branch__upstream_name(&upstream_name, repo, ref_name)) < 0 || + !git_refspec_dst_matches(spec, git_str_cstr(&upstream_name)) || + (error = git_refspec__rtransform(remote_name, spec, upstream_name.ptr)) < 0) { + /* Not an error if there is no upstream */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + *update = 0; + } else { + *update = 1; + } + + git_str_dispose(&upstream_remote); + git_str_dispose(&upstream_name); + return error; +} + +static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_refspec *spec, git_vector *update_heads, git_reference *ref) +{ + git_reference *resolved_ref = NULL; + git_str remote_name = GIT_STR_INIT; + git_config *config = NULL; + const char *ref_name; + int error = 0, update; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(ref); + + *out = NULL; + + error = git_reference_resolve(&resolved_ref, ref); + + /* If we're in an unborn branch, let's pretend nothing happened */ + if (error == GIT_ENOTFOUND && git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + ref_name = git_reference_symbolic_target(ref); + error = 0; + } else { + ref_name = git_reference_name(resolved_ref); + } + + /* + * The ref name may be unresolvable - perhaps it's pointing to + * something invalid. In this case, there is no remote head for + * this ref. + */ + if (!ref_name) { + error = 0; + goto cleanup; + } + + if ((error = ref_to_update(&update, &remote_name, remote, spec, ref_name)) < 0) + goto cleanup; + + if (update) + error = remote_head_for_fetchspec_src(out, update_heads, git_str_cstr(&remote_name)); + +cleanup: + git_str_dispose(&remote_name); + git_reference_free(resolved_ref); + git_config_free(config); + return error; +} + +static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads) +{ + git_reference *head_ref = NULL; + git_fetchhead_ref *fetchhead_ref; + git_remote_head *remote_ref, *merge_remote_ref; + git_vector fetchhead_refs; + bool include_all_fetchheads; + unsigned int i = 0; + int error = 0; + + GIT_ASSERT_ARG(remote); + + /* no heads, nothing to do */ + if (update_heads->length == 0) + return 0; + + if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0) + return -1; + + /* Iff refspec is * (but not subdir slash star), include tags */ + include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0); + + /* Determine what to merge: if refspec was a wildcard, just use HEAD */ + if (git_refspec_is_wildcard(spec)) { + if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 || + (error = remote_head_for_ref(&merge_remote_ref, remote, spec, update_heads, head_ref)) < 0) + goto cleanup; + } else { + /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */ + if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0) + goto cleanup; + } + + /* Create the FETCH_HEAD file */ + git_vector_foreach(update_heads, i, remote_ref) { + int merge_this_fetchhead = (merge_remote_ref == remote_ref); + + if (!include_all_fetchheads && + !git_refspec_src_matches(spec, remote_ref->name) && + !merge_this_fetchhead) + continue; + + if (git_fetchhead_ref_create(&fetchhead_ref, + &remote_ref->oid, + merge_this_fetchhead, + remote_ref->name, + git_remote_url(remote)) < 0) + goto cleanup; + + if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0) + goto cleanup; + } + + git_fetchhead_write(remote->repo, &fetchhead_refs); + +cleanup: + for (i = 0; i < fetchhead_refs.length; ++i) + git_fetchhead_ref_free(fetchhead_refs.contents[i]); + + git_vector_free(&fetchhead_refs); + git_reference_free(head_ref); + + return error; +} + +/** + * Generate a list of candidates for pruning by getting a list of + * references which match the rhs of an active refspec. + */ +static int prune_candidates(git_vector *candidates, git_remote *remote) +{ + git_strarray arr = { 0 }; + size_t i; + int error; + + if ((error = git_reference_list(&arr, remote->repo)) < 0) + return error; + + for (i = 0; i < arr.count; i++) { + const char *refname = arr.strings[i]; + char *refname_dup; + + if (!git_remote__matching_dst_refspec(remote, refname)) + continue; + + refname_dup = git__strdup(refname); + GIT_ERROR_CHECK_ALLOC(refname_dup); + + if ((error = git_vector_insert(candidates, refname_dup)) < 0) + goto out; + } + +out: + git_strarray_dispose(&arr); + return error; +} + +static int find_head(const void *_a, const void *_b) +{ + git_remote_head *a = (git_remote_head *) _a; + git_remote_head *b = (git_remote_head *) _b; + + return strcmp(a->name, b->name); +} + +int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks) +{ + size_t i, j; + git_vector remote_refs = GIT_VECTOR_INIT; + git_vector candidates = GIT_VECTOR_INIT; + const git_refspec *spec; + const char *refname; + int error; + git_oid zero_id = {{ 0 }}; + + if (callbacks) + GIT_ERROR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + + if ((error = ls_to_vector(&remote_refs, remote)) < 0) + goto cleanup; + + git_vector_set_cmp(&remote_refs, find_head); + + if ((error = prune_candidates(&candidates, remote)) < 0) + goto cleanup; + + /* + * Remove those entries from the candidate list for which we + * can find a remote reference in at least one refspec. + */ + git_vector_foreach(&candidates, i, refname) { + git_vector_foreach(&remote->active_refspecs, j, spec) { + git_str buf = GIT_STR_INIT; + size_t pos; + char *src_name; + git_remote_head key = {0}; + + if (!git_refspec_dst_matches(spec, refname)) + continue; + + if ((error = git_refspec__rtransform(&buf, spec, refname)) < 0) + goto cleanup; + + key.name = (char *) git_str_cstr(&buf); + error = git_vector_bsearch(&pos, &remote_refs, &key); + git_str_dispose(&buf); + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (error == GIT_ENOTFOUND) + continue; + + /* If we did find a source, remove it from the candidates. */ + if ((error = git_vector_set((void **) &src_name, &candidates, i, NULL)) < 0) + goto cleanup; + + git__free(src_name); + break; + } + } + + /* + * For those candidates still left in the list, we need to + * remove them. We do not remove symrefs, as those are for + * stuff like origin/HEAD which will never match, but we do + * not want to remove them. + */ + git_vector_foreach(&candidates, i, refname) { + git_reference *ref; + git_oid id; + + if (refname == NULL) + continue; + + error = git_reference_lookup(&ref, remote->repo, refname); + /* as we want it gone, let's not consider this an error */ + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + goto cleanup; + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + git_reference_free(ref); + continue; + } + + git_oid_cpy(&id, git_reference_target(ref)); + error = git_reference_delete(ref); + git_reference_free(ref); + if (error < 0) + goto cleanup; + + if (callbacks && callbacks->update_tips) + error = callbacks->update_tips(refname, &id, &zero_id, callbacks->payload); + + if (error < 0) + goto cleanup; + } + +cleanup: + git_vector_free(&remote_refs); + git_vector_free_deep(&candidates); + return error; +} + +static int update_ref( + const git_remote *remote, + const char *ref_name, + git_oid *id, + const char *msg, + const git_remote_callbacks *callbacks) +{ + git_reference *ref; + git_oid old_id; + int error; + + error = git_reference_name_to_id(&old_id, remote->repo, ref_name); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + else if (error == 0 && git_oid_equal(&old_id, id)) + return 0; + + /* If we did find a current reference, make sure we haven't lost a race */ + if (error) + error = git_reference_create(&ref, remote->repo, ref_name, id, true, msg); + else + error = git_reference_create_matching(&ref, remote->repo, ref_name, id, true, &old_id, msg); + + git_reference_free(ref); + + if (error < 0) + return error; + + if (callbacks && callbacks->update_tips && + (error = callbacks->update_tips(ref_name, &old_id, id, callbacks->payload)) < 0) + return error; + + return 0; +} + +static int update_one_tip( + git_vector *update_heads, + git_remote *remote, + git_refspec *spec, + git_remote_head *head, + git_refspec *tagspec, + git_remote_autotag_option_t tagopt, + const char *log_message, + const git_remote_callbacks *callbacks) +{ + git_odb *odb; + git_str refname = GIT_STR_INIT; + git_reference *ref = NULL; + bool autotag = false; + git_oid old; + int valid; + int error; + + if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) + goto done; + + /* Ignore malformed ref names (which also saves us from tag^{} */ + if ((error = git_reference_name_is_valid(&valid, head->name)) < 0) + goto done; + + if (!valid) + goto done; + + /* If we have a tag, see if the auto-follow rules say to update it */ + if (git_refspec_src_matches(tagspec, head->name)) { + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO) + autotag = true; + + if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { + if (git_str_puts(&refname, head->name) < 0) + goto done; + } + } + + /* If we didn't want to auto-follow the tag, check if the refspec matches */ + if (!autotag && git_refspec_src_matches(spec, head->name)) { + if (spec->dst) { + if ((error = git_refspec__transform(&refname, spec, head->name)) < 0) + goto done; + } else { + /* + * no rhs means store it in FETCH_HEAD, even if we don't + * update anything else. + */ + error = git_vector_insert(update_heads, head); + goto done; + } + } + + /* If we still don't have a refname, we don't want it */ + if (git_str_len(&refname) == 0) + goto done; + + /* In autotag mode, only create tags for objects already in db */ + if (autotag && !git_odb_exists(odb, &head->oid)) + goto done; + + if (!autotag && (error = git_vector_insert(update_heads, head)) < 0) + goto done; + + error = git_reference_name_to_id(&old, remote->repo, refname.ptr); + + if (error < 0 && error != GIT_ENOTFOUND) + goto done; + + if (!(error || error == GIT_ENOTFOUND) && + !spec->force && + !git_graph_descendant_of(remote->repo, &head->oid, &old)) { + error = 0; + goto done; + } + + if (error == GIT_ENOTFOUND) { + memset(&old, 0, sizeof(git_oid)); + error = 0; + + if (autotag && (error = git_vector_insert(update_heads, head)) < 0) + goto done; + } + + if (!git_oid__cmp(&old, &head->oid)) + goto done; + + /* In autotag mode, don't overwrite any locally-existing tags */ + error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, + log_message); + + if (error < 0) { + if (error == GIT_EEXISTS) + error = 0; + + goto done; + } + + if (callbacks && callbacks->update_tips != NULL && + (error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0) + git_error_set_after_callback_function(error, "git_remote_fetch"); + +done: + git_reference_free(ref); + git_str_dispose(&refname); + return error; +} + +static int update_tips_for_spec( + git_remote *remote, + const git_remote_callbacks *callbacks, + int update_fetchhead, + git_remote_autotag_option_t tagopt, + git_refspec *spec, + git_vector *refs, + const char *log_message) +{ + git_refspec tagspec; + git_remote_head *head, oid_head; + git_vector update_heads; + int error = 0; + size_t i; + + GIT_ASSERT_ARG(remote); + + if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) + return -1; + + /* Make a copy of the transport's refs */ + if (git_vector_init(&update_heads, 16, NULL) < 0) + return -1; + + /* Update tips based on the remote heads */ + git_vector_foreach(refs, i, head) { + if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0) + goto on_error; + } + + /* Handle specified oid sources */ + if (git_oid__is_hexstr(spec->src)) { + git_oid id; + + if ((error = git_oid_fromstr(&id, spec->src)) < 0) + goto on_error; + + if (spec->dst && + (error = update_ref(remote, spec->dst, &id, log_message, callbacks)) < 0) + goto on_error; + + git_oid_cpy(&oid_head.oid, &id); + oid_head.name = spec->src; + + if ((error = git_vector_insert(&update_heads, &oid_head)) < 0) + goto on_error; + } + + if (update_fetchhead && + (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) + goto on_error; + + git_refspec__dispose(&tagspec); + git_vector_free(&update_heads); + return 0; + +on_error: + git_refspec__dispose(&tagspec); + git_vector_free(&update_heads); + return -1; + +} + +/** + * Iteration over the three vectors, with a pause whenever we find a match + * + * On each stop, we store the iteration stat in the inout i,j,k + * parameters, and return the currently matching passive refspec as + * well as the head which we matched. + */ +static int next_head(const git_remote *remote, git_vector *refs, + git_refspec **out_spec, git_remote_head **out_head, + size_t *out_i, size_t *out_j, size_t *out_k) +{ + const git_vector *active, *passive; + git_remote_head *head; + git_refspec *spec, *passive_spec; + size_t i, j, k; + int valid; + + active = &remote->active_refspecs; + passive = &remote->passive_refspecs; + + i = *out_i; + j = *out_j; + k = *out_k; + + for (; i < refs->length; i++) { + head = git_vector_get(refs, i); + + if (git_reference_name_is_valid(&valid, head->name) < 0) + return -1; + + if (!valid) + continue; + + for (; j < active->length; j++) { + spec = git_vector_get(active, j); + + if (!git_refspec_src_matches(spec, head->name)) + continue; + + for (; k < passive->length; k++) { + passive_spec = git_vector_get(passive, k); + + if (!git_refspec_src_matches(passive_spec, head->name)) + continue; + + *out_spec = passive_spec; + *out_head = head; + *out_i = i; + *out_j = j; + *out_k = k + 1; + return 0; + + } + k = 0; + } + j = 0; + } + + return GIT_ITEROVER; +} + +static int opportunistic_updates( + const git_remote *remote, + const git_remote_callbacks *callbacks, + git_vector *refs, + const char *msg) +{ + size_t i, j, k; + git_refspec *spec; + git_remote_head *head; + git_str refname = GIT_STR_INIT; + int error = 0; + + i = j = k = 0; + + /* Handle refspecs matching remote heads */ + while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) { + /* + * If we got here, there is a refspec which was used + * for fetching which matches the source of one of the + * passive refspecs, so we should update that + * remote-tracking branch, but not add it to + * FETCH_HEAD + */ + + git_str_clear(&refname); + if ((error = git_refspec__transform(&refname, spec, head->name)) < 0 || + (error = update_ref(remote, refname.ptr, &head->oid, msg, callbacks)) < 0) + goto cleanup; + } + + if (error != GIT_ITEROVER) + goto cleanup; + + error = 0; + +cleanup: + git_str_dispose(&refname); + return error; +} + +static int truncate_fetch_head(const char *gitdir) +{ + git_str path = GIT_STR_INIT; + int error; + + if ((error = git_str_joinpath(&path, gitdir, GIT_FETCH_HEAD_FILE)) < 0) + return error; + + error = git_futils_truncate(path.ptr, GIT_REFS_FILE_MODE); + git_str_dispose(&path); + + return error; +} + +int git_remote_update_tips( + git_remote *remote, + const git_remote_callbacks *callbacks, + int update_fetchhead, + git_remote_autotag_option_t download_tags, + const char *reflog_message) +{ + git_refspec *spec, tagspec; + git_vector refs = GIT_VECTOR_INIT; + git_remote_autotag_option_t tagopt; + int error; + size_t i; + + /* push has its own logic hidden away in the push object */ + if (remote->push) { + return git_push_update_tips(remote->push, callbacks); + } + + if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) + return -1; + + + if ((error = ls_to_vector(&refs, remote)) < 0) + goto out; + + if (download_tags == GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) + tagopt = remote->download_tags; + else + tagopt = download_tags; + + if ((error = truncate_fetch_head(git_repository_path(remote->repo))) < 0) + goto out; + + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { + if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0) + goto out; + } + + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (spec->push) + continue; + + if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0) + goto out; + } + + /* Only try to do opportunistic updates if the refspec lists differ. */ + if (remote->passed_refspecs) + error = opportunistic_updates(remote, callbacks, &refs, reflog_message); + +out: + git_vector_free(&refs); + git_refspec__dispose(&tagspec); + return error; +} + +int git_remote_connected(const git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (!remote->transport || !remote->transport->is_connected) + return 0; + + /* Ask the transport if it's connected. */ + return remote->transport->is_connected(remote->transport); +} + +int git_remote_stop(git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (remote->transport && remote->transport->cancel) + remote->transport->cancel(remote->transport); + + return 0; +} + +int git_remote_disconnect(git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (git_remote_connected(remote)) + remote->transport->close(remote->transport); + + return 0; +} + +static void free_heads(git_vector *heads) +{ + git_remote_head *head; + size_t i; + + git_vector_foreach(heads, i, head) { + git__free(head->name); + git__free(head); + } +} + +void git_remote_free(git_remote *remote) +{ + if (remote == NULL) + return; + + if (remote->transport != NULL) { + git_remote_disconnect(remote); + + remote->transport->free(remote->transport); + remote->transport = NULL; + } + + git_vector_free(&remote->refs); + + free_refspecs(&remote->refspecs); + git_vector_free(&remote->refspecs); + + free_refspecs(&remote->active_refspecs); + git_vector_free(&remote->active_refspecs); + + free_refspecs(&remote->passive_refspecs); + git_vector_free(&remote->passive_refspecs); + + free_heads(&remote->local_heads); + git_vector_free(&remote->local_heads); + + git_push_free(remote->push); + git__free(remote->url); + git__free(remote->pushurl); + git__free(remote->name); + git__free(remote); +} + +static int remote_list_cb(const git_config_entry *entry, void *payload) +{ + git_vector *list = payload; + const char *name = entry->name + strlen("remote."); + size_t namelen = strlen(name); + char *remote_name; + + /* we know name matches "remote..(push)?url" */ + + if (!strcmp(&name[namelen - 4], ".url")) + remote_name = git__strndup(name, namelen - 4); /* strip ".url" */ + else + remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */ + GIT_ERROR_CHECK_ALLOC(remote_name); + + return git_vector_insert(list, remote_name); +} + +int git_remote_list(git_strarray *remotes_list, git_repository *repo) +{ + int error; + git_config *cfg; + git_vector list = GIT_VECTOR_INIT; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if ((error = git_vector_init(&list, 4, git__strcmp_cb)) < 0) + return error; + + error = git_config_foreach_match( + cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list); + + if (error < 0) { + git_vector_free_deep(&list); + return error; + } + + git_vector_uniq(&list, git__free); + + remotes_list->strings = + (char **)git_vector_detach(&remotes_list->count, NULL, &list); + + return 0; +} + +const git_indexer_progress *git_remote_stats(git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return &remote->stats; +} + +git_remote_autotag_option_t git_remote_autotag(const git_remote *remote) +{ + return remote->download_tags; +} + +int git_remote_set_autotag(git_repository *repo, const char *remote, git_remote_autotag_option_t value) +{ + git_str var = GIT_STR_INIT; + git_config *config; + int error; + + GIT_ASSERT_ARG(repo && remote); + + if ((error = ensure_remote_name_is_valid(remote)) < 0) + return error; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_str_printf(&var, CONFIG_TAGOPT_FMT, remote))) + return error; + + switch (value) { + case GIT_REMOTE_DOWNLOAD_TAGS_NONE: + error = git_config_set_string(config, var.ptr, "--no-tags"); + break; + case GIT_REMOTE_DOWNLOAD_TAGS_ALL: + error = git_config_set_string(config, var.ptr, "--tags"); + break; + case GIT_REMOTE_DOWNLOAD_TAGS_AUTO: + error = git_config_delete_entry(config, var.ptr); + if (error == GIT_ENOTFOUND) + error = 0; + break; + default: + git_error_set(GIT_ERROR_INVALID, "invalid value for the tagopt setting"); + error = -1; + } + + git_str_dispose(&var); + return error; +} + +int git_remote_prune_refs(const git_remote *remote) +{ + return remote->prune_refs; +} + +static int rename_remote_config_section( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + git_str old_section_name = GIT_STR_INIT, + new_section_name = GIT_STR_INIT; + int error = -1; + + if (git_str_printf(&old_section_name, "remote.%s", old_name) < 0) + goto cleanup; + + if (new_name && + (git_str_printf(&new_section_name, "remote.%s", new_name) < 0)) + goto cleanup; + + error = git_config_rename_section( + repo, + git_str_cstr(&old_section_name), + new_name ? git_str_cstr(&new_section_name) : NULL); + +cleanup: + git_str_dispose(&old_section_name); + git_str_dispose(&new_section_name); + + return error; +} + +struct update_data { + git_config *config; + const char *old_remote_name; + const char *new_remote_name; +}; + +static int update_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + struct update_data *data = (struct update_data *)payload; + + if (strcmp(entry->value, data->old_remote_name)) + return 0; + + return git_config_set_string( + data->config, entry->name, data->new_remote_name); +} + +static int update_branch_remote_config_entry( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + int error; + struct update_data data = { NULL }; + + if ((error = git_repository_config__weakptr(&data.config, repo)) < 0) + return error; + + data.old_remote_name = old_name; + data.new_remote_name = new_name; + + return git_config_foreach_match( + data.config, "branch\\..+\\.remote", update_config_entries_cb, &data); +} + +static int rename_one_remote_reference( + git_reference *reference_in, + const char *old_remote_name, + const char *new_remote_name) +{ + int error; + git_reference *ref = NULL, *dummy = NULL; + git_str namespace = GIT_STR_INIT, old_namespace = GIT_STR_INIT; + git_str new_name = GIT_STR_INIT; + git_str log_message = GIT_STR_INIT; + size_t pfx_len; + const char *target; + + if ((error = git_str_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0) + return error; + + pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1; + git_str_puts(&new_name, namespace.ptr); + if ((error = git_str_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0) + goto cleanup; + + if ((error = git_str_printf(&log_message, + "renamed remote %s to %s", + old_remote_name, new_remote_name)) < 0) + goto cleanup; + + if ((error = git_reference_rename(&ref, reference_in, git_str_cstr(&new_name), 1, + git_str_cstr(&log_message))) < 0) + goto cleanup; + + if (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC) + goto cleanup; + + /* Handle refs like origin/HEAD -> origin/master */ + target = git_reference_symbolic_target(ref); + if ((error = git_str_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0) + goto cleanup; + + if (git__prefixcmp(target, old_namespace.ptr)) + goto cleanup; + + git_str_clear(&new_name); + git_str_puts(&new_name, namespace.ptr); + if ((error = git_str_puts(&new_name, target + pfx_len)) < 0) + goto cleanup; + + error = git_reference_symbolic_set_target(&dummy, ref, git_str_cstr(&new_name), + git_str_cstr(&log_message)); + + git_reference_free(dummy); + +cleanup: + git_reference_free(reference_in); + git_reference_free(ref); + git_str_dispose(&namespace); + git_str_dispose(&old_namespace); + git_str_dispose(&new_name); + git_str_dispose(&log_message); + return error; +} + +static int rename_remote_references( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + int error; + git_str buf = GIT_STR_INIT; + git_reference *ref; + git_reference_iterator *iter; + + if ((error = git_str_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0) + return error; + + error = git_reference_iterator_glob_new(&iter, repo, git_str_cstr(&buf)); + git_str_dispose(&buf); + + if (error < 0) + return error; + + while ((error = git_reference_next(&ref, iter)) == 0) { + if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) + break; + } + + git_reference_iterator_free(iter); + + return (error == GIT_ITEROVER) ? 0 : error; +} + +static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name) +{ + git_config *config; + git_str base = GIT_STR_INIT, var = GIT_STR_INIT, val = GIT_STR_INIT; + const git_refspec *spec; + size_t i; + int error = 0; + + if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0) + return error; + + if ((error = git_vector_init(problems, 1, NULL)) < 0) + return error; + + if ((error = default_fetchspec_for_name(&base, remote->name)) < 0) + return error; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push) + continue; + + /* Does the dst part of the refspec follow the expected format? */ + if (strcmp(git_str_cstr(&base), spec->string)) { + char *dup; + + dup = git__strdup(spec->string); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(problems, dup)) < 0) + break; + + continue; + } + + /* If we do want to move it to the new section */ + + git_str_clear(&val); + git_str_clear(&var); + + if (default_fetchspec_for_name(&val, new_name) < 0 || + git_str_printf(&var, "remote.%s.fetch", new_name) < 0) + { + error = -1; + break; + } + + if ((error = git_config_set_string( + config, git_str_cstr(&var), git_str_cstr(&val))) < 0) + break; + } + + git_str_dispose(&base); + git_str_dispose(&var); + git_str_dispose(&val); + + if (error < 0) { + char *str; + git_vector_foreach(problems, i, str) + git__free(str); + + git_vector_free(problems); + } + + return error; +} + +int git_remote_rename(git_strarray *out, git_repository *repo, const char *name, const char *new_name) +{ + int error; + git_vector problem_refspecs = GIT_VECTOR_INIT; + git_remote *remote = NULL; + + GIT_ASSERT_ARG(out && repo && name && new_name); + + if ((error = git_remote_lookup(&remote, repo, name)) < 0) + return error; + + if ((error = ensure_remote_name_is_valid(new_name)) < 0) + goto cleanup; + + if ((error = ensure_remote_doesnot_exist(repo, new_name)) < 0) + goto cleanup; + + if ((error = rename_remote_config_section(repo, name, new_name)) < 0) + goto cleanup; + + if ((error = update_branch_remote_config_entry(repo, name, new_name)) < 0) + goto cleanup; + + if ((error = rename_remote_references(repo, name, new_name)) < 0) + goto cleanup; + + if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0) + goto cleanup; + + out->count = problem_refspecs.length; + out->strings = (char **) problem_refspecs.contents; + +cleanup: + if (error < 0) + git_vector_free(&problem_refspecs); + + git_remote_free(remote); + return error; +} + +int git_remote_name_is_valid(int *valid, const char *remote_name) +{ + git_str buf = GIT_STR_INIT; + git_refspec refspec = {0}; + int error; + + GIT_ASSERT(valid); + + *valid = 0; + + if (!remote_name || *remote_name == '\0') + return 0; + + if ((error = git_str_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name)) < 0) + goto done; + + error = git_refspec__parse(&refspec, git_str_cstr(&buf), true); + + if (!error) + *valid = 1; + else if (error == GIT_EINVALIDSPEC) + error = 0; + +done: + git_str_dispose(&buf); + git_refspec__dispose(&refspec); + + return error; +} + +git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (spec->push) + continue; + + if (git_refspec_src_matches(spec, refname)) + return spec; + } + + return NULL; +} + +git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (spec->push) + continue; + + if (git_refspec_dst_matches(spec, refname)) + return spec; + } + + return NULL; +} + +int git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec) +{ + return write_add_refspec(repo, remote, refspec, true); +} + +int git_remote_add_push(git_repository *repo, const char *remote, const char *refspec) +{ + return write_add_refspec(repo, remote, refspec, false); +} + +static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push) +{ + size_t i; + git_vector refspecs; + git_refspec *spec; + char *dup; + + if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0) + return -1; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push != push) + continue; + + if ((dup = git__strdup(spec->string)) == NULL) + goto on_error; + + if (git_vector_insert(&refspecs, dup) < 0) { + git__free(dup); + goto on_error; + } + } + + array->strings = (char **)refspecs.contents; + array->count = refspecs.length; + + return 0; + +on_error: + git_vector_free_deep(&refspecs); + + return -1; +} + +int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote) +{ + return copy_refspecs(array, remote, false); +} + +int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote) +{ + return copy_refspecs(array, remote, true); +} + +size_t git_remote_refspec_count(const git_remote *remote) +{ + return remote->refspecs.length; +} + +const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n) +{ + return git_vector_get(&remote->refspecs, n); +} + +int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT); + return 0; +} + +/* asserts a branch..remote format */ +static const char *name_offset(size_t *len_out, const char *name) +{ + size_t prefix_len; + const char *dot; + + prefix_len = strlen("remote."); + dot = strchr(name + prefix_len, '.'); + + GIT_ASSERT_ARG_WITH_RETVAL(dot, NULL); + + *len_out = dot - name - prefix_len; + return name + prefix_len; +} + +static int remove_branch_config_related_entries( + git_repository *repo, + const char *remote_name) +{ + int error; + git_config *config; + git_config_entry *entry; + git_config_iterator *iter; + git_str buf = GIT_STR_INIT; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) + return error; + + /* find any branches with us as upstream and remove that config */ + while ((error = git_config_next(&entry, iter)) == 0) { + const char *branch; + size_t branch_len; + + if (strcmp(remote_name, entry->value)) + continue; + + if ((branch = name_offset(&branch_len, entry->name)) == NULL) { + error = -1; + break; + } + + git_str_clear(&buf); + if ((error = git_str_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch)) < 0) + break; + + if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { + if (error != GIT_ENOTFOUND) + break; + git_error_clear(); + } + + git_str_clear(&buf); + if ((error = git_str_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch)) < 0) + break; + + if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { + if (error != GIT_ENOTFOUND) + break; + git_error_clear(); + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_str_dispose(&buf); + git_config_iterator_free(iter); + return error; +} + +static int remove_refs(git_repository *repo, const git_refspec *spec) +{ + git_reference_iterator *iter = NULL; + git_vector refs; + const char *name; + char *dup; + int error; + size_t i; + + if ((error = git_vector_init(&refs, 8, NULL)) < 0) + return error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + goto cleanup; + + while ((error = git_reference_next_name(&name, iter)) == 0) { + if (!git_refspec_dst_matches(spec, name)) + continue; + + dup = git__strdup(name); + if (!dup) { + error = -1; + goto cleanup; + } + + if ((error = git_vector_insert(&refs, dup)) < 0) + goto cleanup; + } + if (error == GIT_ITEROVER) + error = 0; + if (error < 0) + goto cleanup; + + git_vector_foreach(&refs, i, name) { + if ((error = git_reference_remove(repo, name)) < 0) + break; + } + +cleanup: + git_reference_iterator_free(iter); + git_vector_foreach(&refs, i, dup) { + git__free(dup); + } + git_vector_free(&refs); + return error; +} + +static int remove_remote_tracking(git_repository *repo, const char *remote_name) +{ + git_remote *remote; + int error; + size_t i, count; + + /* we want to use what's on the config, regardless of changes to the instance in memory */ + if ((error = git_remote_lookup(&remote, repo, remote_name)) < 0) + return error; + + count = git_remote_refspec_count(remote); + for (i = 0; i < count; i++) { + const git_refspec *refspec = git_remote_get_refspec(remote, i); + + /* shouldn't ever actually happen */ + if (refspec == NULL) + continue; + + if ((error = remove_refs(repo, refspec)) < 0) + break; + } + + git_remote_free(remote); + return error; +} + +int git_remote_delete(git_repository *repo, const char *name) +{ + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = remove_branch_config_related_entries(repo, name)) < 0 || + (error = remove_remote_tracking(repo, name)) < 0 || + (error = rename_remote_config_section(repo, name, NULL)) < 0) + return error; + + return 0; +} + +int git_remote_default_branch(git_buf *out, git_remote *remote) +{ + GIT_BUF_WRAP_PRIVATE(out, git_remote__default_branch, remote); +} + +int git_remote__default_branch(git_str *out, git_remote *remote) +{ + const git_remote_head **heads; + const git_remote_head *guess = NULL; + const git_oid *head_id; + size_t heads_len, i; + git_str local_default = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + + if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) + goto done; + + if (heads_len == 0 || strcmp(heads[0]->name, GIT_HEAD_FILE)) { + error = GIT_ENOTFOUND; + goto done; + } + + /* the first one must be HEAD so if that has the symref info, we're done */ + if (heads[0]->symref_target) { + error = git_str_puts(out, heads[0]->symref_target); + goto done; + } + + /* + * If there's no symref information, we have to look over them + * and guess. We return the first match unless the default + * branch is a candidate. Then we return the default branch. + */ + + if ((error = git_repository_initialbranch(&local_default, remote->repo)) < 0) + goto done; + + head_id = &heads[0]->oid; + + for (i = 1; i < heads_len; i++) { + if (git_oid_cmp(head_id, &heads[i]->oid)) + continue; + + if (git__prefixcmp(heads[i]->name, GIT_REFS_HEADS_DIR)) + continue; + + if (!guess) { + guess = heads[i]; + continue; + } + + if (!git__strcmp(local_default.ptr, heads[i]->name)) { + guess = heads[i]; + break; + } + } + + if (!guess) { + error = GIT_ENOTFOUND; + goto done; + } + + error = git_str_puts(out, guess->name); + +done: + git_str_dispose(&local_default); + return error; +} + +GIT_INLINE(int) connect_opts_from_push_opts( + git_remote_connect_options *out, + git_remote *remote, + const git_push_options *push_opts) +{ + git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; + copy_opts(&tmp, push_opts); + return git_remote_connect_options_normalize(out, remote->repo, &tmp); +} + +int git_remote_upload( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + git_push *push; + git_refspec *spec; + size_t i; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if ((error = connect_opts_from_push_opts(&connect_opts, remote, opts)) < 0) + goto cleanup; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_PUSH, &connect_opts)) < 0) + goto cleanup; + + free_refspecs(&remote->active_refspecs); + if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) + goto cleanup; + + if (remote->push) { + git_push_free(remote->push); + remote->push = NULL; + } + + if ((error = git_push_new(&remote->push, remote, opts)) < 0) + goto cleanup; + + push = remote->push; + + if (refspecs && refspecs->count > 0) { + for (i = 0; i < refspecs->count; i++) { + if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0) + goto cleanup; + } + } else { + git_vector_foreach(&remote->refspecs, i, spec) { + if (!spec->push) + continue; + if ((error = git_push_add_refspec(push, spec->string)) < 0) + goto cleanup; + } + } + + if ((error = git_push_finish(push)) < 0) + goto cleanup; + + if (connect_opts.callbacks.push_update_reference && + (error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload)) < 0) + goto cleanup; + +cleanup: + git_remote_connect_options_dispose(&connect_opts); + return error; +} + +int git_remote_push( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_push_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = git_remote_upload(remote, refspecs, opts)) < 0) + goto done; + + error = git_remote_update_tips(remote, &connect_opts.callbacks, 0, 0, NULL); + +done: + git_remote_disconnect(remote); + git_remote_connect_options_dispose(&connect_opts); + return error; +} + +#define PREFIX "url" +#define SUFFIX_FETCH "insteadof" +#define SUFFIX_PUSH "pushinsteadof" + +static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty) +{ + size_t match_length, prefix_length, suffix_length; + char *replacement = NULL; + const char *regexp; + + git_str result = GIT_STR_INIT; + git_config_entry *entry; + git_config_iterator *iter; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(config); + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + + /* Add 1 to prefix/suffix length due to the additional escaped dot */ + prefix_length = strlen(PREFIX) + 1; + if (direction == GIT_DIRECTION_FETCH) { + regexp = PREFIX "\\..*\\." SUFFIX_FETCH; + suffix_length = strlen(SUFFIX_FETCH) + 1; + } else { + regexp = PREFIX "\\..*\\." SUFFIX_PUSH; + suffix_length = strlen(SUFFIX_PUSH) + 1; + } + + if (git_config_iterator_glob_new(&iter, config, regexp) < 0) + return -1; + + match_length = 0; + while (git_config_next(&entry, iter) == 0) { + size_t n, replacement_length; + + /* Check if entry value is a prefix of URL */ + if (git__prefixcmp(url, entry->value)) + continue; + + /* Check if entry value is longer than previous + * prefixes */ + if ((n = strlen(entry->value)) <= match_length) + continue; + + git__free(replacement); + match_length = n; + + /* Cut off prefix and suffix of the value */ + replacement_length = + strlen(entry->name) - (prefix_length + suffix_length); + replacement = git__strndup(entry->name + prefix_length, + replacement_length); + } + + git_config_iterator_free(iter); + + if (match_length == 0 && use_default_if_empty) { + *out = git__strdup(url); + return *out ? 0 : -1; + } else if (match_length == 0) { + *out = NULL; + return 0; + } + + git_str_printf(&result, "%s%s", replacement, url + match_length); + + git__free(replacement); + + *out = git_str_detach(&result); + return 0; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD + +int git_remote_is_valid_name(const char *remote_name) +{ + int valid = 0; + + git_remote_name_is_valid(&valid, remote_name); + return valid; +} + +#endif diff --git a/src/libgit2/remote.h b/src/libgit2/remote.h new file mode 100644 index 000000000..41ee58e0f --- /dev/null +++ b/src/libgit2/remote.h @@ -0,0 +1,60 @@ +/* + * 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_remote_h__ +#define INCLUDE_remote_h__ + +#include "common.h" + +#include "git2/remote.h" +#include "git2/transport.h" +#include "git2/sys/remote.h" +#include "git2/sys/transport.h" + +#include "refspec.h" +#include "vector.h" +#include "net.h" + +#define GIT_REMOTE_ORIGIN "origin" + +struct git_remote { + char *name; + char *url; + char *pushurl; + git_vector refs; + git_vector refspecs; + git_vector active_refspecs; + git_vector passive_refspecs; + git_vector local_heads; + git_transport *transport; + git_repository *repo; + git_push *push; + git_indexer_progress stats; + unsigned int need_pack; + git_remote_autotag_option_t download_tags; + int prune_refs; + int passed_refspecs; +}; + +int git_remote__urlfordirection(git_str *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks); +int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url); + +git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname); +git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname); + +int git_remote__default_branch(git_str *out, git_remote *remote); + +int git_remote_connect_options_dup( + git_remote_connect_options *dst, + const git_remote_connect_options *src); +int git_remote_connect_options_normalize( + git_remote_connect_options *dst, + git_repository *repo, + const git_remote_connect_options *src); + +int git_remote_capabilities(unsigned int *out, git_remote *remote); + +#endif diff --git a/src/libgit2/repo_template.h b/src/libgit2/repo_template.h new file mode 100644 index 000000000..099279aa7 --- /dev/null +++ b/src/libgit2/repo_template.h @@ -0,0 +1,58 @@ +/* + * 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_repo_template_h__ +#define INCLUDE_repo_template_h__ + +#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" +#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" + +#define GIT_HOOKS_DIR "hooks/" +#define GIT_HOOKS_DIR_MODE 0777 + +#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" +#define GIT_HOOKS_README_MODE 0777 +#define GIT_HOOKS_README_CONTENT \ +"#!/bin/sh\n"\ +"#\n"\ +"# Place appropriately named executable hook scripts into this directory\n"\ +"# to intercept various actions that git takes. See `git help hooks` for\n"\ +"# more information.\n" + +#define GIT_INFO_DIR "info/" +#define GIT_INFO_DIR_MODE 0777 + +#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" +#define GIT_INFO_EXCLUDE_MODE 0666 +#define GIT_INFO_EXCLUDE_CONTENT \ +"# File patterns to ignore; see `git help ignore` for more information.\n"\ +"# Lines that start with '#' are comments.\n" + +#define GIT_DESC_FILE "description" +#define GIT_DESC_MODE 0666 +#define GIT_DESC_CONTENT \ +"Unnamed repository; edit this file 'description' to name the repository.\n" + +typedef struct { + const char *path; + mode_t mode; + const char *content; +} repo_template_item; + +static repo_template_item repo_template[] = { + { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */ + { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */ + { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */ + { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */ + { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */ + { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */ + { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, + { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, + { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, + { NULL, 0, NULL } +}; + +#endif diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c new file mode 100644 index 000000000..f761b5f32 --- /dev/null +++ b/src/libgit2/repository.c @@ -0,0 +1,3386 @@ +/* + * 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 "repository.h" + +#include + +#include "git2/object.h" +#include "git2/sys/repository.h" + +#include "buf.h" +#include "common.h" +#include "commit.h" +#include "tag.h" +#include "blob.h" +#include "futils.h" +#include "sysdir.h" +#include "filebuf.h" +#include "index.h" +#include "config.h" +#include "refs.h" +#include "filter.h" +#include "odb.h" +#include "refdb.h" +#include "remote.h" +#include "merge.h" +#include "diff_driver.h" +#include "annotated_commit.h" +#include "submodule.h" +#include "worktree.h" +#include "path.h" +#include "strmap.h" + +#ifdef GIT_WIN32 +# include "win32/w32_util.h" +#endif + +bool git_repository__validate_ownership = true; +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, 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(int *version, git_config *config); +static int check_extensions(git_config *config, int version); +static int load_global_config(git_config **config); + +#define GIT_COMMONDIR_FILE "commondir" +#define GIT_GITDIR_FILE "gitdir" + +#define GIT_FILE_CONTENT_PREFIX "gitdir:" + +#define GIT_BRANCH_DEFAULT "master" + +#define GIT_REPO_VERSION 0 +#define GIT_REPO_MAX_VERSION 1 + +git_str git_repository__reserved_names_win32[] = { + { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, + { GIT_DIR_SHORTNAME, 0, CONST_STRLEN(GIT_DIR_SHORTNAME) } +}; +size_t git_repository__reserved_names_win32_len = 2; + +git_str git_repository__reserved_names_posix[] = { + { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, +}; +size_t git_repository__reserved_names_posix_len = 1; + +static void set_odb(git_repository *repo, git_odb *odb) +{ + if (odb) { + GIT_REFCOUNT_OWN(odb, repo); + GIT_REFCOUNT_INC(odb); + } + + if ((odb = git_atomic_swap(repo->_odb, odb)) != NULL) { + GIT_REFCOUNT_OWN(odb, NULL); + git_odb_free(odb); + } +} + +static void set_refdb(git_repository *repo, git_refdb *refdb) +{ + if (refdb) { + GIT_REFCOUNT_OWN(refdb, repo); + GIT_REFCOUNT_INC(refdb); + } + + if ((refdb = git_atomic_swap(repo->_refdb, refdb)) != NULL) { + GIT_REFCOUNT_OWN(refdb, NULL); + git_refdb_free(refdb); + } +} + +static void set_config(git_repository *repo, git_config *config) +{ + if (config) { + GIT_REFCOUNT_OWN(config, repo); + GIT_REFCOUNT_INC(config); + } + + if ((config = git_atomic_swap(repo->_config, config)) != NULL) { + GIT_REFCOUNT_OWN(config, NULL); + git_config_free(config); + } + + git_repository__configmap_lookup_cache_clear(repo); +} + +static void set_index(git_repository *repo, git_index *index) +{ + if (index) { + GIT_REFCOUNT_OWN(index, repo); + GIT_REFCOUNT_INC(index); + } + + if ((index = git_atomic_swap(repo->_index, index)) != NULL) { + GIT_REFCOUNT_OWN(index, NULL); + git_index_free(index); + } +} + +int git_repository__cleanup(git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + + git_repository_submodule_cache_clear(repo); + git_cache_clear(&repo->objects); + git_attr_cache_flush(repo); + + set_config(repo, NULL); + set_index(repo, NULL); + set_odb(repo, NULL); + set_refdb(repo, NULL); + + return 0; +} + +void git_repository_free(git_repository *repo) +{ + size_t i; + + if (repo == NULL) + return; + + git_repository__cleanup(repo); + + git_cache_dispose(&repo->objects); + + git_diff_driver_registry_free(repo->diff_drivers); + repo->diff_drivers = NULL; + + for (i = 0; i < repo->reserved_names.size; i++) + git_str_dispose(git_array_get(repo->reserved_names, i)); + git_array_clear(repo->reserved_names); + + git__free(repo->gitlink); + git__free(repo->gitdir); + git__free(repo->commondir); + git__free(repo->workdir); + git__free(repo->namespace); + git__free(repo->ident_name); + git__free(repo->ident_email); + + git__memzero(repo, sizeof(*repo)); + git__free(repo); +} + +/* Check if we have a separate commondir (e.g. we have a worktree) */ +static int lookup_commondir(bool *separate, git_str *commondir, git_str *repository_path) +{ + git_str common_link = GIT_STR_INIT; + int error; + + /* + * If there's no commondir file, the repository path is the + * common path, but it needs a trailing slash. + */ + if (!git_fs_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { + if ((error = git_str_set(commondir, repository_path->ptr, repository_path->size)) == 0) + error = git_fs_path_to_dir(commondir); + + *separate = false; + goto done; + } + + *separate = true; + + if ((error = git_str_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 || + (error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0) + goto done; + + git_str_rtrim(&common_link); + if (git_fs_path_is_relative(common_link.ptr)) { + if ((error = git_str_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0) + goto done; + } else { + git_str_swap(commondir, &common_link); + } + + git_str_dispose(&common_link); + + /* Make sure the commondir path always has a trailing slash */ + error = git_fs_path_prettify_dir(commondir, commondir->ptr, NULL); + +done: + return error; +} + +GIT_INLINE(int) validate_repo_path(git_str *path) +{ + /* + * The longest static path in a repository (or commondir) is the + * packed refs file. (Loose refs may be longer since they + * include the reference name, but will be validated when the + * path is constructed.) + */ + static size_t suffix_len = + CONST_STRLEN("objects/pack/pack-.pack.lock") + + GIT_OID_HEXSZ; + + return git_fs_path_validate_str_length_with_suffix( + path, suffix_len); +} + +/* + * Git repository open methods + * + * Open a repository object from its path + */ +static int is_valid_repository_path(bool *out, git_str *repository_path, git_str *common_path) +{ + bool separate_commondir = false; + int error; + + *out = false; + + if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0) + return error; + + /* Ensure HEAD file exists */ + if (git_fs_path_contains_file(repository_path, GIT_HEAD_FILE) == false) + return 0; + + /* Check files in common dir */ + if (git_fs_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) + return 0; + if (git_fs_path_contains_dir(common_path, GIT_REFS_DIR) == false) + return 0; + + /* Ensure the repo (and commondir) are valid paths */ + if ((error = validate_repo_path(common_path)) < 0 || + (separate_commondir && + (error = validate_repo_path(repository_path)) < 0)) + return error; + + *out = true; + return 0; +} + +static git_repository *repository_alloc(void) +{ + git_repository *repo = git__calloc(1, sizeof(git_repository)); + + if (repo == NULL || + git_cache_init(&repo->objects) < 0) + goto on_error; + + git_array_init_to_size(repo->reserved_names, 4); + if (!repo->reserved_names.ptr) + goto on_error; + + /* 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_dispose(&repo->objects); + + git__free(repo); + return NULL; +} + +int git_repository_new(git_repository **out) +{ + git_repository *repo; + + *out = repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + repo->is_bare = 1; + repo->is_worktree = 0; + + return 0; +} + +static int load_config_data(git_repository *repo, const git_config *config) +{ + int is_bare; + + int err = git_config_get_bool(&is_bare, config, "core.bare"); + if (err < 0 && err != GIT_ENOTFOUND) + return err; + + /* Try to figure out if it's bare, default to non-bare if it's not set */ + if (err != GIT_ENOTFOUND) + repo->is_bare = is_bare && !repo->is_worktree; + else + repo->is_bare = 0; + + return 0; +} + +static int load_workdir(git_repository *repo, git_config *config, git_str *parent_path) +{ + int error; + git_config_entry *ce; + git_str worktree = GIT_STR_INIT; + git_str path = GIT_STR_INIT; + + if (repo->is_bare) + return 0; + + if ((error = git_config__lookup_entry( + &ce, config, "core.worktree", false)) < 0) + return error; + + if (repo->is_worktree) { + char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); + if (!gitlink) { + error = -1; + goto cleanup; + } + + git_str_attach(&worktree, gitlink, 0); + + if ((git_fs_path_dirname_r(&worktree, worktree.ptr)) < 0 || + git_fs_path_to_dir(&worktree) < 0) { + error = -1; + goto cleanup; + } + + repo->workdir = git_str_detach(&worktree); + } + else if (ce && ce->value) { + if ((error = git_fs_path_prettify_dir( + &worktree, ce->value, repo->gitdir)) < 0) + goto cleanup; + + repo->workdir = git_str_detach(&worktree); + } + else if (parent_path && git_fs_path_isdir(parent_path->ptr)) + repo->workdir = git_str_detach(parent_path); + else { + if (git_fs_path_dirname_r(&worktree, repo->gitdir) < 0 || + git_fs_path_to_dir(&worktree) < 0) { + error = -1; + goto cleanup; + } + + repo->workdir = git_str_detach(&worktree); + } + + GIT_ERROR_CHECK_ALLOC(repo->workdir); +cleanup: + git_str_dispose(&path); + git_config_entry_free(ce); + return error; +} + +/* + * This function returns furthest offset into path where a ceiling dir + * is found, so we can stop processing the path at that point. + * + * Note: converting this to use git_strs instead of GIT_PATH_MAX buffers on + * the stack could remove directories name limits, but at the cost of doing + * repeated malloc/frees inside the loop below, so let's not do it now. + */ +static size_t find_ceiling_dir_offset( + const char *path, + const char *ceiling_directories) +{ + char buf[GIT_PATH_MAX + 1]; + char buf2[GIT_PATH_MAX + 1]; + const char *ceil, *sep; + size_t len, max_len = 0, min_len; + + GIT_ASSERT_ARG(path); + + min_len = (size_t)(git_fs_path_root(path) + 1); + + if (ceiling_directories == NULL || min_len == 0) + return min_len; + + for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { + for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); + len = sep - ceil; + + if (len == 0 || len >= sizeof(buf) || git_fs_path_root(ceil) == -1) + continue; + + strncpy(buf, ceil, len); + buf[len] = '\0'; + + if (p_realpath(buf, buf2) == NULL) + continue; + + len = strlen(buf2); + if (len > 0 && buf2[len-1] == '/') + buf[--len] = '\0'; + + if (!strncmp(path, buf2, len) && + (path[len] == '/' || !path[len]) && + len > max_len) + { + max_len = len; + } + } + + return (max_len <= min_len ? min_len : max_len); +} + +/* + * Read the contents of `file_path` and set `path_out` to the repo dir that + * it points to. Before calling, set `path_out` to the base directory that + * should be used if the contents of `file_path` are a relative path. + */ +static int read_gitfile(git_str *path_out, const char *file_path) +{ + int error = 0; + git_str file = GIT_STR_INIT; + size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(file_path); + + if (git_futils_readbuffer(&file, file_path) < 0) + return -1; + + git_str_rtrim(&file); + /* apparently on Windows, some people use backslashes in paths */ + git_fs_path_mkposix(file.ptr); + + if (git_str_len(&file) <= prefix_len || + memcmp(git_str_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) + { + git_error_set(GIT_ERROR_REPOSITORY, + "the `.git` file at '%s' is malformed", file_path); + error = -1; + } + else if ((error = git_fs_path_dirname_r(path_out, file_path)) >= 0) { + const char *gitlink = git_str_cstr(&file) + prefix_len; + while (*gitlink && git__isspace(*gitlink)) gitlink++; + + error = git_fs_path_prettify_dir( + path_out, gitlink, git_str_cstr(path_out)); + } + + git_str_dispose(&file); + return error; +} + +typedef struct { + const char *repo_path; + git_str tmp; + bool *is_safe; +} validate_ownership_data; + +static int validate_ownership_cb(const git_config_entry *entry, void *payload) +{ + validate_ownership_data *data = payload; + + if (strcmp(entry->value, "") == 0) + *data->is_safe = false; + + if (git_fs_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 && + strcmp(data->tmp.ptr, data->repo_path) == 0) + *data->is_safe = true; + + return 0; +} + +static int validate_ownership_config(bool *is_safe, const char *path) +{ + validate_ownership_data ownership_data = { + path, GIT_STR_INIT, is_safe + }; + git_config *config; + int error; + + if (load_global_config(&config) != 0) + return 0; + + error = git_config_get_multivar_foreach(config, + "safe.directory", NULL, + validate_ownership_cb, + &ownership_data); + + git_config_free(config); + git_str_dispose(&ownership_data.tmp); + + return error; +} + +static int validate_ownership_path(bool *is_safe, const char *path) +{ + git_fs_path_owner_t owner_level = + GIT_FS_PATH_OWNER_CURRENT_USER | + GIT_FS_PATH_USER_IS_ADMINISTRATOR | + GIT_FS_PATH_OWNER_RUNNING_SUDO; + int error = 0; + + if (path) + error = git_fs_path_owner_is(is_safe, path, owner_level); + + if (error == GIT_ENOTFOUND) { + *is_safe = true; + error = 0; + } + + return error; +} + +static int validate_ownership(git_repository *repo) +{ + const char *validation_paths[3] = { NULL }, *path; + size_t validation_len = 0, i; + bool is_safe = false; + int error = 0; + + /* + * If there's a worktree, validate the permissions to it *and* + * the git directory, and use the worktree as the configuration + * key for allowlisting the directory. In a bare setup, only + * look at the gitdir and use that as the allowlist. So we + * examine all `validation_paths` but use only the first as + * the configuration lookup. + */ + + if (repo->workdir) + validation_paths[validation_len++] = repo->workdir; + + if (repo->gitlink) + validation_paths[validation_len++] = repo->gitlink; + + validation_paths[validation_len++] = repo->gitdir; + + for (i = 0; i < validation_len; i++) { + path = validation_paths[i]; + + if ((error = validate_ownership_path(&is_safe, path)) < 0) + goto done; + + if (!is_safe) + break; + } + + if (is_safe || + (error = validate_ownership_config(&is_safe, validation_paths[0])) < 0) + goto done; + + if (!is_safe) { + git_error_set(GIT_ERROR_CONFIG, + "repository path '%s' is not owned by current user", + path); + error = GIT_EOWNER; + } + +done: + return error; +} + +static int find_repo( + git_str *gitdir_path, + git_str *workdir_path, + git_str *gitlink_path, + git_str *commondir_path, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) +{ + git_str path = GIT_STR_INIT; + git_str repo_link = GIT_STR_INIT; + git_str common_link = GIT_STR_INIT; + struct stat st; + dev_t initial_device = 0; + int min_iterations; + bool in_dot_git, is_valid; + size_t ceiling_offset = 0; + int error; + + git_str_clear(gitdir_path); + + error = git_fs_path_prettify(&path, start_path, NULL); + if (error < 0) + return error; + + /* in_dot_git toggles each loop: + * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a + * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we + * assume we started with /a/b/c.git and don't append .git the first + * time through. + * min_iterations indicates the number of iterations left before going + * further counts as a search. */ + if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { + in_dot_git = true; + min_iterations = 1; + } else { + in_dot_git = false; + min_iterations = 2; + } + + for (;;) { + if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) { + if (!in_dot_git) { + if ((error = git_str_joinpath(&path, path.ptr, DOT_GIT)) < 0) + goto out; + } + in_dot_git = !in_dot_git; + } + + if (p_stat(path.ptr, &st) == 0) { + /* check that we have not crossed device boundaries */ + if (initial_device == 0) + initial_device = st.st_dev; + else if (st.st_dev != initial_device && + !(flags & GIT_REPOSITORY_OPEN_CROSS_FS)) + break; + + if (S_ISDIR(st.st_mode)) { + if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) + goto out; + + if (is_valid) { + if ((error = git_fs_path_to_dir(&path)) < 0 || + (error = git_str_set(gitdir_path, path.ptr, path.size)) < 0) + goto out; + + if (gitlink_path) + if ((error = git_str_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) + goto out; + if (commondir_path) + git_str_swap(&common_link, commondir_path); + + break; + } + } 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_str_swap(gitdir_path, &repo_link); + + if (gitlink_path) + if ((error = git_str_put(gitlink_path, path.ptr, path.size)) < 0) + goto out; + if (commondir_path) + git_str_swap(&common_link, commondir_path); + } + break; + } + } + + /* 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 ((error = git_fs_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. */ + if (min_iterations && (--min_iterations == 0)) + 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))) + break; + } + + if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (!git_str_len(gitdir_path)) + git_str_clear(workdir_path); + else if ((error = git_fs_path_dirname_r(workdir_path, path.ptr)) < 0 || + (error = git_fs_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_str_len(gitdir_path)) { + git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); + error = GIT_ENOTFOUND; + goto out; + } + +out: + git_str_dispose(&path); + git_str_dispose(&repo_link); + git_str_dispose(&common_link); + return error; +} + +int git_repository_open_bare( + git_repository **repo_ptr, + const char *bare_path) +{ + git_str path = GIT_STR_INIT, common_path = GIT_STR_INIT; + git_repository *repo = NULL; + bool is_valid; + int error; + + if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 || + (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) + return error; + + if (!is_valid) { + git_str_dispose(&path); + git_str_dispose(&common_path); + git_error_set(GIT_ERROR_REPOSITORY, "path is not a repository: %s", bare_path); + return GIT_ENOTFOUND; + } + + repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + repo->gitdir = git_str_detach(&path); + GIT_ERROR_CHECK_ALLOC(repo->gitdir); + repo->commondir = git_str_detach(&common_path); + GIT_ERROR_CHECK_ALLOC(repo->commondir); + + /* of course we're bare! */ + repo->is_bare = 1; + repo->is_worktree = 0; + repo->workdir = NULL; + + *repo_ptr = repo; + return 0; +} + +static int _git_repository_open_ext_from_env( + git_repository **out, + const char *start_path) +{ + git_repository *repo = NULL; + git_index *index = NULL; + git_odb *odb = NULL; + git_str dir_buf = GIT_STR_INIT; + git_str ceiling_dirs_buf = GIT_STR_INIT; + git_str across_fs_buf = GIT_STR_INIT; + git_str index_file_buf = GIT_STR_INIT; + git_str namespace_buf = GIT_STR_INIT; + git_str object_dir_buf = GIT_STR_INIT; + git_str alts_buf = GIT_STR_INIT; + git_str work_tree_buf = GIT_STR_INIT; + git_str common_dir_buf = GIT_STR_INIT; + const char *ceiling_dirs = NULL; + unsigned flags = 0; + int error; + + if (!start_path) { + error = git__getenv(&dir_buf, "GIT_DIR"); + if (error == GIT_ENOTFOUND) { + git_error_clear(); + start_path = "."; + } else if (error < 0) + goto error; + else { + start_path = git_str_cstr(&dir_buf); + flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; + flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; + } + } + + error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else + ceiling_dirs = git_str_cstr(&ceiling_dirs_buf); + + error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + int across_fs = 0; + error = git_config_parse_bool(&across_fs, git_str_cstr(&across_fs_buf)); + if (error < 0) + goto error; + if (across_fs) + flags |= GIT_REPOSITORY_OPEN_CROSS_FS; + } + + error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + error = git_index_open(&index, git_str_cstr(&index_file_buf)); + if (error < 0) + goto error; + } + + error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + + error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + error = git_odb_open(&odb, git_str_cstr(&object_dir_buf)); + if (error < 0) + goto error; + } + + error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + git_error_set(GIT_ERROR_INVALID, "GIT_WORK_TREE unimplemented"); + error = GIT_ERROR; + goto error; + } + + error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + git_error_set(GIT_ERROR_INVALID, "GIT_COMMON_DIR unimplemented"); + error = GIT_ERROR; + goto error; + } + + error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); + if (error < 0) + goto error; + + if (odb) + git_repository_set_odb(repo, odb); + + error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } else if (error < 0) + goto error; + else { + const char *end; + char *alt, *sep; + if (!odb) { + error = git_repository_odb(&odb, repo); + if (error < 0) + goto error; + } + + end = git_str_cstr(&alts_buf) + git_str_len(&alts_buf); + for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { + for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) + ; + if (*sep) + *sep = '\0'; + error = git_odb_add_disk_alternate(odb, alt); + if (error < 0) + goto error; + } + } + + if (git_str_len(&namespace_buf)) { + error = git_repository_set_namespace(repo, git_str_cstr(&namespace_buf)); + if (error < 0) + goto error; + } + + git_repository_set_index(repo, index); + + if (out) { + *out = repo; + goto success; + } +error: + git_repository_free(repo); +success: + git_odb_free(odb); + git_index_free(index); + git_str_dispose(&common_dir_buf); + git_str_dispose(&work_tree_buf); + git_str_dispose(&alts_buf); + git_str_dispose(&object_dir_buf); + git_str_dispose(&namespace_buf); + git_str_dispose(&index_file_buf); + git_str_dispose(&across_fs_buf); + git_str_dispose(&ceiling_dirs_buf); + git_str_dispose(&dir_buf); + return error; +} + +static int repo_is_worktree(unsigned *out, const git_repository *repo) +{ + git_str gitdir_link = GIT_STR_INIT; + int error; + + /* Worktrees cannot have the same commondir and gitdir */ + if (repo->commondir && repo->gitdir + && !strcmp(repo->commondir, repo->gitdir)) { + *out = 0; + return 0; + } + + if ((error = git_str_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0) + return -1; + + /* A 'gitdir' file inside a git directory is currently + * only used when the repository is a working tree. */ + *out = !!git_fs_path_exists(gitdir_link.ptr); + + git_str_dispose(&gitdir_link); + return error; +} + +int git_repository_open_ext( + git_repository **repo_ptr, + const char *start_path, + unsigned int flags, + const char *ceiling_dirs) +{ + int error; + unsigned is_worktree; + git_str gitdir = GIT_STR_INIT, workdir = GIT_STR_INIT, + gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT; + git_repository *repo = NULL; + git_config *config = NULL; + int version = 0; + + if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) + return _git_repository_open_ext_from_env(repo_ptr, start_path); + + if (repo_ptr) + *repo_ptr = NULL; + + error = find_repo( + &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); + + if (error < 0 || !repo_ptr) + goto cleanup; + + repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + repo->gitdir = git_str_detach(&gitdir); + GIT_ERROR_CHECK_ALLOC(repo->gitdir); + + if (gitlink.size) { + repo->gitlink = git_str_detach(&gitlink); + GIT_ERROR_CHECK_ALLOC(repo->gitlink); + } + if (commondir.size) { + repo->commondir = git_str_detach(&commondir); + GIT_ERROR_CHECK_ALLOC(repo->commondir); + } + + if ((error = repo_is_worktree(&is_worktree, repo)) < 0) + goto cleanup; + repo->is_worktree = is_worktree; + + /* + * We'd like to have the config, but git doesn't particularly + * care if it's not there, so we need to deal with that. + */ + + error = git_repository_config_snapshot(&config, repo); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (config && (error = check_repositoryformatversion(&version, config)) < 0) + goto cleanup; + + if ((error = check_extensions(config, version)) < 0) + goto cleanup; + + if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) { + repo->is_bare = 1; + } else { + if (config && + ((error = load_config_data(repo, config)) < 0 || + (error = load_workdir(repo, config, &workdir)) < 0)) + goto cleanup; + } + + /* + * Ensure that the git directory and worktree are + * owned by the current user. + */ + if (git_repository__validate_ownership && + (error = validate_ownership(repo)) < 0) + goto cleanup; + +cleanup: + git_str_dispose(&gitdir); + git_str_dispose(&workdir); + git_str_dispose(&gitlink); + git_str_dispose(&commondir); + git_config_free(config); + + if (error < 0) + git_repository_free(repo); + else if (repo_ptr) + *repo_ptr = repo; + + return error; +} + +int git_repository_open(git_repository **repo_out, const char *path) +{ + return git_repository_open_ext( + repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); +} + +int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *wt) +{ + git_str path = GIT_STR_INIT; + git_repository *repo = NULL; + size_t len; + int err; + + GIT_ASSERT_ARG(repo_out); + GIT_ASSERT_ARG(wt); + + *repo_out = NULL; + len = strlen(wt->gitlink_path); + + if (len <= 4 || strcasecmp(wt->gitlink_path + len - 4, ".git")) { + err = -1; + goto out; + } + + if ((err = git_str_set(&path, wt->gitlink_path, len - 4)) < 0) + goto out; + + if ((err = git_repository_open(&repo, path.ptr)) < 0) + goto out; + + *repo_out = repo; + +out: + git_str_dispose(&path); + + return err; +} + +int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb) +{ + git_repository *repo; + + repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + git_repository_set_odb(repo, odb); + *repo_out = repo; + + return 0; +} + +int git_repository_discover( + git_buf *out, + const char *start_path, + int across_fs, + const char *ceiling_dirs) +{ + uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + + GIT_ASSERT_ARG(start_path); + + GIT_BUF_WRAP_PRIVATE(out, find_repo, NULL, NULL, NULL, start_path, flags, ceiling_dirs); +} + +static int load_config( + git_config **out, + git_repository *repo, + const char *global_config_path, + const char *xdg_config_path, + const char *system_config_path, + const char *programdata_path) +{ + int error; + git_str config_path = GIT_STR_INIT; + git_config *cfg = NULL; + + GIT_ASSERT_ARG(out); + + if ((error = git_config_new(&cfg)) < 0) + return error; + + if (repo) { + if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0); + + if (error && error != GIT_ENOTFOUND) + goto on_error; + + git_str_dispose(&config_path); + } + + if (global_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (xdg_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (system_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (programdata_path != NULL && + (error = git_config_add_file_ondisk( + cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + git_error_clear(); /* clear any lingering ENOTFOUND errors */ + + *out = cfg; + return 0; + +on_error: + git_str_dispose(&config_path); + git_config_free(cfg); + *out = NULL; + return error; +} + +static const char *path_unless_empty(git_str *buf) +{ + return git_str_len(buf) > 0 ? git_str_cstr(buf) : NULL; +} + +int git_repository_config__weakptr(git_config **out, git_repository *repo) +{ + int error = 0; + + if (repo->_config == NULL) { + git_str global_buf = GIT_STR_INIT; + git_str xdg_buf = GIT_STR_INIT; + git_str system_buf = GIT_STR_INIT; + git_str programdata_buf = GIT_STR_INIT; + git_config *config; + + git_config__find_global(&global_buf); + git_config__find_xdg(&xdg_buf); + git_config__find_system(&system_buf); + git_config__find_programdata(&programdata_buf); + + /* If there is no global file, open a backend for it anyway */ + if (git_str_len(&global_buf) == 0) + git_config__global_location(&global_buf); + + error = load_config( + &config, repo, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + if (!error) { + GIT_REFCOUNT_OWN(config, repo); + + if (git_atomic_compare_and_swap(&repo->_config, NULL, config) != NULL) { + GIT_REFCOUNT_OWN(config, NULL); + git_config_free(config); + } + } + + git_str_dispose(&global_buf); + git_str_dispose(&xdg_buf); + git_str_dispose(&system_buf); + git_str_dispose(&programdata_buf); + } + + *out = repo->_config; + return error; +} + +int git_repository_config(git_config **out, git_repository *repo) +{ + if (git_repository_config__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_config_snapshot(git_config **out, git_repository *repo) +{ + int error; + git_config *weak; + + if ((error = git_repository_config__weakptr(&weak, repo)) < 0) + return error; + + return git_config_snapshot(out, weak); +} + +int git_repository_set_config(git_repository *repo, git_config *config) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(config); + + set_config(repo, config); + return 0; +} + +int git_repository_odb__weakptr(git_odb **out, git_repository *repo) +{ + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(out); + + *out = git_atomic_load(repo->_odb); + if (*out == NULL) { + git_str odb_path = GIT_STR_INIT; + git_odb *odb; + + if ((error = git_repository__item_path(&odb_path, repo, + GIT_REPOSITORY_ITEM_OBJECTS)) < 0 || + (error = git_odb_new(&odb)) < 0) + return error; + + GIT_REFCOUNT_OWN(odb, repo); + + if ((error = git_odb__set_caps(odb, GIT_ODB_CAP_FROM_OWNER)) < 0 || + (error = git_odb__add_default_backends(odb, odb_path.ptr, 0, 0)) < 0) { + git_odb_free(odb); + return error; + } + + if (git_atomic_compare_and_swap(&repo->_odb, NULL, odb) != NULL) { + GIT_REFCOUNT_OWN(odb, NULL); + git_odb_free(odb); + } + + git_str_dispose(&odb_path); + *out = git_atomic_load(repo->_odb); + } + + return error; +} + +int git_repository_odb(git_odb **out, git_repository *repo) +{ + if (git_repository_odb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_set_odb(git_repository *repo, git_odb *odb) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(odb); + + set_odb(repo, odb); + return 0; +} + +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) +{ + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if (repo->_refdb == NULL) { + git_refdb *refdb; + + error = git_refdb_open(&refdb, repo); + if (!error) { + GIT_REFCOUNT_OWN(refdb, repo); + + if (git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb) != NULL) { + GIT_REFCOUNT_OWN(refdb, NULL); + git_refdb_free(refdb); + } + } + } + + *out = repo->_refdb; + return error; +} + +int git_repository_refdb(git_refdb **out, git_repository *repo) +{ + if (git_repository_refdb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refdb); + + set_refdb(repo, refdb); + return 0; +} + +int git_repository_index__weakptr(git_index **out, git_repository *repo) +{ + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if (repo->_index == NULL) { + git_str index_path = GIT_STR_INIT; + git_index *index; + + if ((error = git_str_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) + return error; + + error = git_index_open(&index, index_path.ptr); + if (!error) { + GIT_REFCOUNT_OWN(index, repo); + + if (git_atomic_compare_and_swap(&repo->_index, NULL, index) != NULL) { + GIT_REFCOUNT_OWN(index, NULL); + git_index_free(index); + } + + error = git_index_set_caps(repo->_index, + GIT_INDEX_CAPABILITY_FROM_OWNER); + } + + git_str_dispose(&index_path); + } + + *out = repo->_index; + return error; +} + +int git_repository_index(git_index **out, git_repository *repo) +{ + if (git_repository_index__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_set_index(git_repository *repo, git_index *index) +{ + GIT_ASSERT_ARG(repo); + set_index(repo, index); + return 0; +} + +int git_repository_set_namespace(git_repository *repo, const char *namespace) +{ + git__free(repo->namespace); + + if (namespace == NULL) { + repo->namespace = NULL; + return 0; + } + + return (repo->namespace = git__strdup(namespace)) ? 0 : -1; +} + +const char *git_repository_get_namespace(git_repository *repo) +{ + return repo->namespace; +} + +#ifdef GIT_WIN32 +static int reserved_names_add8dot3(git_repository *repo, const char *path) +{ + char *name = git_win32_path_8dot3_name(path); + const char *def = GIT_DIR_SHORTNAME; + const char *def_dot_git = DOT_GIT; + size_t name_len, def_len = CONST_STRLEN(GIT_DIR_SHORTNAME); + size_t def_dot_git_len = CONST_STRLEN(DOT_GIT); + git_str *buf; + + if (!name) + return 0; + + name_len = strlen(name); + + if ((name_len == def_len && memcmp(name, def, def_len) == 0) || + (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { + git__free(name); + return 0; + } + + if ((buf = git_array_alloc(repo->reserved_names)) == NULL) + return -1; + + git_str_attach(buf, name, name_len); + return true; +} + +bool git_repository__reserved_names( + git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) +{ + GIT_UNUSED(include_ntfs); + + if (repo->reserved_names.size == 0) { + git_str *buf; + size_t i; + + /* Add the static defaults */ + for (i = 0; i < git_repository__reserved_names_win32_len; i++) { + if ((buf = git_array_alloc(repo->reserved_names)) == NULL) + goto on_error; + + buf->ptr = git_repository__reserved_names_win32[i].ptr; + buf->size = git_repository__reserved_names_win32[i].size; + } + + /* Try to add any repo-specific reserved names - the gitlink file + * within a submodule or the repository (if the repository directory + * is beneath the workdir). These are typically `.git`, but should + * be protected in case they are not. Note, repo and workdir paths + * are always prettified to end in `/`, so a prefixcmp is safe. + */ + if (!repo->is_bare) { + int (*prefixcmp)(const char *, const char *); + int error, ignorecase; + + error = git_repository__configmap_lookup( + &ignorecase, repo, GIT_CONFIGMAP_IGNORECASE); + prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : + git__prefixcmp; + + if (repo->gitlink && + reserved_names_add8dot3(repo, repo->gitlink) < 0) + goto on_error; + + if (repo->gitdir && + prefixcmp(repo->gitdir, repo->workdir) == 0 && + reserved_names_add8dot3(repo, repo->gitdir) < 0) + goto on_error; + } + } + + *out = repo->reserved_names.ptr; + *outlen = repo->reserved_names.size; + + return true; + + /* Always give good defaults, even on OOM */ +on_error: + *out = git_repository__reserved_names_win32; + *outlen = git_repository__reserved_names_win32_len; + + return false; +} +#else +bool git_repository__reserved_names( + git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) +{ + GIT_UNUSED(repo); + + if (include_ntfs) { + *out = git_repository__reserved_names_win32; + *outlen = git_repository__reserved_names_win32_len; + } else { + *out = git_repository__reserved_names_posix; + *outlen = git_repository__reserved_names_posix_len; + } + + return true; +} +#endif + +static int check_repositoryformatversion(int *version, git_config *config) +{ + int error; + + error = git_config_get_int32(version, config, "core.repositoryformatversion"); + /* git ignores this if the config variable isn't there */ + if (error == GIT_ENOTFOUND) + return 0; + + if (error < 0) + return -1; + + if (GIT_REPO_MAX_VERSION < *version) { + git_error_set(GIT_ERROR_REPOSITORY, + "unsupported repository version %d; only versions up to %d are supported", + *version, GIT_REPO_MAX_VERSION); + return -1; + } + + return 0; +} + +static const char *builtin_extensions[] = { + "noop" +}; + +static git_vector user_extensions = GIT_VECTOR_INIT; + +static int check_valid_extension(const git_config_entry *entry, void *payload) +{ + git_str cfg = GIT_STR_INIT; + bool reject; + const char *extension; + size_t i; + int error = 0; + + GIT_UNUSED(payload); + + git_vector_foreach (&user_extensions, i, extension) { + git_str_clear(&cfg); + + /* + * Users can specify that they don't want to support an + * extension with a '!' prefix. + */ + if ((reject = (extension[0] == '!')) == true) + extension = &extension[1]; + + if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) + goto done; + + if (strcmp(entry->name, cfg.ptr) == 0) { + if (reject) + goto fail; + + goto done; + } + } + + for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { + git_str_clear(&cfg); + extension = builtin_extensions[i]; + + if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) + goto done; + + if (strcmp(entry->name, cfg.ptr) == 0) + goto done; + } + +fail: + git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name); + error = -1; + +done: + git_str_dispose(&cfg); + return error; +} + +static int check_extensions(git_config *config, int version) +{ + if (version < 1) + return 0; + + return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL); +} + +int git_repository__extensions(char ***out, size_t *out_len) +{ + git_vector extensions; + const char *builtin, *user; + char *extension; + size_t i, j; + + if (git_vector_init(&extensions, 8, NULL) < 0) + return -1; + + for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { + bool match = false; + + builtin = builtin_extensions[i]; + + git_vector_foreach (&user_extensions, j, user) { + if (user[0] == '!' && strcmp(builtin, &user[1]) == 0) { + match = true; + break; + } + } + + if (match) + continue; + + if ((extension = git__strdup(builtin)) == NULL || + git_vector_insert(&extensions, extension) < 0) + return -1; + } + + git_vector_foreach (&user_extensions, i, user) { + if (user[0] == '!') + continue; + + if ((extension = git__strdup(user)) == NULL || + git_vector_insert(&extensions, extension) < 0) + return -1; + } + + *out = (char **)git_vector_detach(out_len, NULL, &extensions); + return 0; +} + +int git_repository__set_extensions(const char **extensions, size_t len) +{ + char *extension; + size_t i; + + git_repository__free_extensions(); + + for (i = 0; i < len; i++) { + if ((extension = git__strdup(extensions[i])) == NULL || + git_vector_insert(&user_extensions, extension) < 0) + return -1; + } + + return 0; +} + +void git_repository__free_extensions(void) +{ + git_vector_free_deep(&user_extensions); +} + +int git_repository_create_head(const char *git_dir, const char *ref_name) +{ + git_str ref_path = GIT_STR_INIT; + git_filebuf ref = GIT_FILEBUF_INIT; + const char *fmt; + int error; + + if ((error = git_str_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 (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0) + fmt = "ref: %s\n"; + else + fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; + + if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 || + (error = git_filebuf_commit(&ref)) < 0) + goto out; + +out: + git_str_dispose(&ref_path); + git_filebuf_cleanup(&ref); + return error; +} + +static bool is_chmod_supported(const char *file_path) +{ + struct stat st1, st2; + + if (p_stat(file_path, &st1) < 0) + return false; + + if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) + return false; + + if (p_stat(file_path, &st2) < 0) + return false; + + return (st1.st_mode != st2.st_mode); +} + +static bool is_filesystem_case_insensitive(const char *gitdir_path) +{ + git_str path = GIT_STR_INIT; + int is_insensitive = -1; + + if (!git_str_joinpath(&path, gitdir_path, "CoNfIg")) + is_insensitive = git_fs_path_exists(git_str_cstr(&path)); + + git_str_dispose(&path); + return is_insensitive; +} + +/* + * Return a configuration object with only the global and system + * configurations; no repository-level configuration. + */ +static int load_global_config(git_config **config) +{ + git_str global_buf = GIT_STR_INIT; + git_str xdg_buf = GIT_STR_INIT; + git_str system_buf = GIT_STR_INIT; + git_str programdata_buf = GIT_STR_INIT; + int error; + + git_config__find_global(&global_buf); + git_config__find_xdg(&xdg_buf); + git_config__find_system(&system_buf); + git_config__find_programdata(&programdata_buf); + + error = load_config(config, NULL, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + + git_str_dispose(&global_buf); + git_str_dispose(&xdg_buf); + git_str_dispose(&system_buf); + git_str_dispose(&programdata_buf); + + return error; +} + +static bool are_symlinks_supported(const char *wd_path) +{ + git_config *config = NULL; + int symlinks = 0; + + /* + * To emulate Git for Windows, symlinks on Windows must be explicitly + * opted-in. We examine the system configuration for a core.symlinks + * set to true. If found, we then examine the filesystem to see if + * symlinks are _actually_ supported by the current user. If that is + * _not_ set, then we do not test or enable symlink support. + */ +#ifdef GIT_WIN32 + if (load_global_config(&config) < 0 || + git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || + !symlinks) + goto done; +#endif + + if (!(symlinks = git_fs_path_supports_symlinks(wd_path))) + goto done; + +done: + git_config_free(config); + return symlinks != 0; +} + +static int create_empty_file(const char *path, mode_t mode) +{ + int fd; + + if ((fd = p_creat(path, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "error while creating '%s'", path); + return -1; + } + + if (p_close(fd) < 0) { + git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); + return -1; + } + + return 0; +} + +static int repo_local_config( + git_config **out, + git_str *config_dir, + git_repository *repo, + const char *repo_dir) +{ + int error = 0; + git_config *parent; + const char *cfg_path; + + if (git_str_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; + cfg_path = git_str_cstr(config_dir); + + /* make LOCAL config if missing */ + if (!git_fs_path_isfile(cfg_path) && + (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0) + return error; + + /* if no repo, just open that file directly */ + if (!repo) + return git_config_open_ondisk(out, cfg_path); + + /* otherwise, open parent config and get that level */ + if ((error = git_repository_config__weakptr(&parent, repo)) < 0) + return error; + + if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) { + git_error_clear(); + + if (!(error = git_config_add_file_ondisk( + parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, repo, false))) + error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); + } + + git_config_free(parent); + + return error; +} + +static int repo_init_fs_configs( + git_config *cfg, + const char *cfg_path, + const char *repo_dir, + const char *work_dir, + bool update_ignorecase) +{ + int error = 0; + + if (!work_dir) + work_dir = repo_dir; + + if ((error = git_config_set_bool( + cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) + return error; + + if (!are_symlinks_supported(work_dir)) { + if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) + git_error_clear(); + + if (update_ignorecase) { + if (is_filesystem_case_insensitive(repo_dir)) { + if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0) + git_error_clear(); + } + +#ifdef GIT_USE_ICONV + if ((error = git_config_set_bool( + cfg, "core.precomposeunicode", + git_fs_path_does_decompose_unicode(work_dir))) < 0) + return error; + /* on non-iconv platforms, don't even set core.precomposeunicode */ +#endif + + return 0; +} + +static int repo_init_config( + const char *repo_dir, + const char *work_dir, + uint32_t flags, + uint32_t mode) +{ + int error = 0; + git_str cfg_path = GIT_STR_INIT, worktree_path = GIT_STR_INIT; + git_config *config = NULL; + bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); + bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); + int version = 0; + + if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) + goto cleanup; + + if (is_reinit && (error = check_repositoryformatversion(&version, config)) < 0) + goto cleanup; + + if ((error = check_extensions(config, version)) < 0) + goto cleanup; + +#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \ + if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ + goto cleanup; } while (0) + + SET_REPO_CONFIG(bool, "core.bare", is_bare); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); + + if ((error = repo_init_fs_configs( + config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) + goto cleanup; + + if (!is_bare) { + SET_REPO_CONFIG(bool, "core.logallrefupdates", true); + + if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { + if ((error = git_str_sets(&worktree_path, work_dir)) < 0) + goto cleanup; + + if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK)) + if ((error = git_fs_path_make_relative(&worktree_path, repo_dir)) < 0) + goto cleanup; + + SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr); + } else if (is_reinit) { + if (git_config_delete_entry(config, "core.worktree") < 0) + git_error_clear(); + } + } + + if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { + SET_REPO_CONFIG(int32, "core.sharedrepository", 1); + SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); + } + else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) { + SET_REPO_CONFIG(int32, "core.sharedrepository", 2); + SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); + } + +cleanup: + git_str_dispose(&cfg_path); + git_str_dispose(&worktree_path); + git_config_free(config); + + return error; +} + +static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p) +{ + git_repository *smrepo = NULL; + GIT_UNUSED(n); GIT_UNUSED(p); + + if (git_submodule_open(&smrepo, sm) < 0 || + git_repository_reinit_filesystem(smrepo, true) < 0) + git_error_clear(); + git_repository_free(smrepo); + + return 0; +} + +int git_repository_reinit_filesystem(git_repository *repo, int recurse) +{ + int error = 0; + git_str path = GIT_STR_INIT; + git_config *config = NULL; + const char *repo_dir = git_repository_path(repo); + + if (!(error = repo_local_config(&config, &path, repo, repo_dir))) + error = repo_init_fs_configs( + config, path.ptr, repo_dir, git_repository_workdir(repo), true); + + git_config_free(config); + git_str_dispose(&path); + + git_repository__configmap_lookup_cache_clear(repo); + + if (!repo->is_bare && recurse) + (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); + + return error; +} + +static int repo_write_template( + const char *git_dir, + bool allow_overwrite, + const char *file, + mode_t mode, + bool hidden, + const char *content) +{ + git_str path = GIT_STR_INIT; + int fd, error = 0, flags; + + if (git_str_joinpath(&path, git_dir, file) < 0) + return -1; + + if (allow_overwrite) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else + flags = O_WRONLY | O_CREAT | O_EXCL; + + fd = p_open(git_str_cstr(&path), flags, mode); + + if (fd >= 0) { + error = p_write(fd, content, strlen(content)); + + p_close(fd); + } + else if (errno != EEXIST) + error = fd; + +#ifdef GIT_WIN32 + if (!error && hidden) { + if (git_win32__set_hidden(path.ptr, true) < 0) + error = -1; + } +#else + GIT_UNUSED(hidden); +#endif + + git_str_dispose(&path); + + if (error) + git_error_set(GIT_ERROR_OS, + "failed to initialize repository with template '%s'", file); + + return error; +} + +static int repo_write_gitlink( + const char *in_dir, const char *to_repo, bool use_relative_path) +{ + int error; + git_str buf = GIT_STR_INIT; + git_str path_to_repo = GIT_STR_INIT; + struct stat st; + + git_fs_path_dirname_r(&buf, to_repo); + git_fs_path_to_dir(&buf); + if (git_str_oom(&buf)) + return -1; + + /* don't write gitlink to natural workdir */ + if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 && + strcmp(in_dir, buf.ptr) == 0) + { + error = GIT_PASSTHROUGH; + goto cleanup; + } + + if ((error = git_str_joinpath(&buf, in_dir, DOT_GIT)) < 0) + goto cleanup; + + if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { + git_error_set(GIT_ERROR_REPOSITORY, + "cannot overwrite gitlink file into path '%s'", in_dir); + error = GIT_EEXISTS; + goto cleanup; + } + + git_str_clear(&buf); + + error = git_str_sets(&path_to_repo, to_repo); + + if (!error && use_relative_path) + error = git_fs_path_make_relative(&path_to_repo, in_dir); + + if (!error) + error = git_str_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); + + if (!error) + error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); + +cleanup: + git_str_dispose(&buf); + git_str_dispose(&path_to_repo); + return error; +} + +static mode_t pick_dir_mode(git_repository_init_options *opts) +{ + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK) + return 0777; + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) + return (0775 | S_ISGID); + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) + return (0777 | S_ISGID); + return opts->mode; +} + +#include "repo_template.h" + +static int repo_init_structure( + const char *repo_dir, + const char *work_dir, + git_repository_init_options *opts) +{ + int error = 0; + repo_template_item *tpl; + bool external_tpl = + ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); + mode_t dmode = pick_dir_mode(opts); + bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; + + /* Hide the ".git" directory */ +#ifdef GIT_WIN32 + if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { + if (git_win32__set_hidden(repo_dir, true) < 0) { + git_error_set(GIT_ERROR_OS, + "failed to mark Git repository folder as hidden"); + return -1; + } + } +#endif + + /* Create the .git gitlink if appropriate */ + if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && + (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0) + { + if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0) + return -1; + } + + /* Copy external template if requested */ + if (external_tpl) { + git_config *cfg = NULL; + const char *tdir = NULL; + bool default_template = false; + git_str template_buf = GIT_STR_INIT; + + if (opts->template_path) + tdir = opts->template_path; + else if ((error = git_config_open_default(&cfg)) >= 0) { + if (!git_config__get_path(&template_buf, cfg, "init.templatedir")) + tdir = template_buf.ptr; + git_error_clear(); + } + + if (!tdir) { + if (!(error = git_sysdir_find_template_dir(&template_buf))) + tdir = template_buf.ptr; + default_template = true; + } + + /* + * If tdir was the empty string, treat it like tdir was a path to an + * empty directory (so, don't do any copying). This is the behavior + * that git(1) exhibits, although it doesn't seem to be officially + * documented. + */ + if (tdir && git__strcmp(tdir, "") != 0) { + uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | + GIT_CPDIR_SIMPLE_TO_MODE | + GIT_CPDIR_COPY_DOTFILES; + if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) + cpflags |= GIT_CPDIR_CHMOD_DIRS; + error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); + } + + git_str_dispose(&template_buf); + git_config_free(cfg); + + /* If tdir does not exist, then do not error out. This matches the + * behaviour of git(1), which just prints a warning and continues. + * TODO: issue warning when warning API is available. + * `git` prints to stderr: 'warning: templates not found in /path/to/tdir' + */ + if (error < 0) { + if (!default_template && error != GIT_ENOTFOUND) + return error; + + /* if template was default, ignore error and use internal */ + git_error_clear(); + external_tpl = false; + error = 0; + } + } + + /* Copy internal template + * - always ensure existence of dirs + * - only create files if no external template was specified + */ + for (tpl = repo_template; !error && tpl->path; ++tpl) { + if (!tpl->content) { + uint32_t mkdir_flags = GIT_MKDIR_PATH; + if (chmod) + mkdir_flags |= GIT_MKDIR_CHMOD; + + error = git_futils_mkdir_relative( + tpl->path, repo_dir, dmode, mkdir_flags, NULL); + } + else if (!external_tpl) { + const char *content = tpl->content; + + if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0) + content = opts->description; + + error = repo_write_template( + repo_dir, false, tpl->path, tpl->mode, false, content); + } + } + + return error; +} + +static int mkdir_parent(git_str *buf, uint32_t mode, bool skip2) +{ + /* When making parent directories during repository initialization + * don't try to set gid or grant world write access + */ + return git_futils_mkdir( + buf->ptr, mode & ~(S_ISGID | 0002), + GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | + (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); +} + +static int repo_init_directories( + git_str *repo_path, + git_str *wd_path, + const char *given_repo, + git_repository_init_options *opts) +{ + int error = 0; + bool is_bare, add_dotgit, has_dotgit, natural_wd; + mode_t dirmode; + + /* There are three possible rules for what we are allowed to create: + * - MKPATH means anything we need + * - MKDIR means just the .git directory and its parent and the workdir + * - Neither means only the .git directory can be created + * + * There are 5 "segments" of path that we might need to deal with: + * 1. The .git directory + * 2. The parent of the .git directory + * 3. Everything above the parent of the .git directory + * 4. The working directory (often the same as #2) + * 5. Everything above the working directory (often the same as #3) + * + * For all directories created, we start with the init_mode value for + * permissions and then strip off bits in some cases: + * + * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH + * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID + * For all rules, we create #1 using the untouched init_mode + */ + + /* set up repo path */ + + is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); + + add_dotgit = + (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 && + !is_bare && + git__suffixcmp(given_repo, "/" DOT_GIT) != 0 && + git__suffixcmp(given_repo, "/" GIT_DIR) != 0; + + if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) + return -1; + + has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); + if (has_dotgit) + opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; + + /* set up workdir path */ + + if (!is_bare) { + if (opts->workdir_path) { + if (git_fs_path_join_unrooted( + wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0) + return -1; + } else if (has_dotgit) { + if (git_fs_path_dirname_r(wd_path, repo_path->ptr) < 0) + return -1; + } else { + git_error_set(GIT_ERROR_REPOSITORY, "cannot pick working directory" + " for non-bare repository that isn't a '.git' directory"); + return -1; + } + + if (git_fs_path_to_dir(wd_path) < 0) + return -1; + } else { + git_str_clear(wd_path); + } + + natural_wd = + has_dotgit && + wd_path->size > 0 && + wd_path->size + strlen(GIT_DIR) == repo_path->size && + memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0; + if (natural_wd) + opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD; + + /* create directories as needed / requested */ + + dirmode = pick_dir_mode(opts); + + if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) { + /* create path #5 */ + if (wd_path->size > 0 && + (error = mkdir_parent(wd_path, dirmode, false)) < 0) + return error; + + /* create path #3 (if not the same as #5) */ + if (!natural_wd && + (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0) + return error; + } + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) + { + /* create path #4 */ + if (wd_path->size > 0 && + (error = git_futils_mkdir( + wd_path->ptr, dirmode & ~S_ISGID, + GIT_MKDIR_VERIFY_DIR)) < 0) + return error; + + /* create path #2 (if not the same as #4) */ + if (!natural_wd && + (error = git_futils_mkdir( + repo_path->ptr, dirmode & ~S_ISGID, + GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) + return error; + } + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 || + has_dotgit) + { + /* create path #1 */ + error = git_futils_mkdir(repo_path->ptr, dirmode, + GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); + } + + /* prettify both directories now that they are created */ + + if (!error) { + error = git_fs_path_prettify_dir(repo_path, repo_path->ptr, NULL); + + if (!error && wd_path->size > 0) + error = git_fs_path_prettify_dir(wd_path, wd_path->ptr, NULL); + } + + return error; +} + +static int repo_init_head(const char *repo_dir, const char *given) +{ + git_config *cfg = NULL; + git_str head_path = GIT_STR_INIT, cfg_branch = GIT_STR_INIT; + const char *initial_head = NULL; + int error; + + if ((error = git_str_joinpath(&head_path, repo_dir, GIT_HEAD_FILE)) < 0) + goto out; + + /* + * A template may have set a HEAD; use that unless it's been + * overridden by the caller's given initial head setting. + */ + if (git_fs_path_exists(head_path.ptr) && !given) + goto out; + + if (given) { + initial_head = given; + } else if ((error = git_config_open_default(&cfg)) >= 0 && + (error = git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch")) >= 0 && + *cfg_branch.ptr) { + initial_head = cfg_branch.ptr; + } + + if (!initial_head) + initial_head = GIT_BRANCH_DEFAULT; + + error = git_repository_create_head(repo_dir, initial_head); + +out: + git_config_free(cfg); + git_str_dispose(&head_path); + git_str_dispose(&cfg_branch); + + return error; +} + +static int repo_init_create_origin(git_repository *repo, const char *url) +{ + int error; + git_remote *remote; + + if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) { + git_remote_free(remote); + } + + return error; +} + +int git_repository_init( + git_repository **repo_out, const char *path, unsigned is_bare) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */ + if (is_bare) + opts.flags |= GIT_REPOSITORY_INIT_BARE; + + return git_repository_init_ext(repo_out, path, &opts); +} + +int git_repository_init_ext( + git_repository **out, + const char *given_repo, + git_repository_init_options *opts) +{ + git_str repo_path = GIT_STR_INIT, wd_path = GIT_STR_INIT, + common_path = GIT_STR_INIT; + const char *wd; + bool is_valid; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(given_repo); + GIT_ASSERT_ARG(opts); + + GIT_ERROR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options"); + + if ((error = repo_init_directories(&repo_path, &wd_path, given_repo, opts)) < 0) + goto out; + + wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_str_cstr(&wd_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 out; + } + + opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; + + 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 = repo_init_head(repo_path.ptr, opts->initial_head)) < 0) + goto out; + } + + if ((error = git_repository_open(out, repo_path.ptr)) < 0) + goto out; + + if (opts->origin_url && + (error = repo_init_create_origin(*out, opts->origin_url)) < 0) + goto out; + +out: + git_str_dispose(&common_path); + git_str_dispose(&repo_path); + git_str_dispose(&wd_path); + + return error; +} + +int git_repository_head_detached(git_repository *repo) +{ + git_reference *ref; + git_odb *odb = NULL; + int exists; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + return -1; + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + git_reference_free(ref); + return 0; + } + + exists = git_odb_exists(odb, git_reference_target(ref)); + + git_reference_free(ref); + return exists; +} + +int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) +{ + git_reference *ref = NULL; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0) + goto out; + + error = (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC); +out: + git_reference_free(ref); + + return error; +} + +int git_repository_head(git_reference **head_out, git_repository *repo) +{ + git_reference *head; + int error; + + GIT_ASSERT_ARG(head_out); + + if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) + return error; + + if (git_reference_type(head) == GIT_REFERENCE_DIRECT) { + *head_out = head; + return 0; + } + + error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1); + git_reference_free(head); + + return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; +} + +int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) +{ + git_repository *worktree_repo = NULL; + git_worktree *worktree = NULL; + git_reference *head = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + *out = NULL; + + if ((error = git_worktree_lookup(&worktree, repo, name)) < 0 || + (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0 || + (error = git_reference_lookup(&head, worktree_repo, GIT_HEAD_FILE)) < 0) + goto out; + + if (git_reference_type(head) != GIT_REFERENCE_DIRECT) { + if ((error = git_reference_lookup_resolved(out, worktree_repo, git_reference_symbolic_target(head), -1)) < 0) + goto out; + } else { + *out = head; + head = NULL; + } + +out: + git_reference_free(head); + git_worktree_free(worktree); + git_repository_free(worktree_repo); + return error; +} + +int git_repository_foreach_worktree(git_repository *repo, + git_repository_foreach_worktree_cb cb, + void *payload) +{ + git_strarray worktrees = {0}; + git_repository *worktree_repo = NULL; + git_worktree *worktree = NULL; + int error; + size_t i; + + /* apply operation to repository supplied when commondir is empty, implying there's + * no linked worktrees to iterate, which can occur when using custom odb/refdb + */ + if (!repo->commondir) + return cb(repo, payload); + + if ((error = git_repository_open(&worktree_repo, repo->commondir)) < 0 || + (error = cb(worktree_repo, payload) != 0)) + goto out; + + git_repository_free(worktree_repo); + worktree_repo = NULL; + + if ((error = git_worktree_list(&worktrees, repo)) < 0) + goto out; + + for (i = 0; i < worktrees.count; i++) { + git_repository_free(worktree_repo); + worktree_repo = NULL; + git_worktree_free(worktree); + worktree = NULL; + + if ((error = git_worktree_lookup(&worktree, repo, worktrees.strings[i]) < 0) || + (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + continue; + } + + if ((error = cb(worktree_repo, payload)) != 0) + goto out; + } + +out: + git_strarray_dispose(&worktrees); + git_repository_free(worktree_repo); + git_worktree_free(worktree); + return error; +} + +int git_repository_head_unborn(git_repository *repo) +{ + git_reference *ref = NULL; + int error; + + error = git_repository_head(&ref, repo); + git_reference_free(ref); + + if (error == GIT_EUNBORNBRANCH) { + git_error_clear(); + return 1; + } + + if (error < 0) + return -1; + + return 0; +} + +static int repo_contains_no_reference(git_repository *repo) +{ + git_reference_iterator *iter; + const char *refname; + int error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + return error; + + error = git_reference_next_name(&refname, iter); + git_reference_iterator_free(iter); + + if (error == GIT_ITEROVER) + return 1; + + return error; +} + +int git_repository_initialbranch(git_str *out, git_repository *repo) +{ + git_config *config; + git_config_entry *entry = NULL; + const char *branch; + int valid, error; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_get_entry(&entry, config, "init.defaultbranch")) == 0 && + *entry->value) { + branch = entry->value; + } + else if (!error || error == GIT_ENOTFOUND) { + branch = GIT_BRANCH_DEFAULT; + } + else { + goto done; + } + + if ((error = git_str_puts(out, GIT_REFS_HEADS_DIR)) < 0 || + (error = git_str_puts(out, branch)) < 0 || + (error = git_reference_name_is_valid(&valid, out->ptr)) < 0) + goto done; + + if (!valid) { + git_error_set(GIT_ERROR_INVALID, "the value of init.defaultBranch is not a valid branch name"); + error = -1; + } + +done: + git_config_entry_free(entry); + return error; +} + +int git_repository_is_empty(git_repository *repo) +{ + git_reference *head = NULL; + git_str initialbranch = GIT_STR_INIT; + int result = 0; + + if ((result = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0 || + (result = git_repository_initialbranch(&initialbranch, repo)) < 0) + goto done; + + result = (git_reference_type(head) == GIT_REFERENCE_SYMBOLIC && + strcmp(git_reference_symbolic_target(head), initialbranch.ptr) == 0 && + repo_contains_no_reference(repo)); + +done: + git_reference_free(head); + git_str_dispose(&initialbranch); + + return result; +} + +static const char *resolved_parent_path(const git_repository *repo, git_repository_item_t item, git_repository_item_t fallback) +{ + const char *parent; + + switch (item) { + case GIT_REPOSITORY_ITEM_GITDIR: + parent = git_repository_path(repo); + break; + case GIT_REPOSITORY_ITEM_WORKDIR: + parent = git_repository_workdir(repo); + break; + case GIT_REPOSITORY_ITEM_COMMONDIR: + parent = git_repository_commondir(repo); + break; + default: + git_error_set(GIT_ERROR_INVALID, "invalid item directory"); + 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) +{ + GIT_BUF_WRAP_PRIVATE(out, git_repository__item_path, repo, item); +} + +int git_repository__item_path( + git_str *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; + } + + if (git_str_sets(out, parent) < 0) + return -1; + + if (items[item].name) { + if (git_str_joinpath(out, parent, items[item].name) < 0) + return -1; + } + + if (items[item].directory) { + if (git_fs_path_to_dir(out) < 0) + return -1; + } + + return 0; +} + +const char *git_repository_path(const git_repository *repo) +{ + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + return repo->gitdir; +} + +const char *git_repository_workdir(const git_repository *repo) +{ + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + + if (repo->is_bare) + return NULL; + + return repo->workdir; +} + +int git_repository_workdir_path( + git_str *out, git_repository *repo, const char *path) +{ + int error; + + if (!repo->workdir) { + git_error_set(GIT_ERROR_REPOSITORY, "repository has no working directory"); + return GIT_EBAREREPO; + } + + if (!(error = git_str_joinpath(out, repo->workdir, path))) + error = git_path_validate_str_length(repo, out); + + return error; +} + +const char *git_repository_commondir(const git_repository *repo) +{ + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + return repo->commondir; +} + +int git_repository_set_workdir( + git_repository *repo, const char *workdir, int update_gitlink) +{ + int error = 0; + git_str path = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(workdir); + + if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) + return -1; + + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + return 0; + + if (update_gitlink) { + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); + + /* passthrough error means gitlink is unnecessary */ + if (error == GIT_PASSTHROUGH) + error = git_config_delete_entry(config, "core.worktree"); + else if (!error) + error = git_config_set_string(config, "core.worktree", path.ptr); + + if (!error) + error = git_config_set_bool(config, "core.bare", false); + } + + if (!error) { + char *old_workdir = repo->workdir; + + repo->workdir = git_str_detach(&path); + repo->is_bare = 0; + + git__free(old_workdir); + } + + return error; +} + +int git_repository_is_bare(const git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + return repo->is_bare; +} + +int git_repository_is_worktree(const git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + return repo->is_worktree; +} + +int git_repository_set_bare(git_repository *repo) +{ + int error; + git_config *config; + + GIT_ASSERT_ARG(repo); + + if (repo->is_bare) + return 0; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_set_bool(config, "core.bare", true)) < 0) + return error; + + if ((error = git_config__update_entry(config, "core.worktree", NULL, true, true)) < 0) + return error; + + git__free(repo->workdir); + repo->workdir = NULL; + repo->is_bare = 1; + + return 0; +} + +int git_repository_head_tree(git_tree **tree, git_repository *repo) +{ + git_reference *head; + git_object *obj; + int error; + + if ((error = git_repository_head(&head, repo)) < 0) + return error; + + if ((error = git_reference_peel(&obj, head, GIT_OBJECT_TREE)) < 0) + goto cleanup; + + *tree = (git_tree *)obj; + +cleanup: + git_reference_free(head); + return error; +} + +int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + char orig_head_str[GIT_OID_HEXSZ]; + int error = 0; + + git_oid_fmt(orig_head_str, orig_head); + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 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); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int git_repository__message(git_str *out, git_repository *repo) +{ + git_str path = GIT_STR_INIT; + struct stat st; + int error; + + if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) + return -1; + + if ((error = p_stat(git_str_cstr(&path), &st)) < 0) { + if (errno == ENOENT) + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_OS, "could not access message file"); + } else { + error = git_futils_readbuffer(out, git_str_cstr(&path)); + } + + git_str_dispose(&path); + + return error; +} + +int git_repository_message(git_buf *out, git_repository *repo) +{ + GIT_BUF_WRAP_PRIVATE(out, git_repository__message, repo); +} + +int git_repository_message_remove(git_repository *repo) +{ + git_str path = GIT_STR_INIT; + int error; + + if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) + return -1; + + error = p_unlink(git_str_cstr(&path)); + git_str_dispose(&path); + + return error; +} + +int git_repository_hashfile( + git_oid *out, + git_repository *repo, + const char *path, + git_object_t type, + const char *as_path) +{ + int error; + git_filter_list *fl = NULL; + git_file fd = -1; + uint64_t len; + git_str full_path = GIT_STR_INIT; + const char *workdir = git_repository_workdir(repo); + + /* as_path can be NULL */ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(repo); + + if ((error = git_fs_path_join_unrooted(&full_path, path, workdir, NULL)) < 0 || + (error = git_path_validate_str_length(repo, &full_path)) < 0) + return error; + + /* + * NULL as_path means that we should derive it from the + * given path. + */ + if (!as_path) { + if (workdir && !git__prefixcmp(full_path.ptr, workdir)) + as_path = full_path.ptr + strlen(workdir); + else + as_path = ""; + } + + /* passing empty string for "as_path" indicated --no-filters */ + if (strlen(as_path) > 0) { + error = git_filter_list_load( + &fl, repo, NULL, as_path, + GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); + + if (error < 0) + return error; + } + + /* at this point, error is a count of the number of loaded filters */ + + fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) { + error = fd; + goto cleanup; + } + + 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"); + error = -1; + goto cleanup; + } + + error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl); + +cleanup: + if (fd >= 0) + p_close(fd); + git_filter_list_free(fl); + git_str_dispose(&full_path); + + return error; +} + +static int checkout_message(git_str *out, git_reference *old, const char *new) +{ + git_str_puts(out, "checkout: moving from "); + + if (git_reference_type(old) == GIT_REFERENCE_SYMBOLIC) + git_str_puts(out, git_reference__shorthand(git_reference_symbolic_target(old))); + else + git_str_puts(out, git_oid_tostr_s(git_reference_target(old))); + + git_str_puts(out, " to "); + + if (git_reference__is_branch(new) || + git_reference__is_tag(new) || + git_reference__is_remote(new)) + git_str_puts(out, git_reference__shorthand(new)); + else + git_str_puts(out, new); + + if (git_str_oom(out)) + return -1; + + return 0; +} + +static int detach(git_repository *repo, const git_oid *id, const char *new) +{ + int error; + git_str log_message = GIT_STR_INIT; + git_object *object = NULL, *peeled = NULL; + git_reference *new_head = NULL, *current = NULL; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(id); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_object_lookup(&object, repo, id, GIT_OBJECT_ANY)) < 0) + goto cleanup; + + if ((error = git_object_peel(&peeled, object, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + if (new == NULL) + new = git_oid_tostr_s(git_object_id(peeled)); + + if ((error = checkout_message(&log_message, current, new)) < 0) + goto cleanup; + + error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_str_cstr(&log_message)); + +cleanup: + git_str_dispose(&log_message); + git_object_free(object); + git_object_free(peeled); + git_reference_free(current); + git_reference_free(new_head); + return error; +} + +int git_repository_set_head( + git_repository *repo, + const char *refname) +{ + git_reference *ref = NULL, *current = NULL, *new_head = NULL; + git_str log_message = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = checkout_message(&log_message, current, refname)) < 0) + goto cleanup; + + error = git_reference_lookup(&ref, repo, refname); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (ref && current->type == GIT_REFERENCE_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) && + git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) { + git_error_set(GIT_ERROR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD " + "of a linked repository.", git_reference_name(ref)); + error = -1; + goto cleanup; + } + + if (!error) { + if (git_reference_is_branch(ref)) { + error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, + git_reference_name(ref), true, git_str_cstr(&log_message)); + } else { + error = detach(repo, git_reference_target(ref), + git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL); + } + } else if (git_reference__is_branch(refname)) { + error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, + true, git_str_cstr(&log_message)); + } + +cleanup: + git_str_dispose(&log_message); + git_reference_free(current); + git_reference_free(ref); + git_reference_free(new_head); + return error; +} + +int git_repository_set_head_detached( + git_repository *repo, + const git_oid *committish) +{ + return detach(repo, committish, NULL); +} + +int git_repository_set_head_detached_from_annotated( + git_repository *repo, + const git_annotated_commit *committish) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(committish); + + return detach(repo, git_annotated_commit_id(committish), committish->description); +} + +int git_repository_detach_head(git_repository *repo) +{ + git_reference *old_head = NULL, *new_head = NULL, *current = NULL; + git_object *object = NULL; + git_str log_message = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(repo); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_repository_head(&old_head, repo)) < 0) + goto cleanup; + + if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + if ((error = checkout_message(&log_message, current, git_oid_tostr_s(git_object_id(object)))) < 0) + goto cleanup; + + error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head), + 1, git_str_cstr(&log_message)); + +cleanup: + git_str_dispose(&log_message); + git_object_free(object); + git_reference_free(old_head); + git_reference_free(new_head); + git_reference_free(current); + return error; +} + +/** + * Loosely ported from git.git + * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289 + */ +int git_repository_state(git_repository *repo) +{ + git_str repo_path = GIT_STR_INIT; + int state = GIT_REPOSITORY_STATE_NONE; + + GIT_ASSERT_ARG(repo); + + if (git_str_puts(&repo_path, repo->gitdir) < 0) + return -1; + + if (git_fs_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE)) + state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE; + else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR)) + state = GIT_REPOSITORY_STATE_REBASE_MERGE; + else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE)) + state = GIT_REPOSITORY_STATE_REBASE; + else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE)) + state = GIT_REPOSITORY_STATE_APPLY_MAILBOX; + else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR)) + state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE; + else if (git_fs_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE)) + state = GIT_REPOSITORY_STATE_MERGE; + else if (git_fs_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) { + state = GIT_REPOSITORY_STATE_REVERT; + if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { + state = GIT_REPOSITORY_STATE_REVERT_SEQUENCE; + } + } else if (git_fs_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) { + state = GIT_REPOSITORY_STATE_CHERRYPICK; + if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { + state = GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE; + } + } else if (git_fs_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) + state = GIT_REPOSITORY_STATE_BISECT; + + git_str_dispose(&repo_path); + return state; +} + +int git_repository__cleanup_files( + git_repository *repo, const char *files[], size_t files_len) +{ + git_str buf = GIT_STR_INIT; + size_t i; + int error; + + for (error = 0, i = 0; !error && i < files_len; ++i) { + const char *path; + + if (git_str_joinpath(&buf, repo->gitdir, files[i]) < 0) + return -1; + + path = git_str_cstr(&buf); + + if (git_fs_path_isfile(path)) { + error = p_unlink(path); + } else if (git_fs_path_isdir(path)) { + error = git_futils_rmdir_r(path, NULL, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); + } + + git_str_clear(&buf); + } + + git_str_dispose(&buf); + return error; +} + +static const char *state_files[] = { + GIT_MERGE_HEAD_FILE, + GIT_MERGE_MODE_FILE, + GIT_MERGE_MSG_FILE, + GIT_REVERT_HEAD_FILE, + GIT_CHERRYPICK_HEAD_FILE, + GIT_BISECT_LOG_FILE, + GIT_REBASE_MERGE_DIR, + GIT_REBASE_APPLY_DIR, + GIT_SEQUENCER_DIR, +}; + +int git_repository_state_cleanup(git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +int git_repository_is_shallow(git_repository *repo) +{ + git_str path = GIT_STR_INIT; + struct stat st; + int error; + + if ((error = git_str_joinpath(&path, repo->gitdir, "shallow")) < 0) + return error; + + error = git_fs_path_lstat(path.ptr, &st); + git_str_dispose(&path); + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + + if (error < 0) + return error; + return st.st_size == 0 ? 0 : 1; +} + +int git_repository_init_options_init( + git_repository_init_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_repository_init_options, + GIT_REPOSITORY_INIT_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_repository_init_init_options( + git_repository_init_options *opts, unsigned int version) +{ + return git_repository_init_options_init(opts, version); +} +#endif + +int git_repository_ident(const char **name, const char **email, const git_repository *repo) +{ + *name = repo->ident_name; + *email = repo->ident_email; + + return 0; +} + +int git_repository_set_ident(git_repository *repo, const char *name, const char *email) +{ + char *tmp_name = NULL, *tmp_email = NULL; + + if (name) { + tmp_name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(tmp_name); + } + + if (email) { + tmp_email = git__strdup(email); + GIT_ERROR_CHECK_ALLOC(tmp_email); + } + + tmp_name = git_atomic_swap(repo->ident_name, tmp_name); + tmp_email = git_atomic_swap(repo->ident_email, tmp_email); + + git__free(tmp_name); + git__free(tmp_email); + + return 0; +} + +int git_repository_submodule_cache_all(git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + return git_submodule_cache_init(&repo->submodule_cache, repo); +} + +int git_repository_submodule_cache_clear(git_repository *repo) +{ + int error = 0; + GIT_ASSERT_ARG(repo); + + error = git_submodule_cache_free(repo->submodule_cache); + repo->submodule_cache = NULL; + return error; +} diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h new file mode 100644 index 000000000..a488f2bf2 --- /dev/null +++ b/src/libgit2/repository.h @@ -0,0 +1,259 @@ +/* + * 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_repository_h__ +#define INCLUDE_repository_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/oid.h" +#include "git2/odb.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/config.h" + +#include "array.h" +#include "cache.h" +#include "refs.h" +#include "str.h" +#include "object.h" +#include "attrcache.h" +#include "submodule.h" +#include "diff_driver.h" + +#define DOT_GIT ".git" +#define GIT_DIR DOT_GIT "/" +#define GIT_DIR_MODE 0755 +#define GIT_BARE_DIR_MODE 0777 + +/* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */ +#define GIT_DIR_SHORTNAME "GIT~1" + +extern bool git_repository__fsync_gitdir; +extern bool git_repository__validate_ownership; + +/** Cvar cache identifiers */ +typedef enum { + 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_LONGPATHS, /* core.longpaths */ + GIT_CONFIGMAP_CACHE_MAX +} git_configmap_item; + +/** + * Configuration map value enumerations + * + * 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_CONFIGMAP_NOT_CACHED = -1, + + /* core.safecrlf: false, 'fail', 'warn' */ + GIT_SAFE_CRLF_FALSE = 0, + GIT_SAFE_CRLF_FAIL = 1, + GIT_SAFE_CRLF_WARN = 2, + + /* core.autocrlf: false, true, 'input; */ + GIT_AUTO_CRLF_FALSE = 0, + GIT_AUTO_CRLF_TRUE = 1, + GIT_AUTO_CRLF_INPUT = 2, + GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, + + /* core.eol: unset, 'crlf', 'lf', 'native' */ + GIT_EOL_UNSET = 0, + GIT_EOL_CRLF = 1, + GIT_EOL_LF = 2, +#ifdef GIT_WIN32 + GIT_EOL_NATIVE = GIT_EOL_CRLF, +#else + GIT_EOL_NATIVE = GIT_EOL_LF, +#endif + GIT_EOL_DEFAULT = GIT_EOL_NATIVE, + + /* core.symlinks: bool */ + GIT_SYMLINKS_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.ignorecase */ + GIT_IGNORECASE_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.filemode */ + GIT_FILEMODE_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.ignorestat */ + GIT_IGNORESTAT_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.trustctime */ + GIT_TRUSTCTIME_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.abbrev */ + GIT_ABBREV_DEFAULT = 7, + /* core.precomposeunicode */ + GIT_PRECOMPOSE_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.safecrlf */ + GIT_SAFE_CRLF_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.logallrefupdates */ + 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_CONFIGMAP_FALSE, + /* core.protectNTFS */ + GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.fsyncObjectFiles */ + GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.longpaths */ + GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE +} git_configmap_value; + +/* internal repository init flags */ +enum { + GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16), + GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17), + GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18) +}; + +/** Internal structure for repository object */ +struct git_repository { + git_odb *_odb; + git_refdb *_refdb; + git_config *_config; + git_index *_index; + + git_cache objects; + git_attr_cache *attrcache; + git_diff_driver_registry *diff_drivers; + + char *gitlink; + char *gitdir; + char *commondir; + char *workdir; + char *namespace; + + char *ident_name; + char *ident_email; + + git_array_t(git_str) reserved_names; + + unsigned is_bare:1; + unsigned is_worktree:1; + + unsigned int lru_counter; + + git_atomic32 attr_session_key; + + intptr_t configmap_cache[GIT_CONFIGMAP_CACHE_MAX]; + git_strmap *submodule_cache; +}; + +GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) +{ + return repo->attrcache; +} + +int git_repository_head_tree(git_tree **tree, git_repository *repo); +int git_repository_create_head(const char *git_dir, const char *ref_name); + +typedef int (*git_repository_foreach_worktree_cb)(git_repository *, void *); + +int git_repository_foreach_worktree(git_repository *repo, + git_repository_foreach_worktree_cb cb, + void *payload); + +/* + * Weak pointers to repository internals. + * + * The returned pointers do not need to be freed. Do not keep + * permanent references to these (i.e. between API calls), since they may + * become invalidated if the user replaces a repository internal. + */ +int git_repository_config__weakptr(git_config **out, git_repository *repo); +int git_repository_odb__weakptr(git_odb **out, git_repository *repo); +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); +int git_repository_index__weakptr(git_index **out, git_repository *repo); + +/* + * 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__configmap_lookup(int *out, git_repository *repo, git_configmap_item item); +void git_repository__configmap_lookup_cache_clear(git_repository *repo); + +int git_repository__item_path(git_str *out, const git_repository *repo, git_repository_item_t item); + +GIT_INLINE(int) git_repository__ensure_not_bare( + git_repository *repo, + const char *operation_name) +{ + if (!git_repository_is_bare(repo)) + return 0; + + git_error_set( + GIT_ERROR_REPOSITORY, + "cannot %s. This operation is not allowed against bare repositories.", + operation_name); + + return GIT_EBAREREPO; +} + +int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head); + +int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len); + +/* The default "reserved names" for a repository */ +extern git_str git_repository__reserved_names_win32[]; +extern size_t git_repository__reserved_names_win32_len; + +extern git_str git_repository__reserved_names_posix[]; +extern size_t git_repository__reserved_names_posix_len; + +/* + * Gets any "reserved names" in the repository. This will return paths + * that should not be allowed in the repository (like ".git") to avoid + * conflicting with the repository path, or with alternate mechanisms to + * the repository path (eg, "GIT~1"). Every attempt will be made to look + * up all possible reserved names - if there was a conflict for the shortname + * GIT~1, for example, this function will try to look up the alternate + * shortname. If that fails, this function returns false, but out and outlen + * will still be populated with good defaults. + */ +bool git_repository__reserved_names( + git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs); + +/* + * The default branch for the repository; the `init.defaultBranch` + * configuration option, if set, or `master` if it is not. + */ +int git_repository_initialbranch(git_str *out, git_repository *repo); + +/* + * Given a relative `path`, this makes it absolute based on the + * repository's working directory. This will perform validation + * to ensure that the path is not longer than MAX_PATH on Windows + * (unless `core.longpaths` is set in the repo config). + */ +int git_repository_workdir_path(git_str *out, git_repository *repo, const char *path); + +int git_repository__extensions(char ***out, size_t *out_len); +int git_repository__set_extensions(const char **extensions, size_t len); +void git_repository__free_extensions(void); + +#endif diff --git a/src/libgit2/reset.c b/src/libgit2/reset.c new file mode 100644 index 000000000..e0d942e5e --- /dev/null +++ b/src/libgit2/reset.c @@ -0,0 +1,204 @@ +/* + * 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 "commit.h" +#include "tag.h" +#include "merge.h" +#include "diff.h" +#include "annotated_commit.h" +#include "git2/reset.h" +#include "git2/checkout.h" +#include "git2/merge.h" +#include "git2/refs.h" + +#define ERROR_MSG "Cannot perform reset" + +int git_reset_default( + git_repository *repo, + const git_object *target, + const git_strarray *pathspecs) +{ + git_object *commit = NULL; + git_tree *tree = NULL; + git_diff *diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + size_t i, max_i; + git_index_entry entry; + int error; + git_index *index = NULL; + + GIT_ASSERT_ARG(pathspecs && pathspecs->count > 0); + + memset(&entry, 0, sizeof(git_index_entry)); + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + if (target) { + if (git_object_owner(target) != repo) { + git_error_set(GIT_ERROR_OBJECT, + "%s_default - The given target does not belong to this repository.", ERROR_MSG); + return -1; + } + + if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || + (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) + goto cleanup; + } + + opts.pathspec = *pathspecs; + opts.flags = GIT_DIFF_REVERSE; + + if ((error = git_diff_tree_to_index( + &diff, repo, tree, index, &opts)) < 0) + goto cleanup; + + for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) { + const git_diff_delta *delta = git_diff_get_delta(diff, i); + + GIT_ASSERT(delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_MODIFIED || + delta->status == GIT_DELTA_CONFLICTED || + delta->status == GIT_DELTA_DELETED); + + error = git_index_conflict_remove(index, delta->old_file.path); + if (error < 0) { + if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND) + git_error_clear(); + else + goto cleanup; + } + + if (delta->status == GIT_DELTA_DELETED) { + if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) + goto cleanup; + } else { + entry.mode = delta->new_file.mode; + git_oid_cpy(&entry.id, &delta->new_file.id); + entry.path = (char *)delta->new_file.path; + + if ((error = git_index_add(index, &entry)) < 0) + goto cleanup; + } + } + + error = git_index_write(index); + +cleanup: + git_object_free(commit); + git_tree_free(tree); + git_index_free(index); + git_diff_free(diff); + + return error; +} + +static int reset( + git_repository *repo, + const git_object *target, + const char *to, + git_reset_t reset_type, + const git_checkout_options *checkout_opts) +{ + git_object *commit = NULL; + git_index *index = NULL; + git_tree *tree = NULL; + int error = 0; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str log_message = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(target); + + if (checkout_opts) + opts = *checkout_opts; + + if (git_object_owner(target) != repo) { + git_error_set(GIT_ERROR_OBJECT, + "%s - The given target does not belong to this repository.", ERROR_MSG); + return -1; + } + + if (reset_type != GIT_RESET_SOFT && + (error = git_repository__ensure_not_bare(repo, + reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0) + return error; + + if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || + (error = git_repository_index(&index, repo)) < 0 || + (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) + goto cleanup; + + if (reset_type == GIT_RESET_SOFT && + (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE || + git_index_has_conflicts(index))) + { + git_error_set(GIT_ERROR_OBJECT, "%s (soft) in the middle of a merge", ERROR_MSG); + error = GIT_EUNMERGED; + goto cleanup; + } + + if ((error = git_str_printf(&log_message, "reset: moving to %s", to)) < 0) + return error; + + if (reset_type == GIT_RESET_HARD) { + /* overwrite working directory with the new tree */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) + goto cleanup; + } + + /* move HEAD to the new target */ + if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, + git_object_id(commit), NULL, git_str_cstr(&log_message))) < 0) + goto cleanup; + + if (reset_type > GIT_RESET_SOFT) { + /* reset index to the target content */ + + if ((error = git_index_read_tree(index, tree)) < 0 || + (error = git_index_write(index)) < 0) + goto cleanup; + + if ((error = git_repository_state_cleanup(repo)) < 0) { + git_error_set(GIT_ERROR_INDEX, "%s - failed to clean up merge data", ERROR_MSG); + goto cleanup; + } + } + +cleanup: + git_object_free(commit); + git_index_free(index); + git_tree_free(tree); + git_str_dispose(&log_message); + + return error; +} + +int git_reset( + git_repository *repo, + const git_object *target, + git_reset_t reset_type, + const git_checkout_options *checkout_opts) +{ + char to[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(to, GIT_OID_HEXSZ + 1, git_object_id(target)); + return reset(repo, target, to, reset_type, checkout_opts); +} + +int git_reset_from_annotated( + git_repository *repo, + const git_annotated_commit *commit, + git_reset_t reset_type, + const git_checkout_options *checkout_opts) +{ + return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts); +} diff --git a/src/libgit2/revert.c b/src/libgit2/revert.c new file mode 100644 index 000000000..d6ab6ae3c --- /dev/null +++ b/src/libgit2/revert.c @@ -0,0 +1,243 @@ +/* +* 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 "repository.h" +#include "filebuf.h" +#include "merge.h" +#include "index.h" + +#include "git2/types.h" +#include "git2/merge.h" +#include "git2/revert.h" +#include "git2/commit.h" +#include "git2/sys/commit.h" + +#define GIT_REVERT_FILE_MODE 0666 + +static int write_revert_head( + git_repository *repo, + const char *commit_oidstr) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 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); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int write_merge_msg( + git_repository *repo, + const char *commit_oidstr, + const char *commit_msgline) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 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; + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int revert_normalize_opts( + git_repository *repo, + git_revert_options *opts, + const git_revert_options *given, + const char *their_label) +{ + int error = 0; + unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_ALLOW_CONFLICTS; + + GIT_UNUSED(repo); + + if (given != NULL) + memcpy(opts, given, sizeof(git_revert_options)); + else { + git_revert_options default_opts = GIT_REVERT_OPTIONS_INIT; + memcpy(opts, &default_opts, sizeof(git_revert_options)); + } + + if (!opts->checkout_opts.checkout_strategy) + opts->checkout_opts.checkout_strategy = default_checkout_strategy; + + if (!opts->checkout_opts.our_label) + opts->checkout_opts.our_label = "HEAD"; + + if (!opts->checkout_opts.their_label) + opts->checkout_opts.their_label = their_label; + + return error; +} + +static int revert_state_cleanup(git_repository *repo) +{ + const char *state_files[] = { GIT_REVERT_HEAD_FILE, GIT_MERGE_MSG_FILE }; + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +static int revert_seterr(git_commit *commit, const char *fmt) +{ + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + git_oid_fmt(commit_oidstr, git_commit_id(commit)); + commit_oidstr[GIT_OID_HEXSZ] = '\0'; + + git_error_set(GIT_ERROR_REVERT, fmt, commit_oidstr); + + return -1; +} + +int git_revert_commit( + git_index **out, + git_repository *repo, + git_commit *revert_commit, + git_commit *our_commit, + unsigned int mainline, + const git_merge_options *merge_opts) +{ + git_commit *parent_commit = NULL; + git_tree *parent_tree = NULL, *our_tree = NULL, *revert_tree = NULL; + int parent = 0, error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(revert_commit); + GIT_ASSERT_ARG(our_commit); + + if (git_commit_parentcount(revert_commit) > 1) { + if (!mainline) + return revert_seterr(revert_commit, + "mainline branch is not specified but %s is a merge commit"); + + parent = mainline; + } else { + if (mainline) + return revert_seterr(revert_commit, + "mainline branch specified but %s is not a merge commit"); + + parent = git_commit_parentcount(revert_commit); + } + + if (parent && + ((error = git_commit_parent(&parent_commit, revert_commit, (parent - 1))) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) + goto done; + + if ((error = git_commit_tree(&revert_tree, revert_commit)) < 0 || + (error = git_commit_tree(&our_tree, our_commit)) < 0) + goto done; + + error = git_merge_trees(out, repo, revert_tree, our_tree, parent_tree, merge_opts); + +done: + git_tree_free(parent_tree); + git_tree_free(our_tree); + git_tree_free(revert_tree); + git_commit_free(parent_commit); + + return error; +} + +int git_revert( + git_repository *repo, + git_commit *commit, + const git_revert_options *given_opts) +{ + git_revert_options opts; + git_reference *our_ref = NULL; + git_commit *our_commit = NULL; + char commit_oidstr[GIT_OID_HEXSZ + 1]; + const char *commit_msg; + git_str their_label = GIT_STR_INIT; + git_index *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(commit); + + GIT_ERROR_CHECK_VERSION(given_opts, GIT_REVERT_OPTIONS_VERSION, "git_revert_options"); + + if ((error = git_repository__ensure_not_bare(repo, "revert")) < 0) + return error; + + git_oid_fmt(commit_oidstr, git_commit_id(commit)); + commit_oidstr[GIT_OID_HEXSZ] = '\0'; + + if ((commit_msg = git_commit_summary(commit)) == NULL) { + error = -1; + goto on_error; + } + + if ((error = git_str_printf(&their_label, "parent of %.7s... %s", commit_oidstr, commit_msg)) < 0 || + (error = revert_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || + (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || + (error = write_revert_head(repo, commit_oidstr)) < 0 || + (error = write_merge_msg(repo, commit_oidstr, commit_msg)) < 0 || + (error = git_repository_head(&our_ref, repo)) < 0 || + (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || + (error = git_revert_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || + (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || + (error = git_indexwriter_commit(&indexwriter)) < 0) + goto on_error; + + goto done; + +on_error: + revert_state_cleanup(repo); + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_commit_free(our_commit); + git_reference_free(our_ref); + git_str_dispose(&their_label); + + return error; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_revert_init_options(git_revert_options *opts, unsigned int version) +{ + return git_revert_options_init(opts, version); +} +#endif diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c new file mode 100644 index 000000000..9bc28e9fc --- /dev/null +++ b/src/libgit2/revparse.c @@ -0,0 +1,949 @@ +/* + * 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 "str.h" +#include "tree.h" +#include "refdb.h" +#include "regexp.h" +#include "date.h" + +#include "git2.h" + +static int maybe_sha_or_abbrev(git_object **out, git_repository *repo, const char *spec, size_t speclen) +{ + git_oid oid; + + if (git_oid_fromstrn(&oid, spec, speclen) < 0) + return GIT_ENOTFOUND; + + return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJECT_ANY); +} + +static int maybe_sha(git_object **out, git_repository *repo, const char *spec) +{ + size_t speclen = strlen(spec); + + if (speclen != GIT_OID_HEXSZ) + return GIT_ENOTFOUND; + + return maybe_sha_or_abbrev(out, repo, spec, speclen); +} + +static int maybe_abbrev(git_object **out, git_repository *repo, const char *spec) +{ + size_t speclen = strlen(spec); + + return maybe_sha_or_abbrev(out, repo, spec, speclen); +} + +static int build_regex(git_regexp *regex, const char *pattern) +{ + int error; + + if (*pattern == '\0') { + git_error_set(GIT_ERROR_REGEX, "empty pattern"); + return GIT_EINVALIDSPEC; + } + + error = git_regexp_compile(regex, pattern, 0); + if (!error) + return 0; + + git_regexp_dispose(regex); + + return error; +} + +static int maybe_describe(git_object**out, git_repository *repo, const char *spec) +{ + const char *substr; + int error; + git_regexp regex; + + substr = strstr(spec, "-g"); + + if (substr == NULL) + return GIT_ENOTFOUND; + + if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) + return -1; + + error = git_regexp_match(®ex, spec); + git_regexp_dispose(®ex); + + if (error) + return GIT_ENOTFOUND; + + return maybe_abbrev(out, repo, substr+2); +} + +static int revparse_lookup_object( + git_object **object_out, + git_reference **reference_out, + git_repository *repo, + const char *spec) +{ + int error; + git_reference *ref; + + if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND) + return error; + + error = git_reference_dwim(&ref, repo, spec); + if (!error) { + + error = git_object_lookup( + object_out, repo, git_reference_target(ref), GIT_OBJECT_ANY); + + if (!error) + *reference_out = ref; + + return error; + } + + if (error != GIT_ENOTFOUND) + return error; + + if ((strlen(spec) < GIT_OID_HEXSZ) && + ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) + return error; + + if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND) + return error; + + git_error_set(GIT_ERROR_REFERENCE, "revspec '%s' not found", spec); + return GIT_ENOTFOUND; +} + +static int try_parse_numeric(int *n, const char *curly_braces_content) +{ + int32_t content; + const char *end_ptr; + + if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content), + &end_ptr, 10) < 0) + return -1; + + if (*end_ptr != '\0') + return -1; + + *n = (int)content; + return 0; +} + +static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) +{ + git_reference *ref = NULL; + git_reflog *reflog = NULL; + git_regexp preg; + int error = -1; + size_t i, numentries, cur; + const git_reflog_entry *entry; + const char *msg; + git_str buf = GIT_STR_INIT; + + cur = position; + + if (*identifier != '\0' || *base_ref != NULL) + return GIT_EINVALIDSPEC; + + if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) + return -1; + + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + goto cleanup; + + if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) + goto cleanup; + + 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 (git_regexp_search(&preg, msg, 2, regexmatches) < 0) + continue; + + cur--; + + if (cur > 0) + continue; + + if ((git_str_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0) + goto cleanup; + + if ((error = git_reference_dwim(base_ref, repo, git_str_cstr(&buf))) == 0) + goto cleanup; + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + error = maybe_abbrev(out, repo, git_str_cstr(&buf)); + + goto cleanup; + } + + error = GIT_ENOTFOUND; + +cleanup: + git_reference_free(ref); + git_str_dispose(&buf); + git_regexp_dispose(&preg); + git_reflog_free(reflog); + return error; +} + +static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier) +{ + git_reflog *reflog; + size_t numentries; + const git_reflog_entry *entry = NULL; + bool search_by_pos = (identifier <= 100000000); + + if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) + return -1; + + numentries = git_reflog_entrycount(reflog); + + if (search_by_pos) { + if (numentries < identifier + 1) + goto notfound; + + entry = git_reflog_entry_byindex(reflog, identifier); + git_oid_cpy(oid, git_reflog_entry_id_new(entry)); + } else { + size_t i; + git_time commit_time; + + for (i = 0; i < numentries; i++) { + entry = git_reflog_entry_byindex(reflog, i); + commit_time = git_reflog_entry_committer(entry)->when; + + if (commit_time.time > (git_time_t)identifier) + continue; + + git_oid_cpy(oid, git_reflog_entry_id_new(entry)); + break; + } + + if (i == numentries) { + if (entry == NULL) + goto notfound; + + /* + * TODO: emit a warning (log for 'branch' only goes back to ...) + */ + git_oid_cpy(oid, git_reflog_entry_id_new(entry)); + } + } + + git_reflog_free(reflog); + return 0; + +notfound: + git_error_set( + GIT_ERROR_REFERENCE, + "reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ, + git_reference_name(ref), numentries, identifier); + + git_reflog_free(reflog); + return GIT_ENOTFOUND; +} + +static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) +{ + git_reference *ref; + git_oid oid; + int error = -1; + + if (*base_ref == NULL) { + if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if (position == 0) { + error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJECT_ANY); + goto cleanup; + } + + if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) + goto cleanup; + + error = git_object_lookup(out, repo, &oid, GIT_OBJECT_ANY); + +cleanup: + git_reference_free(ref); + return error; +} + +static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) +{ + git_reference *tracking, *ref; + int error = -1; + + if (*base_ref == NULL) { + if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if (!git_reference_is_branch(ref)) { + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + if ((error = git_branch_upstream(&tracking, ref)) < 0) + goto cleanup; + + *base_ref = tracking; + +cleanup: + git_reference_free(ref); + return error; +} + +static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository *repo, const char *curly_braces_content) +{ + bool is_numeric; + int parsed = 0, error = -1; + git_str identifier = GIT_STR_INIT; + git_time_t timestamp; + + GIT_ASSERT(*out == NULL); + + if (git_str_put(&identifier, spec, identifier_len) < 0) + return -1; + + is_numeric = !try_parse_numeric(&parsed, curly_braces_content); + + if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + if (is_numeric) { + if (parsed < 0) + error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_str_cstr(&identifier), -parsed); + else + error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), parsed); + + goto cleanup; + } + + if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { + error = retrieve_remote_tracking_reference(ref, git_str_cstr(&identifier), repo); + + goto cleanup; + } + + if (git_date_parse(×tamp, curly_braces_content) < 0) { + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), (size_t)timestamp); + +cleanup: + git_str_dispose(&identifier); + return error; +} + +static git_object_t parse_obj_type(const char *str) +{ + if (!strcmp(str, "commit")) + return GIT_OBJECT_COMMIT; + + if (!strcmp(str, "tree")) + return GIT_OBJECT_TREE; + + if (!strcmp(str, "blob")) + return GIT_OBJECT_BLOB; + + if (!strcmp(str, "tag")) + return GIT_OBJECT_TAG; + + return GIT_OBJECT_INVALID; +} + +static int dereference_to_non_tag(git_object **out, git_object *obj) +{ + if (git_object_type(obj) == GIT_OBJECT_TAG) + return git_tag_peel(out, (git_tag *)obj); + + return git_object_dup(out, obj); +} + +static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) +{ + git_object *temp_commit = NULL; + int error; + + if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) + return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? + GIT_EINVALIDSPEC : error; + + if (n == 0) { + *out = temp_commit; + return 0; + } + + error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1); + + git_object_free(temp_commit); + return error; +} + +static int handle_linear_syntax(git_object **out, git_object *obj, int n) +{ + git_object *temp_commit = NULL; + int error; + + if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) + return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? + GIT_EINVALIDSPEC : error; + + error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); + + git_object_free(temp_commit); + return error; +} + +static int handle_colon_syntax( + git_object **out, + git_object *obj, + const char *path) +{ + git_object *tree; + int error = -1; + git_tree_entry *entry = NULL; + + if ((error = git_object_peel(&tree, obj, GIT_OBJECT_TREE)) < 0) + return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error; + + if (*path == '\0') { + *out = tree; + return 0; + } + + /* + * TODO: Handle the relative path syntax + * (:./relative/path and :../relative/path) + */ + if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) + goto cleanup; + + error = git_tree_entry_to_object(out, git_object_owner(tree), entry); + +cleanup: + git_tree_entry_free(entry); + git_object_free(tree); + + return error; +} + +static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex) +{ + int error; + git_oid oid; + git_object *obj; + + while (!(error = git_revwalk_next(&oid, walk))) { + + error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJECT_COMMIT); + if ((error < 0) && (error != GIT_ENOTFOUND)) + return -1; + + if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) { + *out = obj; + return 0; + } + + git_object_free(obj); + } + + if (error < 0 && error == GIT_ITEROVER) + error = GIT_ENOTFOUND; + + return error; +} + +static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) +{ + git_regexp preg; + git_revwalk *walk = NULL; + int error; + + if ((error = build_regex(&preg, pattern)) < 0) + return error; + + if ((error = git_revwalk_new(&walk, repo)) < 0) + goto cleanup; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if (spec_oid == NULL) { + if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0) + goto cleanup; + } else if ((error = git_revwalk_push(walk, spec_oid)) < 0) + goto cleanup; + + error = walk_and_search(out, walk, &preg); + +cleanup: + git_regexp_dispose(&preg); + git_revwalk_free(walk); + + return error; +} + +static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content) +{ + git_object_t expected_type; + + if (*curly_braces_content == '\0') + return dereference_to_non_tag(out, obj); + + if (*curly_braces_content == '/') + return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1); + + expected_type = parse_obj_type(curly_braces_content); + + if (expected_type == GIT_OBJECT_INVALID) + return GIT_EINVALIDSPEC; + + return git_object_peel(out, obj, expected_type); +} + +static int extract_curly_braces_content(git_str *buf, const char *spec, size_t *pos) +{ + git_str_clear(buf); + + GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '@'); + + (*pos)++; + + if (spec[*pos] == '\0' || spec[*pos] != '{') + return GIT_EINVALIDSPEC; + + (*pos)++; + + while (spec[*pos] != '}') { + if (spec[*pos] == '\0') + return GIT_EINVALIDSPEC; + + if (git_str_putc(buf, spec[(*pos)++]) < 0) + return -1; + } + + (*pos)++; + + return 0; +} + +static int extract_path(git_str *buf, const char *spec, size_t *pos) +{ + git_str_clear(buf); + + GIT_ASSERT_ARG(spec[*pos] == ':'); + + (*pos)++; + + if (git_str_puts(buf, spec + *pos) < 0) + return -1; + + *pos += git_str_len(buf); + + return 0; +} + +static int extract_how_many(int *n, const char *spec, size_t *pos) +{ + const char *end_ptr; + int parsed, accumulated; + char kind = spec[*pos]; + + GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '~'); + + accumulated = 0; + + do { + do { + (*pos)++; + accumulated++; + } while (spec[(*pos)] == kind && kind == '~'); + + if (git__isdigit(spec[*pos])) { + if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0) + return GIT_EINVALIDSPEC; + + accumulated += (parsed - 1); + *pos = end_ptr - spec; + } + + } while (spec[(*pos)] == kind && kind == '~'); + + *n = accumulated; + + return 0; +} + +static int object_from_reference(git_object **object, git_reference *reference) +{ + git_reference *resolved = NULL; + int error; + + if (git_reference_resolve(&resolved, reference) < 0) + return -1; + + error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJECT_ANY); + git_reference_free(resolved); + + return error; +} + +static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier) +{ + int error; + git_str identifier = GIT_STR_INIT; + + if (*object != NULL) + return 0; + + if (*reference != NULL) + return object_from_reference(object, *reference); + + if (!allow_empty_identifier && identifier_len == 0) + return GIT_EINVALIDSPEC; + + if (git_str_put(&identifier, spec, identifier_len) < 0) + return -1; + + error = revparse_lookup_object(object, reference, repo, git_str_cstr(&identifier)); + git_str_dispose(&identifier); + + return error; +} + +static int ensure_base_rev_is_not_known_yet(git_object *object) +{ + if (object == NULL) + return 0; + + return GIT_EINVALIDSPEC; +} + +static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len) +{ + if (object != NULL) + return true; + + if (reference != NULL) + return true; + + if (identifier_len > 0) + return true; + + return false; +} + +static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference) +{ + if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL) + return 0; + + return GIT_EINVALIDSPEC; +} + +static int revparse( + git_object **object_out, + git_reference **reference_out, + size_t *identifier_len_out, + git_repository *repo, + const char *spec) +{ + size_t pos = 0, identifier_len = 0; + int error = -1, n; + git_str buf = GIT_STR_INIT; + + git_reference *reference = NULL; + git_object *base_rev = NULL; + + bool should_return_reference = true; + + GIT_ASSERT_ARG(object_out); + GIT_ASSERT_ARG(reference_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(spec); + + *object_out = NULL; + *reference_out = NULL; + + while (spec[pos]) { + switch (spec[pos]) { + case '^': + should_return_reference = false; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + } else { + git_object *temp_object = NULL; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + } + break; + + case '~': + { + git_object *temp_object = NULL; + + should_return_reference = false; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + break; + } + + case ':': + { + git_object *temp_object = NULL; + + should_return_reference = false; + + if ((error = extract_path(&buf, spec, &pos)) < 0) + goto cleanup; + + if (any_left_hand_identifier(base_rev, reference, identifier_len)) { + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) + goto cleanup; + + if ((error = handle_colon_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) + goto cleanup; + } else { + if (*git_str_cstr(&buf) == '/') { + if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_str_cstr(&buf) + 1)) < 0) + goto cleanup; + } else { + + /* + * TODO: support merge-stage path lookup (":2:Makefile") + * and plain index blob lookup (:i-am/a/blob) + */ + git_error_set(GIT_ERROR_INVALID, "unimplemented"); + error = GIT_ERROR; + goto cleanup; + } + } + + git_object_free(base_rev); + base_rev = temp_object; + break; + } + + case '@': + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0) + goto cleanup; + + if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_str_cstr(&buf))) < 0) + goto cleanup; + + if (temp_object != NULL) + base_rev = temp_object; + break; + } else if (spec[pos+1] == '\0') { + spec = "HEAD"; + break; + } + /* fall through */ + + default: + if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0) + goto cleanup; + + pos++; + identifier_len++; + } + } + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if (!should_return_reference) { + git_reference_free(reference); + reference = NULL; + } + + *object_out = base_rev; + *reference_out = reference; + *identifier_len_out = identifier_len; + error = 0; + +cleanup: + if (error) { + if (error == GIT_EINVALIDSPEC) + git_error_set(GIT_ERROR_INVALID, + "failed to parse revision specifier - Invalid pattern '%s'", spec); + + git_object_free(base_rev); + git_reference_free(reference); + } + + git_str_dispose(&buf); + return error; +} + +int git_revparse_ext( + git_object **object_out, + git_reference **reference_out, + git_repository *repo, + const char *spec) +{ + int error; + size_t identifier_len; + git_object *obj = NULL; + git_reference *ref = NULL; + + if ((error = revparse(&obj, &ref, &identifier_len, repo, spec)) < 0) + goto cleanup; + + *object_out = obj; + *reference_out = ref; + GIT_UNUSED(identifier_len); + + return 0; + +cleanup: + git_object_free(obj); + git_reference_free(ref); + return error; +} + +int git_revparse_single(git_object **out, git_repository *repo, const char *spec) +{ + int error; + git_object *obj = NULL; + git_reference *ref = NULL; + + *out = NULL; + + if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0) + goto cleanup; + + git_reference_free(ref); + + *out = obj; + + return 0; + +cleanup: + git_object_free(obj); + git_reference_free(ref); + return error; +} + +int git_revparse( + git_revspec *revspec, + git_repository *repo, + const char *spec) +{ + const char *dotdot; + int error = 0; + + GIT_ASSERT_ARG(revspec); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(spec); + + memset(revspec, 0x0, sizeof(*revspec)); + + if ((dotdot = strstr(spec, "..")) != NULL) { + char *lstr; + const char *rstr; + revspec->flags = GIT_REVSPEC_RANGE; + + /* + * Following git.git, don't allow '..' because it makes command line + * arguments which can be either paths or revisions ambiguous when the + * path is almost certainly intended. The empty range '...' is still + * allowed. + */ + if (!git__strcmp(spec, "..")) { + git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); + return GIT_EINVALIDSPEC; + } + + lstr = git__substrdup(spec, dotdot - spec); + rstr = dotdot + 2; + if (dotdot[2] == '.') { + revspec->flags |= GIT_REVSPEC_MERGE_BASE; + rstr++; + } + + error = git_revparse_single( + &revspec->from, + repo, + *lstr == '\0' ? "HEAD" : lstr); + + if (!error) { + error = git_revparse_single( + &revspec->to, + repo, + *rstr == '\0' ? "HEAD" : rstr); + } + + git__free((void*)lstr); + } else { + revspec->flags = GIT_REVSPEC_SINGLE; + error = git_revparse_single(&revspec->from, repo, spec); + } + + return error; +} diff --git a/src/libgit2/revwalk.c b/src/libgit2/revwalk.c new file mode 100644 index 000000000..553e0497a --- /dev/null +++ b/src/libgit2/revwalk.c @@ -0,0 +1,820 @@ +/* + * 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 "revwalk.h" + +#include "commit.h" +#include "odb.h" +#include "pool.h" + +#include "git2/revparse.h" +#include "merge.h" +#include "vector.h" + +static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list); + +git_commit_list_node *git_revwalk__commit_lookup( + git_revwalk *walk, const git_oid *oid) +{ + git_commit_list_node *commit; + + /* lookup and reserve space if not already present */ + if ((commit = git_oidmap_get(walk->commits, oid)) != NULL) + return commit; + + commit = git_commit_list_alloc_node(walk); + if (commit == NULL) + return NULL; + + git_oid_cpy(&commit->oid, oid); + + if ((git_oidmap_set(walk->commits, &commit->oid, commit)) < 0) + return NULL; + + return commit; +} + +int git_revwalk__push_commit(git_revwalk *walk, const git_oid *oid, const git_revwalk__push_options *opts) +{ + git_oid commit_id; + int error; + git_object *obj, *oobj; + git_commit_list_node *commit; + git_commit_list *list; + + if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJECT_ANY)) < 0) + return error; + + error = git_object_peel(&obj, oobj, GIT_OBJECT_COMMIT); + git_object_free(oobj); + + if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) { + /* If this comes from e.g. push_glob("tags"), ignore this */ + if (opts->from_glob) + return 0; + + git_error_set(GIT_ERROR_INVALID, "object is not a committish"); + return error; + } + if (error < 0) + return error; + + git_oid_cpy(&commit_id, git_object_id(obj)); + git_object_free(obj); + + commit = git_revwalk__commit_lookup(walk, &commit_id); + if (commit == NULL) + return -1; /* error already reported by failed lookup */ + + /* A previous hide already told us we don't want this commit */ + if (commit->uninteresting) + return 0; + + if (opts->uninteresting) { + walk->limited = 1; + walk->did_hide = 1; + } else { + walk->did_push = 1; + } + + commit->uninteresting = opts->uninteresting; + list = walk->user_input; + 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; + } + + walk->user_input = list; + + return 0; +} + +int git_revwalk_push(git_revwalk *walk, const git_oid *oid) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(oid); + + 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; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(oid); + + opts.uninteresting = 1; + return git_revwalk__push_commit(walk, oid, &opts); +} + +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 git_revwalk__push_commit(walk, &oid, opts); +} + +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_str buf = GIT_STR_INIT; + git_reference *ref; + git_reference_iterator *iter; + size_t wildcard; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(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_str_joinpath(&buf, GIT_REFS_DIR, glob); + else + git_str_puts(&buf, glob); + GIT_ERROR_CHECK_ALLOC_STR(&buf); + + /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ + wildcard = strcspn(glob, "?*["); + if (!glob[wildcard]) + git_str_put(&buf, "/*", 2); + + 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 = git_revwalk__push_ref(walk, git_reference_name(ref), &opts); + git_reference_free(ref); + if (error < 0) + break; + } + git_reference_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; +out: + git_str_dispose(&buf); + return error; +} + +int git_revwalk_push_glob(git_revwalk *walk, const char *glob) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(glob); + + 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; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(glob); + + 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; + + GIT_ASSERT_ARG(walk); + + 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; + + GIT_ASSERT_ARG(walk); + + 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; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(refname); + + 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_REVSPEC_MERGE_BASE) { + /* TODO: support "..." */ + git_error_set(GIT_ERROR_INVALID, "symmetric differences not implemented in revwalk"); + error = GIT_EINVALIDSPEC; + goto out; + } + + opts.uninteresting = 1; + if ((error = git_revwalk__push_commit(walk, git_object_id(revspec.from), &opts))) + goto out; + + opts.uninteresting = 0; + error = git_revwalk__push_commit(walk, git_object_id(revspec.to), &opts); + +out: + git_object_free(revspec.from); + git_object_free(revspec.to); + return error; +} + +int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(refname); + + opts.uninteresting = 1; + return git_revwalk__push_ref(walk, refname, &opts); +} + +static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) +{ + return git_pqueue_insert(&walk->iterator_time, commit); +} + +static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit) +{ + return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; +} + +static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk) +{ + git_commit_list_node *next; + + while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; + } + } + + git_error_clear(); + return GIT_ITEROVER; +} + +static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk) +{ + int error; + git_commit_list_node *next; + + while (!(error = get_revision(&next, walk, &walk->iterator_rand))) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; + } + } + + return error; +} + +static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) +{ + int error; + git_commit_list_node *next; + + while (!(error = get_revision(&next, walk, &walk->iterator_topo))) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; + } + } + + return error; +} + +static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk) +{ + *object_out = git_commit_list_pop(&walk->iterator_reverse); + return *object_out ? 0 : GIT_ITEROVER; +} + +static void mark_parents_uninteresting(git_commit_list_node *commit) +{ + unsigned short i; + git_commit_list *parents = NULL; + + for (i = 0; i < commit->out_degree; i++) + git_commit_list_insert(commit->parents[i], &parents); + + + while (parents) { + commit = git_commit_list_pop(&parents); + + while (commit) { + if (commit->uninteresting) + break; + + commit->uninteresting = 1; + /* + * If we've reached this commit some other way + * already, we need to mark its parents uninteresting + * as well. + */ + if (!commit->parents) + break; + + for (i = 0; i < commit->out_degree; i++) + git_commit_list_insert(commit->parents[i], &parents); + commit = commit->parents[0]; + } + } +} + +static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list) +{ + unsigned short i; + int error; + + if (commit->added) + return 0; + + commit->added = 1; + + /* + * Go full on in the uninteresting case as we want to include + * as many of these as we can. + * + * Usually we haven't parsed the parent of a parent, but if we + * have it we reached it via other means so we want to mark + * its parents recursively too. + */ + if (commit->uninteresting) { + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + p->uninteresting = 1; + + /* git does it gently here, but we don't like missing objects */ + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + if (p->parents) + mark_parents_uninteresting(p); + + p->seen = 1; + git_commit_list_insert_by_date(p, list); + } + + return 0; + } + + /* + * Now on to what we do if the commit is indeed + * interesting. Here we do want things like first-parent take + * effect as this is what we'll be showing. + */ + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload)) + continue; + + if (!p->seen) { + p->seen = 1; + git_commit_list_insert_by_date(p, list); + } + + if (walk->first_parent) + break; + } + return 0; +} + +/* How many uninteresting commits we want to look at after we run out of interesting ones */ +#define SLOP 5 + +static int still_interesting(git_commit_list *list, int64_t time, int slop) +{ + /* The empty list is pretty boring */ + if (!list) + return 0; + + /* + * If the destination list has commits with an earlier date than our + * source, we want to reset the slop counter as we're not done. + */ + if (time <= list->item->time) + return SLOP; + + for (; list; list = list->next) { + /* + * If the destination list still contains interesting commits we + * want to continue looking. + */ + if (!list->item->uninteresting || list->item->time > time) + return SLOP; + } + + /* Everything's uninteresting, reduce the count */ + return slop - 1; +} + +static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits) +{ + int error, slop = SLOP; + int64_t time = INT64_MAX; + git_commit_list *list = commits; + git_commit_list *newlist = NULL; + git_commit_list **p = &newlist; + + while (list) { + git_commit_list_node *commit = git_commit_list_pop(&list); + + if ((error = add_parents_to_list(walk, commit, &list)) < 0) + return error; + + if (commit->uninteresting) { + mark_parents_uninteresting(commit); + + slop = still_interesting(list, time, slop); + if (slop) + continue; + + break; + } + + if (walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload)) + continue; + + time = commit->time; + p = &git_commit_list_insert(commit, p)->next; + } + + git_commit_list_free(&list); + *out = newlist; + return 0; +} + +static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list) +{ + int error; + git_commit_list_node *commit; + + commit = git_commit_list_pop(list); + if (!commit) { + git_error_clear(); + return GIT_ITEROVER; + } + + /* + * If we did not run limit_list and we must add parents to the + * list ourselves. + */ + if (!walk->limited) { + if ((error = add_parents_to_list(walk, commit, list)) < 0) + return error; + } + + *out = commit; + return 0; +} + +static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list) +{ + git_commit_list *ll = NULL, *newlist, **pptr; + git_commit_list_node *next; + git_pqueue queue; + git_vector_cmp queue_cmp = NULL; + unsigned short i; + int error; + + if (walk->sorting & GIT_SORT_TIME) + queue_cmp = git_commit_list_time_cmp; + + if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp))) + return error; + + /* + * Start by resetting the in-degree to 1 for the commits in + * our list. We want to go through this list again, so we + * store it in the commit list as we extract it from the lower + * machinery. + */ + for (ll = list; ll; ll = ll->next) { + ll->item->in_degree = 1; + } + + /* + * Count up how many children each commit has. We limit + * ourselves to those commits in the original list (in-degree + * of 1) avoiding setting it for any parent that was hidden. + */ + for(ll = list; ll; ll = ll->next) { + for (i = 0; i < ll->item->out_degree; ++i) { + git_commit_list_node *parent = ll->item->parents[i]; + if (parent->in_degree) + parent->in_degree++; + } + } + + /* + * Now we find the tips i.e. those not reachable from any other node + * i.e. those which still have an in-degree of 1. + */ + for(ll = list; ll; ll = ll->next) { + if (ll->item->in_degree == 1) { + if ((error = git_pqueue_insert(&queue, ll->item))) + goto cleanup; + } + } + + /* + * We need to output the tips in the order that they came out of the + * traversal, so if we're not doing time-sorting, we need to reverse the + * pqueue in order to get them to come out as we inserted them. + */ + if ((walk->sorting & GIT_SORT_TIME) == 0) + git_pqueue_reverse(&queue); + + + pptr = &newlist; + newlist = NULL; + while ((next = git_pqueue_pop(&queue)) != NULL) { + for (i = 0; i < next->out_degree; ++i) { + git_commit_list_node *parent = next->parents[i]; + if (parent->in_degree == 0) + continue; + + if (--parent->in_degree == 1) { + if ((error = git_pqueue_insert(&queue, parent))) + goto cleanup; + } + } + + /* All the children of 'item' have been emitted (since we got to it via the priority queue) */ + next->in_degree = 0; + + pptr = &git_commit_list_insert(next, pptr)->next; + } + + *out = newlist; + error = 0; + +cleanup: + git_pqueue_free(&queue); + return error; +} + +static int prepare_walk(git_revwalk *walk) +{ + int error = 0; + git_commit_list *list, *commits = NULL; + git_commit_list_node *next; + + /* If there were no pushes, we know that the walk is already over */ + if (!walk->did_push) { + git_error_clear(); + return GIT_ITEROVER; + } + + for (list = walk->user_input; list; list = list->next) { + git_commit_list_node *commit = list->item; + if ((error = git_commit_list_parse(walk, commit)) < 0) + return error; + + if (commit->uninteresting) + mark_parents_uninteresting(commit); + + if (!commit->seen) { + commit->seen = 1; + git_commit_list_insert(commit, &commits); + } + } + + if (walk->limited && (error = limit_list(&commits, walk, commits)) < 0) + return error; + + if (walk->sorting & GIT_SORT_TOPOLOGICAL) { + error = sort_in_topological_order(&walk->iterator_topo, walk, commits); + git_commit_list_free(&commits); + + if (error < 0) + return error; + + walk->get_next = &revwalk_next_toposort; + } else if (walk->sorting & GIT_SORT_TIME) { + for (list = commits; list && !error; list = list->next) + error = walk->enqueue(walk, list->item); + + git_commit_list_free(&commits); + + if (error < 0) + return error; + } else { + walk->iterator_rand = commits; + walk->get_next = revwalk_next_unsorted; + } + + if (walk->sorting & GIT_SORT_REVERSE) { + + while ((error = walk->get_next(&next, walk)) == 0) + if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL) + return -1; + + if (error != GIT_ITEROVER) + return error; + + walk->get_next = &revwalk_next_reverse; + } + + walk->walking = 1; + return 0; +} + + +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); + + if (git_oidmap_new(&walk->commits) < 0 || + git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || + git_pool_init(&walk->commit_pool, COMMIT_ALLOC) < 0) + return -1; + + walk->get_next = &revwalk_next_unsorted; + walk->enqueue = &revwalk_enqueue_unsorted; + + walk->repo = repo; + + if (git_repository_odb(&walk->odb, repo) < 0) { + git_revwalk_free(walk); + return -1; + } + + *revwalk_out = walk; + return 0; +} + +void git_revwalk_free(git_revwalk *walk) +{ + if (walk == NULL) + return; + + git_revwalk_reset(walk); + git_odb_free(walk->odb); + + git_oidmap_free(walk->commits); + git_pool_clear(&walk->commit_pool); + git_pqueue_free(&walk->iterator_time); + git__free(walk); +} + +git_repository *git_revwalk_repository(git_revwalk *walk) +{ + GIT_ASSERT_ARG_WITH_RETVAL(walk, NULL); + + return walk->repo; +} + +int git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) +{ + GIT_ASSERT_ARG(walk); + + if (walk->walking) + git_revwalk_reset(walk); + + walk->sorting = sort_mode; + + if (walk->sorting & GIT_SORT_TIME) { + walk->get_next = &revwalk_next_timesort; + walk->enqueue = &revwalk_enqueue_timesort; + } else { + walk->get_next = &revwalk_next_unsorted; + walk->enqueue = &revwalk_enqueue_unsorted; + } + + if (walk->sorting != GIT_SORT_NONE) + walk->limited = 1; + + return 0; +} + +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) +{ + int error; + git_commit_list_node *next; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(oid); + + if (!walk->walking) { + if ((error = prepare_walk(walk)) < 0) + return error; + } + + error = walk->get_next(&next, walk); + + if (error == GIT_ITEROVER) { + git_revwalk_reset(walk); + git_error_clear(); + return GIT_ITEROVER; + } + + if (!error) + git_oid_cpy(oid, &next->oid); + + return error; +} + +int git_revwalk_reset(git_revwalk *walk) +{ + git_commit_list_node *commit; + + GIT_ASSERT_ARG(walk); + + git_oidmap_foreach_value(walk->commits, commit, { + commit->seen = 0; + commit->in_degree = 0; + commit->topo_delay = 0; + commit->uninteresting = 0; + commit->added = 0; + commit->flags = 0; + }); + + git_pqueue_clear(&walk->iterator_time); + git_commit_list_free(&walk->iterator_topo); + git_commit_list_free(&walk->iterator_rand); + git_commit_list_free(&walk->iterator_reverse); + git_commit_list_free(&walk->user_input); + walk->first_parent = 0; + walk->walking = 0; + walk->limited = 0; + walk->did_push = walk->did_hide = 0; + walk->sorting = GIT_SORT_NONE; + + return 0; +} + +int git_revwalk_add_hide_cb( + git_revwalk *walk, + git_revwalk_hide_cb hide_cb, + void *payload) +{ + GIT_ASSERT_ARG(walk); + + if (walk->walking) + git_revwalk_reset(walk); + + walk->hide_cb = hide_cb; + walk->hide_cb_payload = payload; + + if (hide_cb) + walk->limited = 1; + + return 0; +} + diff --git a/src/libgit2/revwalk.h b/src/libgit2/revwalk.h new file mode 100644 index 000000000..94b8a6fb1 --- /dev/null +++ b/src/libgit2/revwalk.h @@ -0,0 +1,73 @@ +/* + * 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_revwalk_h__ +#define INCLUDE_revwalk_h__ + +#include "common.h" + +#include "git2/revwalk.h" +#include "oidmap.h" +#include "commit_list.h" +#include "pqueue.h" +#include "pool.h" +#include "vector.h" + +#include "oidmap.h" + +struct git_revwalk { + git_repository *repo; + git_odb *odb; + + git_oidmap *commits; + git_pool commit_pool; + + git_commit_list *iterator_topo; + git_commit_list *iterator_rand; + git_commit_list *iterator_reverse; + git_pqueue iterator_time; + + int (*get_next)(git_commit_list_node **, git_revwalk *); + int (*enqueue)(git_revwalk *, git_commit_list_node *); + + unsigned walking:1, + first_parent: 1, + did_hide: 1, + did_push: 1, + limited: 1; + unsigned int sorting; + + /* the pushes and hides */ + git_commit_list *user_input; + + /* hide callback */ + git_revwalk_hide_cb hide_cb; + void *hide_cb_payload; +}; + +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/libgit2/settings.h b/src/libgit2/settings.h new file mode 100644 index 000000000..dc42ce939 --- /dev/null +++ b/src/libgit2/settings.h @@ -0,0 +1,11 @@ +/* + * 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. + */ + +extern int git_settings_global_init(void); + +extern const char *git_libgit2__user_agent(void); +extern const char *git_libgit2__ssl_ciphers(void); diff --git a/src/libgit2/signature.c b/src/libgit2/signature.c new file mode 100644 index 000000000..5d6ab572c --- /dev/null +++ b/src/libgit2/signature.c @@ -0,0 +1,339 @@ +/* + * 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 "signature.h" + +#include "repository.h" +#include "git2/common.h" +#include "posix.h" + +void git_signature_free(git_signature *sig) +{ + if (sig == NULL) + return; + + git__free(sig->name); + sig->name = NULL; + git__free(sig->email); + sig->email = NULL; + git__free(sig); +} + +static int signature_parse_error(const char *msg) +{ + git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); + return GIT_EINVALID; +} + +static int signature_error(const char *msg) +{ + git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); + return -1; +} + +static bool contains_angle_brackets(const char *input) +{ + return strchr(input, '<') != NULL || strchr(input, '>') != NULL; +} + +static bool is_crud(unsigned char c) +{ + return c <= 32 || + c == '.' || + c == ',' || + c == ':' || + c == ';' || + c == '<' || + c == '>' || + c == '"' || + c == '\\' || + c == '\''; +} + +static char *extract_trimmed(const char *ptr, size_t len) +{ + while (len && is_crud((unsigned char)ptr[0])) { + ptr++; len--; + } + + while (len && is_crud((unsigned char)ptr[len - 1])) { + len--; + } + + return git__substrdup(ptr, len); +} + +int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) +{ + git_signature *p = NULL; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(email); + + *sig_out = NULL; + + if (contains_angle_brackets(name) || + contains_angle_brackets(email)) { + return signature_error( + "Neither `name` nor `email` should contain angle brackets chars."); + } + + p = git__calloc(1, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(p); + + p->name = extract_trimmed(name, strlen(name)); + GIT_ERROR_CHECK_ALLOC(p->name); + p->email = extract_trimmed(email, strlen(email)); + GIT_ERROR_CHECK_ALLOC(p->email); + + if (p->name[0] == '\0' || p->email[0] == '\0') { + git_signature_free(p); + return signature_error("Signature cannot have an empty name or email"); + } + + p->when.time = time; + p->when.offset = offset; + p->when.sign = (offset < 0) ? '-' : '+'; + + *sig_out = p; + return 0; +} + +int git_signature_dup(git_signature **dest, const git_signature *source) +{ + git_signature *signature; + + if (source == NULL) + return 0; + + signature = git__calloc(1, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(signature); + + signature->name = git__strdup(source->name); + GIT_ERROR_CHECK_ALLOC(signature->name); + + signature->email = git__strdup(source->email); + GIT_ERROR_CHECK_ALLOC(signature->email); + + signature->when.time = source->when.time; + signature->when.offset = source->when.offset; + signature->when.sign = source->when.sign; + + *dest = signature; + + return 0; +} + +int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool) +{ + git_signature *signature; + + if (source == NULL) + return 0; + + signature = git_pool_mallocz(pool, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(signature); + + signature->name = git_pool_strdup(pool, source->name); + GIT_ERROR_CHECK_ALLOC(signature->name); + + signature->email = git_pool_strdup(pool, source->email); + GIT_ERROR_CHECK_ALLOC(signature->email); + + signature->when.time = source->when.time; + signature->when.offset = source->when.offset; + signature->when.sign = source->when.sign; + + *dest = signature; + + return 0; +} + +int git_signature_now(git_signature **sig_out, const char *name, const char *email) +{ + time_t now; + time_t offset; + struct tm *utc_tm; + git_signature *sig; + struct tm _utc; + + *sig_out = NULL; + + /* + * Get the current time as seconds since the epoch and + * transform that into a tm struct containing the time at + * UTC. Give that to mktime which considers it a local time + * (tm_isdst = -1 asks it to take DST into account) and gives + * us that time as seconds since the epoch. The difference + * between its return value and 'now' is our offset to UTC. + */ + time(&now); + utc_tm = p_gmtime_r(&now, &_utc); + utc_tm->tm_isdst = -1; + offset = (time_t)difftime(now, mktime(utc_tm)); + offset /= 60; + + if (git_signature_new(&sig, name, email, now, (int)offset) < 0) + return -1; + + *sig_out = sig; + + return 0; +} + +int git_signature_default(git_signature **out, git_repository *repo) +{ + int error; + git_config *cfg; + const char *user_name, *user_email; + + if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) + return error; + + if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && + !(error = git_config_get_string(&user_email, cfg, "user.email"))) + error = git_signature_now(out, user_name, user_email); + + git_config_free(cfg); + return error; +} + +int git_signature__parse(git_signature *sig, const char **buffer_out, + const char *buffer_end, const char *header, char ender) +{ + const char *buffer = *buffer_out; + const char *email_start, *email_end; + + memset(sig, 0, sizeof(git_signature)); + + if (ender && + (buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) + return signature_parse_error("no newline given"); + + if (header) { + const size_t header_len = strlen(header); + + if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0) + return signature_parse_error("expected prefix doesn't match actual"); + + buffer += header_len; + } + + email_start = git__memrchr(buffer, '<', buffer_end - buffer); + email_end = git__memrchr(buffer, '>', buffer_end - buffer); + + if (!email_start || !email_end || email_end <= email_start) + return signature_parse_error("malformed e-mail"); + + email_start += 1; + sig->name = extract_trimmed(buffer, email_start - buffer - 1); + sig->email = extract_trimmed(email_start, email_end - email_start); + + /* Do we even have a time at the end of the signature? */ + if (email_end + 2 < buffer_end) { + const char *time_start = email_end + 2; + const char *time_end; + + if (git__strntol64(&sig->when.time, time_start, + buffer_end - time_start, &time_end, 10) < 0) { + git__free(sig->name); + git__free(sig->email); + sig->name = sig->email = NULL; + return signature_parse_error("invalid Unix timestamp"); + } + + /* do we have a timezone? */ + if (time_end + 1 < buffer_end) { + int offset, hours, mins; + const char *tz_start, *tz_end; + + tz_start = time_end + 1; + + if ((tz_start[0] != '-' && tz_start[0] != '+') || + git__strntol32(&offset, tz_start + 1, + buffer_end - tz_start - 1, &tz_end, 10) < 0) { + /* malformed timezone, just assume it's zero */ + offset = 0; + } + + hours = offset / 100; + mins = offset % 100; + + /* + * only store timezone if it's not overflowing; + * see http://www.worldtimezone.com/faq.html + */ + if (hours <= 14 && mins <= 59) { + sig->when.offset = (hours * 60) + mins; + sig->when.sign = tz_start[0]; + if (tz_start[0] == '-') + sig->when.offset = -sig->when.offset; + } + } + } + + *buffer_out = buffer_end + 1; + return 0; +} + +int git_signature_from_buffer(git_signature **out, const char *buf) +{ + git_signature *sig; + const char *buf_end; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(buf); + + *out = NULL; + + sig = git__calloc(1, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(sig); + + buf_end = buf + strlen(buf); + error = git_signature__parse(sig, &buf, buf_end, NULL, '\0'); + + if (error) + git__free(sig); + else + *out = sig; + + return error; +} + +void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig) +{ + int offset, hours, mins; + char sign; + + offset = sig->when.offset; + sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+'; + + if (offset < 0) + offset = -offset; + + hours = offset / 60; + mins = offset % 60; + + git_str_printf(buf, "%s%s <%s> %u %c%02d%02d\n", + header ? header : "", sig->name, sig->email, + (unsigned)sig->when.time, sign, hours, mins); +} + +bool git_signature__equal(const git_signature *one, const git_signature *two) +{ + GIT_ASSERT_ARG(one); + GIT_ASSERT_ARG(two); + + return + git__strcmp(one->name, two->name) == 0 && + git__strcmp(one->email, two->email) == 0 && + one->when.time == two->when.time && + one->when.offset == two->when.offset && + one->when.sign == two->when.sign; +} + diff --git a/src/libgit2/signature.h b/src/libgit2/signature.h new file mode 100644 index 000000000..5c8270954 --- /dev/null +++ b/src/libgit2/signature.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_signature_h__ +#define INCLUDE_signature_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/signature.h" +#include "repository.h" +#include + +int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); +void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig); +bool git_signature__equal(const git_signature *one, const git_signature *two); + +int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); + +#endif diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c new file mode 100644 index 000000000..5fc01ac36 --- /dev/null +++ b/src/libgit2/stash.c @@ -0,0 +1,1110 @@ +/* + * 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 "repository.h" +#include "commit.h" +#include "tree.h" +#include "reflog.h" +#include "blob.h" +#include "git2/diff.h" +#include "git2/stash.h" +#include "git2/status.h" +#include "git2/checkout.h" +#include "git2/index.h" +#include "git2/transaction.h" +#include "git2/merge.h" +#include "index.h" +#include "signature.h" +#include "iterator.h" +#include "merge.h" +#include "diff.h" +#include "diff_generate.h" + +static int create_error(int error, const char *msg) +{ + git_error_set(GIT_ERROR_STASH, "cannot stash changes - %s", msg); + return error; +} + +static int retrieve_head(git_reference **out, git_repository *repo) +{ + int error = git_repository_head(out, repo); + + if (error == GIT_EUNBORNBRANCH) + return create_error(error, "you do not have the initial commit yet."); + + return error; +} + +static int append_abbreviated_oid(git_str *out, const git_oid *b_commit) +{ + char *formatted_oid; + + formatted_oid = git_oid_allocfmt(b_commit); + GIT_ERROR_CHECK_ALLOC(formatted_oid); + + git_str_put(out, formatted_oid, 7); + git__free(formatted_oid); + + return git_str_oom(out) ? -1 : 0; +} + +static int append_commit_description(git_str *out, git_commit *commit) +{ + const char *summary = git_commit_summary(commit); + GIT_ERROR_CHECK_ALLOC(summary); + + if (append_abbreviated_oid(out, git_commit_id(commit)) < 0) + return -1; + + git_str_putc(out, ' '); + git_str_puts(out, summary); + git_str_putc(out, '\n'); + + return git_str_oom(out) ? -1 : 0; +} + +static int retrieve_base_commit_and_message( + git_commit **b_commit, + git_str *stash_message, + git_repository *repo) +{ + git_reference *head = NULL; + int error; + + if ((error = retrieve_head(&head, repo)) < 0) + return error; + + if (strcmp("HEAD", git_reference_name(head)) == 0) + error = git_str_puts(stash_message, "(no branch): "); + else + error = git_str_printf( + stash_message, + "%s: ", + git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR)); + if (error < 0) + goto cleanup; + + if ((error = git_commit_lookup( + b_commit, repo, git_reference_target(head))) < 0) + goto cleanup; + + if ((error = append_commit_description(stash_message, *b_commit)) < 0) + goto cleanup; + +cleanup: + git_reference_free(head); + return error; +} + +static int build_tree_from_index( + git_tree **out, + git_repository *repo, + git_index *index) +{ + int error; + git_oid i_tree_oid; + + if ((error = git_index_write_tree_to(&i_tree_oid, index, repo)) < 0) + return error; + + return git_tree_lookup(out, repo, &i_tree_oid); +} + +static int commit_index( + git_commit **i_commit, + git_repository *repo, + git_index *index, + const git_signature *stasher, + const char *message, + const git_commit *parent) +{ + git_tree *i_tree = NULL; + git_oid i_commit_oid; + git_str msg = GIT_STR_INIT; + int error; + + if ((error = build_tree_from_index(&i_tree, repo, index)) < 0) + goto cleanup; + + if ((error = git_str_printf(&msg, "index on %s\n", message)) < 0) + goto cleanup; + + if ((error = git_commit_create( + &i_commit_oid, + git_index_owner(index), + NULL, + stasher, + stasher, + NULL, + git_str_cstr(&msg), + i_tree, + 1, + &parent)) < 0) + goto cleanup; + + error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid); + +cleanup: + git_tree_free(i_tree); + git_str_dispose(&msg); + return error; +} + +struct stash_update_rules { + bool include_changed; + bool include_untracked; + bool include_ignored; +}; + +/* + * Similar to git_index_add_bypath but able to operate on any + * index without making assumptions about the repository's index + */ +static int stash_to_index( + git_repository *repo, + git_index *index, + const char *path) +{ + git_index *repo_index = NULL; + git_index_entry entry = {{0}}; + struct stat st; + int error; + + if (!git_repository_is_bare(repo) && + (error = git_repository_index__weakptr(&repo_index, repo)) < 0) + return error; + + if ((error = git_blob__create_from_paths( + &entry.id, &st, repo, NULL, path, 0, true)) < 0) + return error; + + git_index_entry__init_from_stat(&entry, &st, + (repo_index == NULL || !repo_index->distrust_filemode)); + + entry.path = path; + + return git_index_add(index, &entry); +} + +static int stash_update_index_from_diff( + git_repository *repo, + git_index *index, + const git_diff *diff, + struct stash_update_rules *data) +{ + int error = 0; + size_t d, max_d = git_diff_num_deltas(diff); + + for (d = 0; !error && d < max_d; ++d) { + const char *add_path = NULL; + const git_diff_delta *delta = git_diff_get_delta(diff, d); + + switch (delta->status) { + case GIT_DELTA_IGNORED: + if (data->include_ignored) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_UNTRACKED: + if (data->include_untracked && + delta->new_file.mode != GIT_FILEMODE_TREE) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_ADDED: + case GIT_DELTA_MODIFIED: + if (data->include_changed) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_DELETED: + if (data->include_changed && + !git_index_find(NULL, index, delta->old_file.path)) + error = git_index_remove(index, delta->old_file.path, 0); + break; + + default: + /* Unimplemented */ + git_error_set( + GIT_ERROR_INVALID, + "cannot update index. Unimplemented status (%d)", + delta->status); + return -1; + } + + if (add_path != NULL) + error = stash_to_index(repo, index, add_path); + } + + return error; +} + +static int build_untracked_tree( + git_tree **tree_out, + git_repository *repo, + git_commit *i_commit, + uint32_t flags) +{ + git_index *i_index = NULL; + git_tree *i_tree = NULL; + git_diff *diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct stash_update_rules data = {0}; + int error; + + if ((error = git_index_new(&i_index)) < 0) + goto cleanup; + + if (flags & GIT_STASH_INCLUDE_UNTRACKED) { + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS; + data.include_untracked = true; + } + + if (flags & GIT_STASH_INCLUDE_IGNORED) { + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_RECURSE_IGNORED_DIRS; + data.include_ignored = true; + } + + if ((error = git_commit_tree(&i_tree, i_commit)) < 0) + goto cleanup; + + if ((error = git_diff_tree_to_workdir(&diff, repo, i_tree, &opts)) < 0) + goto cleanup; + + if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) + goto cleanup; + + error = build_tree_from_index(tree_out, repo, i_index); + +cleanup: + git_diff_free(diff); + git_tree_free(i_tree); + git_index_free(i_index); + return error; +} + +static int commit_untracked( + git_commit **u_commit, + git_repository *repo, + const git_signature *stasher, + const char *message, + git_commit *i_commit, + uint32_t flags) +{ + git_tree *u_tree = NULL; + git_oid u_commit_oid; + git_str msg = GIT_STR_INIT; + int error; + + if ((error = build_untracked_tree(&u_tree, repo, i_commit, flags)) < 0) + goto cleanup; + + if ((error = git_str_printf(&msg, "untracked files on %s\n", message)) < 0) + goto cleanup; + + if ((error = git_commit_create( + &u_commit_oid, + repo, + NULL, + stasher, + stasher, + NULL, + git_str_cstr(&msg), + u_tree, + 0, + NULL)) < 0) + goto cleanup; + + error = git_commit_lookup(u_commit, repo, &u_commit_oid); + +cleanup: + git_tree_free(u_tree); + git_str_dispose(&msg); + return error; +} + +static git_diff_delta *stash_delta_merge( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) +{ + /* Special case for stash: if a file is deleted in the index, but exists + * in the working tree, we need to stash the workdir copy for the workdir. + */ + if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) { + git_diff_delta *dup = git_diff__delta_dup(b, pool); + + if (dup) + dup->status = GIT_DELTA_MODIFIED; + return dup; + } + + return git_diff__merge_like_cgit(a, b, pool); +} + +static int build_workdir_tree( + git_tree **tree_out, + git_repository *repo, + git_index *i_index, + git_commit *b_commit) +{ + git_tree *b_tree = NULL; + git_diff *diff = NULL, *idx_to_wd = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct stash_update_rules data = {0}; + int error; + + opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED; + + if ((error = git_commit_tree(&b_tree, b_commit)) < 0) + goto cleanup; + + if ((error = git_diff_tree_to_index(&diff, repo, b_tree, i_index, &opts)) < 0 || + (error = git_diff_index_to_workdir(&idx_to_wd, repo, i_index, &opts)) < 0 || + (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0) + goto cleanup; + + data.include_changed = true; + + if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) + goto cleanup; + + error = build_tree_from_index(tree_out, repo, i_index); + +cleanup: + git_diff_free(idx_to_wd); + git_diff_free(diff); + git_tree_free(b_tree); + + return error; +} + +static int commit_worktree( + git_oid *w_commit_oid, + git_repository *repo, + const git_signature *stasher, + const char *message, + git_commit *i_commit, + git_commit *b_commit, + git_commit *u_commit) +{ + 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_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 = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0) + goto cleanup; + + error = git_commit_create( + w_commit_oid, + repo, + NULL, + stasher, + stasher, + NULL, + message, + w_tree, + u_commit ? 3 : 2, + parents); + +cleanup: + git_tree_free(w_tree); + git_index_free(i_index); + git_index_free(r_index); + return error; +} + +static int prepare_worktree_commit_message(git_str *out, const char *user_message) +{ + git_str buf = GIT_STR_INIT; + int error = 0; + + if (!user_message) { + git_str_printf(&buf, "WIP on %s", git_str_cstr(out)); + } else { + const char *colon; + + if ((colon = strchr(git_str_cstr(out), ':')) == NULL) + goto cleanup; + + git_str_puts(&buf, "On "); + git_str_put(&buf, git_str_cstr(out), colon - out->ptr); + git_str_printf(&buf, ": %s\n", user_message); + } + + if (git_str_oom(&buf)) { + error = -1; + goto cleanup; + } + + git_str_swap(out, &buf); + +cleanup: + git_str_dispose(&buf); + return error; +} + +static int update_reflog( + git_oid *w_commit_oid, + git_repository *repo, + const char *message) +{ + git_reference *stash; + int error; + + if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0) + return error; + + error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, message); + + git_reference_free(stash); + + return error; +} + +static int is_dirty_cb(const char *path, unsigned int status, void *payload) +{ + GIT_UNUSED(path); + GIT_UNUSED(status); + GIT_UNUSED(payload); + + return GIT_PASSTHROUGH; +} + +static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flags) +{ + int error; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + + if (flags & GIT_STASH_INCLUDE_UNTRACKED) + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + if (flags & GIT_STASH_INCLUDE_IGNORED) + opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); + + if (error == GIT_PASSTHROUGH) + return 0; + + if (!error) + return create_error(GIT_ENOTFOUND, "there is nothing to stash."); + + return error; +} + +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 (flags & GIT_STASH_INCLUDE_UNTRACKED) + opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; + if (flags & GIT_STASH_INCLUDE_IGNORED) + opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED; + + return git_checkout_tree(repo, (git_object *)commit, &opts); +} + +int git_stash_save( + git_oid *out, + git_repository *repo, + const git_signature *stasher, + const char *message, + uint32_t flags) +{ + git_index *index = NULL; + git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; + git_str msg = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(stasher); + + if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) + return error; + + if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) + goto cleanup; + + 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_str_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_str_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_str_cstr(&msg), + i_commit, b_commit, u_commit)) < 0) + goto cleanup; + + git_str_rtrim(&msg); + + if ((error = update_reflog(out, repo, git_str_cstr(&msg))) < 0) + goto cleanup; + + if ((error = reset_index_and_workdir(repo, (flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, + flags)) < 0) + goto cleanup; + +cleanup: + + git_str_dispose(&msg); + git_commit_free(i_commit); + git_commit_free(b_commit); + git_commit_free(u_commit); + git_index_free(index); + + return error; +} + +static int retrieve_stash_commit( + git_commit **commit, + git_repository *repo, + size_t index) +{ + git_reference *stash = NULL; + git_reflog *reflog = NULL; + int error; + size_t max; + const git_reflog_entry *entry; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + if (!max || index > max - 1) { + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); + goto cleanup; + } + + entry = git_reflog_entry_byindex(reflog, index); + if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0) + goto cleanup; + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +static int retrieve_stash_trees( + git_tree **out_stash_tree, + git_tree **out_base_tree, + git_tree **out_index_tree, + git_tree **out_index_parent_tree, + git_tree **out_untracked_tree, + git_commit *stash_commit) +{ + git_tree *stash_tree = NULL; + git_commit *base_commit = NULL; + git_tree *base_tree = NULL; + git_commit *index_commit = NULL; + git_tree *index_tree = NULL; + git_commit *index_parent_commit = NULL; + git_tree *index_parent_tree = NULL; + git_commit *untracked_commit = NULL; + git_tree *untracked_tree = NULL; + int error; + + if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0) + goto cleanup; + if ((error = git_commit_tree(&base_tree, base_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0) + goto cleanup; + if ((error = git_commit_tree(&index_tree, index_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0) + goto cleanup; + if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0) + goto cleanup; + + if (git_commit_parentcount(stash_commit) == 3) { + if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0) + goto cleanup; + if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0) + goto cleanup; + } + + *out_stash_tree = stash_tree; + *out_base_tree = base_tree; + *out_index_tree = index_tree; + *out_index_parent_tree = index_parent_tree; + *out_untracked_tree = untracked_tree; + +cleanup: + git_commit_free(untracked_commit); + git_commit_free(index_parent_commit); + git_commit_free(index_commit); + git_commit_free(base_commit); + if (error < 0) { + git_tree_free(stash_tree); + git_tree_free(base_tree); + git_tree_free(index_tree); + git_tree_free(index_parent_tree); + git_tree_free(untracked_tree); + } + return error; +} + +static int merge_indexes( + git_index **out, + git_repository *repo, + git_tree *ancestor_tree, + git_index *ours_index, + git_index *theirs_index) +{ + git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0) + goto done; + + error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); + +done: + git_iterator_free(ancestor); + git_iterator_free(ours); + git_iterator_free(theirs); + return error; +} + +static int merge_index_and_tree( + git_index **out, + git_repository *repo, + git_tree *ancestor_tree, + git_index *ours_index, + git_tree *theirs_tree) +{ + git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) + goto done; + + error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); + +done: + git_iterator_free(ancestor); + git_iterator_free(ours); + git_iterator_free(theirs); + return error; +} + +static void normalize_apply_options( + git_stash_apply_options *opts, + const git_stash_apply_options *given_apply_opts) +{ + if (given_apply_opts != NULL) { + memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options)); + } else { + git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT; + memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options)); + } + + opts->checkout_options.checkout_strategy |= GIT_CHECKOUT_NO_REFRESH; + + if (!opts->checkout_options.our_label) + opts->checkout_options.our_label = "Updated upstream"; + + if (!opts->checkout_options.their_label) + opts->checkout_options.their_label = "Stashed changes"; +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) +{ + return git_stash_apply_options_init(opts, version); +} +#endif + +#define NOTIFY_PROGRESS(opts, progress_type) \ + do { \ + if ((opts).progress_cb && \ + (error = (opts).progress_cb((progress_type), (opts).progress_payload))) { \ + error = (error < 0) ? error : -1; \ + goto cleanup; \ + } \ + } while(false); + +static int ensure_clean_index(git_repository *repo, git_index *index) +{ + git_tree *head_tree = NULL; + git_diff *index_diff = NULL; + int error = 0; + + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_diff_tree_to_index( + &index_diff, repo, head_tree, index, NULL)) < 0) + goto done; + + if (git_diff_num_deltas(index_diff) > 0) { + git_error_set(GIT_ERROR_STASH, "%" PRIuZ " uncommitted changes exist in the index", + git_diff_num_deltas(index_diff)); + error = GIT_EUNCOMMITTED; + } + +done: + git_diff_free(index_diff); + git_tree_free(head_tree); + return error; +} + +static int stage_new_file(const git_index_entry **entries, void *data) +{ + git_index *index = data; + + if(entries[0] == NULL) + return git_index_add(index, entries[1]); + else + return git_index_add(index, entries[0]); +} + +static int stage_new_files( + git_index **out, + git_tree *parent_tree, + git_tree *tree) +{ + git_iterator *iterators[2] = { NULL, NULL }; + git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; + git_index *index = NULL; + int error; + + if ((error = git_index_new(&index)) < 0 || + (error = git_iterator_for_tree( + &iterators[0], parent_tree, &iterator_options)) < 0 || + (error = git_iterator_for_tree( + &iterators[1], tree, &iterator_options)) < 0) + goto done; + + error = git_iterator_walk(iterators, 2, stage_new_file, index); + +done: + if (error < 0) + git_index_free(index); + else + *out = index; + + git_iterator_free(iterators[0]); + git_iterator_free(iterators[1]); + + return error; +} + +int git_stash_apply( + git_repository *repo, + size_t index, + const git_stash_apply_options *given_opts) +{ + git_stash_apply_options opts; + unsigned int checkout_strategy; + git_commit *stash_commit = NULL; + git_tree *stash_tree = NULL; + git_tree *stash_parent_tree = NULL; + git_tree *index_tree = NULL; + git_tree *index_parent_tree = NULL; + git_tree *untracked_tree = NULL; + git_index *stash_adds = NULL; + git_index *repo_index = NULL; + git_index *unstashed_index = NULL; + git_index *modified_index = NULL; + git_index *untracked_index = NULL; + int error; + + GIT_ERROR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options"); + + normalize_apply_options(&opts, given_opts); + checkout_strategy = opts.checkout_options.checkout_strategy; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH); + + /* Retrieve commit corresponding to the given stash */ + if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0) + goto cleanup; + + /* Retrieve all trees in the stash */ + if ((error = retrieve_stash_trees( + &stash_tree, &stash_parent_tree, &index_tree, + &index_parent_tree, &untracked_tree, stash_commit)) < 0) + goto cleanup; + + /* Load repo index */ + if ((error = git_repository_index(&repo_index, repo)) < 0) + goto cleanup; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX); + + if ((error = ensure_clean_index(repo, repo_index)) < 0) + goto cleanup; + + /* Restore index if required */ + if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) && + git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) { + + if ((error = merge_index_and_tree( + &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0) + goto cleanup; + + if (git_index_has_conflicts(unstashed_index)) { + error = GIT_ECONFLICT; + goto cleanup; + } + + /* Otherwise, stage any new files in the stash tree. (Note: their + * previously unstaged contents are staged, not the previously staged.) + */ + } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) { + if ((error = stage_new_files( + &stash_adds, stash_parent_tree, stash_tree)) < 0 || + (error = merge_indexes( + &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0) + goto cleanup; + } + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED); + + /* Restore modified files in workdir */ + if ((error = merge_index_and_tree( + &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0) + goto cleanup; + + /* If applicable, restore untracked / ignored files in workdir */ + if (untracked_tree) { + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED); + + if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0) + goto cleanup; + } + + if (untracked_index) { + opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED); + + if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0) + goto cleanup; + + opts.checkout_options.checkout_strategy = checkout_strategy; + } + + + /* If there are conflicts in the modified index, then we need to actually + * check that out as the repo's index. Otherwise, we don't update the + * index. + */ + + if (!git_index_has_conflicts(modified_index)) + opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + + /* Check out the modified index using the existing repo index as baseline, + * so that existing modifications in the index can be rewritten even when + * checking out safely. + */ + opts.checkout_options.baseline_index = repo_index; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED); + + if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0) + goto cleanup; + + if (unstashed_index && !git_index_has_conflicts(modified_index)) { + if ((error = git_index_read_index(repo_index, unstashed_index)) < 0) + goto cleanup; + } + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE); + + error = git_index_write(repo_index); + +cleanup: + git_index_free(untracked_index); + git_index_free(modified_index); + git_index_free(unstashed_index); + git_index_free(stash_adds); + git_index_free(repo_index); + git_tree_free(untracked_tree); + git_tree_free(index_parent_tree); + git_tree_free(index_tree); + git_tree_free(stash_parent_tree); + git_tree_free(stash_tree); + git_commit_free(stash_commit); + return error; +} + +int git_stash_foreach( + git_repository *repo, + git_stash_cb callback, + void *payload) +{ + git_reference *stash; + git_reflog *reflog = NULL; + int error; + size_t i, max; + const git_reflog_entry *entry; + + error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE); + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + if (error < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + for (i = 0; i < max; i++) { + entry = git_reflog_entry_byindex(reflog, i); + + error = callback(i, + git_reflog_entry_message(entry), + git_reflog_entry_id_new(entry), + payload); + + if (error) { + git_error_set_after_callback(error); + break; + } + } + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +int git_stash_drop( + git_repository *repo, + size_t index) +{ + git_transaction *tx; + git_reference *stash = NULL; + git_reflog *reflog = NULL; + size_t max; + int error; + + if ((error = git_transaction_new(&tx, repo)) < 0) + return error; + + if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + + if (!max || index > max - 1) { + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); + goto cleanup; + } + + if ((error = git_reflog_drop(reflog, index, true)) < 0) + goto cleanup; + + if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0) + goto cleanup; + + if (max == 1) { + if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + } else if (index == 0) { + const git_reflog_entry *entry; + + entry = git_reflog_entry_byindex(reflog, 0); + if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0) + goto cleanup; + } + + error = git_transaction_commit(tx); + +cleanup: + git_reference_free(stash); + git_transaction_free(tx); + git_reflog_free(reflog); + return error; +} + +int git_stash_pop( + git_repository *repo, + size_t index, + const git_stash_apply_options *options) +{ + int error; + + if ((error = git_stash_apply(repo, index, options)) < 0) + return error; + + return git_stash_drop(repo, index); +} diff --git a/src/libgit2/status.c b/src/libgit2/status.c new file mode 100644 index 000000000..df0f74507 --- /dev/null +++ b/src/libgit2/status.c @@ -0,0 +1,584 @@ +/* + * 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 "status.h" + +#include "git2.h" +#include "futils.h" +#include "hash.h" +#include "vector.h" +#include "tree.h" +#include "git2/status.h" +#include "repository.h" +#include "ignore.h" +#include "index.h" +#include "wildmatch.h" + +#include "git2/diff.h" +#include "diff.h" +#include "diff_generate.h" + +static unsigned int index_delta2status(const git_diff_delta *head2idx) +{ + git_status_t st = GIT_STATUS_CURRENT; + + switch (head2idx->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + st = GIT_STATUS_INDEX_NEW; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_INDEX_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_INDEX_MODIFIED; + break; + case GIT_DELTA_RENAMED: + st = GIT_STATUS_INDEX_RENAMED; + + if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id)) + st |= GIT_STATUS_INDEX_MODIFIED; + break; + case GIT_DELTA_TYPECHANGE: + st = GIT_STATUS_INDEX_TYPECHANGE; + break; + case GIT_DELTA_CONFLICTED: + st = GIT_STATUS_CONFLICTED; + break; + default: + break; + } + + return st; +} + +static unsigned int workdir_delta2status( + git_diff *diff, git_diff_delta *idx2wd) +{ + git_status_t st = GIT_STATUS_CURRENT; + + switch (idx2wd->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + case GIT_DELTA_UNTRACKED: + st = GIT_STATUS_WT_NEW; + break; + case GIT_DELTA_UNREADABLE: + st = GIT_STATUS_WT_UNREADABLE; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_WT_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_WT_MODIFIED; + break; + case GIT_DELTA_IGNORED: + st = GIT_STATUS_IGNORED; + break; + case GIT_DELTA_RENAMED: + st = GIT_STATUS_WT_RENAMED; + + if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) { + /* if OIDs don't match, we might need to calculate them now to + * discern between RENAMED vs RENAMED+MODIFIED + */ + 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_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)) + idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) + st |= GIT_STATUS_WT_MODIFIED; + } + break; + case GIT_DELTA_TYPECHANGE: + st = GIT_STATUS_WT_TYPECHANGE; + break; + case GIT_DELTA_CONFLICTED: + st = GIT_STATUS_CONFLICTED; + break; + default: + break; + } + + return st; +} + +static bool status_is_included( + git_status_list *status, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES)) + return 1; + + /* if excluding submodules and this is a submodule everywhere */ + if (head2idx) { + if (head2idx->status != GIT_DELTA_ADDED && + head2idx->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (head2idx->status != GIT_DELTA_DELETED && + head2idx->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; + } + if (idx2wd) { + if (idx2wd->status != GIT_DELTA_ADDED && + idx2wd->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (idx2wd->status != GIT_DELTA_DELETED && + idx2wd->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; + } + + /* only get here if every valid mode is GIT_FILEMODE_COMMIT */ + return 0; +} + +static git_status_t status_compute( + git_status_list *status, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + git_status_t st = GIT_STATUS_CURRENT; + + if (head2idx) + st |= index_delta2status(head2idx); + + if (idx2wd) + st |= workdir_delta2status(status->idx2wd, idx2wd); + + return st; +} + +static int status_collect( + git_diff_delta *head2idx, + git_diff_delta *idx2wd, + void *payload) +{ + git_status_list *status = payload; + git_status_entry *status_entry; + + if (!status_is_included(status, head2idx, idx2wd)) + return 0; + + status_entry = git__malloc(sizeof(git_status_entry)); + GIT_ERROR_CHECK_ALLOC(status_entry); + + status_entry->status = status_compute(status, head2idx, idx2wd); + status_entry->head_to_index = head2idx; + status_entry->index_to_workdir = idx2wd; + + return git_vector_insert(&status->paired, status_entry); +} + +GIT_INLINE(int) status_entry_cmp_base( + const void *a, + const void *b, + int (*strcomp)(const char *a, const char *b)) +{ + const git_status_entry *entry_a = a; + const git_status_entry *entry_b = b; + const git_diff_delta *delta_a, *delta_b; + + delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir : + entry_a->head_to_index; + delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir : + entry_b->head_to_index; + + if (!delta_a && delta_b) + return -1; + if (delta_a && !delta_b) + return 1; + if (!delta_a && !delta_b) + return 0; + + return strcomp(delta_a->new_file.path, delta_b->new_file.path); +} + +static int status_entry_icmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcasecmp); +} + +static int status_entry_cmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcmp); +} + +static git_status_list *git_status_list_alloc(git_index *index) +{ + git_status_list *status = NULL; + int (*entrycmp)(const void *a, const void *b); + + if (!(status = git__calloc(1, sizeof(git_status_list)))) + return NULL; + + entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; + + if (git_vector_init(&status->paired, 0, entrycmp) < 0) { + git__free(status); + return NULL; + } + + return status; +} + +static int status_validate_options(const git_status_options *opts) +{ + if (!opts) + return 0; + + GIT_ERROR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); + + if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) { + git_error_set(GIT_ERROR_INVALID, "unknown status 'show' option"); + return -1; + } + + if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 && + (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) { + git_error_set(GIT_ERROR_INVALID, "updating index from status " + "is not allowed when index refresh is disabled"); + return -1; + } + + return 0; +} + +int git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts) +{ + git_index *index = NULL; + git_status_list *status = NULL; + git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT; + git_tree *head = NULL; + git_status_show_t show = + opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + int error = 0; + unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; + + *out = NULL; + + if (status_validate_options(opts) < 0) + return -1; + + 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 { + /* if there is no HEAD, that's okay - we'll make an empty iterator */ + if ((error = git_repository_head_tree(&head, repo)) < 0) { + if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) + goto done; + git_error_clear(); + } + } + + /* refresh index from disk unless prevented */ + if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 && + git_index_read_safely(index) < 0) + git_error_clear(); + + status = git_status_list_alloc(index); + GIT_ERROR_CHECK_ALLOC(status); + + if (opts) { + memcpy(&status->opts, opts, sizeof(git_status_options)); + memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + } + + diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED; + + if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; + if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; + if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; + if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS; + if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; + if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED; + + if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) + findopt.flags = findopt.flags | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES | + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; + + if (opts != NULL && opts->rename_threshold != 0) + findopt.rename_threshold = opts->rename_threshold; + + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { + if ((error = git_diff_tree_to_index( + &status->head2idx, repo, head, index, &diffopt)) < 0) + goto done; + + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && + (error = git_diff_find_similar(status->head2idx, &findopt)) < 0) + goto done; + } + + if (show != GIT_STATUS_SHOW_INDEX_ONLY) { + if ((error = git_diff_index_to_workdir( + &status->idx2wd, repo, index, &diffopt)) < 0) { + goto done; + } + + if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && + (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) + goto done; + } + + error = git_diff__paired_foreach( + status->head2idx, status->idx2wd, status_collect, status); + if (error < 0) + goto done; + + if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_cmp); + if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_icmp); + + if ((flags & + (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY | + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0) + git_vector_sort(&status->paired); + +done: + if (error < 0) { + git_status_list_free(status); + status = NULL; + } + + *out = status; + + if (opts == NULL || opts->baseline != head) + git_tree_free(head); + git_index_free(index); + + return error; +} + +size_t git_status_list_entrycount(git_status_list *status) +{ + GIT_ASSERT_ARG_WITH_RETVAL(status, 0); + + return status->paired.length; +} + +const git_status_entry *git_status_byindex(git_status_list *status, size_t i) +{ + GIT_ASSERT_ARG_WITH_RETVAL(status, NULL); + + return git_vector_get(&status->paired, i); +} + +void git_status_list_free(git_status_list *status) +{ + if (status == NULL) + return; + + git_diff_free(status->head2idx); + git_diff_free(status->idx2wd); + + git_vector_free_deep(&status->paired); + + git__memzero(status, sizeof(*status)); + git__free(status); +} + +int git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb cb, + void *payload) +{ + git_status_list *status; + const git_status_entry *status_entry; + size_t i; + int error = 0; + + if ((error = git_status_list_new(&status, repo, opts)) < 0) { + return error; + } + + git_vector_foreach(&status->paired, i, status_entry) { + const char *path = status_entry->head_to_index ? + status_entry->head_to_index->old_file.path : + status_entry->index_to_workdir->old_file.path; + + if ((error = cb(path, status_entry->status, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + git_status_list_free(status); + + return error; +} + +int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload) +{ + return git_status_foreach_ext(repo, NULL, cb, payload); +} + +struct status_file_info { + char *expected; + unsigned int count; + unsigned int status; + int wildmatch_flags; + int ambiguous; +}; + +static int get_one_status(const char *path, unsigned int status, void *data) +{ + struct status_file_info *sfi = data; + int (*strcomp)(const char *a, const char *b); + + sfi->count++; + sfi->status = status; + + strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp; + + if (sfi->count > 1 || + (strcomp(sfi->expected, path) != 0 && + wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0)) + { + sfi->ambiguous = true; + return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */ + } + + return 0; +} + +int git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path) +{ + int error; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_file_info sfi = {0}; + git_index *index; + + GIT_ASSERT_ARG(status_flags); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(path); + + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + return error; + + if ((sfi.expected = git__strdup(path)) == NULL) + return -1; + if (index->ignore_case) + sfi.wildmatch_flags = WM_CASEFOLD; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = 1; + opts.pathspec.strings = &sfi.expected; + + error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); + + if (error < 0 && sfi.ambiguous) { + git_error_set(GIT_ERROR_INVALID, + "ambiguous path '%s' given to git_status_file", sfi.expected); + error = GIT_EAMBIGUOUS; + } + + if (!error && !sfi.count) { + git_error_set(GIT_ERROR_INVALID, + "attempt to get status of nonexistent file '%s'", path); + error = GIT_ENOTFOUND; + } + + *status_flags = sfi.status; + + git__free(sfi.expected); + + return error; +} + +int git_status_should_ignore( + int *ignored, + git_repository *repo, + const char *path) +{ + return git_ignore_path_is_ignored(ignored, repo, path); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_status_init_options(git_status_options *opts, unsigned int version) +{ + return git_status_options_init(opts, version); +} +#endif + +int git_status_list_get_perfdata( + git_diff_perfdata *out, const git_status_list *status) +{ + GIT_ASSERT_ARG(out); + + GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); + + out->stat_calls = 0; + out->oid_calculations = 0; + + if (status->head2idx) { + out->stat_calls += status->head2idx->perf.stat_calls; + out->oid_calculations += status->head2idx->perf.oid_calculations; + } + if (status->idx2wd) { + out->stat_calls += status->idx2wd->perf.stat_calls; + out->oid_calculations += status->idx2wd->perf.oid_calculations; + } + + return 0; +} + diff --git a/src/libgit2/status.h b/src/libgit2/status.h new file mode 100644 index 000000000..907479a22 --- /dev/null +++ b/src/libgit2/status.h @@ -0,0 +1,25 @@ +/* + * 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_status_h__ +#define INCLUDE_status_h__ + +#include "common.h" + +#include "diff.h" +#include "git2/status.h" +#include "git2/diff.h" + +struct git_status_list { + git_status_options opts; + + git_diff *head2idx; + git_diff *idx2wd; + + git_vector paired; +}; + +#endif diff --git a/src/libgit2/strarray.c b/src/libgit2/strarray.c new file mode 100644 index 000000000..2f9b77cc2 --- /dev/null +++ b/src/libgit2/strarray.c @@ -0,0 +1,64 @@ +/* + * 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 "util.h" + +#include "common.h" + +int git_strarray_copy(git_strarray *tgt, const git_strarray *src) +{ + size_t i; + + GIT_ASSERT_ARG(tgt); + GIT_ASSERT_ARG(src); + + memset(tgt, 0, sizeof(*tgt)); + + if (!src->count) + return 0; + + tgt->strings = git__calloc(src->count, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(tgt->strings); + + for (i = 0; i < src->count; ++i) { + if (!src->strings[i]) + continue; + + tgt->strings[tgt->count] = git__strdup(src->strings[i]); + if (!tgt->strings[tgt->count]) { + git_strarray_dispose(tgt); + memset(tgt, 0, sizeof(*tgt)); + return -1; + } + + tgt->count++; + } + + return 0; +} + +void git_strarray_dispose(git_strarray *array) +{ + size_t i; + + if (array == NULL) + return; + + for (i = 0; i < array->count; ++i) + git__free(array->strings[i]); + + git__free(array->strings); + + memset(array, 0, sizeof(*array)); +} + +#ifndef GIT_DEPRECATE_HARD +void git_strarray_free(git_strarray *array) +{ + git_strarray_dispose(array); +} +#endif diff --git a/src/libgit2/stream.h b/src/libgit2/stream.h new file mode 100644 index 000000000..f16b026fb --- /dev/null +++ b/src/libgit2/stream.h @@ -0,0 +1,86 @@ +/* + * 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_stream_h__ +#define INCLUDE_stream_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +GIT_INLINE(int) git_stream_connect(git_stream *st) +{ + return st->connect(st); +} + +GIT_INLINE(int) git_stream_is_encrypted(git_stream *st) +{ + return st->encrypted; +} + +GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) +{ + if (!st->encrypted) { + git_error_set(GIT_ERROR_INVALID, "an unencrypted stream does not have a certificate"); + return -1; + } + + return st->certificate(out, st); +} + +GIT_INLINE(int) git_stream_supports_proxy(git_stream *st) +{ + return st->proxy_support; +} + +GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const git_proxy_options *proxy_opts) +{ + if (!st->proxy_support) { + git_error_set(GIT_ERROR_INVALID, "proxy not supported on this stream"); + return -1; + } + + return st->set_proxy(st, proxy_opts); +} + +GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) +{ + return st->read(st, data, len); +} + +GIT_INLINE(ssize_t) git_stream_write(git_stream *st, const char *data, size_t len, int flags) +{ + return st->write(st, data, len, flags); +} + +GIT_INLINE(int) git_stream__write_full(git_stream *st, const char *data, size_t len, int flags) +{ + size_t total_written = 0; + + while (total_written < len) { + ssize_t written = git_stream_write(st, data + total_written, len - total_written, flags); + if (written <= 0) + return -1; + + total_written += written; + } + + return 0; +} + +GIT_INLINE(int) git_stream_close(git_stream *st) +{ + return st->close(st); +} + +GIT_INLINE(void) git_stream_free(git_stream *st) +{ + if (!st) + return; + + st->free(st); +} + +#endif diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c new file mode 100644 index 000000000..0cf5c8af1 --- /dev/null +++ b/src/libgit2/streams/mbedtls.c @@ -0,0 +1,482 @@ +/* + * 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 "streams/mbedtls.h" + +#ifdef GIT_MBEDTLS + +#include + +#include "runtime.h" +#include "stream.h" +#include "streams/socket.h" +#include "netops.h" +#include "git2/transport.h" +#include "util.h" + +#ifndef GIT_DEFAULT_CERT_LOCATION +#define GIT_DEFAULT_CERT_LOCATION NULL +#endif + +/* Work around C90-conformance issues */ +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +# if defined(_MSC_VER) +# define inline __inline +# elif defined(__GNUC__) +# define inline __inline__ +# else +# define inline +# endif +#endif + +#include +#include +#include +#include +#include + +#undef inline + +#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" +#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30 + +static mbedtls_ssl_config *git__ssl_conf; +static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT]; +static mbedtls_entropy_context *mbedtls_entropy; + +/** + * This function aims to clean-up the SSL context which + * we allocated. + */ +static void shutdown_ssl(void) +{ + if (git__ssl_conf) { + mbedtls_x509_crt_free(git__ssl_conf->ca_chain); + git__free(git__ssl_conf->ca_chain); + mbedtls_ctr_drbg_free(git__ssl_conf->p_rng); + git__free(git__ssl_conf->p_rng); + mbedtls_ssl_config_free(git__ssl_conf); + git__free(git__ssl_conf); + git__ssl_conf = NULL; + } + if (mbedtls_entropy) { + mbedtls_entropy_free(mbedtls_entropy); + git__free(mbedtls_entropy); + mbedtls_entropy = NULL; + } +} + +int git_mbedtls_stream_global_init(void) +{ + int loaded = 0; + char *crtpath = GIT_DEFAULT_CERT_LOCATION; + struct stat statbuf; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + + size_t ciphers_known = 0; + char *cipher_name = NULL; + char *cipher_string = NULL; + char *cipher_string_tmp = NULL; + + git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config)); + GIT_ERROR_CHECK_ALLOC(git__ssl_conf); + + mbedtls_ssl_config_init(git__ssl_conf); + if (mbedtls_ssl_config_defaults(git__ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT) != 0) { + git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS"); + goto cleanup; + } + + /* configure TLSv1 */ + mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); + + /* verify_server_cert is responsible for making the check. + * OPTIONAL because REQUIRED drops the certificate as soon as the check + * is made, so we can never see the certificate and override it. */ + mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + + /* set the list of allowed ciphersuites */ + ciphers_known = 0; + cipher_string = cipher_string_tmp = git__strdup(GIT_SSL_DEFAULT_CIPHERS); + GIT_ERROR_CHECK_ALLOC(cipher_string); + + while ((cipher_name = git__strtok(&cipher_string_tmp, ":")) != NULL) { + int cipherid = mbedtls_ssl_get_ciphersuite_id(cipher_name); + if (cipherid == 0) continue; + + if (ciphers_known >= ARRAY_SIZE(ciphers_list)) { + git_error_set(GIT_ERROR_SSL, "out of cipher list space"); + goto cleanup; + } + + ciphers_list[ciphers_known++] = cipherid; + } + git__free(cipher_string); + + if (!ciphers_known) { + git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); + goto cleanup; + } + mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); + + /* Seeding the random number generator */ + mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context)); + GIT_ERROR_CHECK_ALLOC(mbedtls_entropy); + + mbedtls_entropy_init(mbedtls_entropy); + + ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context)); + GIT_ERROR_CHECK_ALLOC(ctr_drbg); + + mbedtls_ctr_drbg_init(ctr_drbg); + + if (mbedtls_ctr_drbg_seed(ctr_drbg, + mbedtls_entropy_func, + mbedtls_entropy, NULL, 0) != 0) { + git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool"); + goto cleanup; + } + + mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); + + /* load default certificates */ + if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) + loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0); + if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) + loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0); + + return git_runtime_shutdown_register(shutdown_ssl); + +cleanup: + mbedtls_ctr_drbg_free(ctr_drbg); + git__free(ctr_drbg); + mbedtls_ssl_config_free(git__ssl_conf); + git__free(git__ssl_conf); + git__ssl_conf = NULL; + + return -1; +} + +static int bio_read(void *b, unsigned char *buf, size_t len) +{ + git_stream *io = (git_stream *) b; + return (int) git_stream_read(io, buf, min(len, INT_MAX)); +} + +static int bio_write(void *b, const unsigned char *buf, size_t len) +{ + git_stream *io = (git_stream *) b; + return (int) git_stream_write(io, (const char *)buf, min(len, INT_MAX), 0); +} + +static int ssl_set_error(mbedtls_ssl_context *ssl, int error) +{ + char errbuf[512]; + int ret = -1; + + GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_READ); + GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_WRITE); + + if (error != 0) + mbedtls_strerror( error, errbuf, 512 ); + + switch(error) { + case 0: + git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); + break; + + case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); + ret = GIT_ECERTIFICATE; + break; + + default: + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); + } + + return ret; +} + +static int ssl_teardown(mbedtls_ssl_context *ssl) +{ + int ret = 0; + + ret = mbedtls_ssl_close_notify(ssl); + if (ret < 0) + ret = ssl_set_error(ssl, ret); + + mbedtls_ssl_free(ssl); + return ret; +} + +static int verify_server_cert(mbedtls_ssl_context *ssl) +{ + int ret = -1; + + if ((ret = mbedtls_ssl_get_verify_result(ssl)) != 0) { + char vrfy_buf[512]; + int len = mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "", ret); + if (len >= 1) vrfy_buf[len - 1] = '\0'; /* Remove trailing \n */ + git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid: %#04x - %s", ret, vrfy_buf); + return GIT_ECERTIFICATE; + } + + return 0; +} + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + bool connected; + char *host; + mbedtls_ssl_context *ssl; + git_cert_x509 cert_info; +} mbedtls_stream; + + +static int mbedtls_connect(git_stream *stream) +{ + int ret; + mbedtls_stream *st = (mbedtls_stream *) stream; + + if (st->owned && (ret = git_stream_connect(st->io)) < 0) + return ret; + + st->connected = true; + + mbedtls_ssl_set_hostname(st->ssl, st->host); + + mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL); + + if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0) + return ssl_set_error(st->ssl, ret); + + return verify_server_cert(st->ssl); +} + +static int mbedtls_certificate(git_cert **out, git_stream *stream) +{ + unsigned char *encoded_cert; + mbedtls_stream *st = (mbedtls_stream *) stream; + + const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(st->ssl); + if (!cert) { + git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); + return -1; + } + + /* Retrieve the length of the certificate first */ + if (cert->raw.len == 0) { + git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); + return -1; + } + + encoded_cert = git__malloc(cert->raw.len); + GIT_ERROR_CHECK_ALLOC(encoded_cert); + memcpy(encoded_cert, cert->raw.p, cert->raw.len); + + st->cert_info.parent.cert_type = GIT_CERT_X509; + st->cert_info.data = encoded_cert; + st->cert_info.len = cert->raw.len; + + *out = &st->cert_info.parent; + + return 0; +} + +static int mbedtls_set_proxy(git_stream *stream, const git_proxy_options *proxy_options) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + + return git_stream_set_proxy(st->io, proxy_options); +} + +static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t len, int flags) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + int written; + + GIT_UNUSED(flags); + + /* + * `mbedtls_ssl_write` can only represent INT_MAX bytes + * written via its return value. We thus need to clamp + * the maximum number of bytes written. + */ + len = min(len, INT_MAX); + + if ((written = mbedtls_ssl_write(st->ssl, (const unsigned char *)data, len)) <= 0) + return ssl_set_error(st->ssl, written); + + return written; +} + +static ssize_t mbedtls_stream_read(git_stream *stream, void *data, size_t len) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + int ret; + + if ((ret = mbedtls_ssl_read(st->ssl, (unsigned char *)data, len)) <= 0) + ssl_set_error(st->ssl, ret); + + return ret; +} + +static int mbedtls_stream_close(git_stream *stream) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + int ret = 0; + + if (st->connected && (ret = ssl_teardown(st->ssl)) != 0) + return -1; + + st->connected = false; + + return st->owned ? git_stream_close(st->io) : 0; +} + +static void mbedtls_stream_free(git_stream *stream) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + + if (st->owned) + git_stream_free(st->io); + + git__free(st->host); + git__free(st->cert_info.data); + mbedtls_ssl_free(st->ssl); + git__free(st->ssl); + git__free(st); +} + +static int mbedtls_stream_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + mbedtls_stream *st; + int error; + + st = git__calloc(1, sizeof(mbedtls_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + st->ssl = git__malloc(sizeof(mbedtls_ssl_context)); + GIT_ERROR_CHECK_ALLOC(st->ssl); + mbedtls_ssl_init(st->ssl); + if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) { + git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); + error = -1; + goto out_err; + } + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = mbedtls_connect; + st->parent.certificate = mbedtls_certificate; + st->parent.set_proxy = mbedtls_set_proxy; + st->parent.read = mbedtls_stream_read; + st->parent.write = mbedtls_stream_write; + st->parent.close = mbedtls_stream_close; + st->parent.free = mbedtls_stream_free; + + *out = (git_stream *) st; + return 0; + +out_err: + mbedtls_ssl_free(st->ssl); + git_stream_close(st->io); + git_stream_free(st->io); + git__free(st); + + return error; +} + +int git_mbedtls_stream_wrap( + git_stream **out, + git_stream *in, + const char *host) +{ + return mbedtls_stream_wrap(out, in, host, 0); +} + +int git_mbedtls_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + git_stream *stream; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_socket_stream_new(&stream, host, port)) < 0) + return error; + + if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +int git_mbedtls__set_cert_location(const char *file, const char *path) +{ + int ret = 0; + char errbuf[512]; + mbedtls_x509_crt *cacert; + + GIT_ASSERT_ARG(file || path); + + cacert = git__malloc(sizeof(mbedtls_x509_crt)); + GIT_ERROR_CHECK_ALLOC(cacert); + + mbedtls_x509_crt_init(cacert); + if (file) + ret = mbedtls_x509_crt_parse_file(cacert, file); + if (ret >= 0 && path) + ret = mbedtls_x509_crt_parse_path(cacert, path); + /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */ + if (ret < 0) { + mbedtls_x509_crt_free(cacert); + git__free(cacert); + mbedtls_strerror( ret, errbuf, 512 ); + git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); + return -1; + } + + mbedtls_x509_crt_free(git__ssl_conf->ca_chain); + git__free(git__ssl_conf->ca_chain); + mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); + + return 0; +} + +#else + +#include "stream.h" + +int git_mbedtls_stream_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/libgit2/streams/mbedtls.h b/src/libgit2/streams/mbedtls.h new file mode 100644 index 000000000..bcca6dd40 --- /dev/null +++ b/src/libgit2/streams/mbedtls.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_steams_mbedtls_h__ +#define INCLUDE_steams_mbedtls_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +extern int git_mbedtls_stream_global_init(void); + +#ifdef GIT_MBEDTLS +extern int git_mbedtls__set_cert_location(const char *file, const char *path); + +extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port); +extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host); +#endif + +#endif diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c new file mode 100644 index 000000000..89c96780c --- /dev/null +++ b/src/libgit2/streams/openssl.c @@ -0,0 +1,747 @@ +/* + * 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 "streams/openssl.h" +#include "streams/openssl_legacy.h" +#include "streams/openssl_dynamic.h" + +#ifdef GIT_OPENSSL + +#include + +#include "common.h" +#include "runtime.h" +#include "settings.h" +#include "posix.h" +#include "stream.h" +#include "streams/socket.h" +#include "netops.h" +#include "git2/transport.h" +#include "git2/sys/openssl.h" + +#ifndef GIT_WIN32 +# include +# include +# include +#endif + +#ifndef GIT_OPENSSL_DYNAMIC +# include +# include +# include +# include +#endif + +SSL_CTX *git__ssl_ctx; + +#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" + + +static BIO_METHOD *git_stream_bio_method; +static int init_bio_method(void); + +/** + * This function aims to clean-up the SSL context which + * we allocated. + */ +static void shutdown_ssl(void) +{ + if (git_stream_bio_method) { + BIO_meth_free(git_stream_bio_method); + git_stream_bio_method = NULL; + } + + if (git__ssl_ctx) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } +} + +#ifdef VALGRIND +# if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) + +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); + git__free(mem); +} +# else /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ +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) +{ + git__free(mem); +} +# endif /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ +#endif /* VALGRIND */ + +static int openssl_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 to avoid spurious valgrind warnings. + * Don't error on failure; many builds of OpenSSL do not + * allow you to set these functions. + */ + if (!allocators_initialized) { + CRYPTO_set_mem_functions(git_openssl_malloc, + git_openssl_realloc, + git_openssl_free); + allocators_initialized = true; + } +#endif + + OPENSSL_init_ssl(0, NULL); + + /* + * Load SSLv{2,3} and TLSv1 so that we can talk with servers + * which use the SSL hellos, which are often used for + * compatibility. We then disable SSL so we only allow OpenSSL + * to speak TLSv1 to perform the encryption itself. + */ + if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method()))) + goto error; + + SSL_CTX_set_options(git__ssl_ctx, ssl_opts); + SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) + goto error; + + if (!ciphers) + ciphers = GIT_SSL_DEFAULT_CIPHERS; + + if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) + goto error; + + if (init_bio_method() < 0) + goto error; + + return git_runtime_shutdown_register(shutdown_ssl); + +error: + git_error_set(GIT_ERROR_NET, "could not initialize openssl: %s", + ERR_error_string(ERR_get_error(), NULL)); + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + return -1; +} + +/* + * When we use dynamic loading, we defer OpenSSL initialization until + * it's first used. `openssl_ensure_initialized` will do the work + * under a mutex. + */ +git_mutex openssl_mutex; +bool openssl_initialized; + +int git_openssl_stream_global_init(void) +{ +#ifndef GIT_OPENSSL_DYNAMIC + return openssl_init(); +#else + if (git_mutex_init(&openssl_mutex) != 0) + return -1; + + return 0; +#endif +} + +static int openssl_ensure_initialized(void) +{ +#ifdef GIT_OPENSSL_DYNAMIC + int error = 0; + + if (git_mutex_lock(&openssl_mutex) != 0) + return -1; + + if (!openssl_initialized) { + if ((error = git_openssl_stream_dynamic_init()) == 0) + error = openssl_init(); + + openssl_initialized = true; + } + + error |= git_mutex_unlock(&openssl_mutex); + return error; + +#else + return 0; +#endif +} + +#if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) +int git_openssl_set_locking(void) +{ +# ifdef GIT_THREADS + return 0; +# else + git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); + return -1; +# endif +} +#endif + + +static int bio_create(BIO *b) +{ + BIO_set_init(b, 1); + BIO_set_data(b, NULL); + + return 1; +} + +static int bio_destroy(BIO *b) +{ + if (!b) + return 0; + + BIO_set_data(b, NULL); + + return 1; +} + +static int bio_read(BIO *b, char *buf, int len) +{ + git_stream *io = (git_stream *) BIO_get_data(b); + + return (int) git_stream_read(io, buf, 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); +} + +static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + GIT_UNUSED(b); + GIT_UNUSED(num); + GIT_UNUSED(ptr); + + if (cmd == BIO_CTRL_FLUSH) + return 1; + + return 0; +} + +static int bio_gets(BIO *b, char *buf, int len) +{ + GIT_UNUSED(b); + GIT_UNUSED(buf); + GIT_UNUSED(len); + return -1; +} + +static int bio_puts(BIO *b, const char *str) +{ + return bio_write(b, str, strlen(str)); +} + +static int init_bio_method(void) +{ + /* Set up the BIO_METHOD we use for wrapping our own stream implementations */ + git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream"); + GIT_ERROR_CHECK_ALLOC(git_stream_bio_method); + + BIO_meth_set_write(git_stream_bio_method, bio_write); + BIO_meth_set_read(git_stream_bio_method, bio_read); + BIO_meth_set_puts(git_stream_bio_method, bio_puts); + BIO_meth_set_gets(git_stream_bio_method, bio_gets); + BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl); + BIO_meth_set_create(git_stream_bio_method, bio_create); + BIO_meth_set_destroy(git_stream_bio_method, bio_destroy); + + return 0; +} + +static int ssl_set_error(SSL *ssl, int error) +{ + int err; + unsigned long e; + + err = SSL_get_error(ssl, error); + + GIT_ASSERT(err != SSL_ERROR_WANT_READ); + GIT_ASSERT(err != SSL_ERROR_WANT_WRITE); + + switch (err) { + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + git_error_set(GIT_ERROR_SSL, "SSL error: connection failure"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + git_error_set(GIT_ERROR_SSL, "SSL error: x509 error"); + break; + case SSL_ERROR_SYSCALL: + e = ERR_get_error(); + if (e > 0) { + char errmsg[256]; + ERR_error_string_n(e, errmsg, sizeof(errmsg)); + git_error_set(GIT_ERROR_NET, "SSL error: %s", errmsg); + break; + } else if (error < 0) { + git_error_set(GIT_ERROR_OS, "SSL error: syscall failure"); + break; + } + git_error_set(GIT_ERROR_SSL, "SSL error: received early EOF"); + return GIT_EEOF; + break; + case SSL_ERROR_SSL: + { + char errmsg[256]; + e = ERR_get_error(); + ERR_error_string_n(e, errmsg, sizeof(errmsg)); + git_error_set(GIT_ERROR_SSL, "SSL error: %s", errmsg); + break; + } + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + default: + git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); + break; + } + return -1; +} + +static int ssl_teardown(SSL *ssl) +{ + int ret; + + ret = SSL_shutdown(ssl); + if (ret < 0) + ret = ssl_set_error(ssl, ret); + else + ret = 0; + + return ret; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (gitno__match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(SSL *ssl, const char *host) +{ + X509 *cert = NULL; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr = NULL; + int i = -1, j, error = 0; + + if (SSL_get_verify_result(ssl) != X509_V_OK) { + git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid"); + return GIT_ECERTIFICATE; + } + + /* Try to parse the host as an IP address to see if it is */ + if (p_inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if (p_inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(ssl); + if (!cert) { + error = -1; + git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); + goto cleanup; + } + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = addr && !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto cert_fail_name; + + if (matched == 1) { + goto cleanup; + } + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GIT_ERROR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_get0_data(str), size); + peer_cn[size] = '\0'; + } else { + goto cert_fail_name; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GIT_ERROR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail_name; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail_name; + + goto cleanup; + +cert_fail_name: + error = GIT_ECERTIFICATE; + git_error_set(GIT_ERROR_SSL, "hostname does not match certificate"); + goto cleanup; + +on_error: + error = ssl_set_error(ssl, 0); + goto cleanup; + +cleanup: + X509_free(cert); + OPENSSL_free(peer_cn); + return error; +} + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + bool connected; + char *host; + SSL *ssl; + git_cert_x509 cert_info; +} openssl_stream; + +static int openssl_connect(git_stream *stream) +{ + int ret; + BIO *bio; + openssl_stream *st = (openssl_stream *) stream; + + if (st->owned && (ret = git_stream_connect(st->io)) < 0) + return ret; + + bio = BIO_new(git_stream_bio_method); + GIT_ERROR_CHECK_ALLOC(bio); + + BIO_set_data(bio, st->io); + SSL_set_bio(st->ssl, bio, bio); + + /* specify the host in case SNI is needed */ +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_set_tlsext_host_name(st->ssl, st->host); +#endif + + if ((ret = SSL_connect(st->ssl)) <= 0) + return ssl_set_error(st->ssl, ret); + + st->connected = true; + + return verify_server_cert(st->ssl, st->host); +} + +static int openssl_certificate(git_cert **out, git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + X509 *cert = SSL_get_peer_certificate(st->ssl); + unsigned char *guard, *encoded_cert = NULL; + int error, len; + + /* Retrieve the length of the certificate first */ + len = i2d_X509(cert, NULL); + if (len < 0) { + git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); + error = -1; + goto out; + } + + encoded_cert = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(encoded_cert); + /* i2d_X509 makes 'guard' point to just after the data */ + guard = encoded_cert; + + len = i2d_X509(cert, &guard); + if (len < 0) { + git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); + error = -1; + goto out; + } + + st->cert_info.parent.cert_type = GIT_CERT_X509; + st->cert_info.data = encoded_cert; + st->cert_info.len = len; + encoded_cert = NULL; + + *out = &st->cert_info.parent; + error = 0; + +out: + git__free(encoded_cert); + X509_free(cert); + return error; +} + +static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) +{ + openssl_stream *st = (openssl_stream *) stream; + + return git_stream_set_proxy(st->io, proxy_opts); +} + +static ssize_t openssl_write(git_stream *stream, const char *data, size_t data_len, int flags) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret, len = min(data_len, INT_MAX); + + GIT_UNUSED(flags); + + if ((ret = SSL_write(st->ssl, data, len)) <= 0) + return ssl_set_error(st->ssl, ret); + + return ret; +} + +static ssize_t openssl_read(git_stream *stream, void *data, size_t len) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + + if ((ret = SSL_read(st->ssl, data, len)) <= 0) + return ssl_set_error(st->ssl, ret); + + return ret; +} + +static int openssl_close(git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + + if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) + return -1; + + st->connected = false; + + return st->owned ? git_stream_close(st->io) : 0; +} + +static void openssl_free(git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + + if (st->owned) + git_stream_free(st->io); + + SSL_free(st->ssl); + git__free(st->host); + git__free(st->cert_info.data); + git__free(st); +} + +static int openssl_stream_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + openssl_stream *st; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(host); + + st = git__calloc(1, sizeof(openssl_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + st->ssl = SSL_new(git__ssl_ctx); + if (st->ssl == NULL) { + git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); + git__free(st); + return -1; + } + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = openssl_connect; + st->parent.certificate = openssl_certificate; + st->parent.set_proxy = openssl_set_proxy; + st->parent.read = openssl_read; + st->parent.write = openssl_write; + st->parent.close = openssl_close; + st->parent.free = openssl_free; + + *out = (git_stream *) st; + return 0; +} + +int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host) +{ + if (openssl_ensure_initialized() < 0) + return -1; + + return openssl_stream_wrap(out, in, host, 0); +} + +int git_openssl_stream_new(git_stream **out, const char *host, const char *port) +{ + git_stream *stream = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if (openssl_ensure_initialized() < 0) + return -1; + + if ((error = git_socket_stream_new(&stream, host, port)) < 0) + return error; + + if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +int git_openssl__set_cert_location(const char *file, const char *path) +{ + if (openssl_ensure_initialized() < 0) + return -1; + + if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) { + char errmsg[256]; + + ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); + git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to load certificates: %s", + errmsg); + + return -1; + } + return 0; +} + +#else + +#include "stream.h" +#include "git2/sys/openssl.h" + +int git_openssl_stream_global_init(void) +{ + return 0; +} + +int git_openssl_set_locking(void) +{ + git_error_set(GIT_ERROR_SSL, "libgit2 was not built with OpenSSL support"); + return -1; +} + +#endif diff --git a/src/libgit2/streams/openssl.h b/src/libgit2/streams/openssl.h new file mode 100644 index 000000000..89fb60a82 --- /dev/null +++ b/src/libgit2/streams/openssl.h @@ -0,0 +1,31 @@ +/* + * 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_streams_openssl_h__ +#define INCLUDE_streams_openssl_h__ + +#include "common.h" +#include "streams/openssl_legacy.h" +#include "streams/openssl_dynamic.h" + +#include "git2/sys/stream.h" + +extern int git_openssl_stream_global_init(void); + +#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) +# include +# include +# include +# include +# endif + +#ifdef GIT_OPENSSL +extern int git_openssl__set_cert_location(const char *file, const char *path); +extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); +extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host); +#endif + +#endif diff --git a/src/libgit2/streams/openssl_dynamic.c b/src/libgit2/streams/openssl_dynamic.c new file mode 100644 index 000000000..da16b6ed7 --- /dev/null +++ b/src/libgit2/streams/openssl_dynamic.c @@ -0,0 +1,309 @@ +/* + * 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 "streams/openssl.h" +#include "streams/openssl_dynamic.h" + +#if defined(GIT_OPENSSL) && defined(GIT_OPENSSL_DYNAMIC) + +#include "runtime.h" + +#include + +unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); +const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); +int (*ASN1_STRING_length)(const ASN1_STRING *x); +int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); +int (*ASN1_STRING_type)(const ASN1_STRING *x); + +void *(*BIO_get_data)(BIO *a); +int (*BIO_get_new_index)(void); +int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); +void (*BIO_meth_free)(BIO_METHOD *biom); +int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); +int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); +int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); +int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); +int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); +int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); +int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); +BIO_METHOD *(*BIO_meth_new)(int type, const char *name); +BIO *(*BIO_new)(const BIO_METHOD *type); +void (*BIO_set_data)(BIO *a, void *ptr); +void (*BIO_set_init)(BIO *a, int init); + +void (*CRYPTO_free)(void *ptr, const char *file, int line); +void *(*CRYPTO_malloc)(size_t num, const char *file, int line); +int (*CRYPTO_num_locks)(void); +void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); +int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); +int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); +void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); + +char *(*ERR_error_string)(unsigned long e, char *buf); +void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); +unsigned long (*ERR_get_error)(void); + +int (*SSL_connect)(SSL *ssl); +long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); +void (*SSL_free)(SSL *ssl); +int (*SSL_get_error)(SSL *ssl, int ret); +X509 *(*SSL_get_peer_certificate)(const SSL *ssl); +long (*SSL_get_verify_result)(const SSL *ssl); +int (*SSL_library_init)(void); +void (*SSL_load_error_strings)(void); +SSL *(*SSL_new)(SSL_CTX *ctx); +int (*SSL_read)(SSL *ssl, const void *buf, int num); +void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); +int (*SSL_shutdown)(SSL *ssl); +int (*SSL_write)(SSL *ssl, const void *buf, int num); + +long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); +void (*SSL_CTX_free)(SSL_CTX *ctx); +SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); +int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); +int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); +long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); +void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); +int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); + +const SSL_METHOD *(*SSLv23_method)(void); +const SSL_METHOD *(*TLS_method)(void); + +ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); +X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); +int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); +void (*X509_free)(X509 *a); +void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); +X509_NAME *(*X509_get_subject_name)(const X509 *x); + +int (*i2d_X509)(X509 *a, unsigned char **ppout); + +int (*OPENSSL_sk_num)(const void *sk); +void *(*OPENSSL_sk_value)(const void *sk, int i); +void (*OPENSSL_sk_free)(void *sk); + +int (*sk_num)(const void *sk); +void *(*sk_value)(const void *sk, int i); +void (*sk_free)(void *sk); + +void *openssl_handle; + +GIT_INLINE(void *) openssl_sym(int *err, const char *name, bool required) +{ + void *symbol; + + /* if we've seen an err, noop to retain it */ + if (*err) + return NULL; + + + if ((symbol = dlsym(openssl_handle, name)) == NULL && required) { + const char *msg = dlerror(); + git_error_set(GIT_ERROR_SSL, "could not load ssl function '%s': %s", name, msg ? msg : "unknown error"); + *err = -1; + } + + return symbol; +} + +static void dynamic_shutdown(void) +{ + dlclose(openssl_handle); + openssl_handle = NULL; +} + +int git_openssl_stream_dynamic_init(void) +{ + int err = 0; + + if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL) { + git_error_set(GIT_ERROR_SSL, "could not load ssl libraries"); + return -1; + } + + ASN1_STRING_data = (unsigned char *(*)(ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_data", false); + ASN1_STRING_get0_data = (const unsigned char *(*)(const ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_get0_data", false); + ASN1_STRING_length = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_length", true); + ASN1_STRING_to_UTF8 = (int (*)(unsigned char **, const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_to_UTF8", true); + ASN1_STRING_type = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_type", true); + + BIO_get_data = (void *(*)(BIO *))openssl_sym(&err, "BIO_get_data", false); + BIO_get_new_index = (int (*)(void))openssl_sym(&err, "BIO_get_new_index", false); + BIO_meth_free = (void (*)(BIO_METHOD *))openssl_sym(&err, "BIO_meth_free", false); + BIO_meth_new = (BIO_METHOD *(*)(int, const char *))openssl_sym(&err, "BIO_meth_new", false); + BIO_meth_set_create = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_create", false); + BIO_meth_set_ctrl = (int (*)(BIO_METHOD *, long (*)(BIO *, int, long, void *)))openssl_sym(&err, "BIO_meth_set_ctrl", false); + BIO_meth_set_destroy = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_destroy", false); + BIO_meth_set_gets = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_gets", false); + BIO_meth_set_puts = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *)))openssl_sym(&err, "BIO_meth_set_puts", false); + BIO_meth_set_read = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_read", false); + BIO_meth_set_write = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *, int)))openssl_sym(&err, "BIO_meth_set_write", false); + BIO_new = (BIO *(*)(const BIO_METHOD *))openssl_sym(&err, "BIO_new", true); + BIO_set_data = (void (*)(BIO *a, void *))openssl_sym(&err, "BIO_set_data", false); + BIO_set_init = (void (*)(BIO *a, int))openssl_sym(&err, "BIO_set_init", false); + + CRYPTO_free = (void (*)(void *, const char *, int))openssl_sym(&err, "CRYPTO_free", true); + CRYPTO_malloc = (void *(*)(size_t, const char *, int))openssl_sym(&err, "CRYPTO_malloc", true); + CRYPTO_num_locks = (int (*)(void))openssl_sym(&err, "CRYPTO_num_locks", false); + CRYPTO_set_locking_callback = (void (*)(void (*)(int, int, const char *, int)))openssl_sym(&err, "CRYPTO_set_locking_callback", false); + CRYPTO_set_mem_functions = (int (*)(void *(*)(size_t), void *(*)(void *, size_t), void (*f)(void *)))openssl_sym(&err, "CRYPTO_set_mem_functions", true); + + CRYPTO_THREADID_set_callback = (int (*)(void (*)(CRYPTO_THREADID *)))openssl_sym(&err, "CRYPTO_THREADID_set_callback", false); + CRYPTO_THREADID_set_numeric = (void (*)(CRYPTO_THREADID *, unsigned long))openssl_sym(&err, "CRYPTO_THREADID_set_numeric", false); + + ERR_error_string = (char *(*)(unsigned long, char *))openssl_sym(&err, "ERR_error_string", true); + ERR_error_string_n = (void (*)(unsigned long, char *, size_t))openssl_sym(&err, "ERR_error_string_n", true); + ERR_get_error = (unsigned long (*)(void))openssl_sym(&err, "ERR_get_error", true); + + OPENSSL_init_ssl = (int (*)(uint64_t opts, const void *settings))openssl_sym(&err, "OPENSSL_init_ssl", false); + OPENSSL_sk_num = (int (*)(const void *))openssl_sym(&err, "OPENSSL_sk_num", false); + OPENSSL_sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "OPENSSL_sk_value", false); + OPENSSL_sk_free = (void (*)(void *))openssl_sym(&err, "OPENSSL_sk_free", false); + + sk_num = (int (*)(const void *))openssl_sym(&err, "sk_num", false); + sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "sk_value", false); + sk_free = (void (*)(void *))openssl_sym(&err, "sk_free", false); + + SSL_connect = (int (*)(SSL *))openssl_sym(&err, "SSL_connect", true); + SSL_ctrl = (long (*)(SSL *, int, long, void *))openssl_sym(&err, "SSL_ctrl", true); + SSL_get_peer_certificate = (X509 *(*)(const SSL *))openssl_sym(&err, "SSL_get_peer_certificate", true); + SSL_library_init = (int (*)(void))openssl_sym(&err, "SSL_library_init", false); + SSL_free = (void (*)(SSL *))openssl_sym(&err, "SSL_free", true); + SSL_get_error = (int (*)(SSL *, int))openssl_sym(&err, "SSL_get_error", true); + SSL_get_verify_result = (long (*)(const SSL *ssl))openssl_sym(&err, "SSL_get_verify_result", true); + SSL_load_error_strings = (void (*)(void))openssl_sym(&err, "SSL_load_error_strings", false); + SSL_new = (SSL *(*)(SSL_CTX *))openssl_sym(&err, "SSL_new", true); + SSL_read = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_read", true); + SSL_set_bio = (void (*)(SSL *, BIO *, BIO *))openssl_sym(&err, "SSL_set_bio", true); + SSL_shutdown = (int (*)(SSL *ssl))openssl_sym(&err, "SSL_shutdown", true); + SSL_write = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_write", true); + + SSL_CTX_ctrl = (long (*)(SSL_CTX *, int, long, void *))openssl_sym(&err, "SSL_CTX_ctrl", true); + SSL_CTX_free = (void (*)(SSL_CTX *))openssl_sym(&err, "SSL_CTX_free", true); + SSL_CTX_new = (SSL_CTX *(*)(const SSL_METHOD *))openssl_sym(&err, "SSL_CTX_new", true); + SSL_CTX_set_cipher_list = (int (*)(SSL_CTX *, const char *))openssl_sym(&err, "SSL_CTX_set_cipher_list", true); + SSL_CTX_set_default_verify_paths = (int (*)(SSL_CTX *ctx))openssl_sym(&err, "SSL_CTX_set_default_verify_paths", true); + SSL_CTX_set_options = (long (*)(SSL_CTX *, long))openssl_sym(&err, "SSL_CTX_set_options", false); + SSL_CTX_set_verify = (void (*)(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)))openssl_sym(&err, "SSL_CTX_set_verify", true); + SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *))openssl_sym(&err, "SSL_CTX_load_verify_locations", true); + + SSLv23_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "SSLv23_method", false); + TLS_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "TLS_method", false); + + X509_NAME_ENTRY_get_data = (ASN1_STRING *(*)(const X509_NAME_ENTRY *))openssl_sym(&err, "X509_NAME_ENTRY_get_data", true); + X509_NAME_get_entry = (X509_NAME_ENTRY *(*)(X509_NAME *, int))openssl_sym(&err, "X509_NAME_get_entry", true); + X509_NAME_get_index_by_NID = (int (*)(X509_NAME *, int, int))openssl_sym(&err, "X509_NAME_get_index_by_NID", true); + X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true); + X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true); + X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true); + + i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true); + + if (err) + goto on_error; + + /* Add legacy functionality */ + if (!OPENSSL_init_ssl) { + OPENSSL_init_ssl = OPENSSL_init_ssl__legacy; + + if (!SSL_library_init || + !SSL_load_error_strings || + !CRYPTO_num_locks || + !CRYPTO_set_locking_callback || + !CRYPTO_THREADID_set_callback || + !CRYPTO_THREADID_set_numeric) { + git_error_set(GIT_ERROR_SSL, "could not load legacy openssl initialization functions"); + goto on_error; + } + } + + if (!SSL_CTX_set_options) + SSL_CTX_set_options = SSL_CTX_set_options__legacy; + + if (TLS_method) + SSLv23_method = TLS_method; + + if (!BIO_meth_new) { + BIO_meth_new = BIO_meth_new__legacy; + BIO_meth_new = BIO_meth_new__legacy; + BIO_meth_free = BIO_meth_free__legacy; + BIO_meth_set_write = BIO_meth_set_write__legacy; + BIO_meth_set_read = BIO_meth_set_read__legacy; + BIO_meth_set_puts = BIO_meth_set_puts__legacy; + BIO_meth_set_gets = BIO_meth_set_gets__legacy; + BIO_meth_set_ctrl = BIO_meth_set_ctrl__legacy; + BIO_meth_set_create = BIO_meth_set_create__legacy; + BIO_meth_set_destroy = BIO_meth_set_destroy__legacy; + BIO_get_new_index = BIO_get_new_index__legacy; + BIO_set_data = BIO_set_data__legacy; + BIO_set_init = BIO_set_init__legacy; + BIO_get_data = BIO_get_data__legacy; + } + + if (!ASN1_STRING_get0_data) { + if (!ASN1_STRING_data) { + git_error_set(GIT_ERROR_SSL, "could not load legacy openssl string function"); + goto on_error; + } + + ASN1_STRING_get0_data = ASN1_STRING_get0_data__legacy; + } + + if ((!OPENSSL_sk_num && !sk_num) || + (!OPENSSL_sk_value && !sk_value) || + (!OPENSSL_sk_free && !sk_free)) { + git_error_set(GIT_ERROR_SSL, "could not load legacy openssl stack functions"); + goto on_error; + } + + if (git_runtime_shutdown_register(dynamic_shutdown) != 0) + goto on_error; + + return 0; + +on_error: + dlclose(openssl_handle); + return -1; +} + + +int sk_GENERAL_NAME_num(const GENERAL_NAME *sk) +{ + if (OPENSSL_sk_num) + return OPENSSL_sk_num(sk); + else if (sk_num) + return sk_num(sk); + + GIT_ASSERT_WITH_RETVAL(false, 0); + return 0; +} + +GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i) +{ + if (OPENSSL_sk_value) + return OPENSSL_sk_value(sk, i); + else if (sk_value) + return sk_value(sk, i); + + GIT_ASSERT_WITH_RETVAL(false, NULL); + return NULL; +} + +void GENERAL_NAMES_free(GENERAL_NAME *sk) +{ + if (OPENSSL_sk_free) + OPENSSL_sk_free(sk); + else if (sk_free) + sk_free(sk); +} + +#endif /* GIT_OPENSSL && GIT_OPENSSL_DYNAMIC */ diff --git a/src/libgit2/streams/openssl_dynamic.h b/src/libgit2/streams/openssl_dynamic.h new file mode 100644 index 000000000..a99691910 --- /dev/null +++ b/src/libgit2/streams/openssl_dynamic.h @@ -0,0 +1,348 @@ +/* + * 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. + */ + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are adhered to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * 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 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the routines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 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. + * + * The licence and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ +/* ==================================================================== + * Copyright (c) 1998-2007 The OpenSSL 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: + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED 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 OpenSSL PROJECT OR + * ITS 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. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ +/* ==================================================================== + * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. + * ECC cipher suite support in OpenSSL originally developed by + * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. + */ +/* ==================================================================== + * Copyright 2005 Nokia. All rights reserved. + * + * The portions of the attached software ("Contribution") is developed by + * Nokia Corporation and is licensed pursuant to the OpenSSL open source + * license. + * + * The Contribution, originally written by Mika Kousa and Pasi Eronen of + * Nokia Corporation, consists of the "PSK" (Pre-Shared Key) ciphersuites + * support (see RFC 4279) to OpenSSL. + * + * No patent licenses or other rights except those expressly stated in + * the OpenSSL open source license shall be deemed granted or received + * expressly, by implication, estoppel, or otherwise. + * + * No assurances are provided by Nokia that the Contribution does not + * infringe the patent or other intellectual property rights of any third + * party or that the license provides you with all the necessary rights + * to make use of the Contribution. + * + * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN + * ADDITION TO THE DISCLAIMERS INCLUDED IN THE LICENSE, NOKIA + * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY + * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR + * OTHERWISE. + */ + +#ifndef INCLUDE_streams_openssl_dynamic_h__ +#define INCLUDE_streams_openssl_dynamic_h__ + +#ifdef GIT_OPENSSL_DYNAMIC + +# define BIO_CTRL_FLUSH 11 + +# define BIO_TYPE_SOURCE_SINK 0x0400 + +# define CRYPTO_LOCK 1 + +# define GEN_DNS 2 +# define GEN_IPADD 7 + +# define NID_commonName 13 +# define NID_subject_alt_name 85 + +# define SSL_VERIFY_NONE 0x00 + +# define SSL_CTRL_OPTIONS 32 +# define SSL_CTRL_MODE 33 +# define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 + +# define SSL_ERROR_NONE 0 +# define SSL_ERROR_SSL 1 +# define SSL_ERROR_WANT_READ 2 +# define SSL_ERROR_WANT_WRITE 3 +# define SSL_ERROR_WANT_X509_LOOKUP 4 +# define SSL_ERROR_SYSCALL 5 +# define SSL_ERROR_ZERO_RETURN 6 +# define SSL_ERROR_WANT_CONNECT 7 +# define SSL_ERROR_WANT_ACCEPT 8 + +# define SSL_OP_NO_COMPRESSION 0x00020000L +# define SSL_OP_NO_SSLv2 0x01000000L +# define SSL_OP_NO_SSLv3 0x02000000L + +# define SSL_MODE_AUTO_RETRY 0x00000004L + +# define TLSEXT_NAMETYPE_host_name 0 + +# define V_ASN1_UTF8STRING 12 + +# define X509_V_OK 0 + +/* Most of the OpenSSL types are mercifully opaque, so we can treat them like `void *` */ +typedef struct bio_st BIO; +typedef struct bio_method_st BIO_METHOD; +typedef void bio_info_cb; +typedef void * CRYPTO_EX_DATA; +typedef void CRYPTO_THREADID; +typedef void GENERAL_NAMES; +typedef void SSL; +typedef void SSL_CTX; +typedef void SSL_METHOD; +typedef void X509; +typedef void X509_NAME; +typedef void X509_NAME_ENTRY; +typedef void X509_STORE_CTX; + +typedef struct { + int length; + int type; + unsigned char *data; + long flags; +} ASN1_STRING; + +typedef struct { + int type; + union { + char *ptr; + ASN1_STRING *ia5; + } d; +} GENERAL_NAME; + +struct bio_st { + BIO_METHOD *method; + /* bio, mode, argp, argi, argl, ret */ + long (*callback) (struct bio_st *, int, const char *, int, long, long); + char *cb_arg; /* first argument for the callback */ + int init; + int shutdown; + int flags; /* extra storage */ + int retry_reason; + int num; + void *ptr; + struct bio_st *next_bio; /* used by filter BIOs */ + struct bio_st *prev_bio; /* used by filter BIOs */ + int references; + unsigned long num_read; + unsigned long num_write; + CRYPTO_EX_DATA ex_data; +}; + +struct bio_method_st { + int type; + const char *name; + int (*bwrite) (BIO *, const char *, int); + int (*bread) (BIO *, char *, int); + int (*bputs) (BIO *, const char *); + int (*bgets) (BIO *, char *, int); + long (*ctrl) (BIO *, int, long, void *); + int (*create) (BIO *); + int (*destroy) (BIO *); + long (*callback_ctrl) (BIO *, int, bio_info_cb *); +}; + +extern unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); +extern const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); +extern int (*ASN1_STRING_length)(const ASN1_STRING *x); +extern int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); +extern int (*ASN1_STRING_type)(const ASN1_STRING *x); + +extern void *(*BIO_get_data)(BIO *a); +extern int (*BIO_get_new_index)(void); +extern int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); +extern void (*BIO_meth_free)(BIO_METHOD *biom); +extern int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); +extern int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); +extern int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); +extern int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); +extern int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); +extern int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); +extern int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); +extern BIO_METHOD *(*BIO_meth_new)(int type, const char *name); +extern BIO *(*BIO_new)(const BIO_METHOD *type); +extern void (*BIO_set_data)(BIO *a, void *ptr); +extern void (*BIO_set_init)(BIO *a, int init); + +extern void (*CRYPTO_free)(void *ptr, const char *file, int line); +extern void *(*CRYPTO_malloc)(size_t num, const char *file, int line); +extern int (*CRYPTO_num_locks)(void); +extern void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); +extern int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); +extern int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); +extern void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); + +extern char *(*ERR_error_string)(unsigned long e, char *buf); +extern void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); +extern unsigned long (*ERR_get_error)(void); + +# define OPENSSL_malloc(num) CRYPTO_malloc(num, __FILE__, __LINE__) +# define OPENSSL_free(addr) CRYPTO_free(addr, __FILE__, __LINE__) + +extern int (*SSL_connect)(SSL *ssl); +extern long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); +extern void (*SSL_free)(SSL *ssl); +extern int (*SSL_get_error)(SSL *ssl, int ret); +extern X509 *(*SSL_get_peer_certificate)(const SSL *ssl); +extern long (*SSL_get_verify_result)(const SSL *ssl); +extern int (*SSL_library_init)(void); +extern void (*SSL_load_error_strings)(void); +extern SSL *(*SSL_new)(SSL_CTX *ctx); +extern int (*SSL_read)(SSL *ssl, const void *buf, int num); +extern void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); +extern int (*SSL_shutdown)(SSL *ssl); +extern int (*SSL_write)(SSL *ssl, const void *buf, int num); + +# define SSL_set_tlsext_host_name(s, name) SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (char *)(name)); + +extern long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); +extern void (*SSL_CTX_free)(SSL_CTX *ctx); +extern SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); +extern int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); +extern int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); +extern long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); +extern void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); +extern int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); + +# define SSL_CTX_set_mode(ctx, mode) SSL_CTX_ctrl((ctx), SSL_CTRL_MODE, (mode), NULL); + +extern const SSL_METHOD *(*SSLv23_method)(void); +extern const SSL_METHOD *(*TLS_method)(void); + +extern ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); +extern X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); +extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); +extern void (*X509_free)(X509 *a); +extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); +extern X509_NAME *(*X509_get_subject_name)(const X509 *x); + +extern int (*i2d_X509)(X509 *a, unsigned char **ppout); + +extern int (*OPENSSL_sk_num)(const void *sk); +extern void *(*OPENSSL_sk_value)(const void *sk, int i); +extern void (*OPENSSL_sk_free)(void *sk); + +extern int (*sk_num)(const void *sk); +extern void *(*sk_value)(const void *sk, int i); +extern void (*sk_free)(void *sk); + +extern int sk_GENERAL_NAME_num(const GENERAL_NAME *sk); +extern GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i); +extern void GENERAL_NAMES_free(GENERAL_NAME *sk); + +extern int git_openssl_stream_dynamic_init(void); + +#endif /* GIT_OPENSSL_DYNAMIC */ + +#endif diff --git a/src/libgit2/streams/openssl_legacy.c b/src/libgit2/streams/openssl_legacy.c new file mode 100644 index 000000000..e61e6efbb --- /dev/null +++ b/src/libgit2/streams/openssl_legacy.c @@ -0,0 +1,203 @@ +/* + * 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 "streams/openssl.h" +#include "streams/openssl_legacy.h" + +#include "runtime.h" +#include "git2/sys/openssl.h" + +#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) +# include +# include +# include +# include +#endif + +#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) + +/* + * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it + * which do not exist in previous versions. We define these inline functions so + * we can program against the interface instead of littering the implementation + * with ifdefs. We do the same for OPENSSL_init_ssl. + */ + +int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings) +{ + GIT_UNUSED(opts); + GIT_UNUSED(settings); + SSL_load_error_strings(); + SSL_library_init(); + return 0; +} + +BIO_METHOD *BIO_meth_new__legacy(int type, const char *name) +{ + BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD)); + if (!meth) { + return NULL; + } + + meth->type = type; + meth->name = name; + + return meth; +} + +void BIO_meth_free__legacy(BIO_METHOD *biom) +{ + git__free(biom); +} + +int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)) +{ + biom->bwrite = write; + return 1; +} + +int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)) +{ + biom->bread = read; + return 1; +} + +int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)) +{ + biom->bputs = puts; + return 1; +} + +int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)) + +{ + biom->bgets = gets; + return 1; +} + +int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)) +{ + biom->ctrl = ctrl; + return 1; +} + +int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)) +{ + biom->create = create; + return 1; +} + +int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)) +{ + biom->destroy = destroy; + return 1; +} + +int BIO_get_new_index__legacy(void) +{ + /* This exists as of 1.1 so before we'd just have 0 */ + return 0; +} + +void BIO_set_init__legacy(BIO *b, int init) +{ + b->init = init; +} + +void BIO_set_data__legacy(BIO *a, void *ptr) +{ + a->ptr = ptr; +} + +void *BIO_get_data__legacy(BIO *a) +{ + return a->ptr; +} + +const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x) +{ + return ASN1_STRING_data((ASN1_STRING *)x); +} + +long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op) +{ + return SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, NULL); +} + +# if defined(GIT_THREADS) +static git_mutex *openssl_locks; + +static void openssl_locking_function(int mode, int n, const char *file, int line) +{ + int lock; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + lock = mode & CRYPTO_LOCK; + + if (lock) + (void)git_mutex_lock(&openssl_locks[n]); + else + git_mutex_unlock(&openssl_locks[n]); +} + +static void shutdown_ssl_locking(void) +{ + int num_locks, i; + + num_locks = CRYPTO_num_locks(); + CRYPTO_set_locking_callback(NULL); + + for (i = 0; i < num_locks; ++i) + git_mutex_free(&openssl_locks[i]); + git__free(openssl_locks); +} + +static void threadid_cb(CRYPTO_THREADID *threadid) +{ + GIT_UNUSED(threadid); + CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid()); +} + +int git_openssl_set_locking(void) +{ + int num_locks, i; + +#ifndef GIT_THREADS + git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); + return -1; +#endif + +#ifdef GIT_OPENSSL_DYNAMIC + /* + * This function is required on legacy versions of OpenSSL; when building + * with dynamically-loaded OpenSSL, we detect whether we loaded it or not. + */ + if (!CRYPTO_set_locking_callback) + return 0; +#endif + + CRYPTO_THREADID_set_callback(threadid_cb); + + num_locks = CRYPTO_num_locks(); + openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); + GIT_ERROR_CHECK_ALLOC(openssl_locks); + + for (i = 0; i < num_locks; i++) { + if (git_mutex_init(&openssl_locks[i]) != 0) { + git_error_set(GIT_ERROR_SSL, "failed to initialize openssl locks"); + return -1; + } + } + + CRYPTO_set_locking_callback(openssl_locking_function); + return git_runtime_shutdown_register(shutdown_ssl_locking); +} +#endif /* GIT_THREADS */ + +#endif /* GIT_OPENSSL_LEGACY || GIT_OPENSSL_DYNAMIC */ diff --git a/src/libgit2/streams/openssl_legacy.h b/src/libgit2/streams/openssl_legacy.h new file mode 100644 index 000000000..e6dae9572 --- /dev/null +++ b/src/libgit2/streams/openssl_legacy.h @@ -0,0 +1,63 @@ +/* + * 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_streams_openssl_legacy_h__ +#define INCLUDE_streams_openssl_legacy_h__ + +#include "streams/openssl_dynamic.h" + +#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) +# include +# include +# include +# include + +# if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) +# define GIT_OPENSSL_LEGACY +# endif +#endif + +#if defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) +# define OPENSSL_init_ssl OPENSSL_init_ssl__legacy +# define BIO_meth_new BIO_meth_new__legacy +# define BIO_meth_free BIO_meth_free__legacy +# define BIO_meth_set_write BIO_meth_set_write__legacy +# define BIO_meth_set_read BIO_meth_set_read__legacy +# define BIO_meth_set_puts BIO_meth_set_puts__legacy +# define BIO_meth_set_gets BIO_meth_set_gets__legacy +# define BIO_meth_set_ctrl BIO_meth_set_ctrl__legacy +# define BIO_meth_set_create BIO_meth_set_create__legacy +# define BIO_meth_set_destroy BIO_meth_set_destroy__legacy +# define BIO_get_new_index BIO_get_new_index__legacy +# define BIO_set_data BIO_set_data__legacy +# define BIO_set_init BIO_set_init__legacy +# define BIO_get_data BIO_get_data__legacy +# define ASN1_STRING_get0_data ASN1_STRING_get0_data__legacy +#endif + +#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) + +extern int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings); +extern BIO_METHOD *BIO_meth_new__legacy(int type, const char *name); +extern void BIO_meth_free__legacy(BIO_METHOD *biom); +extern int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); +extern int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); +extern int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); +extern int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); +extern int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); +extern int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)); +extern int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)); +extern int BIO_get_new_index__legacy(void); +extern void BIO_set_data__legacy(BIO *a, void *ptr); +extern void BIO_set_init__legacy(BIO *b, int init); +extern void *BIO_get_data__legacy(BIO *a); +extern const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x); +extern long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op); + +#endif + +#endif diff --git a/src/libgit2/streams/registry.c b/src/libgit2/streams/registry.c new file mode 100644 index 000000000..e60e1cd63 --- /dev/null +++ b/src/libgit2/streams/registry.c @@ -0,0 +1,119 @@ +/* + * 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 "streams/registry.h" + +#include "runtime.h" +#include "streams/tls.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/stransport.h" + +struct stream_registry { + git_rwlock lock; + git_stream_registration callbacks; + git_stream_registration tls_callbacks; +}; + +static struct stream_registry stream_registry; + +static void shutdown_stream_registry(void) +{ + git_rwlock_free(&stream_registry.lock); +} + +int git_stream_registry_global_init(void) +{ + if (git_rwlock_init(&stream_registry.lock) < 0) + return -1; + + return git_runtime_shutdown_register(shutdown_stream_registry); +} + +GIT_INLINE(void) stream_registration_cpy( + git_stream_registration *target, + git_stream_registration *src) +{ + if (src) + memcpy(target, src, sizeof(git_stream_registration)); + else + memset(target, 0, sizeof(git_stream_registration)); +} + +int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type) +{ + git_stream_registration *target; + int error = GIT_ENOTFOUND; + + GIT_ASSERT_ARG(out); + + switch(type) { + case GIT_STREAM_STANDARD: + target = &stream_registry.callbacks; + break; + case GIT_STREAM_TLS: + target = &stream_registry.tls_callbacks; + break; + default: + git_error_set(GIT_ERROR_INVALID, "invalid stream type"); + return -1; + } + + if (git_rwlock_rdlock(&stream_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); + return -1; + } + + if (target->init) { + stream_registration_cpy(out, target); + error = 0; + } + + git_rwlock_rdunlock(&stream_registry.lock); + return error; +} + +int git_stream_register(git_stream_t type, git_stream_registration *registration) +{ + GIT_ASSERT(!registration || registration->init); + + GIT_ERROR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration"); + + if (git_rwlock_wrlock(&stream_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); + return -1; + } + + if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD) + stream_registration_cpy(&stream_registry.callbacks, registration); + + if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS) + stream_registration_cpy(&stream_registry.tls_callbacks, registration); + + git_rwlock_wrunlock(&stream_registry.lock); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_stream_register_tls( + int GIT_CALLBACK(ctor)(git_stream **out, const char *host, const char *port)) +{ + git_stream_registration registration = {0}; + + if (ctor) { + registration.version = GIT_STREAM_VERSION; + registration.init = ctor; + registration.wrap = NULL; + + return git_stream_register(GIT_STREAM_TLS, ®istration); + } else { + return git_stream_register(GIT_STREAM_TLS, NULL); + } +} +#endif diff --git a/src/libgit2/streams/registry.h b/src/libgit2/streams/registry.h new file mode 100644 index 000000000..adc2b8bdf --- /dev/null +++ b/src/libgit2/streams/registry.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_streams_registry_h__ +#define INCLUDE_streams_registry_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +/** Configure stream registry. */ +int git_stream_registry_global_init(void); + +/** Lookup a stream registration. */ +extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type); + +#endif diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c new file mode 100644 index 000000000..9415fe892 --- /dev/null +++ b/src/libgit2/streams/socket.c @@ -0,0 +1,239 @@ +/* + * 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 "streams/socket.h" + +#include "posix.h" +#include "netops.h" +#include "registry.h" +#include "stream.h" + +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +# include +#else +# include +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#ifdef GIT_WIN32 +static void net_set_error(const char *str) +{ + int error = WSAGetLastError(); + char * win32_error = git_win32_get_error_message(error); + + if (win32_error) { + git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error); + git__free(win32_error); + } else { + git_error_set(GIT_ERROR_NET, "%s", str); + } +} +#else +static void net_set_error(const char *str) +{ + git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno)); +} +#endif + +static int close_socket(GIT_SOCKET s) +{ + if (s == INVALID_SOCKET) + return 0; + +#ifdef GIT_WIN32 + if (SOCKET_ERROR == closesocket(s)) + return -1; + + if (0 != WSACleanup()) { + git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); + return -1; + } + + return 0; +#else + return close(s); +#endif + +} + +static int socket_connect(git_stream *stream) +{ + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + git_socket_stream *st = (git_socket_stream *) stream; + GIT_SOCKET s = INVALID_SOCKET; + int ret; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + git_error_set(GIT_ERROR_OS, "winsock init failed"); + return -1; + } + + if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { + WSACleanup(); + git_error_set(GIT_ERROR_OS, "winsock init failed"); + return -1; + } +#endif + + memset(&hints, 0x0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { + git_error_set(GIT_ERROR_NET, + "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); + return -1; + } + + for (p = info; p != NULL; p = p->ai_next) { + s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); + + if (s == INVALID_SOCKET) + continue; + + if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + break; + + /* If we can't connect, try the next one */ + close_socket(s); + s = INVALID_SOCKET; + } + + /* Oops, we couldn't connect to any address */ + if (s == INVALID_SOCKET && p == NULL) { + git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); + p_freeaddrinfo(info); + return -1; + } + + st->s = s; + p_freeaddrinfo(info); + return 0; +} + +static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) +{ + git_socket_stream *st = (git_socket_stream *) stream; + ssize_t written; + + errno = 0; + + if ((written = p_send(st->s, data, len, flags)) < 0) { + net_set_error("error sending data"); + return -1; + } + + return written; +} + +static ssize_t socket_read(git_stream *stream, void *data, size_t len) +{ + ssize_t ret; + 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"); + + return ret; +} + +static int socket_close(git_stream *stream) +{ + git_socket_stream *st = (git_socket_stream *) stream; + int error; + + error = close_socket(st->s); + st->s = INVALID_SOCKET; + + return error; +} + +static void socket_free(git_stream *stream) +{ + git_socket_stream *st = (git_socket_stream *) stream; + + git__free(st->host); + git__free(st->port); + git__free(st); +} + +static int default_socket_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + git_socket_stream *st; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + st = git__calloc(1, sizeof(git_socket_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); + + if (port) { + st->port = git__strdup(port); + GIT_ERROR_CHECK_ALLOC(st->port); + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.connect = socket_connect; + st->parent.write = socket_write; + st->parent.read = socket_read; + st->parent.close = socket_close; + st->parent.free = socket_free; + st->s = INVALID_SOCKET; + + *out = (git_stream *) st; + return 0; +} + +int git_socket_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + int (*init)(git_stream **, const char *, const char *) = NULL; + git_stream_registration custom = {0}; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0) + init = custom.init; + else if (error == GIT_ENOTFOUND) + init = default_socket_stream_new; + else + return error; + + if (!init) { + git_error_set(GIT_ERROR_NET, "there is no socket stream available"); + return -1; + } + + return init(out, host, port); +} diff --git a/src/libgit2/streams/socket.h b/src/libgit2/streams/socket.h new file mode 100644 index 000000000..3235f3167 --- /dev/null +++ b/src/libgit2/streams/socket.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_streams_socket_h__ +#define INCLUDE_streams_socket_h__ + +#include "common.h" + +#include "netops.h" + +typedef struct { + git_stream parent; + char *host; + char *port; + GIT_SOCKET s; +} git_socket_stream; + +extern int git_socket_stream_new(git_stream **out, const char *host, const char *port); + +#endif diff --git a/src/libgit2/streams/stransport.c b/src/libgit2/streams/stransport.c new file mode 100644 index 000000000..3f31d2541 --- /dev/null +++ b/src/libgit2/streams/stransport.c @@ -0,0 +1,326 @@ +/* + * 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 "streams/stransport.h" + +#ifdef GIT_SECURE_TRANSPORT + +#include +#include +#include + +#include "git2/transport.h" + +#include "streams/socket.h" + +static int stransport_error(OSStatus ret) +{ + CFStringRef message; + + if (ret == noErr || ret == errSSLClosedGraceful) { + git_error_clear(); + return 0; + } + +#if !TARGET_OS_IPHONE + message = SecCopyErrorMessageString(ret, NULL); + GIT_ERROR_CHECK_ALLOC(message); + + git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8)); + CFRelease(message); +#else + git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret); + GIT_UNUSED(message); +#endif + + return -1; +} + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + SSLContextRef ctx; + CFDataRef der_data; + git_cert_x509 cert_info; +} stransport_stream; + +static int stransport_connect(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + int error; + SecTrustRef trust = NULL; + SecTrustResultType sec_res; + OSStatus ret; + + if (st->owned && (error = git_stream_connect(st->io)) < 0) + return error; + + ret = SSLHandshake(st->ctx); + if (ret != errSSLServerAuthCompleted) { + git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret); + return -1; + } + + if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) + goto on_error; + + if (!trust) + return GIT_ECERTIFICATE; + + if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr) + goto on_error; + + CFRelease(trust); + + if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) { + git_error_set(GIT_ERROR_SSL, "internal security trust error"); + return -1; + } + + if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure || + sec_res == kSecTrustResultFatalTrustFailure) { + git_error_set(GIT_ERROR_SSL, "untrusted connection error"); + return GIT_ECERTIFICATE; + } + + return 0; + +on_error: + if (trust) + CFRelease(trust); + + return stransport_error(ret); +} + +static int stransport_certificate(git_cert **out, git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + SecTrustRef trust = NULL; + SecCertificateRef sec_cert; + OSStatus ret; + + if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) + return stransport_error(ret); + + sec_cert = SecTrustGetCertificateAtIndex(trust, 0); + st->der_data = SecCertificateCopyData(sec_cert); + CFRelease(trust); + + if (st->der_data == NULL) { + git_error_set(GIT_ERROR_SSL, "retrieved invalid certificate data"); + return -1; + } + + st->cert_info.parent.cert_type = GIT_CERT_X509; + st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data); + st->cert_info.len = CFDataGetLength(st->der_data); + + *out = (git_cert *)&st->cert_info; + return 0; +} + +static int stransport_set_proxy( + git_stream *stream, + const git_proxy_options *proxy_opts) +{ + stransport_stream *st = (stransport_stream *) stream; + + return git_stream_set_proxy(st->io, proxy_opts); +} + +/* + * Contrary to typical network IO callbacks, Secure Transport write callback is + * expected to write *all* passed data, not just as much as it can, and any + * other case would be considered a failure. + * + * This behavior is actually not specified in the Apple documentation, but is + * required for things to work correctly (and incidentally, that's also how + * Apple implements it in its projects at opensource.apple.com). + * + * Libgit2 streams happen to already have this very behavior so this is just + * passthrough. + */ +static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) +{ + git_stream *io = (git_stream *) conn; + + if (git_stream__write_full(io, data, *len, 0) < 0) + return -36; /* "ioErr" from MacErrors.h which is not available on iOS */ + + return noErr; +} + +static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags) +{ + stransport_stream *st = (stransport_stream *) stream; + size_t data_len, processed; + OSStatus ret; + + GIT_UNUSED(flags); + + data_len = min(len, SSIZE_MAX); + if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) + return stransport_error(ret); + + GIT_ASSERT(processed < SSIZE_MAX); + return (ssize_t)processed; +} + +/* + * Contrary to typical network IO callbacks, Secure Transport read callback is + * expected to read *exactly* the requested number of bytes, not just as much + * as it can, and any other case would be considered a failure. + * + * This behavior is actually not specified in the Apple documentation, but is + * required for things to work correctly (and incidentally, that's also how + * Apple implements it in its projects at opensource.apple.com). + */ +static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) +{ + git_stream *io = (git_stream *) conn; + OSStatus error = noErr; + size_t off = 0; + ssize_t ret; + + do { + ret = git_stream_read(io, data + off, *len - off); + if (ret < 0) { + error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */ + break; + } + if (ret == 0) { + error = errSSLClosedGraceful; + break; + } + + off += ret; + } while (off < *len); + + *len = off; + return error; +} + +static ssize_t stransport_read(git_stream *stream, void *data, size_t len) +{ + stransport_stream *st = (stransport_stream *) stream; + size_t processed; + OSStatus ret; + + if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) + return stransport_error(ret); + + return processed; +} + +static int stransport_close(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + OSStatus ret; + + ret = SSLClose(st->ctx); + if (ret != noErr && ret != errSSLClosedGraceful) + return stransport_error(ret); + + return st->owned ? git_stream_close(st->io) : 0; +} + +static void stransport_free(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + + if (st->owned) + git_stream_free(st->io); + + CFRelease(st->ctx); + if (st->der_data) + CFRelease(st->der_data); + git__free(st); +} + +static int stransport_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + stransport_stream *st; + OSStatus ret; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(host); + + st = git__calloc(1, sizeof(stransport_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); + if (!st->ctx) { + git_error_set(GIT_ERROR_NET, "failed to create SSL context"); + git__free(st); + return -1; + } + + if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || + (ret = SSLSetConnection(st->ctx, st->io)) != noErr || + (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr || + (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr || + (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr || + (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) { + CFRelease(st->ctx); + git__free(st); + return stransport_error(ret); + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = stransport_connect; + st->parent.certificate = stransport_certificate; + st->parent.set_proxy = stransport_set_proxy; + st->parent.read = stransport_read; + st->parent.write = stransport_write; + st->parent.close = stransport_close; + st->parent.free = stransport_free; + + *out = (git_stream *) st; + return 0; +} + +int git_stransport_stream_wrap( + git_stream **out, + git_stream *in, + const char *host) +{ + return stransport_wrap(out, in, host, 0); +} + +int git_stransport_stream_new(git_stream **out, const char *host, const char *port) +{ + git_stream *stream = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + + error = git_socket_stream_new(&stream, host, port); + + if (!error) + error = stransport_wrap(out, stream, host, 1); + + if (error < 0 && stream) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +#endif diff --git a/src/libgit2/streams/stransport.h b/src/libgit2/streams/stransport.h new file mode 100644 index 000000000..1026e204b --- /dev/null +++ b/src/libgit2/streams/stransport.h @@ -0,0 +1,21 @@ +/* + * 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_streams_stransport_h__ +#define INCLUDE_streams_stransport_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +#ifdef GIT_SECURE_TRANSPORT + +extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port); +extern int git_stransport_stream_wrap(git_stream **out, git_stream *in, const char *host); + +#endif + +#endif diff --git a/src/libgit2/streams/tls.c b/src/libgit2/streams/tls.c new file mode 100644 index 000000000..e063a33f9 --- /dev/null +++ b/src/libgit2/streams/tls.c @@ -0,0 +1,75 @@ +/* + * 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/errors.h" + +#include "common.h" +#include "streams/registry.h" +#include "streams/tls.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/stransport.h" + +int git_tls_stream_new(git_stream **out, const char *host, const char *port) +{ + int (*init)(git_stream **, const char *, const char *) = NULL; + git_stream_registration custom = {0}; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) { + init = custom.init; + } else if (error == GIT_ENOTFOUND) { +#ifdef GIT_SECURE_TRANSPORT + init = git_stransport_stream_new; +#elif defined(GIT_OPENSSL) + init = git_openssl_stream_new; +#elif defined(GIT_MBEDTLS) + init = git_mbedtls_stream_new; +#endif + } else { + return error; + } + + if (!init) { + git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); + return -1; + } + + return init(out, host, port); +} + +int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host) +{ + int (*wrap)(git_stream **, git_stream *, const char *) = NULL; + git_stream_registration custom = {0}; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(in); + + if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) { + wrap = custom.wrap; + } else { +#ifdef GIT_SECURE_TRANSPORT + wrap = git_stransport_stream_wrap; +#elif defined(GIT_OPENSSL) + wrap = git_openssl_stream_wrap; +#elif defined(GIT_MBEDTLS) + wrap = git_mbedtls_stream_wrap; +#endif + } + + if (!wrap) { + git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); + return -1; + } + + return wrap(out, in, host); +} diff --git a/src/libgit2/streams/tls.h b/src/libgit2/streams/tls.h new file mode 100644 index 000000000..465a6ea89 --- /dev/null +++ b/src/libgit2/streams/tls.h @@ -0,0 +1,31 @@ +/* + * 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_streams_tls_h__ +#define INCLUDE_streams_tls_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +/** + * Create a TLS stream with the most appropriate backend available for + * the current platform, whether that's SecureTransport on macOS, + * OpenSSL or mbedTLS on other Unixes, or something else entirely. + */ +extern int git_tls_stream_new(git_stream **out, const char *host, const char *port); + +/** + * Create a TLS stream on top of an existing insecure stream, using + * the most appropriate backend available for the current platform. + * + * This allows us to create a CONNECT stream on top of a proxy; + * using SecureTransport on macOS, OpenSSL or mbedTLS on other + * Unixes, or something else entirely. + */ +extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host); + +#endif diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c new file mode 100644 index 000000000..0f4f0726c --- /dev/null +++ b/src/libgit2/submodule.c @@ -0,0 +1,2380 @@ +/* + * 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 "submodule.h" + +#include "buf.h" +#include "branch.h" +#include "vector.h" +#include "posix.h" +#include "config_backend.h" +#include "config.h" +#include "repository.h" +#include "tree.h" +#include "iterator.h" +#include "fs_path.h" +#include "str.h" +#include "index.h" +#include "worktree.h" +#include "clone.h" +#include "path.h" + +#include "git2/config.h" +#include "git2/sys/config.h" +#include "git2/types.h" +#include "git2/index.h" + +#define GIT_MODULES_FILE ".gitmodules" + +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_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_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 { + CACHE_OK = 0, + CACHE_REFRESH = 1, + CACHE_FLUSH = 2 +}; +enum { + GITMODULES_EXISTING = 0, + GITMODULES_CREATE = 1 +}; + +static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); +static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); +static int gitmodules_snapshot(git_config **snap, git_repository *repo); +static int get_url_base(git_str *url, git_repository *repo); +static int lookup_head_remote_key(git_str *remote_key, git_repository *repo); +static int lookup_default_remote(git_remote **remote, git_repository *repo); +static int submodule_load_each(const git_config_entry *entry, void *payload); +static int submodule_read_config(git_submodule *sm, git_config *cfg); +static int submodule_load_from_wd_lite(git_submodule *); +static void submodule_get_index_status(unsigned int *, git_submodule *); +static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); +static void submodule_update_from_index_entry(git_submodule *sm, const git_index_entry *ie); +static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, const git_oid *id); + +static int submodule_cmp(const void *a, const void *b) +{ + return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); +} + +static int submodule_config_key_trunc_puts(git_str *key, const char *suffix) +{ + ssize_t idx = git_str_rfind(key, '.'); + git_str_truncate(key, (size_t)(idx + 1)); + return git_str_puts(key, suffix); +} + +/* + * PUBLIC APIS + */ + +static void submodule_set_lookup_error(int error, const char *name) +{ + if (!error) + return; + + git_error_set(GIT_ERROR_SUBMODULE, (error == GIT_ENOTFOUND) ? + "no submodule named '%s'" : + "submodule '%s' has not been added yet", name); +} + +typedef struct { + const char *path; + char *name; +} fbp_data; + +static int find_by_path(const git_config_entry *entry, void *payload) +{ + fbp_data *data = payload; + + if (!strcmp(entry->value, data->path)) { + const char *fdot, *ldot; + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + data->name = git__strndup(fdot + 1, ldot - fdot - 1); + GIT_ERROR_CHECK_ALLOC(data->name); + } + + return 0; +} + +/* + * Checks to see if the submodule shares its name with a file or directory that + * already exists on the index. If so, the submodule cannot be added. + */ +static int is_path_occupied(bool *occupied, git_repository *repo, const char *path) +{ + int error = 0; + git_index *index; + git_str dir = GIT_STR_INIT; + *occupied = false; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + goto out; + + if ((error = git_index_find(NULL, index, path)) != GIT_ENOTFOUND) { + if (!error) { + git_error_set(GIT_ERROR_SUBMODULE, + "File '%s' already exists in the index", path); + *occupied = true; + } + goto out; + } + + if ((error = git_str_sets(&dir, path)) < 0) + goto out; + + if ((error = git_fs_path_to_dir(&dir)) < 0) + goto out; + + if ((error = git_index_find_prefix(NULL, index, dir.ptr)) != GIT_ENOTFOUND) { + if (!error) { + git_error_set(GIT_ERROR_SUBMODULE, + "Directory '%s' already exists in the index", path); + *occupied = true; + } + goto out; + } + + error = 0; + +out: + git_str_dispose(&dir); + return error; +} + +/** + * Release the name map returned by 'load_submodule_names'. + */ +static void free_submodule_names(git_strmap *names) +{ + const char *key; + char *value; + + if (names == NULL) + return; + + git_strmap_foreach(names, key, value, { + git__free((char *) key); + git__free(value); + }); + git_strmap_free(names); + + return; +} + +/** + * Map submodule paths to names. + * TODO: for some use-cases, this might need case-folding on a + * case-insensitive filesystem + */ +static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) +{ + const char *key = "submodule\\..*\\.path"; + git_config_iterator *iter = NULL; + git_config_entry *entry; + git_str buf = GIT_STR_INIT; + git_strmap *names; + int isvalid, error; + + *out = NULL; + + if ((error = git_strmap_new(&names)) < 0) + goto out; + + if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) + goto out; + + while ((error = git_config_next(&entry, iter)) == 0) { + const char *fdot, *ldot; + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + + if (git_strmap_exists(names, entry->value)) { + git_error_set(GIT_ERROR_SUBMODULE, + "duplicated submodule path '%s'", entry->value); + error = -1; + goto out; + } + + git_str_clear(&buf); + git_str_put(&buf, fdot + 1, ldot - fdot - 1); + isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0); + if (isvalid < 0) { + error = isvalid; + goto out; + } + if (!isvalid) + continue; + + if ((error = git_strmap_set(names, git__strdup(entry->value), git_str_detach(&buf))) < 0) { + git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table"); + error = -1; + goto out; + } + } + if (error == GIT_ITEROVER) + error = 0; + + *out = names; + names = NULL; + +out: + free_submodule_names(names); + git_str_dispose(&buf); + git_config_iterator_free(iter); + return error; +} + +int git_submodule_cache_init(git_strmap **out, git_repository *repo) +{ + int error = 0; + git_strmap *cache = NULL; + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + if ((error = git_strmap_new(&cache)) < 0) + return error; + if ((error = git_submodule__map(repo, cache)) < 0) { + git_submodule_cache_free(cache); + return error; + } + *out = cache; + return error; +} + +int git_submodule_cache_free(git_strmap *cache) +{ + git_submodule *sm = NULL; + if (cache == NULL) + return 0; + git_strmap_foreach_value(cache, sm, { + git_submodule_free(sm); + }); + git_strmap_free(cache); + return 0; +} + +int git_submodule_lookup( + git_submodule **out, /* NULL if user only wants to test existence */ + git_repository *repo, + const char *name) /* trailing slash is allowed */ +{ + return git_submodule__lookup_with_cache(out, repo, name, repo->submodule_cache); +} + +int git_submodule__lookup_with_cache( + git_submodule **out, /* NULL if user only wants to test existence */ + git_repository *repo, + const char *name, /* trailing slash is allowed */ + git_strmap *cache) +{ + int error; + unsigned int location; + git_submodule *sm; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if (repo->is_bare) { + git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); + return -1; + } + + if (cache != NULL) { + if ((sm = git_strmap_get(cache, name)) != NULL) { + if (out) { + *out = sm; + GIT_REFCOUNT_INC(*out); + } + return 0; + } + } + + if ((error = submodule_alloc(&sm, repo, name)) < 0) + return error; + + if ((error = git_submodule_reload(sm, false)) < 0) { + git_submodule_free(sm); + return error; + } + + if ((error = git_submodule_location(&location, sm)) < 0) { + git_submodule_free(sm); + return error; + } + + /* If it's not configured or we're looking by path */ + if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { + git_config_backend *mods; + const char *pattern = "submodule\\..*\\.path"; + git_str path = GIT_STR_INIT; + fbp_data data = { NULL, NULL }; + + git_str_puts(&path, name); + while (path.ptr[path.size-1] == '/') { + path.ptr[--path.size] = '\0'; + } + data.path = path.ptr; + + mods = open_gitmodules(repo, GITMODULES_EXISTING); + + if (mods) + error = git_config_backend_foreach_match(mods, pattern, find_by_path, &data); + + git_config_backend_free(mods); + + if (error < 0) { + git_submodule_free(sm); + git_str_dispose(&path); + return error; + } + + if (data.name) { + git__free(sm->name); + sm->name = data.name; + sm->path = git_str_detach(&path); + + /* Try to load again with the right name */ + if ((error = git_submodule_reload(sm, false)) < 0) { + git_submodule_free(sm); + return error; + } + } + + git_str_dispose(&path); + } + + if ((error = git_submodule_location(&location, sm)) < 0) { + git_submodule_free(sm); + return error; + } + + /* If we still haven't found it, do the WD check */ + if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { + git_submodule_free(sm); + error = GIT_ENOTFOUND; + + /* If it's not configured, we still check if there's a repo at the path */ + if (git_repository_workdir(repo)) { + git_str path = GIT_STR_INIT; + if (git_str_join3(&path, '/', + git_repository_workdir(repo), + name, DOT_GIT) < 0 || + git_path_validate_str_length(NULL, &path) < 0) + return -1; + + if (git_fs_path_exists(path.ptr)) + error = GIT_EEXISTS; + + git_str_dispose(&path); + } + + submodule_set_lookup_error(error, name); + return error; + } + + if (out) + *out = sm; + else + git_submodule_free(sm); + + return 0; +} + +int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags) +{ + git_str buf = GIT_STR_INIT; + int error, isvalid; + + if (flags == 0) + flags = GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS; + + /* Avoid allocating a new string if we can avoid it */ + if (strchr(name, '\\') != NULL) { + if ((error = git_fs_path_normalize_slashes(&buf, name)) < 0) + return error; + } else { + git_str_attach_notowned(&buf, name, strlen(name)); + } + + isvalid = git_path_is_valid(repo, buf.ptr, 0, flags); + git_str_dispose(&buf); + + return isvalid; +} + +static void submodule_free_dup(void *sm) +{ + git_submodule_free(sm); +} + +static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name) +{ + git_submodule *sm = NULL; + int error; + + 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; + + if ((error = git_strmap_set(map, sm->name, sm)) < 0) { + git_submodule_free(sm); + return error; + } + +done: + GIT_REFCOUNT_INC(sm); + *out = sm; + return 0; +} + +static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) +{ + int error; + git_iterator *i = NULL; + const git_index_entry *entry; + git_strmap *names; + + if ((error = load_submodule_names(&names, git_index_owner(idx), cfg))) + goto done; + + if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0) + goto done; + + while (!(error = git_iterator_advance(&entry, i))) { + git_submodule *sm; + + 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)) { + const char *name; + + 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); + git_submodule_free(sm); + } + } + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_iterator_free(i); + free_submodule_names(names); + + return error; +} + +static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) +{ + int error; + git_iterator *i = NULL; + const git_index_entry *entry; + git_strmap *names; + + if ((error = load_submodule_names(&names, git_tree_owner(head), cfg))) + goto done; + + if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) + goto done; + + while (!(error = git_iterator_advance(&entry, i))) { + git_submodule *sm; + + 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)) { + const char *name; + + 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( + sm, entry->mode, &entry->id); + git_submodule_free(sm); + } + } + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_iterator_free(i); + free_submodule_names(names); + + return error; +} + +/* If have_sm is true, sm is populated, otherwise map an repo are. */ +typedef struct { + git_config *mods; + git_strmap *map; + git_repository *repo; +} lfc_data; + +int git_submodule__map(git_repository *repo, git_strmap *map) +{ + int error = 0; + git_index *idx = NULL; + git_tree *head = NULL; + git_str path = GIT_STR_INIT; + git_submodule *sm; + git_config *mods = NULL; + bool has_workdir; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(map); + + /* get sources that we will need to check */ + if (git_repository_index(&idx, repo) < 0) + git_error_clear(); + if (git_repository_head_tree(&head, repo) < 0) + git_error_clear(); + + has_workdir = git_repository_workdir(repo) != NULL; + + if (has_workdir && + (error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) + goto cleanup; + + /* add submodule information from .gitmodules */ + if (has_workdir) { + lfc_data data = { 0 }; + data.map = map; + data.repo = repo; + + if ((error = gitmodules_snapshot(&mods, repo)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + goto cleanup; + } + + data.mods = mods; + if ((error = git_config_foreach( + mods, submodule_load_each, &data)) < 0) + goto cleanup; + } + /* add back submodule information from index */ + if (mods && idx) { + if ((error = submodules_from_index(map, idx, mods)) < 0) + goto cleanup; + } + /* add submodule information from HEAD */ + if (mods && head) { + if ((error = submodules_from_head(map, head, mods)) < 0) + goto cleanup; + } + /* shallow scan submodules in work tree as needed */ + if (has_workdir) { + git_strmap_foreach_value(map, sm, { + submodule_load_from_wd_lite(sm); + }); + } + +cleanup: + git_config_free(mods); + /* TODO: if we got an error, mark submodule config as invalid? */ + git_index_free(idx); + git_tree_free(head); + git_str_dispose(&path); + return error; +} + +int git_submodule_foreach( + git_repository *repo, + git_submodule_cb callback, + void *payload) +{ + git_vector snapshot = GIT_VECTOR_INIT; + git_strmap *submodules; + git_submodule *sm; + int error; + size_t i; + + if (repo->is_bare) { + git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); + return -1; + } + + 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_size(submodules), submodule_cmp))) { + + git_strmap_foreach_value(submodules, sm, { + if ((error = git_vector_insert(&snapshot, sm)) < 0) + break; + GIT_REFCOUNT_INC(sm); + }); + } + + if (error < 0) + goto done; + + git_vector_uniq(&snapshot, submodule_free_dup); + + git_vector_foreach(&snapshot, i, sm) { + if ((error = callback(sm, sm->name, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + +done: + git_vector_foreach(&snapshot, i, sm) + git_submodule_free(sm); + git_vector_free(&snapshot); + + git_strmap_foreach_value(submodules, sm, { + git_submodule_free(sm); + }); + git_strmap_free(submodules); + + return error; +} + +static int submodule_repo_init( + git_repository **out, + git_repository *parent_repo, + const char *path, + const char *url, + bool use_gitlink) +{ + int error = 0; + git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; + git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_repository *subrepo = NULL; + + error = git_repository_workdir_path(&workdir, parent_repo, path); + if (error < 0) + goto cleanup; + + initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT; + initopt.origin_url = url; + + /* init submodule repository and add origin remote as needed */ + + /* New style: sub-repo goes in /modules// with a + * gitlink in the sub-repo workdir directory to that repository + * + * Old style: sub-repo goes directly into repo//.git/ + */ + if (use_gitlink) { + error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); + if (error < 0) + goto cleanup; + error = git_str_joinpath(&repodir, repodir.ptr, path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = workdir.ptr; + initopt.flags |= + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + } else + error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt); + +cleanup: + git_str_dispose(&workdir); + git_str_dispose(&repodir); + + *out = subrepo; + + return error; +} + +static int git_submodule__resolve_url( + git_str *out, + git_repository *repo, + const char *url) +{ + int error = 0; + git_str normalized = GIT_STR_INIT; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(url); + + /* We do this in all platforms in case someone on Windows created the .gitmodules */ + if (strchr(url, '\\')) { + if ((error = git_fs_path_normalize_slashes(&normalized, url)) < 0) + return error; + + url = normalized.ptr; + } + + + if (git_fs_path_is_relative(url)) { + if (!(error = get_url_base(out, repo))) + error = git_fs_path_apply_relative(out, url); + } else if (strchr(url, ':') != NULL || url[0] == '/') { + error = git_str_sets(out, url); + } else { + git_error_set(GIT_ERROR_SUBMODULE, "invalid format for submodule URL"); + error = -1; + } + + git_str_dispose(&normalized); + return error; +} + +int git_submodule_resolve_url( + git_buf *out, + git_repository *repo, + const char *url) +{ + GIT_BUF_WRAP_PRIVATE(out, git_submodule__resolve_url, repo, url); +} + +int git_submodule_add_setup( + git_submodule **out, + git_repository *repo, + const char *url, + const char *path, + int use_gitlink) +{ + int error = 0; + git_config_backend *mods = NULL; + git_submodule *sm = NULL; + git_str name = GIT_STR_INIT, real_url = GIT_STR_INIT; + git_repository *subrepo = NULL; + bool path_occupied; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(path); + + /* see if there is already an entry for this submodule */ + + if (git_submodule_lookup(NULL, repo, path) < 0) + git_error_clear(); + else { + git_error_set(GIT_ERROR_SUBMODULE, + "attempt to add submodule '%s' that already exists", path); + return GIT_EEXISTS; + } + + /* validate and normalize path */ + + if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) + path += strlen(git_repository_workdir(repo)); + + if (git_fs_path_root(path) >= 0) { + git_error_set(GIT_ERROR_SUBMODULE, "submodule path must be a relative path"); + error = -1; + goto cleanup; + } + + if ((error = is_path_occupied(&path_occupied, repo, path)) < 0) + goto cleanup; + + if (path_occupied) { + error = GIT_EEXISTS; + goto cleanup; + } + + /* update .gitmodules */ + + if (!(mods = open_gitmodules(repo, GITMODULES_CREATE))) { + git_error_set(GIT_ERROR_SUBMODULE, + "adding submodules to a bare repository is not supported"); + return -1; + } + + if ((error = git_str_printf(&name, "submodule.%s.path", path)) < 0 || + (error = git_config_backend_set_string(mods, name.ptr, path)) < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || + (error = git_config_backend_set_string(mods, name.ptr, url)) < 0) + goto cleanup; + + git_str_clear(&name); + + /* init submodule repository and add origin remote as needed */ + + error = git_repository_workdir_path(&name, repo, path); + if (error < 0) + goto cleanup; + + /* if the repo does not already exist, then init a new repo and add it. + * Otherwise, just add the existing repo. + */ + if (!(git_fs_path_exists(name.ptr) && + git_fs_path_contains(&name, DOT_GIT))) { + + /* resolve the actual URL to use */ + if ((error = git_submodule__resolve_url(&real_url, repo, url)) < 0) + goto cleanup; + + if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0) + goto cleanup; + } + + if ((error = git_submodule_lookup(&sm, repo, path)) < 0) + goto cleanup; + + error = git_submodule_init(sm, false); + +cleanup: + if (error && sm) { + git_submodule_free(sm); + sm = NULL; + } + if (out != NULL) + *out = sm; + + git_config_backend_free(mods); + git_repository_free(subrepo); + git_str_dispose(&real_url); + git_str_dispose(&name); + + return error; +} + +int git_submodule_repo_init( + git_repository **out, + const git_submodule *sm, + int use_gitlink) +{ + int error; + git_repository *sub_repo = NULL; + const char *configured_url; + git_config *cfg = NULL; + git_str buf = GIT_STR_INIT; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(sm); + + /* get the configured remote url of the submodule */ + if ((error = git_str_printf(&buf, "submodule.%s.url", sm->name)) < 0 || + (error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 || + (error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 || + (error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0) + goto done; + + *out = sub_repo; + +done: + git_config_free(cfg); + git_str_dispose(&buf); + 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_str rel_path = GIT_STR_INIT; + git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + GIT_ASSERT_ARG(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; + + error = git_repository_workdir_path(&rel_path, git_submodule_owner(submodule), git_submodule_path(submodule)); + if (error < 0) + goto cleanup; + + error = git_clone__submodule(&clone, git_submodule_url(submodule), git_str_cstr(&rel_path), &opts); + if (error < 0) + goto cleanup; + + if (!out) + git_repository_free(clone); + else + *out = clone; + +cleanup: + git_str_dispose(&rel_path); + + return error; +} + +int git_submodule_add_finalize(git_submodule *sm) +{ + int error; + git_index *index; + + GIT_ASSERT_ARG(sm); + + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || + (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0) + return error; + + return git_submodule_add_to_index(sm, true); +} + +int git_submodule_add_to_index(git_submodule *sm, int write_index) +{ + int error; + git_repository *sm_repo = NULL; + git_index *index; + git_str path = GIT_STR_INIT; + git_commit *head; + git_index_entry entry; + struct stat st; + + GIT_ASSERT_ARG(sm); + + /* force reload of wd OID by git_submodule_open */ + sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; + + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || + (error = git_repository_workdir_path(&path, sm->repo, sm->path)) < 0 || + (error = git_submodule_open(&sm_repo, sm)) < 0) + goto cleanup; + + /* read stat information for submodule working directory */ + if (p_stat(path.ptr, &st) < 0) { + git_error_set(GIT_ERROR_SUBMODULE, + "cannot add submodule without working directory"); + error = -1; + goto cleanup; + } + + memset(&entry, 0, sizeof(entry)); + entry.path = sm->path; + git_index_entry__init_from_stat( + &entry, &st, !(git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE)); + + /* calling git_submodule_open will have set sm->wd_oid if possible */ + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { + git_error_set(GIT_ERROR_SUBMODULE, + "cannot add submodule without HEAD to index"); + error = -1; + goto cleanup; + } + git_oid_cpy(&entry.id, &sm->wd_oid); + + if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) + goto cleanup; + + entry.ctime.seconds = (int32_t)git_commit_time(head); + entry.ctime.nanoseconds = 0; + entry.mtime.seconds = (int32_t)git_commit_time(head); + entry.mtime.nanoseconds = 0; + + git_commit_free(head); + + /* add it */ + error = git_index_add(index, &entry); + + /* write it, if requested */ + if (!error && write_index) { + error = git_index_write(index); + + if (!error) + git_oid_cpy(&sm->index_oid, &sm->wd_oid); + } + +cleanup: + git_repository_free(sm_repo); + git_str_dispose(&path); + return error; +} + +static const char *submodule_update_to_str(git_submodule_update_t update) +{ + int i; + for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i) + if (_sm_update_map[i].map_value == (int)update) + return _sm_update_map[i].str_match; + return NULL; +} + +git_repository *git_submodule_owner(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->repo; +} + +const char *git_submodule_name(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->name; +} + +const char *git_submodule_path(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->path; +} + +const char *git_submodule_url(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->url; +} + +static int write_var(git_repository *repo, const char *name, const char *var, const char *val) +{ + git_str key = GIT_STR_INIT; + git_config_backend *mods; + int error; + + mods = open_gitmodules(repo, GITMODULES_CREATE); + if (!mods) + return -1; + + if ((error = git_str_printf(&key, "submodule.%s.%s", name, var)) < 0) + goto cleanup; + + if (val) + error = git_config_backend_set_string(mods, key.ptr, val); + else + error = git_config_backend_delete(mods, key.ptr); + + git_str_dispose(&key); + +cleanup: + git_config_backend_free(mods); + return error; +} + +static int write_mapped_var(git_repository *repo, const char *name, git_configmap *maps, size_t nmaps, const char *var, int ival) +{ + git_configmap_t type; + const char *val; + + if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) { + git_error_set(GIT_ERROR_SUBMODULE, "invalid value for %s", var); + return -1; + } + + if (type == GIT_CONFIGMAP_TRUE) + val = "true"; + + return write_var(repo, name, var, val); +} + +const char *git_submodule_branch(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->branch; +} + +int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_var(repo, name, "branch", branch); +} + +int git_submodule_set_url(git_repository *repo, const char *name, const char *url) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(url); + + return write_var(repo, name, "url", url); +} + +const git_oid *git_submodule_index_id(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + + if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) + return &submodule->index_oid; + else + return NULL; +} + +const git_oid *git_submodule_head_id(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + + if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) + return &submodule->head_oid; + else + return NULL; +} + +const git_oid *git_submodule_wd_id(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + + /* load unless we think we have a valid oid */ + if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { + git_repository *subrepo; + + /* calling submodule open grabs the HEAD OID if possible */ + if (!git_submodule_open_bare(&subrepo, submodule)) + git_repository_free(subrepo); + else + git_error_clear(); + } + + if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) + return &submodule->wd_oid; + else + return NULL; +} + +git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_IGNORE_UNSPECIFIED); + + return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ? + GIT_SUBMODULE_IGNORE_NONE : submodule->ignore; +} + +int git_submodule_set_ignore(git_repository *repo, const char *name, git_submodule_ignore_t ignore) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_mapped_var(repo, name, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), "ignore", ignore); +} + +git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_UPDATE_NONE); + + return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ? + GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update; +} + +int git_submodule_set_update(git_repository *repo, const char *name, git_submodule_update_t update) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_mapped_var(repo, name, _sm_update_map, ARRAY_SIZE(_sm_update_map), "update", update); +} + +git_submodule_recurse_t git_submodule_fetch_recurse_submodules( + git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_RECURSE_NO); + return submodule->fetch_recurse; +} + +int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_mapped_var(repo, name, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), "fetchRecurseSubmodules", recurse); +} + +static int submodule_repo_create( + git_repository **out, + git_repository *parent_repo, + const char *path) +{ + int error = 0; + git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; + git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_repository *subrepo = NULL; + + initopt.flags = + GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_NO_REINIT | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK; + + /* Workdir: path to sub-repo working directory */ + error = git_repository_workdir_path(&workdir, parent_repo, path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = workdir.ptr; + + /** + * Repodir: path to the sub-repo. sub-repo goes in: + * /modules// with a gitlink in the + * sub-repo workdir directory to that repository. + */ + error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); + if (error < 0) + goto cleanup; + error = git_str_joinpath(&repodir, repodir.ptr, path); + if (error < 0) + goto cleanup; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + +cleanup: + git_str_dispose(&workdir); + git_str_dispose(&repodir); + + *out = subrepo; + + return error; +} + +/** + * Callback to override sub-repository creation when + * cloning a sub-repository. + */ +static int git_submodule_update_repo_init_cb( + git_repository **out, + const char *path, + int bare, + void *payload) +{ + git_submodule *sm; + + GIT_UNUSED(bare); + + sm = payload; + + return submodule_repo_create(out, sm->repo, path); +} + +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; +} + +#ifndef GIT_DEPRECATE_HARD +int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) +{ + return git_submodule_update_options_init(opts, version); +} +#endif + +int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options) +{ + int error; + unsigned int submodule_status; + git_config *config = NULL; + const char *submodule_url; + git_repository *sub_repo = NULL; + git_remote *remote = NULL; + git_object *target_commit = NULL; + git_str buf = GIT_STR_INIT; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT; + + GIT_ASSERT_ARG(sm); + + if (_update_options) + memcpy(&update_options, _update_options, sizeof(git_submodule_update_options)); + + GIT_ERROR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); + + /* Copy over the remote callbacks */ + memcpy(&clone_options.fetch_opts, &update_options.fetch_opts, sizeof(git_fetch_options)); + + /* Get the status of the submodule to determine if it is already initialized */ + if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) + goto done; + + /* + * If submodule work dir is not already initialized, check to see + * what we need to do (initialize, clone, return error...) + */ + if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) { + /* + * Work dir is not initialized, check to see if the submodule + * info has been copied into .git/config + */ + if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || + (error = git_str_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0) + goto done; + + if ((error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) { + /* + * If the error is not "not found" or if it is "not found" and we are not + * initializing the submodule, then return error. + */ + if (error != GIT_ENOTFOUND) + goto done; + + if (!init) { + git_error_set(GIT_ERROR_SUBMODULE, "submodule is not initialized"); + error = GIT_ERROR; + goto done; + } + + /* The submodule has not been initialized yet - initialize it now.*/ + if ((error = git_submodule_init(sm, 0)) < 0) + goto done; + + git_config_free(config); + config = NULL; + + if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || + (error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) + goto done; + } + + /** submodule is initialized - now clone it **/ + /* override repo creation */ + clone_options.repository_cb = git_submodule_update_repo_init_cb; + clone_options.repository_cb_payload = sm; + + /* + * Do not perform checkout as part of clone, instead we + * will checkout the specific commit manually. + */ + clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; + + if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 || + (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0 || + (error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0) + goto done; + } else { + const git_oid *oid; + + /** + * Work dir is initialized - look up the commit in the parent repository's index, + * update the workdir contents of the subrepository, and set the subrepository's + * head to the new commit. + */ + if ((error = git_submodule_open(&sub_repo, sm)) < 0) + goto done; + + if ((oid = git_submodule_index_id(sm)) == NULL) { + git_error_set(GIT_ERROR_SUBMODULE, "could not get ID of submodule in index"); + error = -1; + goto done; + } + + /* Look up the target commit in the submodule. */ + if ((error = git_object_lookup(&target_commit, sub_repo, oid, GIT_OBJECT_COMMIT)) < 0) { + /* If it isn't found then fetch and try again. */ + if (error != GIT_ENOTFOUND || !update_options.allow_fetch || + (error = lookup_default_remote(&remote, sub_repo)) < 0 || + (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 || + (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJECT_COMMIT)) < 0) + goto done; + } + + if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 || + (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0) + goto done; + + /* Invalidate the wd flags as the workdir has been updated. */ + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_SCANNED); + } + +done: + git_str_dispose(&buf); + git_config_free(config); + git_object_free(target_commit); + git_remote_free(remote); + git_repository_free(sub_repo); + + return error; +} + +int git_submodule_init(git_submodule *sm, int overwrite) +{ + int error; + const char *val; + git_str key = GIT_STR_INIT, effective_submodule_url = GIT_STR_INIT; + git_config *cfg = NULL; + + if (!sm->url) { + git_error_set(GIT_ERROR_SUBMODULE, + "no URL configured for submodule '%s'", sm->name); + return -1; + } + + if ((error = git_repository_config(&cfg, sm->repo)) < 0) + return error; + + /* write "submodule.NAME.url" */ + + if ((error = git_submodule__resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 || + (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 || + (error = git_config__update_entry( + cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0) + goto cleanup; + + /* write "submodule.NAME.update" if not default */ + + val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? + NULL : submodule_update_to_str(sm->update); + + if ((error = git_str_printf(&key, "submodule.%s.update", sm->name)) < 0 || + (error = git_config__update_entry( + cfg, key.ptr, val, overwrite != 0, false)) < 0) + goto cleanup; + + /* success */ + +cleanup: + git_config_free(cfg); + git_str_dispose(&key); + git_str_dispose(&effective_submodule_url); + + return error; +} + +int git_submodule_sync(git_submodule *sm) +{ + git_str key = GIT_STR_INIT, url = GIT_STR_INIT, remote_name = GIT_STR_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); + return -1; + } + + /* copy URL over to config only if it already exists */ + if ((error = git_repository_config__weakptr(&cfg, sm->repo)) < 0 || + (error = git_str_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 (!(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 (lookup_head_remote_key(&remote_name, smrepo) == 0) { + if ((error = git_str_join3(&key, '.', "remote", remote_name.ptr, "url")) < 0) + goto out; + } else if ((error = git_str_sets(&key, "remote.origin.url")) < 0) { + goto out; + } + + if ((error = git_config__update_entry(cfg, key.ptr, url.ptr, true, false)) < 0) + goto out; + +out: + git_repository_free(smrepo); + git_str_dispose(&remote_name); + git_str_dispose(&key); + git_str_dispose(&url); + return error; +} + +static int git_submodule__open( + git_repository **subrepo, git_submodule *sm, bool bare) +{ + int error; + git_str path = GIT_STR_INIT; + unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; + const char *wd; + + GIT_ASSERT_ARG(sm); + GIT_ASSERT_ARG(subrepo); + + if (git_repository__ensure_not_bare( + sm->repo, "open submodule repository") < 0) + return GIT_EBAREREPO; + + wd = git_repository_workdir(sm->repo); + + if (git_str_join3(&path, '/', wd, sm->path, DOT_GIT) < 0) + return -1; + + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_SCANNED); + + if (bare) + flags |= GIT_REPOSITORY_OPEN_BARE; + + error = git_repository_open_ext(subrepo, path.ptr, flags, wd); + + /* if we opened the submodule successfully, grab HEAD OID, etc. */ + if (!error) { + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; + else + git_error_clear(); + } else if (git_fs_path_exists(path.ptr)) { + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | + GIT_SUBMODULE_STATUS_IN_WD; + } else { + git_str_rtruncate_at_char(&path, '/'); /* remove "/.git" */ + + if (git_fs_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + } + + git_str_dispose(&path); + + return error; +} + +int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, true); +} + +int git_submodule_open(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, false); +} + +static void submodule_update_from_index_entry( + git_submodule *sm, const git_index_entry *ie) +{ + bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0; + + if (!S_ISGITLINK(ie->mode)) { + if (!already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else { + if (already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; + else + git_oid_cpy(&sm->index_oid, &ie->id); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID; + } +} + +static int submodule_update_index(git_submodule *sm) +{ + git_index *index; + const git_index_entry *ie; + + if (git_repository_index__weakptr(&index, sm->repo) < 0) + return -1; + + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID); + + if (!(ie = git_index_get_bypath(index, sm->path, 0))) + return 0; + + submodule_update_from_index_entry(sm, ie); + + return 0; +} + +static void submodule_update_from_head_data( + git_submodule *sm, mode_t mode, const git_oid *id) +{ + if (!S_ISGITLINK(mode)) + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + else { + git_oid_cpy(&sm->head_oid, id); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID; + } +} + +static int submodule_update_head(git_submodule *submodule) +{ + git_tree *head = NULL; + git_tree_entry *te = NULL; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID); + + /* if we can't look up file in current head, then done */ + if (git_repository_head_tree(&head, submodule->repo) < 0 || + git_tree_entry_bypath(&te, head, submodule->path) < 0) + git_error_clear(); + else + submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te)); + + git_tree_entry_free(te); + git_tree_free(head); + return 0; +} + +int git_submodule_reload(git_submodule *sm, int force) +{ + git_config *mods = NULL; + int error; + + GIT_UNUSED(force); + + GIT_ASSERT_ARG(sm); + + 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 */ + goto out; + + if (git_repository_is_bare(sm->repo)) + goto out; + + /* refresh config data */ + if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) + goto out; + + if (mods != NULL && (error = submodule_read_config(sm, mods)) < 0) + goto out; + + /* refresh wd data */ + sm->flags &= + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_FLAGS); + + 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; +} + +static void submodule_copy_oid_maybe( + git_oid *tgt, const git_oid *src, bool valid) +{ + if (tgt) { + if (valid) + memcpy(tgt, src, sizeof(*tgt)); + else + memset(tgt, 0, sizeof(*tgt)); + } +} + +int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign) +{ + unsigned int status; + git_repository *smrepo = NULL; + + if (ign == GIT_SUBMODULE_IGNORE_UNSPECIFIED) + ign = sm->ignore; + + /* only return location info if ignore == all */ + if (ign == GIT_SUBMODULE_IGNORE_ALL) { + *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS); + return 0; + } + + /* If the user has requested caching submodule state, performing these + * expensive operations (especially `submodule_update_head`, which is + * bottlenecked on `git_repository_head_tree`) eliminates much of the + * advantage. We will, therefore, interpret the request for caching to + * apply here to and skip them. + */ + + if (sm->repo->submodule_cache == NULL) { + /* refresh the index OID */ + if (submodule_update_index(sm) < 0) + return -1; + + /* refresh the HEAD OID */ + if (submodule_update_head(sm) < 0) + return -1; + } + + /* for ignore == dirty, don't scan the working directory */ + if (ign == GIT_SUBMODULE_IGNORE_DIRTY) { + /* git_submodule_open_bare will load WD OID data */ + if (git_submodule_open_bare(&smrepo, sm) < 0) + git_error_clear(); + else + git_repository_free(smrepo); + smrepo = NULL; + } else if (git_submodule_open(&smrepo, sm) < 0) { + git_error_clear(); + smrepo = NULL; + } + + status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags); + + submodule_get_index_status(&status, sm); + submodule_get_wd_status(&status, sm, smrepo, ign); + + git_repository_free(smrepo); + + *out_status = status; + + submodule_copy_oid_maybe(out_head_id, &sm->head_oid, + (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0); + submodule_copy_oid_maybe(out_index_id, &sm->index_oid, + (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0); + submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid, + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0); + + return 0; +} + +int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore) +{ + git_submodule *sm; + int error; + + GIT_ASSERT_ARG(status); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_submodule_lookup(&sm, repo, name)) < 0) + return error; + + error = git_submodule__status(status, NULL, NULL, NULL, sm, ignore); + git_submodule_free(sm); + + return error; +} + +int git_submodule_location(unsigned int *location, git_submodule *sm) +{ + GIT_ASSERT_ARG(location); + GIT_ASSERT_ARG(sm); + + return git_submodule__status( + location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL); +} + +/* + * INTERNAL FUNCTIONS + */ + +static int submodule_alloc( + git_submodule **out, git_repository *repo, const char *name) +{ + size_t namelen; + git_submodule *sm; + + if (!name || !(namelen = strlen(name))) { + git_error_set(GIT_ERROR_SUBMODULE, "invalid submodule name"); + return -1; + } + + sm = git__calloc(1, sizeof(git_submodule)); + GIT_ERROR_CHECK_ALLOC(sm); + + sm->name = sm->path = git__strdup(name); + if (!sm->name) { + git__free(sm); + return -1; + } + + GIT_REFCOUNT_INC(sm); + sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE; + sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT; + sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO; + sm->repo = repo; + sm->branch = NULL; + + *out = sm; + return 0; +} + +static void submodule_release(git_submodule *sm) +{ + if (!sm) + return; + + if (sm->repo) { + sm->repo = NULL; + } + + if (sm->path != sm->name) + git__free(sm->path); + git__free(sm->name); + git__free(sm->url); + git__free(sm->branch); + git__memzero(sm, sizeof(*sm)); + git__free(sm); +} + +int git_submodule_dup(git_submodule **out, git_submodule *source) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(source); + + GIT_REFCOUNT_INC(source); + + *out = source; + return 0; +} + +void git_submodule_free(git_submodule *sm) +{ + if (!sm) + return; + GIT_REFCOUNT_DEC(sm, submodule_release); +} + +static int submodule_config_error(const char *property, const char *value) +{ + git_error_set(GIT_ERROR_INVALID, + "invalid value for submodule '%s' property: '%s'", property, value); + return -1; +} + +int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) { + *out = GIT_SUBMODULE_IGNORE_NONE; + return submodule_config_error("ignore", value); + } + + *out = (git_submodule_ignore_t)val; + return 0; +} + +int git_submodule_parse_update(git_submodule_update_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) { + *out = GIT_SUBMODULE_UPDATE_CHECKOUT; + return submodule_config_error("update", value); + } + + *out = (git_submodule_update_t)val; + return 0; +} + +static int submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) { + *out = GIT_SUBMODULE_RECURSE_YES; + return submodule_config_error("recurse", value); + } + + *out = (git_submodule_recurse_t)val; + return 0; +} + +static int get_value(const char **out, git_config *cfg, git_str *buf, const char *name, const char *field) +{ + int error; + + git_str_clear(buf); + + if ((error = git_str_printf(buf, "submodule.%s.%s", name, field)) < 0 || + (error = git_config_get_string(out, cfg, buf->ptr)) < 0) + return error; + + return error; +} + +static bool looks_like_command_line_option(const char *s) +{ + if (s && s[0] == '-') + return true; + + return false; +} + +static int submodule_read_config(git_submodule *sm, git_config *cfg) +{ + git_str key = GIT_STR_INIT; + const char *value; + int error, in_config = 0; + + /* + * TODO: Look up path in index and if it is present but not a GITLINK + * then this should be deleted (at least to match git's behavior) + */ + + if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { + in_config = 1; + /* We would warn here if we had that API */ + if (!looks_like_command_line_option(value)) { + /* + * TODO: if case insensitive filesystem, then the following strcmp + * should be strcasecmp + */ + if (strcmp(sm->name, value) != 0) { + if (sm->path != sm->name) + git__free(sm->path); + sm->path = git__strdup(value); + GIT_ERROR_CHECK_ALLOC(sm->path); + } + + } + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { + /* We would warn here if we had that API */ + if (!looks_like_command_line_option(value)) { + in_config = 1; + sm->url = git__strdup(value); + GIT_ERROR_CHECK_ALLOC(sm->url); + } + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { + in_config = 1; + sm->branch = git__strdup(value); + GIT_ERROR_CHECK_ALLOC(sm->branch); + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_update(&sm->update, value)) < 0) + goto cleanup; + sm->update_default = sm->update; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { + in_config = 1; + if ((error = submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) + goto cleanup; + sm->fetch_recurse_default = sm->fetch_recurse; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) + goto cleanup; + sm->ignore_default = sm->ignore; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if (in_config) + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + + error = 0; + +cleanup: + git_str_dispose(&key); + return error; +} + +static int submodule_load_each(const git_config_entry *entry, void *payload) +{ + lfc_data *data = payload; + const char *namestart, *property; + git_strmap *map = data->map; + git_str name = GIT_STR_INIT; + git_submodule *sm; + int error, isvalid; + + if (git__prefixcmp(entry->name, "submodule.") != 0) + return 0; + + namestart = entry->name + strlen("submodule."); + property = strrchr(namestart, '.'); + + if (!property || (property == namestart)) + return 0; + + property++; + + if ((error = git_str_set(&name, namestart, property - namestart -1)) < 0) + return error; + + isvalid = git_submodule_name_is_valid(data->repo, name.ptr, 0); + if (isvalid <= 0) { + error = isvalid; + goto done; + } + + /* + * Now that we have the submodule's name, we can use that to + * figure out whether it's in the map. If it's not, we create + * a new submodule, load the config and insert it. If it's + * already inserted, we've already loaded it, so we skip. + */ + if (git_strmap_exists(map, name.ptr)) { + error = 0; + goto done; + } + + if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) + goto done; + + if ((error = submodule_read_config(sm, data->mods)) < 0) { + git_submodule_free(sm); + goto done; + } + + if ((error = git_strmap_set(map, sm->name, sm)) < 0) + goto done; + + error = 0; + +done: + git_str_dispose(&name); + return error; +} + +static int submodule_load_from_wd_lite(git_submodule *sm) +{ + git_str path = GIT_STR_INIT; + + if (git_repository_workdir_path(&path, sm->repo, sm->path) < 0) + return -1; + + if (git_fs_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (git_fs_path_contains(&path, DOT_GIT)) + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; + + git_str_dispose(&path); + return 0; +} + +/** + * Requests a snapshot of $WORK_TREE/.gitmodules. + * + * Returns GIT_ENOTFOUND in case no .gitmodules file exist + */ +static int gitmodules_snapshot(git_config **snap, git_repository *repo) +{ + git_config *mods = NULL; + git_str path = GIT_STR_INIT; + int error; + + if (git_repository_workdir(repo) == NULL) + return GIT_ENOTFOUND; + + if ((error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) + return error; + + if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0) + goto cleanup; + git_str_dispose(&path); + + if ((error = git_config_snapshot(snap, mods)) < 0) + goto cleanup; + + error = 0; + +cleanup: + if (mods) + git_config_free(mods); + git_str_dispose(&path); + + return error; +} + +static git_config_backend *open_gitmodules( + git_repository *repo, + int okay_to_create) +{ + git_str path = GIT_STR_INIT; + git_config_backend *mods = NULL; + + if (git_repository_workdir(repo) != NULL) { + if (git_repository_workdir_path(&path, repo, GIT_MODULES_FILE) != 0) + return NULL; + + if (okay_to_create || git_fs_path_isfile(path.ptr)) { + /* git_config_backend_from_file should only fail if OOM */ + if (git_config_backend_from_file(&mods, path.ptr) < 0) + mods = NULL; + /* open should only fail here if the file is malformed */ + else if (git_config_backend_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) { + git_config_backend_free(mods); + mods = NULL; + } + } + } + + git_str_dispose(&path); + + return mods; +} + +/* Lookup name of remote of the local tracking branch HEAD points to */ +static int lookup_head_remote_key(git_str *remote_name, git_repository *repo) +{ + int error; + git_reference *head = NULL; + git_str upstream_name = GIT_STR_INIT; + + /* lookup and dereference HEAD */ + if ((error = git_repository_head(&head, repo)) < 0) + return error; + + /** + * If head does not refer to a branch, then return + * GIT_ENOTFOUND to indicate that we could not find + * a remote key for the local tracking branch HEAD points to. + **/ + if (!git_reference_is_branch(head)) { + git_error_set(GIT_ERROR_INVALID, + "HEAD does not refer to a branch."); + error = GIT_ENOTFOUND; + goto done; + } + + /* lookup remote tracking branch of HEAD */ + if ((error = git_branch__upstream_name( + &upstream_name, + repo, + git_reference_name(head))) < 0) + goto done; + + /* lookup remote of remote tracking branch */ + if ((error = git_branch__remote_name(remote_name, repo, upstream_name.ptr)) < 0) + goto done; + +done: + git_str_dispose(&upstream_name); + git_reference_free(head); + + return error; +} + +/* Lookup the remote of the local tracking branch HEAD points to */ +static int lookup_head_remote(git_remote **remote, git_repository *repo) +{ + int error; + git_str remote_name = GIT_STR_INIT; + + /* lookup remote of remote tracking branch name */ + if (!(error = lookup_head_remote_key(&remote_name, repo))) + error = git_remote_lookup(remote, repo, remote_name.ptr); + + git_str_dispose(&remote_name); + + return error; +} + +/* Lookup remote, either from HEAD or fall back on origin */ +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 || error == GIT_EUNBORNBRANCH) + error = git_remote_lookup(remote, repo, "origin"); + + if (error == GIT_ENOTFOUND) + git_error_set( + GIT_ERROR_SUBMODULE, + "cannot get default remote for submodule - no local tracking " + "branch for HEAD and origin does not exist"); + + return error; +} + +static int get_url_base(git_str *url, git_repository *repo) +{ + int error; + git_worktree *wt = NULL; + git_remote *remote = NULL; + + if ((error = lookup_default_remote(&remote, repo)) == 0) { + error = git_str_sets(url, git_remote_url(remote)); + goto out; + } else if (error != GIT_ENOTFOUND) + goto out; + else + git_error_clear(); + + /* if repository does not have a default remote, use workdir instead */ + if (git_repository_is_worktree(repo)) { + if ((error = git_worktree_open_from_repository(&wt, repo)) < 0) + goto out; + error = git_str_sets(url, wt->parent_path); + } else { + error = git_str_sets(url, git_repository_workdir(repo)); + } + +out: + git_remote_free(remote); + git_worktree_free(wt); + + return error; +} + +static void submodule_get_index_status(unsigned int *status, git_submodule *sm) +{ + const git_oid *head_oid = git_submodule_head_id(sm); + const git_oid *index_oid = git_submodule_index_id(sm); + + *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS; + + if (!head_oid) { + if (index_oid) + *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; + } + else if (!index_oid) + *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; + else if (!git_oid_equal(head_oid, index_oid)) + *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; +} + + +static void submodule_get_wd_status( + unsigned int *status, + git_submodule *sm, + git_repository *sm_repo, + git_submodule_ignore_t ign) +{ + const git_oid *index_oid = git_submodule_index_id(sm); + const git_oid *wd_oid = + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; + git_tree *sm_head = NULL; + git_index *index = NULL; + git_diff_options opt = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + + *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; + + if (!index_oid) { + if (wd_oid) + *status |= GIT_SUBMODULE_STATUS_WD_ADDED; + } + else if (!wd_oid) { + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 && + (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED; + else + *status |= GIT_SUBMODULE_STATUS_WD_DELETED; + } + else if (!git_oid_equal(index_oid, wd_oid)) + *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; + + /* if we have no repo, then we're done */ + if (!sm_repo) + return; + + /* the diffs below could be optimized with an early termination + * option to the git_diff functions, but for now this is sufficient + * (and certainly no worse that what core git does). + */ + + if (ign == GIT_SUBMODULE_IGNORE_NONE) + opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + (void)git_repository_index__weakptr(&index, sm_repo); + + /* if we don't have an unborn head, check diff with index */ + if (git_repository_head_tree(&sm_head, sm_repo) < 0) + git_error_clear(); + else { + /* perform head to index diff on submodule */ + if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0) + git_error_clear(); + else { + if (git_diff_num_deltas(diff) > 0) + *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; + git_diff_free(diff); + diff = NULL; + } + + git_tree_free(sm_head); + } + + /* perform index-to-workdir diff on submodule */ + if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0) + git_error_clear(); + else { + size_t untracked = + git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); + + if (untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; + + if (git_diff_num_deltas(diff) != untracked) + *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; + + git_diff_free(diff); + diff = NULL; + } +} diff --git a/src/libgit2/submodule.h b/src/libgit2/submodule.h new file mode 100644 index 000000000..7fa982486 --- /dev/null +++ b/src/libgit2/submodule.h @@ -0,0 +1,164 @@ +/* + * 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_submodule_h__ +#define INCLUDE_submodule_h__ + +#include "common.h" + +#include "git2/submodule.h" +#include "git2/repository.h" +#include "futils.h" + +/* Notes: + * + * Submodule information can be in four places: the index, the config files + * (both .git/config and .gitmodules), the HEAD tree, and the working + * directory. + * + * In the index: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the HEAD tree: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the config files: + * - submodule is found by submodule "name" which is usually the path + * - may be missing or present + * - will have a name, path, url, and other properties + * + * In the working directory: + * - submodule is found by path + * - may be missing, an empty directory, a checked out directory, + * or of the wrong type + * - if checked out, will have a HEAD oid + * - if checked out, will have git history that can be used to compare oids + * - if checked out, may have modified files and/or untracked files + */ + +/** + * Description of submodule + * + * This record describes a submodule found in a repository. There should be + * an entry for every submodule found in the HEAD and index, and for every + * submodule described in .gitmodules. The fields are as follows: + * + * - `rc` tracks the refcount of how many hash table entries in the + * git_submodule_cache there are for this submodule. It only comes into + * play if the name and path of the submodule differ. + * + * - `name` is the name of the submodule from .gitmodules. + * - `path` is the path to the submodule from the repo root. It is almost + * always the same as `name`. + * - `url` is the url for the submodule. + * - `update` is a git_submodule_update_t value - see gitmodules(5) update. + * - `update_default` is the update value from the config + * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. + * - `ignore_default` is the ignore value from the config + * - `fetch_recurse` is a git_submodule_recurse_t value - see gitmodules(5) + * fetchRecurseSubmodules. + * - `fetch_recurse_default` is the recurse value from the config + * + * - `repo` is the parent repository that contains this submodule. + * - `flags` after for internal use, tracking where this submodule has been + * found (head, index, config, workdir) and known status info, etc. + * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. + * - `index_oid` is the SHA1 for the submodule recorded in the index. + * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. + * + * If the submodule has been added to .gitmodules but not yet git added, + * then the `index_oid` will be zero but still marked valid. If the + * submodule has been deleted, but the delete has not been committed yet, + * then the `index_oid` will be set, but the `url` will be NULL. + */ +struct git_submodule { + git_refcount rc; + + /* information from config */ + char *name; + char *path; /* important: may just point to "name" string */ + char *url; + char *branch; + git_submodule_update_t update; + git_submodule_update_t update_default; + git_submodule_ignore_t ignore; + git_submodule_ignore_t ignore_default; + git_submodule_recurse_t fetch_recurse; + git_submodule_recurse_t fetch_recurse_default; + + /* internal information */ + git_repository *repo; + uint32_t flags; + git_oid head_oid; + git_oid index_oid; + git_oid wd_oid; +}; + +/* Additional flags on top of public GIT_SUBMODULE_STATUS values */ +enum { + GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20), + GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21), + GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22), + GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23), + GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24), + GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25), + GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26), + GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27) +}; + +#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ + ((S) & ~(0xFFFFFFFFu << 20)) + +/* Initialize an external submodule cache for the provided repo. */ +extern int git_submodule_cache_init(git_strmap **out, git_repository *repo); + +/* Release the resources of the submodule cache. */ +extern int git_submodule_cache_free(git_strmap *cache); + +/* Submodule lookup with an explicit cache */ +extern int git_submodule__lookup_with_cache( + git_submodule **out, git_repository *repo, const char *path, git_strmap *cache); + +/* Internal status fn returns status and optionally the various OIDs */ +extern int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign); + +/* Open submodule repository as bare repo for quick HEAD check, etc. */ +extern int git_submodule_open_bare( + git_repository **repo, + git_submodule *submodule); + +extern int git_submodule_parse_ignore( + git_submodule_ignore_t *out, const char *value); +extern int git_submodule_parse_update( + git_submodule_update_t *out, const char *value); + +extern int git_submodule__map( + git_repository *repo, + git_strmap *map); + +/** + * Check whether a submodule's name is valid. + * + * Check the path against the path validity rules, either the filesystem + * defaults (like checkout does) or whichever you want to compare against. + * + * @param repo the repository which contains the submodule + * @param name the name to check + * @param flags the `GIT_PATH` flags to use for the check (0 to use filesystem defaults) + */ +extern int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags); + +#endif diff --git a/src/libgit2/sysdir.c b/src/libgit2/sysdir.c new file mode 100644 index 000000000..450cb509b --- /dev/null +++ b/src/libgit2/sysdir.c @@ -0,0 +1,363 @@ +/* + * 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 "sysdir.h" + +#include "runtime.h" +#include "str.h" +#include "fs_path.h" +#include +#if GIT_WIN32 +#include "win32/findfile.h" +#else +#include +#include +#endif + +static int git_sysdir_guess_programdata_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_programdata_dirs(out); +#else + git_str_clear(out); + return 0; +#endif +} + +static int git_sysdir_guess_system_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_system_dirs(out, "etc"); +#else + return git_str_sets(out, "/etc"); +#endif +} + +#ifndef GIT_WIN32 +static int get_passwd_home(git_str *out, uid_t uid) +{ + struct passwd pwd, *pwdptr; + char *buf = NULL; + long buflen; + int error; + + GIT_ASSERT_ARG(out); + + if ((buflen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) + buflen = 1024; + + do { + buf = git__realloc(buf, buflen); + error = getpwuid_r(uid, &pwd, buf, buflen, &pwdptr); + buflen *= 2; + } while (error == ERANGE && buflen <= 8192); + + if (error) { + git_error_set(GIT_ERROR_OS, "failed to get passwd entry"); + goto out; + } + + if (!pwdptr) { + git_error_set(GIT_ERROR_OS, "no passwd entry found for user"); + goto out; + } + + if ((error = git_str_puts(out, pwdptr->pw_dir)) < 0) + goto out; + +out: + git__free(buf); + return error; +} +#endif + +static int git_sysdir_guess_global_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_global_dirs(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 (!sandbox_id && uid == euid) + error = git__getenv(out, "HOME"); + else + error = get_passwd_home(out, euid); + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + return error; +#endif +} + +static int git_sysdir_guess_xdg_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_xdg_dirs(out); +#else + git_str env = GIT_STR_INIT; + int error; + uid_t uid, euid; + + uid = getuid(); + euid = geteuid(); + + /* + * In case we are running setuid, only look up passwd + * directory of the effective user. + */ + if (uid == euid) { + if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) + error = git_str_joinpath(out, env.ptr, "git"); + + if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) + error = git_str_joinpath(out, env.ptr, ".config/git"); + } else { + if ((error = get_passwd_home(&env, euid)) == 0) + error = git_str_joinpath(out, env.ptr, ".config/git"); + } + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + git_str_dispose(&env); + return error; +#endif +} + +static int git_sysdir_guess_template_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_system_dirs(out, "share/git-core/templates"); +#else + return git_str_sets(out, "/usr/share/git-core/templates"); +#endif +} + +struct git_sysdir__dir { + git_str buf; + int (*guess)(git_str *out); +}; + +static struct git_sysdir__dir git_sysdir__dirs[] = { + { GIT_STR_INIT, git_sysdir_guess_system_dirs }, + { GIT_STR_INIT, git_sysdir_guess_global_dirs }, + { GIT_STR_INIT, git_sysdir_guess_xdg_dirs }, + { GIT_STR_INIT, git_sysdir_guess_programdata_dirs }, + { GIT_STR_INIT, git_sysdir_guess_template_dirs }, +}; + +static void git_sysdir_global_shutdown(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(git_sysdir__dirs); ++i) + git_str_dispose(&git_sysdir__dirs[i].buf); +} + +int git_sysdir_global_init(void) +{ + size_t i; + int error = 0; + + for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); i++) + error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); + + if (error) + return error; + + return git_runtime_shutdown_register(git_sysdir_global_shutdown); +} + +int git_sysdir_reset(void) +{ + size_t i; + int error = 0; + + for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); ++i) { + git_str_dispose(&git_sysdir__dirs[i].buf); + error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); + } + + return error; +} + +static int git_sysdir_check_selector(git_sysdir_t which) +{ + if (which < ARRAY_SIZE(git_sysdir__dirs)) + return 0; + + git_error_set(GIT_ERROR_INVALID, "config directory selector out of range"); + return -1; +} + + +int git_sysdir_get(const git_str **out, git_sysdir_t which) +{ + GIT_ASSERT_ARG(out); + + *out = NULL; + + GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); + + *out = &git_sysdir__dirs[which].buf; + return 0; +} + +#define PATH_MAGIC "$PATH" + +int git_sysdir_set(git_sysdir_t which, const char *search_path) +{ + const char *expand_path = NULL; + git_str merge = GIT_STR_INIT; + + GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); + + if (search_path != NULL) + expand_path = strstr(search_path, PATH_MAGIC); + + /* reset the default if this path has been cleared */ + if (!search_path) + git_sysdir__dirs[which].guess(&git_sysdir__dirs[which].buf); + + /* if $PATH is not referenced, then just set the path */ + if (!expand_path) { + if (search_path) + git_str_sets(&git_sysdir__dirs[which].buf, search_path); + + goto done; + } + + /* otherwise set to join(before $PATH, old value, after $PATH) */ + if (expand_path > search_path) + git_str_set(&merge, search_path, expand_path - search_path); + + if (git_str_len(&git_sysdir__dirs[which].buf)) + git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, + merge.ptr, git_sysdir__dirs[which].buf.ptr); + + expand_path += strlen(PATH_MAGIC); + if (*expand_path) + git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path); + + git_str_swap(&git_sysdir__dirs[which].buf, &merge); + git_str_dispose(&merge); + +done: + if (git_str_oom(&git_sysdir__dirs[which].buf)) + return -1; + + return 0; +} + +static int git_sysdir_find_in_dirlist( + git_str *path, + const char *name, + git_sysdir_t which, + const char *label) +{ + size_t len; + const char *scan, *next = NULL; + const git_str *syspath; + + GIT_ERROR_CHECK_ERROR(git_sysdir_get(&syspath, which)); + if (!syspath || !git_str_len(syspath)) + goto done; + + for (scan = git_str_cstr(syspath); scan; scan = next) { + /* find unescaped separator or end of string */ + for (next = scan; *next; ++next) { + if (*next == GIT_PATH_LIST_SEPARATOR && + (next <= scan || next[-1] != '\\')) + break; + } + + len = (size_t)(next - scan); + next = (*next ? next + 1 : NULL); + if (!len) + continue; + + GIT_ERROR_CHECK_ERROR(git_str_set(path, scan, len)); + if (name) + GIT_ERROR_CHECK_ERROR(git_str_joinpath(path, path->ptr, name)); + + if (git_fs_path_exists(path->ptr)) + return 0; + } + +done: + if (name) + git_error_set(GIT_ERROR_OS, "the %s file '%s' doesn't exist", label, name); + else + git_error_set(GIT_ERROR_OS, "the %s directory doesn't exist", label); + git_str_dispose(path); + return GIT_ENOTFOUND; +} + +int git_sysdir_find_system_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_SYSTEM, "system"); +} + +int git_sysdir_find_global_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_GLOBAL, "global"); +} + +int git_sysdir_find_xdg_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_XDG, "global/xdg"); +} + +int git_sysdir_find_programdata_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_PROGRAMDATA, "ProgramData"); +} + +int git_sysdir_find_template_dir(git_str *path) +{ + return git_sysdir_find_in_dirlist( + path, NULL, GIT_SYSDIR_TEMPLATE, "template"); +} + +int git_sysdir_expand_global_file(git_str *path, const char *filename) +{ + int error; + + if ((error = git_sysdir_find_global_file(path, NULL)) == 0) { + if (filename) + error = git_str_joinpath(path, path->ptr, filename); + } + + return error; +} diff --git a/src/libgit2/sysdir.h b/src/libgit2/sysdir.h new file mode 100644 index 000000000..568f27940 --- /dev/null +++ b/src/libgit2/sysdir.h @@ -0,0 +1,113 @@ +/* + * 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_sysdir_h__ +#define INCLUDE_sysdir_h__ + +#include "common.h" + +#include "posix.h" +#include "str.h" + +/** + * Find a "global" file (i.e. one in a user's home directory). + * + * @param path buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_global_file(git_str *path, const char *filename); + +/** + * Find an "XDG" file (i.e. one in user's XDG config path). + * + * @param path buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_xdg_file(git_str *path, const char *filename); + +/** + * Find a "system" file (i.e. one shared for all users of the system). + * + * @param path buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_system_file(git_str *path, const char *filename); + +/** + * Find a "ProgramData" file (i.e. one in %PROGRAMDATA%) + * + * @param path buffer to write the full path into + * @param filename name of file to find in the ProgramData directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_programdata_file(git_str *path, const char *filename); + +/** + * Find template directory. + * + * @param path buffer to write the full path into + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_template_dir(git_str *path); + +/** + * Expand the name of a "global" file (i.e. one in a user's home + * directory). Unlike `find_global_file` (above), this makes no + * attempt to check for the existence of the file, and is useful if + * you want the full path regardless of existence. + * + * @param path buffer to write the full path into + * @param filename name of file in the home directory + * @return 0 on success or -1 on error + */ +extern int git_sysdir_expand_global_file(git_str *path, const char *filename); + +typedef enum { + GIT_SYSDIR_SYSTEM = 0, + GIT_SYSDIR_GLOBAL = 1, + GIT_SYSDIR_XDG = 2, + GIT_SYSDIR_PROGRAMDATA = 3, + GIT_SYSDIR_TEMPLATE = 4, + GIT_SYSDIR__MAX = 5 +} git_sysdir_t; + +/** + * Configures global data for configuration file search paths. + * + * @return 0 on success, <0 on failure + */ +extern int git_sysdir_global_init(void); + +/** + * Get the search path for global/system/xdg files + * + * @param out pointer to git_str containing search path + * @param which which list of paths to return + * @return 0 on success, <0 on failure + */ +extern int git_sysdir_get(const git_str **out, git_sysdir_t which); + +/** + * Set search paths for global/system/xdg files + * + * The first occurrence of the magic string "$PATH" in the new value will + * be replaced with the old value of the search path. + * + * @param which Which search path to modify + * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR) + * @return 0 on success, <0 on failure (allocation error) + */ +extern int git_sysdir_set(git_sysdir_t which, const char *paths); + +/** + * Reset search paths for global/system/xdg files. + */ +extern int git_sysdir_reset(void); + +#endif diff --git a/src/libgit2/tag.c b/src/libgit2/tag.c new file mode 100644 index 000000000..792155a4b --- /dev/null +++ b/src/libgit2/tag.c @@ -0,0 +1,580 @@ +/* + * 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 "tag.h" + +#include "commit.h" +#include "signature.h" +#include "wildmatch.h" +#include "git2/object.h" +#include "git2/repository.h" +#include "git2/signature.h" +#include "git2/odb_backend.h" + +void git_tag__free(void *_tag) +{ + git_tag *tag = _tag; + git_signature_free(tag->tagger); + git__free(tag->message); + git__free(tag->tag_name); + git__free(tag); +} + +int git_tag_target(git_object **target, const git_tag *t) +{ + GIT_ASSERT_ARG(t); + return git_object_lookup(target, t->object.repo, &t->target, t->type); +} + +const git_oid *git_tag_target_id(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); + return &t->target; +} + +git_object_t git_tag_target_type(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, GIT_OBJECT_INVALID); + return t->type; +} + +const char *git_tag_name(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); + return t->tag_name; +} + +const git_signature *git_tag_tagger(const git_tag *t) +{ + return t->tagger; +} + +const char *git_tag_message(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); + return t->message; +} + +static int tag_error(const char *str) +{ + git_error_set(GIT_ERROR_TAG, "failed to parse tag: %s", str); + return GIT_EINVALID; +} + +static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) +{ + static const char *tag_types[] = { + NULL, "commit\n", "tree\n", "blob\n", "tag\n" + }; + size_t text_len, alloc_len; + const char *search; + unsigned int i; + int error; + + if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) + return tag_error("object field invalid"); + + if (buffer + 5 >= buffer_end) + return tag_error("object too short"); + + if (memcmp(buffer, "type ", 5) != 0) + return tag_error("type field not found"); + buffer += 5; + + tag->type = GIT_OBJECT_INVALID; + + for (i = 1; i < ARRAY_SIZE(tag_types); ++i) { + size_t type_length = strlen(tag_types[i]); + + if (buffer + type_length >= buffer_end) + return tag_error("object too short"); + + if (memcmp(buffer, tag_types[i], type_length) == 0) { + tag->type = i; + buffer += type_length; + break; + } + } + + if (tag->type == GIT_OBJECT_INVALID) + return tag_error("invalid object type"); + + if (buffer + 4 >= buffer_end) + return tag_error("object too short"); + + if (memcmp(buffer, "tag ", 4) != 0) + return tag_error("tag field not found"); + + buffer += 4; + + search = memchr(buffer, '\n', buffer_end - buffer); + if (search == NULL) + return tag_error("object too short"); + + text_len = search - buffer; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); + tag->tag_name = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(tag->tag_name); + + memcpy(tag->tag_name, buffer, text_len); + tag->tag_name[text_len] = '\0'; + + buffer = search + 1; + + tag->tagger = NULL; + if (buffer < buffer_end && *buffer != '\n') { + tag->tagger = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(tag->tagger); + + if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n')) < 0) + return error; + } + + tag->message = NULL; + if (buffer < buffer_end) { + /* If we're not at the end of the header, search for it */ + if(*buffer != '\n') { + search = git__memmem(buffer, buffer_end - buffer, + "\n\n", 2); + if (search) + buffer = search + 1; + else + return tag_error("tag contains no message"); + } + + text_len = buffer_end - ++buffer; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); + tag->message = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(tag->message); + + memcpy(tag->message, buffer, text_len); + tag->message[text_len] = '\0'; + } + + return 0; +} + +int git_tag__parse_raw(void *_tag, const char *data, size_t size) +{ + return tag_parse(_tag, data, data + size); +} + +int git_tag__parse(void *_tag, git_odb_object *odb_obj) +{ + git_tag *tag = _tag; + const char *buffer = git_odb_object_data(odb_obj); + const char *buffer_end = buffer + git_odb_object_size(odb_obj); + + return tag_parse(tag, buffer, buffer_end); +} + +static int retrieve_tag_reference( + git_reference **tag_reference_out, + git_str *ref_name_out, + git_repository *repo, + const char *tag_name) +{ + git_reference *tag_ref; + int error; + + *tag_reference_out = NULL; + + if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; + + error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); + if (error < 0) + return error; /* Be it not foundo or corrupted */ + + *tag_reference_out = tag_ref; + + return 0; +} + +static int retrieve_tag_reference_oid( + git_oid *oid, + git_str *ref_name_out, + git_repository *repo, + const char *tag_name) +{ + if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; + + return git_reference_name_to_id(oid, repo, ref_name_out->ptr); +} + +static int write_tag_annotation( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message) +{ + git_str tag = GIT_STR_INIT; + git_odb *odb; + + git_oid__writebuf(&tag, "object ", git_object_id(target)); + git_str_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); + git_str_printf(&tag, "tag %s\n", tag_name); + git_signature__writebuf(&tag, "tagger ", tagger); + git_str_putc(&tag, '\n'); + + if (git_str_puts(&tag, message) < 0) + goto on_error; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; + + if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJECT_TAG) < 0) + goto on_error; + + git_str_dispose(&tag); + return 0; + +on_error: + git_str_dispose(&tag); + git_error_set(GIT_ERROR_OBJECT, "failed to create tag annotation"); + return -1; +} + +static bool tag_name_is_valid(const char *tag_name) +{ + /* + * Discourage tag name starting with dash, + * https://github.com/git/git/commit/4f0accd638b8d2 + */ + return tag_name[0] != '-'; +} + +static int git_tag_create__internal( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message, + int allow_ref_overwrite, + int create_tag_annotation) +{ + git_reference *new_ref = NULL; + git_str ref_name = GIT_STR_INIT; + + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(tag_name); + GIT_ASSERT_ARG(target); + GIT_ASSERT_ARG(!create_tag_annotation || (tagger && message)); + + if (git_object_owner(target) != repo) { + git_error_set(GIT_ERROR_INVALID, "the given target does not belong to this repository"); + return -1; + } + + if (!tag_name_is_valid(tag_name)) { + git_error_set(GIT_ERROR_TAG, "'%s' is not a valid tag name", tag_name); + return -1; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explicitly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + git_str_dispose(&ref_name); + git_error_set(GIT_ERROR_TAG, "tag already exists"); + return GIT_EEXISTS; + } + + if (create_tag_annotation) { + if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) + return -1; + } else + git_oid_cpy(oid, git_object_id(target)); + + error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); + +cleanup: + git_reference_free(new_ref); + git_str_dispose(&ref_name); + return error; +} + +int git_tag_create( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message, + int allow_ref_overwrite) +{ + return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); +} + +int git_tag_annotation_create( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message) +{ + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(tag_name); + GIT_ASSERT_ARG(target); + GIT_ASSERT_ARG(tagger); + GIT_ASSERT_ARG(message); + + return write_tag_annotation(oid, repo, tag_name, target, tagger, message); +} + +int git_tag_create_lightweight( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + int allow_ref_overwrite) +{ + return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); +} + +int git_tag_create_from_buffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) +{ + git_tag tag; + int error; + git_odb *odb; + git_odb_stream *stream; + git_odb_object *target_obj; + + git_reference *new_ref = NULL; + git_str ref_name = GIT_STR_INIT; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(buffer); + + memset(&tag, 0, sizeof(tag)); + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + /* validate the buffer */ + if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0) + return -1; + + /* validate the target */ + if (git_odb_read(&target_obj, odb, &tag.target) < 0) + goto on_error; + + if (tag.type != target_obj->cached.type) { + git_error_set(GIT_ERROR_TAG, "the type for the given target is invalid"); + goto on_error; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + /* We don't need these objects after this */ + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explicitly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + git_error_set(GIT_ERROR_TAG, "tag already exists"); + return GIT_EEXISTS; + } + + /* write the buffer */ + if ((error = git_odb_open_wstream( + &stream, odb, strlen(buffer), GIT_OBJECT_TAG)) < 0) + return error; + + if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) + error = git_odb_stream_finalize_write(oid, stream); + + git_odb_stream_free(stream); + + if (error < 0) { + git_str_dispose(&ref_name); + return error; + } + + error = git_reference_create( + &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); + + git_reference_free(new_ref); + git_str_dispose(&ref_name); + + return error; + +on_error: + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + return -1; +} + +int git_tag_delete(git_repository *repo, const char *tag_name) +{ + git_reference *tag_ref; + git_str ref_name = GIT_STR_INIT; + int error; + + error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); + + git_str_dispose(&ref_name); + + if (error < 0) + return error; + + error = git_reference_delete(tag_ref); + + git_reference_free(tag_ref); + + return error; +} + +typedef struct { + git_repository *repo; + git_tag_foreach_cb cb; + void *cb_data; +} tag_cb_data; + +static int tags_cb(const char *ref, void *data) +{ + int error; + git_oid oid; + tag_cb_data *d = (tag_cb_data *)data; + + if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0) + return 0; /* no tag */ + + if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) { + if ((error = d->cb(ref, &oid, d->cb_data)) != 0) + git_error_set_after_callback_function(error, "git_tag_foreach"); + } + + return error; +} + +int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) +{ + tag_cb_data data; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cb); + + data.cb = cb; + data.cb_data = cb_data; + data.repo = repo; + + return git_reference_foreach_name(repo, &tags_cb, &data); +} + +typedef struct { + git_vector *taglist; + const char *pattern; +} tag_filter_data; + +#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) + +static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) +{ + tag_filter_data *filter = (tag_filter_data *)data; + GIT_UNUSED(oid); + + if (!*filter->pattern || + 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); + + return git_vector_insert(filter->taglist, matched); + } + + return 0; +} + +int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) +{ + int error; + tag_filter_data filter; + git_vector taglist; + + GIT_ASSERT_ARG(tag_names); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(pattern); + + if ((error = git_vector_init(&taglist, 8, NULL)) < 0) + return error; + + filter.taglist = &taglist; + filter.pattern = pattern; + + error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); + + if (error < 0) + git_vector_free(&taglist); + + tag_names->strings = + (char **)git_vector_detach(&tag_names->count, NULL, &taglist); + + return 0; +} + +int git_tag_list(git_strarray *tag_names, git_repository *repo) +{ + return git_tag_list_match(tag_names, "", repo); +} + +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); +} + +int git_tag_name_is_valid(int *valid, const char *name) +{ + git_str ref_name = GIT_STR_INIT; + int error = 0; + + GIT_ASSERT(valid); + + *valid = 0; + + if (!name || !tag_name_is_valid(name)) + goto done; + + if ((error = git_str_puts(&ref_name, GIT_REFS_TAGS_DIR)) < 0 || + (error = git_str_puts(&ref_name, name)) < 0) + goto done; + + error = git_reference_name_is_valid(valid, ref_name.ptr); + +done: + git_str_dispose(&ref_name); + return error; +} + +/* Deprecated Functions */ + +#ifndef GIT_DEPRECATE_HARD +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); +} +#endif diff --git a/src/libgit2/tag.h b/src/libgit2/tag.h new file mode 100644 index 000000000..76ae1508e --- /dev/null +++ b/src/libgit2/tag.h @@ -0,0 +1,31 @@ +/* + * 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_tag_h__ +#define INCLUDE_tag_h__ + +#include "common.h" + +#include "git2/tag.h" +#include "repository.h" +#include "odb.h" + +struct git_tag { + git_object object; + + git_oid target; + git_object_t type; + + char *tag_name; + git_signature *tagger; + char *message; +}; + +void git_tag__free(void *tag); +int git_tag__parse(void *tag, git_odb_object *obj); +int git_tag__parse_raw(void *tag, const char *data, size_t size); + +#endif diff --git a/src/libgit2/threadstate.c b/src/libgit2/threadstate.c new file mode 100644 index 000000000..9e3ef5818 --- /dev/null +++ b/src/libgit2/threadstate.c @@ -0,0 +1,84 @@ +/* + * 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 "threadstate.h" +#include "runtime.h" + +/** + * Handle the thread-local state + * + * `git_threadstate_global_init` will be called as part + * of `git_libgit2_init` (which itself must be called + * before calling any other function in the library). + * + * This function allocates a TLS index to store the per- + * thread state. + * + * Any internal method that requires thread-local state + * will then call `git_threadstate_get()` which returns a + * pointer to the thread-local state structure; this + * structure is lazily allocated on each thread. + * + * This mechanism will register a shutdown handler + * (`git_threadstate_global_shutdown`) which will free the + * TLS index. This shutdown handler will be called by + * `git_libgit2_shutdown`. + */ + +static git_tlsdata_key tls_key; + +static void threadstate_dispose(git_threadstate *threadstate) +{ + if (!threadstate) + return; + + if (threadstate->error_t.message != git_str__initstr) + git__free(threadstate->error_t.message); + threadstate->error_t.message = NULL; +} + +static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) +{ + threadstate_dispose(threadstate); + git__free(threadstate); +} + +static void git_threadstate_global_shutdown(void) +{ + git_threadstate *threadstate; + + threadstate = git_tlsdata_get(tls_key); + git_tlsdata_set(tls_key, NULL); + + threadstate_dispose(threadstate); + git__free(threadstate); + + git_tlsdata_dispose(tls_key); +} + +int git_threadstate_global_init(void) +{ + if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) + return -1; + + return git_runtime_shutdown_register(git_threadstate_global_shutdown); +} + +git_threadstate *git_threadstate_get(void) +{ + git_threadstate *threadstate; + + if ((threadstate = git_tlsdata_get(tls_key)) != NULL) + return threadstate; + + if ((threadstate = git__calloc(1, sizeof(git_threadstate))) == NULL || + git_str_init(&threadstate->error_buf, 0) < 0) + return NULL; + + git_tlsdata_set(tls_key, threadstate); + return threadstate; +} diff --git a/src/libgit2/threadstate.h b/src/libgit2/threadstate.h new file mode 100644 index 000000000..c10f26b59 --- /dev/null +++ b/src/libgit2/threadstate.h @@ -0,0 +1,24 @@ +/* + * 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_threadstate_h__ +#define INCLUDE_threadstate_h__ + +#include "common.h" + +typedef struct { + git_error *last_error; + git_error error_t; + git_str error_buf; + char oid_fmt[GIT_OID_HEXSZ+1]; +} git_threadstate; + +extern int git_threadstate_global_init(void); +extern git_threadstate *git_threadstate_get(void); + +#define GIT_THREADSTATE (git_threadstate_get()) + +#endif diff --git a/src/libgit2/trace.c b/src/libgit2/trace.c new file mode 100644 index 000000000..b0c56c4dc --- /dev/null +++ b/src/libgit2/trace.c @@ -0,0 +1,25 @@ +/* + * 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 "trace.h" + +#include "str.h" +#include "runtime.h" +#include "git2/trace.h" + +struct git_trace_data git_trace__data = {0}; + +int git_trace_set(git_trace_level_t level, git_trace_cb callback) +{ + GIT_ASSERT_ARG(level == 0 || callback != NULL); + + git_trace__data.level = level; + git_trace__data.callback = callback; + GIT_MEMORY_BARRIER; + + return 0; +} diff --git a/src/libgit2/trace.h b/src/libgit2/trace.h new file mode 100644 index 000000000..239928dcb --- /dev/null +++ b/src/libgit2/trace.h @@ -0,0 +1,51 @@ +/* + * 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_trace_h__ +#define INCLUDE_trace_h__ + +#include "common.h" + +#include +#include "str.h" + +struct git_trace_data { + git_trace_level_t level; + git_trace_cb callback; +}; + +extern struct git_trace_data git_trace__data; + +GIT_INLINE(void) git_trace__write_fmt( + git_trace_level_t level, + const char *fmt, + va_list ap) +{ + git_trace_cb callback = git_trace__data.callback; + git_str message = GIT_STR_INIT; + + git_str_vprintf(&message, fmt, ap); + + callback(level, git_str_cstr(&message)); + + git_str_dispose(&message); +} + +#define git_trace_level() (git_trace__data.level) + +GIT_INLINE(void) git_trace(git_trace_level_t level, const char *fmt, ...) +{ + if (git_trace__data.level >= level && + git_trace__data.callback != NULL) { + va_list ap; + + va_start(ap, fmt); + git_trace__write_fmt(level, fmt, ap); + va_end(ap); + } +} + +#endif diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c new file mode 100644 index 000000000..4761c9922 --- /dev/null +++ b/src/libgit2/trailer.c @@ -0,0 +1,430 @@ +/* + * 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 "array.h" +#include "common.h" +#include "git2/message.h" + +#include +#include +#include + +#define COMMENT_LINE_CHAR '#' +#define TRAILER_SEPARATORS ":" + +static const char *const git_generated_prefixes[] = { + "Signed-off-by: ", + "(cherry picked from commit ", + NULL +}; + +static int is_blank_line(const char *str) +{ + const char *s = str; + while (*s && *s != '\n' && isspace(*s)) + s++; + return !*s || *s == '\n'; +} + +static const char *next_line(const char *str) +{ + const char *nl = strchr(str, '\n'); + + if (nl) { + return nl + 1; + } else { + /* return pointer to the NUL terminator: */ + return str + strlen(str); + } +} + +/* + * Return the position of the start of the last line. If len is 0, return 0. + */ +static bool last_line(size_t *out, const char *buf, size_t len) +{ + size_t i; + + *out = 0; + + if (len == 0) + return false; + if (len == 1) + 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 + * of the last line anyway. + */ + i = len - 2; + + for (; i > 0; i--) { + if (buf[i] == '\n') { + *out = i + 1; + return true; + } + } + return true; +} + +/* + * If the given line is of the form + * "..." 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 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 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)) { + *out = c - line; + return true; + } + + if (!whitespace_found && (isalnum(*c) || *c == '-')) + continue; + if (c != line && (*c == ' ' || *c == '\t')) { + whitespace_found = 1; + continue; + } + break; + } + return false; +} + +/* + * Inspect the given string and determine the true "end" of the log message, in + * order to find where to put a new Signed-off-by: line. Ignored are + * trailing comment lines and blank lines. To support "git commit -s + * --amend" on an existing commit, we also ignore "Conflicts:". To + * support "git commit -v", we truncate at cut lines. + * + * Returns the number of bytes from the tail to ignore, to be fed as + * the second parameter to append_signoff(). + */ +static size_t ignore_non_trailer(const char *buf, size_t len) +{ + size_t boc = 0, bol = 0; + int in_old_conflicts_block = 0; + size_t cutoff = len; + + while (bol < cutoff) { + const char *next_line = memchr(buf + bol, '\n', len - bol); + + if (!next_line) + next_line = buf + len; + else + next_line++; + + if (buf[bol] == COMMENT_LINE_CHAR || buf[bol] == '\n') { + /* is this the first of the run of comments? */ + if (!boc) + boc = bol; + /* otherwise, it is just continuing */ + } else if (git__prefixcmp(buf + bol, "Conflicts:\n") == 0) { + in_old_conflicts_block = 1; + if (!boc) + boc = bol; + } else if (in_old_conflicts_block && buf[bol] == '\t') { + ; /* a pathname in the conflicts block */ + } else if (boc) { + /* the previous was not trailing comment */ + boc = 0; + in_old_conflicts_block = 0; + } + bol = next_line - buf; + } + return boc ? len - boc : len - cutoff; +} + +/* + * Return the position of the start of the patch or the length of str if there + * is no patch in the message. + */ +static size_t find_patch_start(const char *str) +{ + const char *s; + + for (s = str; *s; s = next_line(s)) { + if (git__prefixcmp(s, "---") == 0) + return s - str; + } + + return s - str; +} + +/* + * Return the position of the first trailer line or len if there are no + * trailers. + */ +static size_t find_trailer_start(const char *buf, size_t len) +{ + const char *s; + 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 + * reset to 0 if we encounter a trailer (since those lines are to be + * considered continuations of that trailer), and added to + * non_trailer_lines if we encounter a non-trailer (since those lines + * are to be considered non-trailers). + */ + int possible_continuation_lines = 0; + + /* The first paragraph is the title and cannot be trailers */ + for (s = buf; s < buf + len; s = next_line(s)) { + if (s[0] == COMMENT_LINE_CHAR) + continue; + if (is_blank_line(s)) + break; + } + end_of_title = s - buf; + + /* + * Get the start of the trailers by looking starting from the end for a + * blank line before a set of non-blank lines that (i) are all + * trailers, or (ii) contains at least one Git-generated trailer and + * consists of at least 25% trailers. + */ + l = len; + while (last_line(&l, buf, l) && l >= end_of_title) { + const char *bol = buf + l; + const char *const *p; + size_t separator_pos = 0; + + if (bol[0] == COMMENT_LINE_CHAR) { + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + continue; + } + if (is_blank_line(bol)) { + if (only_spaces) + continue; + non_trailer_lines += possible_continuation_lines; + if (recognized_prefix && + trailer_lines * 3 >= non_trailer_lines) + return next_line(bol) - buf; + else if (trailer_lines && !non_trailer_lines) + return next_line(bol) - buf; + return len; + } + only_spaces = 0; + + for (p = git_generated_prefixes; *p; p++) { + if (git__prefixcmp(bol, *p) == 0) { + trailer_lines++; + possible_continuation_lines = 0; + recognized_prefix = 1; + goto continue_outer_loop; + } + } + + find_separator(&separator_pos, bol, TRAILER_SEPARATORS); + if (separator_pos >= 1 && !isspace(bol[0])) { + trailer_lines++; + possible_continuation_lines = 0; + if (recognized_prefix) + continue; + } else if (isspace(bol[0])) + possible_continuation_lines++; + else { + non_trailer_lines++; + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + } +continue_outer_loop: + ; + } + + return len; +} + +/* Return the position of the end of the trailers. */ +static size_t find_trailer_end(const char *buf, size_t len) +{ + return len - ignore_non_trailer(buf, len); +} + +static char *extract_trailer_block(const char *message, size_t *len) +{ + size_t patch_start = find_patch_start(message); + size_t trailer_end = find_trailer_end(message, patch_start); + size_t trailer_start = find_trailer_start(message, trailer_end); + + 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; + + *len = trailer_len; + + return buffer; +} + +enum trailer_state { + S_START = 0, + S_KEY = 1, + S_KEY_WS = 2, + S_SEP_WS = 3, + S_VALUE = 4, + S_VALUE_NL = 5, + S_VALUE_END = 6, + S_IGNORE = 7 +}; + +#define NEXT(st) { state = (st); ptr++; continue; } +#define GOTO(st) { state = (st); continue; } + +typedef git_array_t(git_message_trailer) git_array_trailer_t; + +int git_message_trailers(git_message_trailer_array *trailer_arr, const char *message) +{ + enum trailer_state state = S_START; + int rc = 0; + char *ptr; + char *key = NULL; + char *value = NULL; + git_array_trailer_t arr = GIT_ARRAY_INIT; + + size_t trailer_len; + char *trailer = extract_trailer_block(message, &trailer_len); + if (trailer == NULL) + return -1; + + for (ptr = trailer;;) { + switch (state) { + case S_START: { + if (*ptr == 0) { + goto ret; + } + + key = ptr; + GOTO(S_KEY); + } + case S_KEY: { + if (*ptr == 0) { + goto ret; + } + + if (isalnum(*ptr) || *ptr == '-') { + /* legal key character */ + NEXT(S_KEY); + } + + if (*ptr == ' ' || *ptr == '\t') { + /* optional whitespace before separator */ + *ptr = 0; + NEXT(S_KEY_WS); + } + + if (strchr(TRAILER_SEPARATORS, *ptr)) { + *ptr = 0; + NEXT(S_SEP_WS); + } + + /* illegal character */ + GOTO(S_IGNORE); + } + case S_KEY_WS: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == ' ' || *ptr == '\t') { + NEXT(S_KEY_WS); + } + + if (strchr(TRAILER_SEPARATORS, *ptr)) { + NEXT(S_SEP_WS); + } + + /* illegal character */ + GOTO(S_IGNORE); + } + case S_SEP_WS: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == ' ' || *ptr == '\t') { + NEXT(S_SEP_WS); + } + + value = ptr; + NEXT(S_VALUE); + } + case S_VALUE: { + if (*ptr == 0) { + GOTO(S_VALUE_END); + } + + if (*ptr == '\n') { + NEXT(S_VALUE_NL); + } + + NEXT(S_VALUE); + } + case S_VALUE_NL: { + if (*ptr == ' ') { + /* continuation; */ + NEXT(S_VALUE); + } + + ptr[-1] = 0; + GOTO(S_VALUE_END); + } + case S_VALUE_END: { + git_message_trailer *t = git_array_alloc(arr); + + t->key = key; + t->value = value; + + key = NULL; + value = NULL; + + GOTO(S_START); + } + case S_IGNORE: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == '\n') { + NEXT(S_START); + } + + NEXT(S_IGNORE); + } + } + } + +ret: + trailer_arr->_trailer_block = trailer; + trailer_arr->trailers = arr.ptr; + trailer_arr->count = arr.size; + + return rc; +} + +void git_message_trailer_array_free(git_message_trailer_array *arr) +{ + git__free(arr->_trailer_block); + git__free(arr->trailers); +} diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c new file mode 100644 index 000000000..ccffa9984 --- /dev/null +++ b/src/libgit2/transaction.c @@ -0,0 +1,395 @@ +/* + * 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 "transaction.h" + +#include "repository.h" +#include "strmap.h" +#include "refdb.h" +#include "pool.h" +#include "reflog.h" +#include "signature.h" +#include "config.h" + +#include "git2/transaction.h" +#include "git2/signature.h" +#include "git2/sys/refs.h" +#include "git2/sys/refdb_backend.h" + +typedef enum { + TRANSACTION_NONE, + TRANSACTION_REFS, + TRANSACTION_CONFIG +} transaction_t; + +typedef struct { + const char *name; + void *payload; + + git_reference_t ref_type; + union { + git_oid id; + char *symbolic; + } target; + git_reflog *reflog; + + const char *message; + git_signature *sig; + + unsigned int committed :1, + remove :1; +} transaction_node; + +struct git_transaction { + transaction_t type; + git_repository *repo; + git_refdb *db; + git_config *cfg; + + git_strmap *locks; + git_pool pool; +}; + +int git_transaction_config_new(git_transaction **out, git_config *cfg) +{ + git_transaction *tx; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(cfg); + + tx = git__calloc(1, sizeof(git_transaction)); + GIT_ERROR_CHECK_ALLOC(tx); + + tx->type = TRANSACTION_CONFIG; + tx->cfg = cfg; + *out = tx; + return 0; +} + +int git_transaction_new(git_transaction **out, git_repository *repo) +{ + int error; + git_pool pool; + git_transaction *tx = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if ((error = git_pool_init(&pool, 1)) < 0) + goto on_error; + + tx = git_pool_mallocz(&pool, sizeof(git_transaction)); + if (!tx) { + error = -1; + goto on_error; + } + + if ((error = git_strmap_new(&tx->locks)) < 0) { + error = -1; + goto on_error; + } + + if ((error = git_repository_refdb(&tx->db, repo)) < 0) + goto on_error; + + tx->type = TRANSACTION_REFS; + memcpy(&tx->pool, &pool, sizeof(git_pool)); + tx->repo = repo; + *out = tx; + return 0; + +on_error: + git_pool_clear(&pool); + return error; +} + +int git_transaction_lock_ref(git_transaction *tx, const char *refname) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + + node = git_pool_mallocz(&tx->pool, sizeof(transaction_node)); + GIT_ERROR_CHECK_ALLOC(node); + + node->name = git_pool_strdup(&tx->pool, refname); + GIT_ERROR_CHECK_ALLOC(node->name); + + if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) + return error; + + if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) + goto cleanup; + + return 0; + +cleanup: + git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); + + return error; +} + +static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) +{ + transaction_node *node; + + if ((node = git_strmap_get(tx->locks, refname)) == NULL) { + git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); + return GIT_ENOTFOUND; + } + + *out = node; + return 0; +} + +static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg) +{ + if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0) + return -1; + + if (!node->sig) { + git_signature *tmp; + int error; + + if (git_reference__log_signature(&tmp, tx->repo) < 0) + return -1; + + /* make sure the sig we use is in our pool */ + error = git_signature__pdup(&node->sig, tmp, &tx->pool); + git_signature_free(tmp); + if (error < 0) + return error; + } + + if (msg) { + node->message = git_pool_strdup(&tx->pool, msg); + GIT_ERROR_CHECK_ALLOC(node->message); + } + + return 0; +} + +int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + GIT_ASSERT_ARG(target); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = copy_common(node, tx, sig, msg)) < 0) + return error; + + git_oid_cpy(&node->target.id, target); + node->ref_type = GIT_REFERENCE_DIRECT; + + return 0; +} + +int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + GIT_ASSERT_ARG(target); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = copy_common(node, tx, sig, msg)) < 0) + return error; + + node->target.symbolic = git_pool_strdup(&tx->pool, target); + GIT_ERROR_CHECK_ALLOC(node->target.symbolic); + node->ref_type = GIT_REFERENCE_SYMBOLIC; + + return 0; +} + +int git_transaction_remove(git_transaction *tx, const char *refname) +{ + int error; + transaction_node *node; + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + node->remove = true; + node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */ + + return 0; +} + +static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool) +{ + git_reflog *reflog; + git_reflog_entry *entries; + size_t len, i; + + reflog = git_pool_mallocz(pool, sizeof(git_reflog)); + GIT_ERROR_CHECK_ALLOC(reflog); + + reflog->ref_name = git_pool_strdup(pool, in->ref_name); + GIT_ERROR_CHECK_ALLOC(reflog->ref_name); + + len = in->entries.length; + reflog->entries.length = len; + reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(reflog->entries.contents); + + entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry)); + GIT_ERROR_CHECK_ALLOC(entries); + + for (i = 0; i < len; i++) { + const git_reflog_entry *src; + git_reflog_entry *tgt; + + tgt = &entries[i]; + reflog->entries.contents[i] = tgt; + + src = git_vector_get(&in->entries, i); + git_oid_cpy(&tgt->oid_old, &src->oid_old); + git_oid_cpy(&tgt->oid_cur, &src->oid_cur); + + tgt->msg = git_pool_strdup(pool, src->msg); + GIT_ERROR_CHECK_ALLOC(tgt->msg); + + if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0) + return -1; + } + + + *out = reflog; + return 0; +} + +int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + GIT_ASSERT_ARG(reflog); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0) + return error; + + return 0; +} + +static int update_target(git_refdb *db, transaction_node *node) +{ + git_reference *ref; + int error, update_reflog; + + if (node->ref_type == GIT_REFERENCE_DIRECT) { + ref = git_reference__alloc(node->name, &node->target.id, NULL); + } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { + ref = git_reference__alloc_symbolic(node->name, node->target.symbolic); + } else { + abort(); + } + + GIT_ERROR_CHECK_ALLOC(ref); + update_reflog = node->reflog == NULL; + + if (node->remove) { + error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL); + } else if (node->ref_type == GIT_REFERENCE_DIRECT) { + error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); + } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { + error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); + } else { + abort(); + } + + git_reference_free(ref); + node->committed = true; + + return error; +} + +int git_transaction_commit(git_transaction *tx) +{ + transaction_node *node; + int error = 0; + + GIT_ASSERT_ARG(tx); + + if (tx->type == TRANSACTION_CONFIG) { + error = git_config_unlock(tx->cfg, true); + tx->cfg = NULL; + + return error; + } + + git_strmap_foreach_value(tx->locks, node, { + if (node->reflog) { + if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) + return error; + } + + 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; + } + }); + + return 0; +} + +void git_transaction_free(git_transaction *tx) +{ + transaction_node *node; + git_pool pool; + + if (!tx) + return; + + if (tx->type == TRANSACTION_CONFIG) { + if (tx->cfg) { + git_config_unlock(tx->cfg, false); + git_config_free(tx->cfg); + } + + git__free(tx); + return; + } + + /* start by unlocking the ones we've left hanging, if any */ + git_strmap_foreach_value(tx->locks, node, { + if (node->committed) + continue; + + git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); + }); + + git_refdb_free(tx->db); + git_strmap_free(tx->locks); + + /* tx is inside the pool, so we need to extract the data */ + memcpy(&pool, &tx->pool, sizeof(git_pool)); + git_pool_clear(&pool); +} diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h new file mode 100644 index 000000000..780c06830 --- /dev/null +++ b/src/libgit2/transaction.h @@ -0,0 +1,14 @@ +/* + * 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_transaction_h__ +#define INCLUDE_transaction_h__ + +#include "common.h" + +int git_transaction_config_new(git_transaction **out, git_config *cfg); + +#endif diff --git a/src/libgit2/transport.c b/src/libgit2/transport.c new file mode 100644 index 000000000..640ccacae --- /dev/null +++ b/src/libgit2/transport.c @@ -0,0 +1,222 @@ +/* + * 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/types.h" +#include "git2/remote.h" +#include "git2/net.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" +#include "fs_path.h" + +typedef struct transport_definition { + char *prefix; + git_transport_cb fn; + void *param; +} transport_definition; + +static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL }; +static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL }; +#ifdef GIT_SSH +static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL }; +#endif + +static transport_definition local_transport_definition = { "file://", git_transport_local, NULL }; + +static transport_definition transports[] = { + { "git://", git_transport_smart, &git_subtransport_definition }, + { "http://", git_transport_smart, &http_subtransport_definition }, + { "https://", git_transport_smart, &http_subtransport_definition }, + { "file://", git_transport_local, NULL }, +#ifdef GIT_SSH + { "ssh://", git_transport_smart, &ssh_subtransport_definition }, + { "ssh+git://", git_transport_smart, &ssh_subtransport_definition }, + { "git+ssh://", git_transport_smart, &ssh_subtransport_definition }, +#endif + { NULL, 0, 0 } +}; + +static git_vector custom_transports = GIT_VECTOR_INIT; + +#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 + +static transport_definition * transport_find_by_url(const char *url) +{ + size_t i = 0; + transport_definition *d; + + /* Find a user transport who wants to deal with this URI */ + git_vector_foreach(&custom_transports, i, d) { + if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { + return d; + } + } + + /* Find a system transport for this URI */ + for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { + d = &transports[i]; + + if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { + return d; + } + } + + return NULL; +} + +static int transport_find_fn( + git_transport_cb *out, + const char *url, + void **param) +{ + transport_definition *definition = transport_find_by_url(url); + +#ifdef GIT_WIN32 + /* On Windows, it might not be possible to discern between absolute local + * and ssh paths - first check if this is a valid local path that points + * to a directory and if so assume local path, else assume SSH */ + + /* Check to see if the path points to a file on the local file system */ + if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) + definition = &local_transport_definition; +#endif + + /* For other systems, perform the SSH check first, to avoid going to the + * filesystem if it is not necessary */ + + /* It could be a SSH remote path. Check to see if there's a : */ + if (!definition && strrchr(url, ':')) { + /* re-search transports again with ssh:// as url + * so that we can find a third party ssh transport */ + definition = transport_find_by_url("ssh://"); + } + +#ifndef GIT_WIN32 + /* Check to see if the path points to a file on the local file system */ + if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) + definition = &local_transport_definition; +#endif + + if (!definition) + return GIT_ENOTFOUND; + + *out = definition->fn; + *param = definition->param; + + return 0; +} + +/************** + * Public API * + **************/ + +int git_transport_new(git_transport **out, git_remote *owner, const char *url) +{ + git_transport_cb fn; + git_transport *transport; + void *param; + int error; + + if ((error = transport_find_fn(&fn, url, ¶m)) == GIT_ENOTFOUND) { + git_error_set(GIT_ERROR_NET, "unsupported URL protocol"); + return -1; + } else if (error < 0) + return error; + + if ((error = fn(&transport, owner, param)) < 0) + return error; + + GIT_ERROR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); + + *out = transport; + + return 0; +} + +int git_transport_register( + const char *scheme, + git_transport_cb cb, + void *param) +{ + git_str prefix = GIT_STR_INIT; + transport_definition *d, *definition = NULL; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(scheme); + GIT_ASSERT_ARG(cb); + + if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) + goto on_error; + + git_vector_foreach(&custom_transports, i, d) { + if (strcasecmp(d->prefix, prefix.ptr) == 0) { + error = GIT_EEXISTS; + goto on_error; + } + } + + definition = git__calloc(1, sizeof(transport_definition)); + GIT_ERROR_CHECK_ALLOC(definition); + + definition->prefix = git_str_detach(&prefix); + definition->fn = cb; + definition->param = param; + + if (git_vector_insert(&custom_transports, definition) < 0) + goto on_error; + + return 0; + +on_error: + git_str_dispose(&prefix); + git__free(definition); + return error; +} + +int git_transport_unregister(const char *scheme) +{ + git_str prefix = GIT_STR_INIT; + transport_definition *d; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(scheme); + + if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) + goto done; + + git_vector_foreach(&custom_transports, i, d) { + if (strcasecmp(d->prefix, prefix.ptr) == 0) { + if ((error = git_vector_remove(&custom_transports, i)) < 0) + goto done; + + git__free(d->prefix); + git__free(d); + + if (!custom_transports.length) + git_vector_free(&custom_transports); + + error = 0; + goto done; + } + } + + error = GIT_ENOTFOUND; + +done: + git_str_dispose(&prefix); + return error; +} + +int git_transport_init(git_transport *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_transport, GIT_TRANSPORT_INIT); + return 0; +} diff --git a/src/libgit2/transports/auth.c b/src/libgit2/transports/auth.c new file mode 100644 index 000000000..90b6b124f --- /dev/null +++ b/src/libgit2/transports/auth.c @@ -0,0 +1,74 @@ +/* + * 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 "auth.h" + +#include "git2/sys/credential.h" + +static int basic_next_token( + git_str *out, + git_http_auth_context *ctx, + git_credential *c) +{ + git_credential_userpass_plaintext *cred; + git_str raw = GIT_STR_INIT; + int error = GIT_EAUTH; + + GIT_UNUSED(ctx); + + if (c->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + git_error_set(GIT_ERROR_INVALID, "invalid credential type for basic auth"); + goto on_error; + } + + cred = (git_credential_userpass_plaintext *)c; + + git_str_printf(&raw, "%s:%s", cred->username, cred->password); + + if (git_str_oom(&raw) || + git_str_puts(out, "Basic ") < 0 || + git_str_encode_base64(out, git_str_cstr(&raw), raw.size) < 0) + goto on_error; + + error = 0; + +on_error: + if (raw.size) + git__memzero(raw.ptr, raw.size); + + git_str_dispose(&raw); + return error; +} + +static git_http_auth_context basic_context = { + 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 git_net_url *url) +{ + GIT_UNUSED(url); + + *out = &basic_context; + return 0; +} + +int git_http_auth_dummy( + git_http_auth_context **out, const git_net_url *url) +{ + GIT_UNUSED(url); + + *out = NULL; + return GIT_PASSTHROUGH; +} + diff --git a/src/libgit2/transports/auth.h b/src/libgit2/transports/auth.h new file mode 100644 index 000000000..64680cc53 --- /dev/null +++ b/src/libgit2/transports/auth.h @@ -0,0 +1,70 @@ +/* + * 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_h__ +#define INCLUDE_transports_auth_h__ + +#include "common.h" + +#include "netops.h" + +typedef enum { + 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_auth_t type; + + /** Supported credentials */ + 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_str *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); +}; + +typedef struct { + /** Type of scheme */ + git_http_auth_t type; + + /** Name of the scheme (as used in the Authorization header) */ + const char *name; + + /** Credential types this scheme supports */ + git_credential_t credtypes; + + /** Function to initialize an authentication context */ + int (*init_context)( + git_http_auth_context **out, + const git_net_url *url); +} git_http_auth_scheme; + +int git_http_auth_dummy( + git_http_auth_context **out, + const git_net_url *url); + +int git_http_auth_basic( + git_http_auth_context **out, + const git_net_url *url); + +#endif diff --git a/src/libgit2/transports/auth_negotiate.c b/src/libgit2/transports/auth_negotiate.c new file mode 100644 index 000000000..6380504be --- /dev/null +++ b/src/libgit2/transports/auth_negotiate.c @@ -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. + */ + +#include "auth_negotiate.h" + +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) + +#include "git2.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" }; +static gss_OID_desc negotiate_oid_krb5 = + { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; + +static gss_OID negotiate_oids[] = + { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL }; + +typedef struct { + git_http_auth_context parent; + unsigned configured : 1, + complete : 1; + git_str target; + char *challenge; + gss_ctx_id_t gss_context; + gss_OID oid; +} http_auth_negotiate_context; + +static void negotiate_err_set( + OM_uint32 status_major, + OM_uint32 status_minor, + const char *message) +{ + gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + OM_uint32 status_display, context = 0; + + if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE, + GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) { + git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)", + message, (int)buffer.length, (const char *)buffer.value, + status_major, status_minor); + gss_release_buffer(&status_minor, &buffer); + } else { + git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)", + message, status_major, status_minor); + } +} + +static int negotiate_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(challenge); + GIT_ASSERT(ctx->configured); + + git__free(ctx->challenge); + + ctx->challenge = git__strdup(challenge); + GIT_ERROR_CHECK_ALLOC(ctx->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_str_dispose(&ctx->target); + + git__free(ctx->challenge); + ctx->challenge = NULL; +} + +static int negotiate_next_token( + git_str *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + OM_uint32 status_major, status_minor; + gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER, + input_token = GSS_C_EMPTY_BUFFER, + output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER; + git_str input_buf = GIT_STR_INIT; + gss_name_t server = NULL; + gss_OID mech; + size_t challenge_len; + int error = 0; + + GIT_ASSERT_ARG(buf); + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(cred); + + GIT_ASSERT(ctx->configured); + GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT); + + if (ctx->complete) + return 0; + + target_buffer.value = (void *)ctx->target.ptr; + target_buffer.length = ctx->target.size; + + status_major = gss_import_name(&status_minor, &target_buffer, + GSS_C_NT_HOSTBASED_SERVICE, &server); + + if (GSS_ERROR(status_major)) { + negotiate_err_set(status_major, status_minor, + "could not parse principal"); + error = -1; + goto done; + } + + challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; + + 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; + } + + if (challenge_len > 9) { + if (git_str_decode_base64(&input_buf, + ctx->challenge + 10, challenge_len - 10) < 0) { + git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server"); + error = -1; + goto done; + } + + input_token.value = input_buf.ptr; + input_token.length = input_buf.size; + input_token_ptr = &input_token; + } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { + negotiate_context_dispose(ctx); + } + + mech = &negotiate_oid_spnego; + + status_major = gss_init_sec_context( + &status_minor, + GSS_C_NO_CREDENTIAL, + &ctx->gss_context, + server, + mech, + GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + input_token_ptr, + NULL, + &output_token, + NULL, + 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; + } + + if (output_token.length == 0) { + git_error_set(GIT_ERROR_NET, "GSSAPI did not return token"); + error = -1; + goto done; + } + + git_str_puts(buf, "Negotiate "); + git_str_encode_base64(buf, output_token.value, output_token.length); + + if (git_str_oom(buf)) + error = -1; + +done: + gss_release_name(&status_minor, &server); + gss_release_buffer(&status_minor, (gss_buffer_t) &output_token); + git_str_dispose(&input_buf); + return error; +} + +static int negotiate_is_complete(git_http_auth_context *c) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + GIT_ASSERT_ARG(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; + + negotiate_context_dispose(ctx); + + ctx->configured = 0; + ctx->complete = 0; + ctx->oid = NULL; + + git__free(ctx); +} + +static int negotiate_init_context( + http_auth_negotiate_context *ctx, + const git_net_url *url) +{ + OM_uint32 status_major, status_minor; + gss_OID item, *oid; + gss_OID_set mechanism_list; + size_t i; + + /* Query supported mechanisms looking for SPNEGO) */ + 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; + } + + if (mechanism_list) { + for (oid = negotiate_oids; *oid; oid++) { + for (i = 0; i < mechanism_list->count; i++) { + item = &mechanism_list->elements[i]; + + if (item->length == (*oid)->length && + memcmp(item->elements, (*oid)->elements, item->length) == 0) { + ctx->oid = *oid; + break; + } + + } + + if (ctx->oid) + break; + } + } + + gss_release_oid_set(&status_minor, &mechanism_list); + + if (!ctx->oid) { + git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported"); + return GIT_EAUTH; + } + + git_str_puts(&ctx->target, "HTTP@"); + git_str_puts(&ctx->target, url->host); + + if (git_str_oom(&ctx->target)) + return -1; + + ctx->gss_context = GSS_C_NO_CONTEXT; + ctx->configured = 1; + + return 0; +} + +int git_http_auth_negotiate( + git_http_auth_context **out, + const git_net_url *url) +{ + http_auth_negotiate_context *ctx; + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); + GIT_ERROR_CHECK_ALLOC(ctx); + + if (negotiate_init_context(ctx, url) < 0) { + git__free(ctx); + return -1; + } + + 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; + + return 0; +} + +#endif /* GIT_GSSAPI */ + diff --git a/src/libgit2/transports/auth_negotiate.h b/src/libgit2/transports/auth_negotiate.h new file mode 100644 index 000000000..34aff295b --- /dev/null +++ b/src/libgit2/transports/auth_negotiate.h @@ -0,0 +1,27 @@ +/* + * 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_negotiate_h__ +#define INCLUDE_transports_auth_negotiate_h__ + +#include "common.h" +#include "git2.h" +#include "auth.h" + +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) + +extern int git_http_auth_negotiate( + git_http_auth_context **out, + const git_net_url *url); + +#else + +#define git_http_auth_negotiate git_http_auth_dummy + +#endif /* GIT_GSSAPI */ + +#endif diff --git a/src/libgit2/transports/auth_ntlm.c b/src/libgit2/transports/auth_ntlm.c new file mode 100644 index 000000000..f49ce101a --- /dev/null +++ b/src/libgit2/transports/auth_ntlm.c @@ -0,0 +1,227 @@ +/* + * 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 "auth_ntlm.h" + +#include "common.h" +#include "str.h" +#include "auth.h" +#include "git2/sys/credential.h" + +#ifdef GIT_NTLM + +#include "ntlmclient.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; + + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(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; + + GIT_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_str *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + git_str input_buf = GIT_STR_INIT; + const unsigned char *msg; + size_t challenge_len, msg_len; + int error = GIT_EAUTH; + + GIT_ASSERT_ARG(buf); + GIT_ASSERT_ARG(ctx); + + GIT_ASSERT(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_str_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_str_puts(buf, "NTLM "); + git_str_encode_base64(buf, (const char *)msg, msg_len); + + if (git_str_oom(buf)) + goto done; + + error = 0; + +done: + git_str_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; + + GIT_ASSERT_ARG(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/libgit2/transports/auth_ntlm.h b/src/libgit2/transports/auth_ntlm.h new file mode 100644 index 000000000..40689498c --- /dev/null +++ b/src/libgit2/transports/auth_ntlm.h @@ -0,0 +1,37 @@ +/* + * 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 "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/libgit2/transports/credential.c b/src/libgit2/transports/credential.c new file mode 100644 index 000000000..6e00b0282 --- /dev/null +++ b/src/libgit2/transports/credential.c @@ -0,0 +1,486 @@ +/* + * 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; + + GIT_ASSERT_ARG(cred); + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(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; + + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(cred); + GIT_ASSERT_ARG(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; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(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; + + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(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; + + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(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; + + GIT_ASSERT_ARG(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; + + GIT_ASSERT_ARG(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 */ + +#ifndef GIT_DEPRECATE_HARD +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); +} +#endif diff --git a/src/libgit2/transports/credential_helpers.c b/src/libgit2/transports/credential_helpers.c new file mode 100644 index 000000000..6d34a4e97 --- /dev/null +++ b/src/libgit2/transports/credential_helpers.c @@ -0,0 +1,68 @@ +/* + * 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_helpers.h" + +int git_credential_userpass( + git_credential **cred, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) +{ + git_credential_userpass_payload *userpass = (git_credential_userpass_payload*)payload; + const char *effective_username = NULL; + + GIT_UNUSED(url); + + if (!userpass || !userpass->password) return -1; + + /* Username resolution: a username can be passed with the URL, the + * credentials payload, or both. Here's what we do. Note that if we get + * this far, we know that any password the url may contain has already + * failed at least once, so we ignore it. + * + * | Payload | URL | Used | + * +-------------+----------+-----------+ + * | yes | no | payload | + * | yes | yes | payload | + * | no | yes | url | + * | no | no | FAIL | + */ + if (userpass->username) + effective_username = userpass->username; + else if (user_from_url) + effective_username = user_from_url; + else + return -1; + + if (GIT_CREDENTIAL_USERNAME & allowed_types) + return git_credential_username_new(cred, effective_username); + + 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 */ + +#ifndef GIT_DEPRECATE_HARD +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); +} +#endif diff --git a/src/libgit2/transports/git.c b/src/libgit2/transports/git.c new file mode 100644 index 000000000..591e2ab03 --- /dev/null +++ b/src/libgit2/transports/git.c @@ -0,0 +1,361 @@ +/* + * 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 "netops.h" +#include "stream.h" +#include "streams/socket.h" +#include "git2/sys/transport.h" + +#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) + +static const char prefix_git[] = "git://"; +static const char cmd_uploadpack[] = "git-upload-pack"; +static const char cmd_receivepack[] = "git-receive-pack"; + +typedef struct { + git_smart_subtransport_stream parent; + git_stream *io; + const char *cmd; + char *url; + unsigned sent_command : 1; +} git_proto_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + git_proto_stream *current_stream; +} git_subtransport; + +/* + * Create a git protocol request. + * + * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 + */ +static int gen_proto(git_str *request, const char *cmd, const char *url) +{ + char *delim, *repo; + char host[] = "host="; + size_t len; + + delim = strchr(url, '/'); + if (delim == NULL) { + git_error_set(GIT_ERROR_NET, "malformed URL"); + return -1; + } + + repo = delim; + if (repo[1] == '~') + ++repo; + + delim = strchr(url, ':'); + if (delim == NULL) + delim = strchr(url, '/'); + + len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; + + git_str_grow(request, len); + git_str_printf(request, "%04x%s %s%c%s", + (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); + git_str_put(request, url, delim - url); + git_str_putc(request, '\0'); + + if (git_str_oom(request)) + return -1; + + return 0; +} + +static int send_command(git_proto_stream *s) +{ + git_str request = GIT_STR_INIT; + int error; + + if ((error = gen_proto(&request, s->cmd, s->url)) < 0) + goto cleanup; + + if ((error = git_stream__write_full(s->io, request.ptr, request.size, 0)) < 0) + goto cleanup; + + s->sent_command = 1; + +cleanup: + git_str_dispose(&request); + return error; +} + +static int git_proto_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + int error; + git_proto_stream *s = (git_proto_stream *)stream; + gitno_buffer buf; + + *bytes_read = 0; + + if (!s->sent_command && (error = send_command(s)) < 0) + return error; + + gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size); + + if ((error = gitno_recv(&buf)) < 0) + return error; + + *bytes_read = buf.offset; + + return 0; +} + +static int git_proto_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + git_proto_stream *s = (git_proto_stream *)stream; + int error; + + if (!s->sent_command && (error = send_command(s)) < 0) + return error; + + return git_stream__write_full(s->io, buffer, len, 0); +} + +static void git_proto_stream_free(git_smart_subtransport_stream *stream) +{ + git_proto_stream *s; + git_subtransport *t; + + if (!stream) + return; + + s = (git_proto_stream *)stream; + t = OWNING_SUBTRANSPORT(s); + + t->current_stream = NULL; + + git_stream_close(s->io); + git_stream_free(s->io); + git__free(s->url); + git__free(s); +} + +static int git_proto_stream_alloc( + git_subtransport *t, + const char *url, + const char *cmd, + const char *host, + const char *port, + git_smart_subtransport_stream **stream) +{ + git_proto_stream *s; + + if (!stream) + return -1; + + s = git__calloc(1, sizeof(git_proto_stream)); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = git_proto_stream_read; + s->parent.write = git_proto_stream_write; + s->parent.free = git_proto_stream_free; + + s->cmd = cmd; + s->url = git__strdup(url); + + if (!s->url) { + git__free(s); + return -1; + } + + if ((git_socket_stream_new(&s->io, host, port)) < 0) + return -1; + + GIT_ERROR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); + + *stream = &s->parent; + return 0; +} + +static int _git_uploadpack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + git_net_url urldata = GIT_NET_URL_INIT; + const char *stream_url = url; + const char *host, *port; + git_proto_stream *s; + int error; + + *stream = NULL; + + if (!git__prefixcmp(url, prefix_git)) + stream_url += strlen(prefix_git); + + 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_net_url_dispose(&urldata); + + if (error < 0) { + git_proto_stream_free(*stream); + return error; + } + + s = (git_proto_stream *) *stream; + if ((error = git_stream_connect(s->io)) < 0) { + git_proto_stream_free(*stream); + return error; + } + + t->current_stream = s; + + return 0; +} + +static int _git_uploadpack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); + return -1; +} + +static int _git_receivepack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + git_net_url urldata = GIT_NET_URL_INIT; + const char *stream_url = url; + git_proto_stream *s; + int error; + + *stream = NULL; + if (!git__prefixcmp(url, prefix_git)) + stream_url += strlen(prefix_git); + + if ((error = git_net_url_parse(&urldata, url)) < 0) + return error; + + error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); + + git_net_url_dispose(&urldata); + + if (error < 0) { + git_proto_stream_free(*stream); + return error; + } + + s = (git_proto_stream *) *stream; + + if ((error = git_stream_connect(s->io)) < 0) + return error; + + t->current_stream = s; + + return 0; +} + +static int _git_receivepack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); + return -1; +} + +static int _git_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + git_subtransport *t = (git_subtransport *) subtransport; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return _git_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return _git_uploadpack(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return _git_receivepack_ls(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK: + return _git_receivepack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static int _git_close(git_smart_subtransport *subtransport) +{ + git_subtransport *t = (git_subtransport *) subtransport; + + GIT_ASSERT(!t->current_stream); + + GIT_UNUSED(t); + + return 0; +} + +static void _git_free(git_smart_subtransport *subtransport) +{ + git_subtransport *t = (git_subtransport *) subtransport; + + git__free(t); +} + +int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param) +{ + git_subtransport *t; + + GIT_UNUSED(param); + + if (!out) + return -1; + + t = git__calloc(1, sizeof(git_subtransport)); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = owner; + t->parent.action = _git_action; + t->parent.close = _git_close; + t->parent.free = _git_free; + + *out = (git_smart_subtransport *) t; + return 0; +} diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c new file mode 100644 index 000000000..7db5582ca --- /dev/null +++ b/src/libgit2/transports/http.c @@ -0,0 +1,760 @@ +/* + * 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" + +#ifndef GIT_WINHTTP + +#include "http_parser.h" +#include "net.h" +#include "netops.h" +#include "remote.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" +#include "git2/sys/credential.h" + +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 int initial : 1, + 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", + 1, + 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, + 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", + 1, + 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", + 0, + 1 +}; + +#define SERVER_TYPE_REMOTE "remote" +#define SERVER_TYPE_PROXY "proxy" + +#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) + +static int apply_url_credentials( + git_credential **cred, + unsigned int allowed_types, + const char *username, + const char *password) +{ + GIT_ASSERT_ARG(username); + + if (!password) + 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 GIT_PASSTHROUGH; +} + +GIT_INLINE(void) free_cred(git_credential **cred) +{ + if (*cred) { + git_credential_free(*cred); + (*cred) = NULL; + } +} + +static int handle_auth( + http_server *server, + const char *server_type, + const char *url, + unsigned int allowed_schemetypes, + unsigned int allowed_credtypes, + git_credential_acquire_cb callback, + void *callback_payload) +{ + int error = 1; + + 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) { + 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 (error > 0 && callback) { + error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload); + + /* treat GIT_PASSTHROUGH as if callback isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; + } + + if (error > 0) { + git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); + error = GIT_EAUTH; + } + + if (!error) + server->auth_schemetypes = allowed_schemetypes; + + return error; +} + +GIT_INLINE(int) handle_remote_auth( + http_stream *stream, + git_http_response *response) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + + if (response->server_auth_credtypes == 0) { + git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); + return GIT_EAUTH; + } + + /* Otherwise, prompt for credentials. */ + return handle_auth( + &transport->server, + SERVER_TYPE_REMOTE, + transport->owner->url, + response->server_auth_schemetypes, + response->server_auth_credtypes, + connect_opts->callbacks.credentials, + connect_opts->callbacks.payload); +} + +GIT_INLINE(int) handle_proxy_auth( + http_stream *stream, + git_http_response *response) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + + if (response->proxy_auth_credtypes == 0) { + git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); + return GIT_EAUTH; + } + + /* Otherwise, prompt for credentials. */ + return handle_auth( + &transport->proxy, + SERVER_TYPE_PROXY, + connect_opts->proxy_opts.url, + response->server_auth_schemetypes, + response->proxy_auth_credtypes, + connect_opts->proxy_opts.credentials, + connect_opts->proxy_opts.payload); +} + +static bool allow_redirect(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + + switch (transport->owner->connect_opts.follow_redirects) { + case GIT_REMOTE_REDIRECT_INITIAL: + return (stream->service->initial == 1); + case GIT_REMOTE_REDIRECT_ALL: + return true; + default: + return false; + } +} + +static int handle_response( + bool *complete, + http_stream *stream, + git_http_response *response, + bool allow_replay) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + int error; + + *complete = false; + + 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, allow_redirect(stream), stream->service->url) < 0) { + return -1; + } + + return 0; + } else if (git_http_response_is_redirect(response)) { + git_error_set(GIT_ERROR_HTTP, "unexpected redirect"); + return -1; + } + + /* 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 GIT_EAUTH; + } + + 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 (!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 (strcmp(response->content_type, stream->service->response_type) != 0) { + git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type); + return -1; + } + + *complete = true; + stream->state = HTTP_STATE_RECEIVING_RESPONSE; + return 0; +} + +static int lookup_proxy( + bool *out_use, + http_subtransport *transport) +{ + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + const char *proxy; + git_remote *remote; + char *config = NULL; + int error = 0; + + *out_use = false; + git_net_url_dispose(&transport->proxy.url); + + switch (connect_opts->proxy_opts.type) { + case GIT_PROXY_SPECIFIED: + proxy = connect_opts->proxy_opts.url; + break; + + case GIT_PROXY_AUTO: + remote = transport->owner->owner; + + error = git_remote__http_proxy(&config, remote, &transport->server.url); + + if (error || !config) + goto done; + + proxy = config; + break; + + default: + return 0; + } + + if (!proxy || + (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) + goto done; + + *out_use = true; + +done: + git__free(config); + return error; +} + +static int generate_request( + git_net_url *url, + git_http_request *request, + http_stream *stream, + size_t len) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + bool use_proxy = false; + int error; + + if ((error = git_net_url_joinpath(url, + &transport->server.url, stream->service->url)) < 0 || + (error = lookup_proxy(&use_proxy, transport)) < 0) + return error; + + 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->connect_opts.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; + } + + 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; + } + + /* + * Formulate the URL, send the request and read the response + * headers. Some of the request body may also be read. + */ + 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 = 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++; + } + + if (stream->state == HTTP_STATE_SENDING_REQUEST) { + git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); + error = GIT_ERROR; /* not GIT_EAUTH, because the exact cause is unclear */ + goto done; + } + + GIT_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 bool needs_probe(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + + return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM || + transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE); +} + +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; + + /* NTLM requires a full challenge/response */ + if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM) + steps = GIT_AUTH_STEPS_NTLM; + + /* + * 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 ((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; + } + +done: + git_http_response_dispose(&response); + git_net_url_dispose(&url); + return error; +} + +/* +* 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; + + while (stream->state == HTTP_STATE_NONE && + stream->replay_count < GIT_HTTP_REPLAY_MAX) { + + git_net_url_dispose(&url); + git_http_response_dispose(&response); + + /* + * 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 (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 { + stream->state = HTTP_STATE_SENDING_REQUEST; + } + + stream->replay_count++; + } + + if (stream->state == HTTP_STATE_NONE) { + git_error_set(GIT_ERROR_HTTP, + "too many redirects or authentication replays"); + error = GIT_ERROR; /* not GIT_EAUTH because the exact cause is unclear */ + goto done; + } + + GIT_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; +} + +/* +* 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 *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; + + *out_len = 0; + + 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; + + GIT_ASSERT(complete); + stream->state = HTTP_STATE_RECEIVING_RESPONSE; + } + + error = git_http_client_read_body(client, buffer, buffer_size); + + if (error > 0) { + *out_len = error; + error = 0; + } + +done: + git_http_response_dispose(&response); + return error; +} + +static void http_stream_free(git_smart_subtransport_stream *stream) +{ + http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent); + git__free(s); +} + +static const http_service *select_service(git_smart_service_t action) +{ + 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; + } + + return NULL; +} + +static int http_action( + git_smart_subtransport_stream **out, + git_smart_subtransport *t, + const char *url, + git_smart_service_t action) +{ + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + http_stream *stream; + const http_service *service; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(t); + + *out = NULL; + + /* + * If we've seen a redirect then preserve the location that we've + * been given. This is important to continue authorization against + * the redirect target, not the user-given source; the endpoint may + * have redirected us from HTTP->HTTPS and is using an auth mechanism + * that would be insecure in plaintext (eg, HTTP Basic). + */ + if (!git_net_url_valid(&transport->server.url) && + (error = git_net_url_parse(&transport->server.url, url)) < 0) + return error; + + if ((service = select_service(action)) == NULL) { + git_error_set(GIT_ERROR_HTTP, "invalid action"); + 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 = connect_opts->callbacks.certificate_check; + opts.server_certificate_check_payload = connect_opts->callbacks.payload; + opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check; + opts.proxy_certificate_check_payload = connect_opts->proxy_opts.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 int http_close(git_smart_subtransport *t) +{ + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + + free_cred(&transport->server.cred); + free_cred(&transport->proxy.cred); + + transport->server.url_cred_presented = false; + transport->proxy.url_cred_presented = false; + + git_net_url_dispose(&transport->server.url); + git_net_url_dispose(&transport->proxy.url); + + return 0; +} + +static void http_free(git_smart_subtransport *t) +{ + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + + git_http_client_free(transport->http_client); + + http_close(t); + git__free(transport); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) +{ + http_subtransport *transport; + + GIT_UNUSED(param); + + GIT_ASSERT_ARG(out); + + transport = git__calloc(sizeof(http_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(transport); + + transport->owner = (transport_smart *)owner; + transport->parent.action = http_action; + transport->parent.close = http_close; + transport->parent.free = http_free; + + *out = (git_smart_subtransport *) transport; + return 0; +} + +#endif /* !GIT_WINHTTP */ diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h new file mode 100644 index 000000000..8e8e7226e --- /dev/null +++ b/src/libgit2/transports/http.h @@ -0,0 +1,28 @@ +/* + * 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_http_h__ +#define INCLUDE_transports_http_h__ + +#include "settings.h" +#include "httpclient.h" + +#define GIT_HTTP_REPLAY_MAX 15 + +extern bool git_http__expect_continue; + +GIT_INLINE(int) git_http__user_agent(git_str *buf) +{ + const char *ua = git_libgit2__user_agent(); + + if (!ua) + ua = "libgit2 " LIBGIT2_VERSION; + + return git_str_printf(buf, "git/2.0 (%s)", ua); +} + +#endif diff --git a/src/libgit2/transports/httpclient.c b/src/libgit2/transports/httpclient.c new file mode 100644 index 000000000..f07923ef2 --- /dev/null +++ b/src/libgit2/transports/httpclient.c @@ -0,0 +1,1579 @@ +/* + * 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 "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_str parse_header_name; + git_str 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_str request_msg; + git_str 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) +{ + if (!response) + return; + + 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_str *name = &ctx->parse_header_name; + git_str *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_str_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_str_clear(&ctx->parse_header_name); + git_str_clear(&ctx->parse_header_value); + /* Fall through */ + + case PARSE_HEADER_NONE: + case PARSE_HEADER_NAME: + ctx->parse_header_state = PARSE_HEADER_NAME; + + if (git_str_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_str_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; + } + + GIT_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); +} + +static 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_str *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_str token = GIT_STR_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 = GIT_EAUTH; + goto done; + } + + if (token.size > 0) + error = git_str_printf(buf, "%s: %s\r\n", header_name, token.ptr); + +done: + git_str_dispose(&token); + return error; +} + +GIT_INLINE(int) apply_server_credentials( + git_str *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_str *buf, + git_http_client *client, + git_http_request *request) +{ + return apply_credentials(buf, + &client->proxy, + "Proxy-Authorization", + request->proxy_credentials); +} + +static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) +{ + bool ipv6 = git_net_url_is_ipv6(url); + + if (ipv6) + git_str_putc(buf, '['); + + git_str_puts(buf, url->host); + + if (ipv6) + git_str_putc(buf, ']'); + + if (force_port || !git_net_url_is_default_port(url)) { + git_str_putc(buf, ':'); + git_str_puts(buf, url->port); + } + + return git_str_oom(buf) ? -1 : 0; +} + +static int generate_connect_request( + git_http_client *client, + git_http_request *request) +{ + git_str *buf; + int error; + + git_str_clear(&client->request_msg); + buf = &client->request_msg; + + git_str_puts(buf, "CONNECT "); + puts_host_and_port(buf, &client->server.url, true); + git_str_puts(buf, " HTTP/1.1\r\n"); + + git_str_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_str_puts(buf, "\r\n"); + + git_str_puts(buf, "Host: "); + puts_host_and_port(buf, &client->server.url, true); + git_str_puts(buf, "\r\n"); + + if ((error = apply_proxy_credentials(buf, client, request) < 0)) + return -1; + + git_str_puts(buf, "\r\n"); + + return git_str_oom(buf) ? -1 : 0; +} + +static bool use_connect_proxy(git_http_client *client) +{ + return client->proxy.url.host && !strcmp(client->server.url.scheme, "https"); +} + +static int generate_request( + git_http_client *client, + git_http_request *request) +{ + git_str *buf; + size_t i; + int error; + + GIT_ASSERT_ARG(client); + GIT_ASSERT_ARG(request); + + git_str_clear(&client->request_msg); + buf = &client->request_msg; + + /* GET|POST path HTTP/1.1 */ + git_str_puts(buf, name_for_method(request->method)); + git_str_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_str_puts(buf, " HTTP/1.1\r\n"); + + git_str_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_str_puts(buf, "\r\n"); + + git_str_puts(buf, "Host: "); + puts_host_and_port(buf, request->url, false); + git_str_puts(buf, "\r\n"); + + if (request->accept) + git_str_printf(buf, "Accept: %s\r\n", request->accept); + else + git_str_puts(buf, "Accept: */*\r\n"); + + if (request->content_type) + git_str_printf(buf, "Content-Type: %s\r\n", + request->content_type); + + if (request->chunked) + git_str_puts(buf, "Transfer-Encoding: chunked\r\n"); + + if (request->content_length > 0) + git_str_printf(buf, "Content-Length: %"PRIuZ "\r\n", + request->content_length); + + if (request->expect_continue) + git_str_printf(buf, "Expect: 100-continue\r\n"); + + if ((error = apply_server_credentials(buf, client, request)) < 0 || + (!use_connect_proxy(client) && + (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_str_printf(buf, "%s\r\n", hdr); + } + } + + git_str_puts(buf, "\r\n"); + + if (git_str_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; + + GIT_ASSERT_ARG(client); + GIT_ASSERT_ARG(request); + + GIT_ASSERT(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 port %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; + + GIT_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 = use_connect_proxy(client); + + 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 port %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_str 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 (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 + */ + GIT_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_str_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_str_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; + + GIT_ASSERT_ARG(client); + GIT_ASSERT_ARG(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_str url = GIT_STR_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_str_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_str hdr = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(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) { + GIT_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_str_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_str_dispose(&hdr); + return error; +} + +static int complete_request(git_http_client *client) +{ + int error = 0; + + GIT_ASSERT_ARG(client); + GIT_ASSERT(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; + + GIT_ASSERT_ARG(response); + GIT_ASSERT_ARG(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); + + if (client->current_server == PROXY) { + git_vector_free_deep(&client->proxy.auth_challenges); + } else if(client->current_server == SERVER) { + git_vector_free_deep(&client->server.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; + } + + GIT_ASSERT(client->state == READING_BODY || client->state == DONE); + +done: + git_str_dispose(&parser_context.parse_header_name); + git_str_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; + } + + GIT_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 >= 0 && client->state != DONE); + + 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; + + GIT_ASSERT_ARG(out); + + client = git__calloc(1, sizeof(git_http_client)); + GIT_ERROR_CHECK_ALLOC(client); + + git_str_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_str_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_str_dispose(&client->read_buf); + git__free(client); +} diff --git a/src/libgit2/transports/httpclient.h b/src/libgit2/transports/httpclient.h new file mode 100644 index 000000000..6d0ef9edb --- /dev/null +++ b/src/libgit2/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 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/libgit2/transports/local.c b/src/libgit2/transports/local.c new file mode 100644 index 000000000..6c754a034 --- /dev/null +++ b/src/libgit2/transports/local.c @@ -0,0 +1,754 @@ +/* + * 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 "pack-objects.h" +#include "refs.h" +#include "posix.h" +#include "fs_path.h" +#include "repository.h" +#include "odb.h" +#include "push.h" +#include "remote.h" +#include "proxy.h" + +#include "git2/types.h" +#include "git2/net.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/tag.h" +#include "git2/transport.h" +#include "git2/revwalk.h" +#include "git2/odb_backend.h" +#include "git2/pack.h" +#include "git2/commit.h" +#include "git2/revparse.h" +#include "git2/sys/remote.h" + +typedef struct { + git_transport parent; + git_remote *owner; + char *url; + int direction; + git_atomic32 cancelled; + git_repository *repo; + git_remote_connect_options connect_opts; + git_vector refs; + unsigned connected : 1, + have_refs : 1; +} transport_local; + +static void free_head(git_remote_head *head) +{ + git__free(head->name); + git__free(head->symref_target); + git__free(head); +} + +static void free_heads(git_vector *heads) +{ + git_remote_head *head; + size_t i; + + git_vector_foreach(heads, i, head) + free_head(head); + + git_vector_free(heads); +} + +static int add_ref(transport_local *t, const char *name) +{ + const char peeled[] = "^{}"; + git_reference *ref, *resolved; + git_remote_head *head; + git_oid obj_id; + git_object *obj = NULL, *target = NULL; + git_str buf = GIT_STR_INIT; + int error; + + if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) + return error; + + error = git_reference_resolve(&resolved, ref); + if (error < 0) { + git_reference_free(ref); + if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { + /* This is actually okay. Empty repos often have a HEAD that + * points to a nonexistent "refs/heads/master". */ + git_error_clear(); + return 0; + } + return error; + } + + git_oid_cpy(&obj_id, git_reference_target(resolved)); + git_reference_free(resolved); + + head = git__calloc(1, sizeof(git_remote_head)); + GIT_ERROR_CHECK_ALLOC(head); + + head->name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(head->name); + + git_oid_cpy(&head->oid, &obj_id); + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + head->symref_target = git__strdup(git_reference_symbolic_target(ref)); + GIT_ERROR_CHECK_ALLOC(head->symref_target); + } + git_reference_free(ref); + + if ((error = git_vector_insert(&t->refs, head)) < 0) { + free_head(head); + return error; + } + + /* If it's not a tag, we don't need to try to peel it */ + if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + return 0; + + if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0) + return error; + + head = NULL; + + /* If it's not an annotated tag, or if we're mocking + * git-receive-pack, just get out */ + if (git_object_type(obj) != GIT_OBJECT_TAG || + t->direction != GIT_DIRECTION_FETCH) { + git_object_free(obj); + return 0; + } + + /* And if it's a tag, peel it, and add it to the list */ + head = git__calloc(1, sizeof(git_remote_head)); + GIT_ERROR_CHECK_ALLOC(head); + + if (git_str_join(&buf, 0, name, peeled) < 0) { + free_head(head); + return -1; + } + head->name = git_str_detach(&buf); + + if (!(error = git_tag_peel(&target, (git_tag *)obj))) { + git_oid_cpy(&head->oid, git_object_id(target)); + + if ((error = git_vector_insert(&t->refs, head)) < 0) { + free_head(head); + } + } + + git_object_free(obj); + git_object_free(target); + + return error; +} + +static int store_refs(transport_local *t) +{ + size_t i; + git_remote_head *head; + git_strarray ref_names = {0}; + + GIT_ASSERT_ARG(t); + + if (git_reference_list(&ref_names, t->repo) < 0) + goto on_error; + + /* Clear all heads we might have fetched in a previous connect */ + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + /* Clear the vector so we can reuse it */ + git_vector_clear(&t->refs); + + /* Sort the references first */ + git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); + + /* Add HEAD iff direction is fetch */ + if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) + goto on_error; + + for (i = 0; i < ref_names.count; ++i) { + if (add_ref(t, ref_names.strings[i]) < 0) + goto on_error; + } + + t->have_refs = 1; + git_strarray_dispose(&ref_names); + return 0; + +on_error: + git_vector_free(&t->refs); + git_strarray_dispose(&ref_names); + return -1; +} + +/* + * Try to open the url as a git directory. The direction doesn't + * matter in this case because we're calculating the heads ourselves. + */ +static int local_connect( + git_transport *transport, + const char *url, + int direction, + const git_remote_connect_options *connect_opts) +{ + git_repository *repo; + int error; + transport_local *t = (transport_local *)transport; + const char *path; + git_str buf = GIT_STR_INIT; + + if (t->connected) + return 0; + + if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) + return -1; + + free_heads(&t->refs); + + t->url = git__strdup(url); + GIT_ERROR_CHECK_ALLOC(t->url); + t->direction = direction; + + /* 'url' may be a url or path; convert to a path */ + if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) { + git_str_dispose(&buf); + return error; + } + path = git_str_cstr(&buf); + + error = git_repository_open(&repo, path); + + git_str_dispose(&buf); + + if (error < 0) + return -1; + + t->repo = repo; + + if (store_refs(t) < 0) + return -1; + + t->connected = 1; + + return 0; +} + +static int local_set_connect_opts( + git_transport *transport, + const git_remote_connect_options *connect_opts) +{ + transport_local *t = (transport_local *)transport; + + if (!t->connected) { + git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); + return -1; + } + + return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts); +} + +static int local_capabilities(unsigned int *capabilities, git_transport *transport) +{ + GIT_UNUSED(transport); + + *capabilities = GIT_REMOTE_CAPABILITY_TIP_OID | + GIT_REMOTE_CAPABILITY_REACHABLE_OID; + return 0; +} + +static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + if (!t->have_refs) { + git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); + return -1; + } + + *out = (const git_remote_head **)t->refs.contents; + *size = t->refs.length; + + return 0; +} + +static int local_negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count) +{ + transport_local *t = (transport_local*)transport; + git_remote_head *rhead; + unsigned int i; + + GIT_UNUSED(refs); + GIT_UNUSED(count); + + /* Fill in the loids */ + git_vector_foreach(&t->refs, i, rhead) { + git_object *obj; + + int error = git_revparse_single(&obj, repo, rhead->name); + if (!error) + git_oid_cpy(&rhead->loid, git_object_id(obj)); + else if (error != GIT_ENOTFOUND) + return error; + else + git_error_clear(); + git_object_free(obj); + } + + return 0; +} + +static int local_push_update_remote_ref( + git_repository *remote_repo, + const char *lref, + const char *rref, + git_oid *loid, + git_oid *roid) +{ + int error; + git_reference *remote_ref = NULL; + + /* check for lhs, if it's empty it means to delete */ + if (lref[0] != '\0') { + /* Create or update a ref */ + error = git_reference_create(NULL, remote_repo, rref, loid, + !git_oid_is_zero(roid), NULL); + } else { + /* Delete a ref */ + if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + return error; + } + + error = git_reference_delete(remote_ref); + git_reference_free(remote_ref); + } + + return error; +} + +static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload) +{ + const git_remote_callbacks *cbs = payload; + + if (!cbs || !cbs->push_transfer_progress) + return 0; + + return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes, + cbs->payload); +} + +static int local_push( + git_transport *transport, + git_push *push) +{ + transport_local *t = (transport_local *)transport; + git_remote_callbacks *cbs = &t->connect_opts.callbacks; + git_repository *remote_repo = NULL; + push_spec *spec; + char *url = NULL; + const char *path; + git_str buf = GIT_STR_INIT, odb_path = GIT_STR_INIT; + int error; + size_t j; + + /* 'push->remote->url' may be a url or path; convert to a path */ + if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) { + git_str_dispose(&buf); + return error; + } + path = git_str_cstr(&buf); + + error = git_repository_open(&remote_repo, path); + + git_str_dispose(&buf); + + if (error < 0) + return error; + + /* We don't currently support pushing locally to non-bare repos. Proper + non-bare repo push support would require checking configs to see if + we should override the default 'don't let this happen' behavior. + + Note that this is only an issue when pushing to the current branch, + but we forbid all pushes just in case */ + if (!remote_repo->is_bare) { + error = GIT_EBAREREPO; + git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos."); + goto on_error; + } + + if ((error = git_repository__item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 + || (error = git_str_joinpath(&odb_path, odb_path.ptr, "pack")) < 0) + goto on_error; + + error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); + git_str_dispose(&odb_path); + + if (error < 0) + goto on_error; + + push->unpack_ok = 1; + + git_vector_foreach(&push->specs, j, spec) { + push_status *status; + const git_error *last; + char *ref = spec->refspec.dst; + + status = git__calloc(1, sizeof(push_status)); + if (!status) + goto on_error; + + status->ref = git__strdup(ref); + if (!status->ref) { + git_push_status_free(status); + goto on_error; + } + + error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst, + &spec->loid, &spec->roid); + + switch (error) { + case GIT_OK: + break; + case GIT_EINVALIDSPEC: + status->msg = git__strdup("funny refname"); + break; + case GIT_ENOTFOUND: + status->msg = git__strdup("Remote branch not found to delete"); + break; + default: + last = git_error_last(); + + if (last && last->message) + status->msg = git__strdup(last->message); + else + status->msg = git__strdup("Unspecified error encountered"); + break; + } + + /* failed to allocate memory for a status message */ + if (error < 0 && !status->msg) { + git_push_status_free(status); + goto on_error; + } + + /* failed to insert the ref update status */ + if ((error = git_vector_insert(&push->status, status)) < 0) { + git_push_status_free(status); + goto on_error; + } + } + + if (push->specs.length) { + url = git__strdup(t->url); + + if (!url || t->parent.close(&t->parent) < 0 || + t->parent.connect(&t->parent, url, + GIT_DIRECTION_PUSH, NULL)) + goto on_error; + } + + error = 0; + +on_error: + git_repository_free(remote_repo); + git__free(url); + + return error; +} + +typedef struct foreach_data { + git_indexer_progress *stats; + git_indexer_progress_cb progress_cb; + void *progress_payload; + git_odb_writepack *writepack; +} foreach_data; + +static int foreach_cb(void *buf, size_t len, void *payload) +{ + foreach_data *data = (foreach_data*)payload; + + data->stats->received_bytes += len; + return data->writepack->append(data->writepack, buf, len, data->stats); +} + +static const char *counting_objects_fmt = "Counting objects %d\r"; +static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)"; + +static int local_counting(int stage, unsigned int current, unsigned int total, void *payload) +{ + git_str progress_info = GIT_STR_INIT; + transport_local *t = payload; + int error; + + if (!t->connect_opts.callbacks.sideband_progress) + return 0; + + if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) { + git_str_printf(&progress_info, counting_objects_fmt, current); + } else if (stage == GIT_PACKBUILDER_DELTAFICATION) { + float perc = (((float) current) / total) * 100; + git_str_printf(&progress_info, compressing_objects_fmt, perc, current, total); + if (current == total) + git_str_printf(&progress_info, ", done\n"); + else + git_str_putc(&progress_info, '\r'); + + } + + if (git_str_oom(&progress_info)) + return -1; + + if (progress_info.size > INT_MAX) { + git_error_set(GIT_ERROR_NET, "remote sent overly large progress data"); + git_str_dispose(&progress_info); + return -1; + } + + + error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload); + + git_str_dispose(&progress_info); + return error; +} + +static int foreach_reference_cb(git_reference *reference, void *payload) +{ + git_revwalk *walk = (git_revwalk *)payload; + int error; + + if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) { + git_reference_free(reference); + return 0; + } + + error = git_revwalk_hide(walk, git_reference_target(reference)); + /* The reference is in the local repository, so the target may not + * exist on the remote. It also may not be a commit. */ + if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) { + git_error_clear(); + error = 0; + } + + git_reference_free(reference); + + return error; +} + +static int local_download_pack( + git_transport *transport, + git_repository *repo, + git_indexer_progress *stats) +{ + transport_local *t = (transport_local*)transport; + git_revwalk *walk = NULL; + git_remote_head *rhead; + unsigned int i; + int error = -1; + git_packbuilder *pack = NULL; + git_odb_writepack *writepack = NULL; + git_odb *odb = NULL; + git_str progress_info = GIT_STR_INIT; + foreach_data data = {0}; + + if ((error = git_revwalk_new(&walk, t->repo)) < 0) + goto cleanup; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if ((error = git_packbuilder_new(&pack, t->repo)) < 0) + goto cleanup; + + git_packbuilder_set_callbacks(pack, local_counting, t); + + stats->total_objects = 0; + stats->indexed_objects = 0; + stats->received_objects = 0; + stats->received_bytes = 0; + + git_vector_foreach(&t->refs, i, rhead) { + git_object *obj; + if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0) + goto cleanup; + + if (git_object_type(obj) == GIT_OBJECT_COMMIT) { + /* Revwalker includes only wanted commits */ + error = git_revwalk_push(walk, &rhead->oid); + } else { + /* Tag or some other wanted object. Add it on its own */ + error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name); + } + git_object_free(obj); + if (error < 0) + goto cleanup; + } + + if ((error = git_reference_foreach(repo, foreach_reference_cb, walk))) + goto cleanup; + + if ((error = git_packbuilder_insert_walk(pack, walk))) + goto cleanup; + + if (t->connect_opts.callbacks.sideband_progress) { + if ((error = git_str_printf( + &progress_info, + counting_objects_fmt, + git_packbuilder_object_count(pack))) < 0 || + (error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + } + + /* Walk the objects, building a packfile */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + goto cleanup; + + /* One last one with the newline */ + if (t->connect_opts.callbacks.sideband_progress) { + git_str_clear(&progress_info); + + if ((error = git_str_printf( + &progress_info, + counting_objects_fmt, + git_packbuilder_object_count(pack))) < 0 || + (error = git_str_putc(&progress_info, '\n')) < 0 || + (error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + } + + if ((error = git_odb_write_pack( + &writepack, + odb, + t->connect_opts.callbacks.transfer_progress, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + + /* Write the data to the ODB */ + data.stats = stats; + data.progress_cb = t->connect_opts.callbacks.transfer_progress; + data.progress_payload = t->connect_opts.callbacks.payload; + data.writepack = writepack; + + /* autodetect */ + git_packbuilder_set_threads(pack, 0); + + if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) + goto cleanup; + + error = writepack->commit(writepack, stats); + +cleanup: + if (writepack) writepack->free(writepack); + git_str_dispose(&progress_info); + git_packbuilder_free(pack); + git_revwalk_free(walk); + return error; +} + +static int local_is_connected(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + return t->connected; +} + +static void local_cancel(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + git_atomic32_set(&t->cancelled, 1); +} + +static int local_close(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + t->connected = 0; + + if (t->repo) { + git_repository_free(t->repo); + t->repo = NULL; + } + + if (t->url) { + git__free(t->url); + t->url = NULL; + } + + return 0; +} + +static void local_free(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + free_heads(&t->refs); + + /* Close the transport, if it's still open. */ + local_close(transport); + + /* Free the transport */ + git__free(t); +} + +/************** + * Public API * + **************/ + +int git_transport_local(git_transport **out, git_remote *owner, void *param) +{ + int error; + transport_local *t; + + GIT_UNUSED(param); + + t = git__calloc(1, sizeof(transport_local)); + GIT_ERROR_CHECK_ALLOC(t); + + t->parent.version = GIT_TRANSPORT_VERSION; + t->parent.connect = local_connect; + t->parent.set_connect_opts = local_set_connect_opts; + t->parent.capabilities = local_capabilities; + t->parent.negotiate_fetch = local_negotiate_fetch; + t->parent.download_pack = local_download_pack; + t->parent.push = local_push; + t->parent.close = local_close; + t->parent.free = local_free; + t->parent.ls = local_ls; + t->parent.is_connected = local_is_connected; + t->parent.cancel = local_cancel; + + if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) { + git__free(t); + return error; + } + + t->owner = owner; + + *out = (git_transport *) t; + + return 0; +} diff --git a/src/libgit2/transports/smart.c b/src/libgit2/transports/smart.c new file mode 100644 index 000000000..7f57dba2a --- /dev/null +++ b/src/libgit2/transports/smart.c @@ -0,0 +1,484 @@ +/* + * 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 "smart.h" + +#include "git2.h" +#include "git2/sys/remote.h" +#include "refs.h" +#include "refspec.h" +#include "proxy.h" + +static int git_smart__recv_cb(gitno_buffer *buf) +{ + transport_smart *t = (transport_smart *) buf->cb_data; + size_t old_len, bytes_read; + int error; + + GIT_ASSERT(t->current_stream); + + old_len = buf->offset; + + if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0) + return error; + + buf->offset += bytes_read; + + if (t->packetsize_cb && !t->cancelled.val) { + error = t->packetsize_cb(bytes_read, t->packetsize_payload); + if (error) { + git_atomic32_set(&t->cancelled, 1); + return GIT_EUSER; + } + } + + return (int)(buf->offset - old_len); +} + +GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport) +{ + if (t->current_stream) { + t->current_stream->free(t->current_stream); + t->current_stream = NULL; + } + + if (close_subtransport) { + git__free(t->url); + t->url = NULL; + + if (t->wrapped->close(t->wrapped) < 0) + return -1; + } + + return 0; +} + +int git_smart__update_heads(transport_smart *t, git_vector *symrefs) +{ + size_t i; + git_pkt *pkt; + + git_vector_clear(&t->heads); + git_vector_foreach(&t->refs, i, pkt) { + git_pkt_ref *ref = (git_pkt_ref *) pkt; + if (pkt->type != GIT_PKT_REF) + continue; + + if (symrefs) { + git_refspec *spec; + git_str buf = GIT_STR_INIT; + size_t j; + int error = 0; + + git_vector_foreach(symrefs, j, spec) { + git_str_clear(&buf); + if (git_refspec_src_matches(spec, ref->head.name) && + !(error = git_refspec__transform(&buf, spec, ref->head.name))) { + git__free(ref->head.symref_target); + ref->head.symref_target = git_str_detach(&buf); + } + } + + git_str_dispose(&buf); + + if (error < 0) + return error; + } + + if (git_vector_insert(&t->heads, &ref->head) < 0) + return -1; + } + + return 0; +} + +static void free_symrefs(git_vector *symrefs) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(symrefs, i, spec) { + git_refspec__dispose(spec); + git__free(spec); + } + + git_vector_free(symrefs); +} + +static int git_smart__connect( + git_transport *transport, + const char *url, + int direction, + const git_remote_connect_options *connect_opts) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_smart_subtransport_stream *stream; + int error; + git_pkt *pkt; + git_pkt_ref *first; + git_vector symrefs; + git_smart_service_t service; + + if (git_smart__reset_stream(t, true) < 0) + return -1; + + if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) + return -1; + + t->url = git__strdup(url); + GIT_ERROR_CHECK_ALLOC(t->url); + + t->direction = direction; + + if (GIT_DIRECTION_FETCH == t->direction) { + service = GIT_SERVICE_UPLOADPACK_LS; + } else if (GIT_DIRECTION_PUSH == t->direction) { + service = GIT_SERVICE_RECEIVEPACK_LS; + } else { + git_error_set(GIT_ERROR_NET, "invalid direction"); + return -1; + } + + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0) + return error; + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + /* 2 flushes for RPC; 1 for stateful */ + if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) + return error; + + /* Strip the comment packet for RPC */ + if (t->rpc) { + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + + if (!pkt || GIT_PKT_COMMENT != pkt->type) { + git_error_set(GIT_ERROR_NET, "invalid response"); + return -1; + } else { + /* Remove the comment pkt from the list */ + git_vector_remove(&t->refs, 0); + git__free(pkt); + } + } + + /* We now have loaded the refs. */ + t->have_refs = 1; + + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + if (pkt && GIT_PKT_REF != pkt->type) { + git_error_set(GIT_ERROR_NET, "invalid response"); + return -1; + } + first = (git_pkt_ref *)pkt; + + if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) + return error; + + /* Detect capabilities */ + 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_is_zero(&first->head.oid)) { + git_vector_clear(&t->refs); + git_pkt_free((git_pkt *)first); + } + + /* Keep a list of heads for _ls */ + git_smart__update_heads(t, &symrefs); + } else if (error == GIT_ENOTFOUND) { + /* There was no ref packet received, or the cap list was empty */ + error = 0; + } else { + git_error_set(GIT_ERROR_NET, "invalid response"); + goto cleanup; + } + + if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0) + goto cleanup; + + /* We're now logically connected. */ + t->connected = 1; + +cleanup: + free_symrefs(&symrefs); + + return error; +} + +static int git_smart__set_connect_opts( + git_transport *transport, + const git_remote_connect_options *opts) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + if (!t->connected) { + git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); + return -1; + } + + return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts); +} + +static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + *capabilities = 0; + + if (t->caps.want_tip_sha1) + *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID; + + if (t->caps.want_reachable_sha1) + *capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID; + + return 0; +} + +static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *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"); + return -1; + } + + *out = (const git_remote_head **) t->heads.contents; + *size = t->heads.length; + + return 0; +} + +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_smart_subtransport_stream *stream; + int error; + + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; + + if (GIT_DIRECTION_FETCH != t->direction) { + git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch"); + return -1; + } + + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + GIT_ASSERT(t->rpc || t->current_stream == stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + if ((error = stream->write(stream, (const char *)data, len)) < 0) + return error; + + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; +} + +int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream) +{ + int error; + + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; + + if (GIT_DIRECTION_PUSH != t->direction) { + git_error_set(GIT_ERROR_NET, "this operation is only valid for push"); + return -1; + } + + if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + GIT_ASSERT(t->rpc || t->current_stream == *stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = *stream; + + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; +} + +static void git_smart__cancel(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + git_atomic32_set(&t->cancelled, 1); +} + +static int git_smart__is_connected(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + return t->connected; +} + +static int git_smart__close(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + int ret; + git_smart_subtransport_stream *stream; + const char flush[] = "0000"; + + /* + * If we're still connected at this point and not using RPC, + * we should say goodbye by sending a flush, or git-daemon + * will complain that we disconnected unexpectedly. + */ + if (t->connected && !t->rpc && + !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { + t->current_stream->write(t->current_stream, flush, 4); + } + + ret = git_smart__reset_stream(t, true); + + git_vector_foreach(common, i, p) + git_pkt_free(p); + + git_vector_free(common); + + if (t->url) { + git__free(t->url); + t->url = NULL; + } + + t->connected = 0; + + return ret; +} + +static void git_smart__free(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_vector *refs = &t->refs; + unsigned int i; + git_pkt *p; + + /* Make sure that the current stream is closed, if we have one. */ + git_smart__close(transport); + + /* Free the subtransport */ + t->wrapped->free(t->wrapped); + + git_vector_free(&t->heads); + git_vector_foreach(refs, i, p) + git_pkt_free(p); + + git_vector_free(refs); + + git_remote_connect_options_dispose(&t->connect_opts); + + git__free(t); +} + +static int ref_name_cmp(const void *a, const void *b) +{ + const git_pkt_ref *ref_a = a, *ref_b = b; + + return strcmp(ref_a->head.name, ref_b->head.name); +} + +int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_remote_connect_options *connect_opts = &t->connect_opts; + + GIT_ASSERT_ARG(transport); + GIT_ASSERT_ARG(cert); + GIT_ASSERT_ARG(hostname); + + if (!connect_opts->callbacks.certificate_check) + return GIT_PASSTHROUGH; + + return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload); +} + +int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_remote_connect_options *connect_opts = &t->connect_opts; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(transport); + + if (!connect_opts->callbacks.credentials) + return GIT_PASSTHROUGH; + + return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload); +} + +int git_transport_remote_connect_options( + git_remote_connect_options *out, + git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(transport); + + return git_remote_connect_options_dup(out, &t->connect_opts); +} + +int git_transport_smart(git_transport **out, git_remote *owner, void *param) +{ + transport_smart *t; + git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; + + if (!param) + return -1; + + t = git__calloc(1, sizeof(transport_smart)); + GIT_ERROR_CHECK_ALLOC(t); + + t->parent.version = GIT_TRANSPORT_VERSION; + t->parent.connect = git_smart__connect; + t->parent.set_connect_opts = git_smart__set_connect_opts; + t->parent.capabilities = git_smart__capabilities; + t->parent.close = git_smart__close; + t->parent.free = git_smart__free; + t->parent.negotiate_fetch = git_smart__negotiate_fetch; + t->parent.download_pack = git_smart__download_pack; + t->parent.push = git_smart__push; + t->parent.ls = git_smart__ls; + t->parent.is_connected = git_smart__is_connected; + t->parent.cancel = git_smart__cancel; + + t->owner = owner; + t->rpc = definition->rpc; + + if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) { + git__free(t); + return -1; + } + + if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) { + git__free(t); + return -1; + } + + if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) { + git__free(t); + return -1; + } + + *out = (git_transport *) t; + return 0; +} diff --git a/src/libgit2/transports/smart.h b/src/libgit2/transports/smart.h new file mode 100644 index 000000000..9323d6c44 --- /dev/null +++ b/src/libgit2/transports/smart.h @@ -0,0 +1,193 @@ +/* + * 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_smart_h__ +#define INCLUDE_transports_smart_h__ + +#include "common.h" + +#include "git2.h" +#include "vector.h" +#include "netops.h" +#include "push.h" +#include "str.h" +#include "git2/sys/transport.h" + +#define GIT_SIDE_BAND_DATA 1 +#define GIT_SIDE_BAND_PROGRESS 2 +#define GIT_SIDE_BAND_ERROR 3 + +#define GIT_CAP_OFS_DELTA "ofs-delta" +#define GIT_CAP_MULTI_ACK "multi_ack" +#define GIT_CAP_MULTI_ACK_DETAILED "multi_ack_detailed" +#define GIT_CAP_SIDE_BAND "side-band" +#define GIT_CAP_SIDE_BAND_64K "side-band-64k" +#define GIT_CAP_INCLUDE_TAG "include-tag" +#define GIT_CAP_DELETE_REFS "delete-refs" +#define GIT_CAP_REPORT_STATUS "report-status" +#define GIT_CAP_THIN_PACK "thin-pack" +#define GIT_CAP_SYMREF "symref" +#define GIT_CAP_WANT_TIP_SHA1 "allow-tip-sha1-in-want" +#define GIT_CAP_WANT_REACHABLE_SHA1 "allow-reachable-sha1-in-want" + +extern bool git_smart__ofs_delta_enabled; + +typedef enum { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, + GIT_PKT_ACK, + GIT_PKT_NAK, + GIT_PKT_COMMENT, + GIT_PKT_ERR, + GIT_PKT_DATA, + GIT_PKT_PROGRESS, + GIT_PKT_OK, + GIT_PKT_NG, + GIT_PKT_UNPACK +} git_pkt_type; + +/* Used for multi_ack and multi_ack_detailed */ +enum git_ack_status { + GIT_ACK_NONE, + GIT_ACK_CONTINUE, + GIT_ACK_COMMON, + GIT_ACK_READY +}; + +/* This would be a flush pkt */ +typedef struct { + git_pkt_type type; +} git_pkt; + +struct git_pkt_cmd { + git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +typedef struct { + git_pkt_type type; + git_remote_head head; + char *capabilities; +} git_pkt_ref; + +/* Useful later */ +typedef struct { + git_pkt_type type; + git_oid oid; + enum git_ack_status status; +} git_pkt_ack; + +typedef struct { + git_pkt_type type; + char comment[GIT_FLEX_ARRAY]; +} git_pkt_comment; + +typedef struct { + git_pkt_type type; + size_t len; + char data[GIT_FLEX_ARRAY]; +} git_pkt_data; + +typedef git_pkt_data git_pkt_progress; + +typedef struct { + git_pkt_type type; + size_t len; + char error[GIT_FLEX_ARRAY]; +} git_pkt_err; + +typedef struct { + git_pkt_type type; + char *ref; +} git_pkt_ok; + +typedef struct { + git_pkt_type type; + char *ref; + char *msg; +} git_pkt_ng; + +typedef struct { + git_pkt_type type; + int unpack_ok; +} git_pkt_unpack; + +typedef struct transport_smart_caps { + unsigned int common:1, + ofs_delta:1, + multi_ack:1, + multi_ack_detailed:1, + side_band:1, + side_band_64k:1, + include_tag:1, + delete_refs:1, + report_status:1, + thin_pack:1, + want_tip_sha1:1, + want_reachable_sha1:1; +} transport_smart_caps; + +typedef int (*packetsize_cb)(size_t received, void *payload); + +typedef struct { + git_transport parent; + git_remote *owner; + char *url; + git_remote_connect_options connect_opts; + int direction; + git_smart_subtransport *wrapped; + git_smart_subtransport_stream *current_stream; + transport_smart_caps caps; + git_vector refs; + git_vector heads; + git_vector common; + git_atomic32 cancelled; + packetsize_cb packetsize_cb; + void *packetsize_payload; + unsigned rpc : 1, + have_refs : 1, + connected : 1; + gitno_buffer buffer; + char buffer_data[65536]; +} transport_smart; + +/* smart_protocol.c */ +int git_smart__store_refs(transport_smart *t, int flushes); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); +int git_smart__push(git_transport *transport, git_push *push); + +int git_smart__negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count); + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_indexer_progress *stats); + +/* smart.c */ +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); +int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); + +int git_smart__update_heads(transport_smart *t, git_vector *symrefs); + +/* smart_pkt.c */ +int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen); +int git_pkt_buffer_flush(git_str *buf); +int git_pkt_send_flush(GIT_SOCKET s); +int git_pkt_buffer_done(git_str *buf); +int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_str *buf); +int git_pkt_buffer_have(git_oid *oid, git_str *buf); +void git_pkt_free(git_pkt *pkt); + +#endif diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c new file mode 100644 index 000000000..b42edd0d6 --- /dev/null +++ b/src/libgit2/transports/smart_pkt.c @@ -0,0 +1,637 @@ +/* + * 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 "smart.h" +#include "util.h" +#include "netops.h" +#include "posix.h" +#include "str.h" + +#include "git2/types.h" +#include "git2/errors.h" +#include "git2/refs.h" +#include "git2/revwalk.h" + +#include + +#define PKT_LEN_SIZE 4 +static const char pkt_done_str[] = "0009done\n"; +static const char pkt_flush_str[] = "0000"; +static const char pkt_have_prefix[] = "0032have "; +static const char pkt_want_prefix[] = "0032want "; + +static int flush_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_FLUSH; + *out = pkt; + + return 0; +} + +/* the rest of the line will be useful for multi_ack and multi_ack_detailed */ +static int ack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ack *pkt; + + pkt = git__calloc(1, sizeof(git_pkt_ack)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_ACK; + + if (git__prefixncmp(line, len, "ACK ")) + goto out_err; + line += 4; + len -= 4; + + if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->oid, line) < 0) + goto out_err; + line += GIT_OID_HEXSZ; + len -= GIT_OID_HEXSZ; + + if (len && line[0] == ' ') { + line++; + len--; + + if (!git__prefixncmp(line, len, "continue")) + pkt->status = GIT_ACK_CONTINUE; + else if (!git__prefixncmp(line, len, "common")) + pkt->status = GIT_ACK_COMMON; + else if (!git__prefixncmp(line, len, "ready")) + pkt->status = GIT_ACK_READY; + else + goto out_err; + } + + *out = (git_pkt *) pkt; + + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing ACK pkt-line"); + git__free(pkt); + return -1; +} + +static int nak_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_NAK; + *out = pkt; + + return 0; +} + +static int comment_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_comment *pkt; + size_t alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_comment), len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_COMMENT; + memcpy(pkt->comment, line, len); + pkt->comment[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; +} + +static int err_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt = NULL; + size_t alloclen; + + /* Remove "ERR " from the line */ + if (git__prefixncmp(line, len, "ERR ")) + goto out_err; + line += 4; + len -= 4; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_ERR; + pkt->len = len; + + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing ERR pkt-line"); + git__free(pkt); + return -1; +} + +static int data_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_data *pkt; + size_t alloclen; + + line++; + len--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_DATA; + pkt->len = len; + memcpy(pkt->data, line, len); + + *out = (git_pkt *) pkt; + + return 0; +} + +static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_progress *pkt; + size_t alloclen; + + line++; + len--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_PROGRESS; + pkt->len = len; + memcpy(pkt->data, line, len); + + *out = (git_pkt *) pkt; + + return 0; +} + +static int sideband_error_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt; + size_t alloc_len; + + line++; + len--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(git_pkt_err), len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + pkt = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ERR; + pkt->len = (int)len; + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *)pkt; + + return 0; +} + +/* + * Parse an other-ref line. + */ +static int ref_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ref *pkt; + size_t alloclen; + + pkt = git__calloc(1, sizeof(git_pkt_ref)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_REF; + + if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->head.oid, line) < 0) + goto out_err; + line += GIT_OID_HEXSZ; + len -= GIT_OID_HEXSZ; + + if (git__prefixncmp(line, len, " ")) + goto out_err; + line++; + len--; + + if (!len) + goto out_err; + + if (line[len - 1] == '\n') + --len; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + pkt->head.name = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt->head.name); + + memcpy(pkt->head.name, line, len); + pkt->head.name[len] = '\0'; + + if (strlen(pkt->head.name) < len) + pkt->capabilities = strchr(pkt->head.name, '\0') + 1; + + *out = (git_pkt *)pkt; + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing REF pkt-line"); + if (pkt) + git__free(pkt->head.name); + git__free(pkt); + return -1; +} + +static int ok_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ok *pkt; + size_t alloc_len; + + pkt = git__malloc(sizeof(*pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_OK; + + if (git__prefixncmp(line, len, "ok ")) + goto out_err; + line += 3; + len -= 3; + + if (len && line[len - 1] == '\n') + --len; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); + pkt->ref = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(pkt->ref); + + memcpy(pkt->ref, line, len); + pkt->ref[len] = '\0'; + + *out = (git_pkt *)pkt; + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing OK pkt-line"); + git__free(pkt); + return -1; +} + +static int ng_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ng *pkt; + const char *ptr, *eol; + size_t alloclen; + + pkt = git__malloc(sizeof(*pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->ref = NULL; + pkt->type = GIT_PKT_NG; + + eol = line + len; + + if (git__prefixncmp(line, len, "ng ")) + goto out_err; + line += 3; + + if (!(ptr = memchr(line, ' ', eol - line))) + goto out_err; + len = ptr - line; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + pkt->ref = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt->ref); + + memcpy(pkt->ref, line, len); + pkt->ref[len] = '\0'; + + line = ptr + 1; + if (line >= eol) + goto out_err; + + if (!(ptr = memchr(line, '\n', eol - line))) + goto out_err; + len = ptr - line; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + pkt->msg = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt->msg); + + memcpy(pkt->msg, line, len); + pkt->msg[len] = '\0'; + + *out = (git_pkt *)pkt; + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "invalid packet line"); + git__free(pkt->ref); + git__free(pkt); + return -1; +} + +static int unpack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_unpack *pkt; + + pkt = git__malloc(sizeof(*pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_UNPACK; + + if (!git__prefixncmp(line, len, "unpack ok")) + pkt->unpack_ok = 1; + else + pkt->unpack_ok = 0; + + *out = (git_pkt *)pkt; + return 0; +} + +static int parse_len(size_t *out, const char *line, size_t linelen) +{ + char num[PKT_LEN_SIZE + 1]; + int i, k, error; + int32_t len; + const char *num_end; + + /* Not even enough for the length */ + if (linelen < PKT_LEN_SIZE) + return GIT_EBUFS; + + memcpy(num, line, PKT_LEN_SIZE); + num[PKT_LEN_SIZE] = '\0'; + + for (i = 0; i < PKT_LEN_SIZE; ++i) { + if (!isxdigit(num[i])) { + /* Make sure there are no special characters before passing to error message */ + for (k = 0; k < PKT_LEN_SIZE; ++k) { + if(!isprint(num[k])) { + num[k] = '.'; + } + } + + git_error_set(GIT_ERROR_NET, "invalid hex digit in length: '%s'", num); + return -1; + } + } + + if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0) + return error; + + if (len < 0) + return -1; + + *out = (size_t) len; + return 0; +} + +/* + * As per the documentation, the syntax is: + * + * pkt-line = data-pkt / flush-pkt + * data-pkt = pkt-len pkt-payload + * pkt-len = 4*(HEXDIG) + * pkt-payload = (pkt-len -4)*(OCTET) + * flush-pkt = "0000" + * + * Which means that the first four bytes are the length of the line, + * in ASCII hexadecimal (including itself) + */ + +int git_pkt_parse_line( + git_pkt **pkt, const char **endptr, const char *line, size_t linelen) +{ + int error; + size_t len; + + if ((error = parse_len(&len, line, linelen)) < 0) { + /* + * If we fail to parse the length, it might be + * because the server is trying to send us the + * packfile already or because we do not yet have + * enough data. + */ + if (error == GIT_EBUFS) + ; + else if (!git__prefixncmp(line, linelen, "PACK")) + git_error_set(GIT_ERROR_NET, "unexpected pack file"); + else + git_error_set(GIT_ERROR_NET, "bad packet length"); + return error; + } + + /* + * Make sure there is enough in the buffer to satisfy + * this line. + */ + if (linelen < len) + return GIT_EBUFS; + + /* + * The length has to be exactly 0 in case of a flush + * packet or greater than PKT_LEN_SIZE, as the decoded + * length includes its own encoded length of four bytes. + */ + if (len != 0 && len < PKT_LEN_SIZE) + return GIT_ERROR; + + line += PKT_LEN_SIZE; + /* + * The Git protocol does not specify empty lines as part + * of the protocol. Not knowing what to do with an empty + * line, we should return an error upon hitting one. + */ + if (len == PKT_LEN_SIZE) { + git_error_set_str(GIT_ERROR_NET, "Invalid empty packet"); + return GIT_ERROR; + } + + if (len == 0) { /* Flush pkt */ + *endptr = line; + return flush_pkt(pkt); + } + + len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ + + if (*line == GIT_SIDE_BAND_DATA) + error = data_pkt(pkt, line, len); + else if (*line == GIT_SIDE_BAND_PROGRESS) + error = sideband_progress_pkt(pkt, line, len); + else if (*line == GIT_SIDE_BAND_ERROR) + error = sideband_error_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ACK")) + error = ack_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "NAK")) + error = nak_pkt(pkt); + else if (!git__prefixncmp(line, len, "ERR")) + error = err_pkt(pkt, line, len); + else if (*line == '#') + error = comment_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ok")) + error = ok_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ng")) + error = ng_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "unpack")) + error = unpack_pkt(pkt, line, len); + else + error = ref_pkt(pkt, line, len); + + *endptr = line + len; + + return error; +} + +void git_pkt_free(git_pkt *pkt) +{ + if (pkt == NULL) { + return; + } + if (pkt->type == GIT_PKT_REF) { + git_pkt_ref *p = (git_pkt_ref *) pkt; + git__free(p->head.name); + git__free(p->head.symref_target); + } + + if (pkt->type == GIT_PKT_OK) { + git_pkt_ok *p = (git_pkt_ok *) pkt; + git__free(p->ref); + } + + if (pkt->type == GIT_PKT_NG) { + git_pkt_ng *p = (git_pkt_ng *) pkt; + git__free(p->ref); + git__free(p->msg); + } + + git__free(pkt); +} + +int git_pkt_buffer_flush(git_str *buf) +{ + return git_str_put(buf, pkt_flush_str, strlen(pkt_flush_str)); +} + +static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_str *buf) +{ + git_str str = GIT_STR_INIT; + char oid[GIT_OID_HEXSZ +1] = {0}; + size_t len; + + /* Prefer multi_ack_detailed */ + if (caps->multi_ack_detailed) + git_str_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " "); + else if (caps->multi_ack) + git_str_puts(&str, GIT_CAP_MULTI_ACK " "); + + /* Prefer side-band-64k if the server supports both */ + if (caps->side_band_64k) + git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); + else if (caps->side_band) + git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND); + + if (caps->include_tag) + git_str_puts(&str, GIT_CAP_INCLUDE_TAG " "); + + if (caps->thin_pack) + git_str_puts(&str, GIT_CAP_THIN_PACK " "); + + if (caps->ofs_delta) + git_str_puts(&str, GIT_CAP_OFS_DELTA " "); + + if (git_str_oom(&str)) + return -1; + + len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + + git_str_len(&str) + 1 /* LF */; + + if (len > 0xffff) { + git_error_set(GIT_ERROR_NET, + "tried to produce packet with invalid length %" PRIuZ, len); + return -1; + } + + git_str_grow_by(buf, len); + git_oid_fmt(oid, &head->oid); + git_str_printf(buf, + "%04xwant %s %s\n", (unsigned int)len, oid, git_str_cstr(&str)); + git_str_dispose(&str); + + GIT_ERROR_CHECK_ALLOC_STR(buf); + + return 0; +} + +/* + * All "want" packets have the same length and format, so what we do + * is overwrite the OID each time. + */ + +int git_pkt_buffer_wants( + const git_remote_head * const *refs, + size_t count, + transport_smart_caps *caps, + git_str *buf) +{ + size_t i = 0; + const git_remote_head *head; + + if (caps->common) { + for (; i < count; ++i) { + head = refs[i]; + if (!head->local) + break; + } + + if (buffer_want_with_caps(refs[i], caps, buf) < 0) + return -1; + + i++; + } + + for (; i < count; ++i) { + char oid[GIT_OID_HEXSZ]; + + head = refs[i]; + if (head->local) + continue; + + git_oid_fmt(oid, &head->oid); + git_str_put(buf, pkt_want_prefix, strlen(pkt_want_prefix)); + git_str_put(buf, oid, GIT_OID_HEXSZ); + git_str_putc(buf, '\n'); + if (git_str_oom(buf)) + return -1; + } + + return git_pkt_buffer_flush(buf); +} + +int git_pkt_buffer_have(git_oid *oid, git_str *buf) +{ + char oidhex[GIT_OID_HEXSZ + 1]; + + memset(oidhex, 0x0, sizeof(oidhex)); + git_oid_fmt(oidhex, oid); + return git_str_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); +} + +int git_pkt_buffer_done(git_str *buf) +{ + return git_str_puts(buf, pkt_done_str); +} diff --git a/src/libgit2/transports/smart_protocol.c b/src/libgit2/transports/smart_protocol.c new file mode 100644 index 000000000..8cf027133 --- /dev/null +++ b/src/libgit2/transports/smart_protocol.c @@ -0,0 +1,1094 @@ +/* + * 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 "git2/odb_backend.h" + +#include "smart.h" +#include "refs.h" +#include "repository.h" +#include "push.h" +#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). */ +#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 + +bool git_smart__ofs_delta_enabled = true; + +int git_smart__store_refs(transport_smart *t, int flushes) +{ + gitno_buffer *buf = &t->buffer; + git_vector *refs = &t->refs; + int error, flush = 0, recvd; + const char *line_end = NULL; + git_pkt *pkt = NULL; + size_t i; + + /* Clear existing refs in case git_remote_connect() is called again + * after git_remote_disconnect(). + */ + git_vector_foreach(refs, i, pkt) { + git_pkt_free(pkt); + } + git_vector_clear(refs); + pkt = NULL; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, &line_end, buf->data, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) + return error; + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) + return recvd; + + if (recvd == 0) { + git_error_set(GIT_ERROR_NET, "early EOF"); + return GIT_EEOF; + } + + continue; + } + + if (gitno_consume(buf, line_end) < 0) + return -1; + + if (pkt->type == GIT_PKT_ERR) { + git_error_set(GIT_ERROR_NET, "remote error: %s", ((git_pkt_err *)pkt)->error); + git__free(pkt); + return -1; + } + + if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) + return -1; + + if (pkt->type == GIT_PKT_FLUSH) { + flush++; + git_pkt_free(pkt); + } + } while (flush < flushes); + + return flush; +} + +static int append_symref(const char **out, git_vector *symrefs, const char *ptr) +{ + int error; + const char *end; + git_str buf = GIT_STR_INIT; + git_refspec *mapping = NULL; + + ptr += strlen(GIT_CAP_SYMREF); + if (*ptr != '=') + goto on_invalid; + + ptr++; + if (!(end = strchr(ptr, ' ')) && + !(end = strchr(ptr, '\0'))) + goto on_invalid; + + if ((error = git_str_put(&buf, ptr, end - ptr)) < 0) + return error; + + /* symref mapping has refspec format */ + mapping = git__calloc(1, sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(mapping); + + error = git_refspec__parse(mapping, git_str_cstr(&buf), true); + git_str_dispose(&buf); + + /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ + if (error < 0) { + if (git_error_last()->klass != GIT_ERROR_NOMEMORY) + goto on_invalid; + + git__free(mapping); + return error; + } + + if ((error = git_vector_insert(symrefs, mapping)) < 0) + return error; + + *out = end; + return 0; + +on_invalid: + git_error_set(GIT_ERROR_NET, "remote sent invalid symref"); + git_refspec__dispose(mapping); + git__free(mapping); + return -1; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) +{ + const char *ptr; + + /* No refs or capabilities, odd but not a problem */ + if (pkt == NULL || pkt->capabilities == NULL) + return GIT_ENOTFOUND; + + ptr = pkt->capabilities; + while (ptr != NULL && *ptr != '\0') { + if (*ptr == ' ') + ptr++; + + if (git_smart__ofs_delta_enabled && !git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { + caps->common = caps->ofs_delta = 1; + ptr += strlen(GIT_CAP_OFS_DELTA); + continue; + } + + /* Keep multi_ack_detailed before multi_ack */ + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) { + caps->common = caps->multi_ack_detailed = 1; + ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { + caps->common = caps->multi_ack = 1; + ptr += strlen(GIT_CAP_MULTI_ACK); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { + caps->common = caps->include_tag = 1; + ptr += strlen(GIT_CAP_INCLUDE_TAG); + continue; + } + + /* Keep side-band check after side-band-64k */ + if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { + caps->common = caps->side_band_64k = 1; + ptr += strlen(GIT_CAP_SIDE_BAND_64K); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { + caps->common = caps->side_band = 1; + ptr += strlen(GIT_CAP_SIDE_BAND); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { + caps->common = caps->delete_refs = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { + caps->common = caps->thin_pack = 1; + ptr += strlen(GIT_CAP_THIN_PACK); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { + int error; + + if ((error = append_symref(&ptr, symrefs, ptr)) < 0) + return error; + + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_WANT_TIP_SHA1)) { + caps->common = caps->want_tip_sha1 = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_WANT_REACHABLE_SHA1)) { + caps->common = caps->want_reachable_sha1 = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + + /* We don't know this capability, so skip it */ + ptr = strchr(ptr, ' '); + } + + return 0; +} + +static int recv_pkt(git_pkt **out_pkt, git_pkt_type *out_type, gitno_buffer *buf) +{ + const char *ptr = buf->data, *line_end = ptr; + git_pkt *pkt = NULL; + int error = 0, ret; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, &line_end, ptr, buf->offset); + else + error = GIT_EBUFS; + + if (error == 0) + break; /* return the pkt */ + + if (error < 0 && error != GIT_EBUFS) + return error; + + if ((ret = gitno_recv(buf)) < 0) { + return ret; + } else if (ret == 0) { + git_error_set(GIT_ERROR_NET, "early EOF"); + return GIT_EEOF; + } + } while (error); + + if (gitno_consume(buf, line_end) < 0) + return -1; + + if (out_type != NULL) + *out_type = pkt->type; + if (out_pkt != NULL) + *out_pkt = pkt; + else + git__free(pkt); + + return error; +} + +static int store_common(transport_smart *t) +{ + git_pkt *pkt = NULL; + gitno_buffer *buf = &t->buffer; + int error; + + do { + if ((error = recv_pkt(&pkt, NULL, buf)) < 0) + return error; + + if (pkt->type != GIT_PKT_ACK) { + git__free(pkt); + return 0; + } + + if (git_vector_insert(&t->common, pkt) < 0) { + git__free(pkt); + return -1; + } + } while (1); + + return 0; +} + +static int wait_while_ack(gitno_buffer *buf) +{ + int error; + git_pkt *pkt = NULL; + git_pkt_ack *ack = NULL; + + while (1) { + git_pkt_free(pkt); + + if ((error = recv_pkt(&pkt, NULL, buf)) < 0) + return error; + + if (pkt->type == GIT_PKT_NAK) + break; + if (pkt->type != GIT_PKT_ACK) + continue; + + ack = (git_pkt_ack*)pkt; + + if (ack->status != GIT_ACK_CONTINUE && + ack->status != GIT_ACK_COMMON && + ack->status != GIT_ACK_READY) { + break; + } + } + + git_pkt_free(pkt); + return 0; +} + +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_str data = GIT_STR_INIT; + git_revwalk *walk = NULL; + int error = -1; + git_pkt_type pkt_type; + unsigned int i; + git_oid oid; + + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) + return error; + + 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; + + /* + * Our support for ACK extensions is simply to parse them. On + * the first ACK we will accept that as enough common + * objects. We give up if we haven't found an answer in the + * first 256 we send. + */ + i = 0; + while (i < 256) { + error = git_revwalk_next(&oid, walk); + + if (error < 0) { + if (GIT_ITEROVER == error) + break; + + goto on_error; + } + + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + if (t->cancelled.val) { + git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + + git_pkt_buffer_flush(&data); + if (git_str_oom(&data)) { + error = -1; + goto on_error; + } + + if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) + goto on_error; + + git_str_clear(&data); + if (t->caps.multi_ack || t->caps.multi_ack_detailed) { + if ((error = store_common(t)) < 0) + goto on_error; + } else { + if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) + goto on_error; + + if (pkt_type == GIT_PKT_ACK) { + break; + } else if (pkt_type == GIT_PKT_NAK) { + continue; + } else { + git_error_set(GIT_ERROR_NET, "unexpected pkt type"); + error = -1; + goto on_error; + } + } + } + + if (t->common.length > 0) + break; + + if (i % 20 == 0 && t->rpc) { + git_pkt_ack *pkt; + unsigned int j; + + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) + goto on_error; + + git_vector_foreach(&t->common, j, pkt) { + if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) + goto on_error; + } + + if (git_str_oom(&data)) { + error = -1; + goto on_error; + } + } + } + + /* Tell the other end that we're done negotiating */ + if (t->rpc && t->common.length > 0) { + git_pkt_ack *pkt; + unsigned int j; + + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) + goto on_error; + + git_vector_foreach(&t->common, j, pkt) { + if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) + goto on_error; + } + + if (git_str_oom(&data)) { + error = -1; + goto on_error; + } + } + + if ((error = git_pkt_buffer_done(&data)) < 0) + goto on_error; + + if (t->cancelled.val) { + git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) + goto on_error; + + git_str_dispose(&data); + git_revwalk_free(walk); + + /* Now let's eat up whatever the server gives us */ + if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) { + if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) + return error; + + if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { + git_error_set(GIT_ERROR_NET, "unexpected pkt type"); + return -1; + } + } else { + error = wait_while_ack(buf); + } + + return error; + +on_error: + git_revwalk_free(walk); + git_str_dispose(&data); + return error; +} + +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"); + return GIT_EUSER; + } + + if (writepack->append(writepack, buf->data, buf->offset, stats) < 0) + return -1; + + gitno_consume_n(buf, buf->offset); + + if ((recvd = gitno_recv(buf)) < 0) + return recvd; + } while(recvd > 0); + + if (writepack->commit(writepack, stats) < 0) + return -1; + + return 0; +} + +struct network_packetsize_payload +{ + git_indexer_progress_cb callback; + void *payload; + git_indexer_progress *stats; + size_t last_fired_bytes; +}; + +static int network_packetsize(size_t received, void *payload) +{ + struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; + + /* Accumulate bytes */ + npp->stats->received_bytes += received; + + /* Fire notification if the threshold is reached */ + if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { + npp->last_fired_bytes = npp->stats->received_bytes; + + if (npp->callback(npp->stats, npp->payload)) + return GIT_EUSER; + } + + return 0; +} + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_indexer_progress *stats) +{ + transport_smart *t = (transport_smart *)transport; + gitno_buffer *buf = &t->buffer; + git_odb *odb; + struct git_odb_writepack *writepack = NULL; + int error = 0; + struct network_packetsize_payload npp = {0}; + + git_indexer_progress_cb progress_cb = t->connect_opts.callbacks.transfer_progress; + void *progress_payload = t->connect_opts.callbacks.payload; + + memset(stats, 0, sizeof(git_indexer_progress)); + + if (progress_cb) { + npp.callback = progress_cb; + npp.payload = progress_payload; + npp.stats = stats; + t->packetsize_cb = &network_packetsize; + t->packetsize_payload = &npp; + + /* We might have something in the buffer already from negotiate_fetch */ + if (t->buffer.offset > 0 && !t->cancelled.val) + if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload)) + git_atomic32_set(&t->cancelled, 1); + } + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)) + goto done; + + /* + * If the remote doesn't support the side-band, we can feed + * the data directly to the pack writer. Otherwise, we need to + * check which one belongs there. + */ + if (!t->caps.side_band && !t->caps.side_band_64k) { + error = no_sideband(t, writepack, buf, stats); + goto done; + } + + do { + git_pkt *pkt = NULL; + + /* Check cancellation before network call */ + if (t->cancelled.val) { + git_error_clear(); + error = GIT_EUSER; + goto done; + } + + if ((error = recv_pkt(&pkt, NULL, buf)) >= 0) { + /* Check cancellation after network call */ + if (t->cancelled.val) { + git_error_clear(); + error = GIT_EUSER; + } else if (pkt->type == GIT_PKT_PROGRESS) { + if (t->connect_opts.callbacks.sideband_progress) { + git_pkt_progress *p = (git_pkt_progress *) pkt; + + if (p->len > INT_MAX) { + git_error_set(GIT_ERROR_NET, "oversized progress message"); + error = GIT_ERROR; + goto done; + } + + error = t->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, t->connect_opts.callbacks.payload); + } + } else if (pkt->type == GIT_PKT_DATA) { + git_pkt_data *p = (git_pkt_data *) pkt; + + if (p->len) + error = writepack->append(writepack, p->data, p->len, stats); + } else if (pkt->type == GIT_PKT_FLUSH) { + /* A flush indicates the end of the packfile */ + git__free(pkt); + break; + } + } + + git_pkt_free(pkt); + + if (error < 0) + goto done; + + } while (1); + + /* + * 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 + * "received_bytes" value, but until (if?) this is unified + * then we will report progress again to be sure that the + * correct last received_bytes value is reported. + */ + if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) { + error = npp.callback(npp.stats, npp.payload); + if (error != 0) + goto done; + } + + error = writepack->commit(writepack, stats); + +done: + if (writepack) + writepack->free(writepack); + if (progress_cb) { + t->packetsize_cb = NULL; + t->packetsize_payload = NULL; + } + + return error; +} + +static int gen_pktline(git_str *buf, git_push *push) +{ + push_spec *spec; + size_t i, len; + char old_id[GIT_OID_HEXSZ+1], new_id[GIT_OID_HEXSZ+1]; + + old_id[GIT_OID_HEXSZ] = '\0'; new_id[GIT_OID_HEXSZ] = '\0'; + + git_vector_foreach(&push->specs, i, spec) { + len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->refspec.dst); + + if (i == 0) { + ++len; /* '\0' */ + if (push->report_status) + len += strlen(GIT_CAP_REPORT_STATUS) + 1; + len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; + } + + git_oid_fmt(old_id, &spec->roid); + git_oid_fmt(new_id, &spec->loid); + + git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst); + + if (i == 0) { + git_str_putc(buf, '\0'); + /* Core git always starts their capabilities string with a space */ + if (push->report_status) { + git_str_putc(buf, ' '); + git_str_printf(buf, GIT_CAP_REPORT_STATUS); + } + git_str_putc(buf, ' '); + git_str_printf(buf, GIT_CAP_SIDE_BAND_64K); + } + + git_str_putc(buf, '\n'); + } + + git_str_puts(buf, "0000"); + return git_str_oom(buf) ? -1 : 0; +} + +static int add_push_report_pkt(git_push *push, git_pkt *pkt) +{ + push_status *status; + + switch (pkt->type) { + case GIT_PKT_OK: + status = git__calloc(1, sizeof(push_status)); + GIT_ERROR_CHECK_ALLOC(status); + status->msg = NULL; + status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); + if (!status->ref || + git_vector_insert(&push->status, status) < 0) { + git_push_status_free(status); + return -1; + } + break; + case GIT_PKT_NG: + status = git__calloc(1, sizeof(push_status)); + GIT_ERROR_CHECK_ALLOC(status); + status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); + status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); + if (!status->ref || !status->msg || + git_vector_insert(&push->status, status) < 0) { + git_push_status_free(status); + return -1; + } + break; + case GIT_PKT_UNPACK: + push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; + break; + case GIT_PKT_FLUSH: + return GIT_ITEROVER; + default: + git_error_set(GIT_ERROR_NET, "report-status: protocol error"); + return -1; + } + + return 0; +} + +static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_str *data_pkt_buf) +{ + git_pkt *pkt; + const char *line, *line_end = NULL; + size_t line_len; + int error; + int reading_from_buf = data_pkt_buf->size > 0; + + if (reading_from_buf) { + /* We had an existing partial packet, so add the new + * packet to the buffer and parse the whole thing */ + git_str_put(data_pkt_buf, data_pkt->data, data_pkt->len); + line = data_pkt_buf->ptr; + line_len = data_pkt_buf->size; + } + else { + line = data_pkt->data; + line_len = data_pkt->len; + } + + while (line_len > 0) { + error = git_pkt_parse_line(&pkt, &line_end, line, line_len); + + if (error == GIT_EBUFS) { + /* Buffer the data when the inner packet is split + * across multiple sideband packets */ + if (!reading_from_buf) + git_str_put(data_pkt_buf, line, line_len); + error = 0; + goto done; + } + else if (error < 0) + goto done; + + /* Advance in the buffer */ + line_len -= (line_end - line); + line = line_end; + + error = add_push_report_pkt(push, pkt); + + git_pkt_free(pkt); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + + error = 0; + +done: + if (reading_from_buf) + git_str_consume(data_pkt_buf, line_end); + return error; +} + +static int parse_report(transport_smart *transport, git_push *push) +{ + git_pkt *pkt = NULL; + const char *line_end = NULL; + gitno_buffer *buf = &transport->buffer; + int error, recvd; + git_str data_pkt_buf = GIT_STR_INIT; + + for (;;) { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, &line_end, + buf->data, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) { + error = -1; + goto done; + } + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) { + error = recvd; + goto done; + } + + if (recvd == 0) { + git_error_set(GIT_ERROR_NET, "early EOF"); + error = GIT_EEOF; + goto done; + } + continue; + } + + if (gitno_consume(buf, line_end) < 0) + return -1; + + error = 0; + + switch (pkt->type) { + case GIT_PKT_DATA: + /* This is a sideband packet which contains other packets */ + error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt, &data_pkt_buf); + break; + case GIT_PKT_ERR: + git_error_set(GIT_ERROR_NET, "report-status: Error reported: %s", + ((git_pkt_err *)pkt)->error); + error = -1; + break; + case GIT_PKT_PROGRESS: + if (transport->connect_opts.callbacks.sideband_progress) { + git_pkt_progress *p = (git_pkt_progress *) pkt; + + if (p->len > INT_MAX) { + git_error_set(GIT_ERROR_NET, "oversized progress message"); + error = GIT_ERROR; + goto done; + } + + error = transport->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, transport->connect_opts.callbacks.payload); + } + break; + default: + error = add_push_report_pkt(push, pkt); + break; + } + + git_pkt_free(pkt); + + /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ + if (error == GIT_ITEROVER) { + error = 0; + 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"); + error = GIT_ERROR; + } + goto done; + } + + if (error < 0) { + goto done; + } + } +done: + git_str_dispose(&data_pkt_buf); + return error; +} + +static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec) +{ + git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref)); + GIT_ERROR_CHECK_ALLOC(added); + + added->type = GIT_PKT_REF; + git_oid_cpy(&added->head.oid, &push_spec->loid); + added->head.name = git__strdup(push_spec->refspec.dst); + + if (!added->head.name || + git_vector_insert(refs, added) < 0) { + git_pkt_free((git_pkt *)added); + return -1; + } + + return 0; +} + +static int update_refs_from_report( + git_vector *refs, + git_vector *push_specs, + git_vector *push_report) +{ + git_pkt_ref *ref; + push_spec *push_spec; + push_status *push_status; + size_t i, j, refs_len; + int cmp; + + /* For each push spec we sent to the server, we should have + * gotten back a status packet in the push report */ + if (push_specs->length != push_report->length) { + git_error_set(GIT_ERROR_NET, "report-status: protocol error"); + return -1; + } + + /* We require that push_specs be sorted with push_spec_rref_cmp, + * and that push_report be sorted with push_status_ref_cmp */ + git_vector_sort(push_specs); + git_vector_sort(push_report); + + git_vector_foreach(push_specs, i, push_spec) { + push_status = git_vector_get(push_report, i); + + /* For each push spec we sent to the server, we should have + * gotten back a status packet in the push report which matches */ + if (strcmp(push_spec->refspec.dst, push_status->ref)) { + git_error_set(GIT_ERROR_NET, "report-status: protocol error"); + return -1; + } + } + + /* We require that refs be sorted with ref_name_cmp */ + git_vector_sort(refs); + i = j = 0; + refs_len = refs->length; + + /* Merge join push_specs with refs */ + while (i < push_specs->length && j < refs_len) { + push_spec = git_vector_get(push_specs, i); + push_status = git_vector_get(push_report, i); + ref = git_vector_get(refs, j); + + cmp = strcmp(push_spec->refspec.dst, ref->head.name); + + /* Iterate appropriately */ + if (cmp <= 0) i++; + if (cmp >= 0) j++; + + /* Add case */ + if (cmp < 0 && + !push_status->msg && + add_ref_from_push_spec(refs, push_spec) < 0) + return -1; + + /* Update case, delete case */ + if (cmp == 0 && + !push_status->msg) + git_oid_cpy(&ref->head.oid, &push_spec->loid); + } + + for (; i < push_specs->length; i++) { + push_spec = git_vector_get(push_specs, i); + push_status = git_vector_get(push_report, i); + + /* Add case */ + if (!push_status->msg && + add_ref_from_push_spec(refs, push_spec) < 0) + return -1; + } + + /* Remove any refs which we updated to have a zero OID. */ + git_vector_rforeach(refs, i, ref) { + if (git_oid_is_zero(&ref->head.oid)) { + git_vector_remove(refs, i); + git_pkt_free((git_pkt *)ref); + } + } + + git_vector_sort(refs); + + return 0; +} + +struct push_packbuilder_payload +{ + git_smart_subtransport_stream *stream; + git_packbuilder *pb; + git_push_transfer_progress_cb cb; + void *cb_payload; + size_t last_bytes; + double last_progress_report_time; +}; + +static int stream_thunk(void *buf, size_t size, void *data) +{ + int error = 0; + struct push_packbuilder_payload *payload = data; + + if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0) + return error; + + if (payload->cb) { + double current_time = git__timer(); + double elapsed = current_time - payload->last_progress_report_time; + payload->last_bytes += size; + + if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { + payload->last_progress_report_time = current_time; + error = payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload); + } + } + + return error; +} + +int git_smart__push(git_transport *transport, git_push *push) +{ + transport_smart *t = (transport_smart *)transport; + git_remote_callbacks *cbs = &t->connect_opts.callbacks; + struct push_packbuilder_payload packbuilder_payload = {0}; + git_str pktline = GIT_STR_INIT; + int error = 0, need_pack = 0; + push_spec *spec; + unsigned int i; + + packbuilder_payload.pb = push->pb; + + if (cbs && cbs->push_transfer_progress) { + packbuilder_payload.cb = cbs->push_transfer_progress; + packbuilder_payload.cb_payload = cbs->payload; + } + +#ifdef PUSH_DEBUG +{ + git_remote_head *head; + char hex[GIT_OID_HEXSZ+1]; hex[GIT_OID_HEXSZ] = '\0'; + + git_vector_foreach(&push->remote->refs, i, head) { + git_oid_fmt(hex, &head->oid); + fprintf(stderr, "%s (%s)\n", hex, head->name); + } + + git_vector_foreach(&push->specs, i, spec) { + git_oid_fmt(hex, &spec->roid); + fprintf(stderr, "%s (%s) -> ", hex, spec->lref); + git_oid_fmt(hex, &spec->loid); + fprintf(stderr, "%s (%s)\n", hex, spec->rref ? + spec->rref : spec->lref); + } +} +#endif + + /* + * Figure out if we need to send a packfile; which is in all + * cases except when we only send delete commands + */ + git_vector_foreach(&push->specs, i, spec) { + if (spec->refspec.src && spec->refspec.src[0] != '\0') { + need_pack = 1; + break; + } + } + + /* prepare pack before sending pack header to avoid timeouts */ + if (need_pack && ((error = git_packbuilder__prepare(push->pb))) < 0) + goto done; + + if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 || + (error = gen_pktline(&pktline, push)) < 0 || + (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_str_cstr(&pktline), git_str_len(&pktline))) < 0) + goto done; + + if (need_pack && + (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0) + goto done; + + /* If we sent nothing or the server doesn't support report-status, then + * we consider the pack to have been unpacked successfully */ + if (!push->specs.length || !push->report_status) + push->unpack_ok = 1; + else if ((error = parse_report(t, push)) < 0) + goto done; + + /* If progress is being reported write the final report */ + if (cbs && cbs->push_transfer_progress) { + error = cbs->push_transfer_progress( + push->pb->nr_written, + push->pb->nr_objects, + packbuilder_payload.last_bytes, + cbs->payload); + + if (error < 0) + goto done; + } + + if (push->status.length) { + error = update_refs_from_report(&t->refs, &push->specs, &push->status); + if (error < 0) + goto done; + + error = git_smart__update_heads(t, NULL); + } + +done: + git_str_dispose(&pktline); + return error; +} diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c new file mode 100644 index 000000000..89f085230 --- /dev/null +++ b/src/libgit2/transports/ssh.c @@ -0,0 +1,915 @@ +/* + * 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 "ssh.h" + +#ifdef GIT_SSH +#include +#endif + +#include "runtime.h" +#include "net.h" +#include "netops.h" +#include "smart.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) + +static const char cmd_uploadpack[] = "git-upload-pack"; +static const char cmd_receivepack[] = "git-receive-pack"; + +typedef struct { + git_smart_subtransport_stream parent; + git_stream *io; + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + const char *cmd; + git_net_url url; + unsigned sent_command : 1; +} ssh_stream; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + ssh_stream *current_stream; + git_credential *cred; + char *cmd_uploadpack; + char *cmd_receivepack; +} ssh_subtransport; + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); + +static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) +{ + char *ssherr; + libssh2_session_last_error(session, &ssherr, NULL, 0); + + git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); +} + +/* + * Create a git protocol request. + * + * For example: git-upload-pack '/libgit2/libgit2' + */ +static int gen_proto(git_str *request, const char *cmd, git_net_url *url) +{ + const char *repo; + + repo = url->path; + + if (repo && repo[0] == '/' && repo[1] == '~') + repo++; + + if (!repo || !repo[0]) { + git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); + return -1; + } + + git_str_puts(request, cmd); + git_str_puts(request, " '"); + git_str_puts(request, repo); + git_str_puts(request, "'"); + + if (git_str_oom(request)) + return -1; + + return 0; +} + +static int send_command(ssh_stream *s) +{ + int error; + git_str request = GIT_STR_INIT; + + error = gen_proto(&request, s->cmd, &s->url); + if (error < 0) + goto cleanup; + + error = libssh2_channel_exec(s->channel, request.ptr); + if (error < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not execute request"); + goto cleanup; + } + + s->sent_command = 1; + +cleanup: + git_str_dispose(&request); + return error; +} + +static int ssh_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + int rc; + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + + *bytes_read = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read data"); + return -1; + } + + /* + * If we can't get anything out of stdout, it's typically a + * not-found error, so read from stderr and signal EOF on + * stderr. + */ + if (rc == 0) { + if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { + git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer); + return GIT_EEOF; + } else if (rc < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read stderr"); + return -1; + } + } + + + *bytes_read = rc; + + return 0; +} + +static int ssh_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + size_t off = 0; + ssize_t ret = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + do { + ret = libssh2_channel_write(s->channel, buffer + off, len - off); + if (ret < 0) + break; + + off += ret; + + } while (off < len); + + if (ret < 0) { + ssh_error(s->session, "SSH could not write data"); + return -1; + } + + return 0; +} + +static void ssh_stream_free(git_smart_subtransport_stream *stream) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + ssh_subtransport *t; + + if (!stream) + return; + + t = OWNING_SUBTRANSPORT(s); + t->current_stream = NULL; + + if (s->channel) { + libssh2_channel_close(s->channel); + libssh2_channel_free(s->channel); + s->channel = NULL; + } + + if (s->session) { + libssh2_session_disconnect(s->session, "closing transport"); + libssh2_session_free(s->session); + s->session = NULL; + } + + if (s->io) { + git_stream_close(s->io); + git_stream_free(s->io); + s->io = NULL; + } + + git_net_url_dispose(&s->url); + git__free(s); +} + +static int ssh_stream_alloc( + ssh_subtransport *t, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + ssh_stream *s; + + GIT_ASSERT_ARG(stream); + + s = git__calloc(sizeof(ssh_stream), 1); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = ssh_stream_read; + s->parent.write = ssh_stream_write; + s->parent.free = ssh_stream_free; + + s->cmd = cmd; + + *stream = &s->parent; + return 0; +} + +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; + + LIBSSH2_AGENT *agent = libssh2_agent_init(session); + + if (agent == NULL) + return -1; + + rc = libssh2_agent_connect(agent); + + if (rc != LIBSSH2_ERROR_NONE) + goto shutdown; + + rc = libssh2_agent_list_identities(agent); + + if (rc != LIBSSH2_ERROR_NONE) + goto shutdown; + + while (1) { + rc = libssh2_agent_get_identity(agent, &curr, prev); + + if (rc < 0) + goto shutdown; + + /* rc is set to 1 whenever the ssh agent ran out of keys to check. + * Set the error code to authentication failure rather than erroring + * out with an untranslatable error code. + */ + if (rc == 1) { + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + goto shutdown; + } + + rc = libssh2_agent_userauth(agent, c->username, curr); + + if (rc == 0) + break; + + prev = curr; + } + +shutdown: + + if (rc != LIBSSH2_ERROR_NONE) + ssh_error(session, "error authenticating"); + + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + + return rc; +} + +static int _git_ssh_authenticate_session( + LIBSSH2_SESSION *session, + git_credential *cred) +{ + int rc; + + do { + git_error_clear(); + switch (cred->credtype) { + 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_CREDENTIAL_SSH_KEY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; + + if (c->privatekey) + rc = libssh2_userauth_publickey_fromfile( + session, c->username, c->publickey, + c->privatekey, c->passphrase); + else + rc = ssh_agent_auth(session, c); + + break; + } + 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_CREDENTIAL_SSH_INTERACTIVE: { + void **abstract = libssh2_session_abstract(session); + 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(). + * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() + * allows you to pass the `abstract` as part of the call, whereas + * libssh2_userauth_keyboard_interactive() does not! + * + * The only way to set the `abstract` pointer is by calling + * libssh2_session_abstract(), which will replace the existing + * pointer as is done below. This is safe for now (at time of writing), + * but may not be valid in future. + */ + *abstract = c->payload; + + rc = libssh2_userauth_keyboard_interactive( + session, c->username, c->prompt_callback); + break; + } +#ifdef GIT_SSH_MEMORY_CREDENTIALS + case GIT_CREDENTIAL_SSH_MEMORY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; + + GIT_ASSERT(c->username); + GIT_ASSERT(c->privatekey); + + rc = libssh2_userauth_publickey_frommemory( + session, + c->username, + strlen(c->username), + c->publickey, + c->publickey ? strlen(c->publickey) : 0, + c->privatekey, + strlen(c->privatekey), + c->passphrase); + break; + } +#endif + default: + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + } + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || + rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || + rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) + return GIT_EAUTH; + + if (rc != LIBSSH2_ERROR_NONE) { + if (!git_error_last()) + ssh_error(session, "Failed to authenticate SSH session"); + return -1; + } + + return 0; +} + +static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) +{ + int error, no_callback = 0; + git_credential *cred = NULL; + + if (!t->owner->connect_opts.callbacks.credentials) { + no_callback = 1; + } else { + error = t->owner->connect_opts.callbacks.credentials( + &cred, + t->owner->url, + user, + auth_methods, + t->owner->connect_opts.callbacks.payload); + + if (error == GIT_PASSTHROUGH) { + no_callback = 1; + } else if (error < 0) { + return error; + } else if (!cred) { + git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); + return -1; + } + } + + if (no_callback) { + git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); + return GIT_EAUTH; + } + + if (!(cred->credtype & auth_methods)) { + cred->free(cred); + git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); + return GIT_EAUTH; + } + + *out = cred; + + return 0; +} + +static int _git_ssh_session_create( + LIBSSH2_SESSION **session, + git_stream *io) +{ + int rc = 0; + LIBSSH2_SESSION *s; + git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); + + GIT_ASSERT_ARG(session); + + s = libssh2_session_init(); + if (!s) { + git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); + return -1; + } + + do { + rc = libssh2_session_handshake(s, socket->s); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(s, "failed to start SSH session"); + libssh2_session_free(s); + return -1; + } + + libssh2_session_set_blocking(s, 1); + + *session = s; + + 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) +{ + int auth_methods, error = 0; + ssh_stream *s; + git_credential *cred = NULL; + LIBSSH2_SESSION *session=NULL; + LIBSSH2_CHANNEL *channel=NULL; + + t->current_stream = NULL; + + *stream = NULL; + if (ssh_stream_alloc(t, cmd, stream) < 0) + return -1; + + s = (ssh_stream *)*stream; + s->session = NULL; + s->channel = NULL; + + if (git_net_str_is_url(url)) + error = git_net_url_parse(&s->url, url); + else + error = git_net_url_parse_scp(&s->url, url); + + if (error < 0) + goto done; + + if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || + (error = git_stream_connect(s->io)) < 0) + goto done; + + if ((error = _git_ssh_session_create(&session, s->io)) < 0) + goto done; + + if (t->owner->connect_opts.callbacks.certificate_check != NULL) { + git_cert_hostkey cert = {{ 0 }}, *cert_ptr; + const char *key; + size_t cert_len; + int cert_type; + + cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; + + key = libssh2_session_hostkey(session, &cert_len, &cert_type); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_RAW; + cert.hostkey = key; + cert.hostkey_len = cert_len; + switch (cert_type) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS; + break; + +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384; + break; + case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521; + break; +#endif + +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519; + break; +#endif + default: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN; + } + } + +#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; + memcpy(&cert.hash_sha1, key, 20); + } + + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_MD5; + memcpy(&cert.hash_md5, key, 16); + } + + if (cert.type == 0) { + git_error_set(GIT_ERROR_SSH, "unable to get the host key"); + error = -1; + goto done; + } + + /* We don't currently trust any hostkeys */ + git_error_clear(); + + cert_ptr = &cert; + + error = t->owner->connect_opts.callbacks.certificate_check( + (git_cert *)cert_ptr, + 0, + s->url.host, + t->owner->connect_opts.callbacks.payload); + + if (error < 0 && error != GIT_PASSTHROUGH) { + if (!git_error_last()) + git_error_set(GIT_ERROR_NET, "user cancelled hostkey check"); + + goto done; + } + } + + /* we need the username to ask for auth methods */ + if (!s->url.username) { + if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) + goto done; + + s->url.username = git__strdup(((git_credential_username *) cred)->username); + cred->free(cred); + cred = NULL; + if (!s->url.username) + goto done; + } else if (s->url.username && s->url.password) { + if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0) + goto done; + } + + if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) + goto done; + + error = GIT_EAUTH; + /* if we already have something to try */ + if (cred && auth_methods & cred->credtype) + error = _git_ssh_authenticate_session(session, cred); + + while (error == GIT_EAUTH) { + if (cred) { + cred->free(cred); + cred = NULL; + } + + if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0) + goto done; + + if (strcmp(s->url.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, s->url.username)) < 0) + goto done; + else + error = GIT_EAUTH; + } + } + + if (error < 0) + goto done; + + channel = libssh2_channel_open_session(session); + if (!channel) { + error = -1; + ssh_error(session, "Failed to open SSH channel"); + goto done; + } + + libssh2_channel_set_blocking(channel, 1); + + s->session = session; + s->channel = channel; + + t->current_stream = s; + +done: + if (error < 0) { + ssh_stream_free(*stream); + + if (session) + libssh2_session_free(session); + } + + if (cred) + cred->free(cred); + + return error; +} + +static int ssh_uploadpack_ls( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; + + return _git_ssh_setup_conn(t, url, cmd, stream); +} + +static int ssh_uploadpack( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); + return -1; +} + +static int ssh_receivepack_ls( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; + + + return _git_ssh_setup_conn(t, url, cmd, stream); +} + +static int ssh_receivepack( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); + return -1; +} + +static int _ssh_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return ssh_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return ssh_uploadpack(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return ssh_receivepack_ls(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK: + return ssh_receivepack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static int _ssh_close(git_smart_subtransport *subtransport) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + GIT_ASSERT(!t->current_stream); + + GIT_UNUSED(t); + + return 0; +} + +static void _ssh_free(git_smart_subtransport *subtransport) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + git__free(t); +} + +#define SSH_AUTH_PUBLICKEY "publickey" +#define SSH_AUTH_PASSWORD "password" +#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) +{ + const char *list, *ptr; + + *out = 0; + + list = libssh2_userauth_list(session, username, strlen(username)); + + /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ + if (list == NULL && !libssh2_userauth_authenticated(session)) { + ssh_error(session, "Failed to retrieve list of SSH authentication methods"); + return GIT_EAUTH; + } + + ptr = list; + while (ptr) { + if (*ptr == ',') + ptr++; + + if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { + *out |= GIT_CREDENTIAL_SSH_KEY; + *out |= GIT_CREDENTIAL_SSH_CUSTOM; +#ifdef GIT_SSH_MEMORY_CREDENTIALS + *out |= GIT_CREDENTIAL_SSH_MEMORY; +#endif + ptr += strlen(SSH_AUTH_PUBLICKEY); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { + *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + ptr += strlen(SSH_AUTH_PASSWORD); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { + *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; + ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); + continue; + } + + /* Skip it if we don't know it */ + ptr = strchr(ptr, ','); + } + + return 0; +} +#endif + +int git_smart_subtransport_ssh( + git_smart_subtransport **out, git_transport *owner, void *param) +{ +#ifdef GIT_SSH + ssh_subtransport *t; + + GIT_ASSERT_ARG(out); + + GIT_UNUSED(param); + + t = git__calloc(sizeof(ssh_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = (transport_smart *)owner; + t->parent.action = _ssh_action; + t->parent.close = _ssh_close; + t->parent.free = _ssh_free; + + *out = (git_smart_subtransport *) t; + return 0; +#else + GIT_UNUSED(owner); + GIT_UNUSED(param); + + GIT_ASSERT_ARG(out); + *out = NULL; + + git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); + return -1; +#endif +} + +int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) +{ +#ifdef GIT_SSH + git_strarray *paths = (git_strarray *) payload; + git_transport *transport; + transport_smart *smart; + ssh_subtransport *t; + int error; + git_smart_subtransport_definition ssh_definition = { + git_smart_subtransport_ssh, + 0, /* no RPC */ + NULL, + }; + + if (paths->count != 2) { + git_error_set(GIT_ERROR_SSH, "invalid ssh paths, must be two strings"); + return GIT_EINVALIDSPEC; + } + + if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) + return error; + + smart = (transport_smart *) transport; + t = (ssh_subtransport *) smart->wrapped; + + t->cmd_uploadpack = git__strdup(paths->strings[0]); + GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); + t->cmd_receivepack = git__strdup(paths->strings[1]); + GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + + *out = transport; + return 0; +#else + GIT_UNUSED(owner); + GIT_UNUSED(payload); + + GIT_ASSERT_ARG(out); + *out = NULL; + + git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); + return -1; +#endif +} + +#ifdef GIT_SSH +static void shutdown_ssh(void) +{ + libssh2_exit(); +} +#endif + +int git_transport_ssh_global_init(void) +{ +#ifdef GIT_SSH + if (libssh2_init(0) < 0) { + git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); + return -1; + } + + return git_runtime_shutdown_register(shutdown_ssh); + +#else + + /* Nothing to initialize */ + return 0; + +#endif +} diff --git a/src/libgit2/transports/ssh.h b/src/libgit2/transports/ssh.h new file mode 100644 index 000000000..d3e741f1d --- /dev/null +++ b/src/libgit2/transports/ssh.h @@ -0,0 +1,14 @@ +/* + * 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_ssh_h__ +#define INCLUDE_transports_ssh_h__ + +#include "common.h" + +int git_transport_ssh_global_init(void); + +#endif diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c new file mode 100644 index 000000000..8ec5b37c5 --- /dev/null +++ b/src/libgit2/transports/winhttp.c @@ -0,0 +1,1686 @@ +/* + * 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" + +#ifdef GIT_WINHTTP + +#include "git2.h" +#include "git2/transport.h" +#include "posix.h" +#include "str.h" +#include "netops.h" +#include "smart.h" +#include "remote.h" +#include "repository.h" +#include "http.h" +#include "git2/sys/credential.h" + +#include +#include + +/* For IInternetSecurityManager zone check */ +#include +#include + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) + +#define MAX_CONTENT_TYPE_LEN 100 +#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 +#define CACHED_POST_BODY_BUF_SIZE 4096 +#define UUID_LENGTH_CCH 32 +#define TIMEOUT_INFINITE -1 +#define DEFAULT_CONNECT_TIMEOUT 60000 +#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH +#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 0x00002000 +#endif + +#ifndef WINHTTP_NO_CLIENT_CERT_CONTEXT +# define WINHTTP_NO_CLIENT_CERT_CONTEXT NULL +#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"; +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 wchar_t *get_verb = L"GET"; +static const wchar_t *post_verb = L"POST"; +static const wchar_t *pragma_nocache = L"Pragma: no-cache"; +static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked"; +static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | + SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | + SECURITY_FLAG_IGNORE_UNKNOWN_CA; + +#if defined(__MINGW32__) +static const CLSID CLSID_InternetSecurityManager_mingw = + { 0x7B8A2D94, 0x0AC9, 0x11D1, + { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } }; +static const IID IID_IInternetSecurityManager_mingw = + { 0x79EAC9EE, 0xBAF9, 0x11CE, + { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } }; + +# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw +# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw +#endif + +#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) + +typedef enum { + GIT_WINHTTP_AUTH_BASIC = 1, + GIT_WINHTTP_AUTH_NTLM = 2, + GIT_WINHTTP_AUTH_NEGOTIATE = 4, + GIT_WINHTTP_AUTH_DIGEST = 8 +} winhttp_authmechanism_t; + +typedef struct { + git_smart_subtransport_stream parent; + const char *service; + const char *service_url; + const wchar_t *verb; + HINTERNET request; + wchar_t *request_uri; + char *chunk_buffer; + unsigned chunk_buffer_len; + HANDLE post_body; + DWORD post_body_len; + unsigned sent_request : 1, + received_response : 1, + chunked : 1, + status_sending_request_reached: 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; + + winhttp_server server; + winhttp_server proxy; + + HINTERNET session; + HINTERNET connection; +} winhttp_subtransport; + +static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_credential *cred) +{ + 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 = GIT_EAUTH; + goto done; + } + + if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0) + goto done; + + if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0) + goto done; + + if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) { + git_error_set(GIT_ERROR_OS, "failed to set credentials"); + error = -1; + } + +done: + if (user_len > 0) + git__memzero(user, user_len * sizeof(wchar_t)); + + if (pass_len > 0) + git__memzero(pass, pass_len * sizeof(wchar_t)); + + git__free(user); + git__free(pass); + + return error; +} + +static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms) +{ + DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; + DWORD native_scheme = 0; + + if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) { + native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; + } 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 GIT_EAUTH; + } + + /* + * 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, target, native_scheme, NULL, NULL, NULL)) { + git_error_set(GIT_ERROR_OS, "could not configure credentials"); + return -1; + } + + return 0; +} + +static int acquire_url_cred( + git_credential **cred, + unsigned int allowed_types, + 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; + + /* If the target URI supports integrated Windows authentication + * as an authentication mechanism */ + if (GIT_CREDENTIAL_DEFAULT & allowed_types) { + wchar_t *wide_url; + HRESULT hCoInitResult; + + /* Convert URL to wide characters */ + if (git__utf8_to_16_alloc(&wide_url, url) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); + return -1; + } + + hCoInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); + + if (SUCCEEDED(hCoInitResult) || hCoInitResult == RPC_E_CHANGED_MODE) { + IInternetSecurityManager *pISM; + + /* And if the target URI is in the My Computer, Intranet, or Trusted zones */ + if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL, + CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) { + DWORD dwZone; + + if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) && + (URLZONE_LOCAL_MACHINE == dwZone || + URLZONE_INTRANET == dwZone || + URLZONE_TRUSTED == dwZone)) { + git_credential *existing = *cred; + + if (existing) + existing->free(existing); + + /* Then use default Windows credentials to authenticate this request */ + error = git_credential_default_new(cred); + } + + pISM->lpVtbl->Release(pISM); + } + + /* Only uninitialize if the call to CoInitializeEx was successful. */ + if (SUCCEEDED(hCoInitResult)) + CoUninitialize(); + } + + git__free(wide_url); + } + + return error; +} + +static int certificate_check(winhttp_stream *s, int valid) +{ + int error; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + PCERT_CONTEXT cert_ctx; + DWORD cert_ctx_size = sizeof(cert_ctx); + git_cert_x509 cert; + + /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ + if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { + if (!git_error_last()) + git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); + + return GIT_ECERTIFICATE; + } + + if (t->owner->connect_opts.callbacks.certificate_check == 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)) { + git_error_set(GIT_ERROR_OS, "failed to get server certificate"); + return -1; + } + + git_error_clear(); + cert.parent.cert_type = GIT_CERT_X509; + cert.data = cert_ctx->pbCertEncoded; + cert.len = cert_ctx->cbCertEncoded; + error = t->owner->connect_opts.callbacks.certificate_check((git_cert *) &cert, valid, t->server.url.host, t->owner->connect_opts.callbacks.payload); + CertFreeCertificateContext(cert_ctx); + + if (error == GIT_PASSTHROUGH) + error = valid ? 0 : GIT_ECERTIFICATE; + + if (error < 0 && !git_error_last()) + git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); + + return error; +} + +static void winhttp_stream_close(winhttp_stream *s) +{ + if (s->chunk_buffer) { + git__free(s->chunk_buffer); + s->chunk_buffer = NULL; + } + + if (s->post_body) { + CloseHandle(s->post_body); + s->post_body = NULL; + } + + if (s->request_uri) { + git__free(s->request_uri); + s->request_uri = NULL; + } + + if (s->request) { + WinHttpCloseHandle(s->request); + s->request = NULL; + } + + s->sent_request = 0; +} + +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) +{ + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + git_str buf = GIT_STR_INIT; + char *proxy_url = NULL; + wchar_t ct[MAX_CONTENT_TYPE_LEN]; + LPCWSTR types[] = { L"*/*", NULL }; + BOOL peerdist = FALSE; + int error = -1; + 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_str_printf(&buf, "%s%s", t->server.url.path, service_url); + + if (git_str_oom(&buf)) + return -1; + + /* Convert URL to wide characters */ + if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); + goto on_error; + } + + /* Establish request */ + s->request = WinHttpOpenRequest( + t->connection, + s->verb, + s->request_uri, + NULL, + WINHTTP_NO_REFERER, + types, + 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; + } + + proxy_opts = &t->owner->connect_opts.proxy_opts; + if (proxy_opts->type == GIT_PROXY_AUTO) { + /* Set proxy if necessary */ + if (git_remote__http_proxy(&proxy_url, t->owner->owner, &t->server.url) < 0) + goto on_error; + } + else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { + proxy_url = git__strdup(proxy_opts->url); + GIT_ERROR_CHECK_ALLOC(proxy_url); + } + + if (proxy_url) { + git_str processed_url = GIT_STR_INIT; + WINHTTP_PROXY_INFO proxy_info; + wchar_t *proxy_wide; + + git_net_url_dispose(&t->proxy.url); + + if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 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; + } + + git_str_puts(&processed_url, t->proxy.url.scheme); + git_str_PUTS(&processed_url, "://"); + + if (git_net_url_is_ipv6(&t->proxy.url)) + git_str_putc(&processed_url, '['); + + git_str_puts(&processed_url, t->proxy.url.host); + + if (git_net_url_is_ipv6(&t->proxy.url)) + git_str_putc(&processed_url, ']'); + + if (!git_net_url_is_default_port(&t->proxy.url)) + git_str_printf(&processed_url, ":%s", t->proxy.url.port); + + if (git_str_oom(&processed_url)) { + error = -1; + goto on_error; + } + + /* Convert URL to wide characters */ + error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr); + git_str_dispose(&processed_url); + if (error < 0) + goto on_error; + + proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy_info.lpszProxy = proxy_wide; + proxy_info.lpszProxyBypass = NULL; + + if (!WinHttpSetOption(s->request, + WINHTTP_OPTION_PROXY, + &proxy_info, + sizeof(WINHTTP_PROXY_INFO))) { + git_error_set(GIT_ERROR_OS, "failed to set proxy"); + git__free(proxy_wide); + goto on_error; + } + + git__free(proxy_wide); + + 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? + * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae + */ + if (!WinHttpSetOption(s->request, + WINHTTP_OPTION_DISABLE_FEATURE, + &disable_redirects, + sizeof(disable_redirects))) { + git_error_set(GIT_ERROR_OS, "failed to disable redirects"); + error = -1; + goto on_error; + } + + /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP + * adds itself. This option may not be supported by the underlying + * platform, so we do not error-check it */ + WinHttpSetOption(s->request, + WINHTTP_OPTION_PEERDIST_EXTENSION_STATE, + &peerdist, + sizeof(peerdist)); + + /* Send Pragma: no-cache header */ + if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + + if (post_verb == s->verb) { + /* Send Content-Type and Accept headers -- only necessary on a POST */ + git_str_clear(&buf); + if (git_str_printf(&buf, + "Content-Type: application/x-git-%s-request", + s->service) < 0) + goto on_error; + + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + + git_str_clear(&buf); + if (git_str_printf(&buf, + "Accept: application/x-git-%s-result", + s->service) < 0) + goto on_error; + + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + } + + for (i = 0; i < t->owner->connect_opts.custom_headers.count; i++) { + if (t->owner->connect_opts.custom_headers.strings[i]) { + git_str_clear(&buf); + git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + } + } + + if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0) + goto on_error; + + /* We've done everything up to calling WinHttpSendRequest. */ + + error = 0; + +on_error: + if (error < 0) + winhttp_stream_close(s); + + git__free(proxy_url); + git_str_dispose(&buf); + return error; +} + +static int parse_unauthorized_response( + int *allowed_types, + int *allowed_mechanisms, + HINTERNET request) +{ + DWORD supported, first, target; + + *allowed_types = 0; + *allowed_mechanisms = 0; + + /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes(). + * We can assume this was already done, since we know we are unauthorized. + */ + if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) { + git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes"); + return GIT_EAUTH; + } + + if (WINHTTP_AUTH_SCHEME_NTLM & supported) { + *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_CREDENTIAL_DEFAULT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE; + } + + if (WINHTTP_AUTH_SCHEME_BASIC & supported) { + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_BASIC; + } + + if (WINHTTP_AUTH_SCHEME_DIGEST & supported) { + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST; + } + + return 0; +} + +static int write_chunk(HINTERNET request, const char *buffer, size_t len) +{ + DWORD bytes_written; + git_str buf = GIT_STR_INIT; + + /* Chunk header */ + git_str_printf(&buf, "%"PRIXZ"\r\n", len); + + if (git_str_oom(&buf)) + return -1; + + if (!WinHttpWriteData(request, + git_str_cstr(&buf), (DWORD)git_str_len(&buf), + &bytes_written)) { + git_str_dispose(&buf); + git_error_set(GIT_ERROR_OS, "failed to write chunk header"); + return -1; + } + + git_str_dispose(&buf); + + /* Chunk body */ + if (!WinHttpWriteData(request, + buffer, (DWORD)len, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write chunk"); + return -1; + } + + /* Chunk footer */ + if (!WinHttpWriteData(request, + "\r\n", 2, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write chunk footer"); + return -1; + } + + return 0; +} + +static int winhttp_close_connection(winhttp_subtransport *t) +{ + int ret = 0; + + if (t->connection) { + if (!WinHttpCloseHandle(t->connection)) { + git_error_set(GIT_ERROR_OS, "unable to close connection"); + ret = -1; + } + + t->connection = NULL; + } + + if (t->session) { + if (!WinHttpCloseHandle(t->session)) { + git_error_set(GIT_ERROR_OS, "unable to close session"); + ret = -1; + } + + t->session = NULL; + } + + return ret; +} + +static void CALLBACK winhttp_status( + HINTERNET connection, + DWORD_PTR ctx, + DWORD code, + LPVOID info, + DWORD info_len) +{ + DWORD status; + + GIT_UNUSED(connection); + GIT_UNUSED(info_len); + + switch (code) { + case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: + status = *((DWORD *)info); + + if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)) + 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_HTTP, "SSL certificate has expired"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_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_HTTP, "SSL certificate is invalid"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_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_HTTP, "SSL certificate was revoked"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)) + git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded"); + else + git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status); + + break; + + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + ((winhttp_stream *) ctx)->status_sending_request_reached = 1; + + break; + } +} + +static int winhttp_connect( + winhttp_subtransport *t) +{ + wchar_t *wide_host = NULL; + int32_t port; + wchar_t *wide_ua = NULL; + git_str ipv6 = GIT_STR_INIT, ua = GIT_STR_INIT; + const char *host; + int error = -1; + int default_timeout = TIMEOUT_INFINITE; + int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + DWORD protocols = + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; + + t->session = NULL; + t->connection = NULL; + + /* Prepare port */ + if (git__strntol32(&port, t->server.url.port, + strlen(t->server.url.port), NULL, 10) < 0) + goto on_error; + + /* IPv6? Add braces around the host. */ + if (git_net_url_is_ipv6(&t->server.url)) { + if (git_str_printf(&ipv6, "[%s]", t->server.url.host) < 0) + goto on_error; + + host = ipv6.ptr; + } else { + host = t->server.url.host; + } + + /* Prepare host */ + if (git__utf8_to_16_alloc(&wide_host, host) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); + goto on_error; + } + + + if (git_http__user_agent(&ua) < 0) + goto on_error; + + if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); + goto on_error; + } + + /* Establish session */ + t->session = WinHttpOpen( + wide_ua, + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (!t->session) { + git_error_set(GIT_ERROR_OS, "failed to init WinHTTP"); + goto on_error; + } + + /* + * Do a best-effort attempt to enable TLS 1.3 and 1.2 but allow this to + * fail; if TLS 1.2 or 1.3 support is not available for some reason, + * ignore the failure (it will keep the default protocols). + */ + if (WinHttpSetOption(t->session, + WINHTTP_OPTION_SECURE_PROTOCOLS, + &protocols, + sizeof(protocols)) == FALSE) { + protocols &= ~WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; + WinHttpSetOption(t->session, + WINHTTP_OPTION_SECURE_PROTOCOLS, + &protocols, + sizeof(protocols)); + } + + if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { + git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); + goto on_error; + } + + + /* Establish connection */ + t->connection = WinHttpConnect( + t->session, + wide_host, + (INTERNET_PORT) port, + 0); + + if (!t->connection) { + git_error_set(GIT_ERROR_OS, "failed to connect to host"); + goto on_error; + } + + if (WinHttpSetStatusCallback( + t->connection, + winhttp_status, + WINHTTP_CALLBACK_FLAG_SECURE_FAILURE | WINHTTP_CALLBACK_FLAG_SEND_REQUEST, + 0 + ) == WINHTTP_INVALID_STATUS_CALLBACK) { + git_error_set(GIT_ERROR_OS, "failed to set status callback"); + goto on_error; + } + + error = 0; + +on_error: + if (error < 0) + winhttp_close_connection(t); + + git_str_dispose(&ua); + git_str_dispose(&ipv6); + git__free(wide_host); + git__free(wide_ua); + + return error; +} + +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 (chunked) { + success = WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, (DWORD_PTR)s); + } else { + success = WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + (DWORD)len, (DWORD_PTR)s); + } + + if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL) + break; + } + + return success ? 0 : -1; +} + +static int send_request(winhttp_stream *s, size_t len, bool chunked) +{ + int request_failed = 1, error, attempts = 0; + DWORD ignore_flags, send_request_error; + + git_error_clear(); + + while (request_failed && attempts++ < 3) { + int cert_valid = 1; + int client_cert_requested = 0; + request_failed = 0; + if ((error = do_send_request(s, len, chunked)) < 0) { + send_request_error = GetLastError(); + request_failed = 1; + switch (send_request_error) { + case ERROR_WINHTTP_SECURE_FAILURE: + cert_valid = 0; + break; + case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED: + client_cert_requested = 1; + break; + default: + git_error_set(GIT_ERROR_OS, "failed to send request"); + return -1; + } + } + + /* + * Only check the certificate if we were able to reach the sending request phase, or + * received a secure failure error. Otherwise, the server certificate won't be available + * since the request wasn't able to complete (e.g. proxy auth required) + */ + if (!cert_valid || + (!request_failed && s->status_sending_request_reached)) { + git_error_clear(); + if ((error = certificate_check(s, cert_valid)) < 0) { + if (!git_error_last()) + git_error_set(GIT_ERROR_OS, "user cancelled certificate check"); + + return error; + } + } + + /* if neither the request nor the certificate check returned errors, we're done */ + if (!request_failed) + return 0; + + if (!cert_valid) { + ignore_flags = no_check_cert_flags; + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) { + git_error_set(GIT_ERROR_OS, "failed to set security options"); + return -1; + } + } + + if (client_cert_requested) { + /* + * Client certificates are not supported, explicitly tell the server that + * (it's possible a client certificate was requested but is not required) + */ + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) { + git_error_set(GIT_ERROR_OS, "failed to set client cert context"); + return -1; + } + } + } + + 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, + size_t buf_size, + size_t *bytes_read) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + DWORD dw_bytes_read; + char replay_count = 0; + int error; + +replay: + /* Enforce a reasonable cap on the number of replays */ + if (replay_count++ >= GIT_HTTP_REPLAY_MAX) { + git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); + return GIT_ERROR; /* not GIT_EAUTH because the exact cause is not clear */ + } + + /* Connect if necessary */ + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->received_response) { + DWORD status_code, status_code_length, content_type_length, bytes_written; + char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; + wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; + + if (!s->sent_request) { + + if ((error = send_request(s, s->post_body_len, false)) < 0) + return error; + + s->sent_request = 1; + } + + if (s->chunked) { + GIT_ASSERT(s->verb == post_verb); + + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0 && + write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + /* Write the final chunk. */ + if (!WinHttpWriteData(s->request, + "0\r\n\r\n", 5, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write final chunk"); + return -1; + } + } + else if (s->post_body) { + char *buffer; + DWORD len = s->post_body_len, bytes_read; + + if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body, + 0, 0, FILE_BEGIN) && + NO_ERROR != GetLastError()) { + git_error_set(GIT_ERROR_OS, "failed to reset file pointer"); + return -1; + } + + buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + GIT_ERROR_CHECK_ALLOC(buffer); + + while (len > 0) { + DWORD bytes_written; + + if (!ReadFile(s->post_body, buffer, + min(CACHED_POST_BODY_BUF_SIZE, len), + &bytes_read, NULL) || + !bytes_read) { + git__free(buffer); + git_error_set(GIT_ERROR_OS, "failed to read from temp file"); + return -1; + } + + if (!WinHttpWriteData(s->request, buffer, + bytes_read, &bytes_written)) { + git__free(buffer); + git_error_set(GIT_ERROR_OS, "failed to write data"); + return -1; + } + + len -= bytes_read; + GIT_ASSERT(bytes_read == bytes_written); + } + + git__free(buffer); + + /* Eagerly close the temp file */ + CloseHandle(s->post_body); + s->post_body = NULL; + } + + if (!WinHttpReceiveResponse(s->request, 0)) { + git_error_set(GIT_ERROR_OS, "failed to receive response"); + return -1; + } + + /* Verify that we got a 200 back */ + status_code_length = sizeof(status_code); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status_code, &status_code_length, + WINHTTP_NO_HEADER_INDEX)) { + git_error_set(GIT_ERROR_OS, "failed to retrieve status code"); + return -1; + } + + /* The implementation of WinHTTP prior to Windows 7 will not + * redirect to an identical URI. Some Git hosters use self-redirects + * as part of their DoS mitigation strategy. Check first to see if we + * have a redirect status code, and that we haven't already streamed + * a post body. (We can't replay a streamed POST.) */ + if (!s->chunked && + (HTTP_STATUS_MOVED == status_code || + HTTP_STATUS_REDIRECT == status_code || + (HTTP_STATUS_REDIRECT_METHOD == status_code && + get_verb == s->verb) || + 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. */ + wchar_t *location; + DWORD location_length; + char *location8; + + /* OK, fetch the Location header from the redirect. */ + if (WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + WINHTTP_NO_OUTPUT_BUFFER, + &location_length, + WINHTTP_NO_HEADER_INDEX) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + git_error_set(GIT_ERROR_OS, "failed to read Location header"); + return -1; + } + + location = git__malloc(location_length); + GIT_ERROR_CHECK_ALLOC(location); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + location, + &location_length, + WINHTTP_NO_HEADER_INDEX)) { + git_error_set(GIT_ERROR_OS, "failed to read Location header"); + git__free(location); + return -1; + } + + /* Convert the Location header to UTF-8 */ + if (git__utf16_to_8_alloc(&location8, location) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8"); + git__free(location); + return -1; + } + + git__free(location); + + /* Replay the request */ + winhttp_stream_close(s); + + if (!git__prefixcmp_icase(location8, prefix_https)) { + bool follow = (t->owner->connect_opts.follow_redirects != GIT_REMOTE_REDIRECT_NONE); + + /* Upgrade to secure connection; disconnect and start over */ + if (git_net_url_apply_redirect(&t->server.url, location8, follow, s->service_url) < 0) { + git__free(location8); + return -1; + } + + winhttp_close_connection(t); + + if (winhttp_connect(t) < 0) + return -1; + } + + git__free(location8); + goto replay; + } + + /* Handle authentication failures */ + if (status_code == HTTP_STATUS_DENIED) { + int error = acquire_credentials(s->request, + &t->server, + t->owner->url, + t->owner->connect_opts.callbacks.credentials, + t->owner->connect_opts.callbacks.payload); + + if (error < 0) { + return error; + } else if (!error) { + GIT_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->connect_opts.proxy_opts.url, + t->owner->connect_opts.proxy_opts.credentials, + t->owner->connect_opts.proxy_opts.payload); + + if (error < 0) { + return error; + } else if (!error) { + GIT_ASSERT(t->proxy.cred); + winhttp_stream_close(s); + goto replay; + } + } + + if (HTTP_STATUS_OK != status_code) { + git_error_set(GIT_ERROR_HTTP, "request failed with status code: %lu", status_code); + return -1; + } + + /* Verify that we got the correct content-type back */ + if (post_verb == s->verb) + p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); + else + p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); + + if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters"); + return -1; + } + + content_type_length = sizeof(content_type); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_CONTENT_TYPE, + WINHTTP_HEADER_NAME_BY_INDEX, + &content_type, &content_type_length, + WINHTTP_NO_HEADER_INDEX)) { + git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type"); + return -1; + } + + if (wcscmp(expected_content_type, content_type)) { + git_error_set(GIT_ERROR_HTTP, "received unexpected content-type"); + return -1; + } + + s->received_response = 1; + } + + if (!WinHttpReadData(s->request, + (LPVOID)buffer, + (DWORD)buf_size, + &dw_bytes_read)) + { + git_error_set(GIT_ERROR_OS, "failed to read data"); + return -1; + } + + *bytes_read = dw_bytes_read; + + return 0; +} + +static int winhttp_stream_write_single( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + DWORD bytes_written; + int error; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* This implementation of write permits only a single call. */ + if (s->sent_request) { + git_error_set(GIT_ERROR_HTTP, "subtransport configured for only one write"); + return -1; + } + + if ((error = send_request(s, len, false)) < 0) + return error; + + s->sent_request = 1; + + if (!WinHttpWriteData(s->request, + (LPCVOID)buffer, + (DWORD)len, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write data"); + return -1; + } + + GIT_ASSERT((DWORD)len == bytes_written); + + return 0; +} + +static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch) +{ + UUID uuid; + RPC_STATUS status = UuidCreate(&uuid); + int result; + + if (RPC_S_OK != status && + RPC_S_UUID_LOCAL_ONLY != status && + RPC_S_UUID_NO_ADDRESS != status) { + 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_HTTP, "buffer too small for name of temp file"); + return -1; + } + +#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) + result = swprintf_s(buffer, buffer_len_cch, +#else + result = wsprintfW(buffer, +#endif + L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", + uuid.Data1, uuid.Data2, uuid.Data3, + uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], + uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); + + if (result < UUID_LENGTH_CCH) { + git_error_set(GIT_ERROR_OS, "unable to generate name for temp file"); + return -1; + } + + return 0; +} + +static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch) +{ + size_t len; + + if (!GetTempPathW(buffer_len_cch, buffer)) { + git_error_set(GIT_ERROR_OS, "failed to get temp path"); + return -1; + } + + len = wcslen(buffer); + + if (buffer[len - 1] != '\\' && len < buffer_len_cch) + buffer[len++] = '\\'; + + if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0) + return -1; + + return 0; +} + +static int winhttp_stream_write_buffered( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + DWORD bytes_written; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* Buffer the payload, using a temporary file so we delegate + * memory management of the data to the operating system. */ + if (!s->post_body) { + wchar_t temp_path[MAX_PATH + 1]; + + if (get_temp_file(temp_path, MAX_PATH + 1) < 0) + return -1; + + s->post_body = CreateFileW(temp_path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE, NULL, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + + if (INVALID_HANDLE_VALUE == s->post_body) { + s->post_body = NULL; + git_error_set(GIT_ERROR_OS, "failed to create temporary file"); + return -1; + } + } + + if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) { + git_error_set(GIT_ERROR_OS, "failed to write to temporary file"); + return -1; + } + + GIT_ASSERT((DWORD)len == bytes_written); + + s->post_body_len += bytes_written; + + return 0; +} + +static int winhttp_stream_write_chunked( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + int error; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->sent_request) { + /* Send Transfer-Encoding: chunked header */ + if (!WinHttpAddRequestHeaders(s->request, + transfer_encoding, (ULONG) -1L, + WINHTTP_ADDREQ_FLAG_ADD)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + return -1; + } + + if ((error = send_request(s, 0, true)) < 0) + return error; + + s->sent_request = 1; + } + + if (len > CACHED_POST_BODY_BUF_SIZE) { + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0) { + if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + } + + /* Write chunk directly */ + if (write_chunk(s->request, buffer, len) < 0) + return -1; + } + else { + /* 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) { + 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; + buffer += count; + len -= count; + + /* Is the buffer full? If so, then flush */ + if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) { + if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + /* Is there any remaining data from the source? */ + if (len > 0) { + memcpy(s->chunk_buffer, buffer, len); + s->chunk_buffer_len = (unsigned int)len; + } + } + } + + return 0; +} + +static void winhttp_stream_free(git_smart_subtransport_stream *stream) +{ + winhttp_stream *s = (winhttp_stream *)stream; + + winhttp_stream_close(s); + git__free(s); +} + +static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream) +{ + winhttp_stream *s; + + if (!stream) + return -1; + + s = git__calloc(1, sizeof(winhttp_stream)); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = winhttp_stream_read; + s->parent.write = winhttp_stream_write_single; + s->parent.free = winhttp_stream_free; + + *stream = s; + + return 0; +} + +static int winhttp_uploadpack_ls( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + s->service = upload_pack_service; + s->service_url = upload_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_uploadpack( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + s->service = upload_pack_service; + s->service_url = upload_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int winhttp_receivepack_ls( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + s->service = receive_pack_service; + s->service_url = receive_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_receivepack( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + /* WinHTTP only supports Transfer-Encoding: chunked + * on Windows Vista (NT 6.0) and higher. */ + s->chunked = git_has_win32_version(6, 0, 0); + + if (s->chunked) + s->parent.write = winhttp_stream_write_chunked; + else + s->parent.write = winhttp_stream_write_buffered; + + s->service = receive_pack_service; + s->service_url = receive_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int winhttp_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + winhttp_stream *s; + int ret = -1; + + if (!t->connection) + if ((ret = git_net_url_parse(&t->server.url, url)) < 0 || + (ret = winhttp_connect(t)) < 0) + return ret; + + if (winhttp_stream_alloc(t, &s) < 0) + return -1; + + if (!stream) + return -1; + + switch (action) + { + case GIT_SERVICE_UPLOADPACK_LS: + ret = winhttp_uploadpack_ls(t, s); + break; + + case GIT_SERVICE_UPLOADPACK: + ret = winhttp_uploadpack(t, s); + break; + + case GIT_SERVICE_RECEIVEPACK_LS: + ret = winhttp_receivepack_ls(t, s); + break; + + case GIT_SERVICE_RECEIVEPACK: + ret = winhttp_receivepack(t, s); + break; + + default: + GIT_ASSERT(0); + } + + if (!ret) + *stream = &s->parent; + + return ret; +} + +static int winhttp_close(git_smart_subtransport *subtransport) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + + git_net_url_dispose(&t->server.url); + git_net_url_dispose(&t->proxy.url); + + 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; + } + + return winhttp_close_connection(t); +} + +static void winhttp_free(git_smart_subtransport *subtransport) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + + winhttp_close(subtransport); + + git__free(t); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) +{ + winhttp_subtransport *t; + + GIT_UNUSED(param); + + if (!out) + return -1; + + t = git__calloc(1, sizeof(winhttp_subtransport)); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = (transport_smart *)owner; + t->parent.action = winhttp_action; + t->parent.close = winhttp_close; + t->parent.free = winhttp_free; + + *out = (git_smart_subtransport *) t; + return 0; +} + +#endif /* GIT_WINHTTP */ diff --git a/src/libgit2/tree-cache.c b/src/libgit2/tree-cache.c new file mode 100644 index 000000000..cd69e7bf8 --- /dev/null +++ b/src/libgit2/tree-cache.c @@ -0,0 +1,277 @@ +/* + * 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 "tree-cache.h" + +#include "pool.h" +#include "tree.h" + +static git_tree_cache *find_child( + const git_tree_cache *tree, const char *path, const char *end) +{ + size_t i, dirlen = end ? (size_t)(end - path) : strlen(path); + + for (i = 0; i < tree->children_count; ++i) { + git_tree_cache *child = tree->children[i]; + + if (child->namelen == dirlen && !memcmp(path, child->name, dirlen)) + return child; + } + + return NULL; +} + +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) + return; + + tree->entry_count = -1; + + while (ptr != NULL) { + end = strchr(ptr, '/'); + + if (end == NULL) /* End of path */ + break; + + tree = find_child(tree, ptr, end); + if (tree == NULL) /* We don't have that tree */ + return; + + tree->entry_count = -1; + ptr = end + 1; + } +} + +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) { + return NULL; + } + + while (1) { + end = strchr(ptr, '/'); + + tree = find_child(tree, ptr, end); + if (tree == NULL) /* Can't find it */ + return NULL; + + if (end == NULL || *end + 1 == '\0') + return tree; + + ptr = end + 1; + } +} + +static int read_tree_internal(git_tree_cache **out, + const char **buffer_in, const char *buffer_end, + git_pool *pool) +{ + git_tree_cache *tree = NULL; + const char *name_start, *buffer; + int count; + + buffer = name_start = *buffer_in; + + if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) + goto corrupted; + + if (++buffer >= buffer_end) + goto corrupted; + + if (git_tree_cache_new(&tree, name_start, pool) < 0) + return -1; + + /* Blank-terminated ASCII decimal number of entries in this tree */ + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0) + goto corrupted; + + tree->entry_count = count; + + if (*buffer != ' ' || ++buffer >= buffer_end) + goto corrupted; + + /* Number of children of the tree, newline-terminated */ + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0 || count < 0) + goto corrupted; + + tree->children_count = count; + + if (*buffer != '\n' || ++buffer > buffer_end) + goto corrupted; + + /* The SHA1 is only there if it's not invalidated */ + if (tree->entry_count >= 0) { + /* 160-bit SHA-1 for this tree and it's children */ + if (buffer + GIT_OID_RAWSZ > buffer_end) + goto corrupted; + + git_oid_fromraw(&tree->oid, (const unsigned char *)buffer); + buffer += GIT_OID_RAWSZ; + } + + /* Parse children: */ + if (tree->children_count > 0) { + size_t i, bufsize; + + 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, bufsize); + + for (i = 0; i < tree->children_count; ++i) { + if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0) + goto corrupted; + } + } + + *buffer_in = buffer; + *out = tree; + return 0; + + corrupted: + git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index"); + return -1; +} + +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool) +{ + const char *buffer_end = buffer + buffer_size; + + if (read_tree_internal(tree, &buffer, buffer_end, pool) < 0) + return -1; + + if (buffer < buffer_end) { + git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index (unexpected trailing data)"); + return -1; + } + + return 0; +} + +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, alloc_size; + int error; + + repo = git_tree_owner(tree); + + git_oid_cpy(&cache->oid, git_tree_id(tree)); + nentries = git_tree_entrycount(tree); + + /* + * We make sure we know how many trees we need to allocate for + * so we don't have to realloc and change the pointers for the + * parents. + */ + ntrees = 0; + for (i = 0; i < nentries; i++) { + const git_tree_entry *entry; + + entry = git_tree_entry_byindex(tree, i); + if (git_tree_entry_filemode(entry) == GIT_FILEMODE_TREE) + ntrees++; + } + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_size, ntrees, sizeof(git_tree_cache *)); + + cache->children_count = ntrees; + cache->children = git_pool_mallocz(pool, alloc_size); + GIT_ERROR_CHECK_ALLOC(cache->children); + + j = 0; + for (i = 0; i < nentries; i++) { + const git_tree_entry *entry; + git_tree *subtree; + + entry = git_tree_entry_byindex(tree, i); + if (git_tree_entry_filemode(entry) != GIT_FILEMODE_TREE) { + cache->entry_count++; + continue; + } + + if ((error = git_tree_cache_new(&cache->children[j], git_tree_entry_name(entry), pool)) < 0) + return error; + + if ((error = git_tree_lookup(&subtree, repo, git_tree_entry_id(entry))) < 0) + return error; + + error = read_tree_recursive(cache->children[j], subtree, pool); + git_tree_free(subtree); + cache->entry_count += cache->children[j]->entry_count; + j++; + + if (error < 0) + return error; + } + + return 0; +} + +int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool) +{ + int error; + git_tree_cache *cache; + + if ((error = git_tree_cache_new(&cache, "", pool)) < 0) + return error; + + if ((error = read_tree_recursive(cache, tree, pool)) < 0) + return error; + + *out = cache; + return 0; +} + +int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool) +{ + size_t name_len, alloc_size; + git_tree_cache *tree; + + name_len = strlen(name); + + 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)); + /* NUL-terminated tree name */ + tree->namelen = name_len; + memcpy(tree->name, name, name_len); + tree->name[name_len] = '\0'; + + *out = tree; + return 0; +} + +static void write_tree(git_str *out, git_tree_cache *tree) +{ + size_t i; + + git_str_printf(out, "%s%c%"PRIdZ" %"PRIuZ"\n", tree->name, 0, tree->entry_count, tree->children_count); + + if (tree->entry_count != -1) + git_str_put(out, (char *)&tree->oid.id, GIT_OID_RAWSZ); + + for (i = 0; i < tree->children_count; i++) + write_tree(out, tree->children[i]); +} + +int git_tree_cache_write(git_str *out, git_tree_cache *tree) +{ + write_tree(out, tree); + + return git_str_oom(out) ? -1 : 0; +} diff --git a/src/libgit2/tree-cache.h b/src/libgit2/tree-cache.h new file mode 100644 index 000000000..a27e30466 --- /dev/null +++ b/src/libgit2/tree-cache.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_tree_cache_h__ +#define INCLUDE_tree_cache_h__ + +#include "common.h" + +#include "pool.h" +#include "str.h" +#include "git2/oid.h" + +typedef struct git_tree_cache { + struct git_tree_cache **children; + size_t children_count; + + ssize_t entry_count; + git_oid oid; + size_t namelen; + char name[GIT_FLEX_ARRAY]; +} git_tree_cache; + +int git_tree_cache_write(git_str *out, git_tree_cache *tree); +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool); +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path); +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path); +int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool); +/** + * Read a tree as the root of the tree cache (like for `git read-tree`) + */ +int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool); +void git_tree_cache_free(git_tree_cache *tree); + +#endif diff --git a/src/libgit2/tree.c b/src/libgit2/tree.c new file mode 100644 index 000000000..a5371fd87 --- /dev/null +++ b/src/libgit2/tree.c @@ -0,0 +1,1324 @@ +/* + * 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 "tree.h" + +#include "commit.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "futils.h" +#include "tree-cache.h" +#include "index.h" +#include "path.h" + +#define DEFAULT_TREE_SIZE 16 +#define MAX_FILEMODE_BYTES 6 + +#define TREE_ENTRY_CHECK_NAMELEN(n) \ + if (n > UINT16_MAX) { git_error_set(GIT_ERROR_INVALID, "tree entry path too long"); } + +static bool valid_filemode(const int filemode) +{ + return (filemode == GIT_FILEMODE_TREE + || filemode == GIT_FILEMODE_BLOB + || filemode == GIT_FILEMODE_BLOB_EXECUTABLE + || filemode == GIT_FILEMODE_LINK + || filemode == GIT_FILEMODE_COMMIT); +} + +GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) +{ + /* Tree bits set, but it's not a commit */ + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE) + return GIT_FILEMODE_TREE; + + /* If any of the x bits are set */ + if (GIT_PERMS_IS_EXEC(filemode)) + return GIT_FILEMODE_BLOB_EXECUTABLE; + + /* 16XXXX means commit */ + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT) + return GIT_FILEMODE_COMMIT; + + /* 12XXXX means symlink */ + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK) + return GIT_FILEMODE_LINK; + + /* Otherwise, return a blob */ + return GIT_FILEMODE_BLOB; +} + +static int valid_entry_name(git_repository *repo, const char *filename) +{ + return *filename != '\0' && + git_path_is_valid(repo, filename, 0, + GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_FS_PATH_REJECT_SLASH); +} + +static int entry_sort_cmp(const void *a, const void *b) +{ + const git_tree_entry *e1 = (const git_tree_entry *)a; + const git_tree_entry *e2 = (const git_tree_entry *)b; + + return git_fs_path_cmp( + e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), + e2->filename, e2->filename_len, git_tree_entry__is_tree(e2), + git__strncmp); +} + +int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) +{ + return entry_sort_cmp(e1, e2); +} + +/** + * Allocate a new self-contained entry, with enough space after it to + * store the filename and the id. + */ +static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id) +{ + git_tree_entry *entry = NULL; + char *filename_ptr; + size_t tree_len; + + TREE_ENTRY_CHECK_NAMELEN(filename_len); + + if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) || + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) || + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ)) + return NULL; + + entry = git__calloc(1, tree_len); + if (!entry) + return NULL; + + filename_ptr = ((char *) entry) + sizeof(git_tree_entry); + memcpy(filename_ptr, filename, filename_len); + entry->filename = filename_ptr; + entry->filename_len = (uint16_t)filename_len; + + git_oid_cpy(&entry->oid, id); + + return entry; +} + +struct tree_key_search { + const char *filename; + uint16_t filename_len; +}; + +static int homing_search_cmp(const void *key, const void *array_member) +{ + const struct tree_key_search *ksearch = key; + const git_tree_entry *entry = array_member; + + const uint16_t len1 = ksearch->filename_len; + const uint16_t len2 = entry->filename_len; + + return memcmp( + ksearch->filename, + entry->filename, + len1 < len2 ? len1 : len2 + ); +} + +/* + * Search for an entry in a given tree. + * + * Note that this search is performed in two steps because + * of the way tree entries are sorted internally in git: + * + * Entries in a tree are not sorted alphabetically; two entries + * with the same root prefix will have different positions + * depending on whether they are folders (subtrees) or normal files. + * + * Consequently, it is not possible to find an entry on the tree + * with a binary search if you don't know whether the filename + * you're looking for is a folder or a normal file. + * + * To work around this, we first perform a homing binary search + * on the tree, using the minimal length root prefix of our filename. + * Once the comparisons for this homing search start becoming + * ambiguous because of folder vs file sorting, we look linearly + * around the area for our target file. + */ +static int tree_key_search( + size_t *at_pos, + const git_tree *tree, + const char *filename, + size_t filename_len) +{ + struct tree_key_search ksearch; + const git_tree_entry *entry; + size_t homing, i; + + TREE_ENTRY_CHECK_NAMELEN(filename_len); + + ksearch.filename = filename; + ksearch.filename_len = (uint16_t)filename_len; + + /* Initial homing search; find an entry on the tree with + * the same prefix as the filename we're looking for */ + + if (git_array_search(&homing, + tree->entries, &homing_search_cmp, &ksearch) < 0) + return GIT_ENOTFOUND; /* just a signal error; not passed back to user */ + + /* We found a common prefix. Look forward as long as + * there are entries that share the common prefix */ + for (i = homing; i < tree->entries.size; ++i) { + entry = git_array_get(tree->entries, i); + + if (homing_search_cmp(&ksearch, entry) < 0) + break; + + if (entry->filename_len == filename_len && + memcmp(filename, entry->filename, filename_len) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } + + /* If we haven't found our filename yet, look backwards + * too as long as we have entries with the same prefix */ + if (homing > 0) { + i = homing - 1; + + do { + entry = git_array_get(tree->entries, i); + + if (homing_search_cmp(&ksearch, entry) > 0) + break; + + if (entry->filename_len == filename_len && + memcmp(filename, entry->filename, filename_len) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } while (i-- > 0); + } + + /* The filename doesn't exist at all */ + return GIT_ENOTFOUND; +} + +void git_tree_entry_free(git_tree_entry *entry) +{ + if (entry == NULL) + return; + + git__free(entry); +} + +int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source) +{ + git_tree_entry *cpy; + + GIT_ASSERT_ARG(source); + + cpy = alloc_entry(source->filename, source->filename_len, &source->oid); + if (cpy == NULL) + return -1; + + cpy->attr = source->attr; + + *dest = cpy; + return 0; +} + +void git_tree__free(void *_tree) +{ + git_tree *tree = _tree; + + git_odb_object_free(tree->odb_obj); + git_array_clear(tree->entries); + git__free(tree); +} + +git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) +{ + return normalize_filemode(entry->attr); +} + +git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry) +{ + return entry->attr; +} + +const char *git_tree_entry_name(const git_tree_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->filename; +} + +const git_oid *git_tree_entry_id(const git_tree_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return &entry->oid; +} + +git_object_t git_tree_entry_type(const git_tree_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, GIT_OBJECT_INVALID); + + if (S_ISGITLINK(entry->attr)) + return GIT_OBJECT_COMMIT; + else if (S_ISDIR(entry->attr)) + return GIT_OBJECT_TREE; + else + return GIT_OBJECT_BLOB; +} + +int git_tree_entry_to_object( + git_object **object_out, + git_repository *repo, + const git_tree_entry *entry) +{ + GIT_ASSERT_ARG(entry); + GIT_ASSERT_ARG(object_out); + + return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJECT_ANY); +} + +static const git_tree_entry *entry_fromname( + const git_tree *tree, const char *name, size_t name_len) +{ + size_t idx; + + if (tree_key_search(&idx, tree, name, name_len) < 0) + return NULL; + + return git_array_get(tree->entries, idx); +} + +const git_tree_entry *git_tree_entry_byname( + const git_tree *tree, const char *filename) +{ + GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); + + return entry_fromname(tree, filename, strlen(filename)); +} + +const git_tree_entry *git_tree_entry_byindex( + const git_tree *tree, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); + return git_array_get(tree->entries, idx); +} + +const git_tree_entry *git_tree_entry_byid( + const git_tree *tree, const git_oid *id) +{ + size_t i; + const git_tree_entry *e; + + GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); + + git_array_foreach(tree->entries, i, e) { + if (git_oid_equal(&e->oid, id)) + return e; + } + + return NULL; +} + +size_t git_tree_entrycount(const git_tree *tree) +{ + GIT_ASSERT_ARG_WITH_RETVAL(tree, 0); + return tree->entries.size; +} + +size_t git_treebuilder_entrycount(git_treebuilder *bld) +{ + GIT_ASSERT_ARG_WITH_RETVAL(bld, 0); + + return git_strmap_size(bld->map); +} + +GIT_INLINE(void) set_error(const char *str, const char *path) +{ + if (path) + git_error_set(GIT_ERROR_TREE, "%s - %s", str, path); + else + git_error_set(GIT_ERROR_TREE, "%s", str); +} + +static int tree_error(const char *str, const char *path) +{ + set_error(str, path); + return -1; +} + +static int tree_parse_error(const char *str, const char *path) +{ + set_error(str, path); + return GIT_EINVALID; +} + +static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out) +{ + int32_t mode; + int error; + + if (!buffer_len || git__isspace(*buffer)) + return -1; + + if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0) + return error; + + if (mode < 0 || mode > UINT16_MAX) + return -1; + + *mode_out = mode; + + return 0; +} + +int git_tree__parse_raw(void *_tree, const char *data, size_t size) +{ + git_tree *tree = _tree; + const char *buffer; + const char *buffer_end; + + buffer = data; + buffer_end = buffer + size; + + tree->odb_obj = NULL; + git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE); + GIT_ERROR_CHECK_ARRAY(tree->entries); + + while (buffer < buffer_end) { + git_tree_entry *entry; + size_t filename_len; + const char *nul; + uint16_t attr; + + if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer) + return tree_parse_error("failed to parse tree: can't parse filemode", NULL); + + if (buffer >= buffer_end || (*buffer++) != ' ') + return tree_parse_error("failed to parse tree: missing space after filemode", NULL); + + if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL) + return tree_parse_error("failed to parse tree: object is corrupted", NULL); + + if ((filename_len = nul - buffer) == 0 || filename_len > UINT16_MAX) + return tree_parse_error("failed to parse tree: can't parse filename", NULL); + + if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ) + return tree_parse_error("failed to parse tree: can't parse OID", NULL); + + /* Allocate the entry */ + { + entry = git_array_alloc(tree->entries); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->attr = attr; + entry->filename_len = (uint16_t)filename_len; + entry->filename = buffer; + git_oid_fromraw(&entry->oid, ((unsigned char *) buffer + filename_len + 1)); + } + + buffer += filename_len + 1; + buffer += GIT_OID_RAWSZ; + } + + return 0; +} + +int git_tree__parse(void *_tree, git_odb_object *odb_obj) +{ + git_tree *tree = _tree; + const char *data = git_odb_object_data(odb_obj); + size_t size = git_odb_object_size(odb_obj); + int error; + + if ((error = git_tree__parse_raw(tree, data, size)) < 0 || + (error = git_odb_object_dup(&tree->odb_obj, odb_obj)) < 0) + return error; + + return error; +} + +static size_t find_next_dir(const char *dirname, git_index *index, size_t start) +{ + size_t dirlen, i, entries = git_index_entrycount(index); + + dirlen = strlen(dirname); + for (i = start; i < entries; ++i) { + const git_index_entry *entry = git_index_get_byindex(index, i); + if (strlen(entry->path) < dirlen || + memcmp(entry->path, dirname, dirlen) || + (dirlen > 0 && entry->path[dirlen] != '/')) { + break; + } + } + + return i; +} + +static git_object_t otype_from_mode(git_filemode_t filemode) +{ + switch (filemode) { + case GIT_FILEMODE_TREE: + return GIT_OBJECT_TREE; + case GIT_FILEMODE_COMMIT: + return GIT_OBJECT_COMMIT; + default: + return GIT_OBJECT_BLOB; + } +} + +static int check_entry(git_repository *repo, const char *filename, const git_oid *id, git_filemode_t filemode) +{ + if (!valid_filemode(filemode)) + return tree_error("failed to insert entry: invalid filemode for file", filename); + + if (!valid_entry_name(repo, filename)) + return tree_error("failed to insert entry: invalid name for a tree entry", filename); + + if (git_oid_is_zero(id)) + return tree_error("failed to insert entry: invalid null OID", filename); + + if (filemode != GIT_FILEMODE_COMMIT && + !git_object__is_valid(repo, id, otype_from_mode(filemode))) + return tree_error("failed to insert entry: invalid object specified", filename); + + return 0; +} + +static int git_treebuilder__write_with_buffer( + git_oid *oid, + git_treebuilder *bld, + git_str *buf) +{ + int error = 0; + size_t i, entrycount; + git_odb *odb; + git_tree_entry *entry; + git_vector entries = GIT_VECTOR_INIT; + + git_str_clear(buf); + + entrycount = git_strmap_size(bld->map); + if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0) + goto out; + + if (buf->asize == 0 && + (error = git_str_grow(buf, entrycount * 72)) < 0) + goto out; + + git_strmap_foreach_value(bld->map, entry, { + if ((error = git_vector_insert(&entries, entry)) < 0) + goto out; + }); + + git_vector_sort(&entries); + + for (i = 0; i < entries.length && !error; ++i) { + entry = git_vector_get(&entries, i); + + git_str_printf(buf, "%o ", entry->attr); + git_str_put(buf, entry->filename, entry->filename_len + 1); + git_str_put(buf, (char *)entry->oid.id, GIT_OID_RAWSZ); + + if (git_str_oom(buf)) { + error = -1; + goto out; + } + } + + if ((error = git_repository_odb__weakptr(&odb, bld->repo)) == 0) + error = git_odb_write(oid, odb, buf->ptr, buf->size, GIT_OBJECT_TREE); + +out: + git_vector_free(&entries); + + return error; +} + +static int append_entry( + git_treebuilder *bld, + const char *filename, + const git_oid *id, + git_filemode_t filemode, + bool validate) +{ + git_tree_entry *entry; + int error = 0; + + if (validate && ((error = check_entry(bld->repo, filename, id, filemode)) < 0)) + return error; + + entry = alloc_entry(filename, strlen(filename), id); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->attr = (uint16_t)filemode; + + 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; + } + + return 0; +} + +static int write_tree( + git_oid *oid, + git_repository *repo, + git_index *index, + const char *dirname, + size_t start, + git_str *shared_buf) +{ + git_treebuilder *bld = NULL; + size_t i, entries = git_index_entrycount(index); + int error; + size_t dirname_len = strlen(dirname); + const git_tree_cache *cache; + + cache = git_tree_cache_get(index->tree, dirname); + if (cache != NULL && cache->entry_count >= 0){ + git_oid_cpy(oid, &cache->oid); + return (int)find_next_dir(dirname, index, start); + } + + if ((error = git_treebuilder_new(&bld, repo, NULL)) < 0 || bld == NULL) + return -1; + + /* + * This loop is unfortunate, but necessary. The index doesn't have + * any directories, so we need to handle that manually, and we + * need to keep track of the current position. + */ + for (i = start; i < entries; ++i) { + const git_index_entry *entry = git_index_get_byindex(index, i); + const char *filename, *next_slash; + + /* + * If we've left our (sub)tree, exit the loop and return. The + * first check is an early out (and security for the + * third). The second check is a simple prefix comparison. The + * third check catches situations where there is a directory + * win32/sys and a file win32mmap.c. Without it, the following + * code believes there is a file win32/mmap.c + */ + if (strlen(entry->path) < dirname_len || + memcmp(entry->path, dirname, dirname_len) || + (dirname_len > 0 && entry->path[dirname_len] != '/')) { + break; + } + + filename = entry->path + dirname_len; + if (*filename == '/') + filename++; + next_slash = strchr(filename, '/'); + if (next_slash) { + git_oid sub_oid; + int written; + char *subdir, *last_comp; + + subdir = git__strndup(entry->path, next_slash - entry->path); + GIT_ERROR_CHECK_ALLOC(subdir); + + /* Write out the subtree */ + written = write_tree(&sub_oid, repo, index, subdir, i, shared_buf); + if (written < 0) { + git__free(subdir); + goto on_error; + } else { + i = written - 1; /* -1 because of the loop increment */ + } + + /* + * We need to figure out what we want toinsert + * into this tree. If we're traversing + * deps/zlib/, then we only want to write + * 'zlib' into the tree. + */ + last_comp = strrchr(subdir, '/'); + if (last_comp) { + last_comp++; /* Get rid of the '/' */ + } else { + last_comp = subdir; + } + + error = append_entry(bld, last_comp, &sub_oid, S_IFDIR, true); + git__free(subdir); + if (error < 0) + goto on_error; + } else { + error = append_entry(bld, filename, &entry->id, entry->mode, true); + if (error < 0) + goto on_error; + } + } + + if (git_treebuilder__write_with_buffer(oid, bld, shared_buf) < 0) + goto on_error; + + git_treebuilder_free(bld); + return (int)i; + +on_error: + git_treebuilder_free(bld); + return -1; +} + +int git_tree__write_index( + git_oid *oid, git_index *index, git_repository *repo) +{ + int ret; + git_tree *tree; + git_str shared_buf = GIT_STR_INIT; + bool old_ignore_case = false; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(repo); + + if (git_index_has_conflicts(index)) { + git_error_set(GIT_ERROR_INDEX, + "cannot create a tree from a not fully merged index."); + return GIT_EUNMERGED; + } + + if (index->tree != NULL && index->tree->entry_count >= 0) { + git_oid_cpy(oid, &index->tree->oid); + return 0; + } + + /* The tree cache didn't help us; we'll have to write + * out a tree. If the index is ignore_case, we must + * make it case-sensitive for the duration of the tree-write + * operation. */ + + if (index->ignore_case) { + old_ignore_case = true; + git_index__set_ignore_case(index, false); + } + + ret = write_tree(oid, repo, index, "", 0, &shared_buf); + git_str_dispose(&shared_buf); + + if (old_ignore_case) + git_index__set_ignore_case(index, true); + + index->tree = NULL; + + if (ret < 0) + return ret; + + git_pool_clear(&index->tree_pool); + + if ((ret = git_tree_lookup(&tree, repo, oid)) < 0) + return ret; + + /* Read the tree cache into the index */ + ret = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); + git_tree_free(tree); + + return ret; +} + +int git_treebuilder_new( + git_treebuilder **builder_p, + git_repository *repo, + const git_tree *source) +{ + git_treebuilder *bld; + size_t i; + + GIT_ASSERT_ARG(builder_p); + GIT_ASSERT_ARG(repo); + + bld = git__calloc(1, sizeof(git_treebuilder)); + GIT_ERROR_CHECK_ALLOC(bld); + + bld->repo = repo; + + if (git_strmap_new(&bld->map) < 0) { + git__free(bld); + return -1; + } + + if (source != NULL) { + git_tree_entry *entry_src; + + git_array_foreach(source->entries, i, entry_src) { + if (append_entry( + bld, entry_src->filename, + &entry_src->oid, + entry_src->attr, + false) < 0) + goto on_error; + } + } + + *builder_p = bld; + return 0; + +on_error: + git_treebuilder_free(bld); + return -1; +} + +int git_treebuilder_insert( + const git_tree_entry **entry_out, + git_treebuilder *bld, + const char *filename, + const git_oid *id, + git_filemode_t filemode) +{ + git_tree_entry *entry; + int error; + + GIT_ASSERT_ARG(bld); + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(filename); + + if ((error = check_entry(bld->repo, filename, id, filemode)) < 0) + return error; + + if ((entry = git_strmap_get(bld->map, filename)) != NULL) { + git_oid_cpy(&entry->oid, id); + } else { + entry = alloc_entry(filename, strlen(filename), id); + GIT_ERROR_CHECK_ALLOC(entry); + + 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; + } + } + + entry->attr = filemode; + + if (entry_out) + *entry_out = entry; + + return 0; +} + +static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) +{ + GIT_ASSERT_ARG_WITH_RETVAL(bld, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); + + return git_strmap_get(bld->map, filename); +} + +const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) +{ + return treebuilder_get(bld, filename); +} + +int git_treebuilder_remove(git_treebuilder *bld, const char *filename) +{ + git_tree_entry *entry = treebuilder_get(bld, filename); + + if (entry == NULL) + return tree_error("failed to remove entry: file isn't in the tree", filename); + + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + + return 0; +} + +int git_treebuilder_write(git_oid *oid, git_treebuilder *bld) +{ + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(bld); + + return git_treebuilder__write_with_buffer(oid, bld, &bld->write_cache); +} + +int git_treebuilder_filter( + git_treebuilder *bld, + git_treebuilder_filter_cb filter, + void *payload) +{ + const char *filename; + git_tree_entry *entry; + + GIT_ASSERT_ARG(bld); + GIT_ASSERT_ARG(filter); + + git_strmap_foreach(bld->map, filename, entry, { + if (filter(entry, payload)) { + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + } + }); + + return 0; +} + +int git_treebuilder_clear(git_treebuilder *bld) +{ + git_tree_entry *e; + + GIT_ASSERT_ARG(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) +{ + if (bld == NULL) + return; + + git_str_dispose(&bld->write_cache); + git_treebuilder_clear(bld); + git_strmap_free(bld->map); + git__free(bld); +} + +static size_t subpath_len(const char *path) +{ + const char *slash_pos = strchr(path, '/'); + if (slash_pos == NULL) + return strlen(path); + + return slash_pos - path; +} + +int git_tree_entry_bypath( + git_tree_entry **entry_out, + const git_tree *root, + const char *path) +{ + int error = 0; + git_tree *subtree; + const git_tree_entry *entry; + size_t filename_len; + + /* Find how long is the current path component (i.e. + * the filename between two slashes */ + filename_len = subpath_len(path); + + if (filename_len == 0) { + git_error_set(GIT_ERROR_TREE, "invalid tree path given"); + return GIT_ENOTFOUND; + } + + entry = entry_fromname(root, path, filename_len); + + if (entry == NULL) { + git_error_set(GIT_ERROR_TREE, + "the path '%.*s' does not exist in the given tree", (int) filename_len, path); + return GIT_ENOTFOUND; + } + + switch (path[filename_len]) { + case '/': + /* If there are more components in the path... + * then this entry *must* be a tree */ + if (!git_tree_entry__is_tree(entry)) { + git_error_set(GIT_ERROR_TREE, + "the path '%.*s' exists but is not a tree", (int) filename_len, path); + return GIT_ENOTFOUND; + } + + /* If there's only a slash left in the path, we + * return the current entry; otherwise, we keep + * walking down the path */ + if (path[filename_len + 1] != '\0') + break; + /* fall through */ + case '\0': + /* If there are no more components in the path, return + * this entry */ + return git_tree_entry_dup(entry_out, entry); + } + + if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0) + return -1; + + error = git_tree_entry_bypath( + entry_out, + subtree, + path + filename_len + 1 + ); + + git_tree_free(subtree); + return error; +} + +static int tree_walk( + const git_tree *tree, + git_treewalk_cb callback, + git_str *path, + void *payload, + bool preorder) +{ + int error = 0; + size_t i; + const git_tree_entry *entry; + + git_array_foreach(tree->entries, i, entry) { + if (preorder) { + error = callback(path->ptr, entry, payload); + if (error < 0) { /* negative value stops iteration */ + git_error_set_after_callback_function(error, "git_tree_walk"); + break; + } + if (error > 0) { /* positive value skips this entry */ + error = 0; + continue; + } + } + + if (git_tree_entry__is_tree(entry)) { + git_tree *subtree; + size_t path_len = git_str_len(path); + + error = git_tree_lookup(&subtree, tree->object.repo, &entry->oid); + if (error < 0) + break; + + /* append the next entry to the path */ + git_str_puts(path, entry->filename); + git_str_putc(path, '/'); + + if (git_str_oom(path)) + error = -1; + else + error = tree_walk(subtree, callback, path, payload, preorder); + + git_tree_free(subtree); + if (error != 0) + break; + + git_str_truncate(path, path_len); + } + + if (!preorder) { + error = callback(path->ptr, entry, payload); + if (error < 0) { /* negative value stops iteration */ + git_error_set_after_callback_function(error, "git_tree_walk"); + break; + } + error = 0; + } + } + + return error; +} + +int git_tree_walk( + const git_tree *tree, + git_treewalk_mode mode, + git_treewalk_cb callback, + void *payload) +{ + int error = 0; + git_str root_path = GIT_STR_INIT; + + if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) { + git_error_set(GIT_ERROR_INVALID, "invalid walking mode for tree walk"); + return -1; + } + + error = tree_walk( + tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE)); + + git_str_dispose(&root_path); + + return error; +} + +static int compare_entries(const void *_a, const void *_b) +{ + const git_tree_update *a = (git_tree_update *) _a; + const git_tree_update *b = (git_tree_update *) _b; + + return strcmp(a->path, b->path); +} + +static int on_dup_entry(void **old, void *new) +{ + GIT_UNUSED(old); GIT_UNUSED(new); + + git_error_set(GIT_ERROR_TREE, "duplicate entries given for update"); + return -1; +} + +/* + * We keep the previous tree and the new one at each level of the + * stack. When we leave a level we're done with that tree and we can + * write it out to the odb. + */ +typedef struct { + git_treebuilder *bld; + git_tree *tree; + char *name; +} tree_stack_entry; + +/** Count how many slashes (i.e. path components) there are in this string */ +GIT_INLINE(size_t) count_slashes(const char *path) +{ + size_t count = 0; + const char *slash; + + while ((slash = strchr(path, '/')) != NULL) { + count++; + path = slash + 1; + } + + return count; +} + +static bool next_component(git_str *out, const char *in) +{ + const char *slash = strchr(in, '/'); + + git_str_clear(out); + + if (slash) + git_str_put(out, in, slash - in); + + return !!slash; +} + +static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_str *component) +{ + int error; + git_oid new_tree; + + git_tree_free(popped->tree); + + /* If the tree would be empty, remove it from the one higher up */ + if (git_treebuilder_entrycount(popped->bld) == 0) { + git_treebuilder_free(popped->bld); + error = git_treebuilder_remove(current->bld, popped->name); + git__free(popped->name); + return error; + } + + error = git_treebuilder_write(&new_tree, popped->bld); + git_treebuilder_free(popped->bld); + + if (error < 0) { + git__free(popped->name); + return error; + } + + /* We've written out the tree, now we have to put the new value into its parent */ + git_str_clear(component); + git_str_puts(component, popped->name); + git__free(popped->name); + + GIT_ERROR_CHECK_ALLOC(component->ptr); + + /* Error out if this would create a D/F conflict in this update */ + if (current->tree) { + const git_tree_entry *to_replace; + to_replace = git_tree_entry_byname(current->tree, component->ptr); + if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJECT_TREE) { + git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); + return -1; + } + } + + return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE); +} + +int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates) +{ + git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT; + tree_stack_entry *root_elem; + git_vector entries; + int error; + size_t i; + git_str component = GIT_STR_INIT; + + if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0) + return error; + + /* Sort the entries for treversal */ + for (i = 0 ; i < nupdates; i++) { + if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0) + goto cleanup; + } + + root_elem = git_array_alloc(stack); + GIT_ERROR_CHECK_ALLOC(root_elem); + memset(root_elem, 0, sizeof(*root_elem)); + + if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0) + goto cleanup; + + if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0) + goto cleanup; + + for (i = 0; i < nupdates; i++) { + const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1); + const git_tree_update *update = git_vector_get(&entries, i); + size_t common_prefix = 0, steps_up, j; + const char *path; + + /* Figure out how much we need to change from the previous tree */ + if (last_update) + common_prefix = git_fs_path_common_dirlen(last_update->path, update->path); + + /* + * The entries are sorted, so when we find we're no + * longer in the same directory, we need to abandon + * the old tree (steps up) and dive down to the next + * one. + */ + steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]); + + for (j = 0; j < steps_up; j++) { + tree_stack_entry *current, *popped = git_array_pop(stack); + GIT_ASSERT(popped); + + current = git_array_last(stack); + GIT_ASSERT(current); + + if ((error = create_popped_tree(current, popped, &component)) < 0) + goto cleanup; + } + + /* Now that we've created the trees we popped from the stack, let's go back down */ + path = &update->path[common_prefix]; + while (next_component(&component, path)) { + tree_stack_entry *last, *new_entry; + const git_tree_entry *entry; + + last = git_array_last(stack); + entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL; + if (!entry) + entry = treebuilder_get(last->bld, component.ptr); + + if (entry && git_tree_entry_type(entry) != GIT_OBJECT_TREE) { + git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); + error = -1; + goto cleanup; + } + + new_entry = git_array_alloc(stack); + GIT_ERROR_CHECK_ALLOC(new_entry); + memset(new_entry, 0, sizeof(*new_entry)); + + new_entry->tree = NULL; + if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0) + goto cleanup; + + if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0) + goto cleanup; + + new_entry->name = git__strdup(component.ptr); + GIT_ERROR_CHECK_ALLOC(new_entry->name); + + /* Get to the start of the next component */ + path += component.size + 1; + } + + /* After all that, we're finally at the place where we want to perform the update */ + switch (update->action) { + case GIT_TREE_UPDATE_UPSERT: + { + /* Make sure we're replacing something of the same type */ + tree_stack_entry *last = git_array_last(stack); + char *basename = git_fs_path_basename(update->path); + const git_tree_entry *e = git_treebuilder_get(last->bld, basename); + if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) { + git__free(basename); + git_error_set(GIT_ERROR_TREE, "cannot replace '%s' with '%s' at '%s'", + git_object_type2string(git_tree_entry_type(e)), + git_object_type2string(git_object__type_from_filemode(update->filemode)), + update->path); + error = -1; + goto cleanup; + } + + error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode); + git__free(basename); + break; + } + case GIT_TREE_UPDATE_REMOVE: + { + tree_stack_entry *last = git_array_last(stack); + char *basename = git_fs_path_basename(update->path); + error = git_treebuilder_remove(last->bld, basename); + git__free(basename); + break; + } + default: + git_error_set(GIT_ERROR_TREE, "unknown action for update"); + error = -1; + goto cleanup; + } + + if (error < 0) + goto cleanup; + } + + /* We're done, go up the stack again and write out the tree */ + { + tree_stack_entry *current = NULL, *popped = NULL; + while ((popped = git_array_pop(stack)) != NULL) { + current = git_array_last(stack); + /* We've reached the top, current is the root tree */ + if (!current) + break; + + if ((error = create_popped_tree(current, popped, &component)) < 0) + goto cleanup; + } + + /* Write out the root tree */ + git__free(popped->name); + git_tree_free(popped->tree); + + error = git_treebuilder_write(out, popped->bld); + git_treebuilder_free(popped->bld); + if (error < 0) + goto cleanup; + } + +cleanup: + { + tree_stack_entry *e; + while ((e = git_array_pop(stack)) != NULL) { + git_treebuilder_free(e->bld); + git_tree_free(e->tree); + git__free(e->name); + } + } + + git_str_dispose(&component); + git_array_clear(stack); + git_vector_free(&entries); + return error; +} + +/* Deprecated Functions */ + +#ifndef GIT_DEPRECATE_HARD + +int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_buf *buf) +{ + GIT_UNUSED(buf); + + return git_treebuilder_write(oid, bld); +} + +#endif diff --git a/src/libgit2/tree.h b/src/libgit2/tree.h new file mode 100644 index 000000000..0dd963ff2 --- /dev/null +++ b/src/libgit2/tree.h @@ -0,0 +1,58 @@ +/* + * 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_tree_h__ +#define INCLUDE_tree_h__ + +#include "common.h" + +#include "git2/tree.h" +#include "repository.h" +#include "odb.h" +#include "vector.h" +#include "strmap.h" +#include "pool.h" + +struct git_tree_entry { + uint16_t attr; + uint16_t filename_len; + git_oid oid; + const char *filename; +}; + +struct git_tree { + git_object object; + git_odb_object *odb_obj; + git_array_t(git_tree_entry) entries; +}; + +struct git_treebuilder { + git_repository *repo; + git_strmap *map; + git_str write_cache; +}; + +GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) +{ + return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); +} + +void git_tree__free(void *tree); +int git_tree__parse(void *tree, git_odb_object *obj); +int git_tree__parse_raw(void *_tree, const char *data, size_t size); + +/** + * Write a tree to the given repository + */ +int git_tree__write_index( + git_oid *oid, git_index *index, git_repository *repo); + +/** + * Obsolete mode kept for compatibility reasons + */ +#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664 + +#endif diff --git a/src/libgit2/userdiff.h b/src/libgit2/userdiff.h new file mode 100644 index 000000000..c9a80d712 --- /dev/null +++ b/src/libgit2/userdiff.h @@ -0,0 +1,210 @@ +/* + * 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_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 + * original authors for relicensing to libgit2). + */ + +typedef struct { + const char *name; + const char *fns; + const char *words; + int flags; +} git_diff_driver_definition; + +#define WORD_DEFAULT "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" + +/* + * These builtin driver definition macros have same signature as in core + * git userdiff.c so that the data can be extracted verbatim + */ +#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, GIT_REGEXP_ICASE } + +/* + * The table of diff driver patterns + * + * Function name patterns are a list of newline separated patterns that + * match a function declaration (i.e. the line you want in the hunk header), + * or a negative pattern prefixed with a '!' to reject a pattern (such as + * rejecting goto labels in C code). + * + * Word boundary patterns are just a simple pattern that will be OR'ed with + * the default value above (i.e. whitespace or non-ASCII characters). + */ +static git_diff_driver_definition builtin_defs[] = { + +IPATTERN("ada", + "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n" + "!^[ \t]*with[ \t].*$\n" + "^[ \t]*((procedure|function)[ \t]+.*)$\n" + "^[ \t]*((package|protected|task)[ \t]+.*)$", + /* -- */ + "[a-zA-Z][a-zA-Z0-9_]*" + "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?" + "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"), + +IPATTERN("fortran", + "!^([C*]|[ \t]*!)\n" + "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n" + "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA" + "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$", + /* -- */ + "[a-zA-Z][a-zA-Z0-9_]*" + "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\." + /* numbers and format statements like 2E14.4, or ES12.6, 9X. + * Don't worry about format statements without leading digits since + * they would have been matched above as a variable anyway. */ + "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" + "|//|\\*\\*|::|[/<>=]="), + +PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", + "[^<>= \t]+"), + +PATTERNS("java", + "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" + "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=" + "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), + +PATTERNS("matlab", + "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$", + "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"), + +PATTERNS("objc", + /* Negate C statements that can look like functions */ + "!^[ \t]*(do|for|if|else|return|switch|while)\n" + /* Objective-C methods */ + "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n" + /* C functions */ + "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n" + /* Objective-C class/protocol definitions */ + "^(@(implementation|interface|protocol)[ \t].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), + +PATTERNS("pascal", + "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|" + "implementation|initialization|finalization)[ \t]*.*)$" + "\n" + "^(.*=[ \t]*(class|record).*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" + "|<>|<=|>=|:=|\\.\\."), + +PATTERNS("perl", + "^package .*\n" + "^sub [[:alnum:]_':]+[ \t]*" + "(\\([^)]*\\)[ \t]*)?" /* prototype */ + /* + * Attributes. A regex can't count nested parentheses, + * so just slurp up whatever we see, taking care not + * to accept lines like "sub foo; # defined elsewhere". + * + * An attribute could contain a semicolon, but at that + * point it seems reasonable enough to give up. + */ + "(:[^;#]*)?" + "(\\{[ \t]*)?" /* brace can come here or on the next line */ + "(#.*)?$\n" /* comment */ + "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*" + "(\\{[ \t]*)?" /* brace can come here or on the next line */ + "(#.*)?$\n" + "^=head[0-9] .*", /* POD */ + /* -- */ + "[[:alpha:]_'][[:alnum:]_']*" + "|0[xb]?[0-9a-fA-F_]*" + /* taking care not to interpret 3..5 as (3.)(.5) */ + "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?" + "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::" + "|&&=|\\|\\|=|//=|\\*\\*=" + "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?" + "|[-+*/%.^&<>=!|]=" + "|=~|!~" + "|<<|<>|<=>|>>"), + +PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"), + +PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", + /* -- */ + "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." + "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"), + +PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", + "[={}\"]|[^={}\" \t]+"), + +PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", + "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), + +PATTERNS("cpp", + /* Jump targets or access declarations */ + "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n" + /* functions/methods, variables, and compounds at top level */ + "^((::[[:space:]]*)?[A-Za-z_].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"), + +PATTERNS("csharp", + /* Keywords */ + "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" + /* Methods and constructors */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" + /* Properties */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" + /* Type definitions */ + "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" + /* Namespace */ + "^[ \t]*(namespace[ \t]+.*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), + +PATTERNS("php", + "^[ \t]*(((public|private|protected|static|final)[ \t]+)*((class|function)[ \t].*))$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), + +PATTERNS("javascript", + "([a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z0-9_$]+)*[ \t]*=[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" + "([a-zA-Z_$][a-zA-Z0-9_$]*[ \t]*:[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" + "[^a-zA-Z0-9_\\$](function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), +}; + +#undef IPATTERN +#undef PATTERNS +#undef WORD_DEFAULT + +#endif + diff --git a/src/libgit2/worktree.c b/src/libgit2/worktree.c new file mode 100644 index 000000000..2ac2274f1 --- /dev/null +++ b/src/libgit2/worktree.c @@ -0,0 +1,652 @@ +/* + * 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 "worktree.h" + +#include "buf.h" +#include "repository.h" +#include "path.h" + +#include "git2/branch.h" +#include "git2/commit.h" +#include "git2/worktree.h" + +static bool is_worktree_dir(const char *dir) +{ + git_str buf = GIT_STR_INIT; + int error; + + if (git_str_sets(&buf, dir) < 0) + return -1; + + error = git_fs_path_contains_file(&buf, "commondir") + && git_fs_path_contains_file(&buf, "gitdir") + && git_fs_path_contains_file(&buf, "HEAD"); + + git_str_dispose(&buf); + return error; +} + +int git_worktree_list(git_strarray *wts, git_repository *repo) +{ + git_vector worktrees = GIT_VECTOR_INIT; + git_str path = GIT_STR_INIT; + char *worktree; + size_t i, len; + int error; + + GIT_ASSERT_ARG(wts); + GIT_ASSERT_ARG(repo); + + wts->count = 0; + wts->strings = NULL; + + if ((error = git_str_joinpath(&path, repo->commondir, "worktrees/")) < 0) + goto exit; + if (!git_fs_path_exists(path.ptr) || git_fs_path_is_empty_dir(path.ptr)) + goto exit; + if ((error = git_fs_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0) + goto exit; + + len = path.size; + + git_vector_foreach(&worktrees, i, worktree) { + git_str_truncate(&path, len); + git_str_puts(&path, worktree); + + if (!is_worktree_dir(path.ptr)) { + git_vector_remove(&worktrees, i); + git__free(worktree); + } + } + + wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees); + +exit: + git_str_dispose(&path); + + return error; +} + +char *git_worktree__read_link(const char *base, const char *file) +{ + git_str path = GIT_STR_INIT, buf = GIT_STR_INIT; + + GIT_ASSERT_ARG_WITH_RETVAL(base, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(file, NULL); + + if (git_str_joinpath(&path, base, file) < 0) + goto err; + if (git_futils_readbuffer(&buf, path.ptr) < 0) + goto err; + git_str_dispose(&path); + + git_str_rtrim(&buf); + + if (!git_fs_path_is_relative(buf.ptr)) + return git_str_detach(&buf); + + if (git_str_sets(&path, base) < 0) + goto err; + if (git_fs_path_apply_relative(&path, buf.ptr) < 0) + goto err; + git_str_dispose(&buf); + + return git_str_detach(&path); + +err: + git_str_dispose(&buf); + git_str_dispose(&path); + + return NULL; +} + +static int write_wtfile(const char *base, const char *file, const git_str *buf) +{ + git_str path = GIT_STR_INIT; + int err; + + GIT_ASSERT_ARG(base); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(buf); + + if ((err = git_str_joinpath(&path, base, file)) < 0) + goto out; + + if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + goto out; + +out: + git_str_dispose(&path); + + return err; +} + +static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name) +{ + git_str gitdir = GIT_STR_INIT; + git_worktree *wt = NULL; + int error = 0; + + if (!is_worktree_dir(dir)) { + error = -1; + goto out; + } + + if ((error = git_path_validate_length(NULL, dir)) < 0) + goto out; + + if ((wt = git__calloc(1, sizeof(*wt))) == NULL) { + error = -1; + 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_fs_path_dirname(wt->gitlink_path)) == NULL) { + error = -1; + goto out; + } + + if ((error = git_fs_path_prettify_dir(&gitdir, dir, NULL)) < 0) + goto out; + wt->gitdir_path = git_str_detach(&gitdir); + + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + goto out; + wt->locked = !!error; + error = 0; + + *out = wt; + +out: + if (error) + git_worktree_free(wt); + git_str_dispose(&gitdir); + + return error; +} + +int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) +{ + git_str path = GIT_STR_INIT; + git_worktree *wt = NULL; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + *out = NULL; + + if ((error = git_str_join3(&path, '/', repo->commondir, "worktrees", name)) < 0) + goto out; + + if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0) + goto out; + +out: + git_str_dispose(&path); + + if (error) + git_worktree_free(wt); + + return error; +} + +int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) +{ + git_str parent = GIT_STR_INIT; + const char *gitdir, *commondir; + char *name = NULL; + int error = 0; + + if (!git_repository_is_worktree(repo)) { + git_error_set(GIT_ERROR_WORKTREE, "cannot open worktree of a non-worktree repo"); + error = -1; + goto out; + } + + gitdir = git_repository_path(repo); + commondir = git_repository_commondir(repo); + + if ((error = git_fs_path_prettify_dir(&parent, "..", commondir)) < 0) + goto out; + + /* The name is defined by the last component in '.git/worktree/%s' */ + name = git_fs_path_basename(gitdir); + + if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0) + goto out; + +out: + git__free(name); + git_str_dispose(&parent); + + return error; +} + +void git_worktree_free(git_worktree *wt) +{ + if (!wt) + return; + + git__free(wt->commondir_path); + git__free(wt->worktree_path); + git__free(wt->gitlink_path); + git__free(wt->gitdir_path); + git__free(wt->parent_path); + git__free(wt->name); + git__free(wt); +} + +int git_worktree_validate(const git_worktree *wt) +{ + GIT_ASSERT_ARG(wt); + + if (!is_worktree_dir(wt->gitdir_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree gitdir ('%s') is not valid", + wt->gitlink_path); + return GIT_ERROR; + } + + if (wt->parent_path && !git_fs_path_exists(wt->parent_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree parent directory ('%s') does not exist ", + wt->parent_path); + return GIT_ERROR; + } + + if (!git_fs_path_exists(wt->commondir_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree common directory ('%s') does not exist ", + wt->commondir_path); + return GIT_ERROR; + } + + if (!git_fs_path_exists(wt->worktree_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree directory '%s' does not exist", + wt->worktree_path); + return GIT_ERROR; + } + + return 0; +} + +int git_worktree_add_options_init(git_worktree_add_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_worktree_add_init_options(git_worktree_add_options *opts, + unsigned int version) +{ + return git_worktree_add_options_init(opts, version); +} +#endif + +int git_worktree_add(git_worktree **out, git_repository *repo, + const char *name, const char *worktree, + const git_worktree_add_options *opts) +{ + git_str gitdir = GIT_STR_INIT, wddir = GIT_STR_INIT, buf = GIT_STR_INIT; + git_reference *ref = NULL, *head = NULL; + git_commit *commit = NULL; + git_repository *wt = NULL; + git_checkout_options coopts; + git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT; + int err; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options"); + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(worktree); + + *out = NULL; + + if (opts) + memcpy(&wtopts, opts, sizeof(wtopts)); + + memcpy(&coopts, &wtopts.checkout_options, sizeof(coopts)); + + 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_str_joinpath(&gitdir, repo->commondir, "worktrees")) < 0) + goto out; + if (!git_fs_path_exists(gitdir.ptr)) + if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_str_joinpath(&gitdir, gitdir.ptr, name)) < 0) + goto out; + if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_fs_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0) + goto out; + + /* Create worktree work dir */ + if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_fs_path_prettify_dir(&wddir, worktree, NULL)) < 0) + goto out; + + if (wtopts.lock) { + int fd; + + if ((err = git_str_joinpath(&buf, gitdir.ptr, "locked")) < 0) + goto out; + + if ((fd = p_creat(buf.ptr, 0644)) < 0) { + err = fd; + goto out; + } + + p_close(fd); + git_str_clear(&buf); + } + + /* Create worktree .git file */ + if ((err = git_str_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) + goto out; + if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0) + goto out; + + /* Create gitdir files */ + if ((err = git_fs_path_prettify_dir(&buf, repo->commondir, NULL) < 0) + || (err = git_str_putc(&buf, '\n')) < 0 + || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0) + goto out; + if ((err = git_str_joinpath(&buf, wddir.ptr, ".git")) < 0 + || (err = git_str_putc(&buf, '\n')) < 0 + || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) + goto out; + + /* Set up worktree reference */ + if (wtopts.ref) { + if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) + goto out; + } else { + if ((err = git_repository_head(&head, repo)) < 0) + goto out; + if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0) + goto out; + if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0) + goto out; + } + + /* Set worktree's HEAD */ + if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) + goto out; + if ((err = git_repository_open(&wt, wddir.ptr)) < 0) + goto out; + + /* Checkout worktree's HEAD */ + if ((err = git_checkout_head(wt, &coopts)) < 0) + goto out; + + /* Load result */ + if ((err = git_worktree_lookup(out, repo, name)) < 0) + goto out; + +out: + git_str_dispose(&gitdir); + git_str_dispose(&wddir); + git_str_dispose(&buf); + git_reference_free(ref); + git_reference_free(head); + git_commit_free(commit); + git_repository_free(wt); + + return err; +} + +int git_worktree_lock(git_worktree *wt, const char *reason) +{ + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(wt); + + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + goto out; + if (error) { + error = GIT_ELOCKED; + goto out; + } + + if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + + if (reason) + git_str_attach_notowned(&buf, reason, strlen(reason)); + + if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + goto out; + + wt->locked = 1; + +out: + git_str_dispose(&path); + + return error; +} + +int git_worktree_unlock(git_worktree *wt) +{ + git_str path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(wt); + + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + return error; + if (!error) + return 1; + + if (git_str_joinpath(&path, wt->gitdir_path, "locked") < 0) + return -1; + + if (p_unlink(path.ptr) != 0) { + git_str_dispose(&path); + return -1; + } + + wt->locked = 0; + + git_str_dispose(&path); + + return 0; +} + +static int git_worktree__is_locked(git_str *reason, const git_worktree *wt) +{ + git_str path = GIT_STR_INIT; + int error, locked; + + GIT_ASSERT_ARG(wt); + + if (reason) + git_str_clear(reason); + + if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + locked = git_fs_path_exists(path.ptr); + if (locked && reason && + (error = git_futils_readbuffer(reason, path.ptr)) < 0) + goto out; + + error = locked; +out: + git_str_dispose(&path); + + return error; +} + +int git_worktree_is_locked(git_buf *reason, const git_worktree *wt) +{ + git_str str = GIT_STR_INIT; + int error = 0; + + if (reason && (error = git_buf_tostr(&str, reason)) < 0) + return error; + + error = git_worktree__is_locked(reason ? &str : NULL, wt); + + if (error >= 0 && reason) { + if (git_buf_fromstr(reason, &str) < 0) + error = -1; + } + + git_str_dispose(&str); + return error; +} + +const char *git_worktree_name(const git_worktree *wt) +{ + GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); + return wt->name; +} + +const char *git_worktree_path(const git_worktree *wt) +{ + GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); + return wt->worktree_path; +} + +int git_worktree_prune_options_init( + git_worktree_prune_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_worktree_prune_init_options(git_worktree_prune_options *opts, + unsigned int version) +{ + return git_worktree_prune_options_init(opts, version); +} +#endif + +int git_worktree_is_prunable(git_worktree *wt, + git_worktree_prune_options *opts) +{ + git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + "git_worktree_prune_options"); + + if (opts) + memcpy(&popts, opts, sizeof(popts)); + + if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) { + git_str reason = GIT_STR_INIT; + int error; + + if ((error = git_worktree__is_locked(&reason, wt)) < 0) + return error; + + if (error) { + if (!reason.size) + git_str_attach_notowned(&reason, "no reason given", 15); + git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr); + git_str_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"); + return 0; + } + + return 1; +} + +int git_worktree_prune(git_worktree *wt, + git_worktree_prune_options *opts) +{ + git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + char *wtpath; + int err; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + "git_worktree_prune_options"); + + if (opts) + memcpy(&popts, opts, sizeof(popts)); + + if (!git_worktree_is_prunable(wt, &popts)) { + err = -1; + goto out; + } + + /* Delete gitdir in parent repository */ + if ((err = git_str_join3(&path, '/', wt->commondir_path, "worktrees", wt->name)) < 0) + goto out; + if (!git_fs_path_exists(path.ptr)) + { + git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr); + err = -1; + goto out; + } + if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) + goto out; + + /* Skip deletion of the actual working tree if it does + * not exist or deletion was not requested */ + if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || + !git_fs_path_exists(wt->gitlink_path)) + { + goto out; + } + + if ((wtpath = git_fs_path_dirname(wt->gitlink_path)) == NULL) + goto out; + git_str_attach(&path, wtpath, 0); + if (!git_fs_path_exists(path.ptr)) + { + git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr); + err = -1; + goto out; + } + if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) + goto out; + +out: + git_str_dispose(&path); + + return err; +} diff --git a/src/libgit2/worktree.h b/src/libgit2/worktree.h new file mode 100644 index 000000000..587189f81 --- /dev/null +++ b/src/libgit2/worktree.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_worktree_h__ +#define INCLUDE_worktree_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/worktree.h" + +struct git_worktree { + /* Name of the working tree. This is the name of the + * containing directory in the `$PARENT/.git/worktrees/` + * directory. */ + char *name; + + /* Path to the where the worktree lives in the filesystem */ + char *worktree_path; + /* Path to the .git file in the working tree's repository */ + char *gitlink_path; + /* Path to the .git directory inside the parent's + * worktrees directory */ + char *gitdir_path; + /* Path to the common directory contained in the parent + * repository */ + char *commondir_path; + /* Path to the parent's working directory */ + char *parent_path; + + unsigned int locked:1; +}; + +char *git_worktree__read_link(const char *base, const char *file); + +#endif diff --git a/src/libgit2/xdiff/git-xdiff.h b/src/libgit2/xdiff/git-xdiff.h new file mode 100644 index 000000000..b75dba819 --- /dev/null +++ b/src/libgit2/xdiff/git-xdiff.h @@ -0,0 +1,53 @@ +/* + * 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 provides the necessary indirection between xdiff and + * libgit2. libgit2-specific functionality should live here, so + * that git and libgit2 can share a common xdiff implementation. + */ + +#ifndef INCLUDE_git_xdiff_h__ +#define INCLUDE_git_xdiff_h__ + +#include "regexp.h" + +/* Work around C90-conformance issues */ +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +# if defined(_MSC_VER) +# define inline __inline +# elif defined(__GNUC__) +# define inline __inline__ +# else +# define inline +# endif +#endif + +#define xdl_malloc(x) git__malloc(x) +#define xdl_free(ptr) git__free(ptr) +#define xdl_realloc(ptr, x) git__realloc(ptr, x) + +#define XDL_BUG(msg) GIT_ASSERT(msg) + +#define xdl_regex_t git_regexp +#define xdl_regmatch_t git_regmatch + +GIT_INLINE(int) xdl_regexec_buf( + const xdl_regex_t *preg, const char *buf, size_t size, + size_t nmatch, xdl_regmatch_t pmatch[], int eflags) +{ + GIT_UNUSED(preg); + GIT_UNUSED(buf); + GIT_UNUSED(size); + GIT_UNUSED(nmatch); + GIT_UNUSED(pmatch); + GIT_UNUSED(eflags); + GIT_ASSERT("not implemented"); + return -1; +} + +#endif diff --git a/src/libgit2/xdiff/xdiff.h b/src/libgit2/xdiff/xdiff.h new file mode 100644 index 000000000..fb47f63fb --- /dev/null +++ b/src/libgit2/xdiff/xdiff.h @@ -0,0 +1,150 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XDIFF_H) +#define XDIFF_H + +#ifdef __cplusplus +extern "C" { +#endif /* #ifdef __cplusplus */ + +#include "git-xdiff.h" + +/* xpparm_t.flags */ +#define XDF_NEED_MINIMAL (1 << 0) + +#define XDF_IGNORE_WHITESPACE (1 << 1) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3) +#define XDF_IGNORE_CR_AT_EOL (1 << 4) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \ + XDF_IGNORE_WHITESPACE_CHANGE | \ + XDF_IGNORE_WHITESPACE_AT_EOL | \ + XDF_IGNORE_CR_AT_EOL) + +#define XDF_IGNORE_BLANK_LINES (1 << 7) + +#define XDF_PATIENCE_DIFF (1 << 14) +#define XDF_HISTOGRAM_DIFF (1 << 15) +#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) +#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) + +#define XDF_INDENT_HEURISTIC (1 << 23) + +/* xdemitconf_t.flags */ +#define XDL_EMIT_FUNCNAMES (1 << 0) +#define XDL_EMIT_NO_HUNK_HDR (1 << 1) +#define XDL_EMIT_FUNCCONTEXT (1 << 2) + +/* merge simplification levels */ +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 +#define XDL_MERGE_ZEALOUS_ALNUM 3 + +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR_UNION 3 + +/* merge output styles */ +#define XDL_MERGE_DIFF3 1 +#define XDL_MERGE_ZEALOUS_DIFF3 2 + +typedef struct s_mmfile { + char *ptr; + long size; +} mmfile_t; + +typedef struct s_mmbuffer { + char *ptr; + long size; +} mmbuffer_t; + +typedef struct s_xpparam { + unsigned long flags; + + /* -I */ + xdl_regex_t **ignore_regex; + size_t ignore_regex_nr; + + /* See Documentation/diff-options.txt. */ + char **anchors; + size_t anchors_nr; +} xpparam_t; + +typedef struct s_xdemitcb { + void *priv; + int (*out_hunk)(void *, + long old_begin, long old_nr, + long new_begin, long new_nr, + const char *func, long funclen); + int (*out_line)(void *, mmbuffer_t *, int); +} xdemitcb_t; + +typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); + +typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a, + long start_b, long count_b, + void *cb_data); + +typedef struct s_xdemitconf { + long ctxlen; + long interhunkctxlen; + unsigned long flags; + find_func_t find_func; + void *find_func_priv; + xdl_emit_hunk_consume_func_t hunk_func; +} xdemitconf_t; + +typedef struct s_bdiffparam { + long bsize; +} bdiffparam_t; + + +void *xdl_mmfile_first(mmfile_t *mmf, long *size); +long xdl_mmfile_size(mmfile_t *mmf); + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb); + +typedef struct s_xmparam { + xpparam_t xpp; + int marker_size; + int level; + int favor; + int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ +} xmparam_t; + +#define DEFAULT_CONFLICT_MARKER_SIZE 7 + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result); + +#ifdef __cplusplus +} +#endif /* #ifdef __cplusplus */ + +#endif /* #if !defined(XDIFF_H) */ diff --git a/src/libgit2/xdiff/xdiffi.c b/src/libgit2/xdiff/xdiffi.c new file mode 100644 index 000000000..af31b7f4b --- /dev/null +++ b/src/libgit2/xdiff/xdiffi.c @@ -0,0 +1,1088 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +#define XDL_MAX_COST_MIN 256 +#define XDL_HEUR_MIN_COST 256 +#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) +#define XDL_SNAKE_CNT 20 +#define XDL_K_HEUR 4 + +typedef struct s_xdpsplit { + long i1, i2; + int min_lo, min_hi; +} xdpsplit_t; + +/* + * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. + * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both + * the forward diagonal starting from (off1, off2) and the backward diagonal + * starting from (lim1, lim2). If the K values on the same diagonal crosses + * returns the furthest point of reach. We might encounter expensive edge cases + * using this algorithm, so a little bit of heuristic is needed to cut the + * search and to return a suboptimal point. + */ +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv) { + long dmin = off1 - lim2, dmax = lim1 - off2; + long fmid = off1 - off2, bmid = lim1 - lim2; + long odd = (fmid - bmid) & 1; + long fmin = fmid, fmax = fmid; + long bmin = bmid, bmax = bmid; + long ec, d, i1, i2, prev1, best, dd, v, k; + + /* + * Set initial diagonal values for both forward and backward path. + */ + kvdf[fmid] = off1; + kvdb[bmid] = lim1; + + for (ec = 1;; ec++) { + int got_snake = 0; + + /* + * We need to extend the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of + * two. + * + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions in the check inside the core loop. + */ + if (fmin > dmin) + kvdf[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + kvdf[++fmax + 1] = -1; + else + --fmax; + + for (d = fmax; d >= fmin; d -= 2) { + if (kvdf[d - 1] >= kvdf[d + 1]) + i1 = kvdf[d - 1] + 1; + else + i1 = kvdf[d + 1]; + prev1 = i1; + i2 = i1 - d; + for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); + if (i1 - prev1 > xenv->snake_cnt) + got_snake = 1; + kvdf[d] = i1; + if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + /* + * We need to extend the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of + * two. + * + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions in the check inside the core loop. + */ + if (bmin > dmin) + kvdb[--bmin - 1] = XDL_LINE_MAX; + else + ++bmin; + if (bmax < dmax) + kvdb[++bmax + 1] = XDL_LINE_MAX; + else + --bmax; + + for (d = bmax; d >= bmin; d -= 2) { + if (kvdb[d - 1] < kvdb[d + 1]) + i1 = kvdb[d - 1]; + else + i1 = kvdb[d + 1] - 1; + prev1 = i1; + i2 = i1 - d; + for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); + if (prev1 - i1 > xenv->snake_cnt) + got_snake = 1; + kvdb[d] = i1; + if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + if (need_min) + continue; + + /* + * If the edit cost is above the heuristic trigger and if + * we got a good snake, we sample current diagonals to see + * if some of them have reached an "interesting" path. Our + * measure is a function of the distance from the diagonal + * corner (i1 + i2) penalized with the distance from the + * mid diagonal itself. If this value is above the current + * edit cost times a magic factor (XDL_K_HEUR) we consider + * it interesting. + */ + if (got_snake && ec > xenv->heur_min) { + for (best = 0, d = fmax; d >= fmin; d -= 2) { + dd = d > fmid ? d - fmid: fmid - d; + i1 = kvdf[d]; + i2 = i1 - d; + v = (i1 - off1) + (i2 - off2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 + xenv->snake_cnt <= i1 && i1 < lim1 && + off2 + xenv->snake_cnt <= i2 && i2 < lim2) { + for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) + if (k == xenv->snake_cnt) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 1; + spl->min_hi = 0; + return ec; + } + + for (best = 0, d = bmax; d >= bmin; d -= 2) { + dd = d > bmid ? d - bmid: bmid - d; + i1 = kvdb[d]; + i2 = i1 - d; + v = (lim1 - i1) + (lim2 - i2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 < i1 && i1 <= lim1 - xenv->snake_cnt && + off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { + for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) + if (k == xenv->snake_cnt - 1) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 0; + spl->min_hi = 1; + return ec; + } + } + + /* + * Enough is enough. We spent too much time here and now we + * collect the furthest reaching path using the (i1 + i2) + * measure. + */ + if (ec >= xenv->mxcost) { + long fbest, fbest1, bbest, bbest1; + + fbest = fbest1 = -1; + for (d = fmax; d >= fmin; d -= 2) { + i1 = XDL_MIN(kvdf[d], lim1); + i2 = i1 - d; + if (lim2 < i2) + i1 = lim2 + d, i2 = lim2; + if (fbest < i1 + i2) { + fbest = i1 + i2; + fbest1 = i1; + } + } + + bbest = bbest1 = XDL_LINE_MAX; + for (d = bmax; d >= bmin; d -= 2) { + i1 = XDL_MAX(off1, kvdb[d]); + i2 = i1 - d; + if (i2 < off2) + i1 = off2 + d, i2 = off2; + if (i1 + i2 < bbest) { + bbest = i1 + i2; + bbest1 = i1; + } + } + + if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { + spl->i1 = fbest1; + spl->i2 = fbest - fbest1; + spl->min_lo = 1; + spl->min_hi = 0; + } else { + spl->i1 = bbest1; + spl->i2 = bbest - bbest1; + spl->min_lo = 0; + spl->min_hi = 1; + } + return ec; + } + } +} + + +/* + * Rule: "Divide et Impera" (divide & conquer). Recursively split the box in + * sub-boxes by calling the box splitting function. Note that the real job + * (marking changed lines) is done in the two boundary reaching checks. + */ +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { + unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; + + /* + * Shrink the box by walking through each diagonal snake (SW and NE). + */ + for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); + for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); + + /* + * If one dimension is empty, then all records on the other one must + * be obviously changed. + */ + if (off1 == lim1) { + char *rchg2 = dd2->rchg; + long *rindex2 = dd2->rindex; + + for (; off2 < lim2; off2++) + rchg2[rindex2[off2]] = 1; + } else if (off2 == lim2) { + char *rchg1 = dd1->rchg; + long *rindex1 = dd1->rindex; + + for (; off1 < lim1; off1++) + rchg1[rindex1[off1]] = 1; + } else { + xdpsplit_t spl; + spl.i1 = spl.i2 = 0; + + /* + * Divide ... + */ + if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, + need_min, &spl, xenv) < 0) { + + return -1; + } + + /* + * ... et Impera. + */ + if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, + kvdf, kvdb, spl.min_lo, xenv) < 0 || + xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, + kvdf, kvdb, spl.min_hi, xenv) < 0) { + + return -1; + } + } + + return 0; +} + + +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long ndiags; + long *kvd, *kvdf, *kvdb; + xdalgoenv_t xenv; + diffdata_t dd1, dd2; + + if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + + if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF) + return xdl_do_histogram_diff(mf1, mf2, xpp, xe); + + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { + + return -1; + } + + /* + * Allocate and setup K vectors to be used by the differential + * algorithm. + * + * One is to store the forward path and one to store the backward path. + */ + ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; + if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + + xdl_free_env(xe); + return -1; + } + kvdf = kvd; + kvdb = kvdf + ndiags; + kvdf += xe->xdf2.nreff + 1; + kvdb += xe->xdf2.nreff + 1; + + xenv.mxcost = xdl_bogosqrt(ndiags); + if (xenv.mxcost < XDL_MAX_COST_MIN) + xenv.mxcost = XDL_MAX_COST_MIN; + xenv.snake_cnt = XDL_SNAKE_CNT; + xenv.heur_min = XDL_HEUR_MIN_COST; + + dd1.nrec = xe->xdf1.nreff; + dd1.ha = xe->xdf1.ha; + dd1.rchg = xe->xdf1.rchg; + dd1.rindex = xe->xdf1.rindex; + dd2.nrec = xe->xdf2.nreff; + dd2.ha = xe->xdf2.ha; + dd2.rchg = xe->xdf2.rchg; + dd2.rindex = xe->xdf2.rindex; + + if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, + kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { + + xdl_free(kvd); + xdl_free_env(xe); + return -1; + } + + xdl_free(kvd); + + return 0; +} + + +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { + xdchange_t *xch; + + if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) + return NULL; + + xch->next = xscr; + xch->i1 = i1; + xch->i2 = i2; + xch->chg1 = chg1; + xch->chg2 = chg2; + xch->ignore = 0; + + return xch; +} + + +static int recs_match(xrecord_t *rec1, xrecord_t *rec2) +{ + return (rec1->ha == rec2->ha); +} + +/* + * If a line is indented more than this, get_indent() just returns this value. + * This avoids having to do absurd amounts of work for data that are not + * human-readable text, and also ensures that the output of get_indent fits + * within an int. + */ +#define MAX_INDENT 200 + +/* + * Return the amount of indentation of the specified line, treating TAB as 8 + * columns. Return -1 if line is empty or contains only whitespace. Clamp the + * output value at MAX_INDENT. + */ +static int get_indent(xrecord_t *rec) +{ + long i; + int ret = 0; + + for (i = 0; i < rec->size; i++) { + char c = rec->ptr[i]; + + if (!XDL_ISSPACE(c)) + return ret; + else if (c == ' ') + ret += 1; + else if (c == '\t') + ret += 8 - ret % 8; + /* ignore other whitespace characters */ + + if (ret >= MAX_INDENT) + return MAX_INDENT; + } + + /* The line contains only whitespace. */ + return -1; +} + +/* + * If more than this number of consecutive blank rows are found, just return + * this value. This avoids requiring O(N^2) work for pathological cases, and + * also ensures that the output of score_split fits in an int. + */ +#define MAX_BLANKS 20 + +/* Characteristics measured about a hypothetical split position. */ +struct split_measurement { + /* + * Is the split at the end of the file (aside from any blank lines)? + */ + int end_of_file; + + /* + * How much is the line immediately following the split indented (or -1 + * if the line is blank): + */ + int indent; + + /* + * How many consecutive lines above the split are blank? + */ + int pre_blank; + + /* + * How much is the nearest non-blank line above the split indented (or + * -1 if there is no such line)? + */ + int pre_indent; + + /* + * How many lines after the line following the split are blank? + */ + int post_blank; + + /* + * How much is the nearest non-blank line after the line following the + * split indented (or -1 if there is no such line)? + */ + int post_indent; +}; + +struct split_score { + /* The effective indent of this split (smaller is preferred). */ + int effective_indent; + + /* Penalty for this split (smaller is preferred). */ + int penalty; +}; + +/* + * Fill m with information about a hypothetical split of xdf above line split. + */ +static void measure_split(const xdfile_t *xdf, long split, + struct split_measurement *m) +{ + long i; + + if (split >= xdf->nrec) { + m->end_of_file = 1; + m->indent = -1; + } else { + m->end_of_file = 0; + m->indent = get_indent(xdf->recs[split]); + } + + m->pre_blank = 0; + m->pre_indent = -1; + for (i = split - 1; i >= 0; i--) { + m->pre_indent = get_indent(xdf->recs[i]); + if (m->pre_indent != -1) + break; + m->pre_blank += 1; + if (m->pre_blank == MAX_BLANKS) { + m->pre_indent = 0; + break; + } + } + + m->post_blank = 0; + m->post_indent = -1; + for (i = split + 1; i < xdf->nrec; i++) { + m->post_indent = get_indent(xdf->recs[i]); + if (m->post_indent != -1) + break; + m->post_blank += 1; + if (m->post_blank == MAX_BLANKS) { + m->post_indent = 0; + break; + } + } +} + +/* + * The empirically-determined weight factors used by score_split() below. + * Larger values means that the position is a less favorable place to split. + * + * Note that scores are only ever compared against each other, so multiplying + * all of these weight/penalty values by the same factor wouldn't change the + * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*. + * In practice, these numbers are chosen to be large enough that they can be + * adjusted relative to each other with sufficient precision despite using + * integer math. + */ + +/* Penalty if there are no non-blank lines before the split */ +#define START_OF_FILE_PENALTY 1 + +/* Penalty if there are no non-blank lines after the split */ +#define END_OF_FILE_PENALTY 21 + +/* Multiplier for the number of blank lines around the split */ +#define TOTAL_BLANK_WEIGHT (-30) + +/* Multiplier for the number of blank lines after the split */ +#define POST_BLANK_WEIGHT 6 + +/* + * Penalties applied if the line is indented more than its predecessor + */ +#define RELATIVE_INDENT_PENALTY (-4) +#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10 + +/* + * Penalties applied if the line is indented less than both its predecessor and + * its successor + */ +#define RELATIVE_OUTDENT_PENALTY 24 +#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17 + +/* + * Penalties applied if the line is indented less than its predecessor but not + * less than its successor + */ +#define RELATIVE_DEDENT_PENALTY 23 +#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17 + +/* + * We only consider whether the sum of the effective indents for splits are + * less than (-1), equal to (0), or greater than (+1) each other. The resulting + * value is multiplied by the following weight and combined with the penalty to + * determine the better of two scores. + */ +#define INDENT_WEIGHT 60 + +/* + * How far do we slide a hunk at most? + */ +#define INDENT_HEURISTIC_MAX_SLIDING 100 + +/* + * Compute a badness score for the hypothetical split whose measurements are + * stored in m. The weight factors were determined empirically using the tools + * and corpus described in + * + * https://github.com/mhagger/diff-slider-tools + * + * Also see that project if you want to improve the weights based on, for + * example, a larger or more diverse corpus. + */ +static void score_add_split(const struct split_measurement *m, struct split_score *s) +{ + /* + * A place to accumulate penalty factors (positive makes this index more + * favored): + */ + int post_blank, total_blank, indent, any_blanks; + + if (m->pre_indent == -1 && m->pre_blank == 0) + s->penalty += START_OF_FILE_PENALTY; + + if (m->end_of_file) + s->penalty += END_OF_FILE_PENALTY; + + /* + * Set post_blank to the number of blank lines following the split, + * including the line immediately after the split: + */ + post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; + total_blank = m->pre_blank + post_blank; + + /* Penalties based on nearby blank lines: */ + s->penalty += TOTAL_BLANK_WEIGHT * total_blank; + s->penalty += POST_BLANK_WEIGHT * post_blank; + + if (m->indent != -1) + indent = m->indent; + else + indent = m->post_indent; + + any_blanks = (total_blank != 0); + + /* Note that the effective indent is -1 at the end of the file: */ + s->effective_indent += indent; + + if (indent == -1) { + /* No additional adjustments needed. */ + } else if (m->pre_indent == -1) { + /* No additional adjustments needed. */ + } else if (indent > m->pre_indent) { + /* + * The line is indented more than its predecessor. + */ + s->penalty += any_blanks ? + RELATIVE_INDENT_WITH_BLANK_PENALTY : + RELATIVE_INDENT_PENALTY; + } else if (indent == m->pre_indent) { + /* + * The line has the same indentation level as its predecessor. + * No additional adjustments needed. + */ + } else { + /* + * The line is indented less than its predecessor. It could be + * the block terminator of the previous block, but it could + * also be the start of a new block (e.g., an "else" block, or + * maybe the previous block didn't have a block terminator). + * Try to distinguish those cases based on what comes next: + */ + if (m->post_indent != -1 && m->post_indent > indent) { + /* + * The following line is indented more. So it is likely + * that this line is the start of a block. + */ + s->penalty += any_blanks ? + RELATIVE_OUTDENT_WITH_BLANK_PENALTY : + RELATIVE_OUTDENT_PENALTY; + } else { + /* + * That was probably the end of a block. + */ + s->penalty += any_blanks ? + RELATIVE_DEDENT_WITH_BLANK_PENALTY : + RELATIVE_DEDENT_PENALTY; + } + } +} + +static int score_cmp(struct split_score *s1, struct split_score *s2) +{ + /* -1 if s1.effective_indent < s2->effective_indent, etc. */ + int cmp_indents = ((s1->effective_indent > s2->effective_indent) - + (s1->effective_indent < s2->effective_indent)); + + return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty); +} + +/* + * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group + * of lines that was inserted or deleted from the corresponding version of the + * file). We consider there to be such a group at the beginning of the file, at + * the end of the file, and between any two unchanged lines, though most such + * groups will usually be empty. + * + * If the first line in a group is equal to the line following the group, then + * the group can be slid down. Similarly, if the last line in a group is equal + * to the line preceding the group, then the group can be slid up. See + * group_slide_down() and group_slide_up(). + * + * Note that loops that are testing for changed lines in xdf->rchg do not need + * index bounding since the array is prepared with a zero at position -1 and N. + */ +struct xdlgroup { + /* + * The index of the first changed line in the group, or the index of + * the unchanged line above which the (empty) group is located. + */ + long start; + + /* + * The index of the first unchanged line after the group. For an empty + * group, end is equal to start. + */ + long end; +}; + +/* + * Initialize g to point at the first group in xdf. + */ +static void group_init(xdfile_t *xdf, struct xdlgroup *g) +{ + g->start = g->end = 0; + while (xdf->rchg[g->end]) + g->end++; +} + +/* + * Move g to describe the next (possibly empty) group in xdf and return 0. If g + * is already at the end of the file, do nothing and return -1. + */ +static inline int group_next(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->end == xdf->nrec) + return -1; + + g->start = g->end + 1; + for (g->end = g->start; xdf->rchg[g->end]; g->end++) + ; + + return 0; +} + +/* + * Move g to describe the previous (possibly empty) group in xdf and return 0. + * If g is already at the beginning of the file, do nothing and return -1. + */ +static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->start == 0) + return -1; + + g->end = g->start - 1; + for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--) + ; + + return 0; +} + +/* + * If g can be slid toward the end of the file, do so, and if it bumps into a + * following group, expand this group to include it. Return 0 on success or -1 + * if g cannot be slid down. + */ +static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->end < xdf->nrec && + recs_match(xdf->recs[g->start], xdf->recs[g->end])) { + xdf->rchg[g->start++] = 0; + xdf->rchg[g->end++] = 1; + + while (xdf->rchg[g->end]) + g->end++; + + return 0; + } else { + return -1; + } +} + +/* + * If g can be slid toward the beginning of the file, do so, and if it bumps + * into a previous group, expand this group to include it. Return 0 on success + * or -1 if g cannot be slid up. + */ +static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->start > 0 && + recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) { + xdf->rchg[--g->start] = 1; + xdf->rchg[--g->end] = 0; + + while (xdf->rchg[g->start - 1]) + g->start--; + + return 0; + } else { + return -1; + } +} + +/* + * Move back and forward change groups for a consistent and pretty diff output. + * This also helps in finding joinable change groups and reducing the diff + * size. + */ +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + struct xdlgroup g, go; + long earliest_end, end_matching_other; + long groupsize; + + group_init(xdf, &g); + group_init(xdfo, &go); + + while (1) { + /* + * If the group is empty in the to-be-compacted file, skip it: + */ + if (g.end == g.start) + goto next; + + /* + * Now shift the change up and then down as far as possible in + * each direction. If it bumps into any other changes, merge + * them. + */ + do { + groupsize = g.end - g.start; + + /* + * Keep track of the last "end" index that causes this + * group to align with a group of changed lines in the + * other file. -1 indicates that we haven't found such + * a match yet: + */ + end_matching_other = -1; + + /* Shift the group backward as much as possible: */ + while (!group_slide_up(xdf, &g)) + if (group_previous(xdfo, &go)) + XDL_BUG("group sync broken sliding up"); + + /* + * This is this highest that this group can be shifted. + * Record its end index: + */ + earliest_end = g.end; + + if (go.end > go.start) + end_matching_other = g.end; + + /* Now shift the group forward as far as possible: */ + while (1) { + if (group_slide_down(xdf, &g)) + break; + if (group_next(xdfo, &go)) + XDL_BUG("group sync broken sliding down"); + + if (go.end > go.start) + end_matching_other = g.end; + } + } while (groupsize != g.end - g.start); + + /* + * If the group can be shifted, then we can possibly use this + * freedom to produce a more intuitive diff. + * + * The group is currently shifted as far down as possible, so + * the heuristics below only have to handle upwards shifts. + */ + + if (g.end == earliest_end) { + /* no shifting was possible */ + } else if (end_matching_other != -1) { + /* + * Move the possibly merged group of changes back to + * line up with the last group of changes from the + * other file that it can align with. + */ + while (go.end == go.start) { + if (group_slide_up(xdf, &g)) + XDL_BUG("match disappeared"); + if (group_previous(xdfo, &go)) + XDL_BUG("group sync broken sliding to match"); + } + } else if (flags & XDF_INDENT_HEURISTIC) { + /* + * Indent heuristic: a group of pure add/delete lines + * implies two splits, one between the end of the + * "before" context and the start of the group, and + * another between the end of the group and the + * beginning of the "after" context. Some splits are + * aesthetically better and some are worse. We compute + * a badness "score" for each split, and add the scores + * for the two splits to define a "score" for each + * position that the group can be shifted to. Then we + * pick the shift with the lowest score. + */ + long shift, best_shift = -1; + struct split_score best_score; + + shift = earliest_end; + if (g.end - groupsize - 1 > shift) + shift = g.end - groupsize - 1; + if (g.end - INDENT_HEURISTIC_MAX_SLIDING > shift) + shift = g.end - INDENT_HEURISTIC_MAX_SLIDING; + for (; shift <= g.end; shift++) { + struct split_measurement m; + struct split_score score = {0, 0}; + + measure_split(xdf, shift, &m); + score_add_split(&m, &score); + measure_split(xdf, shift - groupsize, &m); + score_add_split(&m, &score); + if (best_shift == -1 || + score_cmp(&score, &best_score) <= 0) { + best_score.effective_indent = score.effective_indent; + best_score.penalty = score.penalty; + best_shift = shift; + } + } + + while (g.end > best_shift) { + if (group_slide_up(xdf, &g)) + XDL_BUG("best shift unreached"); + if (group_previous(xdfo, &go)) + XDL_BUG("group sync broken sliding to blank line"); + } + } + + next: + /* Move past the just-processed group: */ + if (group_next(xdf, &g)) + break; + if (group_next(xdfo, &go)) + XDL_BUG("group sync broken moving to next group"); + } + + if (!group_next(xdfo, &go)) + XDL_BUG("group sync broken at end of file"); + + return 0; +} + + +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { + xdchange_t *cscr = NULL, *xch; + char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; + long i1, i2, l1, l2; + + /* + * Trivial. Collects "groups" of changes and creates an edit script. + */ + for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) + if (rchg1[i1 - 1] || rchg2[i2 - 1]) { + for (l1 = i1; rchg1[i1 - 1]; i1--); + for (l2 = i2; rchg2[i2 - 1]; i2--); + + if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { + xdl_free_script(cscr); + return -1; + } + cscr = xch; + } + + *xscr = cscr; + + return 0; +} + + +void xdl_free_script(xdchange_t *xscr) { + xdchange_t *xch; + + while ((xch = xscr) != NULL) { + xscr = xscr->next; + xdl_free(xch); + } +} + +static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) +{ + xdchange_t *xch, *xche; + + for (xch = xscr; xch; xch = xche->next) { + xche = xdl_get_hunk(&xch, xecfg); + if (!xch) + break; + if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1, + xch->i2, xche->i2 + xche->chg2 - xch->i2, + ecb->priv) < 0) + return -1; + } + return 0; +} + +static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags) +{ + xdchange_t *xch; + + for (xch = xscr; xch; xch = xch->next) { + int ignore = 1; + xrecord_t **rec; + long i; + + rec = &xe->xdf1.recs[xch->i1]; + for (i = 0; i < xch->chg1 && ignore; i++) + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); + + rec = &xe->xdf2.recs[xch->i2]; + for (i = 0; i < xch->chg2 && ignore; i++) + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); + + xch->ignore = ignore; + } +} + +static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) { + xdl_regmatch_t regmatch; + int i; + + for (i = 0; i < xpp->ignore_regex_nr; i++) + if (!xdl_regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1, + ®match, 0)) + return 1; + + return 0; +} + +static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe, + xpparam_t const *xpp) +{ + xdchange_t *xch; + + for (xch = xscr; xch; xch = xch->next) { + xrecord_t **rec; + int ignore = 1; + long i; + + /* + * Do not override --ignore-blank-lines. + */ + if (xch->ignore) + continue; + + rec = &xe->xdf1.recs[xch->i1]; + for (i = 0; i < xch->chg1 && ignore; i++) + ignore = record_matches_regex(rec[i], xpp); + + rec = &xe->xdf2.recs[xch->i2]; + for (i = 0; i < xch->chg2 && ignore; i++) + ignore = record_matches_regex(rec[i], xpp); + + xch->ignore = ignore; + } +} + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb) { + xdchange_t *xscr; + xdfenv_t xe; + emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff; + + if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { + + return -1; + } + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + + xdl_free_env(&xe); + return -1; + } + if (xscr) { + if (xpp->flags & XDF_IGNORE_BLANK_LINES) + xdl_mark_ignorable_lines(xscr, &xe, xpp->flags); + + if (xpp->ignore_regex) + xdl_mark_ignorable_regex(xscr, &xe, xpp); + + if (ef(&xe, xscr, ecb, xecfg) < 0) { + + xdl_free_script(xscr); + xdl_free_env(&xe); + return -1; + } + xdl_free_script(xscr); + } + xdl_free_env(&xe); + + return 0; +} diff --git a/src/libgit2/xdiff/xdiffi.h b/src/libgit2/xdiff/xdiffi.h new file mode 100644 index 000000000..8f1c7c8b0 --- /dev/null +++ b/src/libgit2/xdiff/xdiffi.h @@ -0,0 +1,64 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XDIFFI_H) +#define XDIFFI_H + + +typedef struct s_diffdata { + long nrec; + unsigned long const *ha; + long *rindex; + char *rchg; +} diffdata_t; + +typedef struct s_xdalgoenv { + long mxcost; + long snake_cnt; + long heur_min; +} xdalgoenv_t; + +typedef struct s_xdchange { + struct s_xdchange *next; + long i1, i2; + long chg1, chg2; + int ignore; +} xdchange_t; + + + +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); +void xdl_free_script(xdchange_t *xscr); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); +int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); +int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); + +#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/libgit2/xdiff/xemit.c b/src/libgit2/xdiff/xemit.c new file mode 100644 index 000000000..1cbf2b982 --- /dev/null +++ b/src/libgit2/xdiff/xemit.c @@ -0,0 +1,330 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { + + *rec = xdf->recs[ri]->ptr; + + return xdf->recs[ri]->size; +} + + +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { + long size, psize = strlen(pre); + char const *rec; + + size = xdl_get_rec(xdf, ri, &rec); + if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { + + return -1; + } + + return 0; +} + + +/* + * Starting at the passed change atom, find the latest change atom to be included + * inside the differential hunk according to the specified configuration. + * Also advance xscr if the first changes must be discarded. + */ +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) +{ + xdchange_t *xch, *xchp, *lxch; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + long max_ignorable = xecfg->ctxlen; + unsigned long ignored = 0; /* number of ignored blank lines */ + + /* remove ignorable changes that are too far before other changes */ + for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { + xch = xchp->next; + + if (xch == NULL || + xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable) + *xscr = xch; + } + + if (*xscr == NULL) + return NULL; + + lxch = *xscr; + + for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) { + long distance = xch->i1 - (xchp->i1 + xchp->chg1); + if (distance > max_common) + break; + + if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) { + lxch = xch; + ignored = 0; + } else if (distance < max_ignorable && xch->ignore) { + ignored += xch->chg2; + } else if (lxch != xchp && + xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) { + break; + } else if (!xch->ignore) { + lxch = xch; + ignored = 0; + } else { + ignored += xch->chg2; + } + } + + return lxch; +} + + +static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) +{ + if (len > 0 && + (isalpha((unsigned char)*rec) || /* identifier? */ + *rec == '_' || /* also identifier? */ + *rec == '$')) { /* identifiers from VMS and other esoterico */ + if (len > sz) + len = sz; + while (0 < len && isspace((unsigned char)rec[len - 1])) + len--; + memcpy(buf, rec, len); + return len; + } + return -1; +} + +static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, + char *buf, long sz) +{ + const char *rec; + long len = xdl_get_rec(xdf, ri, &rec); + if (!xecfg->find_func) + return def_ff(rec, len, buf, sz, xecfg->find_func_priv); + return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv); +} + +static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) +{ + char dummy[1]; + return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0; +} + +struct func_line { + long len; + char buf[80]; +}; + +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, + struct func_line *func_line, long start, long limit) +{ + long l, size, step = (start > limit) ? -1 : 1; + char *buf, dummy[1]; + + buf = func_line ? func_line->buf : dummy; + size = func_line ? sizeof(func_line->buf) : sizeof(dummy); + + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size); + if (len >= 0) { + if (func_line) + func_line->len = len; + return l; + } + } + return -1; +} + +static int is_empty_rec(xdfile_t *xdf, long ri) +{ + const char *rec; + long len = xdl_get_rec(xdf, ri, &rec); + + while (len > 0 && XDL_ISSPACE(*rec)) { + rec++; + len--; + } + return !len; +} + +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + long s1, s2, e1, e2, lctx; + xdchange_t *xch, *xche; + long funclineprev = -1; + struct func_line func_line = { 0 }; + + for (xch = xscr; xch; xch = xche->next) { + xdchange_t *xchp = xch; + xche = xdl_get_hunk(&xch, xecfg); + if (!xch) + break; + +pre_context_calculation: + s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); + s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fs1, i1 = xch->i1; + + /* Appended chunk? */ + if (i1 >= xe->xdf1.nrec) { + long i2 = xch->i2; + + /* + * We don't need additional context if + * a whole function was added. + */ + while (i2 < xe->xdf2.nrec) { + if (is_func_rec(&xe->xdf2, xecfg, i2)) + goto post_context_calculation; + i2++; + } + + /* + * Otherwise get more context from the + * pre-image. + */ + i1 = xe->xdf1.nrec - 1; + } + + fs1 = get_func_line(xe, xecfg, NULL, i1, -1); + while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) && + !is_func_rec(&xe->xdf1, xecfg, fs1 - 1)) + fs1--; + if (fs1 < 0) + fs1 = 0; + if (fs1 < s1) { + s2 = XDL_MAX(s2 - (s1 - fs1), 0); + s1 = fs1; + + /* + * Did we extend context upwards into an + * ignored change? + */ + while (xchp != xch && + xchp->i1 + xchp->chg1 <= s1 && + xchp->i2 + xchp->chg2 <= s2) + xchp = xchp->next; + + /* If so, show it after all. */ + if (xchp != xch) { + xch = xchp; + goto pre_context_calculation; + } + } + } + + post_context_calculation: + lctx = xecfg->ctxlen; + lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); + lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); + + e1 = xche->i1 + xche->chg1 + lctx; + e2 = xche->i2 + xche->chg2 + lctx; + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fe1 = get_func_line(xe, xecfg, NULL, + xche->i1 + xche->chg1, + xe->xdf1.nrec); + while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1)) + fe1--; + if (fe1 < 0) + fe1 = xe->xdf1.nrec; + if (fe1 > e1) { + e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec); + e1 = fe1; + } + + /* + * Overlap with next change? Then include it + * in the current hunk and start over to find + * its new end. + */ + if (xche->next) { + long l = XDL_MIN(xche->next->i1, + xe->xdf1.nrec - 1); + if (l - xecfg->ctxlen <= e1 || + get_func_line(xe, xecfg, NULL, l, e1) < 0) { + xche = xche->next; + goto post_context_calculation; + } + } + } + + /* + * Emit current hunk header. + */ + + if (xecfg->flags & XDL_EMIT_FUNCNAMES) { + get_func_line(xe, xecfg, &func_line, + s1 - 1, funclineprev); + funclineprev = s1 - 1; + } + if (!(xecfg->flags & XDL_EMIT_NO_HUNK_HDR) && + xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, + func_line.buf, func_line.len, ecb) < 0) + return -1; + + /* + * Emit pre-context. + */ + for (; s2 < xch->i2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { + /* + * Merge previous with current change atom. + */ + for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + /* + * Removes lines from the first file. + */ + for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) + if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) + return -1; + + /* + * Adds lines from the second file. + */ + for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) + return -1; + + if (xch == xche) + break; + s1 = xch->i1 + xch->chg1; + s2 = xch->i2 + xch->chg2; + } + + /* + * Emit post-context. + */ + for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + } + + return 0; +} diff --git a/src/libgit2/xdiff/xemit.h b/src/libgit2/xdiff/xemit.h new file mode 100644 index 000000000..1b9887e67 --- /dev/null +++ b/src/libgit2/xdiff/xemit.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XEMIT_H) +#define XEMIT_H + + +typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + + + +#endif /* #if !defined(XEMIT_H) */ diff --git a/src/libgit2/xdiff/xhistogram.c b/src/libgit2/xdiff/xhistogram.c new file mode 100644 index 000000000..80794748b --- /dev/null +++ b/src/libgit2/xdiff/xhistogram.c @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in JGit's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * 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. + * + * - Neither the name of the Eclipse Foundation, Inc. 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 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. + */ + +#include "xinclude.h" + +#define MAX_PTR UINT_MAX +#define MAX_CNT UINT_MAX + +#define LINE_END(n) (line##n + count##n - 1) +#define LINE_END_PTR(n) (*line##n + *count##n - 1) + +struct histindex { + struct record { + unsigned int ptr, cnt; + struct record *next; + } **records, /* an occurrence */ + **line_map; /* map of line to record chain */ + chastore_t rcha; + unsigned int *next_ptrs; + unsigned int table_bits, + records_size, + line_map_size; + + unsigned int max_chain_length, + key_shift, + ptr_shift; + + unsigned int cnt, + has_common; + + xdfenv_t *env; + xpparam_t const *xpp; +}; + +struct region { + unsigned int begin1, end1; + unsigned int begin2, end2; +}; + +#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) + +#define NEXT_PTR(index, ptr) \ + (index->next_ptrs[(ptr) - index->ptr_shift]) + +#define CNT(index, ptr) \ + ((LINE_MAP(index, ptr))->cnt) + +#define REC(env, s, l) \ + (env->xdf##s.recs[l - 1]) + +static int cmp_recs(xrecord_t *r1, xrecord_t *r2) +{ + return r1->ha == r2->ha; + +} + +#define CMP(i, s1, l1, s2, l2) \ + (cmp_recs(REC(i->env, s1, l1), REC(i->env, s2, l2))) + +#define TABLE_HASH(index, side, line) \ + XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) + +static int scanA(struct histindex *index, int line1, int count1) +{ + unsigned int ptr, tbl_idx; + unsigned int chain_len; + struct record **rec_chain, *rec; + + for (ptr = LINE_END(1); line1 <= ptr; ptr--) { + tbl_idx = TABLE_HASH(index, 1, ptr); + rec_chain = index->records + tbl_idx; + rec = *rec_chain; + + chain_len = 0; + while (rec) { + if (CMP(index, 1, rec->ptr, 1, ptr)) { + /* + * ptr is identical to another element. Insert + * it onto the front of the existing element + * chain. + */ + NEXT_PTR(index, ptr) = rec->ptr; + rec->ptr = ptr; + /* cap rec->cnt at MAX_CNT */ + rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); + LINE_MAP(index, ptr) = rec; + goto continue_scan; + } + + rec = rec->next; + chain_len++; + } + + if (chain_len == index->max_chain_length) + return -1; + + /* + * This is the first time we have ever seen this particular + * element in the sequence. Construct a new chain for it. + */ + if (!(rec = xdl_cha_alloc(&index->rcha))) + return -1; + rec->ptr = ptr; + rec->cnt = 1; + rec->next = *rec_chain; + *rec_chain = rec; + LINE_MAP(index, ptr) = rec; + +continue_scan: + ; /* no op */ + } + + return 0; +} + +static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr, + int line1, int count1, int line2, int count2) +{ + unsigned int b_next = b_ptr + 1; + struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; + unsigned int as, ae, bs, be, np, rc; + int should_break; + + for (; rec; rec = rec->next) { + if (rec->cnt > index->cnt) { + if (!index->has_common) + index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); + continue; + } + + as = rec->ptr; + if (!CMP(index, 1, as, 2, b_ptr)) + continue; + + index->has_common = 1; + for (;;) { + should_break = 0; + np = NEXT_PTR(index, as); + bs = b_ptr; + ae = as; + be = bs; + rc = rec->cnt; + + while (line1 < as && line2 < bs + && CMP(index, 1, as - 1, 2, bs - 1)) { + as--; + bs--; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, as)); + } + while (ae < LINE_END(1) && be < LINE_END(2) + && CMP(index, 1, ae + 1, 2, be + 1)) { + ae++; + be++; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, ae)); + } + + if (b_next <= be) + b_next = be + 1; + if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { + lcs->begin1 = as; + lcs->begin2 = bs; + lcs->end1 = ae; + lcs->end2 = be; + index->cnt = rc; + } + + if (np == 0) + break; + + while (np <= ae) { + np = NEXT_PTR(index, np); + if (np == 0) { + should_break = 1; + break; + } + } + + if (should_break) + break; + + as = np; + } + } + return b_next; +} + +static int fall_back_to_classic_diff(xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpparam; + + memset(&xpparam, 0, sizeof(xpparam)); + xpparam.flags = xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; + + return xdl_fall_back_diff(env, &xpparam, + line1, count1, line2, count2); +} + +static inline void free_index(struct histindex *index) +{ + xdl_free(index->records); + xdl_free(index->line_map); + xdl_free(index->next_ptrs); + xdl_cha_free(&index->rcha); +} + +static int find_lcs(xpparam_t const *xpp, xdfenv_t *env, + struct region *lcs, + int line1, int count1, int line2, int count2) +{ + int b_ptr; + int sz, ret = -1; + struct histindex index; + + memset(&index, 0, sizeof(index)); + + index.env = env; + index.xpp = xpp; + + index.records = NULL; + index.line_map = NULL; + /* in case of early xdl_cha_free() */ + index.rcha.head = NULL; + + index.table_bits = xdl_hashbits(count1); + sz = index.records_size = 1 << index.table_bits; + sz *= sizeof(struct record *); + if (!(index.records = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.records, 0, sz); + + sz = index.line_map_size = count1; + sz *= sizeof(struct record *); + if (!(index.line_map = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.line_map, 0, sz); + + sz = index.line_map_size; + sz *= sizeof(unsigned int); + if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) + goto cleanup; + memset(index.next_ptrs, 0, sz); + + /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ + if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) + goto cleanup; + + index.ptr_shift = line1; + index.max_chain_length = 64; + + if (scanA(&index, line1, count1)) + goto cleanup; + + index.cnt = index.max_chain_length + 1; + + for (b_ptr = line2; b_ptr <= LINE_END(2); ) + b_ptr = try_lcs(&index, lcs, b_ptr, line1, count1, line2, count2); + + if (index.has_common && index.max_chain_length < index.cnt) + ret = 1; + else + ret = 0; + +cleanup: + free_index(&index); + return ret; +} + +static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct region lcs; + int lcs_found; + int result; +redo: + result = -1; + + if (count1 <= 0 && count2 <= 0) + return 0; + + if (LINE_END(1) >= MAX_PTR) + return -1; + + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&lcs, 0, sizeof(lcs)); + lcs_found = find_lcs(xpp, env, &lcs, line1, count1, line2, count2); + if (lcs_found < 0) + goto out; + else if (lcs_found) + result = fall_back_to_classic_diff(xpp, env, line1, count1, line2, count2); + else { + if (lcs.begin1 == 0 && lcs.begin2 == 0) { + while (count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while (count2--) + env->xdf2.rchg[line2++ - 1] = 1; + result = 0; + } else { + result = histogram_diff(xpp, env, + line1, lcs.begin1 - line1, + line2, lcs.begin2 - line2); + if (result) + goto out; + /* + * result = histogram_diff(xpp, env, + * lcs.end1 + 1, LINE_END(1) - lcs.end1, + * lcs.end2 + 1, LINE_END(2) - lcs.end2); + * but let's optimize tail recursion ourself: + */ + count1 = LINE_END(1) - lcs.end1; + line1 = lcs.end1 + 1; + count2 = LINE_END(2) - lcs.end2; + line2 = lcs.end2 + 1; + goto redo; + } + } +out: + return result; +} + +int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + return histogram_diff(xpp, env, + env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, + env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); +} diff --git a/src/libgit2/xdiff/xinclude.h b/src/libgit2/xdiff/xinclude.h new file mode 100644 index 000000000..75db1d8f3 --- /dev/null +++ b/src/libgit2/xdiff/xinclude.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XINCLUDE_H) +#define XINCLUDE_H + +#include "git-xdiff.h" +#include "xmacros.h" +#include "xdiff.h" +#include "xtypes.h" +#include "xutils.h" +#include "xprepare.h" +#include "xdiffi.h" +#include "xemit.h" + + +#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/libgit2/xdiff/xmacros.h b/src/libgit2/xdiff/xmacros.h new file mode 100644 index 000000000..2809a28ca --- /dev/null +++ b/src/libgit2/xdiff/xmacros.h @@ -0,0 +1,54 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XMACROS_H) +#define XMACROS_H + + + + +#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) +#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) +#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) +#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') +#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) +#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) +#define XDL_MASKBITS(b) ((1UL << (b)) - 1) +#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) +#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) +#define XDL_LE32_PUT(p, v) \ +do { \ + unsigned char *__p = (unsigned char *) (p); \ + *__p++ = (unsigned char) (v); \ + *__p++ = (unsigned char) ((v) >> 8); \ + *__p++ = (unsigned char) ((v) >> 16); \ + *__p = (unsigned char) ((v) >> 24); \ +} while (0) +#define XDL_LE32_GET(p, v) \ +do { \ + unsigned char const *__p = (unsigned char const *) (p); \ + (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ + ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ +} while (0) + + +#endif /* #if !defined(XMACROS_H) */ diff --git a/src/libgit2/xdiff/xmerge.c b/src/libgit2/xdiff/xmerge.c new file mode 100644 index 000000000..433e2d741 --- /dev/null +++ b/src/libgit2/xdiff/xmerge.c @@ -0,0 +1,737 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + * 3 = no conflict, take both. + */ + int mode; + /* + * These point at the respective postimages. E.g. is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ + long i1, i2; + long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + xdl_free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) +{ + xrecord_t **recs; + int size = 0; + + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (needs_cr) { + if (dest) + dest[size] = '\r'; + size++; + } + + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, needs_cr, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, needs_cr, add_nl, dest); +} + +/* + * Returns 1 if the i'th line ends in CR/LF (if it is the last line and + * has no eol, the preceding line, if any), 0 if it ends in LF-only, and + * -1 if the line ending cannot be determined. + */ +static int is_eol_crlf(xdfile_t *file, int i) +{ + long size; + + if (i < file->nrec - 1) + /* All lines before the last *must* end in LF */ + return (size = file->recs[i]->size) > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!file->nrec) + /* Cannot determine eol style from empty file */ + return -1; + if ((size = file->recs[i]->size) && + file->recs[i]->ptr[size - 1] == '\n') + /* Last line; ends in LF; Is it CR/LF? */ + return size > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!i) + /* The only line has no eol */ + return -1; + /* Determine eol from second-to-last line */ + return (size = file->recs[i - 1]->size) > 1 && + file->recs[i - 1]->ptr[size - 2] == '\r'; +} + +static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m) +{ + int needs_cr; + + /* Match post-images' preceding, or first, lines' end-of-line style */ + needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0); + if (needs_cr) + needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0); + /* Look at pre-image's first line, unless we already settled on LF */ + if (needs_cr) + needs_cr = is_eol_crlf(&xe1->xdf1, 0); + /* If still undecided, use LF-only */ + return needs_cr < 0 ? 0 : needs_cr; +} + +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *name3, + int size, int i, int style, + xdmerge_t *m, char *dest, int marker_size) +{ + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int marker3_size = (name3 ? strlen(name3) + 1 : 0); + int needs_cr = is_cr_needed(xe1, xe2, m); + + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + needs_cr + marker1_size; + } else { + memset(dest + size, '<', marker_size); + size += marker_size; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, 1, + dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1 + needs_cr + marker3_size; + } else { + memset(dest + size, '|', marker_size); + size += marker_size; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, needs_cr, 1, + dest ? dest + size : NULL); + } + + if (!dest) { + size += marker_size + 1 + needs_cr; + } else { + memset(dest + size, '=', marker_size); + size += marker_size; + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, needs_cr, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + needs_cr + marker2_size; + } else { + memset(dest + size, '>', marker_size); + size += marker_size; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *ancestor_name, + int favor, + xdmerge_t *m, char *dest, int style, + int marker_size) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, + size, i, style, m, dest, + marker_size); + else if (m->mode & 3) { + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, + dest ? dest + size : NULL); + /* Postimage from side #1 */ + if (m->mode & 1) { + int needs_cr = is_cr_needed(xe1, xe2, m); + + size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, (m->mode & 2), + dest ? dest + size : NULL); + } + /* Postimage from side #2 */ + if (m->mode & 2) + size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, 0, + dest ? dest + size : NULL); + } else + continue; + i = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0, + dest ? dest + size : NULL); + return size; +} + +static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags) +{ + return xdl_recmatch(rec1->ptr, rec1->size, + rec2->ptr, rec2->size, flags); +} + +/* + * Remove any common lines from the beginning and end of the conflicted region. + */ +static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + xrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs; + for (; m; m = m->next) { + /* let's handle just the conflicts */ + if (m->mode) + continue; + + while(m->chg1 && m->chg2 && + recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) { + m->chg1--; + m->chg2--; + m->i1++; + m->i2++; + } + while (m->chg1 && m->chg2 && + recmatch(rec1[m->i1 + m->chg1 - 1], + rec2[m->i2 + m->chg2 - 1], xpp->flags)) { + m->chg1--; + m->chg2--; + } + } +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* no sense refining a conflict when one side is empty */ + if (m->chg1 == 0 || m->chg2 == 0) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, the changes are identical. */ + xdl_free_env(&xe); + m->mode = 4; + continue; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +static int line_contains_alnum(const char *ptr, long size) +{ + while (size--) + if (isalnum((unsigned char)*(ptr++))) + return 1; + return 0; +} + +static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) +{ + for (; chg; chg--, i++) + if (line_contains_alnum(xe->xdf2.recs[i]->ptr, + xe->xdf2.recs[i]->size)) + return 1; + return 0; +} + +/* + * This function merges m and m->next, marking everything between those hunks + * as conflicting, too. + */ +static void xdl_merge_two_conflicts(xdmerge_t *m) +{ + xdmerge_t *next_m = m->next; + m->chg1 = next_m->i1 + next_m->chg1 - m->i1; + m->chg2 = next_m->i2 + next_m->chg2 - m->i2; + m->next = next_m->next; + xdl_free(next_m); +} + +/* + * If there are less than 3 non-conflicting lines between conflicts, + * it appears simpler -- because it takes up less (or as many) lines -- + * if the lines are moved into the conflicts. + */ +static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, + int simplify_if_no_alnum) +{ + int result = 0; + + if (!m) + return result; + for (;;) { + xdmerge_t *next_m = m->next; + int begin, end; + + if (!next_m) + return result; + + begin = m->i1 + m->chg1; + end = next_m->i1; + + if (m->mode != 0 || next_m->mode != 0 || + (end - begin > 3 && + (!simplify_if_no_alnum || + lines_contain_alnum(xe1, begin, end - begin)))) { + m = next_m; + } else { + result++; + xdl_merge_two_conflicts(m); + } + } +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * level == 3: analyze non-identical changes for minimal conflict set, but + * treat hunks not containing any letter or number as conflicting + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdmerge_t *changes, *c; + xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; + int i0, i1, i2, chg0, chg1, chg2; + int level = xmp->level; + int style = xmp->style; + int favor = xmp->favor; + + /* + * XDL_MERGE_DIFF3 does not attempt to refine conflicts by looking + * at common areas of sides 1 & 2, because the base (side 0) does + * not match and is being shown. Similarly, simplification of + * non-conflicts is also skipped due to the skipping of conflict + * refinement. + * + * XDL_MERGE_ZEALOUS_DIFF3, on the other hand, will attempt to + * refine conflicts looking for common areas of sides 1 & 2. + * However, since the base is being shown and does not match, + * it will only look for common areas at the beginning or end + * of the conflict block. Since XDL_MERGE_ZEALOUS_DIFF3's + * conflict refinement is much more limited in this fashion, the + * conflict simplification will be skipped. + */ + if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) { + i0 -= off; + i1 -= off; + } + else + i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo < 0) { + chg0 -= ffo; + chg1 -= ffo; + } else + chg2 += ffo; + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i0 = xscr2->i1; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (style == XDL_MERGE_ZEALOUS_DIFF3) { + xdl_refine_zdiff3_conflicts(xe1, xe2, changes, xpp); + } else if (XDL_MERGE_ZEALOUS <= level && + (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int marker_size = xmp->marker_size; + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, + favor, changes, NULL, style, + marker_size); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, + result->ptr, style, marker_size); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + xpparam_t const *xpp = &xmp->xpp; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) { + return -1; + } + if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_script(xscr1); + xdl_free_env(&xe1); + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, + xmp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} diff --git a/src/libgit2/xdiff/xpatience.c b/src/libgit2/xdiff/xpatience.c new file mode 100644 index 000000000..c5d48e80a --- /dev/null +++ b/src/libgit2/xdiff/xpatience.c @@ -0,0 +1,382 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ +#include "xinclude.h" + +/* + * The basic idea of patience diff is to find lines that are unique in + * both files. These are intuitively the ones that we want to see as + * common lines. + * + * The maximal ordered sequence of such line pairs (where ordered means + * that the order in the sequence agrees with the order of the lines in + * both files) naturally defines an initial set of common lines. + * + * Now, the algorithm tries to extend the set of common lines by growing + * the line ranges where the files have identical lines. + * + * Between those common lines, the patience diff algorithm is applied + * recursively, until no unique line pairs can be found; these line ranges + * are handled by the well-known Myers algorithm. + */ + +#define NON_UNIQUE ULONG_MAX + +/* + * This is a hash mapping from line hash to line numbers in the first and + * second file. + */ +struct hashmap { + int nr, alloc; + struct entry { + unsigned long hash; + /* + * 0 = unused entry, 1 = first line, 2 = second, etc. + * line2 is NON_UNIQUE if the line is not unique + * in either the first or the second file. + */ + unsigned long line1, line2; + /* + * "next" & "previous" are used for the longest common + * sequence; + * initially, "next" reflects only the order in file1. + */ + struct entry *next, *previous; + + /* + * If 1, this entry can serve as an anchor. See + * Documentation/diff-options.txt for more information. + */ + unsigned anchor : 1; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +static int is_anchor(xpparam_t const *xpp, const char *line) +{ + int i; + for (i = 0; i < xpp->anchors_nr; i++) { + if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i]))) + return 1; + } + return 0; +} + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, + int pass) +{ + xrecord_t **records = pass == 1 ? + map->env->xdf1.recs : map->env->xdf2.recs; + xrecord_t *record = records[line - 1]; + /* + * After xdl_prepare_env() (or more precisely, due to + * xdl_classify_record()), the "ha" member of the records (AKA lines) + * is _not_ the hash anymore, but a linearized version of it. In + * other words, the "ha" member is guaranteed to start with 0 and + * the second record's ha can only be 0 or 1, etc. + * + * So we multiply ha by 2 in the hope that the hashing was + * "unique enough". + */ + int index = (int)((record->ha << 1) % map->alloc); + + while (map->entries[index].line1) { + if (map->entries[index].hash != record->ha) { + if (++index >= map->alloc) + index = 0; + continue; + } + if (pass == 2) + map->has_matches = 1; + if (pass == 1 || map->entries[index].line2) + map->entries[index].line2 = NON_UNIQUE; + else + map->entries[index].line2 = line; + return; + } + if (pass == 2) + return; + map->entries[index].line1 = line; + map->entries[index].hash = record->ha; + map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr); + if (!map->first) + map->first = map->entries + index; + if (map->last) { + map->last->next = map->entries + index; + map->entries[index].previous = map->last; + } + map->last = map->entries + index; + map->nr++; +} + +/* + * This function has to be called for each recursion into the inter-hunk + * parts, as previously non-unique lines can become unique when being + * restricted to a smaller part of the files. + * + * It is assumed that env has been prepared using xdl_prepare(). + */ +static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + struct hashmap *result, + int line1, int count1, int line2, int count2) +{ + result->file1 = file1; + result->file2 = file2; + result->xpp = xpp; + result->env = env; + + /* We know exactly how large we want the hash map */ + result->alloc = count1 * 2; + result->entries = (struct entry *) + xdl_malloc(result->alloc * sizeof(struct entry)); + if (!result->entries) + return -1; + memset(result->entries, 0, result->alloc * sizeof(struct entry)); + + /* First, fill with entries from the first file */ + while (count1--) + insert_record(xpp, line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(xpp, line2++, result, 2); + + return 0; +} + +/* + * Find the longest sequence with a smaller last element (meaning a smaller + * line2, as we construct the sequence with entries ordered by line1). + */ +static int binary_search(struct entry **sequence, int longest, + struct entry *entry) +{ + int left = -1, right = longest; + + while (left + 1 < right) { + int middle = left + (right - left) / 2; + /* by construction, no two entries can be equal */ + if (sequence[middle]->line2 > entry->line2) + right = middle; + else + left = middle; + } + /* return the index in "sequence", _not_ the sequence length */ + return left; +} + +/* + * The idea is to start with the list of common unique lines sorted by + * the order in file1. For each of these pairs, the longest (partial) + * sequence whose last element's line2 is smaller is determined. + * + * For efficiency, the sequences are kept in a list containing exactly one + * item per sequence length: the sequence with the smallest last + * element (in terms of line2). + */ +static struct entry *find_longest_common_sequence(struct hashmap *map) +{ + struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); + int longest = 0, i; + struct entry *entry; + + /* + * If not -1, this entry in sequence must never be overridden. + * Therefore, overriding entries before this has no effect, so + * do not do that either. + */ + int anchor_i = -1; + + for (entry = map->first; entry; entry = entry->next) { + if (!entry->line2 || entry->line2 == NON_UNIQUE) + continue; + i = binary_search(sequence, longest, entry); + entry->previous = i < 0 ? NULL : sequence[i]; + ++i; + if (i <= anchor_i) + continue; + sequence[i] = entry; + if (entry->anchor) { + anchor_i = i; + longest = anchor_i + 1; + } else if (i == longest) { + longest++; + } + } + + /* No common unique lines were found */ + if (!longest) { + xdl_free(sequence); + return NULL; + } + + /* Iterate starting at the last element, adjusting the "next" members */ + entry = sequence[longest - 1]; + entry->next = NULL; + while (entry->previous) { + entry->previous->next = entry; + entry = entry->previous; + } + xdl_free(sequence); + return entry; +} + +static int match(struct hashmap *map, int line1, int line2) +{ + xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; + xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; + return record1->ha == record2->ha; +} + +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2); + +static int walk_common_sequence(struct hashmap *map, struct entry *first, + int line1, int count1, int line2, int count2) +{ + int end1 = line1 + count1, end2 = line2 + count2; + int next1, next2; + + for (;;) { + /* Try to grow the line ranges of common lines */ + if (first) { + next1 = first->line1; + next2 = first->line2; + while (next1 > line1 && next2 > line2 && + match(map, next1 - 1, next2 - 1)) { + next1--; + next2--; + } + } else { + next1 = end1; + next2 = end2; + } + while (line1 < next1 && line2 < next2 && + match(map, line1, line2)) { + line1++; + line2++; + } + + /* Recurse */ + if (next1 > line1 || next2 > line2) { + if (patience_diff(map->file1, map->file2, + map->xpp, map->env, + line1, next1 - line1, + line2, next2 - line2)) + return -1; + } + + if (!first) + return 0; + + while (first->next && + first->next->line1 == first->line1 + 1 && + first->next->line2 == first->line2 + 1) + first = first->next; + + line1 = first->line1 + 1; + line2 = first->line2 + 1; + + first = first->next; + } +} + +static int fall_back_to_classic_diff(struct hashmap *map, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + + memset(&xpp, 0, sizeof(xpp)); + xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; + + return xdl_fall_back_diff(map->env, &xpp, + line1, count1, line2, count2); +} + +/* + * Recursively find the longest common sequence of unique lines, + * and if none was found, ask xdl_do_diff() to do the job. + * + * This function assumes that env was prepared with xdl_prepare_env(). + */ +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct hashmap map; + struct entry *first; + int result = 0; + + /* trivial case: one side is empty */ + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&map, 0, sizeof(map)); + if (fill_hashmap(file1, file2, xpp, env, &map, + line1, count1, line2, count2)) + return -1; + + /* are there any matching lines at all? */ + if (!map.has_matches) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + xdl_free(map.entries); + return 0; + } + + first = find_longest_common_sequence(&map); + if (first) + result = walk_common_sequence(&map, first, + line1, count1, line2, count2); + else + result = fall_back_to_classic_diff(&map, + line1, count1, line2, count2); + + xdl_free(map.entries); + return result; +} + +int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + /* environment is cleaned up in xdl_diff() */ + return patience_diff(file1, file2, xpp, env, + 1, env->xdf1.nrec, 1, env->xdf2.nrec); +} diff --git a/src/libgit2/xdiff/xprepare.c b/src/libgit2/xdiff/xprepare.c new file mode 100644 index 000000000..4527a4a07 --- /dev/null +++ b/src/libgit2/xdiff/xprepare.c @@ -0,0 +1,478 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + +#define XDL_KPDIS_RUN 4 +#define XDL_MAX_EQLIMIT 1024 +#define XDL_SIMSCAN_WINDOW 100 +#define XDL_GUESS_NLINES1 256 +#define XDL_GUESS_NLINES2 20 + + +typedef struct s_xdlclass { + struct s_xdlclass *next; + unsigned long ha; + char const *line; + long size; + long idx; + long len1, len2; +} xdlclass_t; + +typedef struct s_xdlclassifier { + unsigned int hbits; + long hsize; + xdlclass_t **rchash; + chastore_t ncha; + xdlclass_t **rcrecs; + long alloc; + long count; + long flags; +} xdlclassifier_t; + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); +static void xdl_free_classifier(xdlclassifier_t *cf); +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec); +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf); +static void xdl_free_ctx(xdfile_t *xdf); +static int xdl_clean_mmatch(char const *dis, long i, long s, long e); +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { + cf->flags = flags; + + cf->hbits = xdl_hashbits((unsigned int) size); + cf->hsize = 1 << cf->hbits; + + if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { + + return -1; + } + if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { + + xdl_cha_free(&cf->ncha); + return -1; + } + memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); + + cf->alloc = size; + if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { + + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); + return -1; + } + + cf->count = 0; + + return 0; +} + + +static void xdl_free_classifier(xdlclassifier_t *cf) { + + xdl_free(cf->rcrecs); + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); +} + + +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec) { + long hi; + char const *line; + xdlclass_t *rcrec; + xdlclass_t **rcrecs; + + line = rec->ptr; + hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); + for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) + if (rcrec->ha == rec->ha && + xdl_recmatch(rcrec->line, rcrec->size, + rec->ptr, rec->size, cf->flags)) + break; + + if (!rcrec) { + if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { + + return -1; + } + rcrec->idx = cf->count++; + if (cf->count > cf->alloc) { + cf->alloc *= 2; + if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { + + return -1; + } + cf->rcrecs = rcrecs; + } + cf->rcrecs[rcrec->idx] = rcrec; + rcrec->line = line; + rcrec->size = rec->size; + rcrec->ha = rec->ha; + rcrec->len1 = rcrec->len2 = 0; + rcrec->next = cf->rchash[hi]; + cf->rchash[hi] = rcrec; + } + + (pass == 1) ? rcrec->len1++ : rcrec->len2++; + + rec->ha = (unsigned long) rcrec->idx; + + hi = (long) XDL_HASHLONG(rec->ha, hbits); + rec->next = rhash[hi]; + rhash[hi] = rec; + + return 0; +} + + +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf) { + unsigned int hbits; + long nrec, hsize, bsize; + unsigned long hav; + char const *blk, *cur, *top, *prev; + xrecord_t *crec; + xrecord_t **recs, **rrecs; + xrecord_t **rhash; + unsigned long *ha; + char *rchg; + long *rindex; + + ha = NULL; + rindex = NULL; + rchg = NULL; + rhash = NULL; + recs = NULL; + + if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) + goto abort; + if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) + goto abort; + + hbits = xdl_hashbits((unsigned int) narec); + hsize = 1 << hbits; + if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) + goto abort; + memset(rhash, 0, hsize * sizeof(xrecord_t *)); + + nrec = 0; + if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { + for (top = blk + bsize; cur < top; ) { + prev = cur; + hav = xdl_hash_record(&cur, top, xpp->flags); + if (nrec >= narec) { + narec *= 2; + if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) + goto abort; + recs = rrecs; + } + if (!(crec = xdl_cha_alloc(&xdf->rcha))) + goto abort; + crec->ptr = prev; + crec->size = (long) (cur - prev); + crec->ha = hav; + recs[nrec++] = crec; + if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) + goto abort; + } + } + + if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) + goto abort; + memset(rchg, 0, (nrec + 2) * sizeof(char)); + + if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && + (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) { + if (!(rindex = xdl_malloc((nrec + 1) * sizeof(*rindex)))) + goto abort; + if (!(ha = xdl_malloc((nrec + 1) * sizeof(*ha)))) + goto abort; + } + + xdf->nrec = nrec; + xdf->recs = recs; + xdf->hbits = hbits; + xdf->rhash = rhash; + xdf->rchg = rchg + 1; + xdf->rindex = rindex; + xdf->nreff = 0; + xdf->ha = ha; + xdf->dstart = 0; + xdf->dend = nrec - 1; + + return 0; + +abort: + xdl_free(ha); + xdl_free(rindex); + xdl_free(rchg); + xdl_free(rhash); + xdl_free(recs); + xdl_cha_free(&xdf->rcha); + return -1; +} + + +static void xdl_free_ctx(xdfile_t *xdf) { + + xdl_free(xdf->rhash); + xdl_free(xdf->rindex); + xdl_free(xdf->rchg - 1); + xdl_free(xdf->ha); + xdl_free(xdf->recs); + xdl_cha_free(&xdf->rcha); +} + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long enl1, enl2, sample; + xdlclassifier_t cf; + + memset(&cf, 0, sizeof(cf)); + + /* + * For histogram diff, we can afford a smaller sample size and + * thus a poorer estimate of the number of lines, as the hash + * table (rhash) won't be filled up/grown. The number of lines + * (nrecs) will be updated correctly anyway by + * xdl_prepare_ctx(). + */ + sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF + ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1); + + enl1 = xdl_guess_lines(mf1, sample) + 1; + enl2 = xdl_guess_lines(mf2, sample) + 1; + + if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) + return -1; + + if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { + + xdl_free_classifier(&cf); + return -1; + } + if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && + (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) && + xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + xdl_free_classifier(&cf); + + return 0; +} + + +void xdl_free_env(xdfenv_t *xe) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); +} + + +static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { + long r, rdis0, rpdis0, rdis1, rpdis1; + + /* + * Limits the window the is examined during the similar-lines + * scan. The loops below stops when dis[i - r] == 1 (line that + * has no match), but there are corner cases where the loop + * proceed all the way to the extremities by causing huge + * performance penalties in case of big files. + */ + if (i - s > XDL_SIMSCAN_WINDOW) + s = i - XDL_SIMSCAN_WINDOW; + if (e - i > XDL_SIMSCAN_WINDOW) + e = i + XDL_SIMSCAN_WINDOW; + + /* + * Scans the lines before 'i' to find a run of lines that either + * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). + * Note that we always call this function with dis[i] > 1, so the + * current line (i) is already a multimatch line. + */ + for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { + if (!dis[i - r]) + rdis0++; + else if (dis[i - r] == 2) + rpdis0++; + else + break; + } + /* + * If the run before the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + * We want to discard multimatch lines only when they appear in the + * middle of runs with nomatch lines (dis[j] == 0). + */ + if (rdis0 == 0) + return 0; + for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { + if (!dis[i + r]) + rdis1++; + else if (dis[i + r] == 2) + rpdis1++; + else + break; + } + /* + * If the run after the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + */ + if (rdis1 == 0) + return 0; + rdis1 += rdis0; + rpdis1 += rpdis0; + + return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); +} + + +/* + * Try to reduce the problem complexity, discard records that have no + * matches on the other file. Also, lines that have multiple matches + * might be potentially discarded if they happear in a run of discardable. + */ +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + long i, nm, nreff, mlim; + xrecord_t **recs; + xdlclass_t *rcrec; + char *dis, *dis1, *dis2; + + if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { + + return -1; + } + memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); + dis1 = dis; + dis2 = dis1 + xdf1->nrec + 1; + + if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len2 : 0; + dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len1 : 0; + dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; + i <= xdf1->dend; i++, recs++) { + if (dis1[i] == 1 || + (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { + xdf1->rindex[nreff] = i; + xdf1->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf1->rchg[i] = 1; + } + xdf1->nreff = nreff; + + for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; + i <= xdf2->dend; i++, recs++) { + if (dis2[i] == 1 || + (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { + xdf2->rindex[nreff] = i; + xdf2->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf2->rchg[i] = 1; + } + xdf2->nreff = nreff; + + xdl_free(dis); + + return 0; +} + + +/* + * Early trim initial and terminal matching records. + */ +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { + long i, lim; + xrecord_t **recs1, **recs2; + + recs1 = xdf1->recs; + recs2 = xdf2->recs; + for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; + i++, recs1++, recs2++) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dstart = xdf2->dstart = i; + + recs1 = xdf1->recs + xdf1->nrec - 1; + recs2 = xdf2->recs + xdf2->nrec - 1; + for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dend = xdf1->nrec - i - 1; + xdf2->dend = xdf2->nrec - i - 1; + + return 0; +} + + +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + + if (xdl_trim_ends(xdf1, xdf2) < 0 || + xdl_cleanup_records(cf, xdf1, xdf2) < 0) { + + return -1; + } + + return 0; +} diff --git a/src/libgit2/xdiff/xprepare.h b/src/libgit2/xdiff/xprepare.h new file mode 100644 index 000000000..947d9fc1b --- /dev/null +++ b/src/libgit2/xdiff/xprepare.h @@ -0,0 +1,34 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XPREPARE_H) +#define XPREPARE_H + + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +void xdl_free_env(xdfenv_t *xe); + + + +#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/libgit2/xdiff/xtypes.h b/src/libgit2/xdiff/xtypes.h new file mode 100644 index 000000000..8442bd436 --- /dev/null +++ b/src/libgit2/xdiff/xtypes.h @@ -0,0 +1,67 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XTYPES_H) +#define XTYPES_H + + + +typedef struct s_chanode { + struct s_chanode *next; + long icurr; +} chanode_t; + +typedef struct s_chastore { + chanode_t *head, *tail; + long isize, nsize; + chanode_t *ancur; + chanode_t *sncur; + long scurr; +} chastore_t; + +typedef struct s_xrecord { + struct s_xrecord *next; + char const *ptr; + long size; + unsigned long ha; +} xrecord_t; + +typedef struct s_xdfile { + chastore_t rcha; + long nrec; + unsigned int hbits; + xrecord_t **rhash; + long dstart, dend; + xrecord_t **recs; + char *rchg; + long *rindex; + long nreff; + unsigned long *ha; +} xdfile_t; + +typedef struct s_xdfenv { + xdfile_t xdf1, xdf2; +} xdfenv_t; + + + +#endif /* #if !defined(XTYPES_H) */ diff --git a/src/libgit2/xdiff/xutils.c b/src/libgit2/xdiff/xutils.c new file mode 100644 index 000000000..cfa6e2220 --- /dev/null +++ b/src/libgit2/xdiff/xutils.c @@ -0,0 +1,434 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + +long xdl_bogosqrt(long n) { + long i; + + /* + * Classical integer square root approximation using shifts. + */ + for (i = 1; n > 0; n >>= 2) + i <<= 1; + + return i; +} + + +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb) { + int i = 2; + mmbuffer_t mb[3]; + + mb[0].ptr = (char *) pre; + mb[0].size = psize; + mb[1].ptr = (char *) rec; + mb[1].size = size; + if (size > 0 && rec[size - 1] != '\n') { + mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; + mb[2].size = strlen(mb[2].ptr); + i++; + } + if (ecb->out_line(ecb->priv, mb, i) < 0) { + + return -1; + } + + return 0; +} + +void *xdl_mmfile_first(mmfile_t *mmf, long *size) +{ + *size = mmf->size; + return mmf->ptr; +} + + +long xdl_mmfile_size(mmfile_t *mmf) +{ + return mmf->size; +} + + +int xdl_cha_init(chastore_t *cha, long isize, long icount) { + + cha->head = cha->tail = NULL; + cha->isize = isize; + cha->nsize = icount * isize; + cha->ancur = cha->sncur = NULL; + cha->scurr = 0; + + return 0; +} + + +void xdl_cha_free(chastore_t *cha) { + chanode_t *cur, *tmp; + + for (cur = cha->head; (tmp = cur) != NULL;) { + cur = cur->next; + xdl_free(tmp); + } +} + + +void *xdl_cha_alloc(chastore_t *cha) { + chanode_t *ancur; + void *data; + + if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { + if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { + + return NULL; + } + ancur->icurr = 0; + ancur->next = NULL; + if (cha->tail) + cha->tail->next = ancur; + if (!cha->head) + cha->head = ancur; + cha->tail = ancur; + cha->ancur = ancur; + } + + data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; + ancur->icurr += cha->isize; + + return data; +} + +long xdl_guess_lines(mmfile_t *mf, long sample) { + long nl = 0, size, tsize = 0; + char const *data, *cur, *top; + + if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { + for (top = data + size; nl < sample && cur < top; ) { + nl++; + if (!(cur = memchr(cur, '\n', top - cur))) + cur = top; + else + cur++; + } + tsize += (long) (cur - data); + } + + if (nl && tsize) + nl = xdl_mmfile_size(mf) / (tsize / nl); + + return nl + 1; +} + +int xdl_blankline(const char *line, long size, long flags) +{ + long i; + + if (!(flags & XDF_WHITESPACE_FLAGS)) + return (size <= 1); + + for (i = 0; i < size && XDL_ISSPACE(line[i]); i++) + ; + + return (i == size); +} + +/* + * Have we eaten everything on the line, except for an optional + * CR at the very end? + */ +static int ends_with_optional_cr(const char *l, long s, long i) +{ + int complete = s && l[s-1] == '\n'; + + if (complete) + s--; + if (s == i) + return 1; + /* do not ignore CR at the end of an incomplete line */ + if (complete && s == i + 1 && l[i] == '\r') + return 1; + return 0; +} + +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) +{ + int i1, i2; + + if (s1 == s2 && !memcmp(l1, l2, s1)) + return 1; + if (!(flags & XDF_WHITESPACE_FLAGS)) + return 0; + + i1 = 0; + i2 = 0; + + /* + * -w matches everything that matches with -b, and -b in turn + * matches everything that matches with --ignore-space-at-eol, + * which in turn matches everything that matches with --ignore-cr-at-eol. + * + * Each flavor of ignoring needs different logic to skip whitespaces + * while we have both sides to compare. + */ + if (flags & XDF_IGNORE_WHITESPACE) { + goto skip_ws; + while (i1 < s1 && i2 < s2) { + if (l1[i1++] != l2[i2++]) + return 0; + skip_ws: + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + } + } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + while (i1 < s1 && i2 < s2) { + if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { + /* Skip matching spaces and try again */ + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + continue; + } + if (l1[i1++] != l2[i2++]) + return 0; + } + } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + } else if (flags & XDF_IGNORE_CR_AT_EOL) { + /* Find the first difference and see how the line ends */ + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + return (ends_with_optional_cr(l1, s1, i1) && + ends_with_optional_cr(l2, s2, i2)); + } + + /* + * After running out of one side, the remaining side must have + * nothing but whitespace for the lines to match. Note that + * ignore-whitespace-at-eol case may break out of the loop + * while there still are characters remaining on both lines. + */ + if (i1 < s1) { + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + if (s1 != i1) + return 0; + } + if (i2 < s2) { + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + return (s2 == i2); + } + return 1; +} + +static unsigned long xdl_hash_record_with_whitespace(char const **data, + char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; + + for (; ptr < top && *ptr != '\n'; ptr++) { + if (cr_at_eol_only) { + /* do not ignore CR at the end of an incomplete line */ + if (*ptr == '\r' && + (ptr + 1 < top && ptr[1] == '\n')) + continue; + } + else if (XDL_ISSPACE(*ptr)) { + const char *ptr2 = ptr; + int at_eol; + while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) + && ptr[1] != '\n') + ptr++; + at_eol = (top <= ptr + 1 || ptr[1] == '\n'); + if (flags & XDF_IGNORE_WHITESPACE) + ; /* already handled */ + else if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && !at_eol) { + ha += (ha << 5); + ha ^= (unsigned long) ' '; + } + else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL + && !at_eol) { + while (ptr2 != ptr + 1) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr2; + ptr2++; + } + } + continue; + } + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + +unsigned long xdl_hash_record(char const **data, char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + if (flags & XDF_WHITESPACE_FLAGS) + return xdl_hash_record_with_whitespace(data, top, flags); + + for (; ptr < top && *ptr != '\n'; ptr++) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + +unsigned int xdl_hashbits(unsigned int size) { + unsigned int val = 1, bits = 0; + + for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); + return bits ? bits: 1; +} + + +int xdl_num_out(char *out, long val) { + char *ptr, *str = out; + char buf[32]; + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + if (val < 0) { + *--ptr = '-'; + val = -val; + } + for (; val && ptr > buf; val /= 10) + *--ptr = "0123456789"[val % 10]; + if (*ptr) + for (; *ptr; ptr++, str++) + *str = *ptr; + else + *str++ = '0'; + *str = '\0'; + + return str - out; +} + +static int xdl_format_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, + xdemitcb_t *ecb) { + int nb = 0; + mmbuffer_t mb; + char buf[128]; + + memcpy(buf, "@@ -", 4); + nb += 4; + + nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); + + if (c1 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c1); + } + + memcpy(buf + nb, " +", 2); + nb += 2; + + nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); + + if (c2 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c2); + } + + memcpy(buf + nb, " @@", 3); + nb += 3; + if (func && funclen) { + buf[nb++] = ' '; + if (funclen > sizeof(buf) - nb - 1) + funclen = sizeof(buf) - nb - 1; + memcpy(buf + nb, func, funclen); + nb += funclen; + } + buf[nb++] = '\n'; + + mb.ptr = buf; + mb.size = nb; + if (ecb->out_line(ecb->priv, &mb, 1) < 0) + return -1; + return 0; +} + +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, + xdemitcb_t *ecb) { + if (!ecb->out_hunk) + return xdl_format_hunk_hdr(s1, c1, s2, c2, func, funclen, ecb); + if (ecb->out_hunk(ecb->priv, + c1 ? s1 : s1 - 1, c1, + c2 ? s2 : s2 - 1, c2, + func, funclen) < 0) + return -1; + return 0; +} + +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2) +{ + /* + * This probably does not work outside Git, since + * we have a very simple mmfile structure. + * + * Note: ideally, we would reuse the prepared environment, but + * the libxdiff interface does not (yet) allow for diffing only + * ranges of lines instead of the whole files. + */ + mmfile_t subfile1, subfile2; + xdfenv_t env; + + subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + + diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + + diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) + return -1; + + memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} diff --git a/src/libgit2/xdiff/xutils.h b/src/libgit2/xdiff/xutils.h new file mode 100644 index 000000000..fba7bae03 --- /dev/null +++ b/src/libgit2/xdiff/xutils.h @@ -0,0 +1,47 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XUTILS_H) +#define XUTILS_H + + + +long xdl_bogosqrt(long n); +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb); +int xdl_cha_init(chastore_t *cha, long isize, long icount); +void xdl_cha_free(chastore_t *cha); +void *xdl_cha_alloc(chastore_t *cha); +long xdl_guess_lines(mmfile_t *mf, long sample); +int xdl_blankline(const char *line, long size, long flags); +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); +unsigned long xdl_hash_record(char const **data, char const *top, long flags); +unsigned int xdl_hashbits(unsigned int size); +int xdl_num_out(char *out, long val); +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb); +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2); + + + +#endif /* #if !defined(XUTILS_H) */ diff --git a/src/mailmap.c b/src/mailmap.c deleted file mode 100644 index 4336fe3e5..000000000 --- a/src/mailmap.c +++ /dev/null @@ -1,500 +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 "mailmap.h" - -#include "common.h" -#include "config.h" -#include "fs_path.h" -#include "repository.h" -#include "signature.h" -#include "git2/config.h" -#include "git2/revparse.h" -#include "blob.h" -#include "parse.h" -#include "path.h" - -#define MM_FILE ".mailmap" -#define MM_FILE_CONFIG "mailmap.file" -#define MM_BLOB_CONFIG "mailmap.blob" -#define MM_BLOB_DEFAULT "HEAD:" MM_FILE - -static void mailmap_entry_free(git_mailmap_entry *entry) -{ - if (!entry) - return; - - git__free(entry->real_name); - git__free(entry->real_email); - git__free(entry->replace_name); - git__free(entry->replace_email); - git__free(entry); -} - -/* - * First we sort by replace_email, then replace_name (if present). - * Entries with names are greater than entries without. - */ -static int mailmap_entry_cmp(const void *a_raw, const void *b_raw) -{ - const git_mailmap_entry *a = (const git_mailmap_entry *)a_raw; - const git_mailmap_entry *b = (const git_mailmap_entry *)b_raw; - int cmp; - - GIT_ASSERT_ARG(a && a->replace_email); - GIT_ASSERT_ARG(b && b->replace_email); - - cmp = git__strcmp(a->replace_email, b->replace_email); - if (cmp) - return cmp; - - /* NULL replace_names are less than not-NULL ones */ - if (a->replace_name == NULL || b->replace_name == NULL) - return (int)(a->replace_name != NULL) - (int)(b->replace_name != NULL); - - return git__strcmp(a->replace_name, b->replace_name); -} - -/* Replace the old entry with the new on duplicate. */ -static int mailmap_entry_replace(void **old_raw, void *new_raw) -{ - mailmap_entry_free((git_mailmap_entry *)*old_raw); - *old_raw = new_raw; - return GIT_EEXISTS; -} - -/* Check if we're at the end of line, w/ comments */ -static bool is_eol(git_parse_ctx *ctx) -{ - char c; - return git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 || c == '#'; -} - -static int advance_until( - const char **start, size_t *len, git_parse_ctx *ctx, char needle) -{ - *start = ctx->line; - while (ctx->line_len > 0 && *ctx->line != '#' && *ctx->line != needle) - git_parse_advance_chars(ctx, 1); - - if (ctx->line_len == 0 || *ctx->line == '#') - return -1; /* end of line */ - - *len = ctx->line - *start; - git_parse_advance_chars(ctx, 1); /* advance past needle */ - return 0; -} - -/* - * Parse a single entry from a mailmap file. - * - * The output git_strs will be non-owning, and should be copied before being - * persisted. - */ -static int parse_mailmap_entry( - git_str *real_name, git_str *real_email, - git_str *replace_name, git_str *replace_email, - git_parse_ctx *ctx) -{ - const char *start; - size_t len; - - git_str_clear(real_name); - git_str_clear(real_email); - git_str_clear(replace_name); - git_str_clear(replace_email); - - git_parse_advance_ws(ctx); - if (is_eol(ctx)) - return -1; /* blank line */ - - /* Parse the real name */ - if (advance_until(&start, &len, ctx, '<') < 0) - return -1; - - git_str_attach_notowned(real_name, start, len); - git_str_rtrim(real_name); - - /* - * If this is the last email in the line, this is the email to replace, - * otherwise, it's the real email. - */ - if (advance_until(&start, &len, ctx, '>') < 0) - return -1; - - /* If we aren't at the end of the line, parse a second name and email */ - if (!is_eol(ctx)) { - git_str_attach_notowned(real_email, start, len); - - git_parse_advance_ws(ctx); - if (advance_until(&start, &len, ctx, '<') < 0) - return -1; - git_str_attach_notowned(replace_name, start, len); - git_str_rtrim(replace_name); - - if (advance_until(&start, &len, ctx, '>') < 0) - return -1; - } - - git_str_attach_notowned(replace_email, start, len); - - if (!is_eol(ctx)) - return -1; - - return 0; -} - -int git_mailmap_new(git_mailmap **out) -{ - int error; - git_mailmap *mm = git__calloc(1, sizeof(git_mailmap)); - GIT_ERROR_CHECK_ALLOC(mm); - - error = git_vector_init(&mm->entries, 0, mailmap_entry_cmp); - if (error < 0) { - git__free(mm); - return error; - } - *out = mm; - return 0; -} - -void git_mailmap_free(git_mailmap *mm) -{ - size_t idx; - git_mailmap_entry *entry; - if (!mm) - return; - - git_vector_foreach(&mm->entries, idx, entry) - mailmap_entry_free(entry); - - git_vector_free(&mm->entries); - git__free(mm); -} - -static int mailmap_add_entry_unterminated( - git_mailmap *mm, - const char *real_name, size_t real_name_size, - const char *real_email, size_t real_email_size, - const char *replace_name, size_t replace_name_size, - const char *replace_email, size_t replace_email_size) -{ - int error; - git_mailmap_entry *entry = git__calloc(1, sizeof(git_mailmap_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - GIT_ASSERT_ARG(mm); - GIT_ASSERT_ARG(replace_email && *replace_email); - - if (real_name_size > 0) { - entry->real_name = git__substrdup(real_name, real_name_size); - GIT_ERROR_CHECK_ALLOC(entry->real_name); - } - if (real_email_size > 0) { - entry->real_email = git__substrdup(real_email, real_email_size); - GIT_ERROR_CHECK_ALLOC(entry->real_email); - } - if (replace_name_size > 0) { - entry->replace_name = git__substrdup(replace_name, replace_name_size); - GIT_ERROR_CHECK_ALLOC(entry->replace_name); - } - entry->replace_email = git__substrdup(replace_email, replace_email_size); - GIT_ERROR_CHECK_ALLOC(entry->replace_email); - - error = git_vector_insert_sorted(&mm->entries, entry, mailmap_entry_replace); - if (error == GIT_EEXISTS) - error = GIT_OK; - else if (error < 0) - mailmap_entry_free(entry); - - return error; -} - -int git_mailmap_add_entry( - git_mailmap *mm, const char *real_name, const char *real_email, - const char *replace_name, const char *replace_email) -{ - return mailmap_add_entry_unterminated( - mm, - real_name, real_name ? strlen(real_name) : 0, - real_email, real_email ? strlen(real_email) : 0, - replace_name, replace_name ? strlen(replace_name) : 0, - replace_email, strlen(replace_email)); -} - -static int mailmap_add_buffer(git_mailmap *mm, const char *buf, size_t len) -{ - int error = 0; - git_parse_ctx ctx; - - /* Scratch buffers containing the real parsed names & emails */ - git_str real_name = GIT_STR_INIT; - git_str real_email = GIT_STR_INIT; - git_str replace_name = GIT_STR_INIT; - git_str replace_email = GIT_STR_INIT; - - /* Buffers may not contain '\0's. */ - if (memchr(buf, '\0', len) != NULL) - return -1; - - git_parse_ctx_init(&ctx, buf, len); - - /* Run the parser */ - while (ctx.remain_len > 0) { - error = parse_mailmap_entry( - &real_name, &real_email, &replace_name, &replace_email, &ctx); - if (error < 0) { - error = 0; /* Skip lines which don't contain a valid entry */ - git_parse_advance_line(&ctx); - continue; /* TODO: warn */ - } - - /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */ - error = mailmap_add_entry_unterminated( - mm, real_name.ptr, real_name.size, real_email.ptr, real_email.size, - replace_name.ptr, replace_name.size, replace_email.ptr, replace_email.size); - if (error < 0) - goto cleanup; - - error = 0; - } - -cleanup: - git_str_dispose(&real_name); - git_str_dispose(&real_email); - git_str_dispose(&replace_name); - git_str_dispose(&replace_email); - return error; -} - -int git_mailmap_from_buffer(git_mailmap **out, const char *data, size_t len) -{ - int error = git_mailmap_new(out); - if (error < 0) - return error; - - error = mailmap_add_buffer(*out, data, len); - if (error < 0) { - git_mailmap_free(*out); - *out = NULL; - } - return error; -} - -static int mailmap_add_blob( - git_mailmap *mm, git_repository *repo, const char *rev) -{ - git_object *object = NULL; - git_blob *blob = NULL; - git_str content = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(mm); - GIT_ASSERT_ARG(repo); - - error = git_revparse_single(&object, repo, rev); - if (error < 0) - goto cleanup; - - error = git_object_peel((git_object **)&blob, object, GIT_OBJECT_BLOB); - if (error < 0) - goto cleanup; - - error = git_blob__getbuf(&content, blob); - if (error < 0) - goto cleanup; - - error = mailmap_add_buffer(mm, content.ptr, content.size); - if (error < 0) - goto cleanup; - -cleanup: - git_str_dispose(&content); - git_blob_free(blob); - git_object_free(object); - return error; -} - -static int mailmap_add_file_ondisk( - git_mailmap *mm, const char *path, git_repository *repo) -{ - const char *base = repo ? git_repository_workdir(repo) : NULL; - git_str fullpath = GIT_STR_INIT; - git_str content = GIT_STR_INIT; - int error; - - error = git_fs_path_join_unrooted(&fullpath, path, base, NULL); - if (error < 0) - goto cleanup; - - error = git_path_validate_str_length(repo, &fullpath); - if (error < 0) - goto cleanup; - - error = git_futils_readbuffer(&content, fullpath.ptr); - if (error < 0) - goto cleanup; - - error = mailmap_add_buffer(mm, content.ptr, content.size); - if (error < 0) - goto cleanup; - -cleanup: - git_str_dispose(&fullpath); - git_str_dispose(&content); - return error; -} - -/* NOTE: Only expose with an error return, currently never errors */ -static void mailmap_add_from_repository(git_mailmap *mm, git_repository *repo) -{ - git_config *config = NULL; - git_str rev_buf = GIT_STR_INIT; - git_str path_buf = GIT_STR_INIT; - const char *rev = NULL; - const char *path = NULL; - - /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */ - if (repo->is_bare) - rev = MM_BLOB_DEFAULT; - - /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */ - if (git_repository_config(&config, repo) == 0) { - if (git_config__get_string_buf(&rev_buf, config, MM_BLOB_CONFIG) == 0) - rev = rev_buf.ptr; - if (git_config__get_path(&path_buf, config, MM_FILE_CONFIG) == 0) - path = path_buf.ptr; - } - - /* - * Load mailmap files in order, overriding previous entries with new ones. - * 1. The '.mailmap' file in the repository's workdir root, - * 2. The blob described by the 'mailmap.blob' config (default HEAD:.mailmap), - * 3. The file described by the 'mailmap.file' config. - * - * We ignore errors from these loads, as these files may not exist, or may - * contain invalid information, and we don't want to report that error. - * - * XXX: Warn? - */ - if (!repo->is_bare) - mailmap_add_file_ondisk(mm, MM_FILE, repo); - if (rev != NULL) - mailmap_add_blob(mm, repo, rev); - if (path != NULL) - mailmap_add_file_ondisk(mm, path, repo); - - git_str_dispose(&rev_buf); - git_str_dispose(&path_buf); - git_config_free(config); -} - -int git_mailmap_from_repository(git_mailmap **out, git_repository *repo) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if ((error = git_mailmap_new(out)) < 0) - return error; - - mailmap_add_from_repository(*out, repo); - return 0; -} - -const git_mailmap_entry *git_mailmap_entry_lookup( - const git_mailmap *mm, const char *name, const char *email) -{ - int error; - ssize_t fallback = -1; - size_t idx; - git_mailmap_entry *entry; - - /* The lookup needle we want to use only sets the replace_email. */ - git_mailmap_entry needle = { NULL }; - needle.replace_email = (char *)email; - - GIT_ASSERT_ARG_WITH_RETVAL(email, NULL); - - if (!mm) - return NULL; - - /* - * We want to find the place to start looking. so we do a binary search for - * the "fallback" nameless entry. If we find it, we advance past it and record - * the index. - */ - error = git_vector_bsearch(&idx, (git_vector *)&mm->entries, &needle); - if (error >= 0) - fallback = idx++; - else if (error != GIT_ENOTFOUND) - return NULL; - - /* do a linear search for an exact match */ - for (; idx < git_vector_length(&mm->entries); ++idx) { - entry = git_vector_get(&mm->entries, idx); - - if (git__strcmp(entry->replace_email, email)) - break; /* it's a different email, so we're done looking */ - - /* should be specific */ - GIT_ASSERT_WITH_RETVAL(entry->replace_name, NULL); - if (!name || !git__strcmp(entry->replace_name, name)) - return entry; - } - - if (fallback < 0) - return NULL; /* no fallback */ - return git_vector_get(&mm->entries, fallback); -} - -int git_mailmap_resolve( - const char **real_name, const char **real_email, - const git_mailmap *mailmap, - const char *name, const char *email) -{ - const git_mailmap_entry *entry = NULL; - - GIT_ASSERT(name); - GIT_ASSERT(email); - - *real_name = name; - *real_email = email; - - if ((entry = git_mailmap_entry_lookup(mailmap, name, email))) { - if (entry->real_name) - *real_name = entry->real_name; - if (entry->real_email) - *real_email = entry->real_email; - } - return 0; -} - -int git_mailmap_resolve_signature( - git_signature **out, const git_mailmap *mailmap, const git_signature *sig) -{ - const char *name = NULL; - const char *email = NULL; - int error; - - if (!sig) - return 0; - - error = git_mailmap_resolve(&name, &email, mailmap, sig->name, sig->email); - if (error < 0) - return error; - - error = git_signature_new(out, name, email, sig->when.time, sig->when.offset); - if (error < 0) - return error; - - /* Copy over the sign, as git_signature_new doesn't let you pass it. */ - (*out)->when.sign = sig->when.sign; - return 0; -} diff --git a/src/mailmap.h b/src/mailmap.h deleted file mode 100644 index 2c9736a4a..000000000 --- a/src/mailmap.h +++ /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. - */ -#ifndef INCLUDE_mailmap_h__ -#define INCLUDE_mailmap_h__ - -#include "git2/mailmap.h" -#include "vector.h" - -/* - * A mailmap is stored as a sorted vector of 'git_mailmap_entry's. These entries - * are sorted first by 'replace_email', and then by 'replace_name'. NULL - * replace_names are ordered first. - * - * Looking up a name and email in the mailmap is done with a binary search. - */ -struct git_mailmap { - git_vector entries; -}; - -/* Single entry parsed from a mailmap */ -typedef struct git_mailmap_entry { - char *real_name; /**< the real name (may be NULL) */ - char *real_email; /**< the real email (may be NULL) */ - char *replace_name; /**< the name to replace (may be NULL) */ - char *replace_email; /**< the email to replace */ -} git_mailmap_entry; - -const git_mailmap_entry *git_mailmap_entry_lookup( - const git_mailmap *mm, const char *name, const char *email); - -#endif diff --git a/src/map.h b/src/map.h deleted file mode 100644 index 01931d199..000000000 --- a/src/map.h +++ /dev/null @@ -1,46 +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_map_h__ -#define INCLUDE_map_h__ - -#include "common.h" - - -/* p_mmap() prot values */ -#define GIT_PROT_NONE 0x0 -#define GIT_PROT_READ 0x1 -#define GIT_PROT_WRITE 0x2 -#define GIT_PROT_EXEC 0x4 - -/* git__mmmap() flags values */ -#define GIT_MAP_FILE 0 -#define GIT_MAP_SHARED 1 -#define GIT_MAP_PRIVATE 2 -#define GIT_MAP_TYPE 0xf -#define GIT_MAP_FIXED 0x10 - -#ifdef __amigaos4__ -#define MAP_FAILED 0 -#endif - -typedef struct { /* memory mapped buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ -#ifdef GIT_WIN32 - HANDLE fmh; /* file mapping handle */ -#endif -} git_map; - -#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ - GIT_ASSERT(out != NULL && len > 0); \ - GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ - GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0) - -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 deleted file mode 100644 index 641b32632..000000000 --- a/src/merge.c +++ /dev/null @@ -1,3435 +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 "merge.h" - -#include "posix.h" -#include "str.h" -#include "repository.h" -#include "revwalk.h" -#include "commit_list.h" -#include "fs_path.h" -#include "refs.h" -#include "object.h" -#include "iterator.h" -#include "refs.h" -#include "diff.h" -#include "diff_generate.h" -#include "diff_tform.h" -#include "checkout.h" -#include "tree.h" -#include "blob.h" -#include "oid.h" -#include "index.h" -#include "filebuf.h" -#include "config.h" -#include "oidarray.h" -#include "annotated_commit.h" -#include "commit.h" -#include "oidarray.h" -#include "merge_driver.h" -#include "oidmap.h" -#include "array.h" - -#include "git2/types.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/commit.h" -#include "git2/merge.h" -#include "git2/refs.h" -#include "git2/reset.h" -#include "git2/checkout.h" -#include "git2/signature.h" -#include "git2/config.h" -#include "git2/tree.h" -#include "git2/oidarray.h" -#include "git2/annotated_commit.h" -#include "git2/sys/index.h" -#include "git2/sys/hashsig.h" - -#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) -#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) - - -typedef enum { - TREE_IDX_ANCESTOR = 0, - TREE_IDX_OURS = 1, - TREE_IDX_THEIRS = 2 -} merge_tree_index_t; - -/* Tracks D/F conflicts */ -struct merge_diff_df_data { - const char *df_path; - const char *prev_path; - 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 */ - -static int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_revwalk *walk = NULL; - git_vector list; - git_commit_list *result = NULL; - git_commit_list_node *commit; - int error = -1; - unsigned int i; - - if (length < 2) { - git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); - return -1; - } - - if (git_vector_init(&list, length - 1, NULL) < 0) - return -1; - - if (git_revwalk_new(&walk, repo) < 0) - goto on_error; - - for (i = 1; i < length; i++) { - commit = git_revwalk__commit_lookup(walk, &input_array[i]); - if (commit == NULL) - goto on_error; - - git_vector_insert(&list, commit); - } - - commit = git_revwalk__commit_lookup(walk, &input_array[0]); - if (commit == NULL) - goto on_error; - - if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) - goto on_error; - - if (!result) { - git_error_set(GIT_ERROR_MERGE, "no merge base found"); - error = GIT_ENOTFOUND; - goto on_error; - } - - *out = result; - *walk_out = walk; - - git_vector_free(&list); - return 0; - -on_error: - git_vector_free(&list); - git_revwalk_free(walk); - return error; -} - -int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_revwalk *walk; - git_commit_list *result = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(input_array); - - if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) - return error; - - git_oid_cpy(out, &result->item->oid); - - git_commit_list_free(&result); - git_revwalk_free(walk); - - return 0; -} - -int git_merge_bases_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_revwalk *walk; - git_commit_list *list, *result = NULL; - int error = 0; - git_array_oid_t array; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(input_array); - - if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) - return error; - - git_array_init(array); - - list = result; - while (list) { - git_oid *id = git_array_alloc(array); - if (id == NULL) { - error = -1; - goto cleanup; - } - - git_oid_cpy(id, &list->item->oid); - list = list->next; - } - - git_oidarray__from_array(out, &array); - -cleanup: - git_commit_list_free(&result); - git_revwalk_free(walk); - - return error; -} - -int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_oid result; - unsigned int i; - int error = -1; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(input_array); - - if (length < 2) { - git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); - return -1; - } - - result = input_array[0]; - for (i = 1; i < length; i++) { - error = git_merge_base(&result, repo, &result, &input_array[i]); - if (error < 0) - return error; - } - - *out = result; - - return 0; -} - -static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two) -{ - git_revwalk *walk; - git_vector list; - git_commit_list *result = NULL; - git_commit_list_node *commit; - void *contents[1]; - - if (git_revwalk_new(&walk, repo) < 0) - return -1; - - commit = git_revwalk__commit_lookup(walk, two); - if (commit == NULL) - goto on_error; - - /* This is just one value, so we can do it on the stack */ - memset(&list, 0x0, sizeof(git_vector)); - contents[0] = commit; - list.length = 1; - list.contents = contents; - - commit = git_revwalk__commit_lookup(walk, one); - if (commit == NULL) - goto on_error; - - if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) - goto on_error; - - if (!result) { - git_revwalk_free(walk); - git_error_set(GIT_ERROR_MERGE, "no merge base found"); - return GIT_ENOTFOUND; - } - - *out = result; - *walk_out = walk; - - return 0; - -on_error: - git_revwalk_free(walk); - return -1; - -} - -int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two) -{ - int error; - git_revwalk *walk; - git_commit_list *result; - - if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) - return error; - - git_oid_cpy(out, &result->item->oid); - git_commit_list_free(&result); - git_revwalk_free(walk); - - return 0; -} - -int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two) -{ - int error; - git_revwalk *walk; - git_commit_list *result, *list; - git_array_oid_t array; - - git_array_init(array); - - if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) - return error; - - list = result; - while (list) { - git_oid *id = git_array_alloc(array); - if (id == NULL) - goto on_error; - - git_oid_cpy(id, &list->item->oid); - list = list->next; - } - - git_oidarray__from_array(out, &array); - git_commit_list_free(&result); - git_revwalk_free(walk); - - return 0; - -on_error: - git_commit_list_free(&result); - git_revwalk_free(walk); - return -1; -} - -static int interesting(git_pqueue *list) -{ - size_t i; - - for (i = 0; i < git_pqueue_size(list); i++) { - git_commit_list_node *commit = git_pqueue_get(list, i); - if ((commit->flags & STALE) == 0) - return 1; - } - - return 0; -} - -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 0; - - commit->flags &= ~mark; - - for (i = 1; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if (git_commit_list_insert(p, plist) == NULL) - return -1; - } - - commit = commit->out_degree ? commit->parents[0] : NULL; - } - - return 0; -} - -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) { - if (git_commit_list_insert(c, &list) == NULL) - return -1; - } - - while (list) - if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) - return -1; - return 0; -} - -static int clear_commit_marks(git_commit_list_node *commit, unsigned int mark) -{ - git_commit_list *list = NULL; - if (git_commit_list_insert(commit, &list) == NULL) - return -1; - while (list) - if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) - return -1; - return 0; -} - -static int paint_down_to_common( - git_commit_list **out, - git_revwalk *walk, - git_commit_list_node *one, - git_vector *twos, - uint32_t minimum_generation) -{ - git_pqueue list; - git_commit_list *result = NULL; - git_commit_list_node *two; - - int error; - unsigned int i; - - if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_generation_cmp) < 0) - return -1; - - one->flags |= PARENT1; - if (git_pqueue_insert(&list, one) < 0) - return -1; - - git_vector_foreach(twos, i, two) { - if (git_commit_list_parse(walk, two) < 0) - return -1; - - two->flags |= PARENT2; - if (git_pqueue_insert(&list, two) < 0) - return -1; - } - - /* as long as there are non-STALE commits */ - while (interesting(&list)) { - git_commit_list_node *commit = git_pqueue_pop(&list); - int flags; - - if (commit == NULL) - break; - - flags = commit->flags & (PARENT1 | PARENT2 | STALE); - if (flags == (PARENT1 | PARENT2)) { - if (!(commit->flags & RESULT)) { - commit->flags |= RESULT; - if (git_commit_list_insert(commit, &result) == NULL) - return -1; - } - /* we mark the parents of a merge stale */ - flags |= STALE; - } - - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if ((p->flags & flags) == flags) - continue; - if (p->generation < minimum_generation) - continue; - - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - - p->flags |= flags; - if (git_pqueue_insert(&list, p) < 0) - return -1; - } - } - - git_pqueue_free(&list); - *out = result; - return 0; -} - -static int remove_redundant(git_revwalk *walk, git_vector *commits, uint32_t minimum_generation) -{ - git_vector work = GIT_VECTOR_INIT; - unsigned char *redundant; - unsigned int *filled_index; - unsigned int i, j; - int error = 0; - - redundant = git__calloc(commits->length, 1); - GIT_ERROR_CHECK_ALLOC(redundant); - filled_index = git__calloc((commits->length - 1), sizeof(unsigned int)); - GIT_ERROR_CHECK_ALLOC(filled_index); - - for (i = 0; i < commits->length; ++i) { - if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0) - goto done; - } - - for (i = 0; i < commits->length; ++i) { - git_commit_list *common = NULL; - git_commit_list_node *commit = commits->contents[i]; - - if (redundant[i]) - continue; - - git_vector_clear(&work); - - for (j = 0; j < commits->length; j++) { - if (i == j || redundant[j]) - continue; - - filled_index[work.length] = j; - if ((error = git_vector_insert(&work, commits->contents[j])) < 0) - goto done; - } - - error = paint_down_to_common(&common, walk, commit, &work, minimum_generation); - if (error < 0) - goto done; - - if (commit->flags & PARENT2) - redundant[i] = 1; - - for (j = 0; j < work.length; j++) { - git_commit_list_node *w = work.contents[j]; - if (w->flags & PARENT1) - redundant[filled_index[j]] = 1; - } - - 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) { - if (redundant[i]) - commits->contents[i] = NULL; - } - -done: - git__free(redundant); - git__free(filled_index); - git_vector_free(&work); - return error; -} - -int git_merge__bases_many( - git_commit_list **out, - git_revwalk *walk, - git_commit_list_node *one, - git_vector *twos, - uint32_t minimum_generation) -{ - int error; - unsigned int i; - git_commit_list_node *two; - git_commit_list *result = NULL, *tmp = NULL; - - /* If there's only the one commit, there can be no merge bases */ - if (twos->length == 0) { - *out = NULL; - return 0; - } - - /* if the commit is repeated, we have a our merge base already */ - git_vector_foreach(twos, i, two) { - if (one == two) - return git_commit_list_insert(one, out) ? 0 : -1; - } - - if (git_commit_list_parse(walk, one) < 0) - return -1; - - error = paint_down_to_common(&result, walk, one, twos, minimum_generation); - if (error < 0) - return error; - - /* filter out any stale commits in the results */ - tmp = result; - result = NULL; - - while (tmp) { - git_commit_list_node *c = git_commit_list_pop(&tmp); - if (!(c->flags & STALE)) - if (git_commit_list_insert_by_date(c, &result) == NULL) - return -1; - } - - /* - * more than one merge base -- see if there are redundant merge - * bases and remove them - */ - if (result && result->next) { - git_vector redundant = GIT_VECTOR_INIT; - - while (result) - git_vector_insert(&redundant, git_commit_list_pop(&result)); - - if ((error = clear_commit_marks(one, ALL_FLAGS)) < 0 || - (error = clear_commit_marks_many(twos, ALL_FLAGS)) < 0 || - (error = remove_redundant(walk, &redundant, minimum_generation)) < 0) { - git_vector_free(&redundant); - return error; - } - - git_vector_foreach(&redundant, i, two) { - if (two != NULL) - git_commit_list_insert_by_date(two, &result); - } - - git_vector_free(&redundant); - } - - *out = result; - return 0; -} - -int git_repository_mergehead_foreach( - git_repository *repo, - git_repository_mergehead_foreach_cb cb, - void *payload) -{ - git_str merge_head_path = GIT_STR_INIT, merge_head_file = GIT_STR_INIT; - char *buffer, *line; - size_t line_num = 1; - git_oid oid; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cb); - - if ((error = git_str_joinpath(&merge_head_path, repo->gitdir, - GIT_MERGE_HEAD_FILE)) < 0) - return error; - - if ((error = git_futils_readbuffer(&merge_head_file, - git_str_cstr(&merge_head_path))) < 0) - goto cleanup; - - buffer = merge_head_file.ptr; - - while ((line = git__strsep(&buffer, "\n")) != NULL) { - if (strlen(line) != GIT_OID_HEXSZ) { - git_error_set(GIT_ERROR_INVALID, "unable to parse OID - invalid length"); - error = -1; - goto cleanup; - } - - if ((error = git_oid_fromstr(&oid, line)) < 0) - goto cleanup; - - if ((error = cb(&oid, payload)) != 0) { - git_error_set_after_callback(error); - goto cleanup; - } - - ++line_num; - } - - if (*buffer) { - git_error_set(GIT_ERROR_MERGE, "no EOL at line %"PRIuZ, line_num); - error = -1; - goto cleanup; - } - -cleanup: - git_str_dispose(&merge_head_path); - git_str_dispose(&merge_head_file); - - return error; -} - -GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b) -{ - int value = 0; - - if (a->path == NULL) - return (b->path == NULL) ? 0 : 1; - - if ((value = a->mode - b->mode) == 0 && - (value = git_oid__cmp(&a->id, &b->id)) == 0) - value = strcmp(a->path, b->path); - - return value; -} - -/* Conflict resolution */ - -static int merge_conflict_resolve_trivial( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict) -{ - int ours_empty, theirs_empty; - int ours_changed, theirs_changed, ours_theirs_differ; - git_index_entry const *result = NULL; - int error = 0; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; - - if (conflict->our_status == GIT_DELTA_RENAMED || - conflict->their_status == GIT_DELTA_RENAMED) - return 0; - - ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); - theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); - - ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); - theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); - ours_theirs_differ = ours_changed && theirs_changed && - index_entry_cmp(&conflict->our_entry, &conflict->their_entry); - - /* - * Note: with only one ancestor, some cases are not distinct: - * - * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge - * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge - * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge - * - * Note that the two cases that take D/F conflicts into account - * specifically do not need to be explicitly tested, as D/F conflicts - * would fail the *empty* test: - * - * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head - * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote - * - * Note that many of these cases need not be explicitly tested, as - * they simply degrade to "all different" cases (eg, 11): - * - * 4: ancest:(empty)^, head:head, remote:remote = result:no merge - * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge - * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge - * 11: ancest:ancest+, head:head, remote:remote = result:no merge - */ - - /* 5ALT: ancest:*, head:head, remote:head = result:head */ - if (ours_changed && !ours_empty && !ours_theirs_differ) - result = &conflict->our_entry; - /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ - else if (ours_changed && ours_empty && theirs_empty) - *resolved = 0; - /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ - else if (ours_empty && !theirs_changed) - *resolved = 0; - /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ - else if (!ours_changed && theirs_empty) - *resolved = 0; - /* 13: ancest:ancest+, head:head, remote:ancest = result:head */ - else if (ours_changed && !theirs_changed) - result = &conflict->our_entry; - /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ - else if (!ours_changed && theirs_changed) - result = &conflict->their_entry; - else - *resolved = 0; - - if (result != NULL && - GIT_MERGE_INDEX_ENTRY_EXISTS(*result) && - (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0) - *resolved = 1; - - /* Note: trivial resolution does not update the REUC. */ - - return error; -} - -static int merge_conflict_resolve_one_removed( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict) -{ - int ours_empty, theirs_empty; - int ours_changed, theirs_changed; - int error = 0; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; - - ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); - theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); - - ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); - theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); - - /* Removed in both */ - if (ours_changed && ours_empty && theirs_empty) - *resolved = 1; - /* Removed in ours */ - else if (ours_empty && !theirs_changed) - *resolved = 1; - /* Removed in theirs */ - else if (!ours_changed && theirs_empty) - *resolved = 1; - - if (*resolved) - git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); - - return error; -} - -static int merge_conflict_resolve_one_renamed( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict) -{ - int ours_renamed, theirs_renamed; - int ours_changed, theirs_changed; - git_index_entry *merged; - int error = 0; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return 0; - - ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED); - theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED); - - if (!ours_renamed && !theirs_renamed) - return 0; - - /* Reject one file in a 2->1 conflict */ - if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || - conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; - - ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0) || - (conflict->ancestor_entry.mode != conflict->our_entry.mode); - - theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0) || - (conflict->ancestor_entry.mode != conflict->their_entry.mode); - - /* if both are modified (and not to a common target) require a merge */ - if (ours_changed && theirs_changed && - git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0) - return 0; - - if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL) - return -1; - - if (ours_changed) - memcpy(merged, &conflict->our_entry, sizeof(git_index_entry)); - else - memcpy(merged, &conflict->their_entry, sizeof(git_index_entry)); - - if (ours_renamed) - merged->path = conflict->our_entry.path; - else - merged->path = conflict->their_entry.path; - - *resolved = 1; - - git_vector_insert(&diff_list->staged, merged); - git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); - - return error; -} - -static bool merge_conflict_can_resolve_contents( - const git_merge_diff *conflict) -{ - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return false; - - /* Reject D/F conflicts */ - if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) - return false; - - /* Reject submodules. */ - if (S_ISGITLINK(conflict->ancestor_entry.mode) || - S_ISGITLINK(conflict->our_entry.mode) || - S_ISGITLINK(conflict->their_entry.mode)) - return false; - - /* Reject link/file conflicts. */ - if ((S_ISLNK(conflict->ancestor_entry.mode) ^ - S_ISLNK(conflict->our_entry.mode)) || - (S_ISLNK(conflict->ancestor_entry.mode) ^ - S_ISLNK(conflict->their_entry.mode))) - return false; - - /* Reject name conflicts */ - if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return false; - - if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && - (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && - strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) - return false; - - return true; -} - -static int merge_conflict_invoke_driver( - git_index_entry **out, - const char *name, - git_merge_driver *driver, - git_merge_diff_list *diff_list, - git_merge_driver_source *src) -{ - git_index_entry *result; - git_buf buf = {0}; - const char *path; - uint32_t mode; - git_odb *odb = NULL; - git_oid oid; - int error; - - *out = NULL; - - if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || - (error = git_repository_odb(&odb, src->repo)) < 0 || - (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJECT_BLOB)) < 0) - goto done; - - result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); - GIT_ERROR_CHECK_ALLOC(result); - - git_oid_cpy(&result->id, &oid); - result->mode = mode; - result->file_size = (uint32_t)buf.size; - - result->path = git_pool_strdup(&diff_list->pool, path); - GIT_ERROR_CHECK_ALLOC(result->path); - - *out = result; - -done: - git_buf_dispose(&buf); - git_odb_free(odb); - - return error; -} - -static int merge_conflict_resolve_contents( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict, - const git_merge_options *merge_opts, - const git_merge_file_options *file_opts) -{ - git_merge_driver_source source = {0}; - git_merge_file_result result = {0}; - git_merge_driver *driver; - git_merge_driver__builtin builtin = {{0}}; - git_index_entry *merge_result; - git_odb *odb = NULL; - const char *name; - bool fallback = false; - int error; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (!merge_conflict_can_resolve_contents(conflict)) - return 0; - - source.repo = diff_list->repo; - source.default_driver = merge_opts->default_driver; - source.file_opts = file_opts; - source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { - /* if the user requested a particular type of resolution (via the - * favor flag) then let that override the gitattributes and use - * the builtin driver. - */ - name = "text"; - builtin.base.apply = git_merge_driver__builtin_apply; - builtin.favor = file_opts->favor; - - driver = &builtin.base; - } else { - /* find the merge driver for this file */ - if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) - goto done; - - if (driver == NULL) - fallback = true; - } - - if (driver) { - error = merge_conflict_invoke_driver(&merge_result, name, driver, - diff_list, &source); - - if (error == GIT_PASSTHROUGH) - fallback = true; - } - - if (fallback) { - error = merge_conflict_invoke_driver(&merge_result, "text", - &git_merge_driver__text.base, diff_list, &source); - } - - if (error < 0) { - if (error == GIT_EMERGECONFLICT) - error = 0; - - goto done; - } - - git_vector_insert(&diff_list->staged, merge_result); - git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); - - *resolved = 1; - -done: - git_merge_file_result_free(&result); - git_odb_free(odb); - - return error; -} - -static int merge_conflict_resolve( - int *out, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict, - const git_merge_options *merge_opts, - const git_merge_file_options *file_opts) -{ - int resolved = 0; - int error = 0; - - *out = 0; - - if ((error = merge_conflict_resolve_trivial( - &resolved, diff_list, conflict)) < 0) - goto done; - - if (!resolved && (error = merge_conflict_resolve_one_removed( - &resolved, diff_list, conflict)) < 0) - goto done; - - if (!resolved && (error = merge_conflict_resolve_one_renamed( - &resolved, diff_list, conflict)) < 0) - goto done; - - if (!resolved && (error = merge_conflict_resolve_contents( - &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) - goto done; - - *out = resolved; - -done: - return error; -} - -/* Rename detection and coalescing */ - -struct merge_diff_similarity { - unsigned char similarity; - size_t other_idx; -}; - -static int index_entry_similarity_calc( - void **out, - git_repository *repo, - git_index_entry *entry, - const git_merge_options *opts) -{ - git_blob *blob; - git_diff_file diff_file = {{{0}}}; - 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) - return error; - - git_oid_cpy(&diff_file.id, &entry->id); - diff_file.path = entry->path; - diff_file.size = entry->file_size; - diff_file.mode = entry->mode; - diff_file.flags = 0; - - blobsize = git_blob_rawsize(blob); - - /* file too big for rename processing */ - if (!git__is_sizet(blobsize)) - return 0; - - 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); - - return error; -} - -static int index_entry_similarity_inexact( - git_repository *repo, - git_index_entry *a, - size_t a_idx, - git_index_entry *b, - size_t b_idx, - void **cache, - const git_merge_options *opts) -{ - int score = 0; - int error = 0; - - if (!GIT_MODE_ISBLOB(a->mode) || !GIT_MODE_ISBLOB(b->mode)) - return 0; - - /* update signature cache if needed */ - 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_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) - return -1; - - /* clip score */ - if (score < 0) - score = 0; - else if (score > 100) - score = 100; - - return score; -} - -/* Tracks deletes by oid for merge_diff_mark_similarity_exact(). This is a -* non-shrinking queue where next_pos is the next position to dequeue. -*/ -typedef struct { - git_array_t(size_t) arr; - size_t next_pos; - size_t first_entry; -} deletes_by_oid_queue; - -static void deletes_by_oid_free(git_oidmap *map) { - deletes_by_oid_queue *queue; - - if (!map) - return; - - git_oidmap_foreach_value(map, queue, { - git_array_clear(queue->arr); - }); - git_oidmap_free(map); -} - -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; - - if ((queue = git_oidmap_get(map, id)) == NULL) { - queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); - GIT_ERROR_CHECK_ALLOC(queue); - - git_array_init(queue->arr); - queue->next_pos = 0; - queue->first_entry = idx; - - if (git_oidmap_set(map, id, queue) < 0) - return -1; - } else { - array_entry = git_array_alloc(queue->arr); - GIT_ERROR_CHECK_ALLOC(array_entry); - *array_entry = idx; - } - - return 0; -} - -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; - - if ((queue = git_oidmap_get(map, id)) == NULL) - return GIT_ENOTFOUND; - - if (queue->next_pos == 0) { - *idx = queue->first_entry; - } else { - array_entry = git_array_get(queue->arr, queue->next_pos - 1); - if (array_entry == NULL) - return GIT_ENOTFOUND; - - *idx = *array_entry; - } - - queue->next_pos++; - return 0; -} - -static int merge_diff_mark_similarity_exact( - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - struct merge_diff_similarity *similarity_theirs) -{ - size_t i, j; - git_merge_diff *conflict_src, *conflict_tgt; - git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; - int error = 0; - - if (git_oidmap_new(&ours_deletes_by_oid) < 0 || - git_oidmap_new(&theirs_deletes_by_oid) < 0) { - error = -1; - goto done; - } - - /* Build a map of object ids to conflicts */ - git_vector_foreach(&diff_list->conflicts, i, conflict_src) { - /* Items can be the source of a rename iff they have an item in the - * ancestor slot and lack an item in the ours or theirs slot. */ - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) - continue; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { - error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); - if (error < 0) - goto done; - } - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { - error = deletes_by_oid_enqueue(theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); - if (error < 0) - goto done; - } - } - - git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) - continue; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry)) { - if (deletes_by_oid_dequeue(&i, ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { - similarity_ours[i].similarity = 100; - similarity_ours[i].other_idx = j; - - similarity_ours[j].similarity = 100; - similarity_ours[j].other_idx = i; - } - } - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry)) { - if (deletes_by_oid_dequeue(&i, theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { - similarity_theirs[i].similarity = 100; - similarity_theirs[i].other_idx = j; - - similarity_theirs[j].similarity = 100; - similarity_theirs[j].other_idx = i; - } - } - } - -done: - deletes_by_oid_free(ours_deletes_by_oid); - deletes_by_oid_free(theirs_deletes_by_oid); - - return error; -} - -static int merge_diff_mark_similarity_inexact( - git_repository *repo, - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - struct merge_diff_similarity *similarity_theirs, - void **cache, - const git_merge_options *opts) -{ - size_t i, j; - git_merge_diff *conflict_src, *conflict_tgt; - int similarity; - - git_vector_foreach(&diff_list->conflicts, i, conflict_src) { - /* Items can be the source of a rename iff they have an item in the - * ancestor slot and lack an item in the ours or theirs slot. */ - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) || - (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) && - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry))) - continue; - - git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { - size_t our_idx = diff_list->conflicts.length + j; - size_t their_idx = (diff_list->conflicts.length * 2) + j; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) - continue; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { - similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); - - if (similarity == GIT_EBUFS) - continue; - else if (similarity < 0) - return similarity; - - if (similarity > similarity_ours[i].similarity && - similarity > similarity_ours[j].similarity) { - /* Clear previous best similarity */ - if (similarity_ours[i].similarity > 0) - similarity_ours[similarity_ours[i].other_idx].similarity = 0; - - if (similarity_ours[j].similarity > 0) - similarity_ours[similarity_ours[j].other_idx].similarity = 0; - - similarity_ours[i].similarity = similarity; - similarity_ours[i].other_idx = j; - - similarity_ours[j].similarity = similarity; - similarity_ours[j].other_idx = i; - } - } - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { - similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); - - if (similarity > similarity_theirs[i].similarity && - similarity > similarity_theirs[j].similarity) { - /* Clear previous best similarity */ - if (similarity_theirs[i].similarity > 0) - similarity_theirs[similarity_theirs[i].other_idx].similarity = 0; - - if (similarity_theirs[j].similarity > 0) - similarity_theirs[similarity_theirs[j].other_idx].similarity = 0; - - similarity_theirs[i].similarity = similarity; - similarity_theirs[i].other_idx = j; - - similarity_theirs[j].similarity = similarity; - similarity_theirs[j].other_idx = i; - } - } - } - } - - return 0; -} - -/* - * Rename conflicts: - * - * Ancestor Ours Theirs - * - * 0a A A A No rename - * b A A* A No rename (ours was rewritten) - * c A A A* No rename (theirs rewritten) - * 1a A A B[A] Rename or rename/edit - * b A B[A] A (automergeable) - * 2 A B[A] B[A] Both renamed (automergeable) - * 3a A B[A] Rename/delete - * b A B[A] (same) - * 4a A B[A] B Rename/add [B~ours B~theirs] - * b A B B[A] (same) - * 5 A B[A] C[A] Both renamed ("1 -> 2") - * 6 A C[A] Both renamed ("2 -> 1") - * B C[B] [C~ours C~theirs] (automergeable) - */ -static void merge_diff_mark_rename_conflict( - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - bool ours_renamed, - size_t ours_source_idx, - struct merge_diff_similarity *similarity_theirs, - bool theirs_renamed, - size_t theirs_source_idx, - git_merge_diff *target, - const git_merge_options *opts) -{ - git_merge_diff *ours_source = NULL, *theirs_source = NULL; - - if (ours_renamed) - ours_source = diff_list->conflicts.contents[ours_source_idx]; - - if (theirs_renamed) - theirs_source = diff_list->conflicts.contents[theirs_source_idx]; - - /* Detect 2->1 conflicts */ - if (ours_renamed && theirs_renamed) { - /* Both renamed to the same target name. */ - if (ours_source_idx == theirs_source_idx) - ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED; - else { - ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; - theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; - } - } else if (ours_renamed) { - /* If our source was also renamed in theirs, this is a 1->2 */ - if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold) - ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; - - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) { - ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; - target->type = GIT_MERGE_DIFF_RENAMED_ADDED; - } - - else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry)) - ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; - - else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) - ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; - } else if (theirs_renamed) { - /* If their source was also renamed in ours, this is a 1->2 */ - if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold) - theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; - - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) { - theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; - target->type = GIT_MERGE_DIFF_RENAMED_ADDED; - } - - else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry)) - theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; - - else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) - theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; - } -} - -GIT_INLINE(void) merge_diff_coalesce_rename( - git_index_entry *source_entry, - git_delta_t *source_status, - git_index_entry *target_entry, - git_delta_t *target_status) -{ - /* Coalesce the rename target into the rename source. */ - memcpy(source_entry, target_entry, sizeof(git_index_entry)); - *source_status = GIT_DELTA_RENAMED; - - memset(target_entry, 0x0, sizeof(git_index_entry)); - *target_status = GIT_DELTA_UNMODIFIED; -} - -static void merge_diff_list_coalesce_renames( - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - struct merge_diff_similarity *similarity_theirs, - const git_merge_options *opts) -{ - size_t i; - bool ours_renamed = 0, theirs_renamed = 0; - size_t ours_source_idx = 0, theirs_source_idx = 0; - git_merge_diff *ours_source, *theirs_source, *target; - - for (i = 0; i < diff_list->conflicts.length; i++) { - target = diff_list->conflicts.contents[i]; - - ours_renamed = 0; - theirs_renamed = 0; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) && - similarity_ours[i].similarity >= opts->rename_threshold) { - ours_source_idx = similarity_ours[i].other_idx; - - ours_source = diff_list->conflicts.contents[ours_source_idx]; - - merge_diff_coalesce_rename( - &ours_source->our_entry, - &ours_source->our_status, - &target->our_entry, - &target->our_status); - - similarity_ours[ours_source_idx].similarity = 0; - similarity_ours[i].similarity = 0; - - ours_renamed = 1; - } - - /* insufficient to determine direction */ - if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) && - similarity_theirs[i].similarity >= opts->rename_threshold) { - theirs_source_idx = similarity_theirs[i].other_idx; - - theirs_source = diff_list->conflicts.contents[theirs_source_idx]; - - merge_diff_coalesce_rename( - &theirs_source->their_entry, - &theirs_source->their_status, - &target->their_entry, - &target->their_status); - - similarity_theirs[theirs_source_idx].similarity = 0; - similarity_theirs[i].similarity = 0; - - theirs_renamed = 1; - } - - merge_diff_mark_rename_conflict(diff_list, - similarity_ours, ours_renamed, ours_source_idx, - similarity_theirs, theirs_renamed, theirs_source_idx, - target, opts); - } -} - -static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p) -{ - git_merge_diff *conflict = conflicts->contents[idx]; - - GIT_UNUSED(p); - - return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)); -} - -static void merge_diff_list_count_candidates( - git_merge_diff_list *diff_list, - size_t *src_count, - size_t *tgt_count) -{ - git_merge_diff *entry; - size_t i; - - *src_count = 0; - *tgt_count = 0; - - git_vector_foreach(&diff_list->conflicts, i, entry) { - if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) && - (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) || - !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry))) - (*src_count)++; - else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry)) - (*tgt_count)++; - } -} - -int git_merge_diff_list__find_renames( - git_repository *repo, - git_merge_diff_list *diff_list, - const git_merge_options *opts) -{ - struct merge_diff_similarity *similarity_ours, *similarity_theirs; - void **cache = NULL; - size_t cache_size = 0; - size_t src_count, tgt_count, i; - int error = 0; - - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(opts); - - if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0 || - !diff_list->conflicts.length) - return 0; - - similarity_ours = git__calloc(diff_list->conflicts.length, - sizeof(struct merge_diff_similarity)); - GIT_ERROR_CHECK_ALLOC(similarity_ours); - - similarity_theirs = git__calloc(diff_list->conflicts.length, - sizeof(struct merge_diff_similarity)); - GIT_ERROR_CHECK_ALLOC(similarity_theirs); - - /* Calculate similarity between items that were deleted from the ancestor - * and added in the other branch. - */ - if ((error = merge_diff_mark_similarity_exact(diff_list, similarity_ours, similarity_theirs)) < 0) - goto done; - - if (opts->rename_threshold < 100 && diff_list->conflicts.length <= opts->target_limit) { - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3); - cache = git__calloc(cache_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(cache); - - merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count); - - if (src_count > opts->target_limit || tgt_count > opts->target_limit) { - /* TODO: report! */ - } else { - if ((error = merge_diff_mark_similarity_inexact( - repo, diff_list, similarity_ours, similarity_theirs, cache, opts)) < 0) - goto done; - } - } - - /* For entries that are appropriately similar, merge the new name's entry - * into the old name. - */ - merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts); - - /* And remove any entries that were merged and are now empty. */ - git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL); - -done: - if (cache != NULL) { - for (i = 0; i < cache_size; ++i) { - if (cache[i] != NULL && cache[i] != &cache_invalid_marker) - opts->metric->free_signature(cache[i], opts->metric->payload); - } - - git__free(cache); - } - - git__free(similarity_ours); - git__free(similarity_theirs); - - return error; -} - -/* Directory/file conflict handling */ - -GIT_INLINE(const char *) merge_diff_path( - const git_merge_diff *conflict) -{ - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) - return conflict->ancestor_entry.path; - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry)) - return conflict->our_entry.path; - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return conflict->their_entry.path; - - return NULL; -} - -GIT_INLINE(bool) merge_diff_any_side_added_or_modified( - const git_merge_diff *conflict) -{ - if (conflict->our_status == GIT_DELTA_ADDED || - conflict->our_status == GIT_DELTA_MODIFIED || - conflict->their_status == GIT_DELTA_ADDED || - conflict->their_status == GIT_DELTA_MODIFIED) - return true; - - return false; -} - -GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child) -{ - size_t child_len = strlen(child); - size_t parent_len = strlen(parent); - - if (child_len < parent_len || - strncmp(parent, child, parent_len) != 0) - return 0; - - return (child[parent_len] == '/'); -} - -GIT_INLINE(int) merge_diff_detect_df_conflict( - struct merge_diff_df_data *df_data, - git_merge_diff *conflict) -{ - const char *cur_path = merge_diff_path(conflict); - - /* Determine if this is a D/F conflict or the child of one */ - if (df_data->df_path && - path_is_prefixed(df_data->df_path, cur_path)) - conflict->type = GIT_MERGE_DIFF_DF_CHILD; - else if(df_data->df_path) - df_data->df_path = NULL; - else if (df_data->prev_path && - merge_diff_any_side_added_or_modified(df_data->prev_conflict) && - merge_diff_any_side_added_or_modified(conflict) && - path_is_prefixed(df_data->prev_path, cur_path)) { - conflict->type = GIT_MERGE_DIFF_DF_CHILD; - - df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE; - df_data->df_path = df_data->prev_path; - } - - df_data->prev_path = cur_path; - df_data->prev_conflict = conflict; - - return 0; -} - -/* Conflict handling */ - -GIT_INLINE(int) merge_diff_detect_type( - git_merge_diff *conflict) -{ - if (conflict->our_status == GIT_DELTA_ADDED && - conflict->their_status == GIT_DELTA_ADDED) - conflict->type = GIT_MERGE_DIFF_BOTH_ADDED; - else if (conflict->our_status == GIT_DELTA_MODIFIED && - conflict->their_status == GIT_DELTA_MODIFIED) - conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED; - else if (conflict->our_status == GIT_DELTA_DELETED && - conflict->their_status == GIT_DELTA_DELETED) - conflict->type = GIT_MERGE_DIFF_BOTH_DELETED; - else if (conflict->our_status == GIT_DELTA_MODIFIED && - conflict->their_status == GIT_DELTA_DELETED) - conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; - else if (conflict->our_status == GIT_DELTA_DELETED && - conflict->their_status == GIT_DELTA_MODIFIED) - conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; - else - conflict->type = GIT_MERGE_DIFF_NONE; - - return 0; -} - -GIT_INLINE(int) index_entry_dup_pool( - git_index_entry *out, - git_pool *pool, - const git_index_entry *src) -{ - if (src != NULL) { - memcpy(out, src, sizeof(git_index_entry)); - if ((out->path = git_pool_strdup(pool, src->path)) == NULL) - return -1; - } - - return 0; -} - -GIT_INLINE(int) merge_delta_type_from_index_entries( - const git_index_entry *ancestor, - const git_index_entry *other) -{ - if (ancestor == NULL && other == NULL) - return GIT_DELTA_UNMODIFIED; - else if (ancestor == NULL && other != NULL) - return GIT_DELTA_ADDED; - else if (ancestor != NULL && other == NULL) - return GIT_DELTA_DELETED; - else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode)) - return GIT_DELTA_TYPECHANGE; - else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode)) - return GIT_DELTA_TYPECHANGE; - else if (git_oid__cmp(&ancestor->id, &other->id) || - ancestor->mode != other->mode) - return GIT_DELTA_MODIFIED; - - return GIT_DELTA_UNMODIFIED; -} - -static git_merge_diff *merge_diff_from_index_entries( - git_merge_diff_list *diff_list, - const git_index_entry **entries) -{ - git_merge_diff *conflict; - git_pool *pool = &diff_list->pool; - - if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL) - return NULL; - - if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 || - index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 || - index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0) - return NULL; - - conflict->our_status = merge_delta_type_from_index_entries( - entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]); - conflict->their_status = merge_delta_type_from_index_entries( - entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]); - - return conflict; -} - -/* Merge trees */ - -static int merge_diff_list_insert_conflict( - git_merge_diff_list *diff_list, - struct merge_diff_df_data *merge_df_data, - const git_index_entry *tree_items[3]) -{ - git_merge_diff *conflict; - - if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL || - merge_diff_detect_type(conflict) < 0 || - merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 || - git_vector_insert(&diff_list->conflicts, conflict) < 0) - return -1; - - return 0; -} - -static int merge_diff_list_insert_unmodified( - git_merge_diff_list *diff_list, - const git_index_entry *tree_items[3]) -{ - int error = 0; - git_index_entry *entry; - - entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0) - error = git_vector_insert(&diff_list->staged, entry); - - return error; -} - -struct merge_diff_find_data { - git_merge_diff_list *diff_list; - struct merge_diff_df_data df_data; -}; - -static int queue_difference(const git_index_entry **entries, void *data) -{ - struct merge_diff_find_data *find_data = data; - bool item_modified = false; - size_t i; - - if (!entries[0] || !entries[1] || !entries[2]) { - item_modified = true; - } else { - for (i = 1; i < 3; i++) { - if (index_entry_cmp(entries[0], entries[i]) != 0) { - item_modified = true; - break; - } - } - } - - return item_modified ? - merge_diff_list_insert_conflict( - find_data->diff_list, &find_data->df_data, entries) : - merge_diff_list_insert_unmodified(find_data->diff_list, entries); -} - -int git_merge_diff_list__find_differences( - git_merge_diff_list *diff_list, - git_iterator *ancestor_iter, - git_iterator *our_iter, - git_iterator *their_iter) -{ - git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter }; - struct merge_diff_find_data find_data = { diff_list }; - - return git_iterator_walk(iterators, 3, queue_difference, &find_data); -} - -git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) -{ - git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list)); - - if (diff_list == NULL) - return NULL; - - diff_list->repo = repo; - - - if (git_pool_init(&diff_list->pool, 1) < 0 || - git_vector_init(&diff_list->staged, 0, NULL) < 0 || - git_vector_init(&diff_list->conflicts, 0, NULL) < 0 || - git_vector_init(&diff_list->resolved, 0, NULL) < 0) { - git_merge_diff_list__free(diff_list); - return NULL; - } - - return diff_list; -} - -void git_merge_diff_list__free(git_merge_diff_list *diff_list) -{ - if (!diff_list) - return; - - git_vector_free(&diff_list->staged); - git_vector_free(&diff_list->conflicts); - git_vector_free(&diff_list->resolved); - git_pool_clear(&diff_list->pool); - git__free(diff_list); -} - -static int merge_normalize_opts( - git_repository *repo, - git_merge_options *opts, - const git_merge_options *given) -{ - git_config *cfg = NULL; - git_config_entry *entry = NULL; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(opts); - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - if (given != NULL) { - memcpy(opts, given, sizeof(git_merge_options)); - } else { - git_merge_options init = GIT_MERGE_OPTIONS_INIT; - memcpy(opts, &init, sizeof(init)); - } - - if ((opts->flags & GIT_MERGE_FIND_RENAMES) && !opts->rename_threshold) - opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; - - if (given && given->default_driver) { - opts->default_driver = git__strdup(given->default_driver); - GIT_ERROR_CHECK_ALLOC(opts->default_driver); - } else { - error = git_config_get_entry(&entry, cfg, "merge.default"); - - if (error == 0) { - opts->default_driver = git__strdup(entry->value); - GIT_ERROR_CHECK_ALLOC(opts->default_driver); - } else if (error == GIT_ENOTFOUND) { - error = 0; - } else { - goto done; - } - } - - if (!opts->target_limit) { - int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); - - if (!limit) - limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); - - opts->target_limit = (limit <= 0) ? - GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit; - } - - /* assign the internal metric with whitespace flag as payload */ - if (!opts->metric) { - opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); - GIT_ERROR_CHECK_ALLOC(opts->metric); - - opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; - opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; - opts->metric->free_signature = git_diff_find_similar__hashsig_free; - opts->metric->similarity = git_diff_find_similar__calc_similarity; - opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; - } - -done: - git_config_entry_free(entry); - return error; -} - - -static int merge_index_insert_reuc( - git_index *index, - size_t idx, - const git_index_entry *entry) -{ - const git_index_reuc_entry *reuc; - int mode[3] = { 0, 0, 0 }; - git_oid const *oid[3] = { NULL, NULL, NULL }; - size_t i; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry)) - return 0; - - if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) { - for (i = 0; i < 3; i++) { - mode[i] = reuc->mode[i]; - oid[i] = &reuc->oid[i]; - } - } - - mode[idx] = entry->mode; - oid[idx] = &entry->id; - - return git_index_reuc_add(index, entry->path, - mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]); -} - -static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list) -{ - int error; - size_t i; - git_merge_diff *conflict; - - /* Add each entry in the resolved conflict to the REUC independently, since - * the paths may differ due to renames. */ - git_vector_foreach(&diff_list->resolved, i, conflict) { - const git_index_entry *ancestor = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - - const git_index_entry *ours = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - - const git_index_entry *theirs = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if (ancestor != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) - return error; - - if (ours != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) - return error; - - if (theirs != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) - return error; - } - - return 0; -} - -static int index_from_diff_list(git_index **out, - git_merge_diff_list *diff_list, bool skip_reuc) -{ - git_index *index; - size_t i; - git_merge_diff *conflict; - int error = 0; - - *out = NULL; - - if ((error = git_index_new(&index)) < 0) - return error; - - if ((error = git_index__fill(index, &diff_list->staged)) < 0) - goto on_error; - - git_vector_foreach(&diff_list->conflicts, i, conflict) { - const git_index_entry *ancestor = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - - const git_index_entry *ours = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - - const git_index_entry *theirs = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0) - goto on_error; - } - - /* Add each rename entry to the rename portion of the index. */ - git_vector_foreach(&diff_list->conflicts, i, conflict) { - const char *ancestor_path, *our_path, *their_path; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) - continue; - - ancestor_path = conflict->ancestor_entry.path; - - our_path = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - conflict->our_entry.path : NULL; - - their_path = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - conflict->their_entry.path : NULL; - - if ((our_path && strcmp(ancestor_path, our_path) != 0) || - (their_path && strcmp(ancestor_path, their_path) != 0)) { - if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0) - goto on_error; - } - } - - if (!skip_reuc) { - if ((error = index_update_reuc(index, diff_list)) < 0) - goto on_error; - } - - *out = index; - return 0; - -on_error: - git_index_free(index); - return error; -} - -static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) -{ - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - - if (given) - return given; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if (git_iterator_for_nothing(empty, &opts) < 0) - return NULL; - - return *empty; -} - -int git_merge__iterators( - git_index **out, - git_repository *repo, - git_iterator *ancestor_iter, - git_iterator *our_iter, - git_iterator *theirs_iter, - const git_merge_options *given_opts) -{ - git_iterator *empty_ancestor = NULL, - *empty_ours = NULL, - *empty_theirs = NULL; - git_merge_diff_list *diff_list; - git_merge_options opts; - git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_diff *conflict; - git_vector changes; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - GIT_ERROR_CHECK_VERSION( - given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options"); - - if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) - return error; - - file_opts.favor = opts.file_favor; - file_opts.flags = opts.file_flags; - - /* use the git-inspired labels when virtual base building */ - if (opts.flags & GIT_MERGE_VIRTUAL_BASE) { - file_opts.ancestor_label = "merged common ancestors"; - file_opts.our_label = "Temporary merge branch 1"; - file_opts.their_label = "Temporary merge branch 2"; - file_opts.flags |= GIT_MERGE_FILE_ACCEPT_CONFLICTS; - file_opts.marker_size = GIT_MERGE_CONFLICT_MARKER_SIZE + 2; - } - - diff_list = git_merge_diff_list__alloc(repo); - GIT_ERROR_CHECK_ALLOC(diff_list); - - ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter); - our_iter = iterator_given_or_empty(&empty_ours, our_iter); - theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter); - - if ((error = git_merge_diff_list__find_differences( - diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 || - (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0) - goto done; - - memcpy(&changes, &diff_list->conflicts, sizeof(git_vector)); - git_vector_clear(&diff_list->conflicts); - - git_vector_foreach(&changes, i, conflict) { - int resolved = 0; - - if ((error = merge_conflict_resolve( - &resolved, diff_list, conflict, &opts, &file_opts)) < 0) - goto done; - - if (!resolved) { - if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) { - git_error_set(GIT_ERROR_MERGE, "merge conflicts exist"); - error = GIT_EMERGECONFLICT; - goto done; - } - - git_vector_insert(&diff_list->conflicts, conflict); - } - } - - error = index_from_diff_list(out, diff_list, - (opts.flags & GIT_MERGE_SKIP_REUC)); - -done: - if (!given_opts || !given_opts->metric) - git__free(opts.metric); - - git__free((char *)opts.default_driver); - - git_merge_diff_list__free(diff_list); - git_iterator_free(empty_ancestor); - git_iterator_free(empty_ours); - git_iterator_free(empty_theirs); - - return error; -} - -int git_merge_trees( - git_index **out, - git_repository *repo, - const git_tree *ancestor_tree, - const git_tree *our_tree, - const git_tree *their_tree, - const git_merge_options *merge_opts) -{ - git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - /* if one side is treesame to the ancestor, take the other side */ - if (ancestor_tree && merge_opts && (merge_opts->flags & GIT_MERGE_SKIP_REUC)) { - const git_tree *result = NULL; - const git_oid *ancestor_tree_id = git_tree_id(ancestor_tree); - - if (our_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(our_tree))) - result = their_tree; - else if (their_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(their_tree))) - result = our_tree; - - if (result) { - if ((error = git_index_new(out)) == 0) - error = git_index_read_tree(*out, result); - - return error; - } - } - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_iterator_for_tree( - &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || - (error = git_iterator_for_tree( - &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || - (error = git_iterator_for_tree( - &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) - goto done; - - error = git_merge__iterators( - out, repo, ancestor_iter, our_iter, their_iter, merge_opts); - -done: - git_iterator_free(ancestor_iter); - git_iterator_free(our_iter); - git_iterator_free(their_iter); - - return error; -} - -static int merge_annotated_commits( - git_index **index_out, - git_annotated_commit **base_out, - git_repository *repo, - git_annotated_commit *our_commit, - git_annotated_commit *their_commit, - size_t recursion_level, - const git_merge_options *opts); - -GIT_INLINE(int) insert_head_ids( - git_array_oid_t *ids, - const git_annotated_commit *annotated_commit) -{ - git_oid *id; - size_t i; - - if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) { - id = git_array_alloc(*ids); - GIT_ERROR_CHECK_ALLOC(id); - - git_oid_cpy(id, git_commit_id(annotated_commit->commit)); - } else { - for (i = 0; i < annotated_commit->parents.size; i++) { - id = git_array_alloc(*ids); - GIT_ERROR_CHECK_ALLOC(id); - - git_oid_cpy(id, &annotated_commit->parents.ptr[i]); - } - } - - return 0; -} - -static int create_virtual_base( - git_annotated_commit **out, - git_repository *repo, - git_annotated_commit *one, - git_annotated_commit *two, - const git_merge_options *opts, - size_t recursion_level) -{ - git_annotated_commit *result = NULL; - git_index *index = NULL; - git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; - - /* Conflicts in the merge base creation do not propagate to conflicts - * in the result; the conflicted base will act as the common ancestor. - */ - if (opts) - memcpy(&virtual_opts, opts, sizeof(git_merge_options)); - - virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; - virtual_opts.flags |= GIT_MERGE_VIRTUAL_BASE; - - if ((merge_annotated_commits(&index, NULL, repo, one, two, - recursion_level + 1, &virtual_opts)) < 0) - return -1; - - result = git__calloc(1, sizeof(git_annotated_commit)); - GIT_ERROR_CHECK_ALLOC(result); - result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; - result->index = index; - - if (insert_head_ids(&result->parents, one) < 0 || - insert_head_ids(&result->parents, two) < 0) { - git_annotated_commit_free(result); - return -1; - } - - *out = result; - return 0; -} - -static int compute_base( - git_annotated_commit **out, - git_repository *repo, - const git_annotated_commit *one, - const git_annotated_commit *two, - const git_merge_options *given_opts, - size_t recursion_level) -{ - git_array_oid_t head_ids = GIT_ARRAY_INIT; - git_oidarray bases = {0}; - git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - size_t i, base_count; - int error; - - *out = NULL; - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_merge_options)); - - /* With more than two commits, merge_bases_many finds the base of - * the first commit and a hypothetical merge of the others. Since - * "one" may itself be a virtual commit, which insert_head_ids - * substitutes multiple ancestors for, it needs to be added - * after "two" which is always a single real commit. - */ - if ((error = insert_head_ids(&head_ids, two)) < 0 || - (error = insert_head_ids(&head_ids, one)) < 0 || - (error = git_merge_bases_many(&bases, repo, - head_ids.size, head_ids.ptr)) < 0) - goto done; - - base_count = (opts.flags & GIT_MERGE_NO_RECURSIVE) ? 0 : bases.count; - - if (base_count) - git_oidarray__reverse(&bases); - - if ((error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0) - goto done; - - for (i = 1; i < base_count; i++) { - recursion_level++; - - if (opts.recursion_limit && recursion_level > opts.recursion_limit) - break; - - if ((error = git_annotated_commit_lookup(&other, repo, - &bases.ids[i])) < 0 || - (error = create_virtual_base(&new_base, repo, base, other, &opts, - recursion_level)) < 0) - goto done; - - git_annotated_commit_free(base); - git_annotated_commit_free(other); - - base = new_base; - new_base = NULL; - other = NULL; - } - -done: - if (error == 0) - *out = base; - else - git_annotated_commit_free(base); - - git_annotated_commit_free(other); - git_annotated_commit_free(new_base); - git_oidarray_dispose(&bases); - git_array_clear(head_ids); - return error; -} - -static int iterator_for_annotated_commit( - git_iterator **out, - git_annotated_commit *commit) -{ - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if (commit == NULL) { - error = git_iterator_for_nothing(out, &opts); - } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) { - error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts); - } else { - if (!commit->tree && - (error = git_commit_tree(&commit->tree, commit->commit)) < 0) - goto done; - - error = git_iterator_for_tree(out, commit->tree, &opts); - } - -done: - return error; -} - -static int merge_annotated_commits( - git_index **index_out, - git_annotated_commit **base_out, - git_repository *repo, - git_annotated_commit *ours, - git_annotated_commit *theirs, - size_t recursion_level, - const git_merge_options *opts) -{ - git_annotated_commit *base = NULL; - git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; - int error; - - if ((error = compute_base(&base, repo, ours, theirs, opts, - recursion_level)) < 0) { - - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - } - - if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || - (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || - (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 || - (error = git_merge__iterators(index_out, repo, base_iter, our_iter, - their_iter, opts)) < 0) - goto done; - - if (base_out) { - *base_out = base; - base = NULL; - } - -done: - git_annotated_commit_free(base); - git_iterator_free(base_iter); - git_iterator_free(our_iter); - git_iterator_free(their_iter); - return error; -} - - -int git_merge_commits( - git_index **out, - git_repository *repo, - const git_commit *our_commit, - const git_commit *their_commit, - const git_merge_options *opts) -{ - git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL; - int error = 0; - - if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 || - (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0) - goto done; - - error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts); - -done: - git_annotated_commit_free(ours); - git_annotated_commit_free(theirs); - git_annotated_commit_free(base); - return error; -} - -/* Merge setup / cleanup */ - -static int write_merge_head( - git_repository *repo, - const git_annotated_commit *heads[], - size_t heads_len) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(heads); - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 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++) { - if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->id_str)) < 0) - goto cleanup; - } - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int write_merge_mode(git_repository *repo) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - GIT_ASSERT_ARG(repo); - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 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) - goto cleanup; - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -struct merge_msg_entry { - const git_annotated_commit *merge_head; - bool written; -}; - -static int msg_entry_is_branch( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - GIT_UNUSED(entries); - - return (entry->written == 0 && - entry->merge_head->remote_url == NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0); -} - -static int msg_entry_is_tracking( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - GIT_UNUSED(entries); - - return (entry->written == 0 && - entry->merge_head->remote_url == NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0); -} - -static int msg_entry_is_tag( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - GIT_UNUSED(entries); - - return (entry->written == 0 && - entry->merge_head->remote_url == NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0); -} - -static int msg_entry_is_remote( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - if (entry->written == 0 && - entry->merge_head->remote_url != NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0) - { - struct merge_msg_entry *existing; - - /* Match only branches from the same remote */ - if (entries->length == 0) - return 1; - - existing = git_vector_get(entries, 0); - - return (git__strcmp(existing->merge_head->remote_url, - entry->merge_head->remote_url) == 0); - } - - return 0; -} - -static int msg_entry_is_oid( - const struct merge_msg_entry *merge_msg_entry) -{ - return (merge_msg_entry->written == 0 && - merge_msg_entry->merge_head->ref_name == NULL && - merge_msg_entry->merge_head->remote_url == NULL); -} - -static int merge_msg_entry_written( - const struct merge_msg_entry *merge_msg_entry) -{ - return (merge_msg_entry->written == 1); -} - -static int merge_msg_entries( - git_vector *v, - const struct merge_msg_entry *entries, - size_t len, - int (*match)(const struct merge_msg_entry *entry, git_vector *entries)) -{ - size_t i; - int matches, total = 0; - - git_vector_clear(v); - - for (i = 0; i < len; i++) { - if ((matches = match(&entries[i], v)) < 0) - return matches; - else if (!matches) - continue; - - git_vector_insert(v, (struct merge_msg_entry *)&entries[i]); - total++; - } - - return total; -} - -static int merge_msg_write_entries( - git_filebuf *file, - git_vector *entries, - const char *item_name, - const char *item_plural_name, - size_t ref_name_skip, - const char *source, - char sep) -{ - struct merge_msg_entry *entry; - size_t i; - int error = 0; - - if (entries->length == 0) - return 0; - - if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0) - goto done; - - if ((error = git_filebuf_printf(file, "%s ", - (entries->length == 1) ? item_name : item_plural_name)) < 0) - goto done; - - git_vector_foreach(entries, i, entry) { - if (i > 0 && - (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0) - goto done; - - if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0) - goto done; - - entry->written = 1; - } - - if (source) - error = git_filebuf_printf(file, " of %s", source); - -done: - return error; -} - -static int merge_msg_write_branches( - git_filebuf *file, - git_vector *entries, - char sep) -{ - return merge_msg_write_entries(file, entries, - "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep); -} - -static int merge_msg_write_tracking( - git_filebuf *file, - git_vector *entries, - char sep) -{ - return merge_msg_write_entries(file, entries, - "remote-tracking branch", "remote-tracking branches", 0, NULL, sep); -} - -static int merge_msg_write_tags( - git_filebuf *file, - git_vector *entries, - char sep) -{ - return merge_msg_write_entries(file, entries, - "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep); -} - -static int merge_msg_write_remotes( - git_filebuf *file, - git_vector *entries, - char sep) -{ - const char *source; - - if (entries->length == 0) - return 0; - - source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url; - - return merge_msg_write_entries(file, entries, - "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep); -} - -static int write_merge_msg( - git_repository *repo, - const git_annotated_commit *heads[], - size_t heads_len) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - struct merge_msg_entry *entries; - git_vector matching = GIT_VECTOR_INIT; - size_t i; - char sep = 0; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(heads); - - entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); - GIT_ERROR_CHECK_ALLOC(entries); - - if (git_vector_init(&matching, heads_len, NULL) < 0) { - git__free(entries); - return -1; - } - - for (i = 0; i < heads_len; i++) - entries[i].merge_head = heads[i]; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 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; - - /* - * This is to emulate the format of MERGE_MSG by core git. - * - * Core git will write all the commits specified by OID, in the order - * provided, until the first named branch or tag is reached, at which - * point all branches will be written in the order provided, then all - * tags, then all remote tracking branches and finally all commits that - * were specified by OID that were not already written. - * - * Yes. Really. - */ - for (i = 0; i < heads_len; i++) { - if (!msg_entry_is_oid(&entries[i])) - break; - - if ((error = git_filebuf_printf(&file, - "%scommit '%s'", (i > 0) ? "; " : "", - entries[i].merge_head->id_str)) < 0) - goto cleanup; - - entries[i].written = 1; - } - - if (i) - sep = ';'; - - if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 || - (error = merge_msg_write_branches(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - - if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 || - (error = merge_msg_write_tracking(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - - if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 || - (error = merge_msg_write_tags(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - - /* We should never be called with multiple remote branches, but handle - * it in case we are... */ - while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) { - if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - } - - if (error < 0) - goto cleanup; - - for (i = 0; i < heads_len; i++) { - if (merge_msg_entry_written(&entries[i])) - continue; - - if ((error = git_filebuf_printf(&file, "; commit '%s'", - entries[i].merge_head->id_str)) < 0) - goto cleanup; - } - - if ((error = git_filebuf_printf(&file, "\n")) < 0 || - (error = git_filebuf_commit(&file)) < 0) - goto cleanup; - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - git_vector_free(&matching); - git__free(entries); - - return error; -} - -int git_merge__setup( - git_repository *repo, - const git_annotated_commit *our_head, - const git_annotated_commit *heads[], - size_t heads_len) -{ - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(our_head); - GIT_ASSERT_ARG(heads); - - if ((error = git_repository__set_orig_head(repo, git_annotated_commit_id(our_head))) == 0 && - (error = write_merge_head(repo, heads, heads_len)) == 0 && - (error = write_merge_mode(repo)) == 0) { - error = write_merge_msg(repo, heads, heads_len); - } - - return error; -} - -/* Merge branches */ - -static int merge_ancestor_head( - git_annotated_commit **ancestor_head, - git_repository *repo, - const git_annotated_commit *our_head, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_oid *oids, ancestor_oid; - size_t i, alloc_len; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(our_head); - GIT_ASSERT_ARG(their_heads); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, their_heads_len, 1); - oids = git__calloc(alloc_len, sizeof(git_oid)); - GIT_ERROR_CHECK_ALLOC(oids); - - git_oid_cpy(&oids[0], git_commit_id(our_head->commit)); - - for (i = 0; i < their_heads_len; i++) - git_oid_cpy(&oids[i + 1], git_annotated_commit_id(their_heads[i])); - - if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0) - goto on_error; - - error = git_annotated_commit_lookup(ancestor_head, repo, &ancestor_oid); - -on_error: - git__free(oids); - return error; -} - -static const char *merge_their_label(const char *branchname) -{ - const char *slash; - - if ((slash = strrchr(branchname, '/')) == NULL) - return branchname; - - if (*(slash+1) == '\0') - return "theirs"; - - return slash+1; -} - -static int merge_normalize_checkout_opts( - git_checkout_options *out, - git_repository *repo, - const git_checkout_options *given_checkout_opts, - unsigned int checkout_strategy, - git_annotated_commit *ancestor, - const git_annotated_commit *our_head, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - int error = 0; - - GIT_UNUSED(repo); - - if (given_checkout_opts != NULL) - memcpy(out, given_checkout_opts, sizeof(git_checkout_options)); - else - memcpy(out, &default_checkout_opts, sizeof(git_checkout_options)); - - out->checkout_strategy = checkout_strategy; - - if (!out->ancestor_label) { - if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL) - out->ancestor_label = git_commit_summary(ancestor->commit); - else if (ancestor) - out->ancestor_label = "merged common ancestors"; - else - out->ancestor_label = "empty base"; - } - - if (!out->our_label) { - if (our_head && our_head->ref_name) - out->our_label = our_head->ref_name; - else - out->our_label = "ours"; - } - - if (!out->their_label) { - if (their_heads_len == 1 && their_heads[0]->ref_name) - out->their_label = merge_their_label(their_heads[0]->ref_name); - else if (their_heads_len == 1) - out->their_label = their_heads[0]->id_str; - else - out->their_label = "theirs"; - } - - return error; -} - -static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) -{ - git_tree *head_tree = NULL; - git_index *index_repo = NULL; - git_iterator *iter_repo = NULL, *iter_new = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - git_diff *staged_diff_list = NULL, *index_diff_list = NULL; - git_diff_delta *delta; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_vector staged_paths = GIT_VECTOR_INIT; - size_t i; - int error = 0; - - GIT_UNUSED(merged_paths); - - *conflicts = 0; - - /* No staged changes may exist unless the change staged is identical to - * the result of the merge. This allows one to apply to merge manually, - * then run merge. Any other staged change would be overwritten by - * a reset merge. - */ - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_repository_index(&index_repo, repo)) < 0 || - (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0) - goto done; - - if (staged_diff_list->deltas.length == 0) - goto done; - - git_vector_foreach(&staged_diff_list->deltas, i, delta) { - if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0) - goto done; - } - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - iter_opts.pathlist.strings = (char **)staged_paths.contents; - iter_opts.pathlist.count = staged_paths.length; - - if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 || - (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || - (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) - goto done; - - *conflicts = index_diff_list->deltas.length; - -done: - git_tree_free(head_tree); - git_index_free(index_repo); - git_iterator_free(iter_repo); - git_iterator_free(iter_new); - git_diff_free(staged_diff_list); - git_diff_free(index_diff_list); - git_vector_free(&staged_paths); - - return error; -} - -static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) -{ - git_diff *wd_diff_list = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - int error = 0; - - GIT_UNUSED(index_new); - - *conflicts = 0; - - /* We need to have merged at least 1 file for the possibility to exist to - * have conflicts with the workdir. Passing 0 as the pathspec count parameter - * will consider all files in the working directory, that is, we may detect - * a conflict if there were untracked files in the workdir prior to starting - * the merge. This typically happens when cherry-picking a commit whose - * changes have already been applied. - */ - if (merged_paths->length == 0) - return 0; - - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - /* Workdir changes may exist iff they do not conflict with changes that - * will be applied by the merge (including conflicts). Ensure that there - * are no changes in the workdir to these paths. - */ - opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; - opts.pathspec.count = merged_paths->length; - opts.pathspec.strings = (char **)merged_paths->contents; - opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; - - if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0) - goto done; - - *conflicts = wd_diff_list->deltas.length; - -done: - git_diff_free(wd_diff_list); - - return error; -} - -int git_merge__check_result(git_repository *repo, git_index *index_new) -{ - git_tree *head_tree = NULL; - git_iterator *iter_head = NULL, *iter_new = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - git_diff *merged_list = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_delta *delta; - git_vector paths = GIT_VECTOR_INIT; - size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts; - const git_index_entry *e; - int error = 0; - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || - (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || - (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) - goto done; - - git_vector_foreach(&merged_list->deltas, i, delta) { - if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0) - goto done; - } - - for (i = 0; i < git_index_entrycount(index_new); i++) { - e = git_index_get_byindex(index_new, i); - - if (git_index_entry_is_conflict(e) && - (git_vector_last(&paths) == NULL || - strcmp(git_vector_last(&paths), e->path) != 0)) { - - if ((error = git_vector_insert(&paths, (char *)e->path)) < 0) - goto done; - } - } - - /* Make sure the index and workdir state do not prevent merging */ - if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || - (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) - goto done; - - if ((conflicts = index_conflicts + wd_conflicts) > 0) { - git_error_set(GIT_ERROR_MERGE, "%" PRIuZ " uncommitted change%s would be overwritten by merge", - conflicts, (conflicts != 1) ? "s" : ""); - error = GIT_ECONFLICT; - } - -done: - git_vector_free(&paths); - git_tree_free(head_tree); - git_iterator_free(iter_head); - git_iterator_free(iter_new); - git_diff_free(merged_list); - - return error; -} - -int git_merge__append_conflicts_to_merge_msg( - git_repository *repo, - git_index *index) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - const char *last = NULL; - size_t i; - int error; - - if (!git_index_has_conflicts(index)) - return 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) - goto cleanup; - - git_filebuf_printf(&file, "\n#Conflicts:\n"); - - for (i = 0; i < git_index_entrycount(index); i++) { - const git_index_entry *e = git_index_get_byindex(index, i); - - if (!git_index_entry_is_conflict(e)) - continue; - - if (last == NULL || strcmp(e->path, last) != 0) - git_filebuf_printf(&file, "#\t%s\n", e->path); - - last = e->path; - } - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int merge_state_cleanup(git_repository *repo) -{ - const char *state_files[] = { - GIT_MERGE_HEAD_FILE, - GIT_MERGE_MODE_FILE, - GIT_MERGE_MSG_FILE, - }; - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -static int merge_heads( - git_annotated_commit **ancestor_head_out, - git_annotated_commit **our_head_out, - git_repository *repo, - git_reference *our_ref, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_annotated_commit *ancestor_head = NULL, *our_head = NULL; - int error = 0; - - *ancestor_head_out = NULL; - *our_head_out = NULL; - - if ((error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0) - goto done; - - if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) { - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - error = 0; - } - - *ancestor_head_out = ancestor_head; - *our_head_out = our_head; - -done: - if (error < 0) { - git_annotated_commit_free(ancestor_head); - git_annotated_commit_free(our_head); - } - - return error; -} - -static int merge_preference(git_merge_preference_t *out, git_repository *repo) -{ - git_config *config; - const char *value; - int bool_value, error = 0; - - *out = GIT_MERGE_PREFERENCE_NONE; - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - goto done; - - if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if (git_config_parse_bool(&bool_value, value) == 0) { - if (!bool_value) - *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; - } else { - if (strcasecmp(value, "only") == 0) - *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; - } - -done: - git_config_free(config); - return error; -} - -int git_merge_analysis_for_ref( - git_merge_analysis_t *analysis_out, - git_merge_preference_t *preference_out, - git_repository *repo, - git_reference *our_ref, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_annotated_commit *ancestor_head = NULL, *our_head = NULL; - int error = 0; - bool unborn; - - GIT_ASSERT_ARG(analysis_out); - GIT_ASSERT_ARG(preference_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(their_heads && their_heads_len > 0); - - if (their_heads_len != 1) { - git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); - error = -1; - goto done; - } - - *analysis_out = GIT_MERGE_ANALYSIS_NONE; - - if ((error = merge_preference(preference_out, repo)) < 0) - goto done; - - if ((error = git_reference__is_unborn_head(&unborn, our_ref, repo)) < 0) - goto done; - - if (unborn) { - *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; - error = 0; - goto done; - } - - if ((error = merge_heads(&ancestor_head, &our_head, repo, our_ref, their_heads, their_heads_len)) < 0) - goto done; - - /* We're up-to-date if we're trying to merge our own common ancestor. */ - if (ancestor_head && git_oid_equal( - git_annotated_commit_id(ancestor_head), git_annotated_commit_id(their_heads[0]))) - *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; - - /* We're fastforwardable if we're our own common ancestor. */ - else if (ancestor_head && git_oid_equal( - git_annotated_commit_id(ancestor_head), git_annotated_commit_id(our_head))) - *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; - - /* Otherwise, just a normal merge is possible. */ - else - *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; - -done: - git_annotated_commit_free(ancestor_head); - git_annotated_commit_free(our_head); - return error; -} - -int git_merge_analysis( - git_merge_analysis_t *analysis_out, - git_merge_preference_t *preference_out, - git_repository *repo, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_reference *head_ref = NULL; - int error = 0; - - if ((error = git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE)) < 0) { - git_error_set(GIT_ERROR_MERGE, "failed to lookup HEAD reference"); - return error; - } - - error = git_merge_analysis_for_ref(analysis_out, preference_out, repo, head_ref, their_heads, their_heads_len); - - git_reference_free(head_ref); - - return error; -} - -int git_merge( - git_repository *repo, - const git_annotated_commit **their_heads, - size_t their_heads_len, - const git_merge_options *merge_opts, - const git_checkout_options *given_checkout_opts) -{ - git_reference *our_ref = NULL; - git_checkout_options checkout_opts; - git_annotated_commit *our_head = NULL, *base = NULL; - git_index *repo_index = NULL, *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - unsigned int checkout_strategy; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(their_heads && their_heads_len > 0); - - if (their_heads_len != 1) { - git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); - return -1; - } - - if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) - goto done; - - checkout_strategy = given_checkout_opts ? - given_checkout_opts->checkout_strategy : - GIT_CHECKOUT_SAFE; - - if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, - &checkout_strategy)) < 0) - goto done; - - if ((error = git_repository_index(&repo_index, repo) < 0) || - (error = git_index_read(repo_index, 0) < 0)) - goto done; - - /* Write the merge setup files to the repository. */ - if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 || - (error = git_merge__setup(repo, our_head, their_heads, - their_heads_len)) < 0) - goto done; - - /* TODO: octopus */ - - if ((error = merge_annotated_commits(&index, &base, repo, our_head, - (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 || - (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0) - goto done; - - /* check out the merge results */ - - if ((error = merge_normalize_checkout_opts(&checkout_opts, repo, - given_checkout_opts, checkout_strategy, - base, our_head, their_heads, their_heads_len)) < 0 || - (error = git_checkout_index(repo, index, &checkout_opts)) < 0) - goto done; - - error = git_indexwriter_commit(&indexwriter); - -done: - if (error < 0) - merge_state_cleanup(repo); - - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_annotated_commit_free(our_head); - git_annotated_commit_free(base); - git_reference_free(our_ref); - git_index_free(repo_index); - - return error; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_merge_init_options(git_merge_options *opts, unsigned int version) -{ - return git_merge_options_init(opts, version); -} -#endif - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) -{ - return git_merge_file_input_init(input, version); -} -#endif - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_merge_file_init_options( - git_merge_file_options *opts, unsigned int version) -{ - return git_merge_file_options_init(opts, version); -} -#endif diff --git a/src/merge.h b/src/merge.h deleted file mode 100644 index 23932905e..000000000 --- a/src/merge.h +++ /dev/null @@ -1,204 +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_merge_h__ -#define INCLUDE_merge_h__ - -#include "common.h" - -#include "vector.h" -#include "commit_list.h" -#include "pool.h" -#include "iterator.h" - -#include "git2/types.h" -#include "git2/merge.h" -#include "git2/sys/merge.h" - -#define GIT_MERGE_MSG_FILE "MERGE_MSG" -#define GIT_MERGE_MODE_FILE "MERGE_MODE" -#define GIT_MERGE_FILE_MODE 0666 - -#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 -#define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 - -/** Types of changes when files are merged from branch to branch. */ -typedef enum { - /* No conflict - a change only occurs in one branch. */ - GIT_MERGE_DIFF_NONE = 0, - - /* Occurs when a file is modified in both branches. */ - GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0), - - /* Occurs when a file is added in both branches. */ - GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1), - - /* Occurs when a file is deleted in both branches. */ - GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2), - - /* Occurs when a file is modified in one branch and deleted in the other. */ - GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3), - - /* Occurs when a file is renamed in one branch and modified in the other. */ - GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4), - - /* Occurs when a file is renamed in one branch and deleted in the other. */ - GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5), - - /* Occurs when a file is renamed in one branch and a file with the same - * name is added in the other. Eg, A->B and new file B. Core git calls - * this a "rename/delete". */ - GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6), - - /* Occurs when both a file is renamed to the same name in the ours and - * theirs branches. Eg, A->B and A->B in both. Automergeable. */ - GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7), - - /* Occurs when a file is renamed to different names in the ours and theirs - * branches. Eg, A->B and A->C. */ - GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8), - - /* Occurs when two files are renamed to the same name in the ours and - * theirs branches. Eg, A->C and B->C. */ - GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9), - - /* Occurs when an item at a path in one branch is a directory, and an - * item at the same path in a different branch is a file. */ - GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10), - - /* The child of a folder that is in a directory/file conflict. */ - GIT_MERGE_DIFF_DF_CHILD = (1 << 11) -} git_merge_diff_t; - -typedef struct { - git_repository *repo; - git_pool pool; - - /* Vector of git_index_entry that represent the merged items that - * have been staged, either because only one side changed, or because - * the two changes were non-conflicting and mergeable. These items - * will be written as staged entries in the main index. - */ - git_vector staged; - - /* Vector of git_merge_diff entries that represent the conflicts that - * have not been automerged. These items will be written to high-stage - * entries in the main index. - */ - git_vector conflicts; - - /* Vector of git_merge_diff that have been automerged. These items - * will be written to the REUC when the index is produced. - */ - git_vector resolved; -} git_merge_diff_list; - -/** - * Description of changes to one file across three trees. - */ -typedef struct { - git_merge_diff_t type; - - git_index_entry ancestor_entry; - - git_index_entry our_entry; - git_delta_t our_status; - - git_index_entry their_entry; - git_delta_t their_status; - -} git_merge_diff; - -int git_merge__bases_many( - git_commit_list **out, - git_revwalk *walk, - git_commit_list_node *one, - git_vector *twos, - uint32_t minimum_generation); - -/* - * Three-way tree differencing - */ - -git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo); - -int git_merge_diff_list__find_differences( - git_merge_diff_list *merge_diff_list, - git_iterator *ancestor_iterator, - git_iterator *ours_iter, - git_iterator *theirs_iter); - -int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts); - -void git_merge_diff_list__free(git_merge_diff_list *diff_list); - -/* Merge metadata setup */ - -int git_merge__setup( - git_repository *repo, - const git_annotated_commit *our_head, - const git_annotated_commit *heads[], - size_t heads_len); - -int git_merge__iterators( - git_index **out, - git_repository *repo, - git_iterator *ancestor_iter, - git_iterator *our_iter, - git_iterator *their_iter, - const git_merge_options *given_opts); - -int git_merge__check_result(git_repository *repo, git_index *index_new); - -int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); - -/* Merge files */ - -GIT_INLINE(const char *) git_merge_file__best_path( - const char *ancestor, - const char *ours, - const char *theirs) -{ - if (!ancestor) { - if (ours && theirs && strcmp(ours, theirs) == 0) - return ours; - - return NULL; - } - - if (ours && strcmp(ancestor, ours) == 0) - return theirs; - else if(theirs && strcmp(ancestor, theirs) == 0) - return ours; - - return NULL; -} - -GIT_INLINE(uint32_t) git_merge_file__best_mode( - uint32_t ancestor, uint32_t ours, uint32_t theirs) -{ - /* - * If ancestor didn't exist and either ours or theirs is executable, - * assume executable. Otherwise, if any mode changed from the ancestor, - * use that one. - */ - if (!ancestor) { - if (ours == GIT_FILEMODE_BLOB_EXECUTABLE || - theirs == GIT_FILEMODE_BLOB_EXECUTABLE) - return GIT_FILEMODE_BLOB_EXECUTABLE; - - return GIT_FILEMODE_BLOB; - } else if (ours && theirs) { - if (ancestor == ours) - return theirs; - - return ours; - } - - return 0; -} - -#endif diff --git a/src/merge_driver.c b/src/merge_driver.c deleted file mode 100644 index 19b35ac0e..000000000 --- a/src/merge_driver.c +++ /dev/null @@ -1,432 +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 "merge_driver.h" - -#include "vector.h" -#include "runtime.h" -#include "merge.h" -#include "git2/merge.h" -#include "git2/sys/merge.h" - -static const char *merge_driver_name__text = "text"; -static const char *merge_driver_name__union = "union"; -static const char *merge_driver_name__binary = "binary"; - -struct merge_driver_registry { - git_rwlock lock; - git_vector drivers; -}; - -typedef struct { - git_merge_driver *driver; - int initialized; - char name[GIT_FLEX_ARRAY]; -} git_merge_driver_entry; - -static struct merge_driver_registry merge_driver_registry; - -static void git_merge_driver_global_shutdown(void); - -git_repository *git_merge_driver_source_repo( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->repo; -} - -const git_index_entry *git_merge_driver_source_ancestor( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->ancestor; -} - -const git_index_entry *git_merge_driver_source_ours( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->ours; -} - -const git_index_entry *git_merge_driver_source_theirs( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->theirs; -} - -const git_merge_file_options *git_merge_driver_source_file_options( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->file_opts; -} - -int git_merge_driver__builtin_apply( - git_merge_driver *self, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; - git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - int error; - - GIT_UNUSED(filter_name); - - if (src->file_opts) - memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); - - if (driver->favor) - file_opts.favor = driver->favor; - - if ((error = git_merge_file_from_index(&result, src->repo, - src->ancestor, src->ours, src->theirs, &file_opts)) < 0) - goto done; - - if (!result.automergeable && - !(file_opts.flags & GIT_MERGE_FILE_ACCEPT_CONFLICTS)) { - error = GIT_EMERGECONFLICT; - goto done; - } - - *path_out = git_merge_file__best_path( - src->ancestor ? src->ancestor->path : NULL, - src->ours ? src->ours->path : NULL, - src->theirs ? src->theirs->path : NULL); - - *mode_out = git_merge_file__best_mode( - src->ancestor ? src->ancestor->mode : 0, - src->ours ? src->ours->mode : 0, - src->theirs ? src->theirs->mode : 0); - - merged_out->ptr = (char *)result.ptr; - merged_out->size = result.len; - merged_out->reserved = 0; - result.ptr = NULL; - -done: - git_merge_file_result_free(&result); - return error; -} - -static int merge_driver_binary_apply( - git_merge_driver *self, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - GIT_UNUSED(self); - GIT_UNUSED(path_out); - GIT_UNUSED(mode_out); - GIT_UNUSED(merged_out); - GIT_UNUSED(filter_name); - GIT_UNUSED(src); - - return GIT_EMERGECONFLICT; -} - -static int merge_driver_entry_cmp(const void *a, const void *b) -{ - const git_merge_driver_entry *entry_a = a; - const git_merge_driver_entry *entry_b = b; - - return strcmp(entry_a->name, entry_b->name); -} - -static int merge_driver_entry_search(const void *a, const void *b) -{ - const char *name_a = a; - const git_merge_driver_entry *entry_b = b; - - return strcmp(name_a, entry_b->name); -} - -git_merge_driver__builtin git_merge_driver__text = { - { - GIT_MERGE_DRIVER_VERSION, - NULL, - NULL, - git_merge_driver__builtin_apply, - }, - GIT_MERGE_FILE_FAVOR_NORMAL -}; - -git_merge_driver__builtin git_merge_driver__union = { - { - GIT_MERGE_DRIVER_VERSION, - NULL, - NULL, - git_merge_driver__builtin_apply, - }, - GIT_MERGE_FILE_FAVOR_UNION -}; - -git_merge_driver git_merge_driver__binary = { - GIT_MERGE_DRIVER_VERSION, - NULL, - NULL, - merge_driver_binary_apply -}; - -/* Note: callers must lock the registry before calling this function */ -static int merge_driver_registry_insert( - const char *name, git_merge_driver *driver) -{ - git_merge_driver_entry *entry; - - entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); - GIT_ERROR_CHECK_ALLOC(entry); - - strcpy(entry->name, name); - entry->driver = driver; - - return git_vector_insert_sorted( - &merge_driver_registry.drivers, entry, NULL); -} - -int git_merge_driver_global_init(void) -{ - int error; - - if (git_rwlock_init(&merge_driver_registry.lock) < 0) - return -1; - - if ((error = git_vector_init(&merge_driver_registry.drivers, 3, - merge_driver_entry_cmp)) < 0) - goto done; - - if ((error = merge_driver_registry_insert( - merge_driver_name__text, &git_merge_driver__text.base)) < 0 || - (error = merge_driver_registry_insert( - merge_driver_name__union, &git_merge_driver__union.base)) < 0 || - (error = merge_driver_registry_insert( - merge_driver_name__binary, &git_merge_driver__binary)) < 0) - goto done; - - error = git_runtime_shutdown_register(git_merge_driver_global_shutdown); - -done: - if (error < 0) - git_vector_free_deep(&merge_driver_registry.drivers); - - return error; -} - -static void git_merge_driver_global_shutdown(void) -{ - git_merge_driver_entry *entry; - size_t i; - - if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) - return; - - git_vector_foreach(&merge_driver_registry.drivers, i, entry) { - if (entry->driver->shutdown) - entry->driver->shutdown(entry->driver); - - git__free(entry); - } - - git_vector_free(&merge_driver_registry.drivers); - - git_rwlock_wrunlock(&merge_driver_registry.lock); - git_rwlock_free(&merge_driver_registry.lock); -} - -/* Note: callers must lock the registry before calling this function */ -static int merge_driver_registry_find(size_t *pos, const char *name) -{ - return git_vector_search2(pos, &merge_driver_registry.drivers, - merge_driver_entry_search, name); -} - -/* Note: callers must lock the registry before calling this function */ -static git_merge_driver_entry *merge_driver_registry_lookup( - size_t *pos, const char *name) -{ - git_merge_driver_entry *entry = NULL; - - if (!merge_driver_registry_find(pos, name)) - entry = git_vector_get(&merge_driver_registry.drivers, *pos); - - return entry; -} - -int git_merge_driver_register(const char *name, git_merge_driver *driver) -{ - int error; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(driver); - - if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); - return -1; - } - - if (!merge_driver_registry_find(NULL, name)) { - git_error_set(GIT_ERROR_MERGE, "attempt to reregister existing driver '%s'", - name); - error = GIT_EEXISTS; - goto done; - } - - error = merge_driver_registry_insert(name, driver); - -done: - git_rwlock_wrunlock(&merge_driver_registry.lock); - return error; -} - -int git_merge_driver_unregister(const char *name) -{ - git_merge_driver_entry *entry; - size_t pos; - int error = 0; - - if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); - return -1; - } - - if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { - git_error_set(GIT_ERROR_MERGE, "cannot find merge driver '%s' to unregister", - name); - error = GIT_ENOTFOUND; - goto done; - } - - git_vector_remove(&merge_driver_registry.drivers, pos); - - if (entry->initialized && entry->driver->shutdown) { - entry->driver->shutdown(entry->driver); - entry->initialized = false; - } - - git__free(entry); - -done: - git_rwlock_wrunlock(&merge_driver_registry.lock); - return error; -} - -git_merge_driver *git_merge_driver_lookup(const char *name) -{ - git_merge_driver_entry *entry; - size_t pos; - int error; - - /* If we've decided the merge driver to use internally - and not - * based on user configuration (in merge_driver_name_for_path) - * then we can use a hardcoded name to compare instead of bothering - * to take a lock and look it up in the vector. - */ - if (name == merge_driver_name__text) - return &git_merge_driver__text.base; - else if (name == merge_driver_name__binary) - return &git_merge_driver__binary; - - if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); - return NULL; - } - - entry = merge_driver_registry_lookup(&pos, name); - - git_rwlock_rdunlock(&merge_driver_registry.lock); - - if (entry == NULL) { - git_error_set(GIT_ERROR_MERGE, "cannot use an unregistered filter"); - return NULL; - } - - if (!entry->initialized) { - if (entry->driver->initialize && - (error = entry->driver->initialize(entry->driver)) < 0) - return NULL; - - entry->initialized = 1; - } - - return entry->driver; -} - -static int merge_driver_name_for_path( - const char **out, - git_repository *repo, - const char *path, - const char *default_driver) -{ - const char *value; - int error; - - *out = NULL; - - if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) - return error; - - /* set: use the built-in 3-way merge driver ("text") */ - if (GIT_ATTR_IS_TRUE(value)) - *out = merge_driver_name__text; - - /* unset: do not merge ("binary") */ - else if (GIT_ATTR_IS_FALSE(value)) - *out = merge_driver_name__binary; - - else if (GIT_ATTR_IS_UNSPECIFIED(value) && default_driver) - *out = default_driver; - - else if (GIT_ATTR_IS_UNSPECIFIED(value)) - *out = merge_driver_name__text; - - else - *out = value; - - return 0; -} - - -GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( - const char *name) -{ - git_merge_driver *driver = git_merge_driver_lookup(name); - - if (driver == NULL) - driver = git_merge_driver_lookup("*"); - - return driver; -} - -int git_merge_driver_for_source( - const char **name_out, - git_merge_driver **driver_out, - const git_merge_driver_source *src) -{ - const char *path, *driver_name; - int error = 0; - - path = git_merge_file__best_path( - src->ancestor ? src->ancestor->path : NULL, - src->ours ? src->ours->path : NULL, - src->theirs ? src->theirs->path : NULL); - - if ((error = merge_driver_name_for_path( - &driver_name, src->repo, path, src->default_driver)) < 0) - return error; - - *name_out = driver_name; - *driver_out = merge_driver_lookup_with_wildcard(driver_name); - return error; -} - diff --git a/src/merge_driver.h b/src/merge_driver.h deleted file mode 100644 index 6b7da5287..000000000 --- a/src/merge_driver.h +++ /dev/null @@ -1,62 +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_merge_driver_h__ -#define INCLUDE_merge_driver_h__ - -#include "common.h" - -#include "git2/merge.h" -#include "git2/index.h" -#include "git2/sys/merge.h" - -struct git_merge_driver_source { - git_repository *repo; - const char *default_driver; - const git_merge_file_options *file_opts; - - const git_index_entry *ancestor; - const git_index_entry *ours; - const git_index_entry *theirs; -}; - -typedef struct git_merge_driver__builtin { - git_merge_driver base; - git_merge_file_favor_t favor; -} git_merge_driver__builtin; - -extern int git_merge_driver_global_init(void); - -extern int git_merge_driver_for_path( - char **name_out, - git_merge_driver **driver_out, - git_repository *repo, - const char *path); - -/* Merge driver configuration */ -extern int git_merge_driver_for_source( - const char **name_out, - git_merge_driver **driver_out, - const git_merge_driver_source *src); - -extern int git_merge_driver__builtin_apply( - git_merge_driver *self, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src); - -/* Merge driver for text files, performs a standard three-way merge */ -extern git_merge_driver__builtin git_merge_driver__text; - -/* Merge driver for union-style merging */ -extern git_merge_driver__builtin git_merge_driver__union; - -/* Merge driver for unmergeable (binary) files: always produces conflicts */ -extern git_merge_driver git_merge_driver__binary; - -#endif diff --git a/src/merge_file.c b/src/merge_file.c deleted file mode 100644 index 732a047b1..000000000 --- a/src/merge_file.c +++ /dev/null @@ -1,327 +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 "common.h" - -#include "repository.h" -#include "posix.h" -#include "futils.h" -#include "index.h" -#include "diff_xdiff.h" -#include "merge.h" - -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/index.h" -#include "git2/merge.h" - -#include "xdiff/xdiff.h" - -/* only examine the first 8000 bytes for binaryness. - * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197 - */ -#define GIT_MERGE_FILE_BINARY_SIZE 8000 - -#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) - -static int merge_file_input_from_index( - git_merge_file_input *input_out, - git_odb_object **odb_object_out, - git_odb *odb, - const git_index_entry *entry) -{ - int error = 0; - - GIT_ASSERT_ARG(input_out); - GIT_ASSERT_ARG(odb_object_out); - GIT_ASSERT_ARG(odb); - GIT_ASSERT_ARG(entry); - - if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0) - goto done; - - input_out->path = entry->path; - input_out->mode = entry->mode; - input_out->ptr = (char *)git_odb_object_data(*odb_object_out); - input_out->size = git_odb_object_size(*odb_object_out); - -done: - return error; -} - -static void merge_file_normalize_opts( - git_merge_file_options *out, - const git_merge_file_options *given_opts) -{ - if (given_opts) - memcpy(out, given_opts, sizeof(git_merge_file_options)); - else { - git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT; - memcpy(out, &default_opts, sizeof(git_merge_file_options)); - } -} - -static int merge_file__xdiff( - git_merge_file_result *out, - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *given_opts) -{ - xmparam_t xmparam; - mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0}; - mmbuffer_t mmbuffer; - git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT; - const char *path; - int xdl_result; - int error = 0; - - memset(out, 0x0, sizeof(git_merge_file_result)); - - merge_file_normalize_opts(&options, given_opts); - - memset(&xmparam, 0x0, sizeof(xmparam_t)); - - if (ours->size > LONG_MAX || - theirs->size > LONG_MAX || - (ancestor && ancestor->size > LONG_MAX)) { - git_error_set(GIT_ERROR_MERGE, "failed to merge files"); - error = -1; - goto done; - } - - if (ancestor) { - xmparam.ancestor = (options.ancestor_label) ? - options.ancestor_label : ancestor->path; - ancestor_mmfile.ptr = (char *)ancestor->ptr; - ancestor_mmfile.size = (long)ancestor->size; - } - - xmparam.file1 = (options.our_label) ? - options.our_label : ours->path; - our_mmfile.ptr = (char *)ours->ptr; - our_mmfile.size = (long)ours->size; - - xmparam.file2 = (options.their_label) ? - options.their_label : theirs->path; - their_mmfile.ptr = (char *)theirs->ptr; - their_mmfile.size = (long)theirs->size; - - if (options.favor == GIT_MERGE_FILE_FAVOR_OURS) - xmparam.favor = XDL_MERGE_FAVOR_OURS; - else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS) - xmparam.favor = XDL_MERGE_FAVOR_THEIRS; - else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION) - xmparam.favor = XDL_MERGE_FAVOR_UNION; - - xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ? - XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS; - - if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3) - xmparam.style = XDL_MERGE_DIFF3; - if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3) - xmparam.style = XDL_MERGE_ZEALOUS_DIFF3; - - if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE) - xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE; - if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE) - xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE; - if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL) - xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; - - if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE) - xmparam.xpp.flags |= XDF_PATIENCE_DIFF; - - if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL) - xmparam.xpp.flags |= XDF_NEED_MINIMAL; - - xmparam.marker_size = options.marker_size; - - if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, - &their_mmfile, &xmparam, &mmbuffer)) < 0) { - git_error_set(GIT_ERROR_MERGE, "failed to merge files"); - error = -1; - goto done; - } - - path = git_merge_file__best_path( - ancestor ? ancestor->path : NULL, - ours->path, - theirs->path); - - if (path != NULL && (out->path = git__strdup(path)) == NULL) { - error = -1; - goto done; - } - - out->automergeable = (xdl_result == 0); - out->ptr = (const char *)mmbuffer.ptr; - out->len = mmbuffer.size; - out->mode = git_merge_file__best_mode( - ancestor ? ancestor->mode : 0, - ours->mode, - theirs->mode); - -done: - if (error < 0) - git_merge_file_result_free(out); - - return error; -} - -static bool merge_file__is_binary(const git_merge_file_input *file) -{ - size_t len = file ? file->size : 0; - - if (len > GIT_XDIFF_MAX_SIZE) - return true; - if (len > GIT_MERGE_FILE_BINARY_SIZE) - len = GIT_MERGE_FILE_BINARY_SIZE; - - return len ? (memchr(file->ptr, 0, len) != NULL) : false; -} - -static int merge_file__binary( - git_merge_file_result *out, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *given_opts) -{ - const git_merge_file_input *favored = NULL; - - memset(out, 0x0, sizeof(git_merge_file_result)); - - if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS) - favored = ours; - else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS) - favored = theirs; - else - goto done; - - if ((out->path = git__strdup(favored->path)) == NULL || - (out->ptr = git__malloc(favored->size)) == NULL) - goto done; - - memcpy((char *)out->ptr, favored->ptr, favored->size); - out->len = favored->size; - out->mode = favored->mode; - out->automergeable = 1; - -done: - return 0; -} - -static int merge_file__from_inputs( - git_merge_file_result *out, - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *given_opts) -{ - if (merge_file__is_binary(ancestor) || - merge_file__is_binary(ours) || - merge_file__is_binary(theirs)) - return merge_file__binary(out, ours, theirs, given_opts); - - return merge_file__xdiff(out, ancestor, ours, theirs, given_opts); -} - -static git_merge_file_input *git_merge_file__normalize_inputs( - git_merge_file_input *out, - const git_merge_file_input *given) -{ - memcpy(out, given, sizeof(git_merge_file_input)); - - if (!out->path) - out->path = "file.txt"; - - if (!out->mode) - out->mode = 0100644; - - return out; -} - -int git_merge_file( - git_merge_file_result *out, - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *options) -{ - git_merge_file_input inputs[3] = { {0} }; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ours); - GIT_ASSERT_ARG(theirs); - - memset(out, 0x0, sizeof(git_merge_file_result)); - - if (ancestor) - ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor); - - ours = git_merge_file__normalize_inputs(&inputs[1], ours); - theirs = git_merge_file__normalize_inputs(&inputs[2], theirs); - - return merge_file__from_inputs(out, ancestor, ours, theirs, options); -} - -int git_merge_file_from_index( - git_merge_file_result *out, - git_repository *repo, - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs, - const git_merge_file_options *options) -{ - git_merge_file_input *ancestor_ptr = NULL, - ancestor_input = {0}, our_input = {0}, their_input = {0}; - git_odb *odb = NULL; - git_odb_object *odb_object[3] = { 0 }; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ours); - GIT_ASSERT_ARG(theirs); - - memset(out, 0x0, sizeof(git_merge_file_result)); - - if ((error = git_repository_odb(&odb, repo)) < 0) - goto done; - - if (ancestor) { - if ((error = merge_file_input_from_index( - &ancestor_input, &odb_object[0], odb, ancestor)) < 0) - goto done; - - ancestor_ptr = &ancestor_input; - } - - if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 || - (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0) - goto done; - - error = merge_file__from_inputs(out, - ancestor_ptr, &our_input, &their_input, options); - -done: - git_odb_object_free(odb_object[0]); - git_odb_object_free(odb_object[1]); - git_odb_object_free(odb_object[2]); - git_odb_free(odb); - - return error; -} - -void git_merge_file_result_free(git_merge_file_result *result) -{ - if (result == NULL) - return; - - git__free((char *)result->path); - git__free((char *)result->ptr); -} diff --git a/src/message.c b/src/message.c deleted file mode 100644 index ec0103a33..000000000 --- a/src/message.c +++ /dev/null @@ -1,75 +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 "buf.h" - -#include "git2/message.h" - -static size_t line_length_without_trailing_spaces(const char *line, size_t len) -{ - while (len) { - unsigned char c = line[len - 1]; - if (!git__isspace(c)) - break; - len--; - } - - return len; -} - -/* Greatly inspired from git.git "stripspace" */ -/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ -static int git_message__prettify( - git_str *message_out, - const char *message, - int strip_comments, - char comment_char) -{ - const size_t message_len = strlen(message); - - int consecutive_empty_lines = 0; - size_t i, line_length, rtrimmed_line_length; - char *next_newline; - - for (i = 0; i < strlen(message); i += line_length) { - next_newline = memchr(message + i, '\n', message_len - i); - - if (next_newline != NULL) { - line_length = next_newline - (message + i) + 1; - } else { - line_length = message_len - i; - } - - if (strip_comments && line_length && message[i] == comment_char) - continue; - - rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); - - if (!rtrimmed_line_length) { - consecutive_empty_lines++; - continue; - } - - if (consecutive_empty_lines > 0 && message_out->size > 0) - git_str_putc(message_out, '\n'); - - consecutive_empty_lines = 0; - git_str_put(message_out, message + i, rtrimmed_line_length); - git_str_putc(message_out, '\n'); - } - - return git_str_oom(message_out) ? -1 : 0; -} - -int git_message_prettify( - git_buf *message_out, - const char *message, - int strip_comments, - char comment_char) -{ - GIT_BUF_WRAP_PRIVATE(message_out, git_message__prettify, message, strip_comments, comment_char); -} diff --git a/src/midx.c b/src/midx.c deleted file mode 100644 index 0092601f6..000000000 --- a/src/midx.c +++ /dev/null @@ -1,901 +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 "midx.h" - -#include "array.h" -#include "buf.h" -#include "filebuf.h" -#include "futils.h" -#include "hash.h" -#include "odb.h" -#include "pack.h" -#include "fs_path.h" -#include "repository.h" -#include "str.h" - -#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ -#define MIDX_VERSION 1 -#define MIDX_OBJECT_ID_VERSION 1 -struct git_midx_header { - uint32_t signature; - uint8_t version; - uint8_t object_id_version; - uint8_t chunks; - uint8_t base_midx_files; - uint32_t packfiles; -}; - -#define MIDX_PACKFILE_NAMES_ID 0x504e414d /* "PNAM" */ -#define MIDX_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ -#define MIDX_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ -#define MIDX_OBJECT_OFFSETS_ID 0x4f4f4646 /* "OOFF" */ -#define MIDX_OBJECT_LARGE_OFFSETS_ID 0x4c4f4646 /* "LOFF" */ - -struct git_midx_chunk { - off64_t offset; - size_t length; -}; - -typedef int (*midx_write_cb)(const char *buf, size_t size, void *cb_data); - -static int midx_error(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "invalid multi-pack-index file - %s", message); - return -1; -} - -static int midx_parse_packfile_names( - git_midx_file *idx, - const unsigned char *data, - uint32_t packfiles, - struct git_midx_chunk *chunk) -{ - int error; - uint32_t i; - char *packfile_name = (char *)(data + chunk->offset); - size_t chunk_size = chunk->length, len; - if (chunk->offset == 0) - return midx_error("missing Packfile Names chunk"); - if (chunk->length == 0) - return midx_error("empty Packfile Names chunk"); - if ((error = git_vector_init(&idx->packfile_names, packfiles, git__strcmp_cb)) < 0) - return error; - for (i = 0; i < packfiles; ++i) { - len = p_strnlen(packfile_name, chunk_size); - if (len == 0) - return midx_error("empty packfile name"); - if (len + 1 > chunk_size) - return midx_error("unterminated packfile name"); - git_vector_insert(&idx->packfile_names, packfile_name); - if (i && strcmp(git_vector_get(&idx->packfile_names, i - 1), packfile_name) >= 0) - return midx_error("packfile names are not sorted"); - if (strlen(packfile_name) <= strlen(".idx") || git__suffixcmp(packfile_name, ".idx") != 0) - return midx_error("non-.idx packfile name"); - if (strchr(packfile_name, '/') != NULL || strchr(packfile_name, '\\') != NULL) - return midx_error("non-local packfile"); - packfile_name += len + 1; - chunk_size -= len + 1; - } - return 0; -} - -static int midx_parse_oid_fanout( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_oid_fanout) -{ - uint32_t i, nr; - if (chunk_oid_fanout->offset == 0) - return midx_error("missing OID Fanout chunk"); - if (chunk_oid_fanout->length == 0) - return midx_error("empty OID Fanout chunk"); - if (chunk_oid_fanout->length != 256 * 4) - return midx_error("OID Fanout chunk has wrong length"); - - idx->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); - nr = 0; - for (i = 0; i < 256; ++i) { - uint32_t n = ntohl(idx->oid_fanout[i]); - if (n < nr) - return midx_error("index is non-monotonic"); - nr = n; - } - idx->num_objects = nr; - return 0; -} - -static int midx_parse_oid_lookup( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_oid_lookup) -{ - uint32_t i; - git_oid *oid, *prev_oid, zero_oid = {{0}}; - - if (chunk_oid_lookup->offset == 0) - return midx_error("missing OID Lookup chunk"); - if (chunk_oid_lookup->length == 0) - return midx_error("empty OID Lookup chunk"); - if (chunk_oid_lookup->length != idx->num_objects * GIT_OID_RAWSZ) - return midx_error("OID Lookup chunk has wrong length"); - - idx->oid_lookup = oid = (git_oid *)(data + chunk_oid_lookup->offset); - prev_oid = &zero_oid; - for (i = 0; i < idx->num_objects; ++i, ++oid) { - if (git_oid_cmp(prev_oid, oid) >= 0) - return midx_error("OID Lookup index is non-monotonic"); - prev_oid = oid; - } - - return 0; -} - -static int midx_parse_object_offsets( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_object_offsets) -{ - if (chunk_object_offsets->offset == 0) - return midx_error("missing Object Offsets chunk"); - if (chunk_object_offsets->length == 0) - return midx_error("empty Object Offsets chunk"); - if (chunk_object_offsets->length != idx->num_objects * 8) - return midx_error("Object Offsets chunk has wrong length"); - - idx->object_offsets = data + chunk_object_offsets->offset; - - return 0; -} - -static int midx_parse_object_large_offsets( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_object_large_offsets) -{ - if (chunk_object_large_offsets->length == 0) - return 0; - if (chunk_object_large_offsets->length % 8 != 0) - return midx_error("malformed Object Large Offsets chunk"); - - idx->object_large_offsets = data + chunk_object_large_offsets->offset; - idx->num_object_large_offsets = chunk_object_large_offsets->length / 8; - - return 0; -} - -int git_midx_parse( - git_midx_file *idx, - const unsigned char *data, - size_t size) -{ - struct git_midx_header *hdr; - const unsigned char *chunk_hdr; - struct git_midx_chunk *last_chunk; - uint32_t i; - off64_t last_chunk_offset, chunk_offset, trailer_offset; - size_t checksum_size; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - int error; - struct git_midx_chunk chunk_packfile_names = {0}, - chunk_oid_fanout = {0}, - chunk_oid_lookup = {0}, - chunk_object_offsets = {0}, - chunk_object_large_offsets = {0}; - - GIT_ASSERT_ARG(idx); - - if (size < sizeof(struct git_midx_header) + GIT_OID_RAWSZ) - return midx_error("multi-pack index is too short"); - - hdr = ((struct git_midx_header *)data); - - if (hdr->signature != htonl(MIDX_SIGNATURE) || - hdr->version != MIDX_VERSION || - hdr->object_id_version != MIDX_OBJECT_ID_VERSION) { - return midx_error("unsupported multi-pack index version"); - } - if (hdr->chunks == 0) - return midx_error("no chunks in multi-pack index"); - - /* - * The very first chunk's offset should be after the header, all the chunk - * headers, and a special zero chunk. - */ - last_chunk_offset = - sizeof(struct git_midx_header) + - (1 + hdr->chunks) * 12; - - checksum_size = GIT_HASH_SHA1_SIZE; - trailer_offset = size - checksum_size; - - if (trailer_offset < last_chunk_offset) - return midx_error("wrong index size"); - memcpy(idx->checksum, data + trailer_offset, checksum_size); - - if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) - return midx_error("could not calculate signature"); - if (memcmp(checksum, idx->checksum, checksum_size) != 0) - return midx_error("index signature mismatch"); - - chunk_hdr = data + sizeof(struct git_midx_header); - last_chunk = NULL; - for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { - uint32_t chunk_id = ntohl(*((uint32_t *)(chunk_hdr + 0))); - uint64_t high_offset = ((uint64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) & 0xffffffffu; - uint64_t low_offset = ((uint64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))) & 0xffffffffu; - - if (high_offset >= INT32_MAX) - return midx_error("chunk offset out of range"); - chunk_offset = (off64_t)(high_offset << 32 | low_offset); - if (chunk_offset < last_chunk_offset) - return midx_error("chunks are non-monotonic"); - if (chunk_offset >= trailer_offset) - return midx_error("chunks extend beyond the trailer"); - if (last_chunk != NULL) - last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); - last_chunk_offset = chunk_offset; - - switch (chunk_id) { - case MIDX_PACKFILE_NAMES_ID: - chunk_packfile_names.offset = last_chunk_offset; - last_chunk = &chunk_packfile_names; - break; - - case MIDX_OID_FANOUT_ID: - chunk_oid_fanout.offset = last_chunk_offset; - last_chunk = &chunk_oid_fanout; - break; - - case MIDX_OID_LOOKUP_ID: - chunk_oid_lookup.offset = last_chunk_offset; - last_chunk = &chunk_oid_lookup; - break; - - case MIDX_OBJECT_OFFSETS_ID: - chunk_object_offsets.offset = last_chunk_offset; - last_chunk = &chunk_object_offsets; - break; - - case MIDX_OBJECT_LARGE_OFFSETS_ID: - chunk_object_large_offsets.offset = last_chunk_offset; - last_chunk = &chunk_object_large_offsets; - break; - - default: - return midx_error("unrecognized chunk ID"); - } - } - last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); - - error = midx_parse_packfile_names( - idx, data, ntohl(hdr->packfiles), &chunk_packfile_names); - if (error < 0) - return error; - error = midx_parse_oid_fanout(idx, data, &chunk_oid_fanout); - if (error < 0) - return error; - error = midx_parse_oid_lookup(idx, data, &chunk_oid_lookup); - if (error < 0) - return error; - error = midx_parse_object_offsets(idx, data, &chunk_object_offsets); - if (error < 0) - return error; - error = midx_parse_object_large_offsets(idx, data, &chunk_object_large_offsets); - if (error < 0) - return error; - - return 0; -} - -int git_midx_open( - git_midx_file **idx_out, - const char *path) -{ - git_midx_file *idx; - git_file fd = -1; - size_t idx_size; - struct stat st; - int error; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return fd; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "multi-pack-index file not found - '%s'", path); - return -1; - } - - if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); - return -1; - } - idx_size = (size_t)st.st_size; - - idx = git__calloc(1, sizeof(git_midx_file)); - GIT_ERROR_CHECK_ALLOC(idx); - - error = git_str_sets(&idx->filename, path); - if (error < 0) - return error; - - error = git_futils_mmap_ro(&idx->index_map, fd, 0, idx_size); - p_close(fd); - if (error < 0) { - git_midx_free(idx); - return error; - } - - if ((error = git_midx_parse(idx, idx->index_map.data, idx_size)) < 0) { - git_midx_free(idx); - return error; - } - - *idx_out = idx; - return 0; -} - -bool git_midx_needs_refresh( - const git_midx_file *idx, - const char *path) -{ - git_file fd = -1; - struct stat st; - ssize_t bytes_read; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return true; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - return true; - } - - if (!S_ISREG(st.st_mode) || - !git__is_sizet(st.st_size) || - (size_t)st.st_size != idx->index_map.len) { - p_close(fd); - return true; - } - - checksum_size = GIT_HASH_SHA1_SIZE; - bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - GIT_OID_RAWSZ); - p_close(fd); - - if (bytes_read != (ssize_t)checksum_size) - return true; - - return (memcmp(checksum, idx->checksum, checksum_size) != 0); -} - -int git_midx_entry_find( - git_midx_entry *e, - git_midx_file *idx, - const git_oid *short_oid, - size_t len) -{ - int pos, found = 0; - size_t pack_index; - uint32_t hi, lo; - const git_oid *current = NULL; - const unsigned char *object_offset; - off64_t offset; - - GIT_ASSERT_ARG(idx); - - hi = ntohl(idx->oid_fanout[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(idx->oid_fanout[(int)short_oid->id[0] - 1])); - - pos = git_pack__lookup_sha1(idx->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = idx->oid_lookup + pos; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = -1 - pos; - if (pos < (int)idx->num_objects) { - current = idx->oid_lookup + pos; - - if (!git_oid_ncmp(short_oid, current, len)) - found = 1; - } - } - - if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)idx->num_objects) { - /* Check for ambiguousity */ - const git_oid *next = current + 1; - - if (!git_oid_ncmp(short_oid, next, len)) { - found = 2; - } - } - - if (!found) - return git_odb__error_notfound("failed to find offset for multi-pack index entry", short_oid, len); - if (found > 1) - return git_odb__error_ambiguous("found multiple offsets for multi-pack index entry"); - - object_offset = idx->object_offsets + pos * 8; - offset = ntohl(*((uint32_t *)(object_offset + 4))); - if (offset & 0x80000000) { - uint32_t object_large_offsets_pos = offset & 0x7fffffff; - const unsigned char *object_large_offsets_index = idx->object_large_offsets; - - /* Make sure we're not being sent out of bounds */ - if (object_large_offsets_pos >= idx->num_object_large_offsets) - return git_odb__error_notfound("invalid index into the object large offsets table", short_oid, len); - - object_large_offsets_index += 8 * object_large_offsets_pos; - - offset = (((uint64_t)ntohl(*((uint32_t *)(object_large_offsets_index + 0)))) << 32) | - ntohl(*((uint32_t *)(object_large_offsets_index + 4))); - } - pack_index = ntohl(*((uint32_t *)(object_offset + 0))); - if (pack_index >= git_vector_length(&idx->packfile_names)) - return midx_error("invalid index into the packfile names table"); - e->pack_index = pack_index; - e->offset = offset; - git_oid_cpy(&e->sha1, current); - return 0; -} - -int git_midx_foreach_entry( - git_midx_file *idx, - git_odb_foreach_cb cb, - void *data) -{ - size_t i; - int error; - - GIT_ASSERT_ARG(idx); - - for (i = 0; i < idx->num_objects; ++i) { - if ((error = cb(&idx->oid_lookup[i], data)) != 0) - return git_error_set_after_callback(error); - } - - return error; -} - -int git_midx_close(git_midx_file *idx) -{ - GIT_ASSERT_ARG(idx); - - if (idx->index_map.data) - git_futils_mmap_free(&idx->index_map); - - git_vector_free(&idx->packfile_names); - - return 0; -} - -void git_midx_free(git_midx_file *idx) -{ - if (!idx) - return; - - git_str_dispose(&idx->filename); - git_midx_close(idx); - git__free(idx); -} - -static int packfile__cmp(const void *a_, const void *b_) -{ - const struct git_pack_file *a = a_; - const struct git_pack_file *b = b_; - - return strcmp(a->pack_name, b->pack_name); -} - -int git_midx_writer_new( - git_midx_writer **out, - const char *pack_dir) -{ - git_midx_writer *w = git__calloc(1, sizeof(git_midx_writer)); - GIT_ERROR_CHECK_ALLOC(w); - - if (git_str_sets(&w->pack_dir, pack_dir) < 0) { - git__free(w); - return -1; - } - git_fs_path_squash_slashes(&w->pack_dir); - - if (git_vector_init(&w->packs, 0, packfile__cmp) < 0) { - git_str_dispose(&w->pack_dir); - git__free(w); - return -1; - } - - *out = w; - return 0; -} - -void git_midx_writer_free(git_midx_writer *w) -{ - struct git_pack_file *p; - size_t i; - - if (!w) - return; - - git_vector_foreach (&w->packs, i, p) - git_mwindow_put_pack(p); - git_vector_free(&w->packs); - git_str_dispose(&w->pack_dir); - git__free(w); -} - -int git_midx_writer_add( - git_midx_writer *w, - const char *idx_path) -{ - git_str idx_path_buf = GIT_STR_INIT; - int error; - struct git_pack_file *p; - - error = git_fs_path_prettify(&idx_path_buf, idx_path, git_str_cstr(&w->pack_dir)); - if (error < 0) - return error; - - error = git_mwindow_get_pack(&p, git_str_cstr(&idx_path_buf)); - git_str_dispose(&idx_path_buf); - if (error < 0) - return error; - - error = git_vector_insert(&w->packs, p); - if (error < 0) { - git_mwindow_put_pack(p); - return error; - } - - return 0; -} - -typedef git_array_t(git_midx_entry) object_entry_array_t; - -struct object_entry_cb_state { - uint32_t pack_index; - object_entry_array_t *object_entries_array; -}; - -static int object_entry__cb(const git_oid *oid, off64_t offset, void *data) -{ - struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; - - git_midx_entry *entry = git_array_alloc(*state->object_entries_array); - GIT_ERROR_CHECK_ALLOC(entry); - - git_oid_cpy(&entry->sha1, oid); - entry->offset = offset; - entry->pack_index = state->pack_index; - - return 0; -} - -static int object_entry__cmp(const void *a_, const void *b_) -{ - const git_midx_entry *a = (const git_midx_entry *)a_; - const git_midx_entry *b = (const git_midx_entry *)b_; - - return git_oid_cmp(&a->sha1, &b->sha1); -} - -static int write_offset(off64_t offset, midx_write_cb write_cb, void *cb_data) -{ - int error; - uint32_t word; - - word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - - return 0; -} - -static int write_chunk_header(int chunk_id, off64_t offset, midx_write_cb write_cb, void *cb_data) -{ - uint32_t word = htonl(chunk_id); - int error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - return write_offset(offset, write_cb, cb_data); - - return 0; -} - -static int midx_write_buf(const char *buf, size_t size, void *data) -{ - git_str *b = (git_str *)data; - return git_str_put(b, buf, size); -} - -struct midx_write_hash_context { - midx_write_cb write_cb; - void *cb_data; - git_hash_ctx *ctx; -}; - -static int midx_write_hash(const char *buf, size_t size, void *data) -{ - struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data; - int error; - - error = git_hash_update(ctx->ctx, buf, size); - if (error < 0) - return error; - - return ctx->write_cb(buf, size, ctx->cb_data); -} - -static int midx_write( - git_midx_writer *w, - midx_write_cb write_cb, - void *cb_data) -{ - int error = 0; - size_t i; - struct git_pack_file *p; - struct git_midx_header hdr = {0}; - uint32_t oid_fanout_count; - uint32_t object_large_offsets_count; - uint32_t oid_fanout[256]; - off64_t offset; - git_str packfile_names = GIT_STR_INIT, - oid_lookup = GIT_STR_INIT, - object_offsets = GIT_STR_INIT, - object_large_offsets = GIT_STR_INIT; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - git_midx_entry *entry; - object_entry_array_t object_entries_array = GIT_ARRAY_INIT; - git_vector object_entries = GIT_VECTOR_INIT; - git_hash_ctx ctx; - struct midx_write_hash_context hash_cb_data = {0}; - - hdr.signature = htonl(MIDX_SIGNATURE); - hdr.version = MIDX_VERSION; - hdr.object_id_version = MIDX_OBJECT_ID_VERSION; - hdr.base_midx_files = 0; - - hash_cb_data.write_cb = write_cb; - hash_cb_data.cb_data = cb_data; - hash_cb_data.ctx = &ctx; - - checksum_size = GIT_HASH_SHA1_SIZE; - error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); - if (error < 0) - return error; - cb_data = &hash_cb_data; - write_cb = midx_write_hash; - - git_vector_sort(&w->packs); - git_vector_foreach (&w->packs, i, p) { - git_str relative_index = GIT_STR_INIT; - struct object_entry_cb_state state = {0}; - size_t path_len; - - state.pack_index = (uint32_t)i; - state.object_entries_array = &object_entries_array; - - error = git_str_sets(&relative_index, p->pack_name); - if (error < 0) - goto cleanup; - error = git_fs_path_make_relative(&relative_index, git_str_cstr(&w->pack_dir)); - if (error < 0) { - git_str_dispose(&relative_index); - goto cleanup; - } - path_len = git_str_len(&relative_index); - if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(&relative_index), ".pack") != 0) { - git_str_dispose(&relative_index); - git_error_set(GIT_ERROR_INVALID, "invalid packfile name: '%s'", p->pack_name); - error = -1; - goto cleanup; - } - path_len -= strlen(".pack"); - - git_str_put(&packfile_names, git_str_cstr(&relative_index), path_len); - git_str_puts(&packfile_names, ".idx"); - git_str_putc(&packfile_names, '\0'); - git_str_dispose(&relative_index); - - error = git_pack_foreach_entry_offset(p, object_entry__cb, &state); - if (error < 0) - goto cleanup; - } - - /* Sort the object entries. */ - error = git_vector_init(&object_entries, git_array_size(object_entries_array), object_entry__cmp); - if (error < 0) - goto cleanup; - git_array_foreach (object_entries_array, i, entry) { - if ((error = git_vector_set(NULL, &object_entries, i, entry)) < 0) - goto cleanup; - } - git_vector_set_sorted(&object_entries, 0); - git_vector_sort(&object_entries); - git_vector_uniq(&object_entries, NULL); - - /* Pad the packfile names so it is a multiple of four. */ - while (git_str_len(&packfile_names) & 3) - git_str_putc(&packfile_names, '\0'); - - /* Fill the OID Fanout table. */ - oid_fanout_count = 0; - for (i = 0; i < 256; i++) { - while (oid_fanout_count < git_vector_length(&object_entries) && - ((const git_midx_entry *)git_vector_get(&object_entries, oid_fanout_count))->sha1.id[0] <= i) - ++oid_fanout_count; - oid_fanout[i] = htonl(oid_fanout_count); - } - - /* Fill the OID Lookup table. */ - git_vector_foreach (&object_entries, i, entry) { - error = git_str_put(&oid_lookup, (const char *)&entry->sha1, sizeof(entry->sha1)); - if (error < 0) - goto cleanup; - } - - /* Fill the Object Offsets and Object Large Offsets tables. */ - object_large_offsets_count = 0; - git_vector_foreach (&object_entries, i, entry) { - uint32_t word; - - word = htonl((uint32_t)entry->pack_index); - error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - if (entry->offset >= 0x80000000l) { - word = htonl(0x80000000u | object_large_offsets_count++); - if ((error = write_offset(entry->offset, midx_write_buf, &object_large_offsets)) < 0) - goto cleanup; - } else { - word = htonl((uint32_t)entry->offset & 0x7fffffffu); - } - - error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - } - - /* Write the header. */ - hdr.packfiles = htonl((uint32_t)git_vector_length(&w->packs)); - hdr.chunks = 4; - if (git_str_len(&object_large_offsets) > 0) - hdr.chunks++; - error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); - if (error < 0) - goto cleanup; - - /* Write the chunk headers. */ - offset = sizeof(hdr) + (hdr.chunks + 1) * 12; - error = write_chunk_header(MIDX_PACKFILE_NAMES_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&packfile_names); - error = write_chunk_header(MIDX_OID_FANOUT_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += sizeof(oid_fanout); - error = write_chunk_header(MIDX_OID_LOOKUP_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&oid_lookup); - error = write_chunk_header(MIDX_OBJECT_OFFSETS_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&object_offsets); - if (git_str_len(&object_large_offsets) > 0) { - error = write_chunk_header(MIDX_OBJECT_LARGE_OFFSETS_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&object_large_offsets); - } - error = write_chunk_header(0, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - - /* Write all the chunks. */ - error = write_cb(git_str_cstr(&packfile_names), git_str_len(&packfile_names), cb_data); - if (error < 0) - goto cleanup; - error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&object_offsets), git_str_len(&object_offsets), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&object_large_offsets), git_str_len(&object_large_offsets), cb_data); - if (error < 0) - goto cleanup; - - /* Finalize the checksum and write the trailer. */ - error = git_hash_final(checksum, &ctx); - if (error < 0) - goto cleanup; - error = write_cb((char *)checksum, checksum_size, cb_data); - if (error < 0) - goto cleanup; - -cleanup: - git_array_clear(object_entries_array); - git_vector_free(&object_entries); - git_str_dispose(&packfile_names); - git_str_dispose(&oid_lookup); - git_str_dispose(&object_offsets); - git_str_dispose(&object_large_offsets); - git_hash_ctx_cleanup(&ctx); - return error; -} - -static int midx_write_filebuf(const char *buf, size_t size, void *data) -{ - git_filebuf *f = (git_filebuf *)data; - return git_filebuf_write(f, buf, size); -} - -int git_midx_writer_commit( - git_midx_writer *w) -{ - int error; - int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; - git_str midx_path = GIT_STR_INIT; - git_filebuf output = GIT_FILEBUF_INIT; - - error = git_str_joinpath(&midx_path, git_str_cstr(&w->pack_dir), "multi-pack-index"); - if (error < 0) - return error; - - if (git_repository__fsync_gitdir) - filebuf_flags |= GIT_FILEBUF_FSYNC; - error = git_filebuf_open(&output, git_str_cstr(&midx_path), filebuf_flags, 0644); - git_str_dispose(&midx_path); - if (error < 0) - return error; - - error = midx_write(w, midx_write_filebuf, &output); - if (error < 0) { - git_filebuf_cleanup(&output); - return error; - } - - return git_filebuf_commit(&output); -} - -int git_midx_writer_dump( - git_buf *midx, - git_midx_writer *w) -{ - git_str str = GIT_STR_INIT; - int error; - - if ((error = git_buf_tostr(&str, midx)) < 0 || - (error = midx_write(w, midx_write_buf, &str)) == 0) - error = git_buf_fromstr(midx, &str); - - git_str_dispose(&str); - return error; -} diff --git a/src/midx.h b/src/midx.h deleted file mode 100644 index 7dd851bd3..000000000 --- a/src/midx.h +++ /dev/null @@ -1,110 +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_midx_h__ -#define INCLUDE_midx_h__ - -#include "common.h" - -#include - -#include "git2/sys/midx.h" - -#include "map.h" -#include "mwindow.h" -#include "odb.h" - -/* - * A multi-pack-index file. - * - * This file contains a merged index for multiple independent .pack files. This - * can help speed up locating objects without requiring a garbage collection - * cycle to create a single .pack file. - * - * Support for this feature was added in git 2.21, and requires the - * `core.multiPackIndex` config option to be set. - */ -typedef struct git_midx_file { - git_map index_map; - - /* The table of Packfile Names. */ - git_vector packfile_names; - - /* The OID Fanout table. */ - const uint32_t *oid_fanout; - /* The total number of objects in the index. */ - uint32_t num_objects; - - /* The OID Lookup table. */ - git_oid *oid_lookup; - - /* The Object Offsets table. Each entry has two 4-byte fields with the pack index and the offset. */ - const unsigned char *object_offsets; - - /* The Object Large Offsets table. */ - const unsigned char *object_large_offsets; - /* The number of entries in the Object Large Offsets table. Each entry has an 8-byte with an offset */ - size_t num_object_large_offsets; - - /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - - /* something like ".git/objects/pack/multi-pack-index". */ - git_str filename; -} git_midx_file; - -/* - * An entry in the multi-pack-index file. Similar in purpose to git_pack_entry. - */ -typedef struct git_midx_entry { - /* The index within idx->packfile_names where the packfile name can be found. */ - size_t pack_index; - /* The offset within the .pack file where the requested object is found. */ - off64_t offset; - /* The SHA-1 hash of the requested object. */ - git_oid sha1; -} git_midx_entry; - -/* - * A writer for `multi-pack-index` files. - */ -struct git_midx_writer { - /* - * The path of the directory where the .pack/.idx files are stored. The - * `multi-pack-index` file will be written to the same directory. - */ - git_str pack_dir; - - /* The list of `git_pack_file`s. */ - git_vector packs; -}; - -int git_midx_open( - git_midx_file **idx_out, - const char *path); -bool git_midx_needs_refresh( - const git_midx_file *idx, - const char *path); -int git_midx_entry_find( - git_midx_entry *e, - git_midx_file *idx, - const git_oid *short_oid, - size_t len); -int git_midx_foreach_entry( - git_midx_file *idx, - git_odb_foreach_cb cb, - void *data); -int git_midx_close(git_midx_file *idx); -void git_midx_free(git_midx_file *idx); - -/* This is exposed for use in the fuzzers. */ -int git_midx_parse( - git_midx_file *idx, - const unsigned char *data, - size_t size); - -#endif diff --git a/src/mwindow.c b/src/mwindow.c deleted file mode 100644 index d06b7a80e..000000000 --- a/src/mwindow.c +++ /dev/null @@ -1,541 +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 "mwindow.h" - -#include "vector.h" -#include "futils.h" -#include "map.h" -#include "runtime.h" -#include "strmap.h" -#include "pack.h" - -#define DEFAULT_WINDOW_SIZE \ - (sizeof(void*) >= 8 \ - ? 1 * 1024 * 1024 * 1024 \ - : 32 * 1024 * 1024) - -#define DEFAULT_MAPPED_LIMIT \ - ((1024 * 1024) * (sizeof(void*) >= 8 ? UINT64_C(8192) : UINT64_C(256))) - -/* default is unlimited */ -#define DEFAULT_FILE_LIMIT 0 - -size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE; -size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; -size_t git_mwindow__file_limit = DEFAULT_FILE_LIMIT; - -/* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */ -git_mutex git__mwindow_mutex; - -/* Whenever you want to read or modify this, grab `git__mwindow_mutex` */ -git_mwindow_ctl git_mwindow__mem_ctl; - -/* Global list of mwindow files, to open packs once across repos */ -git_strmap *git__pack_cache = NULL; - -static void git_mwindow_global_shutdown(void) -{ - git_strmap *tmp = git__pack_cache; - - git_mutex_free(&git__mwindow_mutex); - - git__pack_cache = NULL; - git_strmap_free(tmp); -} - -int git_mwindow_global_init(void) -{ - int error; - - GIT_ASSERT(!git__pack_cache); - - if ((error = git_mutex_init(&git__mwindow_mutex)) < 0 || - (error = git_strmap_new(&git__pack_cache)) < 0) - return error; - - return git_runtime_shutdown_register(git_mwindow_global_shutdown); -} - -int git_mwindow_get_pack(struct git_pack_file **out, const char *path) -{ - struct git_pack_file *pack; - char *packname; - int error; - - if ((error = git_packfile__name(&packname, path)) < 0) - return error; - - if (git_mutex_lock(&git__mwindow_mutex) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex"); - return -1; - } - - pack = git_strmap_get(git__pack_cache, packname); - git__free(packname); - - if (pack != NULL) { - git_atomic32_inc(&pack->refcount); - git_mutex_unlock(&git__mwindow_mutex); - *out = pack; - return 0; - } - - /* If we didn't find it, we need to create it */ - if ((error = git_packfile_alloc(&pack, path)) < 0) { - git_mutex_unlock(&git__mwindow_mutex); - return error; - } - - git_atomic32_inc(&pack->refcount); - - error = git_strmap_set(git__pack_cache, pack->pack_name, pack); - git_mutex_unlock(&git__mwindow_mutex); - if (error < 0) { - git_packfile_free(pack, false); - return error; - } - - *out = pack; - return 0; -} - -int git_mwindow_put_pack(struct git_pack_file *pack) -{ - int count, error; - struct git_pack_file *pack_to_delete = NULL; - - if ((error = git_mutex_lock(&git__mwindow_mutex)) < 0) - return error; - - /* put before get would be a corrupted state */ - GIT_ASSERT(git__pack_cache); - - /* if we cannot find it, the state is corrupted */ - GIT_ASSERT(git_strmap_exists(git__pack_cache, pack->pack_name)); - - count = git_atomic32_dec(&pack->refcount); - if (count == 0) { - git_strmap_delete(git__pack_cache, pack->pack_name); - pack_to_delete = pack; - } - git_mutex_unlock(&git__mwindow_mutex); - git_packfile_free(pack_to_delete, false); - - return 0; -} - -/* - * Free all the windows in a sequence, typically because we're done - * with the file. Needs to hold the git__mwindow_mutex. - */ -static int git_mwindow_free_all_locked(git_mwindow_file *mwf) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - size_t i; - - /* - * Remove these windows from the global list - */ - for (i = 0; i < ctl->windowfiles.length; ++i){ - if (git_vector_get(&ctl->windowfiles, i) == mwf) { - git_vector_remove(&ctl->windowfiles, i); - break; - } - } - - if (ctl->windowfiles.length == 0) { - git_vector_free(&ctl->windowfiles); - ctl->windowfiles.contents = NULL; - } - - while (mwf->windows) { - git_mwindow *w = mwf->windows; - GIT_ASSERT(w->inuse_cnt == 0); - - ctl->mapped -= w->window_map.len; - ctl->open_windows--; - - git_futils_mmap_free(&w->window_map); - - mwf->windows = w->next; - git__free(w); - } - - return 0; -} - -int git_mwindow_free_all(git_mwindow_file *mwf) -{ - int error; - - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return -1; - } - - error = git_mwindow_free_all_locked(mwf); - - git_mutex_unlock(&git__mwindow_mutex); - - return error; -} - -/* - * Check if a window 'win' contains the address 'offset' - */ -int git_mwindow_contains(git_mwindow *win, off64_t offset) -{ - off64_t win_off = win->offset; - return win_off <= offset - && offset <= (off64_t)(win_off + win->window_map.len); -} - -#define GIT_MWINDOW__LRU -1 -#define GIT_MWINDOW__MRU 1 - -/* - * Find the least- or most-recently-used window in a file that is not currently - * being used. The 'only_unused' flag controls whether the caller requires the - * file to only have unused windows. If '*out_window' is non-null, it is used as - * a starting point for the comparison. - * - * Returns whether such a window was found in the file. - */ -static bool git_mwindow_scan_recently_used( - git_mwindow_file *mwf, - git_mwindow **out_window, - git_mwindow **out_last, - bool only_unused, - int comparison_sign) -{ - git_mwindow *w, *w_last; - git_mwindow *lru_window = NULL, *lru_last = NULL; - bool found = false; - - GIT_ASSERT_ARG(mwf); - GIT_ASSERT_ARG(out_window); - - lru_window = *out_window; - if (out_last) - lru_last = *out_last; - - for (w_last = NULL, w = mwf->windows; w; w_last = w, w = w->next) { - if (w->inuse_cnt) { - if (only_unused) - return false; - /* This window is currently being used. Skip it. */ - continue; - } - - /* - * If the current one is more (or less) recent than the last one, - * store it in the output parameter. If lru_window is NULL, - * it's the first loop, so store it as well. - */ - if (!lru_window || - (comparison_sign == GIT_MWINDOW__LRU && lru_window->last_used > w->last_used) || - (comparison_sign == GIT_MWINDOW__MRU && lru_window->last_used < w->last_used)) { - lru_window = w; - lru_last = w_last; - found = true; - } - } - - if (!found) - return false; - - *out_window = lru_window; - if (out_last) - *out_last = lru_last; - return true; -} - -/* - * Close the least recently used window (that is currently not being used) out - * of all the files. Called under lock from new_window_locked. - */ -static int git_mwindow_close_lru_window_locked(void) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow_file *cur; - size_t i; - git_mwindow *lru_window = NULL, *lru_last = NULL, **list = NULL; - - git_vector_foreach(&ctl->windowfiles, i, cur) { - if (git_mwindow_scan_recently_used( - cur, &lru_window, &lru_last, false, GIT_MWINDOW__LRU)) { - list = &cur->windows; - } - } - - if (!lru_window) { - git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU"); - return -1; - } - - ctl->mapped -= lru_window->window_map.len; - git_futils_mmap_free(&lru_window->window_map); - - if (lru_last) - lru_last->next = lru_window->next; - else - *list = lru_window->next; - - git__free(lru_window); - ctl->open_windows--; - - return 0; -} - -/* - * Finds the file that does not have any open windows AND whose - * most-recently-used window is the least-recently used one across all - * currently open files. - * - * Called under lock from new_window_locked. - */ -static int git_mwindow_find_lru_file_locked(git_mwindow_file **out) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow_file *lru_file = NULL, *current_file = NULL; - git_mwindow *lru_window = NULL; - size_t i; - - git_vector_foreach(&ctl->windowfiles, i, current_file) { - git_mwindow *mru_window = NULL; - if (!git_mwindow_scan_recently_used( - current_file, &mru_window, NULL, true, GIT_MWINDOW__MRU)) { - continue; - } - if (!lru_window || lru_window->last_used > mru_window->last_used) { - lru_window = mru_window; - lru_file = current_file; - } - } - - if (!lru_file) { - git_error_set(GIT_ERROR_OS, "failed to close memory window file; couldn't find LRU"); - return -1; - } - - *out = lru_file; - return 0; -} - -/* This gets called under lock from git_mwindow_open */ -static git_mwindow *new_window_locked( - git_file fd, - off64_t size, - off64_t offset) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - size_t walign = git_mwindow__window_size / 2; - off64_t len; - git_mwindow *w; - - w = git__calloc(1, sizeof(*w)); - - if (w == NULL) - return NULL; - - w->offset = (offset / walign) * walign; - - len = size - w->offset; - if (len > (off64_t)git_mwindow__window_size) - len = (off64_t)git_mwindow__window_size; - - ctl->mapped += (size_t)len; - - while (git_mwindow__mapped_limit < ctl->mapped && - git_mwindow_close_lru_window_locked() == 0) /* nop */; - - /* - * We treat `mapped_limit` as a soft limit. If we can't find a - * window to close and are above the limit, we still mmap the new - * window. - */ - - if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { - /* - * The first error might be down to memory fragmentation even if - * we're below our soft limits, so free up what we can and try again. - */ - - while (git_mwindow_close_lru_window_locked() == 0) - /* nop */; - - if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { - git__free(w); - return NULL; - } - } - - ctl->mmap_calls++; - ctl->open_windows++; - - if (ctl->mapped > ctl->peak_mapped) - ctl->peak_mapped = ctl->mapped; - - if (ctl->open_windows > ctl->peak_open_windows) - ctl->peak_open_windows = ctl->open_windows; - - return w; -} - -/* - * Open a new window, closing the least recenty used until we have - * enough space. Don't forget to add it to your list - */ -unsigned char *git_mwindow_open( - git_mwindow_file *mwf, - git_mwindow **cursor, - off64_t offset, - size_t extra, - unsigned int *left) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow *w = *cursor; - - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return NULL; - } - - if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { - if (w) { - w->inuse_cnt--; - } - - for (w = mwf->windows; w; w = w->next) { - if (git_mwindow_contains(w, offset) && - git_mwindow_contains(w, offset + extra)) - break; - } - - /* - * If there isn't a suitable window, we need to create a new - * one. - */ - if (!w) { - w = new_window_locked(mwf->fd, mwf->size, offset); - if (w == NULL) { - git_mutex_unlock(&git__mwindow_mutex); - return NULL; - } - w->next = mwf->windows; - mwf->windows = w; - } - } - - /* If we changed w, store it in the cursor */ - if (w != *cursor) { - w->last_used = ctl->used_ctr++; - w->inuse_cnt++; - *cursor = w; - } - - offset -= w->offset; - - if (left) - *left = (unsigned int)(w->window_map.len - offset); - - git_mutex_unlock(&git__mwindow_mutex); - return (unsigned char *) w->window_map.data + offset; -} - -int git_mwindow_file_register(git_mwindow_file *mwf) -{ - git_vector closed_files = GIT_VECTOR_INIT; - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - int error; - size_t i; - git_mwindow_file *closed_file = NULL; - - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return -1; - } - - if (ctl->windowfiles.length == 0 && - (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < 0) { - git_mutex_unlock(&git__mwindow_mutex); - goto cleanup; - } - - if (git_mwindow__file_limit) { - git_mwindow_file *lru_file; - while (git_mwindow__file_limit <= ctl->windowfiles.length && - git_mwindow_find_lru_file_locked(&lru_file) == 0) { - if ((error = git_vector_insert(&closed_files, lru_file)) < 0) { - /* - * Exceeding the file limit seems preferable to being open to - * data races that can end up corrupting the heap. - */ - break; - } - git_mwindow_free_all_locked(lru_file); - } - } - - error = git_vector_insert(&ctl->windowfiles, mwf); - git_mutex_unlock(&git__mwindow_mutex); - if (error < 0) - goto cleanup; - - /* - * Once we have released the global windowfiles lock, we can close each - * individual file. Before doing so, acquire that file's lock to avoid - * closing a file that is currently being used. - */ - git_vector_foreach(&closed_files, i, closed_file) { - error = git_mutex_lock(&closed_file->lock); - if (error < 0) - continue; - p_close(closed_file->fd); - closed_file->fd = -1; - git_mutex_unlock(&closed_file->lock); - } - -cleanup: - git_vector_free(&closed_files); - return error; -} - -void git_mwindow_file_deregister(git_mwindow_file *mwf) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow_file *cur; - size_t i; - - if (git_mutex_lock(&git__mwindow_mutex)) - return; - - git_vector_foreach(&ctl->windowfiles, i, cur) { - if (cur == mwf) { - git_vector_remove(&ctl->windowfiles, i); - git_mutex_unlock(&git__mwindow_mutex); - return; - } - } - git_mutex_unlock(&git__mwindow_mutex); -} - -void git_mwindow_close(git_mwindow **window) -{ - git_mwindow *w = *window; - if (w) { - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return; - } - - w->inuse_cnt--; - git_mutex_unlock(&git__mwindow_mutex); - *window = NULL; - } -} diff --git a/src/mwindow.h b/src/mwindow.h deleted file mode 100644 index e3a03f019..000000000 --- a/src/mwindow.h +++ /dev/null @@ -1,54 +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_mwindow__ -#define INCLUDE_mwindow__ - -#include "common.h" - -#include "map.h" -#include "vector.h" - -typedef struct git_mwindow { - struct git_mwindow *next; - git_map window_map; - off64_t offset; - size_t last_used; - size_t inuse_cnt; -} git_mwindow; - -typedef struct git_mwindow_file { - git_mutex lock; /* protects updates to fd */ - git_mwindow *windows; - int fd; - off64_t size; -} git_mwindow_file; - -typedef struct git_mwindow_ctl { - size_t mapped; - unsigned int open_windows; - unsigned int mmap_calls; - unsigned int peak_open_windows; - size_t peak_mapped; - size_t used_ctr; - git_vector windowfiles; -} git_mwindow_ctl; - -int git_mwindow_contains(git_mwindow *win, off64_t offset); -int git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ -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); - -extern int git_mwindow_global_init(void); - -struct git_pack_file; /* just declaration to avoid cyclical includes */ -int git_mwindow_get_pack(struct git_pack_file **out, const char *path); -int git_mwindow_put_pack(struct git_pack_file *pack); - -#endif diff --git a/src/net.c b/src/net.c deleted file mode 100644 index a76fd1d7c..000000000 --- a/src/net.c +++ /dev/null @@ -1,750 +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 "net.h" -#include "netops.h" - -#include - -#include "posix.h" -#include "str.h" -#include "http_parser.h" -#include "runtime.h" - -#define DEFAULT_PORT_HTTP "80" -#define DEFAULT_PORT_HTTPS "443" -#define DEFAULT_PORT_GIT "9418" -#define DEFAULT_PORT_SSH "22" - -bool git_net_str_is_url(const char *str) -{ - const char *c; - - for (c = str; *c; c++) { - if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') - return true; - - if ((*c < 'a' || *c > 'z') && - (*c < 'A' || *c > 'Z') && - (*c < '0' || *c > '9') && - (*c != '+' && *c != '-' && *c != '.')) - break; - } - - return false; -} - -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 || - strcmp(scheme, "ssh+git") == 0 || - strcmp(scheme, "git+ssh") == 0) - return DEFAULT_PORT_SSH; - - return NULL; -} - -int git_net_url_dup(git_net_url *out, git_net_url *in) -{ - if (in->scheme) { - out->scheme = git__strdup(in->scheme); - GIT_ERROR_CHECK_ALLOC(out->scheme); - } - - if (in->host) { - out->host = git__strdup(in->host); - GIT_ERROR_CHECK_ALLOC(out->host); - } - - if (in->port) { - out->port = git__strdup(in->port); - GIT_ERROR_CHECK_ALLOC(out->port); - } - - if (in->path) { - out->path = git__strdup(in->path); - GIT_ERROR_CHECK_ALLOC(out->path); - } - - if (in->query) { - out->query = git__strdup(in->query); - GIT_ERROR_CHECK_ALLOC(out->query); - } - - if (in->username) { - out->username = git__strdup(in->username); - GIT_ERROR_CHECK_ALLOC(out->username); - } - - if (in->password) { - out->password = git__strdup(in->password); - GIT_ERROR_CHECK_ALLOC(out->password); - } - - return 0; -} - -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_str scheme = GIT_STR_INIT, - host = GIT_STR_INIT, - port = GIT_STR_INIT, - path = GIT_STR_INIT, - username = GIT_STR_INIT, - password = GIT_STR_INIT, - query = GIT_STR_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_str_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_str_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_str_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_str_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_str_put(&path, url_path, url_path_len); - } else { - git_str_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_str_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_str_decode_percent(&username, url_username, url_username_len); - git_str_decode_percent(&password, url_password, url_password_len); - } else { - git_str_decode_percent(&username, url_userinfo, url_userinfo_len); - } - } - - if (git_str_oom(&scheme) || - git_str_oom(&host) || - git_str_oom(&port) || - git_str_oom(&path) || - git_str_oom(&query) || - git_str_oom(&username) || - git_str_oom(&password)) - return -1; - - url->scheme = git_str_detach(&scheme); - url->host = git_str_detach(&host); - url->port = git_str_detach(&port); - url->path = git_str_detach(&path); - url->query = git_str_detach(&query); - url->username = git_str_detach(&username); - url->password = git_str_detach(&password); - - error = 0; - -done: - git_str_dispose(&scheme); - git_str_dispose(&host); - git_str_dispose(&port); - git_str_dispose(&path); - git_str_dispose(&query); - git_str_dispose(&username); - git_str_dispose(&password); - return error; -} - -static int scp_invalid(const char *message) -{ - git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); - return GIT_EINVALIDSPEC; -} - -static bool is_ipv6(const char *str) -{ - const char *c; - size_t colons = 0; - - if (*str++ != '[') - return false; - - for (c = str; *c; c++) { - if (*c == ':') - colons++; - - if (*c == ']') - return (colons > 1); - - if (*c != ':' && - (*c < '0' || *c > '9') && - (*c < 'a' || *c > 'f') && - (*c < 'A' || *c > 'F')) - return false; - } - - return false; -} - -static bool has_at(const char *str) -{ - const char *c; - - for (c = str; *c; c++) { - if (*c == '@') - return true; - - if (*c == ':') - break; - } - - return false; -} - -int git_net_url_parse_scp(git_net_url *url, const char *given) -{ - const char *default_port = default_port_for_scheme("ssh"); - const char *c, *user, *host, *port, *path = NULL; - size_t user_len = 0, host_len = 0, port_len = 0; - unsigned short bracket = 0; - - enum { - NONE, - USER, - HOST_START, HOST, HOST_END, - IPV6, IPV6_END, - PORT_START, PORT, PORT_END, - PATH_START - } state = NONE; - - memset(url, 0, sizeof(git_net_url)); - - for (c = given; *c && !path; c++) { - switch (state) { - case NONE: - switch (*c) { - case '@': - return scp_invalid("unexpected '@'"); - case ':': - return scp_invalid("unexpected ':'"); - case '[': - if (is_ipv6(c)) { - state = IPV6; - host = c; - } else if (bracket++ > 1) { - return scp_invalid("unexpected '['"); - } - break; - default: - if (has_at(c)) { - state = USER; - user = c; - } else { - state = HOST; - host = c; - } - break; - } - break; - - case USER: - if (*c == '@') { - user_len = (c - user); - state = HOST_START; - } - break; - - case HOST_START: - state = (*c == '[') ? IPV6 : HOST; - host = c; - break; - - case HOST: - if (*c == ':') { - host_len = (c - host); - state = bracket ? PORT_START : PATH_START; - } else if (*c == ']') { - if (bracket-- == 0) - return scp_invalid("unexpected ']'"); - - host_len = (c - host); - state = HOST_END; - } - break; - - case HOST_END: - if (*c != ':') - return scp_invalid("unexpected character after hostname"); - state = PATH_START; - break; - - case IPV6: - if (*c == ']') - state = IPV6_END; - break; - - case IPV6_END: - if (*c != ':') - return scp_invalid("unexpected character after ipv6 address"); - - host_len = (c - host); - state = bracket ? PORT_START : PATH_START; - break; - - case PORT_START: - port = c; - state = PORT; - break; - - case PORT: - if (*c == ']') { - if (bracket-- == 0) - return scp_invalid("unexpected ']'"); - - port_len = c - port; - state = PORT_END; - } - break; - - case PORT_END: - if (*c != ':') - return scp_invalid("unexpected character after ipv6 address"); - - state = PATH_START; - break; - - case PATH_START: - path = c; - break; - - default: - GIT_ASSERT("unhandled state"); - } - } - - if (!path) - return scp_invalid("path is required"); - - GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh")); - - if (user_len) - GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len)); - - GIT_ASSERT(host_len); - GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); - - if (port_len) - GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); - else - GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); - - GIT_ASSERT(path); - GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); - - return 0; -} - -int git_net_url_joinpath( - git_net_url *out, - git_net_url *one, - const char *two) -{ - git_str path = GIT_STR_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_str_put(&path, one->path, one_len); - git_str_putc(&path, '/'); - git_str_put(&path, two, two_len); - - if (git_str_oom(&path)) - return -1; - - out->path = git_str_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, - bool allow_offsite, - const char *service_suffix) -{ - git_net_url tmp = GIT_NET_URL_INIT; - int error = 0; - - GIT_ASSERT(url); - GIT_ASSERT(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 && - !allow_offsite && - 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); -} - -bool git_net_url_is_default_port(git_net_url *url) -{ - const char *default_port; - - if ((default_port = default_port_for_scheme(url->scheme)) != NULL) - return (strcmp(url->port, default_port) == 0); - else - return false; -} - -bool git_net_url_is_ipv6(git_net_url *url) -{ - return (strchr(url->host, ':') != NULL); -} - -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_str *buf, git_net_url *url) -{ - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(url->scheme); - GIT_ASSERT_ARG(url->host); - - git_str_puts(buf, url->scheme); - git_str_puts(buf, "://"); - - if (url->username) { - git_str_puts(buf, url->username); - - if (url->password) { - git_str_puts(buf, ":"); - git_str_puts(buf, url->password); - } - - git_str_putc(buf, '@'); - } - - git_str_puts(buf, url->host); - - if (url->port && !git_net_url_is_default_port(url)) { - git_str_putc(buf, ':'); - git_str_puts(buf, url->port); - } - - git_str_puts(buf, url->path ? url->path : "/"); - - if (url->query) { - git_str_putc(buf, '?'); - git_str_puts(buf, url->query); - } - - return git_str_oom(buf) ? -1 : 0; -} - -int git_net_url_fmt_path(git_str *buf, git_net_url *url) -{ - git_str_puts(buf, url->path ? url->path : "/"); - - if (url->query) { - git_str_putc(buf, '?'); - git_str_puts(buf, url->query); - } - - return git_str_oom(buf) ? -1 : 0; -} - -static bool matches_pattern( - git_net_url *url, - const char *pattern, - size_t pattern_len) -{ - const char *domain, *port = NULL, *colon; - size_t host_len, domain_len, port_len = 0, wildcard = 0; - - GIT_UNUSED(url); - GIT_UNUSED(pattern); - - if (!pattern_len) - return false; - else if (pattern_len == 1 && pattern[0] == '*') - return true; - else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') - wildcard = 2; - else if (pattern[0] == '.') - wildcard = 1; - - domain = pattern + wildcard; - domain_len = pattern_len - wildcard; - - if ((colon = memchr(domain, ':', domain_len)) != NULL) { - domain_len = colon - domain; - port = colon + 1; - port_len = pattern_len - wildcard - domain_len - 1; - } - - /* A pattern's port *must* match if it's specified */ - if (port_len && git__strlcmp(url->port, port, port_len) != 0) - return false; - - /* No wildcard? Host must match exactly. */ - if (!wildcard) - return !git__strlcmp(url->host, domain, domain_len); - - /* Wildcard: ensure there's (at least) a suffix match */ - if ((host_len = strlen(url->host)) < domain_len || - memcmp(url->host + (host_len - domain_len), domain, domain_len)) - return false; - - /* The pattern is *.domain and the host is simply domain */ - if (host_len == domain_len) - return true; - - /* The pattern is *.domain and the host is foo.domain */ - return (url->host[host_len - domain_len - 1] == '.'); -} - -bool git_net_url_matches_pattern(git_net_url *url, const char *pattern) -{ - return matches_pattern(url, pattern, strlen(pattern)); -} - -bool git_net_url_matches_pattern_list( - git_net_url *url, - const char *pattern_list) -{ - const char *pattern, *pattern_end, *sep; - - for (pattern = pattern_list; - pattern && *pattern; - pattern = sep ? sep + 1 : NULL) { - sep = strchr(pattern, ','); - pattern_end = sep ? sep : strchr(pattern, '\0'); - - if (matches_pattern(url, pattern, (pattern_end - pattern))) - return true; - } - - return false; -} - -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 deleted file mode 100644 index 499315e6c..000000000 --- a/src/net.h +++ /dev/null @@ -1,78 +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_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 } - -/** Is a given string a url? */ -extern bool git_net_str_is_url(const char *str); - -/** Duplicate a URL */ -extern int git_net_url_dup(git_net_url *out, git_net_url *in); - -/** Parses a string containing a URL into a structure. */ -extern int git_net_url_parse(git_net_url *url, const char *str); - -/** Parses a string containing an SCP style path into a URL structure. */ -extern int git_net_url_parse_scp(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 true if the URL is on the default port. */ -extern bool git_net_url_is_default_port(git_net_url *url); - -/** Returns true if the host portion of the URL is an ipv6 address. */ -extern bool git_net_url_is_ipv6(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, - bool allow_offsite, - 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_str *out, git_net_url *url); - -/** Place the path and query string into the given buffer. */ -extern int git_net_url_fmt_path(git_str *buf, git_net_url *url); - -/** Determines if the url matches given pattern or pattern list */ -extern bool git_net_url_matches_pattern( - git_net_url *url, - const char *pattern); -extern bool git_net_url_matches_pattern_list( - git_net_url *url, - const char *pattern_list); - -/** 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 deleted file mode 100644 index 0a27365b8..000000000 --- a/src/netops.c +++ /dev/null @@ -1,125 +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 "netops.h" - -#include -#include "git2/errors.h" - -#include "posix.h" -#include "str.h" -#include "http_parser.h" -#include "runtime.h" - -int gitno_recv(gitno_buffer *buf) -{ - return buf->recv(buf); -} - -void gitno_buffer_setup_callback( - gitno_buffer *buf, - char *data, - size_t len, - int (*recv)(gitno_buffer *buf), void *cb_data) -{ - memset(data, 0x0, len); - buf->data = data; - buf->len = len; - buf->offset = 0; - buf->recv = recv; - buf->cb_data = cb_data; -} - -static int recv_stream(gitno_buffer *buf) -{ - git_stream *io = (git_stream *) buf->cb_data; - size_t readlen = buf->len - buf->offset; - ssize_t ret; - - 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 (int)ret; -} - -void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) -{ - memset(data, 0x0, len); - buf->data = data; - buf->len = len; - buf->offset = 0; - buf->recv = recv_stream; - buf->cb_data = st; -} - -/* Consume up to ptr and move the rest of the buffer to the beginning */ -int gitno_consume(gitno_buffer *buf, const char *ptr) -{ - size_t consumed; - - GIT_ASSERT(ptr - buf->data >= 0); - GIT_ASSERT(ptr - buf->data <= (int) buf->len); - - consumed = ptr - buf->data; - - memmove(buf->data, ptr, buf->offset - consumed); - memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); - buf->offset -= consumed; - - return 0; -} - -/* Consume const bytes and move the rest of the buffer to the beginning */ -void gitno_consume_n(gitno_buffer *buf, size_t cons) -{ - memmove(buf->data, buf->data + cons, buf->len - buf->offset); - memset(buf->data + cons, 0x0, buf->len - buf->offset); - buf->offset -= cons; -} - -/* Match host names according to RFC 2818 rules */ -int gitno__match_host(const char *pattern, const char *host) -{ - for (;;) { - char c = git__tolower(*pattern++); - - if (c == '\0') - return *host ? -1 : 0; - - if (c == '*') { - c = *pattern; - /* '*' at the end matches everything left */ - if (c == '\0') - return 0; - - /* - * We've found a pattern, so move towards the next matching - * char. The '.' is handled specially because wildcards aren't - * allowed to cross subdomains. - */ - - while(*host) { - char h = git__tolower(*host); - if (c == h) - return gitno__match_host(pattern, host++); - if (h == '.') - return gitno__match_host(pattern, host); - host++; - } - return -1; - } - - if (c != git__tolower(*host++)) - return -1; - } - - return -1; -} diff --git a/src/netops.h b/src/netops.h deleted file mode 100644 index 56f968534..000000000 --- a/src/netops.h +++ /dev/null @@ -1,68 +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_netops_h__ -#define INCLUDE_netops_h__ - -#include "common.h" - -#include "posix.h" -#include "stream.h" -#include "net.h" - -#ifdef GIT_OPENSSL -# include "streams/openssl.h" -#endif - -typedef struct gitno_ssl { -#ifdef GIT_OPENSSL - SSL *ssl; -#else - size_t dummy; -#endif -} gitno_ssl; - -/* Represents a socket that may or may not be using SSL */ -typedef struct gitno_socket { - GIT_SOCKET socket; - gitno_ssl ssl; -} gitno_socket; - -typedef struct gitno_buffer { - char *data; - size_t len; - size_t offset; - int (*recv)(struct gitno_buffer *buffer); - void *cb_data; -} gitno_buffer; - -/* Flags to gitno_connect */ -enum { - /* Attempt to create an SSL connection. */ - GITNO_CONNECT_SSL = 1 -}; - -/** - * Check if the name in a cert matches the wanted hostname - * - * Check if a pattern from a certificate matches the hostname we - * wanted to connect to according to RFC2818 rules (which specifies - * HTTP over TLS). Mainly, an asterisk matches anything, but is - * limited to a single url component. - * - * Note that this does not set an error message. It expects the user - * to provide the message for the user. - */ -int gitno__match_host(const char *pattern, const char *host); - -void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len); -void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); -int gitno_recv(gitno_buffer *buf); - -int gitno_consume(gitno_buffer *buf, const char *ptr); -void gitno_consume_n(gitno_buffer *buf, size_t cons); - -#endif diff --git a/src/notes.c b/src/notes.c deleted file mode 100644 index d1a2b0f64..000000000 --- a/src/notes.c +++ /dev/null @@ -1,809 +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 "notes.h" - -#include "buf.h" -#include "refs.h" -#include "config.h" -#include "iterator.h" -#include "signature.h" -#include "blob.h" - -static int note_error_notfound(void) -{ - git_error_set(GIT_ERROR_INVALID, "note could not be found"); - return GIT_ENOTFOUND; -} - -static int find_subtree_in_current_level( - git_tree **out, - git_repository *repo, - git_tree *parent, - const char *annotated_object_sha, - int fanout) -{ - size_t i; - const git_tree_entry *entry; - - *out = NULL; - - if (parent == NULL) - return note_error_notfound(); - - for (i = 0; i < git_tree_entrycount(parent); i++) { - entry = git_tree_entry_byindex(parent, i); - - if (!git__ishex(git_tree_entry_name(entry))) - continue; - - if (S_ISDIR(git_tree_entry_filemode(entry)) - && strlen(git_tree_entry_name(entry)) == 2 - && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2)) - return git_tree_lookup(out, repo, git_tree_entry_id(entry)); - - /* Not a DIR, so do we have an already existing blob? */ - if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout)) - return GIT_EEXISTS; - } - - return note_error_notfound(); -} - -static int find_subtree_r(git_tree **out, git_tree *root, - git_repository *repo, const char *target, int *fanout) -{ - int error; - git_tree *subtree = NULL; - - *out = NULL; - - error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout); - if (error == GIT_EEXISTS) - return git_tree_lookup(out, repo, git_tree_id(root)); - - if (error < 0) - return error; - - *fanout += 2; - error = find_subtree_r(out, subtree, repo, target, fanout); - git_tree_free(subtree); - - return error; -} - -static int find_blob(git_oid *blob, git_tree *tree, const char *target) -{ - size_t i; - const git_tree_entry *entry; - - for (i=0; iid, note_oid); - - if (git_signature_dup(¬e->author, git_commit_author(commit)) < 0 || - git_signature_dup(¬e->committer, git_commit_committer(commit)) < 0) - return -1; - - blobsize = git_blob_rawsize(blob); - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - - note->message = git__strndup(git_blob_rawcontent(blob), (size_t)blobsize); - GIT_ERROR_CHECK_ALLOC(note->message); - - *out = note; - return 0; -} - -static int note_lookup( - git_note **out, - git_repository *repo, - git_commit *commit, - git_tree *tree, - const char *target) -{ - int error, fanout = 0; - git_oid oid; - git_blob *blob = NULL; - git_note *note = NULL; - git_tree *subtree = NULL; - - if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0) - goto cleanup; - - if ((error = find_blob(&oid, subtree, target + fanout)) < 0) - goto cleanup; - - if ((error = git_blob_lookup(&blob, repo, &oid)) < 0) - goto cleanup; - - if ((error = note_new(¬e, &oid, commit, blob)) < 0) - goto cleanup; - - *out = note; - -cleanup: - git_tree_free(subtree); - git_blob_free(blob); - return error; -} - -static int note_remove( - git_oid *notes_commit_out, - git_repository *repo, - const git_signature *author, const git_signature *committer, - const char *notes_ref, git_tree *tree, - const char *target, git_commit **parents) -{ - int error; - git_tree *tree_after_removal = NULL; - git_oid oid; - - if ((error = manipulate_note_in_tree_r( - &tree_after_removal, repo, tree, NULL, target, 0, - remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0) - goto cleanup; - - error = git_commit_create(&oid, repo, notes_ref, author, committer, - NULL, GIT_NOTES_DEFAULT_MSG_RM, - tree_after_removal, - *parents == NULL ? 0 : 1, - (const git_commit **) parents); - - if (error < 0) - goto cleanup; - - if (notes_commit_out) - git_oid_cpy(notes_commit_out, &oid); - -cleanup: - git_tree_free(tree_after_removal); - return error; -} - -static int note_get_default_ref(git_str *out, git_repository *repo) -{ - git_config *cfg; - int error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - error = git_config__get_string_buf(out, cfg, "core.notesref"); - - if (error == GIT_ENOTFOUND) - error = git_str_puts(out, GIT_NOTES_DEFAULT_REF); - - return error; -} - -static int normalize_namespace(git_str *out, git_repository *repo, const char *notes_ref) -{ - if (notes_ref) - return git_str_puts(out, notes_ref); - - return note_get_default_ref(out, repo); -} - -static int retrieve_note_commit( - git_commit **commit_out, - git_str *notes_ref_out, - git_repository *repo, - const char *notes_ref) -{ - int error; - git_oid oid; - - if ((error = normalize_namespace(notes_ref_out, repo, notes_ref)) < 0) - return error; - - if ((error = git_reference_name_to_id(&oid, repo, notes_ref_out->ptr)) < 0) - return error; - - if (git_commit_lookup(commit_out, repo, &oid) < 0) - return error; - - return 0; -} - -int git_note_commit_read( - git_note **out, - git_repository *repo, - git_commit *notes_commit, - const git_oid *oid) -{ - int error; - git_tree *tree = NULL; - char target[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(target, sizeof(target), oid); - - if ((error = git_commit_tree(&tree, notes_commit)) < 0) - goto cleanup; - - error = note_lookup(out, repo, notes_commit, tree, target); - -cleanup: - git_tree_free(tree); - return error; -} - -int git_note_read(git_note **out, git_repository *repo, - const char *notes_ref_in, const git_oid *oid) -{ - int error; - git_str notes_ref = GIT_STR_INIT; - git_commit *commit = NULL; - - error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); - - if (error < 0) - goto cleanup; - - error = git_note_commit_read(out, repo, commit, oid); - -cleanup: - git_str_dispose(¬es_ref); - git_commit_free(commit); - return error; -} - -int git_note_commit_create( - git_oid *notes_commit_out, - git_oid *notes_blob_out, - git_repository *repo, - git_commit *parent, - const git_signature *author, - const git_signature *committer, - const git_oid *oid, - const char *note, - int allow_note_overwrite) -{ - int error; - git_tree *tree = NULL; - char target[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(target, sizeof(target), oid); - - if (parent != NULL && (error = git_commit_tree(&tree, parent)) < 0) - goto cleanup; - - error = note_write(notes_commit_out, notes_blob_out, repo, author, - committer, NULL, note, tree, target, &parent, allow_note_overwrite); - - if (error < 0) - goto cleanup; - -cleanup: - git_tree_free(tree); - return error; -} - -int git_note_create( - git_oid *out, - git_repository *repo, - const char *notes_ref_in, - const git_signature *author, - const git_signature *committer, - const git_oid *oid, - const char *note, - int allow_note_overwrite) -{ - int error; - git_str notes_ref = GIT_STR_INIT; - git_commit *existing_notes_commit = NULL; - git_reference *ref = NULL; - git_oid notes_blob_oid, notes_commit_oid; - - error = retrieve_note_commit(&existing_notes_commit, ¬es_ref, - repo, notes_ref_in); - - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - error = git_note_commit_create(¬es_commit_oid, - ¬es_blob_oid, - repo, existing_notes_commit, author, - committer, oid, note, - allow_note_overwrite); - if (error < 0) - goto cleanup; - - error = git_reference_create(&ref, repo, notes_ref.ptr, - ¬es_commit_oid, 1, NULL); - - if (out != NULL) - git_oid_cpy(out, ¬es_blob_oid); - -cleanup: - git_str_dispose(¬es_ref); - git_commit_free(existing_notes_commit); - git_reference_free(ref); - return error; -} - -int git_note_commit_remove( - git_oid *notes_commit_out, - git_repository *repo, - git_commit *notes_commit, - const git_signature *author, - const git_signature *committer, - const git_oid *oid) -{ - int error; - git_tree *tree = NULL; - char target[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(target, sizeof(target), oid); - - if ((error = git_commit_tree(&tree, notes_commit)) < 0) - goto cleanup; - - error = note_remove(notes_commit_out, - repo, author, committer, NULL, tree, target, ¬es_commit); - -cleanup: - git_tree_free(tree); - return error; -} - -int git_note_remove(git_repository *repo, const char *notes_ref_in, - const git_signature *author, const git_signature *committer, - const git_oid *oid) -{ - int error; - git_str notes_ref_target = GIT_STR_INIT; - git_commit *existing_notes_commit = NULL; - git_oid new_notes_commit; - git_reference *notes_ref = NULL; - - error = retrieve_note_commit(&existing_notes_commit, ¬es_ref_target, - repo, notes_ref_in); - - if (error < 0) - goto cleanup; - - error = git_note_commit_remove(&new_notes_commit, repo, - existing_notes_commit, author, committer, oid); - if (error < 0) - goto cleanup; - - error = git_reference_create(¬es_ref, repo, notes_ref_target.ptr, - &new_notes_commit, 1, NULL); - -cleanup: - git_str_dispose(¬es_ref_target); - git_reference_free(notes_ref); - git_commit_free(existing_notes_commit); - return error; -} - -int git_note_default_ref(git_buf *out, git_repository *repo) -{ - GIT_BUF_WRAP_PRIVATE(out, note_get_default_ref, repo); -} - -const git_signature *git_note_committer(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return note->committer; -} - -const git_signature *git_note_author(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return note->author; -} - -const char *git_note_message(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return note->message; -} - -const git_oid *git_note_id(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return ¬e->id; -} - -void git_note_free(git_note *note) -{ - if (note == NULL) - return; - - git_signature_free(note->committer); - git_signature_free(note->author); - git__free(note->message); - git__free(note); -} - -static int process_entry_path( - const char *entry_path, - git_oid *annotated_object_id) -{ - int error = 0; - size_t i = 0, j = 0, len; - git_str buf = GIT_STR_INIT; - - if ((error = git_str_puts(&buf, entry_path)) < 0) - goto cleanup; - - len = git_str_len(&buf); - - while (i < len) { - if (buf.ptr[i] == '/') { - i++; - continue; - } - - if (git__fromhex(buf.ptr[i]) < 0) { - /* This is not a note entry */ - goto cleanup; - } - - if (i != j) - buf.ptr[j] = buf.ptr[i]; - - i++; - j++; - } - - buf.ptr[j] = '\0'; - buf.size = j; - - if (j != GIT_OID_HEXSZ) { - /* This is not a note entry */ - goto cleanup; - } - - error = git_oid_fromstr(annotated_object_id, buf.ptr); - -cleanup: - git_str_dispose(&buf); - return error; -} - -int git_note_foreach( - git_repository *repo, - const char *notes_ref, - git_note_foreach_cb note_cb, - void *payload) -{ - int error; - git_note_iterator *iter = NULL; - git_oid note_id, annotated_id; - - if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0) - return error; - - while (!(error = git_note_next(¬e_id, &annotated_id, iter))) { - if ((error = note_cb(¬e_id, &annotated_id, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_note_iterator_free(iter); - return error; -} - -void git_note_iterator_free(git_note_iterator *it) -{ - if (it == NULL) - return; - - git_iterator_free(it); -} - -int git_note_commit_iterator_new( - git_note_iterator **it, - git_commit *notes_commit) -{ - int error; - git_tree *tree; - - if ((error = git_commit_tree(&tree, notes_commit)) < 0) - goto cleanup; - - if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) - git_iterator_free(*it); - -cleanup: - git_tree_free(tree); - - return error; -} - -int git_note_iterator_new( - git_note_iterator **it, - git_repository *repo, - const char *notes_ref_in) -{ - int error; - git_commit *commit = NULL; - git_str notes_ref = GIT_STR_INIT; - - error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); - if (error < 0) - goto cleanup; - - error = git_note_commit_iterator_new(it, commit); - -cleanup: - git_str_dispose(¬es_ref); - git_commit_free(commit); - - return error; -} - -int git_note_next( - git_oid *note_id, - git_oid *annotated_id, - git_note_iterator *it) -{ - int error; - const git_index_entry *item; - - if ((error = git_iterator_current(&item, it)) < 0) - return error; - - git_oid_cpy(note_id, &item->id); - - if ((error = process_entry_path(item->path, annotated_id)) < 0) - return error; - - if ((error = git_iterator_advance(NULL, it)) < 0 && error != GIT_ITEROVER) - return error; - - return 0; -} diff --git a/src/notes.h b/src/notes.h deleted file mode 100644 index 2168e4595..000000000 --- a/src/notes.h +++ /dev/null @@ -1,32 +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_note_h__ -#define INCLUDE_note_h__ - -#include "common.h" - -#include "git2/oid.h" -#include "git2/types.h" - -#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" - -#define GIT_NOTES_DEFAULT_MSG_ADD \ - "Notes added by 'git_note_create' from libgit2" - -#define GIT_NOTES_DEFAULT_MSG_RM \ - "Notes removed by 'git_note_remove' from libgit2" - -struct git_note { - git_oid id; - - git_signature *author; - git_signature *committer; - - char *message; -}; - -#endif diff --git a/src/object.c b/src/object.c deleted file mode 100644 index 7bc256fce..000000000 --- a/src/object.c +++ /dev/null @@ -1,601 +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 "object.h" - -#include "git2/object.h" - -#include "repository.h" - -#include "buf.h" -#include "commit.h" -#include "hash.h" -#include "tree.h" -#include "blob.h" -#include "oid.h" -#include "tag.h" - -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 */ - size_t size; /* size in bytes of the object structure */ - - int (*parse)(void *self, git_odb_object *obj); - int (*parse_raw)(void *self, const char *data, size_t size); - void (*free)(void *self); -} git_object_def; - -static git_object_def git_objects_table[] = { - /* 0 = GIT_OBJECT__EXT1 */ - { "", 0, NULL, NULL, NULL }, - - /* 1 = GIT_OBJECT_COMMIT */ - { "commit", sizeof(git_commit), git_commit__parse, git_commit__parse_raw, git_commit__free }, - - /* 2 = GIT_OBJECT_TREE */ - { "tree", sizeof(git_tree), git_tree__parse, git_tree__parse_raw, git_tree__free }, - - /* 3 = GIT_OBJECT_BLOB */ - { "blob", sizeof(git_blob), git_blob__parse, git_blob__parse_raw, git_blob__free }, - - /* 4 = GIT_OBJECT_TAG */ - { "tag", sizeof(git_tag), git_tag__parse, git_tag__parse_raw, git_tag__free }, - - /* 5 = GIT_OBJECT__EXT2 */ - { "", 0, NULL, NULL, NULL }, - /* 6 = GIT_OBJECT_OFS_DELTA */ - { "OFS_DELTA", 0, NULL, NULL, NULL }, - /* 7 = GIT_OBJECT_REF_DELTA */ - { "REF_DELTA", 0, NULL, NULL, NULL }, -}; - -int git_object__from_raw( - git_object **object_out, - const char *data, - size_t size, - git_object_t type) -{ - git_object_def *def; - git_object *object; - size_t object_size; - int error; - - GIT_ASSERT_ARG(object_out); - *object_out = NULL; - - /* Validate type match */ - if (type != GIT_OBJECT_BLOB && type != GIT_OBJECT_TREE && type != GIT_OBJECT_COMMIT && type != GIT_OBJECT_TAG) { - git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); - return GIT_ENOTFOUND; - } - - if ((object_size = git_object__size(type)) == 0) { - git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); - return GIT_ENOTFOUND; - } - - /* Allocate and initialize base object */ - object = git__calloc(1, object_size); - GIT_ERROR_CHECK_ALLOC(object); - object->cached.flags = GIT_CACHE_STORE_PARSED; - object->cached.type = type; - if ((error = git_odb_hash(&object->cached.oid, data, size, type)) < 0) - return error; - - /* Parse raw object data */ - def = &git_objects_table[type]; - GIT_ASSERT(def->free && def->parse_raw); - - if ((error = def->parse_raw(object, data, size)) < 0) { - def->free(object); - return error; - } - - git_cached_obj_incref(object); - *object_out = object; - - return 0; -} - -int git_object__from_odb_object( - git_object **object_out, - git_repository *repo, - git_odb_object *odb_obj, - git_object_t type) -{ - int error; - size_t object_size; - git_object_def *def; - git_object *object = NULL; - - GIT_ASSERT_ARG(object_out); - *object_out = NULL; - - /* Validate type match */ - if (type != GIT_OBJECT_ANY && type != odb_obj->cached.type) { - git_error_set(GIT_ERROR_INVALID, - "the requested type does not match the type in the ODB"); - return GIT_ENOTFOUND; - } - - if ((object_size = git_object__size(odb_obj->cached.type)) == 0) { - git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); - return GIT_ENOTFOUND; - } - - /* Allocate and initialize base object */ - object = git__calloc(1, object_size); - GIT_ERROR_CHECK_ALLOC(object); - - git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); - object->cached.type = odb_obj->cached.type; - object->cached.size = odb_obj->cached.size; - object->repo = repo; - - /* Parse raw object data */ - def = &git_objects_table[odb_obj->cached.type]; - GIT_ASSERT(def->free && def->parse); - - if ((error = def->parse(object, odb_obj)) < 0) { - /* - * parse returns EINVALID on invalid data; downgrade - * that to a normal -1 error code. - */ - def->free(object); - return -1; - } - - *object_out = git_cache_store_parsed(&repo->objects, object); - return 0; -} - -void git_object__free(void *obj) -{ - git_object_t type = ((git_object *)obj)->cached.type; - - if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) || - !git_objects_table[type].free) - git__free(obj); - else - git_objects_table[type].free(obj); -} - -int git_object_lookup_prefix( - git_object **object_out, - git_repository *repo, - const git_oid *id, - size_t len, - git_object_t type) -{ - git_object *object = NULL; - git_odb *odb = NULL; - git_odb_object *odb_obj = NULL; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(object_out); - GIT_ASSERT_ARG(id); - - if (len < GIT_OID_MINPREFIXLEN) { - git_error_set(GIT_ERROR_OBJECT, "ambiguous lookup - OID prefix is too short"); - return GIT_EAMBIGUOUS; - } - - error = git_repository_odb__weakptr(&odb, repo); - if (error < 0) - return error; - - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - if (len == GIT_OID_HEXSZ) { - git_cached_obj *cached = NULL; - - /* We want to match the full id : we can first look up in the cache, - * since there is no need to check for non ambiguousity - */ - cached = git_cache_get_any(&repo->objects, id); - if (cached != NULL) { - if (cached->flags == GIT_CACHE_STORE_PARSED) { - object = (git_object *)cached; - - 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 the ODB"); - return GIT_ENOTFOUND; - } - - *object_out = object; - return 0; - } else if (cached->flags == GIT_CACHE_STORE_RAW) { - odb_obj = (git_odb_object *)cached; - } else { - GIT_ASSERT(!"Wrong caching type in the global object cache"); - } - } else { - /* Object was not found in the cache, let's explore the backends. - * We could just use git_odb_read_unique_short_oid, - * it is the same cost for packed and loose object backends, - * but it may be much more costly for sqlite and hiredis. - */ - error = git_odb_read(&odb_obj, odb, id); - } - } else { - git_oid short_oid = {{ 0 }}; - - git_oid__cpy_prefix(&short_oid, id, len); - - /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have - * 2 options : - * - We always search in the cache first. If we find that short oid is - * ambiguous, we can stop. But in all the other cases, we must then - * explore all the backends (to find an object if there was match, - * or to check that oid is not ambiguous if we have found 1 match in - * the cache) - * - We never explore the cache, go right to exploring the backends - * We chose the latter : we explore directly the backends. - */ - error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len); - } - - if (error < 0) - return error; - - error = git_object__from_odb_object(object_out, repo, odb_obj, type); - - git_odb_object_free(odb_obj); - - return error; -} - -int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_object_t type) { - return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type); -} - -void git_object_free(git_object *object) -{ - if (object == NULL) - return; - - git_cached_obj_decref(object); -} - -const git_oid *git_object_id(const git_object *obj) -{ - GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); - return &obj->cached.oid; -} - -git_object_t git_object_type(const git_object *obj) -{ - GIT_ASSERT_ARG_WITH_RETVAL(obj, GIT_OBJECT_INVALID); - return obj->cached.type; -} - -git_repository *git_object_owner(const git_object *obj) -{ - GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); - return obj->repo; -} - -const char *git_object_type2string(git_object_t type) -{ - if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) - return ""; - - return git_objects_table[type].str; -} - -git_object_t git_object_string2type(const char *str) -{ - if (!str) - return GIT_OBJECT_INVALID; - - return git_object_stringn2type(str, strlen(str)); -} - -git_object_t git_object_stringn2type(const char *str, size_t len) -{ - size_t i; - - if (!str || !len || !*str) - return GIT_OBJECT_INVALID; - - for (i = 0; i < ARRAY_SIZE(git_objects_table); i++) - if (*git_objects_table[i].str && - !git__prefixncmp(str, len, git_objects_table[i].str)) - return (git_object_t)i; - - return GIT_OBJECT_INVALID; -} - -int git_object_typeisloose(git_object_t type) -{ - if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) - return 0; - - return (git_objects_table[type].size > 0) ? 1 : 0; -} - -size_t git_object__size(git_object_t type) -{ - if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) - return 0; - - return git_objects_table[type].size; -} - -static int dereference_object(git_object **dereferenced, git_object *obj) -{ - git_object_t type = git_object_type(obj); - - switch (type) { - case GIT_OBJECT_COMMIT: - return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); - - case GIT_OBJECT_TAG: - return git_tag_target(dereferenced, (git_tag*)obj); - - case GIT_OBJECT_BLOB: - case GIT_OBJECT_TREE: - return GIT_EPEEL; - - default: - return GIT_EINVALIDSPEC; - } -} - -static int peel_error(int error, const git_oid *oid, git_object_t type) -{ - const char *type_name; - char hex_oid[GIT_OID_HEXSZ + 1]; - - type_name = git_object_type2string(type); - - git_oid_fmt(hex_oid, oid); - hex_oid[GIT_OID_HEXSZ] = '\0'; - - git_error_set(GIT_ERROR_OBJECT, "the git_object of id '%s' can not be " - "successfully peeled into a %s (git_object_t=%i).", hex_oid, type_name, type); - - return error; -} - -static int check_type_combination(git_object_t type, git_object_t target) -{ - if (type == target) - return 0; - - switch (type) { - case GIT_OBJECT_BLOB: - case GIT_OBJECT_TREE: - /* a blob or tree can never be peeled to anything but themselves */ - return GIT_EINVALIDSPEC; - break; - case GIT_OBJECT_COMMIT: - /* a commit can only be peeled to a tree */ - if (target != GIT_OBJECT_TREE && target != GIT_OBJECT_ANY) - return GIT_EINVALIDSPEC; - break; - case GIT_OBJECT_TAG: - /* a tag may point to anything, so we let anything through */ - break; - default: - return GIT_EINVALIDSPEC; - } - - return 0; -} - -int git_object_peel( - git_object **peeled, - const git_object *object, - git_object_t target_type) -{ - git_object *source, *deref = NULL; - int error; - - GIT_ASSERT_ARG(object); - GIT_ASSERT_ARG(peeled); - - GIT_ASSERT_ARG(target_type == GIT_OBJECT_TAG || - target_type == GIT_OBJECT_COMMIT || - target_type == GIT_OBJECT_TREE || - target_type == GIT_OBJECT_BLOB || - target_type == GIT_OBJECT_ANY); - - if ((error = check_type_combination(git_object_type(object), target_type)) < 0) - return peel_error(error, git_object_id(object), target_type); - - if (git_object_type(object) == target_type) - return git_object_dup(peeled, (git_object *)object); - - source = (git_object *)object; - - while (!(error = dereference_object(&deref, source))) { - - if (source != object) - git_object_free(source); - - if (git_object_type(deref) == target_type) { - *peeled = deref; - return 0; - } - - if (target_type == GIT_OBJECT_ANY && - git_object_type(deref) != git_object_type(object)) - { - *peeled = deref; - return 0; - } - - source = deref; - deref = NULL; - } - - if (source != object) - git_object_free(source); - - git_object_free(deref); - - if (error) - error = peel_error(error, git_object_id(object), target_type); - - return error; -} - -int git_object_dup(git_object **dest, git_object *source) -{ - git_cached_obj_incref(source); - *dest = source; - return 0; -} - -int git_object_lookup_bypath( - git_object **out, - const git_object *treeish, - const char *path, - git_object_t type) -{ - int error = -1; - git_tree *tree = NULL; - git_tree_entry *entry = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(treeish); - GIT_ASSERT_ARG(path); - - if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJECT_TREE)) < 0 || - (error = git_tree_entry_bypath(&entry, tree, path)) < 0) - { - goto cleanup; - } - - if (type != GIT_OBJECT_ANY && git_tree_entry_type(entry) != type) - { - git_error_set(GIT_ERROR_OBJECT, - "object at path '%s' is not of the asked-for type %d", - path, type); - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - error = git_tree_entry_to_object(out, git_object_owner(treeish), entry); - -cleanup: - git_tree_entry_free(entry); - git_tree_free(tree); - return error; -} - -static int git_object__short_id(git_str *out, const git_object *obj) -{ - git_repository *repo; - int len = GIT_ABBREV_DEFAULT, error; - git_oid id = {{0}}; - git_odb *odb; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(obj); - - repo = git_object_owner(obj); - - if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) - return error; - - if ((error = git_repository_odb(&odb, repo)) < 0) - return error; - - while (len < GIT_OID_HEXSZ) { - /* set up short oid */ - memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2); - if (len & 1) - id.id[len / 2] &= 0xf0; - - error = git_odb_exists_prefix(NULL, odb, &id, len); - if (error != GIT_EAMBIGUOUS) - break; - - git_error_clear(); - len++; - } - - if (!error && !(error = git_str_grow(out, len + 1))) { - git_oid_tostr(out->ptr, len + 1, &id); - out->size = len; - } - - git_odb_free(odb); - - return error; -} - -int git_object_short_id(git_buf *out, const git_object *obj) -{ - GIT_BUF_WRAP_PRIVATE(out, git_object__short_id, obj); -} - -bool git_object__is_valid( - git_repository *repo, const git_oid *id, git_object_t expected_type) -{ - git_odb *odb; - git_object_t actual_type; - size_t len; - int error; - - if (!git_object__strict_input_validation) - return true; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0) - return false; - - if (expected_type != GIT_OBJECT_ANY && expected_type != actual_type) { - git_error_set(GIT_ERROR_INVALID, - "the requested type does not match the type in the ODB"); - return false; - } - - return true; -} - -int git_object_rawcontent_is_valid( - int *valid, - const char *buf, - size_t len, - git_object_t type) -{ - git_object *obj = NULL; - int error; - - GIT_ASSERT_ARG(valid); - GIT_ASSERT_ARG(buf); - - /* Blobs are always valid; don't bother parsing. */ - if (type == GIT_OBJECT_BLOB) { - *valid = 1; - return 0; - } - - error = git_object__from_raw(&obj, buf, len, type); - git_object_free(obj); - - if (error == 0) { - *valid = 1; - return 0; - } else if (error == GIT_EINVALID) { - *valid = 0; - return 0; - } - - return error; -} diff --git a/src/object.h b/src/object.h deleted file mode 100644 index 66be57557..000000000 --- a/src/object.h +++ /dev/null @@ -1,71 +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_object_h__ -#define INCLUDE_object_h__ - -#include "common.h" - -#include "repository.h" - -#define GIT_OBJECT_SIZE_MAX UINT64_MAX - -extern bool git_object__strict_input_validation; - -/** Base git object for inheritance */ -struct git_object { - git_cached_obj cached; - git_repository *repo; -}; - -/* fully free the object; internal method, DO NOT EXPORT */ -void git_object__free(void *object); - -/* - * Parse object from raw data. Note that the resulting object is - * tied to the lifetime of the data, as some objects simply point - * into it. - */ -int git_object__from_raw( - git_object **object_out, - const char *data, - size_t size, - git_object_t type); - -int git_object__from_odb_object( - git_object **object_out, - git_repository *repo, - git_odb_object *odb_obj, - git_object_t type); - -int git_object__resolve_to_type(git_object **obj, git_object_t type); - -git_object_t git_object_stringn2type(const char *str, size_t len); - -int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); - -void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid); - -bool git_object__is_valid( - git_repository *repo, const git_oid *id, git_object_t expected_type); - -GIT_INLINE(git_object_t) git_object__type_from_filemode(git_filemode_t mode) -{ - switch (mode) { - case GIT_FILEMODE_TREE: - return GIT_OBJECT_TREE; - case GIT_FILEMODE_COMMIT: - return GIT_OBJECT_COMMIT; - case GIT_FILEMODE_BLOB: - case GIT_FILEMODE_BLOB_EXECUTABLE: - case GIT_FILEMODE_LINK: - return GIT_OBJECT_BLOB; - default: - return GIT_OBJECT_INVALID; - } -} - -#endif diff --git a/src/object_api.c b/src/object_api.c deleted file mode 100644 index d45abd5ce..000000000 --- a/src/object_api.c +++ /dev/null @@ -1,148 +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 "common.h" - -#include "git2/object.h" - -#include "repository.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tag.h" - -/** - * Commit - */ -int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_COMMIT); -} - -int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_COMMIT); -} - -void git_commit_free(git_commit *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_commit_id(const git_commit *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_commit_owner(const git_commit *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_commit_dup(git_commit **out, git_commit *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} - -/** - * Tree - */ -int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TREE); -} - -int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TREE); -} - -void git_tree_free(git_tree *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_tree_id(const git_tree *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_tree_owner(const git_tree *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_tree_dup(git_tree **out, git_tree *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} - -/** - * Tag - */ -int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TAG); -} - -int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TAG); -} - -void git_tag_free(git_tag *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_tag_id(const git_tag *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_tag_owner(const git_tag *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_tag_dup(git_tag **out, git_tag *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} - -/** - * Blob - */ -int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_BLOB); -} - -int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_BLOB); -} - -void git_blob_free(git_blob *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_blob_id(const git_blob *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_blob_owner(const git_blob *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_blob_dup(git_blob **out, git_blob *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} diff --git a/src/odb.c b/src/odb.c deleted file mode 100644 index 14eff53c7..000000000 --- a/src/odb.c +++ /dev/null @@ -1,1831 +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 "odb.h" - -#include -#include "git2/object.h" -#include "git2/sys/odb_backend.h" -#include "futils.h" -#include "hash.h" -#include "delta.h" -#include "filter.h" -#include "repository.h" -#include "blob.h" -#include "oid.h" - -#include "git2/odb_backend.h" -#include "git2/oid.h" -#include "git2/oidarray.h" - -#define GIT_ALTERNATES_FILE "info/alternates" - -#define GIT_ALTERNATES_MAX_DEPTH 5 - -/* - * We work under the assumption that most objects for long-running - * operations will be packed - */ -int git_odb__loose_priority = GIT_ODB_DEFAULT_LOOSE_PRIORITY; -int git_odb__packed_priority = GIT_ODB_DEFAULT_PACKED_PRIORITY; - -bool git_odb__strict_hash_verification = true; - -typedef struct -{ - git_odb_backend *backend; - int priority; - bool is_alternate; - ino_t disk_inode; -} backend_internal; - -static git_cache *odb_cache(git_odb *odb) -{ - git_repository *owner = GIT_REFCOUNT_OWNER(odb); - if (owner != NULL) { - return &owner->objects; - } - - return &odb->own_cache; -} - -static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id); -static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); -static int error_null_oid(int error, const char *message); - -static git_object_t odb_hardcoded_type(const git_oid *id) -{ - if (!git_oid_cmp(id, &git_oid__empty_tree_sha1)) - return GIT_OBJECT_TREE; - - return GIT_OBJECT_INVALID; -} - -static int odb_read_hardcoded(bool *found, git_rawobj *raw, const git_oid *id) -{ - git_object_t type; - - *found = false; - - if ((type = odb_hardcoded_type(id)) == GIT_OBJECT_INVALID) - return 0; - - raw->type = type; - raw->len = 0; - raw->data = git__calloc(1, sizeof(uint8_t)); - GIT_ERROR_CHECK_ALLOC(raw->data); - - *found = true; - return 0; -} - -int git_odb__format_object_header( - size_t *written, - char *hdr, - size_t hdr_size, - git_object_size_t obj_len, - git_object_t obj_type) -{ - const char *type_str = git_object_type2string(obj_type); - int hdr_max = (hdr_size > INT_MAX-2) ? (INT_MAX-2) : (int)hdr_size; - int len; - - len = p_snprintf(hdr, hdr_max, "%s %"PRId64, type_str, (int64_t)obj_len); - - if (len < 0 || len >= hdr_max) { - git_error_set(GIT_ERROR_OS, "object header creation failed"); - return -1; - } - - *written = (size_t)(len + 1); - return 0; -} - -int git_odb__hashobj(git_oid *id, git_rawobj *obj) -{ - git_str_vec vec[2]; - char header[64]; - size_t hdrlen; - int error; - - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(obj); - - if (!git_object_typeisloose(obj->type)) { - git_error_set(GIT_ERROR_INVALID, "invalid object type"); - return -1; - } - - if (!obj->data && obj->len != 0) { - git_error_set(GIT_ERROR_INVALID, "invalid object"); - return -1; - } - - if ((error = git_odb__format_object_header(&hdrlen, - header, sizeof(header), obj->len, obj->type)) < 0) - return error; - - vec[0].data = header; - vec[0].len = hdrlen; - vec[1].data = obj->data; - vec[1].len = obj->len; - - return git_hash_vec(id->id, vec, 2, GIT_HASH_ALGORITHM_SHA1); -} - - -static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source) -{ - git_odb_object *object = git__calloc(1, sizeof(git_odb_object)); - - if (object != NULL) { - git_oid_cpy(&object->cached.oid, oid); - object->cached.type = source->type; - object->cached.size = source->len; - object->buffer = source->data; - } - - return object; -} - -void git_odb_object__free(void *object) -{ - if (object != NULL) { - git__free(((git_odb_object *)object)->buffer); - git__free(object); - } -} - -const git_oid *git_odb_object_id(git_odb_object *object) -{ - return &object->cached.oid; -} - -const void *git_odb_object_data(git_odb_object *object) -{ - return object->buffer; -} - -size_t git_odb_object_size(git_odb_object *object) -{ - return object->cached.size; -} - -git_object_t git_odb_object_type(git_odb_object *object) -{ - return object->cached.type; -} - -int git_odb_object_dup(git_odb_object **dest, git_odb_object *source) -{ - git_cached_obj_incref(source); - *dest = source; - return 0; -} - -void git_odb_object_free(git_odb_object *object) -{ - if (object == NULL) - return; - - git_cached_obj_decref(object); -} - -int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type) -{ - size_t hdr_len; - char hdr[64], buffer[FILEIO_BUFSIZE]; - git_hash_ctx ctx; - ssize_t read_len = 0; - int error = 0; - - if (!git_object_typeisloose(type)) { - git_error_set(GIT_ERROR_INVALID, "invalid object type for hash"); - return -1; - } - - if ((error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) - return error; - - if ((error = git_odb__format_object_header(&hdr_len, hdr, - sizeof(hdr), size, type)) < 0) - goto done; - - if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0) - goto done; - - while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - if ((error = git_hash_update(&ctx, buffer, read_len)) < 0) - goto done; - - size -= read_len; - } - - /* If p_read returned an error code, the read obviously failed. - * If size is not zero, the file was truncated after we originally - * stat'd it, so we consider this a read failure too */ - if (read_len < 0 || size > 0) { - git_error_set(GIT_ERROR_OS, "error reading file for hashing"); - error = -1; - - goto done; - } - - error = git_hash_final(out->id, &ctx); - -done: - git_hash_ctx_cleanup(&ctx); - return error; -} - -int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t size, git_object_t type, git_filter_list *fl) -{ - int error; - git_str raw = GIT_STR_INIT; - - if (!fl) - return git_odb__hashfd(out, fd, size, type); - - /* size of data is used in header, so we have to read the whole file - * into memory to apply filters before beginning to calculate the hash - */ - - if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) { - git_str post = GIT_STR_INIT; - - error = git_filter_list__convert_buf(&post, fl, &raw); - - if (!error) - error = git_odb_hash(out, post.ptr, post.size, type); - - git_str_dispose(&post); - } - - return error; -} - -int git_odb__hashlink(git_oid *out, const char *path) -{ - struct stat st; - int size; - int result; - - if (git_fs_path_lstat(path, &st) < 0) - return -1; - - if (!git__is_int(st.st_size) || (int)st.st_size < 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "file size overflow for 32-bit systems"); - return -1; - } - - size = (int)st.st_size; - - if (S_ISLNK(st.st_mode)) { - char *link_data; - int read_len; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, size, 1); - link_data = git__malloc(alloc_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(path, link_data, size); - if (read_len == -1) { - git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", path); - git__free(link_data); - return -1; - } - GIT_ASSERT(read_len <= size); - link_data[read_len] = '\0'; - - result = git_odb_hash(out, link_data, read_len, GIT_OBJECT_BLOB); - git__free(link_data); - } else { - int fd = git_futils_open_ro(path); - if (fd < 0) - return -1; - result = git_odb__hashfd(out, fd, size, GIT_OBJECT_BLOB); - p_close(fd); - } - - return result; -} - -int git_odb_hashfile(git_oid *out, const char *path, git_object_t type) -{ - uint64_t size; - int fd, error = 0; - - if ((fd = git_futils_open_ro(path)) < 0) - return fd; - - 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"); - error = -1; - goto done; - } - - error = git_odb__hashfd(out, fd, (size_t)size, type); - -done: - p_close(fd); - return error; -} - -int git_odb_hash(git_oid *id, const void *data, size_t len, git_object_t type) -{ - git_rawobj raw; - - GIT_ASSERT_ARG(id); - - raw.data = (void *)data; - raw.len = len; - raw.type = type; - - return git_odb__hashobj(id, &raw); -} - -/** - * FAKE WSTREAM - */ - -typedef struct { - git_odb_stream stream; - char *buffer; - size_t size, written; - git_object_t type; -} fake_wstream; - -static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid) -{ - fake_wstream *stream = (fake_wstream *)_stream; - return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type); -} - -static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len) -{ - fake_wstream *stream = (fake_wstream *)_stream; - - GIT_ASSERT(stream->written + len <= stream->size); - - memcpy(stream->buffer + stream->written, data, len); - stream->written += len; - return 0; -} - -static void fake_wstream__free(git_odb_stream *_stream) -{ - fake_wstream *stream = (fake_wstream *)_stream; - - git__free(stream->buffer); - git__free(stream); -} - -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; - - GIT_ERROR_CHECK_BLOBSIZE(size); - blobsize = (size_t)size; - - stream = git__calloc(1, sizeof(fake_wstream)); - GIT_ERROR_CHECK_ALLOC(stream); - - stream->size = blobsize; - stream->type = type; - stream->buffer = git__malloc(blobsize); - if (stream->buffer == NULL) { - git__free(stream); - return -1; - } - - stream->stream.backend = backend; - stream->stream.read = NULL; /* read only */ - stream->stream.write = &fake_wstream__write; - stream->stream.finalize_write = &fake_wstream__fwrite; - stream->stream.free = &fake_wstream__free; - stream->stream.mode = GIT_STREAM_WRONLY; - - *stream_p = (git_odb_stream *)stream; - return 0; -} - -/*********************************************************** - * - * OBJECT DATABASE PUBLIC API - * - * Public calls for the ODB functionality - * - ***********************************************************/ - -static int backend_sort_cmp(const void *a, const void *b) -{ - const backend_internal *backend_a = (const backend_internal *)(a); - const backend_internal *backend_b = (const backend_internal *)(b); - - if (backend_b->priority == backend_a->priority) { - if (backend_a->is_alternate) - return -1; - if (backend_b->is_alternate) - return 1; - return 0; - } - return (backend_b->priority - backend_a->priority); -} - -int git_odb_new(git_odb **out) -{ - git_odb *db = git__calloc(1, sizeof(*db)); - GIT_ERROR_CHECK_ALLOC(db); - - if (git_mutex_init(&db->lock) < 0) { - git__free(db); - return -1; - } - if (git_cache_init(&db->own_cache) < 0) { - git_mutex_free(&db->lock); - git__free(db); - return -1; - } - if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { - git_cache_dispose(&db->own_cache); - git_mutex_free(&db->lock); - git__free(db); - return -1; - } - - *out = db; - GIT_REFCOUNT_INC(db); - return 0; -} - -static int add_backend_internal( - git_odb *odb, git_odb_backend *backend, - int priority, bool is_alternate, ino_t disk_inode) -{ - backend_internal *internal; - - GIT_ASSERT_ARG(odb); - GIT_ASSERT_ARG(backend); - - GIT_ERROR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend"); - - /* Check if the backend is already owned by another ODB */ - GIT_ASSERT(!backend->odb || backend->odb == odb); - - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); - - internal->backend = backend; - internal->priority = priority; - internal->is_alternate = is_alternate; - internal->disk_inode = disk_inode; - - if (git_mutex_lock(&odb->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return -1; - } - if (git_vector_insert(&odb->backends, internal) < 0) { - git_mutex_unlock(&odb->lock); - git__free(internal); - return -1; - } - git_vector_sort(&odb->backends); - internal->backend->odb = odb; - git_mutex_unlock(&odb->lock); - return 0; -} - -int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority) -{ - return add_backend_internal(odb, backend, priority, false, 0); -} - -int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) -{ - return add_backend_internal(odb, backend, priority, true, 0); -} - -size_t git_odb_num_backends(git_odb *odb) -{ - size_t length; - bool locked = true; - - GIT_ASSERT_ARG(odb); - - if (git_mutex_lock(&odb->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - locked = false; - } - length = odb->backends.length; - if (locked) - git_mutex_unlock(&odb->lock); - return length; -} - -static int git_odb__error_unsupported_in_backend(const char *action) -{ - git_error_set(GIT_ERROR_ODB, - "cannot %s - unsupported in the loaded odb backends", action); - return -1; -} - - -int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos) -{ - backend_internal *internal; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(odb); - - - if ((error = git_mutex_lock(&odb->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - internal = git_vector_get(&odb->backends, pos); - - if (!internal || !internal->backend) { - git_mutex_unlock(&odb->lock); - - git_error_set(GIT_ERROR_ODB, "no ODB backend loaded at index %" PRIuZ, pos); - return GIT_ENOTFOUND; - } - *out = internal->backend; - git_mutex_unlock(&odb->lock); - - return 0; -} - -int git_odb__add_default_backends( - git_odb *db, const char *objects_dir, - bool as_alternates, int alternate_depth) -{ - size_t i = 0; - struct stat st; - ino_t inode; - git_odb_backend *loose, *packed; - - /* TODO: inodes are not really relevant on Win32, so we need to find - * a cross-platform workaround for this */ -#ifdef GIT_WIN32 - GIT_UNUSED(i); - GIT_UNUSED(&st); - - inode = 0; -#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); - return -1; - } - - inode = st.st_ino; - - if (git_mutex_lock(&db->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return -1; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *backend = git_vector_get(&db->backends, i); - if (backend->disk_inode == inode) { - git_mutex_unlock(&db->lock); - return 0; - } - } - git_mutex_unlock(&db->lock); -#endif - - /* add the loose object backend */ - if (git_odb_backend_loose(&loose, objects_dir, -1, db->do_fsync, 0, 0) < 0 || - add_backend_internal(db, loose, git_odb__loose_priority, as_alternates, inode) < 0) - return -1; - - /* add the packed file backend */ - if (git_odb_backend_pack(&packed, objects_dir) < 0 || - add_backend_internal(db, packed, git_odb__packed_priority, as_alternates, inode) < 0) - return -1; - - if (git_mutex_lock(&db->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return -1; - } - if (!db->cgraph && git_commit_graph_new(&db->cgraph, objects_dir, false) < 0) { - git_mutex_unlock(&db->lock); - return -1; - } - git_mutex_unlock(&db->lock); - - return load_alternates(db, objects_dir, alternate_depth); -} - -static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth) -{ - git_str alternates_path = GIT_STR_INIT; - git_str alternates_buf = GIT_STR_INIT; - char *buffer; - const char *alternate; - int result = 0; - - /* Git reports an error, we just ignore anything deeper */ - if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) - return 0; - - if (git_str_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) - return -1; - - if (git_fs_path_exists(alternates_path.ptr) == false) { - git_str_dispose(&alternates_path); - return 0; - } - - if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { - git_str_dispose(&alternates_path); - return -1; - } - - buffer = (char *)alternates_buf.ptr; - - /* add each alternate as a new backend; one alternate per line */ - while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { - if (*alternate == '\0' || *alternate == '#') - continue; - - /* - * Relative path: build based on the current `objects` - * folder. However, relative paths are only allowed in - * the current repository. - */ - if (*alternate == '.' && !alternate_depth) { - if ((result = git_str_joinpath(&alternates_path, objects_dir, alternate)) < 0) - break; - alternate = git_str_cstr(&alternates_path); - } - - if ((result = git_odb__add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0) - break; - } - - git_str_dispose(&alternates_path); - git_str_dispose(&alternates_buf); - - return result; -} - -int git_odb_add_disk_alternate(git_odb *odb, const char *path) -{ - return git_odb__add_default_backends(odb, path, true, 0); -} - -int git_odb_set_commit_graph(git_odb *odb, git_commit_graph *cgraph) -{ - int error = 0; - - GIT_ASSERT_ARG(odb); - - if ((error = git_mutex_lock(&odb->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); - return error; - } - git_commit_graph_free(odb->cgraph); - odb->cgraph = cgraph; - git_mutex_unlock(&odb->lock); - - return error; -} - -int git_odb_open(git_odb **out, const char *objects_dir) -{ - git_odb *db; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(objects_dir); - - *out = NULL; - - if (git_odb_new(&db) < 0) - return -1; - - if (git_odb__add_default_backends(db, objects_dir, 0, 0) < 0) { - git_odb_free(db); - return -1; - } - - *out = db; - return 0; -} - -int git_odb__set_caps(git_odb *odb, int caps) -{ - if (caps == GIT_ODB_CAP_FROM_OWNER) { - git_repository *repo = GIT_REFCOUNT_OWNER(odb); - int val; - - if (!repo) { - git_error_set(GIT_ERROR_ODB, "cannot access repository to set odb caps"); - return -1; - } - - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FSYNCOBJECTFILES)) - odb->do_fsync = !!val; - } - - return 0; -} - -static void odb_free(git_odb *db) -{ - size_t i; - bool locked = true; - - if (git_mutex_lock(&db->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - locked = false; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *backend = internal->backend; - - backend->free(backend); - - git__free(internal); - } - if (locked) - git_mutex_unlock(&db->lock); - - git_commit_graph_free(db->cgraph); - git_vector_free(&db->backends); - git_cache_dispose(&db->own_cache); - git_mutex_free(&db->lock); - - git__memzero(db, sizeof(*db)); - git__free(db); -} - -void git_odb_free(git_odb *db) -{ - if (db == NULL) - return; - - GIT_REFCOUNT_DEC(db, odb_free); -} - -static int odb_exists_1( - git_odb *db, - const git_oid *id, - bool only_refreshed) -{ - size_t i; - bool found = false; - int error; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length && !found; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->exists != NULL) - found = (bool)b->exists(b, id); - } - git_mutex_unlock(&db->lock); - - return (int)found; -} - -int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *db) -{ - int error = 0; - git_commit_graph_file *result = NULL; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); - return error; - } - if (!db->cgraph) { - error = GIT_ENOTFOUND; - goto done; - } - error = git_commit_graph_get_file(&result, db->cgraph); - if (error) - goto done; - *out = result; - -done: - git_mutex_unlock(&db->lock); - return error; -} - -static int odb_freshen_1( - git_odb *db, - const git_oid *id, - bool only_refreshed) -{ - size_t i; - bool found = false; - int error; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length && !found; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->freshen != NULL) - found = !b->freshen(b, id); - else if (b->exists != NULL) - found = b->exists(b, id); - } - git_mutex_unlock(&db->lock); - - return (int)found; -} - -int git_odb__freshen(git_odb *db, const git_oid *id) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - - if (odb_freshen_1(db, id, false)) - return 1; - - if (!git_odb_refresh(db)) - return odb_freshen_1(db, id, true); - - /* Failed to refresh, hence not found */ - return 0; -} - -int git_odb_exists(git_odb *db, const git_oid *id) -{ - return git_odb_exists_ext(db, id, 0); -} - -int git_odb_exists_ext(git_odb *db, const git_oid *id, unsigned int flags) -{ - git_odb_object *object; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - - if (git_oid_is_zero(id)) - return 0; - - if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { - git_odb_object_free(object); - return 1; - } - - if (odb_exists_1(db, id, false)) - return 1; - - if (!(flags & GIT_ODB_LOOKUP_NO_REFRESH) && !git_odb_refresh(db)) - return odb_exists_1(db, id, true); - - /* Failed to refresh, hence not found */ - return 0; -} - -static int odb_exists_prefix_1(git_oid *out, git_odb *db, - const git_oid *key, size_t len, bool only_refreshed) -{ - size_t i; - int error = GIT_ENOTFOUND, num_found = 0; - git_oid last_found = {{0}}, found; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ENOTFOUND; - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (!b->exists_prefix) - continue; - - error = b->exists_prefix(&found, b, key, len); - if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) - continue; - if (error) { - git_mutex_unlock(&db->lock); - return error; - } - - /* make sure found item doesn't introduce ambiguity */ - if (num_found) { - if (git_oid__cmp(&last_found, &found)) { - git_mutex_unlock(&db->lock); - return git_odb__error_ambiguous("multiple matches for prefix"); - } - } else { - git_oid_cpy(&last_found, &found); - num_found++; - } - } - git_mutex_unlock(&db->lock); - - if (!num_found) - return GIT_ENOTFOUND; - - if (out) - git_oid_cpy(out, &last_found); - - return 0; -} - -int git_odb_exists_prefix( - git_oid *out, git_odb *db, const git_oid *short_id, size_t len) -{ - int error; - git_oid key = {{0}}; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(short_id); - - if (len < GIT_OID_MINPREFIXLEN) - return git_odb__error_ambiguous("prefix length too short"); - - if (len >= GIT_OID_HEXSZ) { - if (git_odb_exists(db, short_id)) { - if (out) - git_oid_cpy(out, short_id); - return 0; - } else { - return git_odb__error_notfound( - "no match for id prefix", short_id, len); - } - } - - git_oid__cpy_prefix(&key, short_id, len); - - error = odb_exists_prefix_1(out, db, &key, len, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = odb_exists_prefix_1(out, db, &key, len, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("no match for id prefix", &key, len); - - return error; -} - -int git_odb_expand_ids( - git_odb *db, - git_odb_expand_id *ids, - size_t count) -{ - size_t i; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(ids); - - for (i = 0; i < count; i++) { - git_odb_expand_id *query = &ids[i]; - int error = GIT_EAMBIGUOUS; - - if (!query->type) - query->type = GIT_OBJECT_ANY; - - /* if we have a short OID, expand it first */ - if (query->length >= GIT_OID_MINPREFIXLEN && query->length < GIT_OID_HEXSZ) { - git_oid actual_id; - - error = odb_exists_prefix_1(&actual_id, db, &query->id, query->length, false); - if (!error) { - git_oid_cpy(&query->id, &actual_id); - query->length = GIT_OID_HEXSZ; - } - } - - /* - * now we ought to have a 40-char OID, either because we've expanded it - * or because the user passed a full OID. Ensure its type is right. - */ - if (query->length >= GIT_OID_HEXSZ) { - git_object_t actual_type; - - error = odb_otype_fast(&actual_type, db, &query->id); - if (!error) { - if (query->type != GIT_OBJECT_ANY && query->type != actual_type) - error = GIT_ENOTFOUND; - else - query->type = actual_type; - } - } - - switch (error) { - /* no errors, so we've successfully expanded the OID */ - case 0: - continue; - - /* the object is missing or ambiguous */ - case GIT_ENOTFOUND: - case GIT_EAMBIGUOUS: - memset(&query->id, 0, sizeof(git_oid)); - query->length = 0; - query->type = 0; - break; - - /* something went very wrong with the ODB; bail hard */ - default: - return error; - } - } - - git_error_clear(); - return 0; -} - -int git_odb_read_header(size_t *len_p, git_object_t *type_p, git_odb *db, const git_oid *id) -{ - int error; - git_odb_object *object; - - error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); - - if (object) - git_odb_object_free(object); - - return error; -} - -static int odb_read_header_1( - size_t *len_p, git_object_t *type_p, git_odb *db, - const git_oid *id, bool only_refreshed) -{ - size_t i; - git_object_t ht; - bool passthrough = false; - int error; - - if (!only_refreshed && (ht = odb_hardcoded_type(id)) != GIT_OBJECT_INVALID) { - *type_p = ht; - *len_p = 0; - return 0; - } - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (!b->read_header) { - passthrough = true; - continue; - } - - error = b->read_header(len_p, type_p, b, id); - - switch (error) { - case GIT_PASSTHROUGH: - passthrough = true; - break; - case GIT_ENOTFOUND: - break; - default: - git_mutex_unlock(&db->lock); - return error; - } - } - git_mutex_unlock(&db->lock); - - return passthrough ? GIT_PASSTHROUGH : GIT_ENOTFOUND; -} - -int git_odb__read_header_or_object( - git_odb_object **out, size_t *len_p, git_object_t *type_p, - git_odb *db, const git_oid *id) -{ - int error = GIT_ENOTFOUND; - git_odb_object *object; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(len_p); - GIT_ASSERT_ARG(type_p); - - *out = NULL; - - 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) { - *len_p = object->cached.size; - *type_p = object->cached.type; - *out = object; - return 0; - } - - error = odb_read_header_1(len_p, type_p, db, id, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = odb_read_header_1(len_p, type_p, db, id, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("cannot read header for", id, GIT_OID_HEXSZ); - - /* we found the header; return early */ - if (!error) - return 0; - - if (error == GIT_PASSTHROUGH) { - /* - * no backend has header-reading functionality - * so try using `git_odb_read` instead - */ - error = git_odb_read(&object, db, id); - if (!error) { - *len_p = object->cached.size; - *type_p = object->cached.type; - *out = object; - } - } - - return error; -} - -static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, - bool only_refreshed) -{ - size_t i; - git_rawobj raw; - git_odb_object *object; - git_oid hashed; - bool found = false; - int error = 0; - - if (!only_refreshed) { - if ((error = odb_read_hardcoded(&found, &raw, id)) < 0) - return error; - } - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length && !found; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->read != NULL) { - error = b->read(&raw.data, &raw.len, &raw.type, b, id); - if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND) - continue; - - if (error < 0) { - git_mutex_unlock(&db->lock); - return error; - } - - found = true; - } - } - git_mutex_unlock(&db->lock); - - if (!found) - return GIT_ENOTFOUND; - - if (git_odb__strict_hash_verification) { - if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0) - goto out; - - if (!git_oid_equal(id, &hashed)) { - error = git_odb__error_mismatch(id, &hashed); - goto out; - } - } - - git_error_clear(); - if ((object = odb_object__alloc(id, &raw)) == NULL) { - error = -1; - goto out; - } - - *out = git_cache_store_raw(odb_cache(db), object); - -out: - if (error) - git__free(raw.data); - return error; -} - -int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(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); - if (*out != NULL) - return 0; - - error = odb_read_1(out, db, id, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = odb_read_1(out, db, id, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("no match for id", id, GIT_OID_HEXSZ); - - return error; -} - -static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id) -{ - git_odb_object *object; - size_t _unused; - int error; - - 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) { - *type_p = object->cached.type; - git_odb_object_free(object); - return 0; - } - - error = odb_read_header_1(&_unused, type_p, db, id, false); - - if (error == GIT_PASSTHROUGH) { - error = odb_read_1(&object, db, id, false); - if (!error) - *type_p = object->cached.type; - git_odb_object_free(object); - } - - return error; -} - -static int read_prefix_1(git_odb_object **out, git_odb *db, - const git_oid *key, size_t len, bool only_refreshed) -{ - size_t i; - int error = 0; - git_oid found_full_oid = {{0}}; - git_rawobj raw = {0}; - void *data = NULL; - bool found = false; - git_odb_object *object; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->read_prefix != NULL) { - git_oid full_oid; - error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len); - - if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) { - error = 0; - continue; - } - - if (error) { - git_mutex_unlock(&db->lock); - goto out; - } - - git__free(data); - data = raw.data; - - if (found && git_oid__cmp(&full_oid, &found_full_oid)) { - git_str buf = GIT_STR_INIT; - - git_str_printf(&buf, "multiple matches for prefix: %s", - git_oid_tostr_s(&full_oid)); - git_str_printf(&buf, " %s", - git_oid_tostr_s(&found_full_oid)); - - error = git_odb__error_ambiguous(buf.ptr); - git_str_dispose(&buf); - git_mutex_unlock(&db->lock); - goto out; - } - - found_full_oid = full_oid; - found = true; - } - } - git_mutex_unlock(&db->lock); - - if (!found) - return GIT_ENOTFOUND; - - if (git_odb__strict_hash_verification) { - git_oid hash; - - if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0) - goto out; - - if (!git_oid_equal(&found_full_oid, &hash)) { - error = git_odb__error_mismatch(&found_full_oid, &hash); - goto out; - } - } - - if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) { - error = -1; - goto out; - } - - *out = git_cache_store_raw(odb_cache(db), object); - -out: - if (error) - git__free(raw.data); - - return error; -} - -int git_odb_read_prefix( - git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) -{ - git_oid key = {{0}}; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(db); - - if (len < GIT_OID_MINPREFIXLEN) - return git_odb__error_ambiguous("prefix length too short"); - - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - if (len == GIT_OID_HEXSZ) { - *out = git_cache_get_raw(odb_cache(db), short_id); - if (*out != NULL) - return 0; - } - - git_oid__cpy_prefix(&key, short_id, len); - - error = read_prefix_1(out, db, &key, len, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = read_prefix_1(out, db, &key, len, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("no match for prefix", &key, len); - - return error; -} - -int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) -{ - unsigned int i; - git_vector backends = GIT_VECTOR_INIT; - backend_internal *internal; - int error = 0; - - /* Make a copy of the backends vector to invoke the callback without holding the lock. */ - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - goto cleanup; - } - error = git_vector_dup(&backends, &db->backends, NULL); - git_mutex_unlock(&db->lock); - - if (error < 0) - goto cleanup; - - git_vector_foreach(&backends, i, internal) { - git_odb_backend *b = internal->backend; - error = b->foreach(b, cb, payload); - if (error != 0) - goto cleanup; - } - -cleanup: - git_vector_free(&backends); - - return error; -} - -int git_odb_write( - git_oid *oid, git_odb *db, const void *data, size_t len, git_object_t type) -{ - size_t i; - int error; - git_odb_stream *stream; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(db); - - if ((error = git_odb_hash(oid, data, len, type)) < 0) - return error; - - if (git_oid_is_zero(oid)) - return error_null_oid(GIT_EINVALID, "cannot write object"); - - if (git_odb__freshen(db, oid)) - return 0; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0, error = GIT_ERROR; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->write != NULL) - error = b->write(b, oid, data, len, type); - } - git_mutex_unlock(&db->lock); - - if (!error || error == GIT_PASSTHROUGH) - return 0; - - /* if no backends were able to write the object directly, we try a - * streaming write to the backends; just write the whole object into the - * stream in one push - */ - if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) - return error; - - if ((error = stream->write(stream, data, len)) == 0) - error = stream->finalize_write(stream, oid); - - git_odb_stream_free(stream); - return error; -} - -static int hash_header(git_hash_ctx *ctx, git_object_size_t size, git_object_t type) -{ - char header[64]; - size_t hdrlen; - int error; - - if ((error = git_odb__format_object_header(&hdrlen, - header, sizeof(header), size, type)) < 0) - return error; - - return git_hash_update(ctx, header, hdrlen); -} - -int git_odb_open_wstream( - 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; - git_hash_ctx *ctx = NULL; - - GIT_ASSERT_ARG(stream); - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ERROR; - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->writestream != NULL) { - ++writes; - error = b->writestream(stream, b, size, type); - } else if (b->write != NULL) { - ++writes; - error = init_fake_wstream(stream, b, size, type); - } - } - git_mutex_unlock(&db->lock); - - if (error < 0) { - if (error == GIT_PASSTHROUGH) - error = 0; - else if (!writes) - error = git_odb__error_unsupported_in_backend("write object"); - - goto done; - } - - ctx = git__malloc(sizeof(git_hash_ctx)); - GIT_ERROR_CHECK_ALLOC(ctx); - - if ((error = git_hash_ctx_init(ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = hash_header(ctx, size, type)) < 0) - goto done; - - (*stream)->hash_ctx = ctx; - (*stream)->declared_size = size; - (*stream)->received_bytes = 0; - -done: - if (error) - git__free(ctx); - return error; -} - -static int git_odb_stream__invalid_length( - const git_odb_stream *stream, - const char *action) -{ - git_error_set(GIT_ERROR_ODB, - "cannot %s - " - "Invalid length. %"PRId64" was expected. The " - "total size of the received chunks amounts to %"PRId64".", - action, stream->declared_size, stream->received_bytes); - - return -1; -} - -int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) -{ - git_hash_update(stream->hash_ctx, buffer, len); - - stream->received_bytes += len; - - if (stream->received_bytes > stream->declared_size) - return git_odb_stream__invalid_length(stream, - "stream_write()"); - - return stream->write(stream, buffer, len); -} - -int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) -{ - if (stream->received_bytes != stream->declared_size) - return git_odb_stream__invalid_length(stream, - "stream_finalize_write()"); - - git_hash_final(out->id, stream->hash_ctx); - - if (git_odb__freshen(stream->backend->odb, out)) - return 0; - - return stream->finalize_write(stream, out); -} - -int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) -{ - return stream->read(stream, buffer, len); -} - -void git_odb_stream_free(git_odb_stream *stream) -{ - if (stream == NULL) - return; - - git_hash_ctx_cleanup(stream->hash_ctx); - git__free(stream->hash_ctx); - stream->free(stream); -} - -int git_odb_open_rstream( - git_odb_stream **stream, - size_t *len, - git_object_t *type, - git_odb *db, - const git_oid *oid) -{ - size_t i, reads = 0; - int error = GIT_ERROR; - - GIT_ASSERT_ARG(stream); - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ERROR; - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (b->readstream != NULL) { - ++reads; - error = b->readstream(stream, len, type, b, oid); - } - } - git_mutex_unlock(&db->lock); - - if (error == GIT_PASSTHROUGH) - error = 0; - if (error < 0 && !reads) - error = git_odb__error_unsupported_in_backend("read object streamed"); - - return error; -} - -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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ERROR; - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->writepack != NULL) { - ++writes; - error = b->writepack(out, b, db, progress_cb, progress_payload); - } - } - git_mutex_unlock(&db->lock); - - if (error == GIT_PASSTHROUGH) - error = 0; - if (error < 0 && !writes) - error = git_odb__error_unsupported_in_backend("write pack"); - - return error; -} - -int git_odb_write_multi_pack_index(git_odb *db) -{ - size_t i, writes = 0; - int error = GIT_ERROR; - - GIT_ASSERT_ARG(db); - - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->writemidx != NULL) { - ++writes; - error = b->writemidx(b); - } - } - - if (error == GIT_PASSTHROUGH) - error = 0; - if (error < 0 && !writes) - error = git_odb__error_unsupported_in_backend("write multi-pack-index"); - - return error; -} - -void *git_odb_backend_data_alloc(git_odb_backend *backend, size_t len) -{ - GIT_UNUSED(backend); - return git__malloc(len); -} - -#ifndef GIT_DEPRECATE_HARD -void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) -{ - return git_odb_backend_data_alloc(backend, len); -} -#endif - -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; - int error; - - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (b->refresh != NULL) { - int error = b->refresh(b); - if (error < 0) { - git_mutex_unlock(&db->lock); - return error; - } - } - } - if (db->cgraph) - git_commit_graph_refresh(db->cgraph); - git_mutex_unlock(&db->lock); - - return 0; -} - -int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual) -{ - char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(expected_oid, sizeof(expected_oid), expected); - git_oid_tostr(actual_oid, sizeof(actual_oid), actual); - - git_error_set(GIT_ERROR_ODB, "object hash mismatch - expected %s but got %s", - expected_oid, actual_oid); - - return GIT_EMISMATCH; -} - -int git_odb__error_notfound( - const char *message, const git_oid *oid, size_t oid_len) -{ - if (oid != NULL) { - char oid_str[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid_str, oid_len+1, oid); - git_error_set(GIT_ERROR_ODB, "object not found - %s (%.*s)", - message, (int) oid_len, oid_str); - } else - git_error_set(GIT_ERROR_ODB, "object not found - %s", message); - - return GIT_ENOTFOUND; -} - -static int error_null_oid(int error, const char *message) -{ - git_error_set(GIT_ERROR_ODB, "odb: %s: null OID cannot exist", message); - return error; -} - -int git_odb__error_ambiguous(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "ambiguous SHA1 prefix - %s", message); - return GIT_EAMBIGUOUS; -} - -int git_odb_init_backend(git_odb_backend *backend, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT); - return 0; -} diff --git a/src/odb.h b/src/odb.h deleted file mode 100644 index 5aa4cc84a..000000000 --- a/src/odb.h +++ /dev/null @@ -1,149 +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_odb_h__ -#define INCLUDE_odb_h__ - -#include "common.h" - -#include "git2/odb.h" -#include "git2/oid.h" -#include "git2/types.h" -#include "git2/sys/commit_graph.h" - -#include "cache.h" -#include "commit_graph.h" -#include "filter.h" -#include "posix.h" -#include "vector.h" - -#define GIT_OBJECTS_DIR "objects/" -#define GIT_OBJECT_DIR_MODE 0777 -#define GIT_OBJECT_FILE_MODE 0444 - -#define GIT_ODB_DEFAULT_LOOSE_PRIORITY 1 -#define GIT_ODB_DEFAULT_PACKED_PRIORITY 2 - -extern bool git_odb__strict_hash_verification; - -/* DO NOT EXPORT */ -typedef struct { - void *data; /**< Raw, decompressed object data. */ - size_t len; /**< Total number of bytes in data. */ - git_object_t type; /**< Type of this object. */ -} git_rawobj; - -/* EXPORT */ -struct git_odb_object { - git_cached_obj cached; - void *buffer; -}; - -/* EXPORT */ -struct git_odb { - git_refcount rc; - git_mutex lock; /* protects backends */ - git_vector backends; - git_cache own_cache; - git_commit_graph *cgraph; - unsigned int do_fsync :1; -}; - -typedef enum { - GIT_ODB_CAP_FROM_OWNER = -1 -} git_odb_cap_t; - -/* - * Set the capabilities for the object database. - */ -int git_odb__set_caps(git_odb *odb, int caps); - -/* - * Add the default loose and packed backends for a database. - */ -int git_odb__add_default_backends( - git_odb *db, const char *objects_dir, - bool as_alternates, int alternate_depth); - -/* - * Hash a git_rawobj internally. - * The `git_rawobj` is supposed to be previously initialized - */ -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_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, - * but the fd is already open and we have the size of the contents. - * - * Saves us some `stat` calls. - * - * The fd is never closed, not even on error. It must be opened and closed - * by the caller - */ -int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type); - -/* - * Hash an open file descriptor applying an array of filters - * Acts just like git_odb__hashfd with the addition of filters... - */ -int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t len, git_object_t type, git_filter_list *fl); - -/* - * Hash a `path`, assuming it could be a POSIX symlink: if the path is a - * 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_OBJECT_BLOB` because - * symlinks may only point to blobs. - */ -int git_odb__hashlink(git_oid *out, const char *path); - -/** - * Generate a GIT_EMISMATCH error for the ODB. - */ -int git_odb__error_mismatch( - const git_oid *expected, const git_oid *actual); - -/* - * Generate a GIT_ENOTFOUND error for the ODB. - */ -int git_odb__error_notfound( - const char *message, const git_oid *oid, size_t oid_len); - -/* - * Generate a GIT_EAMBIGUOUS error for the ODB. - */ -int git_odb__error_ambiguous(const char *message); - -/* - * Attempt to read object header or just return whole object if it could - * not be read. - */ -int git_odb__read_header_or_object( - git_odb_object **out, size_t *len_p, git_object_t *type_p, - git_odb *db, const git_oid *id); - -/* - * Attempt to get the ODB's commit-graph file. This object is still owned by - * the ODB. If the repository does not contain a commit-graph, it will return - * GIT_ENOTFOUND. - */ -int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *odb); - -/* freshen an entry in the object database */ -int git_odb__freshen(git_odb *db, const git_oid *id); - -/* fully free the object; internal method, DO NOT EXPORT */ -void git_odb_object__free(void *object); - -#endif diff --git a/src/odb_loose.c b/src/odb_loose.c deleted file mode 100644 index 463e24fa5..000000000 --- a/src/odb_loose.c +++ /dev/null @@ -1,1182 +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 "common.h" - -#include -#include "git2/object.h" -#include "git2/sys/odb_backend.h" -#include "futils.h" -#include "hash.h" -#include "odb.h" -#include "delta.h" -#include "filebuf.h" -#include "object.h" -#include "zstream.h" - -#include "git2/odb_backend.h" -#include "git2/types.h" - -/* maximum possible header length */ -#define MAX_HEADER_LEN 64 - -typedef struct { /* object header data */ - git_object_t type; /* object type */ - size_t size; /* object size */ -} obj_hdr; - -typedef struct { - git_odb_stream stream; - git_filebuf fbuf; -} loose_writestream; - -typedef struct { - git_odb_stream stream; - git_map map; - char start[MAX_HEADER_LEN]; - size_t start_len; - size_t start_read; - git_zstream zstream; -} loose_readstream; - -typedef struct loose_backend { - git_odb_backend parent; - - int object_zlib_level; /** loose object zlib compression level. */ - int fsync_object_files; /** loose object file fsync flag. */ - mode_t object_file_mode; - mode_t object_dir_mode; - - size_t objects_dirlen; - char objects_dir[GIT_FLEX_ARRAY]; -} loose_backend; - -/* State structure for exploring directories, - * in order to locate objects matching a short oid. - */ -typedef struct { - size_t dir_len; - unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ - size_t short_oid_len; - int found; /* number of matching - * objects already found */ - unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of - * the object found */ -} loose_locate_object_state; - - -/*********************************************************** - * - * MISCELLANEOUS HELPER FUNCTIONS - * - ***********************************************************/ - -static int object_file_name( - git_str *name, const loose_backend *be, const git_oid *id) -{ - size_t alloclen; - - /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, be->objects_dirlen, GIT_OID_HEXSZ); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 3); - if (git_str_grow(name, alloclen) < 0) - return -1; - - git_str_set(name, be->objects_dir, be->objects_dirlen); - git_fs_path_to_dir(name); - - /* loose object filename: aa/aaa... (41 bytes) */ - git_oid_pathfmt(name->ptr + name->size, id); - name->size += GIT_OID_HEXSZ + 1; - name->ptr[name->size] = '\0'; - - return 0; -} - -static int object_mkdir(const git_str *name, const loose_backend *be) -{ - return git_futils_mkdir_relative( - name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); -} - -static int parse_header_packlike( - obj_hdr *out, size_t *out_len, const unsigned char *data, size_t len) -{ - unsigned long c; - size_t shift, size, used = 0; - - if (len == 0) - goto on_error; - - c = data[used++]; - out->type = (c >> 4) & 7; - - size = c & 15; - shift = 4; - while (c & 0x80) { - if (len <= used) - goto on_error; - - if (sizeof(size_t) * 8 <= shift) - goto on_error; - - c = data[used++]; - size += (c & 0x7f) << shift; - shift += 7; - } - - out->size = size; - - if (out_len) - *out_len = used; - - return 0; - -on_error: - git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); - return -1; -} - -static int parse_header( - obj_hdr *out, - size_t *out_len, - const unsigned char *_data, - size_t data_len) -{ - const char *data = (char *)_data; - size_t i, typename_len, size_idx, size_len; - int64_t size; - - *out_len = 0; - - /* find the object type name */ - for (i = 0, typename_len = 0; i < data_len; i++, typename_len++) { - if (data[i] == ' ') - break; - } - - if (typename_len == data_len) - goto on_error; - - out->type = git_object_stringn2type(data, typename_len); - - size_idx = typename_len + 1; - for (i = size_idx, size_len = 0; i < data_len; i++, size_len++) { - if (data[i] == '\0') - break; - } - - if (i == data_len) - goto on_error; - - if (git__strntol64(&size, &data[size_idx], size_len, NULL, 10) < 0 || - size < 0) - goto on_error; - - if ((uint64_t)size > SIZE_MAX) { - git_error_set(GIT_ERROR_OBJECT, "object is larger than available memory"); - return -1; - } - - out->size = (size_t)size; - - if (GIT_ADD_SIZET_OVERFLOW(out_len, i, 1)) - goto on_error; - - return 0; - -on_error: - git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); - return -1; -} - -static int is_zlib_compressed_data(unsigned char *data, size_t data_len) -{ - unsigned int w; - - if (data_len < 2) - return 0; - - w = ((unsigned int)(data[0]) << 8) + data[1]; - return (data[0] & 0x8F) == 0x08 && !(w % 31); -} - -/*********************************************************** - * - * ODB OBJECT READING & WRITING - * - * Backend for the public API; read headers and full objects - * from the ODB. Write raw data to the ODB. - * - ***********************************************************/ - - -/* - * At one point, there was a loose object format that was intended to - * mimic the format used in pack-files. This was to allow easy copying - * of loose object data into packs. This format is no longer used, but - * we must still read it. - */ -static int read_loose_packlike(git_rawobj *out, git_str *obj) -{ - git_str body = GIT_STR_INIT; - const unsigned char *obj_data; - obj_hdr hdr; - size_t obj_len, head_len, alloc_size; - int error; - - obj_data = (unsigned char *)obj->ptr; - obj_len = obj->size; - - /* - * read the object header, which is an (uncompressed) - * binary encoding of the object type and size. - */ - if ((error = parse_header_packlike(&hdr, &head_len, obj_data, obj_len)) < 0) - goto done; - - if (!git_object_typeisloose(hdr.type) || head_len > obj_len) { - git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); - error = -1; - goto done; - } - - obj_data += head_len; - obj_len -= head_len; - - /* - * allocate a buffer and inflate the data into it - */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || - git_str_init(&body, alloc_size) < 0) { - error = -1; - goto done; - } - - if ((error = git_zstream_inflatebuf(&body, obj_data, obj_len)) < 0) - goto done; - - out->len = hdr.size; - out->type = hdr.type; - out->data = git_str_detach(&body); - -done: - git_str_dispose(&body); - return error; -} - -static int read_loose_standard(git_rawobj *out, git_str *obj) -{ - git_zstream zstream = GIT_ZSTREAM_INIT; - unsigned char head[MAX_HEADER_LEN], *body = NULL; - size_t decompressed, head_len, body_len, alloc_size; - obj_hdr hdr; - int error; - - if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0 || - (error = git_zstream_set_input(&zstream, git_str_cstr(obj), git_str_len(obj))) < 0) - goto done; - - decompressed = sizeof(head); - - /* - * inflate the initial part of the compressed buffer in order to - * parse the header; read the largest header possible, then push the - * remainder into the body buffer. - */ - if ((error = git_zstream_get_output(head, &decompressed, &zstream)) < 0 || - (error = parse_header(&hdr, &head_len, head, decompressed)) < 0) - goto done; - - if (!git_object_typeisloose(hdr.type)) { - git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); - error = -1; - goto done; - } - - /* - * allocate a buffer and inflate the object data into it - * (including the initial sequence in the head buffer). - */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || - (body = git__calloc(1, alloc_size)) == NULL) { - error = -1; - goto done; - } - - GIT_ASSERT(decompressed >= head_len); - body_len = decompressed - head_len; - - if (body_len) - memcpy(body, head + head_len, body_len); - - decompressed = hdr.size - body_len; - if ((error = git_zstream_get_output(body + body_len, &decompressed, &zstream)) < 0) - goto done; - - if (!git_zstream_done(&zstream)) { - git_error_set(GIT_ERROR_ZLIB, "failed to finish zlib inflation: stream aborted prematurely"); - error = -1; - goto done; - } - - body[hdr.size] = '\0'; - - out->data = body; - out->len = hdr.size; - out->type = hdr.type; - -done: - if (error < 0) - git__free(body); - - git_zstream_free(&zstream); - return error; -} - -static int read_loose(git_rawobj *out, git_str *loc) -{ - int error; - git_str obj = GIT_STR_INIT; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(loc); - - if (git_str_oom(loc)) - return -1; - - out->data = NULL; - out->len = 0; - out->type = GIT_OBJECT_INVALID; - - if ((error = git_futils_readbuffer(&obj, loc->ptr)) < 0) - goto done; - - if (!is_zlib_compressed_data((unsigned char *)obj.ptr, obj.size)) - error = read_loose_packlike(out, &obj); - else - error = read_loose_standard(out, &obj); - -done: - git_str_dispose(&obj); - return error; -} - -static int read_header_loose_packlike( - git_rawobj *out, const unsigned char *data, size_t len) -{ - obj_hdr hdr; - size_t header_len; - int error; - - if ((error = parse_header_packlike(&hdr, &header_len, data, len)) < 0) - return error; - - out->len = hdr.size; - out->type = hdr.type; - - return error; -} - -static int read_header_loose_standard( - git_rawobj *out, const unsigned char *data, size_t len) -{ - git_zstream zs = GIT_ZSTREAM_INIT; - obj_hdr hdr = {0}; - unsigned char inflated[MAX_HEADER_LEN] = {0}; - size_t header_len, inflated_len = sizeof(inflated); - int error; - - if ((error = git_zstream_init(&zs, GIT_ZSTREAM_INFLATE)) < 0 || - (error = git_zstream_set_input(&zs, data, len)) < 0 || - (error = git_zstream_get_output_chunk(inflated, &inflated_len, &zs)) < 0 || - (error = parse_header(&hdr, &header_len, inflated, inflated_len)) < 0) - goto done; - - out->len = hdr.size; - out->type = hdr.type; - -done: - git_zstream_free(&zs); - return error; -} - -static int read_header_loose(git_rawobj *out, git_str *loc) -{ - unsigned char obj[1024]; - ssize_t obj_len; - int fd, error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(loc); - - if (git_str_oom(loc)) - return -1; - - out->data = NULL; - - 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 - error = read_header_loose_standard(out, obj, (size_t)obj_len); - - if (!error && !git_object_typeisloose(out->type)) { - git_error_set(GIT_ERROR_ZLIB, "failed to read loose object header"); - error = -1; - goto done; - } - -done: - if (fd >= 0) - p_close(fd); - return error; -} - -static int locate_object( - git_str *object_location, - loose_backend *backend, - const git_oid *oid) -{ - int error = object_file_name(object_location, backend, oid); - - if (!error && !git_fs_path_exists(object_location->ptr)) - return GIT_ENOTFOUND; - - return error; -} - -/* Explore an entry of a directory and see if it matches a short oid */ -static int fn_locate_object_short_oid(void *state, git_str *pathbuf) { - loose_locate_object_state *sstate = (loose_locate_object_state *)state; - - if (git_str_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { - /* Entry cannot be an object. Continue to next entry */ - return 0; - } - - if (git_fs_path_isdir(pathbuf->ptr) == false) { - /* We are already in the directory matching the 2 first hex characters, - * compare the first ncmp characters of the oids */ - if (!memcmp(sstate->short_oid + 2, - (unsigned char *)pathbuf->ptr + sstate->dir_len, - sstate->short_oid_len - 2)) { - - if (!sstate->found) { - sstate->res_oid[0] = sstate->short_oid[0]; - sstate->res_oid[1] = sstate->short_oid[1]; - memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); - } - sstate->found++; - } - } - - if (sstate->found > 1) - return GIT_EAMBIGUOUS; - - return 0; -} - -/* Locate an object matching a given short oid */ -static int locate_object_short_oid( - git_str *object_location, - git_oid *res_oid, - loose_backend *backend, - const git_oid *short_oid, - size_t len) -{ - char *objects_dir = backend->objects_dir; - size_t dir_len = strlen(objects_dir), alloc_len; - loose_locate_object_state state; - int error; - - /* prealloc memory for OBJ_DIR/xx/xx..38x..xx */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 3); - if (git_str_grow(object_location, alloc_len) < 0) - return -1; - - git_str_set(object_location, objects_dir, dir_len); - git_fs_path_to_dir(object_location); - - /* save adjusted position at end of dir so it can be restored later */ - dir_len = git_str_len(object_location); - - /* Convert raw oid to hex formatted oid */ - git_oid_fmt((char *)state.short_oid, short_oid); - - /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ - if (git_str_put(object_location, (char *)state.short_oid, 3) < 0) - return -1; - object_location->ptr[object_location->size - 1] = '/'; - - /* Check that directory exists */ - if (git_fs_path_isdir(object_location->ptr) == false) - return git_odb__error_notfound("no matching loose object for prefix", - short_oid, len); - - state.dir_len = git_str_len(object_location); - state.short_oid_len = len; - state.found = 0; - - /* Explore directory to find a unique object matching short_oid */ - error = git_fs_path_direach( - object_location, 0, fn_locate_object_short_oid, &state); - if (error < 0 && error != GIT_EAMBIGUOUS) - return error; - - if (!state.found) - return git_odb__error_notfound("no matching loose object for prefix", - short_oid, len); - - if (state.found > 1) - return git_odb__error_ambiguous("multiple matches in loose objects"); - - /* Convert obtained hex formatted oid to raw */ - error = git_oid_fromstr(res_oid, (char *)state.res_oid); - if (error) - return error; - - /* Update the location according to the oid obtained */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - git_str_truncate(object_location, dir_len); - if (git_str_grow(object_location, alloc_len) < 0) - return -1; - - git_oid_pathfmt(object_location->ptr + dir_len, res_oid); - - object_location->size += GIT_OID_HEXSZ + 1; - object_location->ptr[object_location->size] = '\0'; - - return 0; -} - - - - - - - - - -/*********************************************************** - * - * LOOSE BACKEND PUBLIC API - * - * Implement the git_odb_backend API calls - * - ***********************************************************/ - -static int loose_backend__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) -{ - git_str object_path = GIT_STR_INIT; - git_rawobj raw; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - raw.len = 0; - raw.type = GIT_OBJECT_INVALID; - - if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { - error = git_odb__error_notfound("no matching loose object", - oid, GIT_OID_HEXSZ); - } else if ((error = read_header_loose(&raw, &object_path)) == 0) { - *len_p = raw.len; - *type_p = raw.type; - } - - git_str_dispose(&object_path); - - return error; -} - -static int loose_backend__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) -{ - git_str object_path = GIT_STR_INIT; - git_rawobj raw; - int error = 0; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { - error = git_odb__error_notfound("no matching loose object", - oid, GIT_OID_HEXSZ); - } else if ((error = read_loose(&raw, &object_path)) == 0) { - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - } - - git_str_dispose(&object_path); - - return error; -} - -static int loose_backend__read_prefix( - git_oid *out_oid, - void **buffer_p, - size_t *len_p, - git_object_t *type_p, - git_odb_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error = 0; - - GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ); - - if (len == GIT_OID_HEXSZ) { - /* We can fall back to regular read method */ - error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (!error) - git_oid_cpy(out_oid, short_oid); - } else { - git_str object_path = GIT_STR_INIT; - git_rawobj raw; - - GIT_ASSERT_ARG(backend && short_oid); - - if ((error = locate_object_short_oid(&object_path, out_oid, - (loose_backend *)backend, short_oid, len)) == 0 && - (error = read_loose(&raw, &object_path)) == 0) - { - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - } - - git_str_dispose(&object_path); - } - - return error; -} - -static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) -{ - git_str object_path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - error = locate_object(&object_path, (loose_backend *)backend, oid); - - git_str_dispose(&object_path); - - return !error; -} - -static int loose_backend__exists_prefix( - git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) -{ - git_str object_path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(short_id); - GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN); - - error = locate_object_short_oid( - &object_path, out, (loose_backend *)backend, short_id, len); - - git_str_dispose(&object_path); - - return error; -} - -struct foreach_state { - size_t dir_len; - git_odb_foreach_cb cb; - void *data; -}; - -GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr) -{ - int v, i = 0; - if (strlen(ptr) != GIT_OID_HEXSZ+1) - return -1; - - if (ptr[2] != '/') { - return -1; - } - - v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]); - if (v < 0) - return -1; - - oid->id[0] = (unsigned char) v; - - ptr += 3; - for (i = 0; i < 38; i += 2) { - v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]); - if (v < 0) - return -1; - - oid->id[1 + i/2] = (unsigned char) v; - } - - return 0; -} - -static int foreach_object_dir_cb(void *_state, git_str *path) -{ - git_oid oid; - struct foreach_state *state = (struct foreach_state *) _state; - - if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0) - return 0; - - return git_error_set_after_callback_function( - state->cb(&oid, state->data), "git_odb_foreach"); -} - -static int foreach_cb(void *_state, git_str *path) -{ - struct foreach_state *state = (struct foreach_state *) _state; - - /* non-dir is some stray file, ignore it */ - if (!git_fs_path_isdir(git_str_cstr(path))) - return 0; - - return git_fs_path_direach(path, 0, foreach_object_dir_cb, state); -} - -static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) -{ - char *objects_dir; - int error; - git_str buf = GIT_STR_INIT; - struct foreach_state state; - loose_backend *backend = (loose_backend *) _backend; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(cb); - - objects_dir = backend->objects_dir; - - git_str_sets(&buf, objects_dir); - git_fs_path_to_dir(&buf); - if (git_str_oom(&buf)) - return -1; - - memset(&state, 0, sizeof(state)); - state.cb = cb; - state.data = data; - state.dir_len = git_str_len(&buf); - - error = git_fs_path_direach(&buf, 0, foreach_cb, &state); - - git_str_dispose(&buf); - - return error; -} - -static int loose_backend__writestream_finalize(git_odb_stream *_stream, const git_oid *oid) -{ - loose_writestream *stream = (loose_writestream *)_stream; - loose_backend *backend = (loose_backend *)_stream->backend; - git_str final_path = GIT_STR_INIT; - int error = 0; - - if (object_file_name(&final_path, backend, oid) < 0 || - object_mkdir(&final_path, backend) < 0) - error = -1; - else - error = git_filebuf_commit_at( - &stream->fbuf, final_path.ptr); - - git_str_dispose(&final_path); - - return error; -} - -static int loose_backend__writestream_write(git_odb_stream *_stream, const char *data, size_t len) -{ - loose_writestream *stream = (loose_writestream *)_stream; - return git_filebuf_write(&stream->fbuf, data, len); -} - -static void loose_backend__writestream_free(git_odb_stream *_stream) -{ - loose_writestream *stream = (loose_writestream *)_stream; - - git_filebuf_cleanup(&stream->fbuf); - git__free(stream); -} - -static int filebuf_flags(loose_backend *backend) -{ - int flags = GIT_FILEBUF_TEMPORARY | - (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT); - - if (backend->fsync_object_files || git_repository__fsync_gitdir) - flags |= GIT_FILEBUF_FSYNC; - - return flags; -} - -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; - char hdr[MAX_HEADER_LEN]; - git_str tmp_path = GIT_STR_INIT; - size_t hdrlen; - int error; - - GIT_ASSERT_ARG(_backend); - - backend = (loose_backend *)_backend; - *stream_out = NULL; - - if ((error = git_odb__format_object_header(&hdrlen, - hdr, sizeof(hdr), length, type)) < 0) - return error; - - stream = git__calloc(1, sizeof(loose_writestream)); - GIT_ERROR_CHECK_ALLOC(stream); - - stream->stream.backend = _backend; - stream->stream.read = NULL; /* read only */ - stream->stream.write = &loose_backend__writestream_write; - stream->stream.finalize_write = &loose_backend__writestream_finalize; - stream->stream.free = &loose_backend__writestream_free; - stream->stream.mode = GIT_STREAM_WRONLY; - - if (git_str_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || - git_filebuf_open(&stream->fbuf, tmp_path.ptr, filebuf_flags(backend), - backend->object_file_mode) < 0 || - stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) - { - git_filebuf_cleanup(&stream->fbuf); - git__free(stream); - stream = NULL; - } - git_str_dispose(&tmp_path); - *stream_out = (git_odb_stream *)stream; - - return !stream ? -1 : 0; -} - -static int loose_backend__readstream_read( - git_odb_stream *_stream, - char *buffer, - size_t buffer_len) -{ - loose_readstream *stream = (loose_readstream *)_stream; - 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. - */ - if (start_remain && buffer_len) { - size_t chunk = min(start_remain, buffer_len); - memcpy(buffer, stream->start + stream->start_read, chunk); - - buffer += chunk; - stream->start_read += chunk; - - total += (int)chunk; - buffer_len -= chunk; - } - - if (buffer_len) { - size_t chunk = buffer_len; - - if ((error = git_zstream_get_output(buffer, &chunk, &stream->zstream)) < 0) - return error; - - total += (int)chunk; - } - - return (int)total; -} - -static void loose_backend__readstream_free(git_odb_stream *_stream) -{ - loose_readstream *stream = (loose_readstream *)_stream; - - git_futils_mmap_free(&stream->map); - git_zstream_free(&stream->zstream); - git__free(stream); -} - -static int loose_backend__readstream_packlike( - obj_hdr *hdr, - loose_readstream *stream) -{ - const unsigned char *data; - size_t data_len, head_len; - int error; - - data = stream->map.data; - data_len = stream->map.len; - - /* - * read the object header, which is an (uncompressed) - * binary encoding of the object type and size. - */ - if ((error = parse_header_packlike(hdr, &head_len, data, data_len)) < 0) - return error; - - if (!git_object_typeisloose(hdr->type)) { - git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); - return -1; - } - - return git_zstream_set_input(&stream->zstream, - data + head_len, data_len - head_len); -} - -static int loose_backend__readstream_standard( - obj_hdr *hdr, - loose_readstream *stream) -{ - unsigned char head[MAX_HEADER_LEN]; - size_t init, head_len; - int error; - - if ((error = git_zstream_set_input(&stream->zstream, - stream->map.data, stream->map.len)) < 0) - return error; - - init = sizeof(head); - - /* - * inflate the initial part of the compressed buffer in order to - * parse the header; read the largest header possible, then store - * it in the `start` field of the stream object. - */ - if ((error = git_zstream_get_output(head, &init, &stream->zstream)) < 0 || - (error = parse_header(hdr, &head_len, head, init)) < 0) - return error; - - if (!git_object_typeisloose(hdr->type)) { - git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); - return -1; - } - - if (init > head_len) { - stream->start_len = init - head_len; - memcpy(stream->start, head + head_len, init - head_len); - } - - return 0; -} - -static int loose_backend__readstream( - git_odb_stream **stream_out, - size_t *len_out, - git_object_t *type_out, - git_odb_backend *_backend, - const git_oid *oid) -{ - loose_backend *backend; - loose_readstream *stream = NULL; - git_hash_ctx *hash_ctx = NULL; - git_str object_path = GIT_STR_INIT; - obj_hdr hdr; - int error = 0; - - GIT_ASSERT_ARG(stream_out); - GIT_ASSERT_ARG(len_out); - GIT_ASSERT_ARG(type_out); - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(oid); - - backend = (loose_backend *)_backend; - *stream_out = NULL; - *len_out = 0; - *type_out = GIT_OBJECT_INVALID; - - if (locate_object(&object_path, backend, oid) < 0) { - error = git_odb__error_notfound("no matching loose object", - oid, GIT_OID_HEXSZ); - goto done; - } - - stream = git__calloc(1, sizeof(loose_readstream)); - GIT_ERROR_CHECK_ALLOC(stream); - - hash_ctx = git__malloc(sizeof(git_hash_ctx)); - GIT_ERROR_CHECK_ALLOC(hash_ctx); - - if ((error = git_hash_ctx_init(hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = git_futils_mmap_ro_file(&stream->map, object_path.ptr)) < 0 || - (error = git_zstream_init(&stream->zstream, GIT_ZSTREAM_INFLATE)) < 0) - goto done; - - /* check for a packlike loose object */ - if (!is_zlib_compressed_data(stream->map.data, stream->map.len)) - error = loose_backend__readstream_packlike(&hdr, stream); - else - error = loose_backend__readstream_standard(&hdr, stream); - - if (error < 0) - goto done; - - stream->stream.backend = _backend; - stream->stream.hash_ctx = hash_ctx; - stream->stream.read = &loose_backend__readstream_read; - stream->stream.free = &loose_backend__readstream_free; - - *stream_out = (git_odb_stream *)stream; - *len_out = hdr.size; - *type_out = hdr.type; - -done: - if (error < 0) { - if (stream) { - git_futils_mmap_free(&stream->map); - git_zstream_free(&stream->zstream); - git__free(stream); - } - if (hash_ctx) { - git_hash_ctx_cleanup(hash_ctx); - git__free(hash_ctx); - } - } - - git_str_dispose(&object_path); - return error; -} - -static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) -{ - int error = 0; - git_str final_path = GIT_STR_INIT; - char header[MAX_HEADER_LEN]; - size_t header_len; - git_filebuf fbuf = GIT_FILEBUF_INIT; - loose_backend *backend; - - backend = (loose_backend *)_backend; - - /* prepare the header for the file */ - if ((error = git_odb__format_object_header(&header_len, - header, sizeof(header), len, type)) < 0) - goto cleanup; - - if (git_str_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || - git_filebuf_open(&fbuf, final_path.ptr, filebuf_flags(backend), - backend->object_file_mode) < 0) - { - error = -1; - goto cleanup; - } - - git_filebuf_write(&fbuf, header, header_len); - git_filebuf_write(&fbuf, data, len); - - if (object_file_name(&final_path, backend, oid) < 0 || - object_mkdir(&final_path, backend) < 0 || - git_filebuf_commit_at(&fbuf, final_path.ptr) < 0) - error = -1; - -cleanup: - if (error < 0) - git_filebuf_cleanup(&fbuf); - git_str_dispose(&final_path); - return error; -} - -static int loose_backend__freshen( - git_odb_backend *_backend, - const git_oid *oid) -{ - loose_backend *backend = (loose_backend *)_backend; - git_str path = GIT_STR_INIT; - int error; - - if (object_file_name(&path, backend, oid) < 0) - return -1; - - error = git_futils_touch(path.ptr, NULL); - git_str_dispose(&path); - - return error; -} - -static void loose_backend__free(git_odb_backend *_backend) -{ - git__free(_backend); -} - -int git_odb_backend_loose( - git_odb_backend **backend_out, - const char *objects_dir, - int compression_level, - int do_fsync, - unsigned int dir_mode, - unsigned int file_mode) -{ - loose_backend *backend; - size_t objects_dirlen, alloclen; - - GIT_ASSERT_ARG(backend_out); - GIT_ASSERT_ARG(objects_dir); - - objects_dirlen = strlen(objects_dir); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(loose_backend), objects_dirlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 2); - backend = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(backend); - - backend->parent.version = GIT_ODB_BACKEND_VERSION; - backend->objects_dirlen = objects_dirlen; - memcpy(backend->objects_dir, objects_dir, objects_dirlen); - if (backend->objects_dir[backend->objects_dirlen - 1] != '/') - backend->objects_dir[backend->objects_dirlen++] = '/'; - - if (compression_level < 0) - compression_level = Z_BEST_SPEED; - - if (dir_mode == 0) - dir_mode = GIT_OBJECT_DIR_MODE; - - if (file_mode == 0) - file_mode = GIT_OBJECT_FILE_MODE; - - backend->object_zlib_level = compression_level; - backend->fsync_object_files = do_fsync; - backend->object_dir_mode = dir_mode; - backend->object_file_mode = file_mode; - - backend->parent.read = &loose_backend__read; - backend->parent.write = &loose_backend__write; - backend->parent.read_prefix = &loose_backend__read_prefix; - backend->parent.read_header = &loose_backend__read_header; - backend->parent.writestream = &loose_backend__writestream; - backend->parent.readstream = &loose_backend__readstream; - backend->parent.exists = &loose_backend__exists; - backend->parent.exists_prefix = &loose_backend__exists_prefix; - backend->parent.foreach = &loose_backend__foreach; - backend->parent.freshen = &loose_backend__freshen; - backend->parent.free = &loose_backend__free; - - *backend_out = (git_odb_backend *)backend; - return 0; -} diff --git a/src/odb_mempack.c b/src/odb_mempack.c deleted file mode 100644 index 6f27f45f8..000000000 --- a/src/odb_mempack.c +++ /dev/null @@ -1,189 +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 "common.h" - -#include "buf.h" -#include "futils.h" -#include "hash.h" -#include "odb.h" -#include "array.h" -#include "oidmap.h" -#include "pack-objects.h" - -#include "git2/odb_backend.h" -#include "git2/object.h" -#include "git2/types.h" -#include "git2/pack.h" -#include "git2/sys/odb_backend.h" -#include "git2/sys/mempack.h" - -struct memobject { - git_oid oid; - size_t len; - git_object_t type; - char data[GIT_FLEX_ARRAY]; -}; - -struct memory_packer_db { - git_odb_backend parent; - git_oidmap *objects; - git_array_t(struct memobject *) commits; -}; - -static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - struct memobject *obj = NULL; - size_t alloc_len; - - if (git_oidmap_exists(db->objects, oid)) - return 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len); - obj = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(obj); - - memcpy(obj->data, data, len); - git_oid_cpy(&obj->oid, oid); - obj->len = len; - obj->type = type; - - 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); - GIT_ERROR_CHECK_ALLOC(store); - *store = obj; - } - - return 0; -} - -static int impl__exists(git_odb_backend *backend, const git_oid *oid) -{ - struct memory_packer_db *db = (struct memory_packer_db *)backend; - - return git_oidmap_exists(db->objects, 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; - - if ((obj = git_oidmap_get(db->objects, oid)) == NULL) - return GIT_ENOTFOUND; - - *len_p = obj->len; - *type_p = obj->type; - *buffer_p = git__malloc(obj->len); - GIT_ERROR_CHECK_ALLOC(*buffer_p); - - memcpy(*buffer_p, obj->data, obj->len); - return 0; -} - -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; - - if ((obj = git_oidmap_get(db->objects, oid)) == NULL) - return GIT_ENOTFOUND; - - *len_p = obj->len; - *type_p = obj->type; - return 0; -} - -static int git_mempack__dump( - git_str *pack, - git_repository *repo, - git_odb_backend *_backend) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - git_packbuilder *packbuilder; - uint32_t i; - int err = -1; - - if (git_packbuilder_new(&packbuilder, repo) < 0) - return -1; - - git_packbuilder_set_threads(packbuilder, 0); - - for (i = 0; i < db->commits.size; ++i) { - struct memobject *commit = db->commits.ptr[i]; - - err = git_packbuilder_insert_commit(packbuilder, &commit->oid); - if (err < 0) - goto cleanup; - } - - err = git_packbuilder__write_buf(pack, packbuilder); - -cleanup: - git_packbuilder_free(packbuilder); - return err; -} - -int git_mempack_dump( - git_buf *pack, - git_repository *repo, - git_odb_backend *_backend) -{ - GIT_BUF_WRAP_PRIVATE(pack, git_mempack__dump, repo, _backend); -} - -int git_mempack_reset(git_odb_backend *_backend) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - struct memobject *object = NULL; - - git_oidmap_foreach_value(db->objects, object, { - git__free(object); - }); - - git_array_clear(db->commits); - - git_oidmap_clear(db->objects); - - return 0; -} - -static void impl__free(git_odb_backend *_backend) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - - git_mempack_reset(_backend); - git_oidmap_free(db->objects); - git__free(db); -} - -int git_mempack_new(git_odb_backend **out) -{ - struct memory_packer_db *db; - - GIT_ASSERT_ARG(out); - - db = git__calloc(1, sizeof(struct memory_packer_db)); - GIT_ERROR_CHECK_ALLOC(db); - - if (git_oidmap_new(&db->objects) < 0) - return -1; - - db->parent.version = GIT_ODB_BACKEND_VERSION; - db->parent.read = &impl__read; - db->parent.write = &impl__write; - db->parent.read_header = &impl__read_header; - db->parent.exists = &impl__exists; - db->parent.free = &impl__free; - - *out = (git_odb_backend *)db; - return 0; -} diff --git a/src/odb_pack.c b/src/odb_pack.c deleted file mode 100644 index 818cc6125..000000000 --- a/src/odb_pack.c +++ /dev/null @@ -1,921 +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 "common.h" - -#include -#include "git2/repository.h" -#include "git2/indexer.h" -#include "git2/sys/odb_backend.h" -#include "delta.h" -#include "futils.h" -#include "hash.h" -#include "midx.h" -#include "mwindow.h" -#include "odb.h" -#include "pack.h" - -#include "git2/odb_backend.h" - -/* re-freshen pack files no more than every 2 seconds */ -#define FRESHEN_FREQUENCY 2 - -struct pack_backend { - git_odb_backend parent; - git_midx_file *midx; - git_vector midx_packs; - git_vector packs; - struct git_pack_file *last_found; - char *pack_folder; -}; - -struct pack_writepack { - struct git_odb_writepack parent; - git_indexer *indexer; -}; - -/** - * The wonderful tale of a Packed Object lookup query - * =================================================== - * A riveting and epic story of epicness and ASCII - * art, presented by yours truly, - * Sir Vicent of Marti - * - * - * Chapter 1: Once upon a time... - * Initialization of the Pack Backend - * -------------------------------------------------- - * - * # git_odb_backend_pack - * | Creates the pack backend structure, initializes the - * | callback pointers to our default read() and exist() methods, - * | and tries to find the `pack` folder, if it exists. ODBs without a `pack` - * | folder are ignored altogether. If there is a `pack` folder, it tries to - * | preload all the known packfiles in the ODB. - * | - * |-# pack_backend__refresh - * | The `multi-pack-index` is loaded if it exists and is valid. - * | Then we run a `dirent` callback through every file in the pack folder, - * | even those present in `multi-pack-index`. The unindexed packfiles are - * | then sorted according to a sorting callback. - * | - * |-# refresh_multi_pack_index - * | Detect the presence of the `multi-pack-index` file. If it needs to be - * | refreshed, frees the old copy and tries to load the new one, together - * | with all the packfiles it indexes. If the process fails, fall back to - * | the old behavior, as if the `multi-pack-index` file was not there. - * | - * |-# packfile_load__cb - * | | This callback is called from `dirent` with every single file - * | | inside the pack folder. We find the packs by actually locating - * | | their index (ends in ".idx"). From that index, we verify that - * | | the corresponding packfile exists and is valid, and if so, we - * | | add it to the pack list. - * | | - * | # git_mwindow_get_pack - * | Make sure that there's a packfile to back this index, and store - * | some very basic information regarding the packfile itself, - * | such as the full path, the size, and the modification time. - * | We don't actually open the packfile to check for internal consistency. - * | - * |-# packfile_sort__cb - * Sort all the preloaded packs according to some specific criteria: - * we prioritize the "newer" packs because it's more likely they - * contain the objects we are looking for, and we prioritize local - * packs over remote ones. - * - * - * - * Chapter 2: To be, or not to be... - * A standard packed `exist` query for an OID - * -------------------------------------------------- - * - * # pack_backend__exists / pack_backend__exists_prefix - * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the - * | packs that have been loaded for our ODB. - * | - * |-# pack_entry_find / pack_entry_find_prefix - * | If there is a multi-pack-index present, search the SHA1 oid in that - * | index first. If it is not found there, iterate through all the unindexed - * | packs that have been preloaded (starting by the pack where the latest - * | object was found) to try to find the OID in one of them. - * | - * |-# git_midx_entry_find - * | Search for the SHA1 oid in the multi-pack-index. See - * | - * | for specifics on the multi-pack-index format and how do we find - * | entries in it. - * | - * |-# git_pack_entry_find - * | Check the index of an individual unindexed pack to see if the SHA1 - * | OID can be found. If we can find the offset to that SHA1 inside of the - * | index, that means the object is contained inside of the packfile and - * | we can stop searching. Before returning, we verify that the - * | packfile behind the index we are searching still exists on disk. - * | - * |-# pack_entry_find_offset - * | Mmap the actual index file to disk if it hasn't been opened - * | yet, and run a binary search through it to find the OID. - * | See - * | for specifics on the Packfile Index format and how do we find - * | entries in it. - * | - * |-# pack_index_open - * | Guess the name of the index based on the full path to the - * | packfile, open it and verify its contents. Only if the index - * | has not been opened already. - * | - * |-# pack_index_check - * Mmap the index file and do a quick run through the header - * to guess the index version (right now we support v1 and v2), - * and to verify that the size of the index makes sense. - * - * - * - * Chapter 3: The neverending story... - * A standard packed `lookup` query for an OID - * -------------------------------------------------- - * - * # pack_backend__read / pack_backend__read_prefix - * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the - * | packs that have been loaded for our ODB. If it does, open the packfile and - * | read from it. - * | - * |-# git_packfile_unpack - * Armed with a packfile and the offset within it, we can finally unpack - * the object pointed at by the SHA1 oid. This involves mmapping part of - * the `.pack` file, and uncompressing the object within it (if it is - * stored in the undelfitied representation), or finding a base object and - * applying some deltas to its uncompressed representation (if it is stored - * in the deltified representation). See - * - * for specifics on the Packfile format and how do we read from it. - * - */ - - -/*********************************************************** - * - * FORWARD DECLARATIONS - * - ***********************************************************/ - -static int packfile_sort__cb(const void *a_, const void *b_); - -static int packfile_load__cb(void *_data, git_str *path); - -static int packfile_byname_search_cmp(const void *path, const void *pack_entry); - -static int pack_entry_find(struct git_pack_entry *e, - struct pack_backend *backend, const git_oid *oid); - -/* Can find the offset of an object given - * a prefix of an identifier. - * Sets GIT_EAMBIGUOUS if short oid is ambiguous. - * This method assumes that len is between - * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. - */ -static int pack_entry_find_prefix( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len); - - - -/*********************************************************** - * - * PACK WINDOW MANAGEMENT - * - ***********************************************************/ - -static int packfile_byname_search_cmp(const void *path_, const void *p_) -{ - const git_str *path = (const git_str *)path_; - const struct git_pack_file *p = (const struct git_pack_file *)p_; - - return strncmp(p->pack_name, git_str_cstr(path), git_str_len(path)); -} - -static int packfile_sort__cb(const void *a_, const void *b_) -{ - const struct git_pack_file *a = a_; - const struct git_pack_file *b = b_; - int st; - - /* - * Local packs tend to contain objects specific to our - * variant of the project than remote ones. In addition, - * remote ones could be on a network mounted filesystem. - * Favor local ones for these reasons. - */ - st = a->pack_local - b->pack_local; - if (st) - return -st; - - /* - * Younger packs tend to contain more recent objects, - * and more recent objects tend to get accessed more - * often. - */ - if (a->mtime < b->mtime) - return 1; - else if (a->mtime == b->mtime) - return 0; - - return -1; -} - - -static int packfile_load__cb(void *data, git_str *path) -{ - struct pack_backend *backend = data; - struct git_pack_file *pack; - const char *path_str = git_str_cstr(path); - git_str index_prefix = GIT_STR_INIT; - size_t cmp_len = git_str_len(path); - int error; - - if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0) - return 0; /* not an index */ - - cmp_len -= strlen(".idx"); - git_str_attach_notowned(&index_prefix, path_str, cmp_len); - - if (git_vector_search2(NULL, &backend->midx_packs, packfile_byname_search_cmp, &index_prefix) == 0) - return 0; - if (git_vector_search2(NULL, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) - return 0; - - error = git_mwindow_get_pack(&pack, path->ptr); - - /* ignore missing .pack file as git does */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - - if (!error) - error = git_vector_insert(&backend->packs, pack); - - return error; - -} - -static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) -{ - struct git_pack_file *last_found = backend->last_found, *p; - git_midx_entry midx_entry; - size_t i; - - if (backend->midx && - git_midx_entry_find(&midx_entry, backend->midx, oid, GIT_OID_HEXSZ) == 0 && - midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { - e->offset = midx_entry.offset; - git_oid_cpy(&e->sha1, &midx_entry.sha1); - e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); - return 0; - } - - if (last_found && - git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) - return 0; - - git_vector_foreach(&backend->packs, i, p) { - if (p == last_found) - continue; - - if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { - backend->last_found = p; - return 0; - } - } - - return git_odb__error_notfound( - "failed to find pack entry", oid, GIT_OID_HEXSZ); -} - -static int pack_entry_find_prefix( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error; - size_t i; - git_oid found_full_oid = {{0}}; - bool found = false; - struct git_pack_file *last_found = backend->last_found, *p; - git_midx_entry midx_entry; - - if (backend->midx) { - error = git_midx_entry_find(&midx_entry, backend->midx, short_oid, len); - if (error == GIT_EAMBIGUOUS) - return error; - if (!error && midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { - e->offset = midx_entry.offset; - git_oid_cpy(&e->sha1, &midx_entry.sha1); - e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); - git_oid_cpy(&found_full_oid, &e->sha1); - found = true; - } - } - - if (last_found) { - error = git_pack_entry_find(e, last_found, short_oid, len); - if (error == GIT_EAMBIGUOUS) - return error; - if (!error) { - if (found && git_oid_cmp(&e->sha1, &found_full_oid)) - return git_odb__error_ambiguous("found multiple pack entries"); - git_oid_cpy(&found_full_oid, &e->sha1); - found = true; - } - } - - git_vector_foreach(&backend->packs, i, p) { - if (p == last_found) - continue; - - error = git_pack_entry_find(e, p, short_oid, len); - if (error == GIT_EAMBIGUOUS) - return error; - if (!error) { - if (found && git_oid_cmp(&e->sha1, &found_full_oid)) - return git_odb__error_ambiguous("found multiple pack entries"); - git_oid_cpy(&found_full_oid, &e->sha1); - found = true; - backend->last_found = p; - } - } - - if (!found) - return git_odb__error_notfound("no matching pack entry for prefix", - short_oid, len); - else - return 0; -} - -/*********************************************************** - * - * MULTI-PACK-INDEX SUPPORT - * - * Functions needed to support the multi-pack-index. - * - ***********************************************************/ - -/* - * Remove the multi-pack-index, and move all midx_packs to packs. - */ -static int remove_multi_pack_index(struct pack_backend *backend) -{ - size_t i, j = git_vector_length(&backend->packs); - struct pack_backend *p; - int error = git_vector_size_hint( - &backend->packs, - j + git_vector_length(&backend->midx_packs)); - if (error < 0) - return error; - - git_vector_foreach(&backend->midx_packs, i, p) - git_vector_set(NULL, &backend->packs, j++, p); - git_vector_clear(&backend->midx_packs); - - git_midx_free(backend->midx); - backend->midx = NULL; - - return 0; -} - -/* - * Loads a single .pack file referred to by the multi-pack-index. These must - * match the order in which they are declared in the multi-pack-index file, - * since these files are referred to by their index. - */ -static int process_multi_pack_index_pack( - struct pack_backend *backend, - size_t i, - const char *packfile_name) -{ - int error; - struct git_pack_file *pack; - size_t found_position; - git_str pack_path = GIT_STR_INIT, index_prefix = GIT_STR_INIT; - - error = git_str_joinpath(&pack_path, backend->pack_folder, packfile_name); - if (error < 0) - return error; - - /* This is ensured by midx_parse_packfile_name() */ - if (git_str_len(&pack_path) <= strlen(".idx") || git__suffixcmp(git_str_cstr(&pack_path), ".idx") != 0) - return git_odb__error_notfound("midx file contained a non-index", NULL, 0); - - git_str_attach_notowned(&index_prefix, git_str_cstr(&pack_path), git_str_len(&pack_path) - strlen(".idx")); - - if (git_vector_search2(&found_position, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) { - /* Pack was found in the packs list. Moving it to the midx_packs list. */ - git_str_dispose(&pack_path); - git_vector_set(NULL, &backend->midx_packs, i, git_vector_get(&backend->packs, found_position)); - git_vector_remove(&backend->packs, found_position); - return 0; - } - - /* Pack was not found. Allocate a new one. */ - error = git_mwindow_get_pack(&pack, git_str_cstr(&pack_path)); - git_str_dispose(&pack_path); - if (error < 0) - return error; - - git_vector_set(NULL, &backend->midx_packs, i, pack); - return 0; -} - -/* - * Reads the multi-pack-index. If this fails for whatever reason, the - * multi-pack-index object is freed, and all the packfiles that are related to - * it are moved to the unindexed packfiles vector. - */ -static int refresh_multi_pack_index(struct pack_backend *backend) -{ - int error; - git_str midx_path = GIT_STR_INIT; - const char *packfile_name; - size_t i; - - error = git_str_joinpath(&midx_path, backend->pack_folder, "multi-pack-index"); - if (error < 0) - return error; - - /* - * Check whether the multi-pack-index has changed. If it has, close any - * old multi-pack-index and move all the packfiles to the unindexed - * packs. This is done to prevent losing any open packfiles in case - * refreshing the new multi-pack-index fails, or the file is deleted. - */ - if (backend->midx) { - if (!git_midx_needs_refresh(backend->midx, git_str_cstr(&midx_path))) { - git_str_dispose(&midx_path); - return 0; - } - error = remove_multi_pack_index(backend); - if (error < 0) { - git_str_dispose(&midx_path); - return error; - } - } - - error = git_midx_open(&backend->midx, git_str_cstr(&midx_path)); - git_str_dispose(&midx_path); - if (error < 0) - return error; - - git_vector_resize_to(&backend->midx_packs, git_vector_length(&backend->midx->packfile_names)); - - git_vector_foreach(&backend->midx->packfile_names, i, packfile_name) { - error = process_multi_pack_index_pack(backend, i, packfile_name); - if (error < 0) { - /* - * Something failed during reading multi-pack-index. - * Restore the state of backend as if the - * multi-pack-index was never there, and move all - * packfiles that have been processed so far to the - * unindexed packs. - */ - git_vector_resize_to(&backend->midx_packs, i); - remove_multi_pack_index(backend); - return error; - } - } - - return 0; -} - -/*********************************************************** - * - * PACKED BACKEND PUBLIC API - * - * Implement the git_odb_backend API calls - * - ***********************************************************/ -static int pack_backend__refresh(git_odb_backend *backend_) -{ - int error; - struct stat st; - git_str path = GIT_STR_INIT; - struct pack_backend *backend = (struct pack_backend *)backend_; - - if (backend->pack_folder == NULL) - return 0; - - if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) - return git_odb__error_notfound("failed to refresh packfiles", NULL, 0); - - if (refresh_multi_pack_index(backend) < 0) { - /* - * It is okay if this fails. We will just not use the - * multi-pack-index in this case. - */ - git_error_clear(); - } - - /* reload all packs */ - git_str_sets(&path, backend->pack_folder); - error = git_fs_path_direach(&path, 0, packfile_load__cb, backend); - - git_str_dispose(&path); - git_vector_sort(&backend->packs); - - return error; -} - -static int pack_backend__read_header( - size_t *len_p, git_object_t *type_p, - struct git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - int error; - - GIT_ASSERT_ARG(len_p); - GIT_ASSERT_ARG(type_p); - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) - return error; - - return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); -} - -static int pack_backend__freshen( - git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - time_t now; - int error; - - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) - return error; - - now = time(NULL); - - if (e.p->last_freshen > now - FRESHEN_FREQUENCY) - return 0; - - if ((error = git_futils_touch(e.p->pack_name, &now)) < 0) - return error; - - e.p->last_freshen = now; - return 0; -} - -static int pack_backend__read( - void **buffer_p, size_t *len_p, git_object_t *type_p, - git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - git_rawobj raw = {NULL}; - int error; - - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 || - (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0) - return error; - - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - - return 0; -} - -static int pack_backend__read_prefix( - git_oid *out_oid, - void **buffer_p, - size_t *len_p, - git_object_t *type_p, - git_odb_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error = 0; - - if (len < GIT_OID_MINPREFIXLEN) - error = git_odb__error_ambiguous("prefix length too short"); - - else if (len >= GIT_OID_HEXSZ) { - /* We can fall back to regular read method */ - error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (!error) - git_oid_cpy(out_oid, short_oid); - } else { - struct git_pack_entry e; - git_rawobj raw = {NULL}; - - if ((error = pack_entry_find_prefix( - &e, (struct pack_backend *)backend, short_oid, len)) == 0 && - (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0) - { - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - git_oid_cpy(out_oid, &e.sha1); - } - } - - return error; -} - -static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; -} - -static int pack_backend__exists_prefix( - git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) -{ - int error; - struct pack_backend *pb = (struct pack_backend *)backend; - struct git_pack_entry e = {0}; - - error = pack_entry_find_prefix(&e, pb, short_id, len); - git_oid_cpy(out, &e.sha1); - return error; -} - -static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) -{ - int error; - struct git_pack_file *p; - struct pack_backend *backend; - unsigned int i; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(cb); - - backend = (struct pack_backend *)_backend; - - /* Make sure we know about the packfiles */ - if ((error = pack_backend__refresh(_backend)) != 0) - return error; - - if (backend->midx && (error = git_midx_foreach_entry(backend->midx, cb, data)) != 0) - return error; - git_vector_foreach(&backend->packs, i, p) { - if ((error = git_pack_foreach_entry(p, cb, data)) != 0) - return error; - } - - return 0; -} - -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; - - GIT_ASSERT_ARG(writepack); - - return git_indexer_append(writepack->indexer, data, size, stats); -} - -static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_indexer_progress *stats) -{ - struct pack_writepack *writepack = (struct pack_writepack *)_writepack; - - GIT_ASSERT_ARG(writepack); - - return git_indexer_commit(writepack->indexer, stats); -} - -static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) -{ - struct pack_writepack *writepack; - - if (!_writepack) - return; - - writepack = (struct pack_writepack *)_writepack; - - git_indexer_free(writepack->indexer); - git__free(writepack); -} - -static int pack_backend__writepack(struct git_odb_writepack **out, - git_odb_backend *_backend, - git_odb *odb, - git_indexer_progress_cb progress_cb, - void *progress_payload) -{ - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - struct pack_backend *backend; - struct pack_writepack *writepack; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(_backend); - - *out = NULL; - - opts.progress_cb = progress_cb; - opts.progress_cb_payload = progress_payload; - - backend = (struct pack_backend *)_backend; - - writepack = git__calloc(1, sizeof(struct pack_writepack)); - GIT_ERROR_CHECK_ALLOC(writepack); - - if (git_indexer_new(&writepack->indexer, - backend->pack_folder, 0, odb, &opts) < 0) { - git__free(writepack); - return -1; - } - - writepack->parent.backend = _backend; - writepack->parent.append = pack_backend__writepack_append; - writepack->parent.commit = pack_backend__writepack_commit; - writepack->parent.free = pack_backend__writepack_free; - - *out = (git_odb_writepack *)writepack; - - return 0; -} - -static int get_idx_path( - git_str *idx_path, - struct pack_backend *backend, - struct git_pack_file *p) -{ - size_t path_len; - int error; - - error = git_fs_path_prettify(idx_path, p->pack_name, backend->pack_folder); - if (error < 0) - return error; - path_len = git_str_len(idx_path); - if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(idx_path), ".pack") != 0) - return git_odb__error_notfound("packfile does not end in .pack", NULL, 0); - path_len -= strlen(".pack"); - error = git_str_splice(idx_path, path_len, strlen(".pack"), ".idx", strlen(".idx")); - if (error < 0) - return error; - - return 0; -} - -static int pack_backend__writemidx(git_odb_backend *_backend) -{ - struct pack_backend *backend; - git_midx_writer *w = NULL; - struct git_pack_file *p; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(_backend); - - backend = (struct pack_backend *)_backend; - - error = git_midx_writer_new(&w, backend->pack_folder); - if (error < 0) - return error; - - git_vector_foreach(&backend->midx_packs, i, p) { - git_str idx_path = GIT_STR_INIT; - error = get_idx_path(&idx_path, backend, p); - if (error < 0) - goto cleanup; - error = git_midx_writer_add(w, git_str_cstr(&idx_path)); - git_str_dispose(&idx_path); - if (error < 0) - goto cleanup; - } - git_vector_foreach(&backend->packs, i, p) { - git_str idx_path = GIT_STR_INIT; - error = get_idx_path(&idx_path, backend, p); - if (error < 0) - goto cleanup; - error = git_midx_writer_add(w, git_str_cstr(&idx_path)); - git_str_dispose(&idx_path); - if (error < 0) - goto cleanup; - } - - /* - * Invalidate the previous midx before writing the new one. - */ - error = remove_multi_pack_index(backend); - if (error < 0) - goto cleanup; - error = git_midx_writer_commit(w); - if (error < 0) - goto cleanup; - error = refresh_multi_pack_index(backend); - -cleanup: - git_midx_writer_free(w); - return error; -} - -static void pack_backend__free(git_odb_backend *_backend) -{ - struct pack_backend *backend; - struct git_pack_file *p; - size_t i; - - if (!_backend) - return; - - backend = (struct pack_backend *)_backend; - - git_vector_foreach(&backend->midx_packs, i, p) - git_mwindow_put_pack(p); - git_vector_foreach(&backend->packs, i, p) - git_mwindow_put_pack(p); - - git_midx_free(backend->midx); - git_vector_free(&backend->midx_packs); - git_vector_free(&backend->packs); - git__free(backend->pack_folder); - git__free(backend); -} - -static int pack_backend__alloc(struct pack_backend **out, size_t initial_size) -{ - struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - if (git_vector_init(&backend->midx_packs, 0, NULL) < 0) { - git__free(backend); - return -1; - } - if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) { - git_vector_free(&backend->midx_packs); - git__free(backend); - return -1; - } - - backend->parent.version = GIT_ODB_BACKEND_VERSION; - - backend->parent.read = &pack_backend__read; - backend->parent.read_prefix = &pack_backend__read_prefix; - backend->parent.read_header = &pack_backend__read_header; - backend->parent.exists = &pack_backend__exists; - backend->parent.exists_prefix = &pack_backend__exists_prefix; - backend->parent.refresh = &pack_backend__refresh; - backend->parent.foreach = &pack_backend__foreach; - backend->parent.writepack = &pack_backend__writepack; - backend->parent.writemidx = &pack_backend__writemidx; - backend->parent.freshen = &pack_backend__freshen; - backend->parent.free = &pack_backend__free; - - *out = backend; - return 0; -} - -int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) -{ - struct pack_backend *backend = NULL; - struct git_pack_file *packfile = NULL; - - if (pack_backend__alloc(&backend, 1) < 0) - return -1; - - if (git_mwindow_get_pack(&packfile, idx) < 0 || - git_vector_insert(&backend->packs, packfile) < 0) - { - pack_backend__free((git_odb_backend *)backend); - return -1; - } - - *backend_out = (git_odb_backend *)backend; - return 0; -} - -int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) -{ - int error = 0; - struct pack_backend *backend = NULL; - git_str path = GIT_STR_INIT; - - if (pack_backend__alloc(&backend, 8) < 0) - return -1; - - if (!(error = git_str_joinpath(&path, objects_dir, "pack")) && - git_fs_path_isdir(git_str_cstr(&path))) - { - backend->pack_folder = git_str_detach(&path); - error = pack_backend__refresh((git_odb_backend *)backend); - } - - if (error < 0) { - pack_backend__free((git_odb_backend *)backend); - backend = NULL; - } - - *backend_out = (git_odb_backend *)backend; - - git_str_dispose(&path); - - return error; -} diff --git a/src/offmap.c b/src/offmap.c deleted file mode 100644 index be9eb66d8..000000000 --- a/src/offmap.c +++ /dev/null @@ -1,101 +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 "offmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(off, off64_t, void *) - -__KHASH_IMPL(off, static kh_inline, off64_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) - - -int git_offmap_new(git_offmap **out) -{ - *out = kh_init(off); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_offmap_free(git_offmap *map) -{ - kh_destroy(off, map); -} - -void git_offmap_clear(git_offmap *map) -{ - kh_clear(off, map); -} - -size_t git_offmap_size(git_offmap *map) -{ - return kh_size(map); -} - -void *git_offmap_get(git_offmap *map, const off64_t 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_set(git_offmap *map, const off64_t key, void *value) -{ - 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_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_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_value(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/offmap.h b/src/offmap.h deleted file mode 100644 index 81c459b01..000000000 --- a/src/offmap.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2012 the libgit2 contributors - * - * 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_offmap_h__ -#define INCLUDE_offmap_h__ - -#include "common.h" - -#include "git2/types.h" - -/** A map with `off64_t`s as key. */ -typedef struct kh_off_s git_offmap; - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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 = 0; \ - while (git_offmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ - code; \ - } } - -#define git_offmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_offmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/oid.c b/src/oid.c deleted file mode 100644 index 19061e899..000000000 --- a/src/oid.c +++ /dev/null @@ -1,463 +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 "oid.h" - -#include "git2/oid.h" -#include "repository.h" -#include "threadstate.h" -#include -#include - -const git_oid git_oid__empty_blob_sha1 = - {{ 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, 0xd6, 0x43, 0x4b, 0x8b, - 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91 }}; -const git_oid git_oid__empty_tree_sha1 = - {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, - 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }}; - -static char to_hex[] = "0123456789abcdef"; - -static int oid_error_invalid(const char *msg) -{ - git_error_set(GIT_ERROR_INVALID, "unable to parse OID - %s", msg); - return -1; -} - -int git_oid_fromstrn(git_oid *out, const char *str, size_t length) -{ - size_t p; - int v; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(str); - - if (!length) - return oid_error_invalid("too short"); - - if (length > GIT_OID_HEXSZ) - return oid_error_invalid("too long"); - - memset(out->id, 0, GIT_OID_RAWSZ); - - for (p = 0; p < length; p++) { - v = git__fromhex(str[p]); - if (v < 0) - return oid_error_invalid("contains invalid characters"); - - out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4)); - } - - return 0; -} - -int git_oid_fromstrp(git_oid *out, const char *str) -{ - return git_oid_fromstrn(out, str, strlen(str)); -} - -int git_oid_fromstr(git_oid *out, const char *str) -{ - return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); -} - -GIT_INLINE(char) *fmt_one(char *str, unsigned int val) -{ - *str++ = to_hex[val >> 4]; - *str++ = to_hex[val & 0xf]; - return str; -} - -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 0; - } - if (n > GIT_OID_HEXSZ) { - memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ); - n = GIT_OID_HEXSZ; - } - - max_i = n / 2; - - for (i = 0; i < max_i; i++) - str = fmt_one(str, oid->id[i]); - - if (n & 1) - *str++ = to_hex[oid->id[i] >> 4]; - - return 0; -} - -int git_oid_fmt(char *str, const git_oid *oid) -{ - return git_oid_nfmt(str, GIT_OID_HEXSZ, oid); -} - -int git_oid_pathfmt(char *str, const git_oid *oid) -{ - size_t i; - - str = fmt_one(str, oid->id[0]); - *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) -{ - char *str = GIT_THREADSTATE->oid_fmt; - git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); - return str; -} - -char *git_oid_allocfmt(const git_oid *oid) -{ - char *str = git__malloc(GIT_OID_HEXSZ + 1); - if (!str) - return NULL; - git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); - return str; -} - -char *git_oid_tostr(char *out, size_t n, const git_oid *oid) -{ - if (!out || n == 0) - return ""; - - if (n > GIT_OID_HEXSZ + 1) - n = GIT_OID_HEXSZ + 1; - - git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */ - out[n - 1] = '\0'; - - return out; -} - -int git_oid__parse( - git_oid *oid, const char **buffer_out, - const char *buffer_end, const char *header) -{ - const size_t sha_len = GIT_OID_HEXSZ; - const size_t header_len = strlen(header); - - const char *buffer = *buffer_out; - - if (buffer + (header_len + sha_len + 1) > buffer_end) - return -1; - - if (memcmp(buffer, header, header_len) != 0) - return -1; - - if (buffer[header_len + sha_len] != '\n') - return -1; - - if (git_oid_fromstr(oid, buffer + header_len) < 0) - return -1; - - *buffer_out = buffer + (header_len + sha_len + 1); - - return 0; -} - -void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid) -{ - char hex_oid[GIT_OID_HEXSZ]; - - git_oid_fmt(hex_oid, oid); - git_str_puts(buf, header); - git_str_put(buf, hex_oid, GIT_OID_HEXSZ); - git_str_putc(buf, '\n'); -} - -int git_oid_fromraw(git_oid *out, const unsigned char *raw) -{ - memcpy(out->id, raw, sizeof(out->id)); - return 0; -} - -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) -{ - return git_oid__cmp(a, b); -} - -int git_oid_equal(const git_oid *a, const git_oid *b) -{ - return (git_oid__cmp(a, b) == 0); -} - -int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len) -{ - const unsigned char *a = oid_a->id; - const unsigned char *b = oid_b->id; - - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - while (len > 1) { - if (*a != *b) - return 1; - a++; - b++; - len -= 2; - }; - - if (len) - if ((*a ^ *b) & 0xf0) - return 1; - - return 0; -} - -int git_oid_strcmp(const git_oid *oid_a, const char *str) -{ - const unsigned char *a; - unsigned char strval; - int hexval; - - for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) { - if ((hexval = git__fromhex(*str++)) < 0) - return -1; - strval = (unsigned char)(hexval << 4); - if (*str) { - if ((hexval = git__fromhex(*str++)) < 0) - return -1; - strval |= hexval; - } - if (*a != strval) - return (*a - strval); - } - - return 0; -} - -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_is_zero(const git_oid *oid_a) -{ - const unsigned char *a = oid_a->id; - unsigned int i; - for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) - if (*a != 0) - return 0; - return 1; -} - -#ifndef GIT_DEPRECATE_HARD -int git_oid_iszero(const git_oid *oid_a) -{ - return git_oid_is_zero(oid_a); -} -#endif - -typedef short node_index; - -typedef union { - const char *tail; - node_index children[16]; -} trie_node; - -struct git_oid_shorten { - trie_node *nodes; - size_t node_count, size; - int min_length, full; -}; - -static int resize_trie(git_oid_shorten *self, size_t new_size) -{ - self->nodes = git__reallocarray(self->nodes, new_size, sizeof(trie_node)); - GIT_ERROR_CHECK_ALLOC(self->nodes); - - if (new_size > self->size) { - memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); - } - - self->size = new_size; - return 0; -} - -static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) -{ - trie_node *node, *leaf; - node_index idx_leaf; - - if (os->node_count >= os->size) { - if (resize_trie(os, os->size * 2) < 0) - return NULL; - } - - idx_leaf = (node_index)os->node_count++; - - if (os->node_count == SHRT_MAX) { - os->full = 1; - return NULL; - } - - node = &os->nodes[idx]; - node->children[push_at] = -idx_leaf; - - leaf = &os->nodes[idx_leaf]; - leaf->tail = oid; - - return node; -} - -git_oid_shorten *git_oid_shorten_new(size_t min_length) -{ - git_oid_shorten *os; - - GIT_ASSERT_ARG_WITH_RETVAL((size_t)((int)min_length) == min_length, NULL); - - os = git__calloc(1, sizeof(git_oid_shorten)); - if (os == NULL) - return NULL; - - if (resize_trie(os, 16) < 0) { - git__free(os); - return NULL; - } - - os->node_count = 1; - os->min_length = (int)min_length; - - return os; -} - -void git_oid_shorten_free(git_oid_shorten *os) -{ - if (os == NULL) - return; - - git__free(os->nodes); - git__free(os); -} - - -/* - * What wizardry is this? - * - * This is just a memory-optimized trie: basically a very fancy - * 16-ary tree, which is used to store the prefixes of the OID - * strings. - * - * Read more: http://en.wikipedia.org/wiki/Trie - * - * Magic that happens in this method: - * - * - Each node in the trie is an union, so it can work both as - * a normal node, or as a leaf. - * - * - Each normal node points to 16 children (one for each possible - * character in the oid). This is *not* stored in an array of - * pointers, because in a 64-bit arch this would be sucking - * 16*sizeof(void*) = 128 bytes of memory per node, which is - * insane. What we do is store Node Indexes, and use these indexes - * to look up each node in the om->index array. These indexes are - * signed shorts, so this limits the amount of unique OIDs that - * fit in the structure to about 20000 (assuming a more or less uniform - * distribution). - * - * - All the nodes in om->index array are stored contiguously in - * memory, and each of them is 32 bytes, so we fit 2x nodes per - * cache line. Convenient for speed. - * - * - To differentiate the leafs from the normal nodes, we store all - * the indexes towards a leaf as a negative index (indexes to normal - * nodes are positives). When we find that one of the children for - * a node has a negative value, that means it's going to be a leaf. - * This reduces the amount of indexes we have by two, but also reduces - * the size of each node by 1-4 bytes (the amount we would need to - * add a `is_leaf` field): this is good because it allows the nodes - * to fit cleanly in cache lines. - * - * - Once we reach an empty children, instead of continuing to insert - * new nodes for each remaining character of the OID, we store a pointer - * to the tail in the leaf; if the leaf is reached again, we turn it - * into a normal node and use the tail to create a new leaf. - * - * This is a pretty good balance between performance and memory usage. - */ -int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) -{ - int i; - bool is_leaf; - node_index idx; - - if (os->full) { - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); - return -1; - } - - if (text_oid == NULL) - return os->min_length; - - idx = 0; - is_leaf = false; - - for (i = 0; i < GIT_OID_HEXSZ; ++i) { - int c = git__fromhex(text_oid[i]); - trie_node *node; - - if (c == -1) { - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - invalid hex value"); - return -1; - } - - node = &os->nodes[idx]; - - if (is_leaf) { - const char *tail; - - tail = node->tail; - node->tail = NULL; - - node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); - if (node == NULL) { - if (os->full) - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); - return -1; - } - } - - if (node->children[c] == 0) { - if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) { - if (os->full) - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); - return -1; - } - break; - } - - idx = node->children[c]; - is_leaf = false; - - if (idx < 0) { - node->children[c] = idx = -idx; - is_leaf = true; - } - } - - if (++i > os->min_length) - os->min_length = i; - - return os->min_length; -} - diff --git a/src/oid.h b/src/oid.h deleted file mode 100644 index 5baec33e5..000000000 --- a/src/oid.h +++ /dev/null @@ -1,66 +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_oid_h__ -#define INCLUDE_oid_h__ - -#include "common.h" - -#include "git2/oid.h" - -extern const git_oid git_oid__empty_blob_sha1; -extern const git_oid git_oid__empty_tree_sha1; - -/** - * Format a git_oid into a newly allocated c-string. - * - * The c-string is owned by the caller and needs to be manually freed. - * - * @param id the oid structure to format - * @return the c-string; NULL if memory is exhausted. Caller must - * deallocate the string with git__free(). - */ -char *git_oid_allocfmt(const git_oid *id); - -GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) -{ - return memcmp(sha1, sha2, GIT_OID_RAWSZ); -} - -/* - * Compare two oid structures. - * - * @param a first oid structure. - * @param b second oid structure. - * @return <0, 0, >0 if a < b, a == b, a > b. - */ -GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) -{ - return git_oid__hashcmp(a->id, b->id); -} - -GIT_INLINE(void) git_oid__cpy_prefix( - git_oid *out, const git_oid *id, size_t len) -{ - memcpy(&out->id, id->id, (len + 1) / 2); - - if (len & 1) - out->id[len / 2] &= 0xF0; -} - -GIT_INLINE(bool) git_oid__is_hexstr(const char *str) -{ - size_t i; - - for (i = 0; str[i] != '\0'; i++) { - if (git__fromhex(str[i]) < 0) - return false; - } - - return (i == GIT_OID_HEXSZ); -} - -#endif diff --git a/src/oidarray.c b/src/oidarray.c deleted file mode 100644 index 583017c4e..000000000 --- a/src/oidarray.c +++ /dev/null @@ -1,43 +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 "oidarray.h" - -#include "git2/oidarray.h" -#include "array.h" - -void git_oidarray_dispose(git_oidarray *arr) -{ - git__free(arr->ids); -} - -void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array) -{ - arr->count = array->size; - arr->ids = array->ptr; -} - -void git_oidarray__reverse(git_oidarray *arr) -{ - size_t i; - git_oid tmp; - - for (i = 0; i < arr->count / 2; i++) { - git_oid_cpy(&tmp, &arr->ids[i]); - git_oid_cpy(&arr->ids[i], &arr->ids[(arr->count-1)-i]); - git_oid_cpy(&arr->ids[(arr->count-1)-i], &tmp); - } -} - -#ifndef GIT_DEPRECATE_HARD - -void git_oidarray_free(git_oidarray *arr) -{ - git_oidarray_dispose(arr); -} - -#endif diff --git a/src/oidarray.h b/src/oidarray.h deleted file mode 100644 index eed3a1091..000000000 --- a/src/oidarray.h +++ /dev/null @@ -1,20 +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_oidarray_h__ -#define INCLUDE_oidarray_h__ - -#include "common.h" - -#include "git2/oidarray.h" -#include "array.h" - -typedef git_array_t(git_oid) git_array_oid_t; - -extern void git_oidarray__reverse(git_oidarray *arr); -extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array); - -#endif diff --git a/src/oidmap.c b/src/oidmap.c deleted file mode 100644 index 0ae8bf33e..000000000 --- a/src/oidmap.c +++ /dev/null @@ -1,107 +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 "oidmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(oid, const git_oid *, void *) - -GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) -{ - khint_t h; - memcpy(&h, oid, sizeof(khint_t)); - return h; -} - -__KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal) - -int git_oidmap_new(git_oidmap **out) -{ - *out = kh_init(oid); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_oidmap_free(git_oidmap *map) -{ - kh_destroy(oid, map); -} - -void git_oidmap_clear(git_oidmap *map) -{ - kh_clear(oid, map); -} - -size_t git_oidmap_size(git_oidmap *map) -{ - return kh_size(map); -} - -void *git_oidmap_get(git_oidmap *map, const git_oid *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_set(git_oidmap *map, const git_oid *key, void *value) -{ - 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) -{ - return kh_get(oid, map, key) != kh_end(map); -} - -int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_value(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/oidmap.h b/src/oidmap.h deleted file mode 100644 index b748f727c..000000000 --- a/src/oidmap.h +++ /dev/null @@ -1,128 +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_oidmap_h__ -#define INCLUDE_oidmap_h__ - -#include "common.h" - -#include "git2/oid.h" - -/** A map with `git_oid`s as key. */ -typedef struct kh_oid_s git_oidmap; - -/** - * 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); - -/** - * 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); - -/** - * 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); - -#define git_oidmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_oidmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/pack-objects.c b/src/pack-objects.c deleted file mode 100644 index 1aa6731b3..000000000 --- a/src/pack-objects.c +++ /dev/null @@ -1,1821 +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 "pack-objects.h" - -#include "buf.h" -#include "zstream.h" -#include "delta.h" -#include "iterator.h" -#include "netops.h" -#include "pack.h" -#include "thread.h" -#include "tree.h" -#include "util.h" -#include "revwalk.h" -#include "commit_list.h" - -#include "git2/pack.h" -#include "git2/commit.h" -#include "git2/tag.h" -#include "git2/indexer.h" -#include "git2/config.h" - -struct unpacked { - git_pobject *object; - void *data; - struct git_delta_index *index; - size_t depth; -}; - -struct tree_walk_context { - git_packbuilder *pb; - git_str buf; -}; - -struct pack_write_context { - git_indexer *indexer; - git_indexer_progress *stats; -}; - -struct walk_object { - git_oid id; - unsigned int uninteresting:1, - seen:1; -}; - -#ifdef GIT_THREADS -# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git_mutex_##op(&(pb)->mtx) -#else -# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git__noop() -#endif - -#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock) -#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock) -#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock) -#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock) - -/* The minimal interval between progress updates (in seconds). */ -#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 - -/* Size of the buffer to feed to zlib */ -#define COMPRESS_BUFLEN (1024 * 1024) - -static unsigned name_hash(const char *name) -{ - unsigned c, hash = 0; - - if (!name) - return 0; - - /* - * This effectively just creates a sortable number from the - * last sixteen non-whitespace characters. Last characters - * count "most", so things that end in ".c" sort together. - */ - while ((c = *name++) != 0) { - if (git__isspace(c)) - continue; - hash = (hash >> 2) + (c << 24); - } - return hash; -} - -static int packbuilder_config(git_packbuilder *pb) -{ - git_config *config; - int ret = 0; - int64_t val; - - if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0) - return ret; - -#define config_get(KEY,DST,DFLT) do { \ - ret = git_config_get_int64(&val, config, KEY); \ - if (!ret) { \ - if (!git__is_sizet(val)) { \ - git_error_set(GIT_ERROR_CONFIG, \ - "configuration value '%s' is too large", KEY); \ - ret = -1; \ - goto out; \ - } \ - (DST) = (size_t)val; \ - } else if (ret == GIT_ENOTFOUND) { \ - (DST) = (DFLT); \ - ret = 0; \ - } else if (ret < 0) goto out; } while (0) - - config_get("pack.deltaCacheSize", pb->max_delta_cache_size, - GIT_PACK_DELTA_CACHE_SIZE); - config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size, - GIT_PACK_DELTA_CACHE_LIMIT); - config_get("pack.deltaCacheSize", pb->big_file_threshold, - GIT_PACK_BIG_FILE_THRESHOLD); - config_get("pack.windowMemory", pb->window_memory_limit, 0); - -#undef config_get - -out: - git_config_free(config); - - return ret; -} - -int git_packbuilder_new(git_packbuilder **out, git_repository *repo) -{ - git_packbuilder *pb; - - *out = NULL; - - pb = git__calloc(1, sizeof(*pb)); - GIT_ERROR_CHECK_ALLOC(pb); - - if (git_oidmap_new(&pb->object_ix) < 0 || - git_oidmap_new(&pb->walk_objects) < 0 || - git_pool_init(&pb->object_pool, sizeof(struct walk_object)) < 0) - goto on_error; - - pb->repo = repo; - pb->nr_threads = 1; /* do not spawn any thread by default */ - - if (git_hash_ctx_init(&pb->ctx, GIT_HASH_ALGORITHM_SHA1) < 0 || - git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 || - git_repository_odb(&pb->odb, repo) < 0 || - packbuilder_config(pb) < 0) - goto on_error; - -#ifdef GIT_THREADS - - if (git_mutex_init(&pb->cache_mutex) || - git_mutex_init(&pb->progress_mutex) || - git_cond_init(&pb->progress_cond)) - { - git_error_set(GIT_ERROR_OS, "failed to initialize packbuilder mutex"); - goto on_error; - } - -#endif - - *out = pb; - return 0; - -on_error: - git_packbuilder_free(pb); - return -1; -} - -unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n) -{ - GIT_ASSERT_ARG(pb); - -#ifdef GIT_THREADS - pb->nr_threads = n; -#else - GIT_UNUSED(n); - GIT_ASSERT(pb->nr_threads == 1); -#endif - - return pb->nr_threads; -} - -static int rehash(git_packbuilder *pb) -{ - git_pobject *po; - size_t i; - - git_oidmap_clear(pb->object_ix); - - for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, 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; - int ret; - - GIT_ASSERT_ARG(pb); - GIT_ASSERT_ARG(oid); - - /* If the object already exists in the hash table, then we don't - * have any work to do */ - if (git_oidmap_exists(pb->object_ix, oid)) - return 0; - - if (pb->nr_objects >= pb->nr_alloc) { - GIT_ERROR_CHECK_ALLOC_ADD(&newsize, pb->nr_alloc, 1024); - 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."); - return -1; - } - - pb->nr_alloc = newsize; - - pb->object_list = git__reallocarray(pb->object_list, - pb->nr_alloc, sizeof(*po)); - GIT_ERROR_CHECK_ALLOC(pb->object_list); - - if (rehash(pb) < 0) - return -1; - } - - po = pb->object_list + pb->nr_objects; - memset(po, 0x0, sizeof(*po)); - - if ((ret = git_odb_read_header(&po->size, &po->type, pb->odb, oid)) < 0) - return ret; - - pb->nr_objects++; - git_oid_cpy(&po->id, oid); - po->hash = name_hash(name); - - if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) { - git_error_set_oom(); - return -1; - } - - pb->done = false; - - if (pb->progress_cb) { - double current_time = git__timer(); - double elapsed = current_time - pb->last_progress_report_time; - - if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { - pb->last_progress_report_time = current_time; - - ret = pb->progress_cb( - GIT_PACKBUILDER_ADDING_OBJECTS, - pb->nr_objects, 0, pb->progress_cb_payload); - - if (ret) - return git_error_set_after_callback(ret); - } - } - - return 0; -} - -static int get_delta(void **out, git_odb *odb, git_pobject *po) -{ - git_odb_object *src = NULL, *trg = NULL; - size_t delta_size; - void *delta_buf; - int error; - - *out = NULL; - - if (git_odb_read(&src, odb, &po->delta->id) < 0 || - git_odb_read(&trg, odb, &po->id) < 0) - goto on_error; - - error = git_delta(&delta_buf, &delta_size, - git_odb_object_data(src), git_odb_object_size(src), - git_odb_object_data(trg), git_odb_object_size(trg), - 0); - - if (error < 0 && error != GIT_EBUFS) - goto on_error; - - if (error == GIT_EBUFS || delta_size != po->delta_size) { - git_error_set(GIT_ERROR_INVALID, "delta size changed"); - goto on_error; - } - - *out = delta_buf; - - git_odb_object_free(src); - git_odb_object_free(trg); - return 0; - -on_error: - git_odb_object_free(src); - git_odb_object_free(trg); - return -1; -} - -static int write_object( - git_packbuilder *pb, - git_pobject *po, - int (*write_cb)(void *buf, size_t size, void *cb_data), - void *cb_data) -{ - git_odb_object *obj = NULL; - git_object_t type; - unsigned char hdr[10], *zbuf = NULL; - void *data = NULL; - size_t hdr_len, zbuf_len = COMPRESS_BUFLEN, data_len; - int error; - - /* - * If we have a delta base, let's use the delta to save space. - * Otherwise load the whole object. 'data' ends up pointing to - * whatever data we want to put into the packfile. - */ - if (po->delta) { - if (po->delta_data) - data = po->delta_data; - else if ((error = get_delta(&data, pb->odb, po)) < 0) - goto done; - - data_len = po->delta_size; - type = GIT_OBJECT_REF_DELTA; - } else { - if ((error = git_odb_read(&obj, pb->odb, &po->id)) < 0) - goto done; - - data = (void *)git_odb_object_data(obj); - data_len = git_odb_object_size(obj); - type = git_odb_object_type(obj); - } - - /* Write header */ - if ((error = git_packfile__object_header(&hdr_len, hdr, data_len, type)) < 0 || - (error = write_cb(hdr, hdr_len, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, hdr, hdr_len)) < 0) - goto done; - - if (type == GIT_OBJECT_REF_DELTA) { - if ((error = write_cb(po->delta->id.id, GIT_OID_RAWSZ, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ)) < 0) - goto done; - } - - /* Write data */ - if (po->z_delta_size) { - data_len = po->z_delta_size; - - if ((error = write_cb(data, data_len, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, data, data_len)) < 0) - goto done; - } else { - zbuf = git__malloc(zbuf_len); - GIT_ERROR_CHECK_ALLOC(zbuf); - - git_zstream_reset(&pb->zstream); - - 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 || - (error = write_cb(zbuf, zbuf_len, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, zbuf, zbuf_len)) < 0) - goto done; - - zbuf_len = COMPRESS_BUFLEN; /* reuse buffer */ - } - } - - /* - * If po->delta is true, data is a delta and it is our - * responsibility to free it (otherwise it's a git_object's - * data). We set po->delta_data to NULL in case we got the - * data from there instead of get_delta(). If we didn't, - * there's no harm. - */ - if (po->delta) { - git__free(data); - po->delta_data = NULL; - } - - pb->nr_written++; - -done: - git__free(zbuf); - git_odb_object_free(obj); - return error; -} - -enum write_one_status { - WRITE_ONE_SKIP = -1, /* already written */ - WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */ - WRITE_ONE_WRITTEN = 1, /* normal */ - WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ -}; - -static int write_one( - enum write_one_status *status, - git_packbuilder *pb, - git_pobject *po, - int (*write_cb)(void *buf, size_t size, void *cb_data), - void *cb_data) -{ - int error; - - if (po->recursing) { - *status = WRITE_ONE_RECURSIVE; - return 0; - } else if (po->written) { - *status = WRITE_ONE_SKIP; - return 0; - } - - if (po->delta) { - po->recursing = 1; - - if ((error = write_one(status, pb, po->delta, write_cb, cb_data)) < 0) - return error; - - /* we cannot depend on this one */ - if (*status == WRITE_ONE_RECURSIVE) - po->delta = NULL; - } - - *status = WRITE_ONE_WRITTEN; - po->written = 1; - po->recursing = 0; - - return write_object(pb, po, write_cb, cb_data); -} - -GIT_INLINE(void) add_to_write_order(git_pobject **wo, size_t *endp, - git_pobject *po) -{ - if (po->filled) - return; - wo[(*endp)++] = po; - po->filled = 1; -} - -static void add_descendants_to_write_order(git_pobject **wo, size_t *endp, - git_pobject *po) -{ - int add_to_order = 1; - while (po) { - if (add_to_order) { - git_pobject *s; - /* add this node... */ - add_to_write_order(wo, endp, po); - /* all its siblings... */ - for (s = po->delta_sibling; s; s = s->delta_sibling) { - add_to_write_order(wo, endp, s); - } - } - /* drop down a level to add left subtree nodes if possible */ - if (po->delta_child) { - add_to_order = 1; - po = po->delta_child; - } else { - add_to_order = 0; - /* our sibling might have some children, it is next */ - if (po->delta_sibling) { - po = po->delta_sibling; - continue; - } - /* go back to our parent node */ - po = po->delta; - while (po && !po->delta_sibling) { - /* we're on the right side of a subtree, keep - * going up until we can go right again */ - po = po->delta; - } - if (!po) { - /* done- we hit our original root node */ - return; - } - /* pass it off to sibling at this level */ - po = po->delta_sibling; - } - }; -} - -static void add_family_to_write_order(git_pobject **wo, size_t *endp, - git_pobject *po) -{ - git_pobject *root; - - for (root = po; root->delta; root = root->delta) - ; /* nothing */ - add_descendants_to_write_order(wo, endp, root); -} - -static int cb_tag_foreach(const char *name, git_oid *oid, void *data) -{ - git_packbuilder *pb = data; - git_pobject *po; - - GIT_UNUSED(name); - - if ((po = git_oidmap_get(pb->object_ix, oid)) == NULL) - return 0; - - po->tagged = 1; - - /* TODO: peel objects */ - - return 0; -} - -static int compute_write_order(git_pobject ***out, git_packbuilder *pb) -{ - size_t i, wo_end, last_untagged; - git_pobject **wo; - - *out = NULL; - - if (!pb->nr_objects) - return 0; - - if ((wo = git__mallocarray(pb->nr_objects, sizeof(*wo))) == NULL) - return -1; - - for (i = 0; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - po->tagged = 0; - po->filled = 0; - po->delta_child = NULL; - po->delta_sibling = NULL; - } - - /* - * Fully connect delta_child/delta_sibling network. - * Make sure delta_sibling is sorted in the original - * recency order. - */ - for (i = pb->nr_objects; i > 0;) { - git_pobject *po = &pb->object_list[--i]; - if (!po->delta) - continue; - /* Mark me as the first child */ - po->delta_sibling = po->delta->delta_child; - po->delta->delta_child = po; - } - - /* - * Mark objects that are at the tip of tags. - */ - if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) { - git__free(wo); - return -1; - } - - /* - * Give the objects in the original recency order until - * we see a tagged tip. - */ - for (i = wo_end = 0; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->tagged) - break; - add_to_write_order(wo, &wo_end, po); - } - last_untagged = i; - - /* - * Then fill all the tagged tips. - */ - for (; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->tagged) - add_to_write_order(wo, &wo_end, po); - } - - /* - * And then all remaining commits and tags. - */ - for (i = last_untagged; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->type != GIT_OBJECT_COMMIT && - po->type != GIT_OBJECT_TAG) - continue; - add_to_write_order(wo, &wo_end, po); - } - - /* - * And then all the trees. - */ - for (i = last_untagged; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->type != GIT_OBJECT_TREE) - continue; - add_to_write_order(wo, &wo_end, po); - } - - /* - * Finally all the rest in really tight order - */ - for (i = last_untagged; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (!po->filled) - add_family_to_write_order(wo, &wo_end, po); - } - - if (wo_end != pb->nr_objects) { - git__free(wo); - git_error_set(GIT_ERROR_INVALID, "invalid write order"); - return -1; - } - - *out = wo; - return 0; -} - -static int write_pack(git_packbuilder *pb, - int (*write_cb)(void *buf, size_t size, void *cb_data), - void *cb_data) -{ - git_pobject **write_order; - git_pobject *po; - enum write_one_status status; - struct git_pack_header ph; - git_oid entry_oid; - size_t i = 0; - int error; - - if ((error = compute_write_order(&write_order, pb)) < 0) - return error; - - if (!git__is_uint32(pb->nr_objects)) { - git_error_set(GIT_ERROR_INVALID, "too many objects"); - error = -1; - goto done; - } - - /* Write pack header */ - ph.hdr_signature = htonl(PACK_SIGNATURE); - ph.hdr_version = htonl(PACK_VERSION); - ph.hdr_entries = htonl(pb->nr_objects); - - if ((error = write_cb(&ph, sizeof(ph), cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0) - goto done; - - pb->nr_remaining = pb->nr_objects; - do { - pb->nr_written = 0; - for ( ; i < pb->nr_objects; ++i) { - po = write_order[i]; - - if ((error = write_one(&status, pb, po, write_cb, cb_data)) < 0) - goto done; - } - - pb->nr_remaining -= pb->nr_written; - } while (pb->nr_remaining && i < pb->nr_objects); - - if ((error = git_hash_final(entry_oid.id, &pb->ctx)) < 0) - goto done; - - error = write_cb(entry_oid.id, GIT_OID_RAWSZ, cb_data); - -done: - /* if callback cancelled writing, we must still free delta_data */ - for ( ; i < pb->nr_objects; ++i) { - po = write_order[i]; - if (po->delta_data) { - git__free(po->delta_data); - po->delta_data = NULL; - } - } - - git__free(write_order); - return error; -} - -static int write_pack_buf(void *buf, size_t size, void *data) -{ - git_str *b = (git_str *)data; - return git_str_put(b, buf, size); -} - -static int type_size_sort(const void *_a, const void *_b) -{ - const git_pobject *a = (git_pobject *)_a; - const git_pobject *b = (git_pobject *)_b; - - if (a->type > b->type) - return -1; - if (a->type < b->type) - return 1; - if (a->hash > b->hash) - return -1; - if (a->hash < b->hash) - return 1; - /* - * TODO - * - if (a->preferred_base > b->preferred_base) - return -1; - if (a->preferred_base < b->preferred_base) - return 1; - */ - if (a->size > b->size) - return -1; - if (a->size < b->size) - return 1; - return a < b ? -1 : (a > b); /* newest first */ -} - -static int delta_cacheable( - git_packbuilder *pb, - size_t src_size, - size_t trg_size, - size_t delta_size) -{ - size_t new_size; - - if (git__add_sizet_overflow(&new_size, pb->delta_cache_size, delta_size)) - return 0; - - if (pb->max_delta_cache_size && new_size > pb->max_delta_cache_size) - return 0; - - if (delta_size < pb->cache_max_small_delta_size) - return 1; - - /* cache delta, if objects are large enough compared to delta size */ - if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) - return 1; - - return 0; -} - -static int try_delta(git_packbuilder *pb, struct unpacked *trg, - struct unpacked *src, size_t max_depth, - size_t *mem_usage, int *ret) -{ - git_pobject *trg_object = trg->object; - git_pobject *src_object = src->object; - git_odb_object *obj; - size_t trg_size, src_size, delta_size, sizediff, max_size, sz; - size_t ref_depth; - void *delta_buf; - - /* Don't bother doing diffs between different types */ - if (trg_object->type != src_object->type) { - *ret = -1; - return 0; - } - - *ret = 0; - - /* TODO: support reuse-delta */ - - /* Let's not bust the allowed depth. */ - if (src->depth >= max_depth) - return 0; - - /* Now some size filtering heuristics. */ - trg_size = trg_object->size; - if (!trg_object->delta) { - max_size = trg_size/2 - 20; - ref_depth = 1; - } else { - max_size = trg_object->delta_size; - ref_depth = trg->depth; - } - - max_size = (uint64_t)max_size * (max_depth - src->depth) / - (max_depth - ref_depth + 1); - if (max_size == 0) - return 0; - - src_size = src_object->size; - sizediff = src_size < trg_size ? trg_size - src_size : 0; - if (sizediff >= max_size) - return 0; - if (trg_size < src_size / 32) - return 0; - - /* Load data if not already done */ - if (!trg->data) { - if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0) - return -1; - - sz = git_odb_object_size(obj); - trg->data = git__malloc(sz); - GIT_ERROR_CHECK_ALLOC(trg->data); - memcpy(trg->data, git_odb_object_data(obj), sz); - - git_odb_object_free(obj); - - if (sz != trg_size) { - git_error_set(GIT_ERROR_INVALID, - "inconsistent target object length"); - return -1; - } - - *mem_usage += sz; - } - if (!src->data) { - size_t obj_sz; - - if (git_odb_read(&obj, pb->odb, &src_object->id) < 0 || - !git__is_ulong(obj_sz = git_odb_object_size(obj))) - return -1; - - sz = obj_sz; - src->data = git__malloc(sz); - GIT_ERROR_CHECK_ALLOC(src->data); - memcpy(src->data, git_odb_object_data(obj), sz); - - git_odb_object_free(obj); - - if (sz != src_size) { - git_error_set(GIT_ERROR_INVALID, - "inconsistent source object length"); - return -1; - } - - *mem_usage += sz; - } - if (!src->index) { - if (git_delta_index_init(&src->index, src->data, src_size) < 0) - return 0; /* suboptimal pack - out of memory */ - - *mem_usage += git_delta_index_size(src->index); - } - - if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size, - max_size) < 0) - return 0; - - if (trg_object->delta) { - /* Prefer only shallower same-sized deltas. */ - if (delta_size == trg_object->delta_size && - src->depth + 1 >= trg->depth) { - git__free(delta_buf); - return 0; - } - } - - GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); - - if (trg_object->delta_data) { - git__free(trg_object->delta_data); - GIT_ASSERT(pb->delta_cache_size >= trg_object->delta_size); - pb->delta_cache_size -= trg_object->delta_size; - trg_object->delta_data = NULL; - } - if (delta_cacheable(pb, src_size, trg_size, delta_size)) { - bool overflow = git__add_sizet_overflow( - &pb->delta_cache_size, pb->delta_cache_size, delta_size); - - GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); - - if (overflow) { - git__free(delta_buf); - return -1; - } - - trg_object->delta_data = git__realloc(delta_buf, delta_size); - GIT_ERROR_CHECK_ALLOC(trg_object->delta_data); - } else { - /* create delta when writing the pack */ - GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); - git__free(delta_buf); - } - - trg_object->delta = src_object; - trg_object->delta_size = delta_size; - trg->depth = src->depth + 1; - - *ret = 1; - return 0; -} - -static size_t check_delta_limit(git_pobject *me, size_t n) -{ - git_pobject *child = me->delta_child; - size_t m = n; - - while (child) { - size_t c = check_delta_limit(child, n + 1); - if (m < c) - m = c; - child = child->delta_sibling; - } - return m; -} - -static size_t free_unpacked(struct unpacked *n) -{ - size_t freed_mem = 0; - - if (n->index) { - freed_mem += git_delta_index_size(n->index); - git_delta_index_free(n->index); - } - n->index = NULL; - - if (n->data) { - freed_mem += n->object->size; - git__free(n->data); - n->data = NULL; - } - n->object = NULL; - n->depth = 0; - return freed_mem; -} - -static int report_delta_progress( - git_packbuilder *pb, uint32_t count, bool force) -{ - int ret; - - if (pb->progress_cb) { - double current_time = git__timer(); - double elapsed = current_time - pb->last_progress_report_time; - - if (force || elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { - pb->last_progress_report_time = current_time; - - ret = pb->progress_cb( - GIT_PACKBUILDER_DELTAFICATION, - count, pb->nr_objects, pb->progress_cb_payload); - - if (ret) - return git_error_set_after_callback(ret); - } - } - - return 0; -} - -static int find_deltas(git_packbuilder *pb, git_pobject **list, - size_t *list_size, size_t window, size_t depth) -{ - git_pobject *po; - git_str zbuf = GIT_STR_INIT; - struct unpacked *array; - size_t idx = 0, count = 0; - size_t mem_usage = 0; - size_t i; - int error = -1; - - array = git__calloc(window, sizeof(struct unpacked)); - GIT_ERROR_CHECK_ALLOC(array); - - for (;;) { - struct unpacked *n = array + idx; - size_t max_depth, j, best_base = SIZE_MAX; - - GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); - if (!*list_size) { - GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); - break; - } - - pb->nr_deltified += 1; - report_delta_progress(pb, pb->nr_deltified, false); - - po = *list++; - (*list_size)--; - GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); - - mem_usage -= free_unpacked(n); - n->object = po; - - while (pb->window_memory_limit && - mem_usage > pb->window_memory_limit && - count > 1) { - size_t tail = (idx + window - count) % window; - mem_usage -= free_unpacked(array + tail); - count--; - } - - /* - * If the current object is at pack edge, take the depth the - * objects that depend on the current object into account - * otherwise they would become too deep. - */ - max_depth = depth; - if (po->delta_child) { - size_t delta_limit = check_delta_limit(po, 0); - - if (delta_limit > max_depth) - goto next; - - max_depth -= delta_limit; - } - - j = window; - while (--j > 0) { - int ret; - size_t other_idx = idx + j; - struct unpacked *m; - - if (other_idx >= window) - other_idx -= window; - - m = array + other_idx; - if (!m->object) - break; - - if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0) - goto on_error; - if (ret < 0) - break; - else if (ret > 0) - best_base = other_idx; - } - - /* - * If we decided to cache the delta data, then it is best - * to compress it right away. First because we have to do - * it anyway, and doing it here while we're threaded will - * save a lot of time in the non threaded write phase, - * as well as allow for caching more deltas within - * the same cache size limit. - * ... - * But only if not writing to stdout, since in that case - * the network is most likely throttling writes anyway, - * and therefore it is best to go to the write phase ASAP - * instead, as we can afford spending more time compressing - * between writes at that moment. - */ - if (po->delta_data) { - if (git_zstream_deflatebuf(&zbuf, po->delta_data, po->delta_size) < 0) - goto on_error; - - git__free(po->delta_data); - po->delta_data = git__malloc(zbuf.size); - GIT_ERROR_CHECK_ALLOC(po->delta_data); - - memcpy(po->delta_data, zbuf.ptr, zbuf.size); - po->z_delta_size = zbuf.size; - git_str_clear(&zbuf); - - GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); - pb->delta_cache_size -= po->delta_size; - pb->delta_cache_size += po->z_delta_size; - GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); - } - - /* - * If we made n a delta, and if n is already at max - * depth, leaving it in the window is pointless. we - * should evict it first. - */ - if (po->delta && max_depth <= n->depth) - continue; - - /* - * Move the best delta base up in the window, after the - * currently deltified object, to keep it longer. It will - * be the first base object to be attempted next. - */ - if (po->delta) { - struct unpacked swap = array[best_base]; - size_t dist = (window + idx - best_base) % window; - size_t dst = best_base; - while (dist--) { - size_t src = (dst + 1) % window; - array[dst] = array[src]; - dst = src; - } - array[dst] = swap; - } - - next: - idx++; - if (count + 1 < window) - count++; - if (idx >= window) - idx = 0; - } - error = 0; - -on_error: - for (i = 0; i < window; ++i) { - git__free(array[i].index); - git__free(array[i].data); - } - git__free(array); - git_str_dispose(&zbuf); - - return error; -} - -#ifdef GIT_THREADS - -struct thread_params { - git_thread thread; - git_packbuilder *pb; - - git_pobject **list; - - git_cond cond; - git_mutex mutex; - - size_t list_size; - size_t remaining; - - size_t window; - size_t depth; - size_t working; - size_t data_ready; -}; - -static void *threaded_find_deltas(void *arg) -{ - struct thread_params *me = arg; - - while (me->remaining) { - if (find_deltas(me->pb, me->list, &me->remaining, - me->window, me->depth) < 0) { - ; /* TODO */ - } - - GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); - me->working = 0; - git_cond_signal(&me->pb->progress_cond); - GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_unlock(me->pb) == 0, NULL); - - if (git_mutex_lock(&me->mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); - return NULL; - } - - while (!me->data_ready) - git_cond_wait(&me->cond, &me->mutex); - - /* - * We must not set ->data_ready before we wait on the - * condition because the main thread may have set it to 1 - * before we get here. In order to be sure that new - * work is available if we see 1 in ->data_ready, it - * was initialized to 0 before this thread was spawned - * and we reset it to 0 right away. - */ - me->data_ready = 0; - git_mutex_unlock(&me->mutex); - } - /* leave ->working 1 so that this doesn't get more work assigned */ - return NULL; -} - -static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, - size_t list_size, size_t window, size_t depth) -{ - struct thread_params *p; - size_t i; - int ret, active_threads = 0; - - if (!pb->nr_threads) - pb->nr_threads = git__online_cpus(); - - if (pb->nr_threads <= 1) { - find_deltas(pb, list, &list_size, window, depth); - return 0; - } - - p = git__mallocarray(pb->nr_threads, sizeof(*p)); - GIT_ERROR_CHECK_ALLOC(p); - - /* Partition the work among the threads */ - for (i = 0; i < pb->nr_threads; ++i) { - size_t sub_size = list_size / (pb->nr_threads - i); - - /* don't use too small segments or no deltas will be found */ - if (sub_size < 2*window && i+1 < pb->nr_threads) - sub_size = 0; - - p[i].pb = pb; - p[i].window = window; - p[i].depth = depth; - p[i].working = 1; - p[i].data_ready = 0; - - /* try to split chunks on "path" boundaries */ - while (sub_size && sub_size < list_size && - list[sub_size]->hash && - list[sub_size]->hash == list[sub_size-1]->hash) - sub_size++; - - p[i].list = list; - p[i].list_size = sub_size; - p[i].remaining = sub_size; - - list += sub_size; - list_size -= sub_size; - } - - /* Start work threads */ - for (i = 0; i < pb->nr_threads; ++i) { - if (!p[i].list_size) - continue; - - git_mutex_init(&p[i].mutex); - git_cond_init(&p[i].cond); - - ret = git_thread_create(&p[i].thread, - threaded_find_deltas, &p[i]); - if (ret) { - git_error_set(GIT_ERROR_THREAD, "unable to create thread"); - return -1; - } - active_threads++; - } - - /* - * Now let's wait for work completion. Each time a thread is done - * with its work, we steal half of the remaining work from the - * thread with the largest number of unprocessed objects and give - * it to that newly idle thread. This ensure good load balancing - * until the remaining object list segments are simply too short - * to be worth splitting anymore. - */ - while (active_threads) { - struct thread_params *target = NULL; - struct thread_params *victim = NULL; - size_t sub_size = 0; - - /* Start by locating a thread that has transitioned its - * 'working' flag from 1 -> 0. This indicates that it is - * ready to receive more work using our work-stealing - * algorithm. */ - GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); - for (;;) { - for (i = 0; !target && i < pb->nr_threads; i++) - if (!p[i].working) - target = &p[i]; - if (target) - break; - git_cond_wait(&pb->progress_cond, &pb->progress_mutex); - } - - /* At this point we hold the progress lock and have located - * a thread to receive more work. We still need to locate a - * thread from which to steal work (the victim). */ - for (i = 0; i < pb->nr_threads; i++) - if (p[i].remaining > 2*window && - (!victim || victim->remaining < p[i].remaining)) - victim = &p[i]; - - if (victim) { - sub_size = victim->remaining / 2; - list = victim->list + victim->list_size - sub_size; - while (sub_size && list[0]->hash && - list[0]->hash == list[-1]->hash) { - list++; - sub_size--; - } - if (!sub_size) { - /* - * It is possible for some "paths" to have - * so many objects that no hash boundary - * might be found. Let's just steal the - * exact half in that case. - */ - sub_size = victim->remaining / 2; - list -= sub_size; - } - target->list = list; - victim->list_size -= sub_size; - victim->remaining -= sub_size; - } - target->list_size = sub_size; - target->remaining = sub_size; - target->working = 1; - GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); - - if (git_mutex_lock(&target->mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); - git__free(p); - return -1; - } - - target->data_ready = 1; - git_cond_signal(&target->cond); - git_mutex_unlock(&target->mutex); - - if (!sub_size) { - git_thread_join(&target->thread, NULL); - git_cond_free(&target->cond); - git_mutex_free(&target->mutex); - active_threads--; - } - } - - git__free(p); - return 0; -} - -#else -#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d) -#endif - -int git_packbuilder__prepare(git_packbuilder *pb) -{ - git_pobject **delta_list; - size_t i, n = 0; - - if (pb->nr_objects == 0 || pb->done) - return 0; /* nothing to do */ - - /* - * Although we do not report progress during deltafication, we - * at least report that we are in the deltafication stage - */ - if (pb->progress_cb) - pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); - - delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list)); - GIT_ERROR_CHECK_ALLOC(delta_list); - - for (i = 0; i < pb->nr_objects; ++i) { - git_pobject *po = pb->object_list + i; - - /* Make sure the item is within our size limits */ - if (po->size < 50 || po->size > pb->big_file_threshold) - continue; - - delta_list[n++] = po; - } - - if (n > 1) { - git__tsort((void **)delta_list, n, type_size_sort); - if (ll_find_deltas(pb, delta_list, n, - GIT_PACK_WINDOW + 1, - GIT_PACK_DEPTH) < 0) { - git__free(delta_list); - return -1; - } - } - - report_delta_progress(pb, pb->nr_objects, true); - - pb->done = true; - git__free(delta_list); - return 0; -} - -#define PREPARE_PACK if (git_packbuilder__prepare(pb) < 0) { return -1; } - -int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) -{ - PREPARE_PACK; - return write_pack(pb, cb, payload); -} - -int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb) -{ - PREPARE_PACK; - - return write_pack(pb, &write_pack_buf, buf); -} - -int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) -{ - GIT_BUF_WRAP_PRIVATE(buf, git_packbuilder__write_buf, pb); -} - -static int write_cb(void *buf, size_t len, void *payload) -{ - struct pack_write_context *ctx = payload; - return git_indexer_append(ctx->indexer, buf, len, ctx->stats); -} - -int git_packbuilder_write( - git_packbuilder *pb, - const char *path, - unsigned int mode, - git_indexer_progress_cb progress_cb, - void *progress_cb_payload) -{ - int error = -1; - git_str object_path = GIT_STR_INIT; - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - git_indexer *indexer = NULL; - git_indexer_progress stats; - struct pack_write_context ctx; - int t; - - PREPARE_PACK; - - if (path == NULL) { - if ((error = git_repository__item_path(&object_path, pb->repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0) - goto cleanup; - if ((error = git_str_joinpath(&object_path, git_str_cstr(&object_path), "pack")) < 0) - goto cleanup; - path = git_str_cstr(&object_path); - } - - opts.progress_cb = progress_cb; - opts.progress_cb_payload = progress_cb_payload; - - if ((error = git_indexer_new(&indexer, path, mode, pb->odb, &opts)) < 0) - goto cleanup; - - if (!git_repository__configmap_lookup(&t, pb->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) - git_indexer__set_fsync(indexer, 1); - - ctx.indexer = indexer; - ctx.stats = &stats; - - if ((error = git_packbuilder_foreach(pb, write_cb, &ctx)) < 0) - goto cleanup; - - if ((error = git_indexer_commit(indexer, &stats)) < 0) - goto cleanup; - -#ifndef GIT_DEPRECATE_HARD - git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer)); -#endif - - pb->pack_name = git__strdup(git_indexer_name(indexer)); - GIT_ERROR_CHECK_ALLOC(pb->pack_name); - -cleanup: - git_indexer_free(indexer); - git_str_dispose(&object_path); - return error; -} - -#undef PREPARE_PACK - -#ifndef GIT_DEPRECATE_HARD -const git_oid *git_packbuilder_hash(git_packbuilder *pb) -{ - return &pb->pack_oid; -} -#endif - -const char *git_packbuilder_name(git_packbuilder *pb) -{ - return pb->pack_name; -} - - -static int cb_tree_walk( - const char *root, const git_tree_entry *entry, void *payload) -{ - int error; - struct tree_walk_context *ctx = payload; - - /* A commit inside a tree represents a submodule commit and should be skipped. */ - if (git_tree_entry_type(entry) == GIT_OBJECT_COMMIT) - return 0; - - if (!(error = git_str_sets(&ctx->buf, root)) && - !(error = git_str_puts(&ctx->buf, git_tree_entry_name(entry)))) - error = git_packbuilder_insert( - ctx->pb, git_tree_entry_id(entry), git_str_cstr(&ctx->buf)); - - return error; -} - -int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid) -{ - git_commit *commit; - - if (git_commit_lookup(&commit, pb->repo, oid) < 0 || - git_packbuilder_insert(pb, oid, NULL) < 0) - return -1; - - if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0) - return -1; - - git_commit_free(commit); - return 0; -} - -int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid) -{ - int error; - git_tree *tree = NULL; - struct tree_walk_context context = { pb, GIT_STR_INIT }; - - if (!(error = git_tree_lookup(&tree, pb->repo, oid)) && - !(error = git_packbuilder_insert(pb, oid, NULL))) - error = git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context); - - git_tree_free(tree); - git_str_dispose(&context.buf); - return error; -} - -int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name) -{ - git_object *obj; - int error; - - GIT_ASSERT_ARG(pb); - GIT_ASSERT_ARG(id); - - if ((error = git_object_lookup(&obj, pb->repo, id, GIT_OBJECT_ANY)) < 0) - return error; - - switch (git_object_type(obj)) { - case GIT_OBJECT_BLOB: - error = git_packbuilder_insert(pb, id, name); - break; - case GIT_OBJECT_TREE: - error = git_packbuilder_insert_tree(pb, id); - break; - case GIT_OBJECT_COMMIT: - error = git_packbuilder_insert_commit(pb, id); - break; - case GIT_OBJECT_TAG: - if ((error = git_packbuilder_insert(pb, id, name)) < 0) - goto cleanup; - error = git_packbuilder_insert_recur(pb, git_tag_target_id((git_tag *) obj), NULL); - break; - - default: - git_error_set(GIT_ERROR_INVALID, "unknown object type"); - error = -1; - } - -cleanup: - git_object_free(obj); - return error; -} - -size_t git_packbuilder_object_count(git_packbuilder *pb) -{ - return pb->nr_objects; -} - -size_t git_packbuilder_written(git_packbuilder *pb) -{ - return pb->nr_written; -} - -static int lookup_walk_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) -{ - struct walk_object *obj; - - obj = git_pool_mallocz(&pb->object_pool, 1); - if (!obj) { - git_error_set_oom(); - return -1; - } - - git_oid_cpy(&obj->id, id); - - *out = obj; - return 0; -} - -static int retrieve_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) -{ - struct walk_object *obj; - int error; - - if ((obj = git_oidmap_get(pb->walk_objects, id)) == NULL) { - if ((error = lookup_walk_object(&obj, pb, id)) < 0) - return error; - - if ((error = git_oidmap_set(pb->walk_objects, &obj->id, obj)) < 0) - return error; - } - - *out = obj; - return 0; -} - -static int mark_blob_uninteresting(git_packbuilder *pb, const git_oid *id) -{ - int error; - struct walk_object *obj; - - if ((error = retrieve_object(&obj, pb, id)) < 0) - return error; - - obj->uninteresting = 1; - - return 0; -} - -static int mark_tree_uninteresting(git_packbuilder *pb, const git_oid *id) -{ - struct walk_object *obj; - git_tree *tree; - int error; - size_t i; - - if ((error = retrieve_object(&obj, pb, id)) < 0) - return error; - - if (obj->uninteresting) - return 0; - - obj->uninteresting = 1; - - if ((error = git_tree_lookup(&tree, pb->repo, id)) < 0) - return error; - - for (i = 0; i < git_tree_entrycount(tree); i++) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - const git_oid *entry_id = git_tree_entry_id(entry); - switch (git_tree_entry_type(entry)) { - case GIT_OBJECT_TREE: - if ((error = mark_tree_uninteresting(pb, entry_id)) < 0) - goto cleanup; - break; - case GIT_OBJECT_BLOB: - if ((error = mark_blob_uninteresting(pb, entry_id)) < 0) - goto cleanup; - break; - default: - /* it's a submodule or something unknown, we don't want it */ - ; - } - } - -cleanup: - git_tree_free(tree); - return error; -} - -/* - * Mark the edges of the graph uninteresting. Since we start from a - * git_revwalk, the commits are already uninteresting, but we need to - * mark the trees and blobs. - */ -static int mark_edges_uninteresting(git_packbuilder *pb, git_commit_list *commits) -{ - int error; - git_commit_list *list; - git_commit *commit; - - for (list = commits; list; list = list->next) { - if (!list->item->uninteresting) - continue; - - if ((error = git_commit_lookup(&commit, pb->repo, &list->item->oid)) < 0) - return error; - - error = mark_tree_uninteresting(pb, git_commit_tree_id(commit)); - git_commit_free(commit); - - if (error < 0) - return error; - } - - return 0; -} - -static int pack_objects_insert_tree(git_packbuilder *pb, git_tree *tree) -{ - size_t i; - int error; - git_tree *subtree; - struct walk_object *obj; - const char *name; - - if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) - return error; - - if (obj->seen || obj->uninteresting) - return 0; - - obj->seen = 1; - - if ((error = git_packbuilder_insert(pb, &obj->id, NULL))) - return error; - - for (i = 0; i < git_tree_entrycount(tree); i++) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - const git_oid *entry_id = git_tree_entry_id(entry); - switch (git_tree_entry_type(entry)) { - case GIT_OBJECT_TREE: - if ((error = git_tree_lookup(&subtree, pb->repo, entry_id)) < 0) - return error; - - error = pack_objects_insert_tree(pb, subtree); - git_tree_free(subtree); - - if (error < 0) - return error; - - break; - case GIT_OBJECT_BLOB: - if ((error = retrieve_object(&obj, pb, entry_id)) < 0) - return error; - if (obj->uninteresting) - continue; - name = git_tree_entry_name(entry); - if ((error = git_packbuilder_insert(pb, entry_id, name)) < 0) - return error; - break; - default: - /* it's a submodule or something unknown, we don't want it */ - ; - } - } - - - return error; -} - -static int pack_objects_insert_commit(git_packbuilder *pb, struct walk_object *obj) -{ - int error; - git_commit *commit = NULL; - git_tree *tree = NULL; - - obj->seen = 1; - - if ((error = git_packbuilder_insert(pb, &obj->id, NULL)) < 0) - return error; - - if ((error = git_commit_lookup(&commit, pb->repo, &obj->id)) < 0) - return error; - - if ((error = git_tree_lookup(&tree, pb->repo, git_commit_tree_id(commit))) < 0) - goto cleanup; - - if ((error = pack_objects_insert_tree(pb, tree)) < 0) - goto cleanup; - -cleanup: - git_commit_free(commit); - git_tree_free(tree); - return error; -} - -int git_packbuilder_insert_walk(git_packbuilder *pb, git_revwalk *walk) -{ - int error; - git_oid id; - struct walk_object *obj; - - GIT_ASSERT_ARG(pb); - GIT_ASSERT_ARG(walk); - - if ((error = mark_edges_uninteresting(pb, walk->user_input)) < 0) - return error; - - /* - * TODO: git marks the parents of the edges - * uninteresting. This may provide a speed advantage, but does - * seem to assume the remote does not have a single-commit - * history on the other end. - */ - - /* walk down each tree up to the blobs and insert them, stopping when uninteresting */ - while ((error = git_revwalk_next(&id, walk)) == 0) { - if ((error = retrieve_object(&obj, pb, &id)) < 0) - return error; - - if (obj->seen || obj->uninteresting) - continue; - - if ((error = pack_objects_insert_commit(pb, obj)) < 0) - return error; - } - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload) -{ - if (!pb) - return -1; - - pb->progress_cb = progress_cb; - pb->progress_cb_payload = progress_cb_payload; - - return 0; -} - -void git_packbuilder_free(git_packbuilder *pb) -{ - if (pb == NULL) - return; - -#ifdef GIT_THREADS - - git_mutex_free(&pb->cache_mutex); - git_mutex_free(&pb->progress_mutex); - git_cond_free(&pb->progress_cond); - -#endif - - if (pb->odb) - git_odb_free(pb->odb); - - if (pb->object_ix) - git_oidmap_free(pb->object_ix); - - if (pb->object_list) - git__free(pb->object_list); - - git_oidmap_free(pb->walk_objects); - git_pool_clear(&pb->object_pool); - - git_hash_ctx_cleanup(&pb->ctx); - git_zstream_free(&pb->zstream); - - git__free(pb->pack_name); - - git__free(pb); -} diff --git a/src/pack-objects.h b/src/pack-objects.h deleted file mode 100644 index 2faa3ec7f..000000000 --- a/src/pack-objects.h +++ /dev/null @@ -1,106 +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_pack_objects_h__ -#define INCLUDE_pack_objects_h__ - -#include "common.h" - -#include "str.h" -#include "hash.h" -#include "oidmap.h" -#include "netops.h" -#include "zstream.h" -#include "pool.h" -#include "indexer.h" - -#include "git2/oid.h" -#include "git2/pack.h" - -#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */ -#define GIT_PACK_DEPTH 50 /* max delta depth */ -#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024) -#define GIT_PACK_DELTA_CACHE_LIMIT 1000 -#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024) - -typedef struct git_pobject { - git_oid id; - git_object_t type; - off64_t offset; - - size_t size; - - unsigned int hash; /* name hint hash */ - - struct git_pobject *delta; /* delta base object */ - struct git_pobject *delta_child; /* deltified objects who bases me */ - struct git_pobject *delta_sibling; /* other deltified objects - * who uses the same base as - * me */ - - void *delta_data; - size_t delta_size; - size_t z_delta_size; - - unsigned int written:1, - recursing:1, - tagged:1, - filled:1; -} git_pobject; - -struct git_packbuilder { - git_repository *repo; /* associated repository */ - git_odb *odb; /* associated object database */ - - git_hash_ctx ctx; - git_zstream zstream; - - uint32_t nr_objects, - nr_deltified, - nr_written, - nr_remaining; - - size_t nr_alloc; - - git_pobject *object_list; - - git_oidmap *object_ix; - - git_oidmap *walk_objects; - git_pool object_pool; - -#ifndef GIT_DEPRECATE_HARD - git_oid pack_oid; /* hash of written pack */ -#endif - char *pack_name; /* name of written pack */ - - /* synchronization objects */ - git_mutex cache_mutex; - git_mutex progress_mutex; - git_cond progress_cond; - - /* configs */ - size_t delta_cache_size; - size_t max_delta_cache_size; - size_t cache_max_small_delta_size; - size_t big_file_threshold; - size_t window_memory_limit; - - unsigned int nr_threads; /* nr of threads to use */ - - git_packbuilder_progress progress_cb; - void *progress_cb_payload; - double last_progress_report_time; /* the time progress was last reported */ - - bool done; -}; - -int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb); -int git_packbuilder__prepare(git_packbuilder *pb); - - -#endif diff --git a/src/pack.c b/src/pack.c deleted file mode 100644 index 5c0cba7e8..000000000 --- a/src/pack.c +++ /dev/null @@ -1,1629 +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 "pack.h" - -#include "delta.h" -#include "futils.h" -#include "mwindow.h" -#include "odb.h" -#include "oid.h" -#include "oidarray.h" - -/* Option to bypass checking existence of '.keep' files */ -bool git_disable_pack_keep_file_checks = false; - -static int packfile_open_locked(struct git_pack_file *p); -static off64_t nth_packed_object_offset_locked(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, - off64_t *curpos, - size_t size, - git_object_t type); - -/* Can find the offset of an object given - * a prefix of an identifier. - * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid - * is ambiguous within the pack. - * This method assumes that len is between - * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. - */ -static int pack_entry_find_offset( - off64_t *offset_out, - git_oid *found_oid, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len); - -static int packfile_error(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "invalid pack file - %s", message); - return -1; -} - -/******************** - * Delta base cache - ********************/ - -static git_pack_cache_entry *new_cache_object(git_rawobj *source) -{ - git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry)); - if (!e) - return NULL; - - git_atomic32_inc(&e->refcount); - memcpy(&e->raw, source, sizeof(git_rawobj)); - - return e; -} - -static void free_cache_object(void *o) -{ - git_pack_cache_entry *e = (git_pack_cache_entry *)o; - - if (e != NULL) { - git__free(e->raw.data); - git__free(e); - } -} - -static void cache_free(git_pack_cache *cache) -{ - git_pack_cache_entry *entry; - - if (cache->entries) { - git_offmap_foreach_value(cache->entries, entry, { - free_cache_object(entry); - }); - - git_offmap_free(cache->entries); - cache->entries = NULL; - } -} - -static int cache_init(git_pack_cache *cache) -{ - if (git_offmap_new(&cache->entries) < 0) - return -1; - - cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT; - - if (git_mutex_init(&cache->lock)) { - git_error_set(GIT_ERROR_OS, "failed to initialize pack cache mutex"); - - git__free(cache->entries); - cache->entries = NULL; - - return -1; - } - - return 0; -} - -static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) -{ - git_pack_cache_entry *entry; - - if (git_mutex_lock(&cache->lock) < 0) - return NULL; - - if ((entry = git_offmap_get(cache->entries, offset)) != NULL) { - git_atomic32_inc(&entry->refcount); - entry->last_usage = cache->use_ctr++; - } - git_mutex_unlock(&cache->lock); - - return entry; -} - -/* Run with the cache lock held */ -static void free_lowest_entry(git_pack_cache *cache) -{ - off64_t offset; - git_pack_cache_entry *entry; - - git_offmap_foreach(cache->entries, offset, entry, { - if (entry && git_atomic32_get(&entry->refcount) == 0) { - cache->memory_used -= entry->raw.len; - git_offmap_delete(cache->entries, offset); - free_cache_object(entry); - } - }); -} - -static int cache_add( - git_pack_cache_entry **cached_out, - git_pack_cache *cache, - git_rawobj *base, - off64_t offset) -{ - git_pack_cache_entry *entry; - int exists; - - if (base->len > GIT_PACK_CACHE_SIZE_LIMIT) - return -1; - - entry = new_cache_object(base); - if (entry) { - if (git_mutex_lock(&cache->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock cache"); - git__free(entry); - return -1; - } - /* Add it to the cache if nobody else has */ - exists = git_offmap_exists(cache->entries, offset); - if (!exists) { - while (cache->memory_used + base->len > cache->memory_limit) - free_lowest_entry(cache); - - git_offmap_set(cache->entries, offset, entry); - cache->memory_used += entry->raw.len; - - *cached_out = entry; - } - git_mutex_unlock(&cache->lock); - /* Somebody beat us to adding it into the cache */ - if (exists) { - git__free(entry); - return -1; - } - } - - return 0; -} - -/*********************************************************** - * - * PACK INDEX METHODS - * - ***********************************************************/ - -static void pack_index_free(struct git_pack_file *p) -{ - if (p->oids) { - git__free(p->oids); - p->oids = NULL; - } - if (p->index_map.data) { - git_futils_mmap_free(&p->index_map); - p->index_map.data = NULL; - } -} - -/* Run with the packfile lock held */ -static int pack_index_check_locked(const char *path, struct git_pack_file *p) -{ - struct git_pack_idx_header *hdr; - uint32_t version, nr, i, *index; - void *idx_map; - size_t idx_size; - struct stat st; - int error; - /* TODO: properly open the file without access time using O_NOATIME */ - git_file fd = git_futils_open_ro(path); - if (fd < 0) - return fd; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_OS, "unable to stat pack index '%s'", path); - return -1; - } - - if (!S_ISREG(st.st_mode) || - !git__is_sizet(st.st_size) || - (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20) - { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); - return -1; - } - - error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); - - p_close(fd); - - if (error < 0) - return error; - - hdr = idx_map = p->index_map.data; - - if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { - version = ntohl(hdr->idx_version); - - if (version < 2 || version > 2) { - git_futils_mmap_free(&p->index_map); - return packfile_error("unsupported index version"); - } - - } else - version = 1; - - nr = 0; - index = idx_map; - - if (version > 1) - index += 2; /* skip index header */ - - for (i = 0; i < 256; i++) { - uint32_t n = ntohl(index[i]); - if (n < nr) { - git_futils_mmap_free(&p->index_map); - return packfile_error("index is non-monotonic"); - } - nr = n; - } - - if (version == 1) { - /* - * Total size: - * - 256 index entries 4 bytes each - * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - */ - if (idx_size != 4*256 + nr * 24 + 20 + 20) { - git_futils_mmap_free(&p->index_map); - return packfile_error("index is corrupted"); - } - } else if (version == 2) { - /* - * Minimum size: - * - 8 bytes of header - * - 256 index entries 4 bytes each - * - 20-byte sha1 entry * nr - * - 4-byte crc entry * nr - * - 4-byte offset entry * nr - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - * And after the 4-byte offset table might be a - * variable sized table containing 8-byte entries - * for offsets larger than 2^31. - */ - unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; - unsigned long max_size = min_size; - - if (nr) - max_size += (nr - 1)*8; - - if (idx_size < min_size || idx_size > max_size) { - git_futils_mmap_free(&p->index_map); - return packfile_error("wrong index size"); - } - } - - p->num_objects = nr; - p->index_version = version; - return 0; -} - -/* Run with the packfile lock held */ -static int pack_index_open_locked(struct git_pack_file *p) -{ - int error = 0; - size_t name_len; - git_str idx_name = GIT_STR_INIT; - - if (p->index_version > -1) - goto cleanup; - - /* checked by git_pack_file alloc */ - name_len = strlen(p->pack_name); - GIT_ASSERT(name_len > strlen(".pack")); - - if ((error = git_str_init(&idx_name, name_len)) < 0) - goto cleanup; - - git_str_put(&idx_name, p->pack_name, name_len - strlen(".pack")); - git_str_puts(&idx_name, ".idx"); - if (git_str_oom(&idx_name)) { - error = -1; - goto cleanup; - } - - if (p->index_version == -1) - error = pack_index_check_locked(idx_name.ptr, p); - -cleanup: - git_str_dispose(&idx_name); - - return error; -} - -static unsigned char *pack_window_open( - struct git_pack_file *p, - git_mwindow **w_cursor, - off64_t offset, - unsigned int *left) -{ - unsigned char *pack_data = NULL; - - if (git_mutex_lock(&p->lock) < 0) { - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); - return NULL; - } - if (git_mutex_lock(&p->mwf.lock) < 0) { - git_mutex_unlock(&p->lock); - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); - return NULL; - } - - if (p->mwf.fd == -1 && packfile_open_locked(p) < 0) - goto cleanup; - - /* Since packfiles end in a hash of their content and it's - * pointless to ask for an offset into the middle of that - * hash, and the pack_window_contains function above wouldn't match - * don't allow an offset too close to the end of the file. - * - * Don't allow a negative offset, as that means we've wrapped - * around. - */ - if (offset > (p->mwf.size - 20)) - goto cleanup; - if (offset < 0) - goto cleanup; - - pack_data = git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); - -cleanup: - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - return pack_data; - } - -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", - * then three bits of "type", - * with the high bit being "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type) -{ - unsigned char *hdr_base; - unsigned char c; - - GIT_ASSERT_ARG(type >= GIT_OBJECT_COMMIT && type <= GIT_OBJECT_REF_DELTA); - - /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ - - c = (unsigned char)((type << 4) | (size & 15)); - size >>= 4; - hdr_base = hdr; - - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - } - *hdr++ = c; - - *out = (hdr - hdr_base); - return 0; -} - - -static int packfile_unpack_header1( - unsigned long *usedp, - size_t *sizep, - git_object_t *type, - const unsigned char *buf, - unsigned long len) -{ - unsigned shift; - unsigned long size, c; - unsigned long used = 0; - - c = buf[used++]; - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (len <= used) { - git_error_set(GIT_ERROR_ODB, "buffer too small"); - return GIT_EBUFS; - } - - if (bitsizeof(long) <= shift) { - *usedp = 0; - git_error_set(GIT_ERROR_ODB, "packfile corrupted"); - return -1; - } - - c = buf[used++]; - size += (c & 0x7f) << shift; - shift += 7; - } - - *sizep = (size_t)size; - *usedp = used; - return 0; -} - -int git_packfile_unpack_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos) -{ - unsigned char *base; - unsigned int left; - unsigned long used; - int error; - - if ((error = git_mutex_lock(&p->lock)) < 0) - return error; - if ((error = git_mutex_lock(&p->mwf.lock)) < 0) { - git_mutex_unlock(&p->lock); - return error; - } - - if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { - git_mutex_unlock(&p->lock); - git_mutex_unlock(&p->mwf.lock); - return error; - } - - /* pack_window_open() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash - * size that is assured.) With our object header encoding - * the maximum deflated object size is 2^137, which is just - * insane, so we know won't exceed what we have been given. - */ - base = git_mwindow_open(&p->mwf, w_curs, *curpos, 20, &left); - git_mutex_unlock(&p->lock); - git_mutex_unlock(&p->mwf.lock); - if (base == NULL) - return GIT_EBUFS; - - error = packfile_unpack_header1(&used, size_p, type_p, base, left); - git_mwindow_close(w_curs); - if (error == GIT_EBUFS) - return error; - else if (error < 0) - return packfile_error("header length is zero"); - - *curpos += used; - return 0; -} - -int git_packfile_resolve_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - off64_t offset) -{ - git_mwindow *w_curs = NULL; - off64_t curpos = offset; - size_t size; - git_object_t type; - off64_t base_offset; - int error; - - error = git_mutex_lock(&p->lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - error = git_mutex_lock(&p->mwf.lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - git_mutex_unlock(&p->lock); - return error; - } - - if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - return error; - } - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - - error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); - if (error < 0) - return error; - - if (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { - size_t base_size; - git_packfile_stream stream; - - error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, offset); - git_mwindow_close(&w_curs); - - if (error < 0) - return error; - - if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0) - return error; - error = git_delta_read_header_fromstream(&base_size, size_p, &stream); - git_packfile_stream_dispose(&stream); - if (error < 0) - return error; - } else { - *size_p = size; - base_offset = 0; - } - - while (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { - curpos = base_offset; - error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); - if (error < 0) - return error; - if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) - break; - - error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, base_offset); - git_mwindow_close(&w_curs); - - if (error < 0) - return error; - } - *type_p = type; - - return error; -} - -#define SMALL_STACK_SIZE 64 - -/** - * Generate the chain of dependencies which we need to get to the - * object at `off`. `chain` is used a stack, popping gives the right - * order to apply deltas on. If an object is found in the pack's base - * cache, we stop calculating there. - */ -static int pack_dependency_chain(git_dependency_chain *chain_out, - 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, off64_t obj_offset) -{ - git_dependency_chain chain = GIT_ARRAY_INIT; - git_mwindow *w_curs = NULL; - off64_t curpos = obj_offset, base_offset; - int error = 0, use_heap = 0; - size_t size, elem_pos; - git_object_t type; - - elem_pos = 0; - while (true) { - struct pack_chain_elem *elem; - git_pack_cache_entry *cached = NULL; - - /* if we have a base cached, we can stop here instead */ - if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { - *cached_out = cached; - *cached_off = obj_offset; - break; - } - - /* if we run out of space on the small stack, use the array */ - if (elem_pos == SMALL_STACK_SIZE) { - git_array_init_to_size(chain, elem_pos); - GIT_ERROR_CHECK_ARRAY(chain); - memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); - chain.size = elem_pos; - use_heap = 1; - } - - curpos = obj_offset; - if (!use_heap) { - elem = &small_stack[elem_pos]; - } else { - elem = git_array_alloc(chain); - if (!elem) { - error = -1; - goto on_error; - } - } - - elem->base_key = obj_offset; - - error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); - if (error < 0) - goto on_error; - - elem->offset = curpos; - elem->size = size; - elem->type = type; - elem->base_key = obj_offset; - - if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) - break; - - error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, obj_offset); - git_mwindow_close(&w_curs); - - if (error < 0) - goto on_error; - - /* we need to pass the pos *after* the delta-base bit */ - elem->offset = curpos; - - /* go through the loop again, but with the new object */ - obj_offset = base_offset; - elem_pos++; - } - - - *stack_sz = elem_pos + 1; - *chain_out = chain; - return error; - -on_error: - git_array_clear(chain); - return error; -} - -int git_packfile_unpack( - git_rawobj *obj, - struct git_pack_file *p, - off64_t *obj_offset) -{ - git_mwindow *w_curs = NULL; - off64_t curpos = *obj_offset; - int error, free_base = 0; - git_dependency_chain chain = GIT_ARRAY_INIT; - struct pack_chain_elem *elem = NULL, *stack; - git_pack_cache_entry *cached = NULL; - struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; - size_t stack_size = 0, elem_pos, alloclen; - git_object_t base_type; - - error = git_mutex_lock(&p->lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - error = git_mutex_lock(&p->mwf.lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - git_mutex_unlock(&p->lock); - return error; - } - - if (p->mwf.fd == -1) - error = packfile_open_locked(p); - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - if (error < 0) - return error; - - /* - * TODO: optionally check the CRC on the packfile - */ - - error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); - if (error < 0) - return error; - - obj->data = NULL; - obj->len = 0; - obj->type = GIT_OBJECT_INVALID; - - /* let's point to the right stack */ - stack = chain.ptr ? chain.ptr : small_stack; - - elem_pos = stack_size; - if (cached) { - memcpy(obj, &cached->raw, sizeof(git_rawobj)); - base_type = obj->type; - elem_pos--; /* stack_size includes the base, which isn't actually there */ - } else { - elem = &stack[--elem_pos]; - base_type = elem->type; - } - - switch (base_type) { - case GIT_OBJECT_COMMIT: - case GIT_OBJECT_TREE: - case GIT_OBJECT_BLOB: - case GIT_OBJECT_TAG: - if (!cached) { - curpos = elem->offset; - error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); - git_mwindow_close(&w_curs); - base_type = elem->type; - } - if (error < 0) - goto cleanup; - break; - case GIT_OBJECT_OFS_DELTA: - case GIT_OBJECT_REF_DELTA: - error = packfile_error("dependency chain ends in a delta"); - goto cleanup; - default: - error = packfile_error("invalid packfile type in header"); - goto cleanup; - } - - /* - * Finding the object we want a cached base element is - * problematic, as we need to make sure we don't accidentally - * give the caller the cached object, which it would then feel - * free to free, so we need to copy the data. - */ - if (cached && stack_size == 1) { - void *data = obj->data; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, obj->len, 1); - obj->data = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(obj->data); - - memcpy(obj->data, data, obj->len + 1); - git_atomic32_dec(&cached->refcount); - goto cleanup; - } - - /* we now apply each consecutive delta until we run out */ - while (elem_pos > 0 && !error) { - git_rawobj base, delta; - - /* - * We can now try to add the base to the cache, as - * long as it's not already the cached one. - */ - if (!cached) - free_base = !!cache_add(&cached, &p->bases, obj, elem->base_key); - - elem = &stack[elem_pos - 1]; - curpos = elem->offset; - error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); - git_mwindow_close(&w_curs); - - if (error < 0) { - /* We have transferred ownership of the data to the cache. */ - obj->data = NULL; - break; - } - - /* the current object becomes the new base, on which we apply the delta */ - base = *obj; - obj->data = NULL; - obj->len = 0; - obj->type = GIT_OBJECT_INVALID; - - error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len); - obj->type = base_type; - - /* - * We usually don't want to free the base at this - * point, as we put it into the cache in the previous - * iteration. free_base lets us know that we got the - * base object directly from the packfile, so we can free it. - */ - git__free(delta.data); - if (free_base) { - free_base = 0; - git__free(base.data); - } - - if (cached) { - git_atomic32_dec(&cached->refcount); - cached = NULL; - } - - if (error < 0) - break; - - elem_pos--; - } - -cleanup: - if (error < 0) { - git__free(obj->data); - if (cached) - git_atomic32_dec(&cached->refcount); - } - - if (elem) - *obj_offset = curpos; - - git_array_clear(chain); - return error; -} - -int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos) -{ - memset(obj, 0, sizeof(git_packfile_stream)); - obj->curpos = curpos; - obj->p = p; - - if (git_zstream_init(&obj->zstream, GIT_ZSTREAM_INFLATE) < 0) { - git_error_set(GIT_ERROR_ZLIB, "failed to init packfile stream"); - return -1; - } - - return 0; -} - -ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len) -{ - unsigned int window_len; - unsigned char *in; - int error; - - if (obj->done) - return 0; - - if ((in = pack_window_open(obj->p, &obj->mw, obj->curpos, &window_len)) == NULL) - return GIT_EBUFS; - - 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; - } - - 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 (!len && !git_zstream_eos(&obj->zstream)) - return GIT_EBUFS; - - return len; - -} - -void git_packfile_stream_dispose(git_packfile_stream *obj) -{ - git_zstream_free(&obj->zstream); -} - -static int packfile_unpack_compressed( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **mwindow, - off64_t *position, - size_t size, - git_object_t type) -{ - git_zstream zstream = GIT_ZSTREAM_INIT; - size_t buffer_len, total = 0; - char *data = NULL; - int error; - - GIT_ERROR_CHECK_ALLOC_ADD(&buffer_len, size, 1); - data = git__calloc(1, buffer_len); - GIT_ERROR_CHECK_ALLOC(data); - - if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0) { - git_error_set(GIT_ERROR_ZLIB, "failed to init zlib stream on unpack"); - goto out; - } - - do { - size_t bytes = buffer_len - total; - unsigned int window_len, consumed; - unsigned char *in; - - if ((in = pack_window_open(p, mwindow, *position, &window_len)) == NULL) { - error = -1; - goto out; - } - - 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; - } - - git_mwindow_close(mwindow); - - consumed = window_len - (unsigned int)zstream.in_len; - - if (!bytes && !consumed) { - git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); - error = -1; - goto out; - } - - *position += consumed; - total += bytes; - } while (!git_zstream_eos(&zstream)); - - if (total != size || !git_zstream_eos(&zstream)) { - git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); - error = -1; - goto out; - } - - obj->type = type; - obj->len = size; - 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 - */ -int get_delta_base( - off64_t *delta_base_out, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos, - git_object_t type, - off64_t delta_obj_offset) -{ - unsigned int left = 0; - unsigned char *base_info; - off64_t base_offset; - git_oid unused; - - GIT_ASSERT_ARG(delta_base_out); - - base_info = pack_window_open(p, w_curs, *curpos, &left); - /* Assumption: the only reason this would fail is because the file is too small */ - if (base_info == NULL) - return GIT_EBUFS; - /* pack_window_open() assured us we have [base_info, base_info + 20) - * as a range that we can look at without walking off the - * end of the mapped window. Its actually the hash size - * that is assured. An OFS_DELTA longer than the hash size - * is stupid, as then a REF_DELTA would be smaller to store. - */ - if (type == GIT_OBJECT_OFS_DELTA) { - unsigned used = 0; - unsigned char c = base_info[used++]; - size_t unsigned_base_offset = c & 127; - while (c & 128) { - if (left <= used) - return GIT_EBUFS; - unsigned_base_offset += 1; - if (!unsigned_base_offset || MSB(unsigned_base_offset, 7)) - return packfile_error("overflow"); - c = base_info[used++]; - unsigned_base_offset = (unsigned_base_offset << 7) + (c & 127); - } - if (unsigned_base_offset == 0 || (size_t)delta_obj_offset <= unsigned_base_offset) - return packfile_error("out of bounds"); - base_offset = delta_obj_offset - unsigned_base_offset; - *curpos += used; - } 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; - - git_oid_fromraw(&oid, base_info); - if ((entry = git_oidmap_get(p->idx_cache, &oid)) != NULL) { - if (entry->offset == 0) - return packfile_error("delta offset is zero"); - - *curpos += 20; - *delta_base_out = entry->offset; - return 0; - } 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 - * progress again in the next loop. - */ - return GIT_PASSTHROUGH; - } - } - - /* The base entry _must_ be in the same pack */ - if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0) - return packfile_error("base entry delta is not in the same pack"); - *curpos += 20; - } else - return packfile_error("unknown object type"); - - if (base_offset == 0) - return packfile_error("delta offset is zero"); - - *delta_base_out = base_offset; - return 0; -} - -/*********************************************************** - * - * PACKFILE METHODS - * - ***********************************************************/ - -void git_packfile_free(struct git_pack_file *p, bool unlink_packfile) -{ - bool locked = true; - - if (!p) - return; - - cache_free(&p->bases); - - if (git_mutex_lock(&p->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile"); - locked = false; - } - if (p->mwf.fd >= 0) { - git_mwindow_free_all(&p->mwf); - p_close(p->mwf.fd); - p->mwf.fd = -1; - } - if (locked) - git_mutex_unlock(&p->lock); - - if (unlink_packfile) - p_unlink(p->pack_name); - - pack_index_free(p); - - git__free(p->bad_object_sha1); - - git_mutex_free(&p->bases.lock); - git_mutex_free(&p->mwf.lock); - git_mutex_free(&p->lock); - git__free(p); -} - -/* Run with the packfile and mwf locks held */ -static int packfile_open_locked(struct git_pack_file *p) -{ - struct stat st; - struct git_pack_header hdr; - git_oid sha1; - unsigned char *idx_sha1; - - if (pack_index_open_locked(p) < 0) - return git_odb__error_notfound("failed to open packfile", NULL, 0); - - if (p->mwf.fd >= 0) - return 0; - - /* TODO: open with noatime */ - p->mwf.fd = git_futils_open_ro(p->pack_name); - if (p->mwf.fd < 0) - goto cleanup; - - if (p_fstat(p->mwf.fd, &st) < 0) { - git_error_set(GIT_ERROR_OS, "could not stat packfile"); - goto cleanup; - } - - /* If we created the struct before we had the pack we lack size. */ - if (!p->mwf.size) { - if (!S_ISREG(st.st_mode)) - goto cleanup; - p->mwf.size = (off64_t)st.st_size; - } else if (p->mwf.size != st.st_size) - goto cleanup; - -#if 0 - /* We leave these file descriptors open with sliding mmap; - * there is no point keeping them open across exec(), though. - */ - fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); - if (fd_flag < 0) - goto cleanup; - - fd_flag |= FD_CLOEXEC; - if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - goto cleanup; -#endif - - /* Verify we recognize this pack file format. */ - if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 || - hdr.hdr_signature != htonl(PACK_SIGNATURE) || - !pack_version_ok(hdr.hdr_version)) - goto cleanup; - - /* Verify the pack matches its index. */ - if (p->num_objects != ntohl(hdr.hdr_entries) || - p_pread(p->mwf.fd, sha1.id, GIT_OID_RAWSZ, p->mwf.size - GIT_OID_RAWSZ) < 0) - goto cleanup; - - idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; - - if (git_oid__cmp(&sha1, (git_oid *)idx_sha1) != 0) - goto cleanup; - - if (git_mwindow_file_register(&p->mwf) < 0) - goto cleanup; - - return 0; - -cleanup: - git_error_set(GIT_ERROR_OS, "invalid packfile '%s'", p->pack_name); - - if (p->mwf.fd >= 0) - p_close(p->mwf.fd); - p->mwf.fd = -1; - - return -1; -} - -int git_packfile__name(char **out, const char *path) -{ - size_t path_len; - git_str buf = GIT_STR_INIT; - - path_len = strlen(path); - - if (path_len < strlen(".idx")) - return git_odb__error_notfound("invalid packfile path", NULL, 0); - - if (git_str_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0) - return -1; - - *out = git_str_detach(&buf); - return 0; -} - -int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) -{ - struct stat st; - struct git_pack_file *p; - size_t path_len = path ? strlen(path) : 0, alloc_len; - - *pack_out = NULL; - - if (path_len < strlen(".idx")) - return git_odb__error_notfound("invalid packfile path", NULL, 0); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*p), path_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - p = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(p); - - memcpy(p->pack_name, path, path_len + 1); - - /* - * Make sure a corresponding .pack file exists and that - * the index looks sane. - */ - if (git__suffixcmp(path, ".idx") == 0) { - size_t root_len = path_len - strlen(".idx"); - - if (!git_disable_pack_keep_file_checks) { - memcpy(p->pack_name + root_len, ".keep", sizeof(".keep")); - if (git_fs_path_exists(p->pack_name) == true) - p->pack_keep = 1; - } - - memcpy(p->pack_name + root_len, ".pack", sizeof(".pack")); - } - - if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) { - git__free(p); - return git_odb__error_notfound("packfile not found", NULL, 0); - } - - /* ok, it looks sane as far as we can check without - * actually mapping the pack file. - */ - p->mwf.fd = -1; - p->mwf.size = st.st_size; - p->pack_local = 1; - p->mtime = (git_time_t)st.st_mtime; - p->index_version = -1; - - if (git_mutex_init(&p->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize packfile mutex"); - git__free(p); - return -1; - } - - if (git_mutex_init(&p->mwf.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize packfile window mutex"); - git_mutex_free(&p->lock); - git__free(p); - return -1; - } - - if (cache_init(&p->bases) < 0) { - git_mutex_free(&p->mwf.lock); - git_mutex_free(&p->lock); - git__free(p); - return -1; - } - - *pack_out = p; - - return 0; -} - -/*********************************************************** - * - * PACKFILE ENTRY SEARCH INTERNALS - * - ***********************************************************/ - -static off64_t nth_packed_object_offset_locked(struct git_pack_file *p, uint32_t n) -{ - const unsigned char *index, *end; - uint32_t off32; - - index = p->index_map.data; - end = index + p->index_map.len; - index += 4 * 256; - if (p->index_version == 1) - return ntohl(*((uint32_t *)(index + 24 * n))); - - index += 8 + p->num_objects * (20 + 4); - off32 = ntohl(*((uint32_t *)(index + 4 * n))); - if (!(off32 & 0x80000000)) - return off32; - index += p->num_objects * 4 + (off32 & 0x7fffffff) * 8; - - /* Make sure we're not being sent out of bounds */ - if (index >= end - 8) - return -1; - - return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | - ntohl(*((uint32_t *)(index + 4))); -} - -static int git__memcmp4(const void *a, const void *b) { - return memcmp(a, b, 4); -} - -int git_pack_foreach_entry( - struct git_pack_file *p, - git_odb_foreach_cb cb, - void *data) -{ - const unsigned char *index, *current; - uint32_t i; - int error = 0; - git_array_oid_t oids = GIT_ARRAY_INIT; - git_oid *oid; - - if (git_mutex_lock(&p->lock) < 0) - return packfile_error("failed to get lock for git_pack_foreach_entry"); - - if ((error = pack_index_open_locked(p)) < 0) { - git_mutex_unlock(&p->lock); - return error; - } - - if (!p->index_map.data) { - git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); - git_mutex_unlock(&p->lock); - return -1; - } - - index = p->index_map.data; - - if (p->index_version > 1) - index += 8; - - index += 4 * 256; - - if (p->oids == NULL) { - git_vector offsets, oids; - - if ((error = git_vector_init(&oids, p->num_objects, NULL))) { - git_mutex_unlock(&p->lock); - return error; - } - - if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4))) { - git_mutex_unlock(&p->lock); - return error; - } - - if (p->index_version > 1) { - const unsigned char *off = index + 24 * p->num_objects; - for (i = 0; i < p->num_objects; i++) - git_vector_insert(&offsets, (void*)&off[4 * i]); - git_vector_sort(&offsets); - git_vector_foreach(&offsets, i, current) - git_vector_insert(&oids, (void*)&index[5 * (current - off)]); - } else { - for (i = 0; i < p->num_objects; i++) - git_vector_insert(&offsets, (void*)&index[24 * i]); - git_vector_sort(&offsets); - git_vector_foreach(&offsets, i, current) - git_vector_insert(&oids, (void*)¤t[4]); - } - - git_vector_free(&offsets); - p->oids = (git_oid **)git_vector_detach(NULL, NULL, &oids); - } - - /* We need to copy the OIDs to another array before we relinquish the lock to avoid races. */ - git_array_init_to_size(oids, p->num_objects); - if (!oids.ptr) { - git_mutex_unlock(&p->lock); - git_array_clear(oids); - GIT_ERROR_CHECK_ARRAY(oids); - } - for (i = 0; i < p->num_objects; i++) { - oid = git_array_alloc(oids); - if (!oid) { - git_mutex_unlock(&p->lock); - git_array_clear(oids); - GIT_ERROR_CHECK_ALLOC(oid); - } - git_oid_cpy(oid, p->oids[i]); - } - - git_mutex_unlock(&p->lock); - - git_array_foreach(oids, i, oid) { - if ((error = cb(oid, data)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - git_array_clear(oids); - return error; -} - -int git_pack_foreach_entry_offset( - struct git_pack_file *p, - git_pack_foreach_entry_offset_cb cb, - void *data) -{ - const unsigned char *index; - off64_t current_offset; - const git_oid *current_oid; - uint32_t i; - int error = 0; - - if (git_mutex_lock(&p->lock) < 0) - return packfile_error("failed to get lock for git_pack_foreach_entry_offset"); - - index = p->index_map.data; - if (index == NULL) { - if ((error = pack_index_open_locked(p)) < 0) - goto cleanup; - - if (!p->index_map.data) { - git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); - goto cleanup; - } - - index = p->index_map.data; - } - - if (p->index_version > 1) - index += 8; - - index += 4 * 256; - - /* all offsets should have been validated by pack_index_check_locked */ - if (p->index_version > 1) { - const unsigned char *offsets = index + 24 * p->num_objects; - const unsigned char *large_offset_ptr; - const unsigned char *large_offsets = index + 28 * p->num_objects; - const unsigned char *large_offsets_end = ((const unsigned char *)p->index_map.data) + p->index_map.len - 20; - for (i = 0; i < p->num_objects; i++) { - current_offset = ntohl(*(const uint32_t *)(offsets + 4 * i)); - if (current_offset & 0x80000000) { - large_offset_ptr = large_offsets + (current_offset & 0x7fffffff) * 8; - if (large_offset_ptr >= large_offsets_end) { - error = packfile_error("invalid large offset"); - goto cleanup; - } - current_offset = (((off64_t)ntohl(*((uint32_t *)(large_offset_ptr + 0)))) << 32) | - ntohl(*((uint32_t *)(large_offset_ptr + 4))); - } - current_oid = (const git_oid *)(index + 20 * i); - if ((error = cb(current_oid, current_offset, data)) != 0) { - error = git_error_set_after_callback(error); - goto cleanup; - } - } - } else { - for (i = 0; i < p->num_objects; i++) { - current_offset = ntohl(*(const uint32_t *)(index + 24 * i)); - current_oid = (const git_oid *)(index + 24 * i + 4); - if ((error = cb(current_oid, current_offset, data)) != 0) { - error = git_error_set_after_callback(error); - goto cleanup; - } - } - } - -cleanup: - git_mutex_unlock(&p->lock); - return error; -} - -int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, - unsigned hi, const unsigned char *oid_prefix) -{ - const unsigned char *base = oid_lookup_table; - - while (lo < hi) { - unsigned mi = (lo + hi) / 2; - int cmp = git_oid__hashcmp(base + mi * stride, oid_prefix); - - if (!cmp) - return mi; - - if (cmp > 0) - hi = mi; - else - lo = mi+1; - } - - return -((int)lo)-1; -} - -static int pack_entry_find_offset( - off64_t *offset_out, - git_oid *found_oid, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len) -{ - const uint32_t *level1_ofs; - const unsigned char *index; - unsigned hi, lo, stride; - int pos, found = 0; - off64_t offset; - const unsigned char *current = 0; - int error = 0; - - *offset_out = 0; - - if (git_mutex_lock(&p->lock) < 0) - return packfile_error("failed to get lock for pack_entry_find_offset"); - - if ((error = pack_index_open_locked(p)) < 0) - goto cleanup; - - if (!p->index_map.data) { - git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); - goto cleanup; - } - - index = p->index_map.data; - level1_ofs = p->index_map.data; - - if (p->index_version > 1) { - level1_ofs += 2; - index += 8; - } - - index += 4 * 256; - hi = ntohl(level1_ofs[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); - - if (p->index_version > 1) { - stride = 20; - } else { - stride = 24; - index += 4; - } - -#ifdef INDEX_DEBUG_LOOKUP - printf("%02x%02x%02x... lo %u hi %u nr %d\n", - short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); -#endif - - pos = git_pack__lookup_sha1(index, stride, lo, hi, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = index + pos * stride; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = - 1 - pos; - if (pos < (int)p->num_objects) { - current = index + pos * stride; - - if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) - found = 1; - } - } - - if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { - /* Check for ambiguousity */ - const unsigned char *next = current + stride; - - if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) { - found = 2; - } - } - - if (!found) { - error = git_odb__error_notfound("failed to find offset for pack entry", short_oid, len); - goto cleanup; - } - if (found > 1) { - error = git_odb__error_ambiguous("found multiple offsets for pack entry"); - goto cleanup; - } - - if ((offset = nth_packed_object_offset_locked(p, pos)) < 0) { - git_error_set(GIT_ERROR_ODB, "packfile index is corrupt"); - error = -1; - goto cleanup; - } - - *offset_out = offset; - git_oid_fromraw(found_oid, current); - -#ifdef INDEX_DEBUG_LOOKUP - { - unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; - git_oid_fmt(hex_sha1, found_oid); - hex_sha1[GIT_OID_HEXSZ] = '\0'; - printf("found lo=%d %s\n", lo, hex_sha1); - } -#endif - -cleanup: - git_mutex_unlock(&p->lock); - return error; -} - -int git_pack_entry_find( - struct git_pack_entry *e, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len) -{ - off64_t offset; - git_oid found_oid; - int error; - - GIT_ASSERT_ARG(p); - - if (len == GIT_OID_HEXSZ && p->num_bad_objects) { - unsigned i; - for (i = 0; i < p->num_bad_objects; i++) - if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0) - return packfile_error("bad object found in packfile"); - } - - error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); - if (error < 0) - return error; - - error = git_mutex_lock(&p->lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - error = git_mutex_lock(&p->mwf.lock); - if (error < 0) { - git_mutex_unlock(&p->lock); - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - - /* we found a unique entry in the index; - * make sure the packfile backing the index - * still exists on disk */ - if (p->mwf.fd == -1) - error = packfile_open_locked(p); - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - if (error < 0) - return error; - - e->offset = offset; - e->p = p; - - git_oid_cpy(&e->sha1, &found_oid); - return 0; -} diff --git a/src/pack.h b/src/pack.h deleted file mode 100644 index bf279c6b6..000000000 --- a/src/pack.h +++ /dev/null @@ -1,199 +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_pack_h__ -#define INCLUDE_pack_h__ - -#include "common.h" - -#include "git2/oid.h" - -#include "array.h" -#include "map.h" -#include "mwindow.h" -#include "odb.h" -#include "offmap.h" -#include "oidmap.h" -#include "zstream.h" - -/** - * Function type for callbacks from git_pack_foreach_entry_offset. - */ -typedef int git_pack_foreach_entry_offset_cb( - const git_oid *id, - off64_t offset, - void *payload); - -#define GIT_PACK_FILE_MODE 0444 - -#define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 -#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) -struct git_pack_header { - uint32_t hdr_signature; - uint32_t hdr_version; - uint32_t hdr_entries; -}; - -/* - * The first four bytes of index formats later than version 1 should - * start with this signature, as all older git binaries would find this - * value illegal and abort reading the file. - * - * This is the case because the number of objects in a packfile - * cannot exceed 1,431,660,000 as every object would need at least - * 3 bytes of data and the overall packfile cannot exceed 4 GiB with - * version 1 of the index file due to the offsets limited to 32 bits. - * Clearly the signature exceeds this maximum. - * - * Very old git binaries will also compare the first 4 bytes to the - * next 4 bytes in the index and abort with a "non-monotonic index" - * error if the second 4 byte word is smaller than the first 4 - * byte word. This would be true in the proposed future index - * format as idx_signature would be greater than idx_version. - */ - -#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ - -struct git_pack_idx_header { - uint32_t idx_signature; - uint32_t idx_version; -}; - -typedef struct git_pack_cache_entry { - size_t last_usage; /* enough? */ - git_atomic32 refcount; - git_rawobj raw; -} git_pack_cache_entry; - -struct pack_chain_elem { - 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; - -#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024 -#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */ - -typedef struct { - size_t memory_used; - size_t memory_limit; - size_t use_ctr; - git_mutex lock; - git_offmap *entries; -} git_pack_cache; - -struct git_pack_file { - git_mwindow_file mwf; - git_map index_map; - git_mutex lock; /* protect updates to index_map */ - git_atomic32 refcount; - - uint32_t num_objects; - uint32_t num_bad_objects; - git_oid *bad_object_sha1; /* array of git_oid */ - - int index_version; - git_time_t mtime; - unsigned pack_local:1, pack_keep:1, has_cache:1; - git_oidmap *idx_cache; - git_oid **oids; - - git_pack_cache bases; /* delta base cache */ - - time_t last_freshen; /* last time the packfile was freshened */ - - /* something like ".git/objects/pack/xxxxx.pack" */ - char pack_name[GIT_FLEX_ARRAY]; /* more */ -}; - -/** - * Return the position where an OID (or a prefix) would be inserted within the - * OID Lookup Table of an .idx file. This performs binary search between the lo - * and hi indices. - * - * The stride parameter is provided because .idx files version 1 store the OIDs - * interleaved with the 4-byte file offsets of the objects within the .pack - * file (stride = 24), whereas files with version 2 store them in a contiguous - * flat array (stride = 20). - */ -int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, - unsigned hi, const unsigned char *oid_prefix); - -struct git_pack_entry { - off64_t offset; - git_oid sha1; - struct git_pack_file *p; -}; - -typedef struct git_packfile_stream { - off64_t curpos; - int done; - git_zstream zstream; - struct git_pack_file *p; - git_mwindow *mw; -} git_packfile_stream; - -int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type); - -int git_packfile__name(char **out, const char *path); - -int git_packfile_unpack_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos); - -int git_packfile_resolve_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - off64_t 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, 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); - -int get_delta_base( - off64_t *delta_base_out, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos, - git_object_t type, - off64_t delta_obj_offset); - -void git_packfile_free(struct git_pack_file *p, bool unlink_packfile); -int git_packfile_alloc(struct git_pack_file **pack_out, const char *path); - -int git_pack_entry_find( - struct git_pack_entry *e, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len); -int git_pack_foreach_entry( - struct git_pack_file *p, - git_odb_foreach_cb cb, - void *data); -/** - * Similar to git_pack_foreach_entry, but: - * - It also provides the offset of the object within the - * packfile. - * - It does not sort the objects in any order. - * - It retains the lock while invoking the callback. - */ -int git_pack_foreach_entry_offset( - struct git_pack_file *p, - git_pack_foreach_entry_offset_cb cb, - void *data); - -#endif diff --git a/src/parse.c b/src/parse.c deleted file mode 100644 index 0a10758bf..000000000 --- a/src/parse.c +++ /dev/null @@ -1,134 +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 "parse.h" - -int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len) -{ - if (content && content_len) { - ctx->content = content; - ctx->content_len = content_len; - } else { - ctx->content = ""; - ctx->content_len = 0; - } - - ctx->remain = ctx->content; - ctx->remain_len = ctx->content_len; - ctx->line = ctx->remain; - ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); - ctx->line_num = 1; - - return 0; -} - -void git_parse_ctx_clear(git_parse_ctx *ctx) -{ - memset(ctx, 0, sizeof(*ctx)); - ctx->content = ""; -} - -void git_parse_advance_line(git_parse_ctx *ctx) -{ - ctx->line += ctx->line_len; - ctx->remain_len -= ctx->line_len; - ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); - ctx->line_num++; -} - -void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt) -{ - ctx->line += char_cnt; - ctx->remain_len -= char_cnt; - ctx->line_len -= char_cnt; -} - -int git_parse_advance_expected( - git_parse_ctx *ctx, - const char *expected, - size_t expected_len) -{ - if (ctx->line_len < expected_len) - return -1; - - if (memcmp(ctx->line, expected, expected_len) != 0) - return -1; - - git_parse_advance_chars(ctx, expected_len); - return 0; -} - -int git_parse_advance_ws(git_parse_ctx *ctx) -{ - int ret = -1; - - while (ctx->line_len > 0 && - ctx->line[0] != '\n' && - git__isspace(ctx->line[0])) { - ctx->line++; - ctx->line_len--; - ctx->remain_len--; - ret = 0; - } - - return ret; -} - -int git_parse_advance_nl(git_parse_ctx *ctx) -{ - if (ctx->line_len != 1 || ctx->line[0] != '\n') - return -1; - - git_parse_advance_line(ctx); - return 0; -} - -int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base) -{ - const char *end; - int ret; - - if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) - return -1; - - if ((ret = git__strntol64(out, ctx->line, ctx->line_len, &end, base)) < 0) - return -1; - - git_parse_advance_chars(ctx, (end - ctx->line)); - 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; - const char *ptr = ctx->line; - - while (remain) { - char c = *ptr; - - if ((flags & GIT_PARSE_PEEK_SKIP_WHITESPACE) && - git__isspace(c)) { - remain--; - ptr++; - continue; - } - - *out = c; - return 0; - } - - return -1; -} diff --git a/src/parse.h b/src/parse.h deleted file mode 100644 index 0ecb7c103..000000000 --- a/src/parse.h +++ /dev/null @@ -1,61 +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_parse_h__ -#define INCLUDE_parse_h__ - -#include "common.h" - -typedef struct { - /* Original content buffer */ - const char *content; - size_t content_len; - - /* The remaining (unparsed) buffer */ - const char *remain; - size_t remain_len; - - const char *line; - size_t line_len; - 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_ctx_contains_s(ctx, str) \ - git_parse_ctx_contains(ctx, str, sizeof(str) - 1) - -GIT_INLINE(bool) git_parse_ctx_contains( - git_parse_ctx *ctx, const char *str, size_t len) -{ - return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); -} - -void git_parse_advance_line(git_parse_ctx *ctx); -void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt); -int git_parse_advance_expected( - git_parse_ctx *ctx, - const char *expected, - size_t expected_len); - -#define git_parse_advance_expected_str(ctx, str) \ - git_parse_advance_expected(ctx, str, strlen(str)) - -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) -}; - -int git_parse_peek(char *out, git_parse_ctx *ctx, int flags); - -#endif diff --git a/src/patch.c b/src/patch.c deleted file mode 100644 index a30546f3c..000000000 --- a/src/patch.c +++ /dev/null @@ -1,230 +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 "patch.h" - -#include "git2/patch.h" -#include "diff.h" - -int git_patch__invoke_callbacks( - git_patch *patch, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *payload) -{ - int error = 0; - uint32_t i, j; - - if (file_cb) - error = file_cb(patch->delta, 0, payload); - - if (error) - return error; - - if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { - if (binary_cb) - error = binary_cb(patch->delta, &patch->binary, payload); - - return error; - } - - if (!hunk_cb && !line_cb) - return error; - - for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { - git_patch_hunk *h = git_array_get(patch->hunks, i); - - if (hunk_cb) - error = hunk_cb(patch->delta, &h->hunk, payload); - - if (!line_cb) - continue; - - for (j = 0; !error && j < h->line_count; ++j) { - git_diff_line *l = - git_array_get(patch->lines, h->line_start + j); - - error = line_cb(patch->delta, &h->hunk, l, payload); - } - } - - return error; -} - -size_t git_patch_size( - git_patch *patch, - int include_context, - int include_hunk_headers, - int include_file_headers) -{ - size_t out; - - GIT_ASSERT_ARG(patch); - - out = patch->content_size; - - if (!include_context) - out -= patch->context_size; - - if (include_hunk_headers) - out += patch->header_size; - - if (include_file_headers) { - git_str file_header = GIT_STR_INIT; - - if (git_diff_delta__format_file_header( - &file_header, patch->delta, NULL, NULL, 0, true) < 0) - git_error_clear(); - else - out += git_str_len(&file_header); - - git_str_dispose(&file_header); - } - - return out; -} - -int git_patch_line_stats( - size_t *total_ctxt, - size_t *total_adds, - size_t *total_dels, - const git_patch *patch) -{ - size_t totals[3], idx; - - memset(totals, 0, sizeof(totals)); - - for (idx = 0; idx < git_array_size(patch->lines); ++idx) { - git_diff_line *line = git_array_get(patch->lines, idx); - if (!line) - continue; - - switch (line->origin) { - case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; - case GIT_DIFF_LINE_ADDITION: totals[1]++; break; - case GIT_DIFF_LINE_DELETION: totals[2]++; break; - default: - /* diff --stat and --numstat don't count EOFNL marks because - * they will always be paired with a ADDITION or DELETION line. - */ - break; - } - } - - if (total_ctxt) - *total_ctxt = totals[0]; - if (total_adds) - *total_adds = totals[1]; - if (total_dels) - *total_dels = totals[2]; - - return 0; -} - -const git_diff_delta *git_patch_get_delta(const git_patch *patch) -{ - GIT_ASSERT_ARG_WITH_RETVAL(patch, NULL); - return patch->delta; -} - -size_t git_patch_num_hunks(const git_patch *patch) -{ - GIT_ASSERT_ARG(patch); - return git_array_size(patch->hunks); -} - -static int patch_error_outofrange(const char *thing) -{ - git_error_set(GIT_ERROR_INVALID, "patch %s index out of range", thing); - return GIT_ENOTFOUND; -} - -int git_patch_get_hunk( - const git_diff_hunk **out, - size_t *lines_in_hunk, - git_patch *patch, - size_t hunk_idx) -{ - git_patch_hunk *hunk; - GIT_ASSERT_ARG(patch); - - hunk = git_array_get(patch->hunks, hunk_idx); - - if (!hunk) { - if (out) *out = NULL; - if (lines_in_hunk) *lines_in_hunk = 0; - return patch_error_outofrange("hunk"); - } - - if (out) *out = &hunk->hunk; - if (lines_in_hunk) *lines_in_hunk = hunk->line_count; - return 0; -} - -int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) -{ - git_patch_hunk *hunk; - GIT_ASSERT_ARG(patch); - - if (!(hunk = git_array_get(patch->hunks, hunk_idx))) - return patch_error_outofrange("hunk"); - return (int)hunk->line_count; -} - -int git_patch_get_line_in_hunk( - const git_diff_line **out, - git_patch *patch, - size_t hunk_idx, - size_t line_of_hunk) -{ - git_patch_hunk *hunk; - git_diff_line *line; - - GIT_ASSERT_ARG(patch); - - if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { - if (out) *out = NULL; - return patch_error_outofrange("hunk"); - } - - if (line_of_hunk >= hunk->line_count || - !(line = git_array_get( - patch->lines, hunk->line_start + line_of_hunk))) { - if (out) *out = NULL; - return patch_error_outofrange("line"); - } - - if (out) *out = line; - return 0; -} - -git_repository *git_patch_owner(const git_patch *patch) -{ - return patch->repo; -} - -int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - GIT_ASSERT_ARG(diff->patch_fn); - return diff->patch_fn(out, diff, idx); -} - -static void git_patch__free(git_patch *patch) -{ - if (patch->free_fn) - patch->free_fn(patch); -} - -void git_patch_free(git_patch *patch) -{ - if (patch) - GIT_REFCOUNT_DEC(patch, git_patch__free); -} diff --git a/src/patch.h b/src/patch.h deleted file mode 100644 index 1e1471ed6..000000000 --- a/src/patch.h +++ /dev/null @@ -1,69 +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_patch_h__ -#define INCLUDE_patch_h__ - -#include "common.h" - -#include "git2/patch.h" -#include "array.h" - -/* cached information about a hunk in a patch */ -typedef struct git_patch_hunk { - git_diff_hunk hunk; - size_t line_start; - size_t line_count; -} git_patch_hunk; - -struct git_patch { - git_refcount rc; - - git_repository *repo; /* may be null */ - - git_diff_options diff_opts; - - git_diff_delta *delta; - git_diff_binary binary; - git_array_t(git_patch_hunk) hunks; - git_array_t(git_diff_line) lines; - - size_t header_size; - size_t content_size; - size_t context_size; - - void (*free_fn)(git_patch *patch); -}; - -extern int git_patch__invoke_callbacks( - git_patch *patch, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *payload); - -extern int git_patch_line_stats( - size_t *total_ctxt, - size_t *total_adds, - size_t *total_dels, - const git_patch *patch); - -/** Options for parsing patch files. */ -typedef struct { - /** - * The length of the prefix (in path segments) for the filenames. - * This prefix will be removed when looking for files. The default is 1. - */ - uint32_t prefix_len; -} git_patch_options; - -#define GIT_PATCH_OPTIONS_INIT { 1 } - -extern int git_patch__to_buf(git_str *out, git_patch *patch); -extern void git_patch_free(git_patch *patch); - -#endif diff --git a/src/patch_generate.c b/src/patch_generate.c deleted file mode 100644 index bc598fea8..000000000 --- a/src/patch_generate.c +++ /dev/null @@ -1,915 +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 "patch_generate.h" - -#include "git2/blob.h" -#include "diff.h" -#include "diff_generate.h" -#include "diff_file.h" -#include "diff_driver.h" -#include "diff_xdiff.h" -#include "delta.h" -#include "zstream.h" -#include "futils.h" - -static void diff_output_init( - git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, - git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); - -static void diff_output_to_patch( - git_patch_generated_output *, git_patch_generated *); - -static void patch_generated_free(git_patch *p) -{ - git_patch_generated *patch = (git_patch_generated *)p; - - git_array_clear(patch->base.lines); - git_array_clear(patch->base.hunks); - - git__free((char *)patch->base.binary.old_file.data); - git__free((char *)patch->base.binary.new_file.data); - - git_diff_file_content__clear(&patch->ofile); - git_diff_file_content__clear(&patch->nfile); - - git_diff_free(patch->diff); /* decrements refcount */ - patch->diff = NULL; - - git_pool_clear(&patch->flattened); - - git__free((char *)patch->base.diff_opts.old_prefix); - git__free((char *)patch->base.diff_opts.new_prefix); - - if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED) - git__free(patch); -} - -static void patch_generated_update_binary(git_patch_generated *patch) -{ - if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) - return; - - if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || - (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - - else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || - patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - - else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && - (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) - patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; -} - -static void patch_generated_init_common(git_patch_generated *patch) -{ - patch->base.free_fn = patch_generated_free; - - patch_generated_update_binary(patch); - - patch->flags |= GIT_PATCH_GENERATED_INITIALIZED; - - if (patch->diff) - git_diff_addref(patch->diff); -} - -static int patch_generated_normalize_options( - git_diff_options *out, - const git_diff_options *opts) -{ - if (opts) { - GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); - memcpy(out, opts, sizeof(git_diff_options)); - } else { - git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT; - memcpy(out, &default_opts, sizeof(git_diff_options)); - } - - out->old_prefix = opts && opts->old_prefix ? - git__strdup(opts->old_prefix) : - git__strdup(DIFF_OLD_PREFIX_DEFAULT); - - out->new_prefix = opts && opts->new_prefix ? - git__strdup(opts->new_prefix) : - git__strdup(DIFF_NEW_PREFIX_DEFAULT); - - GIT_ERROR_CHECK_ALLOC(out->old_prefix); - GIT_ERROR_CHECK_ALLOC(out->new_prefix); - - return 0; -} - -static int patch_generated_init( - git_patch_generated *patch, git_diff *diff, size_t delta_index) -{ - int error = 0; - - memset(patch, 0, sizeof(*patch)); - - patch->diff = diff; - patch->base.repo = diff->repo; - patch->base.delta = git_vector_get(&diff->deltas, delta_index); - patch->delta_index = delta_index; - - if ((error = patch_generated_normalize_options( - &patch->base.diff_opts, &diff->opts)) < 0 || - (error = git_diff_file_content__init_from_diff( - &patch->ofile, diff, patch->base.delta, true)) < 0 || - (error = git_diff_file_content__init_from_diff( - &patch->nfile, diff, patch->base.delta, false)) < 0) - return error; - - patch_generated_init_common(patch); - - return 0; -} - -static int patch_generated_alloc_from_diff( - git_patch_generated **out, git_diff *diff, size_t delta_index) -{ - int error; - git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated)); - GIT_ERROR_CHECK_ALLOC(patch); - - if (!(error = patch_generated_init(patch, diff, delta_index))) { - patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; - GIT_REFCOUNT_INC(&patch->base); - } else { - git__free(patch); - patch = NULL; - } - - *out = patch; - return error; -} - -GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file) -{ - if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) - return false; - - return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; -} - -static bool patch_generated_diffable(git_patch_generated *patch) -{ - size_t olen, nlen; - - if (patch->base.delta->status == GIT_DELTA_UNMODIFIED) - return false; - - /* if we've determined this to be binary (and we are not showing binary - * data) then we have skipped loading the map data. instead, query the - * file data itself. - */ - if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && - (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { - olen = (size_t)patch->ofile.file->size; - nlen = (size_t)patch->nfile.file->size; - } else { - olen = patch->ofile.map.len; - nlen = patch->nfile.map.len; - } - - /* if both sides are empty, files are identical */ - if (!olen && !nlen) - return false; - - /* otherwise, check the file sizes and the oid */ - return (olen != nlen || - !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); -} - -static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output) -{ - int error = 0; - bool incomplete_data; - - if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0) - return 0; - - /* if no hunk and data callbacks and user doesn't care if data looks - * binary, then there is no need to actually load the data - */ - if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 && - output && !output->binary_cb && !output->hunk_cb && !output->data_cb) - return 0; - - incomplete_data = - (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || - (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) && - ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || - (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0)); - - if ((error = git_diff_file_content__load( - &patch->ofile, &patch->base.diff_opts)) < 0 || - (error = git_diff_file_content__load( - &patch->nfile, &patch->base.diff_opts)) < 0 || - should_skip_binary(patch, patch->nfile.file)) - goto cleanup; - - /* if previously missing an oid, and now that we have it the two sides - * are the same (and not submodules), update MODIFIED -> UNMODIFIED - */ - if (incomplete_data && - patch->ofile.file->mode == patch->nfile.file->mode && - patch->ofile.file->mode != GIT_FILEMODE_COMMIT && - git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && - patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ - patch->base.delta->status = GIT_DELTA_UNMODIFIED; - -cleanup: - patch_generated_update_binary(patch); - - if (!error) { - if (patch_generated_diffable(patch)) - patch->flags |= GIT_PATCH_GENERATED_DIFFABLE; - - patch->flags |= GIT_PATCH_GENERATED_LOADED; - } - - return error; -} - -static int patch_generated_invoke_file_callback( - git_patch_generated *patch, git_patch_generated_output *output) -{ - float progress = patch->diff ? - ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; - - if (!output->file_cb) - return 0; - - return git_error_set_after_callback_function( - output->file_cb(patch->base.delta, progress, output->payload), - "git_patch"); -} - -static int create_binary( - git_diff_binary_t *out_type, - char **out_data, - size_t *out_datalen, - size_t *out_inflatedlen, - const char *a_data, - size_t a_datalen, - const char *b_data, - size_t b_datalen) -{ - git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT; - size_t delta_data_len = 0; - int error; - - /* The git_delta function accepts unsigned long only */ - if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen)) - return GIT_EBUFS; - - if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0) - goto done; - - /* The git_delta function accepts unsigned long only */ - if (!git__is_ulong(deflate.size)) { - error = GIT_EBUFS; - goto done; - } - - if (a_datalen && b_datalen) { - void *delta_data; - - error = git_delta(&delta_data, &delta_data_len, - a_data, a_datalen, - b_data, b_datalen, - deflate.size); - - if (error == 0) { - error = git_zstream_deflatebuf( - &delta, delta_data, delta_data_len); - - git__free(delta_data); - } else if (error == GIT_EBUFS) { - error = 0; - } - - if (error < 0) - goto done; - } - - if (delta.size && delta.size < deflate.size) { - *out_type = GIT_DIFF_BINARY_DELTA; - *out_datalen = delta.size; - *out_data = git_str_detach(&delta); - *out_inflatedlen = delta_data_len; - } else { - *out_type = GIT_DIFF_BINARY_LITERAL; - *out_datalen = deflate.size; - *out_data = git_str_detach(&deflate); - *out_inflatedlen = b_datalen; - } - -done: - git_str_dispose(&deflate); - git_str_dispose(&delta); - - return error; -} - -static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch) -{ - git_diff_binary binary = {0}; - const char *old_data = patch->ofile.map.data; - const char *new_data = patch->nfile.map.data; - size_t old_len = patch->ofile.map.len, - new_len = patch->nfile.map.len; - int error; - - /* Only load contents if the user actually wants to diff - * binary files. */ - if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) { - binary.contains_data = 1; - - /* Create the old->new delta (as the "new" side of the patch), - * and the new->old delta (as the "old" side) - */ - if ((error = create_binary(&binary.old_file.type, - (char **)&binary.old_file.data, - &binary.old_file.datalen, - &binary.old_file.inflatedlen, - new_data, new_len, old_data, old_len)) < 0 || - (error = create_binary(&binary.new_file.type, - (char **)&binary.new_file.data, - &binary.new_file.datalen, - &binary.new_file.inflatedlen, - old_data, old_len, new_data, new_len)) < 0) - return error; - } - - error = git_error_set_after_callback_function( - output->binary_cb(patch->base.delta, &binary, output->payload), - "git_patch"); - - git__free((char *) binary.old_file.data); - git__free((char *) binary.new_file.data); - - return error; -} - -static int patch_generated_create( - git_patch_generated *patch, - git_patch_generated_output *output) -{ - int error = 0; - - if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0) - return 0; - - /* if we are not looking at the binary or text data, don't do the diff */ - if (!output->binary_cb && !output->hunk_cb && !output->data_cb) - return 0; - - if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 && - (error = patch_generated_load(patch, output)) < 0) - return error; - - if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0) - return 0; - - if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { - if (output->binary_cb) - error = diff_binary(output, patch); - } - else { - if (output->diff_cb) - error = output->diff_cb(output, patch); - } - - patch->flags |= GIT_PATCH_GENERATED_DIFFED; - return error; -} - -static int diff_required(git_diff *diff, const char *action) -{ - if (diff) - return 0; - git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action); - return -1; -} - -typedef struct { - git_patch_generated patch; - git_diff_delta delta; - char paths[GIT_FLEX_ARRAY]; -} patch_generated_with_delta; - -static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo) -{ - int error = 0; - git_patch_generated *patch = &pd->patch; - bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); - bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); - - pd->delta.status = has_new ? - (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : - (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); - - if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) - pd->delta.status = GIT_DELTA_UNMODIFIED; - - patch->base.delta = &pd->delta; - - patch_generated_init_common(patch); - - if (pd->delta.status == GIT_DELTA_UNMODIFIED && - !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) { - - /* Even empty patches are flagged as binary, and even though - * there's no difference, we flag this as "containing data" - * (the data is known to be empty, as opposed to wholly unknown). - */ - if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) - patch->base.binary.contains_data = 1; - - return error; - } - - error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo); - - if (!error) - error = patch_generated_create(patch, (git_patch_generated_output *)xo); - - return error; -} - -static int patch_generated_from_sources( - patch_generated_with_delta *pd, - git_xdiff_output *xo, - git_diff_file_content_src *oldsrc, - git_diff_file_content_src *newsrc, - const git_diff_options *opts) -{ - int error = 0; - git_repository *repo = - oldsrc->blob ? git_blob_owner(oldsrc->blob) : - newsrc->blob ? git_blob_owner(newsrc->blob) : NULL; - git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; - git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; - - if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0) - return error; - - if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { - void *tmp = lfile; lfile = rfile; rfile = tmp; - tmp = ldata; ldata = rdata; rdata = tmp; - } - - pd->patch.base.delta = &pd->delta; - - if (!oldsrc->as_path) { - if (newsrc->as_path) - oldsrc->as_path = newsrc->as_path; - else - oldsrc->as_path = newsrc->as_path = "file"; - } - else if (!newsrc->as_path) - newsrc->as_path = oldsrc->as_path; - - lfile->path = oldsrc->as_path; - rfile->path = newsrc->as_path; - - if ((error = git_diff_file_content__init_from_src( - ldata, repo, opts, oldsrc, lfile)) < 0 || - (error = git_diff_file_content__init_from_src( - rdata, repo, opts, newsrc, rfile)) < 0) - return error; - - return diff_single_generate(pd, xo); -} - -static int patch_generated_with_delta_alloc( - patch_generated_with_delta **out, - const char **old_path, - const char **new_path) -{ - patch_generated_with_delta *pd; - size_t old_len = *old_path ? strlen(*old_path) : 0; - size_t new_len = *new_path ? strlen(*new_path) : 0; - size_t alloc_len; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - *out = pd = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(pd); - - pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED; - - if (*old_path) { - memcpy(&pd->paths[0], *old_path, old_len); - *old_path = &pd->paths[0]; - } else if (*new_path) - *old_path = &pd->paths[old_len + 1]; - - if (*new_path) { - memcpy(&pd->paths[old_len + 1], *new_path, new_len); - *new_path = &pd->paths[old_len + 1]; - } else if (*old_path) - *new_path = &pd->paths[0]; - - return 0; -} - -static int diff_from_sources( - git_diff_file_content_src *oldsrc, - git_diff_file_content_src *newsrc, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - int error = 0; - patch_generated_with_delta pd; - git_xdiff_output xo; - - memset(&xo, 0, sizeof(xo)); - diff_output_init( - &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); - git_xdiff_init(&xo, opts); - - memset(&pd, 0, sizeof(pd)); - - error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts); - - git_patch_free(&pd.patch.base); - - return error; -} - -static int patch_from_sources( - git_patch **out, - git_diff_file_content_src *oldsrc, - git_diff_file_content_src *newsrc, - const git_diff_options *opts) -{ - int error = 0; - patch_generated_with_delta *pd; - git_xdiff_output xo; - - GIT_ASSERT_ARG(out); - *out = NULL; - - if ((error = patch_generated_with_delta_alloc( - &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) - return error; - - memset(&xo, 0, sizeof(xo)); - diff_output_to_patch(&xo.output, &pd->patch); - git_xdiff_init(&xo, opts); - - if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts))) - *out = (git_patch *)pd; - else - git_patch_free((git_patch *)pd); - - return error; -} - -int git_diff_blobs( - const git_blob *old_blob, - const char *old_path, - const git_blob *new_blob, - const char *new_path, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); - return diff_from_sources( - &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); -} - -int git_patch_from_blobs( - git_patch **out, - const git_blob *old_blob, - const char *old_path, - const git_blob *new_blob, - const char *new_path, - const git_diff_options *opts) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); - return patch_from_sources(out, &osrc, &nsrc, opts); -} - -int git_diff_blob_to_buffer( - const git_blob *old_blob, - const char *old_path, - const char *buf, - size_t buflen, - const char *buf_path, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); - return diff_from_sources( - &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); -} - -int git_patch_from_blob_and_buffer( - git_patch **out, - const git_blob *old_blob, - const char *old_path, - const void *buf, - size_t buflen, - const char *buf_path, - const git_diff_options *opts) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); - return patch_from_sources(out, &osrc, &nsrc, opts); -} - -int git_diff_buffers( - const void *old_buf, - size_t old_len, - const char *old_path, - const void *new_buf, - size_t new_len, - const char *new_path, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); - return diff_from_sources( - &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); -} - -int git_patch_from_buffers( - git_patch **out, - const void *old_buf, - size_t old_len, - const char *old_path, - const void *new_buf, - size_t new_len, - const char *new_path, - const git_diff_options *opts) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); - return patch_from_sources(out, &osrc, &nsrc, opts); -} - -int git_patch_generated_from_diff( - git_patch **patch_ptr, git_diff *diff, size_t idx) -{ - int error = 0; - git_xdiff_output xo; - git_diff_delta *delta = NULL; - git_patch_generated *patch = NULL; - - if (patch_ptr) *patch_ptr = NULL; - - if (diff_required(diff, "git_patch_from_diff") < 0) - return -1; - - delta = git_vector_get(&diff->deltas, idx); - if (!delta) { - git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff"); - return GIT_ENOTFOUND; - } - - if (git_diff_delta__should_skip(&diff->opts, delta)) - return 0; - - /* don't load the patch data unless we need it for binary check */ - if (!patch_ptr && - ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || - (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) - return 0; - - if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0) - return error; - - memset(&xo, 0, sizeof(xo)); - diff_output_to_patch(&xo.output, patch); - git_xdiff_init(&xo, &diff->opts); - - error = patch_generated_invoke_file_callback(patch, &xo.output); - - if (!error) - error = patch_generated_create(patch, &xo.output); - - if (!error) { - /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ - /* TODO: and unload the file content */ - } - - if (error || !patch_ptr) - git_patch_free(&patch->base); - else - *patch_ptr = &patch->base; - - return error; -} - -git_diff_driver *git_patch_generated_driver(git_patch_generated *patch) -{ - /* ofile driver is representative for whole patch */ - return patch->ofile.driver; -} - -int git_patch_generated_old_data( - char **ptr, long *len, git_patch_generated *patch) -{ - if (patch->ofile.map.len > LONG_MAX || - patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { - git_error_set(GIT_ERROR_INVALID, "files too large for diff"); - return -1; - } - - *ptr = patch->ofile.map.data; - *len = (long)patch->ofile.map.len; - - return 0; -} - -int git_patch_generated_new_data( - char **ptr, long *len, git_patch_generated *patch) -{ - if (patch->ofile.map.len > LONG_MAX || - patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { - git_error_set(GIT_ERROR_INVALID, "files too large for diff"); - return -1; - } - - *ptr = patch->nfile.map.data; - *len = (long)patch->nfile.map.len; - - return 0; -} - -static int patch_generated_file_cb( - const git_diff_delta *delta, - float progress, - void *payload) -{ - GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload); - return 0; -} - -static int patch_generated_binary_cb( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *payload) -{ - git_patch *patch = payload; - - GIT_UNUSED(delta); - - memcpy(&patch->binary, binary, sizeof(git_diff_binary)); - - if (binary->old_file.data) { - patch->binary.old_file.data = git__malloc(binary->old_file.datalen); - GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data); - - memcpy((char *)patch->binary.old_file.data, - binary->old_file.data, binary->old_file.datalen); - } - - if (binary->new_file.data) { - patch->binary.new_file.data = git__malloc(binary->new_file.datalen); - GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data); - - memcpy((char *)patch->binary.new_file.data, - binary->new_file.data, binary->new_file.datalen); - } - - return 0; -} - -static int git_patch_hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk_, - void *payload) -{ - git_patch_generated *patch = payload; - git_patch_hunk *hunk; - - GIT_UNUSED(delta); - - hunk = git_array_alloc(patch->base.hunks); - GIT_ERROR_CHECK_ALLOC(hunk); - - memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); - - patch->base.header_size += hunk_->header_len; - - hunk->line_start = git_array_size(patch->base.lines); - hunk->line_count = 0; - - return 0; -} - -static int patch_generated_line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk_, - const git_diff_line *line_, - void *payload) -{ - git_patch_generated *patch = payload; - git_patch_hunk *hunk; - git_diff_line *line; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk_); - - hunk = git_array_last(patch->base.hunks); - GIT_ASSERT(hunk); /* programmer error if no hunk is available */ - - line = git_array_alloc(patch->base.lines); - GIT_ERROR_CHECK_ALLOC(line); - - memcpy(line, line_, sizeof(*line)); - - /* do some bookkeeping so we can provide old/new line numbers */ - - patch->base.content_size += line->content_len; - - if (line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION) - patch->base.content_size += 1; - else if (line->origin == GIT_DIFF_LINE_CONTEXT) { - patch->base.content_size += 1; - patch->base.context_size += line->content_len + 1; - } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) - patch->base.context_size += line->content_len; - - hunk->line_count++; - - return 0; -} - -static void diff_output_init( - git_patch_generated_output *out, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - GIT_UNUSED(opts); - - memset(out, 0, sizeof(*out)); - - out->file_cb = file_cb; - out->binary_cb = binary_cb; - out->hunk_cb = hunk_cb; - out->data_cb = data_cb; - out->payload = payload; -} - -static void diff_output_to_patch( - git_patch_generated_output *out, git_patch_generated *patch) -{ - diff_output_init( - out, - NULL, - patch_generated_file_cb, - patch_generated_binary_cb, - git_patch_hunk_cb, - patch_generated_line_cb, - patch); -} diff --git a/src/patch_generate.h b/src/patch_generate.h deleted file mode 100644 index 56e3e9df4..000000000 --- a/src/patch_generate.h +++ /dev/null @@ -1,69 +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_patch_generate_h__ -#define INCLUDE_patch_generate_h__ - -#include "common.h" - -#include "diff.h" -#include "diff_file.h" -#include "patch.h" - -enum { - GIT_PATCH_GENERATED_ALLOCATED = (1 << 0), - GIT_PATCH_GENERATED_INITIALIZED = (1 << 1), - GIT_PATCH_GENERATED_LOADED = (1 << 2), - /* the two sides are different */ - GIT_PATCH_GENERATED_DIFFABLE = (1 << 3), - /* the difference between the two sides has been computed */ - GIT_PATCH_GENERATED_DIFFED = (1 << 4), - GIT_PATCH_GENERATED_FLATTENED = (1 << 5) -}; - -struct git_patch_generated { - struct git_patch base; - - git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ - size_t delta_index; - git_diff_file_content ofile; - git_diff_file_content nfile; - uint32_t flags; - git_pool flattened; -}; - -typedef struct git_patch_generated git_patch_generated; - -extern git_diff_driver *git_patch_generated_driver(git_patch_generated *); - -extern int git_patch_generated_old_data( - char **, long *, git_patch_generated *); -extern int git_patch_generated_new_data( - char **, long *, git_patch_generated *); -extern int git_patch_generated_from_diff( - git_patch **, git_diff *, size_t); - -typedef struct git_patch_generated_output git_patch_generated_output; - -struct git_patch_generated_output { - /* these callbacks are issued with the diff data */ - git_diff_file_cb file_cb; - git_diff_binary_cb binary_cb; - git_diff_hunk_cb hunk_cb; - git_diff_line_cb data_cb; - void *payload; - - /* this records the actual error in cases where it may be obscured */ - int error; - - /* this callback is used to do the diff and drive the other callbacks. - * see diff_xdiff.h for how to use this in practice for now. - */ - int (*diff_cb)(git_patch_generated_output *output, - git_patch_generated *patch); -}; - -#endif diff --git a/src/patch_parse.c b/src/patch_parse.c deleted file mode 100644 index 78cd96252..000000000 --- a/src/patch_parse.c +++ /dev/null @@ -1,1231 +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 "patch_parse.h" - -#include "git2/patch.h" -#include "patch.h" -#include "diff_parse.h" -#include "fs_path.h" - -typedef struct { - git_patch base; - - git_patch_parse_ctx *ctx; - - /* the paths from the `diff --git` header, these will be used if this is not - * a rename (and rename paths are specified) or if no `+++`/`---` line specify - * the paths. - */ - char *header_old_path, *header_new_path; - - /* renamed paths are precise and are not prefixed */ - char *rename_old_path, *rename_new_path; - - /* the paths given in `---` and `+++` lines */ - char *old_path, *new_path; - - /* the prefixes from the old/new paths */ - char *old_prefix, *new_prefix; -} git_patch_parsed; - -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, "\""); - size_t len; - - for (len = quoted; len < ctx->parse_ctx.line_len; len++) { - if (!quoted && git__isspace(ctx->parse_ctx.line[len])) - break; - else if (quoted && !inquote && ctx->parse_ctx.line[len] == '"') { - len++; - break; - } - - inquote = (!inquote && ctx->parse_ctx.line[len] == '\\'); - } - - return len; -} - -static int parse_header_path_buf(git_str *path, git_patch_parse_ctx *ctx, size_t path_len) -{ - int error; - - if ((error = git_str_put(path, ctx->parse_ctx.line, path_len)) < 0) - return error; - - git_parse_advance_chars(&ctx->parse_ctx, path_len); - - git_str_rtrim(path); - - if (path->size > 0 && path->ptr[0] == '"' && - (error = git_str_unquote(path)) < 0) - return error; - - git_fs_path_squash_slashes(path); - - 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_str path = GIT_STR_INIT; - int error; - - if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0) - goto out; - *out = git_str_detach(&path); - -out: - git_str_dispose(&path); - return error; -} - -static int parse_header_git_oldpath( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - git_str old_path = GIT_STR_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; - - patch->old_path = git_str_detach(&old_path); - -out: - git_str_dispose(&old_path); - return error; -} - -static int parse_header_git_newpath( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - git_str new_path = GIT_STR_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_str_detach(&new_path); - -out: - git_str_dispose(&new_path); - return error; -} - -static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) -{ - int64_t m; - - if ((git_parse_advance_digit(&m, &ctx->parse_ctx, 8)) < 0) - return git_parse_err("invalid file mode at line %"PRIuZ, ctx->parse_ctx.line_num); - - if (m > UINT16_MAX) - return -1; - - *mode = (uint16_t)m; - - return 0; -} - -static int parse_header_oid( - git_oid *oid, - uint16_t *oid_len, - git_patch_parse_ctx *ctx) -{ - size_t len; - - for (len = 0; len < ctx->parse_ctx.line_len && len < GIT_OID_HEXSZ; len++) { - if (!git__isxdigit(ctx->parse_ctx.line[len])) - break; - } - - if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || - git_oid_fromstrn(oid, ctx->parse_ctx.line, len) < 0) - return git_parse_err("invalid hex formatted object id at line %"PRIuZ, - ctx->parse_ctx.line_num); - - git_parse_advance_chars(&ctx->parse_ctx, len); - - *oid_len = (uint16_t)len; - - return 0; -} - -static int parse_header_git_index( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - char c; - - if (parse_header_oid(&patch->base.delta->old_file.id, - &patch->base.delta->old_file.id_abbrev, ctx) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, "..") < 0 || - parse_header_oid(&patch->base.delta->new_file.id, - &patch->base.delta->new_file.id_abbrev, ctx) < 0) - return -1; - - if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ' ') { - uint16_t mode = 0; - - git_parse_advance_chars(&ctx->parse_ctx, 1); - - if (parse_header_mode(&mode, ctx) < 0) - return -1; - - if (!patch->base.delta->new_file.mode) - patch->base.delta->new_file.mode = mode; - - if (!patch->base.delta->old_file.mode) - patch->base.delta->old_file.mode = mode; - } - - return 0; -} - -static int parse_header_git_oldmode( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - return parse_header_mode(&patch->base.delta->old_file.mode, ctx); -} - -static int parse_header_git_newmode( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - return parse_header_mode(&patch->base.delta->new_file.mode, ctx); -} - -static int parse_header_git_deletedfilemode( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - git__free((char *)patch->base.delta->new_file.path); - - patch->base.delta->new_file.path = NULL; - patch->base.delta->status = GIT_DELTA_DELETED; - patch->base.delta->nfiles = 1; - - return parse_header_mode(&patch->base.delta->old_file.mode, ctx); -} - -static int parse_header_git_newfilemode( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - git__free((char *)patch->base.delta->old_file.path); - - patch->base.delta->old_file.path = NULL; - patch->base.delta->status = GIT_DELTA_ADDED; - patch->base.delta->nfiles = 1; - - return parse_header_mode(&patch->base.delta->new_file.mode, ctx); -} - -static int parse_header_rename( - char **out, - git_patch_parse_ctx *ctx) -{ - git_str path = GIT_STR_INIT; - - if (parse_header_path_buf(&path, ctx, header_path_len(ctx)) < 0) - return -1; - - /* Note: the `rename from` and `rename to` lines include the literal - * filename. They do *not* include the prefix. (Who needs consistency?) - */ - *out = git_str_detach(&path); - return 0; -} - -static int parse_header_renamefrom( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_RENAMED; - return parse_header_rename(&patch->rename_old_path, ctx); -} - -static int parse_header_renameto( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_RENAMED; - return parse_header_rename(&patch->rename_new_path, ctx); -} - -static int parse_header_copyfrom( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_COPIED; - return parse_header_rename(&patch->rename_old_path, ctx); -} - -static int parse_header_copyto( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_COPIED; - return parse_header_rename(&patch->rename_new_path, ctx); -} - -static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) -{ - int64_t val; - - if (git_parse_advance_digit(&val, &ctx->parse_ctx, 10) < 0) - return -1; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "%") < 0) - return -1; - - if (val < 0 || val > 100) - return -1; - - *out = (uint16_t)val; - return 0; -} - -static int parse_header_similarity( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) - return git_parse_err("invalid similarity percentage at line %"PRIuZ, - ctx->parse_ctx.line_num); - - return 0; -} - -static int parse_header_dissimilarity( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - uint16_t dissimilarity; - - if (parse_header_percent(&dissimilarity, ctx) < 0) - return git_parse_err("invalid similarity percentage at line %"PRIuZ, - ctx->parse_ctx.line_num); - - patch->base.delta->similarity = 100 - dissimilarity; - - return 0; -} - -static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - if (parse_header_path(&patch->header_old_path, ctx) < 0) - return git_parse_err("corrupt old path in git diff header at line %"PRIuZ, - ctx->parse_ctx.line_num); - - if (git_parse_advance_ws(&ctx->parse_ctx) < 0 || - parse_header_path(&patch->header_new_path, ctx) < 0) - return git_parse_err("corrupt new path in git diff header at line %"PRIuZ, - ctx->parse_ctx.line_num); - - /* - * We cannot expect to be able to always parse paths correctly at this - * point. Due to the possibility of unquoted names, whitespaces in - * filenames and custom prefixes we have to allow that, though, and just - * proceed here. We then hope for the "---" and "+++" lines to fix that - * for us. - */ - if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1) && - !git_parse_ctx_contains(&ctx->parse_ctx, "\r\n", 2)) { - git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); - - git__free(patch->header_old_path); - patch->header_old_path = NULL; - git__free(patch->header_new_path); - patch->header_new_path = NULL; - } - - return 0; -} - -typedef enum { - STATE_START, - - STATE_DIFF, - STATE_FILEMODE, - STATE_MODE, - STATE_INDEX, - STATE_PATH, - - STATE_SIMILARITY, - STATE_RENAME, - STATE_COPY, - - STATE_END -} parse_header_state; - -typedef struct { - const char *str; - parse_header_state expected_state; - parse_header_state next_state; - int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); -} parse_header_transition; - -static const parse_header_transition transitions[] = { - /* Start */ - { "diff --git " , STATE_START, STATE_DIFF, parse_header_start }, - - { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode }, - { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode }, - { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode }, - { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode }, - - { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, - { "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_FILEMODE, STATE_PATH, parse_header_git_oldpath }, - { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, - { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, - { "Binary files " , STATE_INDEX, STATE_END, NULL }, - - { "similarity index " , STATE_END, STATE_SIMILARITY, parse_header_similarity }, - { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity }, - { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity }, - { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, - { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, - { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom }, - { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto }, - { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto }, - { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, - - /* Next patch */ - { "diff --git " , STATE_END, 0, NULL }, - { "@@ -" , STATE_END, 0, NULL }, - { "-- " , STATE_INDEX, 0, NULL }, - { "-- " , STATE_END, 0, NULL }, -}; - -static int parse_header_git( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - size_t i; - int error = 0; - parse_header_state state = STATE_START; - - /* Parse remaining header lines */ - for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { - bool found = false; - - if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') - break; - - for (i = 0; i < ARRAY_SIZE(transitions); i++) { - const parse_header_transition *transition = &transitions[i]; - size_t op_len = strlen(transition->str); - - if (transition->expected_state != state || - git__prefixcmp(ctx->parse_ctx.line, transition->str) != 0) - continue; - - state = transition->next_state; - - /* Do not advance if this is the patch separator */ - if (transition->fn == NULL) - goto done; - - git_parse_advance_chars(&ctx->parse_ctx, op_len); - - if ((error = transition->fn(patch, ctx)) < 0) - goto done; - - git_parse_advance_ws(&ctx->parse_ctx); - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "\n") < 0 || - ctx->parse_ctx.line_len > 0) { - error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - found = true; - break; - } - - if (!found) { - error = git_parse_err("invalid patch header at line %"PRIuZ, - ctx->parse_ctx.line_num); - goto done; - } - } - - if (state != STATE_END) { - error = git_parse_err("unexpected header line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - -done: - return error; -} - -static int parse_int(int *out, git_patch_parse_ctx *ctx) -{ - int64_t num; - - if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) - return -1; - - *out = (int)num; - return 0; -} - -static int parse_hunk_header( - git_patch_hunk *hunk, - git_patch_parse_ctx *ctx) -{ - const char *header_start = ctx->parse_ctx.line; - char c; - - hunk->hunk.old_lines = 1; - hunk->hunk.new_lines = 1; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "@@ -") < 0 || - parse_int(&hunk->hunk.old_start, ctx) < 0) - goto fail; - - if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { - if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || - parse_int(&hunk->hunk.old_lines, ctx) < 0) - goto fail; - } - - if (git_parse_advance_expected_str(&ctx->parse_ctx, " +") < 0 || - parse_int(&hunk->hunk.new_start, ctx) < 0) - goto fail; - - if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { - if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || - parse_int(&hunk->hunk.new_lines, ctx) < 0) - goto fail; - } - - if (git_parse_advance_expected_str(&ctx->parse_ctx, " @@") < 0) - goto fail; - - git_parse_advance_line(&ctx->parse_ctx); - - if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) - goto fail; - - hunk->hunk.header_len = ctx->parse_ctx.line - header_start; - if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) - return git_parse_err("oversized patch hunk header at line %"PRIuZ, - ctx->parse_ctx.line_num); - - memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); - hunk->hunk.header[hunk->hunk.header_len] = '\0'; - - return 0; - -fail: - git_error_set(GIT_ERROR_PATCH, "invalid patch hunk header at line %"PRIuZ, - ctx->parse_ctx.line_num); - 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, - git_patch_parse_ctx *ctx) -{ - git_diff_line *line; - int error = 0; - - int oldlines = hunk->hunk.old_lines; - int newlines = hunk->hunk.new_lines; - int last_origin = 0; - - for (; - ctx->parse_ctx.remain_len > 1 && - (oldlines || newlines) && - !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; - - 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, - ctx->parse_ctx.line_num); - goto done; - } - - git_parse_peek(&c, &ctx->parse_ctx, 0); - - switch (c) { - case '\n': - prefix = 0; - /* fall through */ - - case ' ': - origin = GIT_DIFF_LINE_CONTEXT; - oldlines--; - newlines--; - break; - - case '-': - origin = GIT_DIFF_LINE_DELETION; - oldlines--; - new_lineno = -1; - break; - - case '+': - origin = GIT_DIFF_LINE_ADDITION; - newlines--; - 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; - } - - 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 - 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; - line->old_lineno = old_lineno; - line->new_lineno = new_lineno; - - hunk->line_count++; - - last_origin = origin; - } - - if (oldlines || newlines) { - error = git_parse_err( - "invalid patch hunk, expected %d old lines and %d new lines", - hunk->hunk.old_lines, hunk->hunk.new_lines); - goto done; - } - - /* - * 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. - */ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && - git_array_size(patch->base.lines) > 0) { - - line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); - - if (line->content_len < 1) { - error = git_parse_err("last line has no trailing newline"); - goto done; - } - - 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); - } - -done: - return error; -} - -static int parse_patch_header( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - int error = 0; - - for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { - /* This line is too short to be a patch header. */ - if (ctx->parse_ctx.line_len < 6) - continue; - - /* This might be a hunk header without a patch header, provide a - * sensible error message. */ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { - size_t line_num = ctx->parse_ctx.line_num; - git_patch_hunk hunk; - - /* If this cannot be parsed as a hunk header, it's just leading - * noise, continue. - */ - if (parse_hunk_header(&hunk, ctx) < 0) { - git_error_clear(); - continue; - } - - error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, - line_num); - goto done; - } - - /* This buffer is too short to contain a patch. */ - if (ctx->parse_ctx.remain_len < ctx->parse_ctx.line_len + 6) - break; - - /* A proper git patch */ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "diff --git ")) { - error = parse_header_git(patch, ctx); - goto done; - } - - error = 0; - continue; - } - - git_error_set(GIT_ERROR_PATCH, "no patch found"); - error = GIT_ENOTFOUND; - -done: - return error; -} - -static int parse_patch_binary_side( - git_diff_binary_file *binary, - git_patch_parse_ctx *ctx) -{ - git_diff_binary_t type = GIT_DIFF_BINARY_NONE; - git_str base85 = GIT_STR_INIT, decoded = GIT_STR_INIT; - int64_t len; - int error = 0; - - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { - type = GIT_DIFF_BINARY_LITERAL; - git_parse_advance_chars(&ctx->parse_ctx, 8); - } else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "delta ")) { - type = GIT_DIFF_BINARY_DELTA; - git_parse_advance_chars(&ctx->parse_ctx, 6); - } else { - error = git_parse_err( - "unknown binary delta type at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - if (git_parse_advance_digit(&len, &ctx->parse_ctx, 10) < 0 || - git_parse_advance_nl(&ctx->parse_ctx) < 0 || len < 0) { - error = git_parse_err("invalid binary size at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - while (ctx->parse_ctx.line_len) { - char c; - size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; - - git_parse_peek(&c, &ctx->parse_ctx, 0); - - if (c == '\n') - break; - else if (c >= 'A' && c <= 'Z') - decoded_len = c - 'A' + 1; - else if (c >= 'a' && c <= 'z') - decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; - - if (!decoded_len) { - error = git_parse_err("invalid binary length at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - git_parse_advance_chars(&ctx->parse_ctx, 1); - - encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; - - 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; - } - - if ((error = git_str_decode_base85( - &decoded, ctx->parse_ctx.line, encoded_len, decoded_len)) < 0) - goto done; - - if (decoded.size - decoded_orig != decoded_len) { - error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - git_parse_advance_chars(&ctx->parse_ctx, encoded_len); - - if (git_parse_advance_nl(&ctx->parse_ctx) < 0) { - error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - } - - binary->type = type; - binary->inflatedlen = (size_t)len; - binary->datalen = decoded.size; - binary->data = git_str_detach(&decoded); - -done: - git_str_dispose(&base85); - git_str_dispose(&decoded); - return error; -} - -static int parse_patch_binary( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - int error; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "GIT binary patch") < 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); - - /* parse old->new binary diff */ - if ((error = parse_patch_binary_side( - &patch->base.binary.new_file, ctx)) < 0) - return error; - - if (git_parse_advance_nl(&ctx->parse_ctx) < 0) - return git_parse_err("corrupt git binary separator at line %"PRIuZ, - ctx->parse_ctx.line_num); - - /* parse new->old binary diff */ - if ((error = parse_patch_binary_side( - &patch->base.binary.old_file, ctx)) < 0) - return error; - - if (git_parse_advance_nl(&ctx->parse_ctx) < 0) - return git_parse_err("corrupt git binary patch separator at line %"PRIuZ, - ctx->parse_ctx.line_num); - - patch->base.binary.contains_data = 1; - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - return 0; -} - -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, 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; - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - return 0; -} - -static int parse_patch_hunks( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - git_patch_hunk *hunk; - int error = 0; - - while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { - hunk = git_array_alloc(patch->base.hunks); - GIT_ERROR_CHECK_ALLOC(hunk); - - memset(hunk, 0, sizeof(git_patch_hunk)); - - hunk->line_start = git_array_size(patch->base.lines); - hunk->line_count = 0; - - if ((error = parse_hunk_header(hunk, ctx)) < 0 || - (error = parse_hunk_body(patch, hunk, ctx)) < 0) - goto done; - } - - patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; - -done: - return error; -} - -static int parse_patch_body( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "GIT binary patch")) - return parse_patch_binary(patch, ctx); - else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "Binary files ")) - return parse_patch_binary_nodata(patch, ctx); - else - return parse_patch_hunks(patch, ctx); -} - -static int check_header_names( - const char *one, - const char *two, - const char *old_or_new, - bool two_null) -{ - if (!one || !two) - return 0; - - if (two_null && strcmp(two, "/dev/null") != 0) - return git_parse_err("expected %s path of '/dev/null'", old_or_new); - - else if (!two_null && strcmp(one, two) != 0) - return git_parse_err("mismatched %s path names", old_or_new); - - return 0; -} - -static int check_prefix( - char **out, - size_t *out_len, - git_patch_parsed *patch, - const char *path_start) -{ - const char *path = path_start; - size_t prefix_len = patch->ctx->opts.prefix_len; - size_t remain_len = prefix_len; - - *out = NULL; - *out_len = 0; - - if (prefix_len == 0) - goto done; - - /* leading slashes do not count as part of the prefix in git apply */ - while (*path == '/') - path++; - - while (*path && remain_len) { - if (*path == '/') - remain_len--; - - path++; - } - - if (remain_len || !*path) - return git_parse_err( - "header filename does not contain %"PRIuZ" path components", - prefix_len); - -done: - *out_len = (path - path_start); - *out = git__strndup(path_start, *out_len); - - return (*out == NULL) ? -1 : 0; -} - -static int check_filenames(git_patch_parsed *patch) -{ - const char *prefixed_new, *prefixed_old; - size_t old_prefixlen = 0, new_prefixlen = 0; - bool added = (patch->base.delta->status == GIT_DELTA_ADDED); - bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); - - if (patch->old_path && !patch->new_path) - return git_parse_err("missing new path"); - - if (!patch->old_path && patch->new_path) - return git_parse_err("missing old path"); - - /* Ensure (non-renamed) paths match */ - if (check_header_names(patch->header_old_path, patch->old_path, "old", added) < 0 || - check_header_names(patch->header_new_path, patch->new_path, "new", deleted) < 0) - return -1; - - prefixed_old = (!added && patch->old_path) ? patch->old_path : patch->header_old_path; - prefixed_new = (!deleted && patch->new_path) ? patch->new_path : patch->header_new_path; - - if ((prefixed_old && check_prefix(&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0) || - (prefixed_new && check_prefix(&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)) - return -1; - - /* 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 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 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) - return git_parse_err("git diff header lacks old / new paths"); - - return 0; -} - -static int check_patch(git_patch_parsed *patch) -{ - git_diff_delta *delta = patch->base.delta; - - if (check_filenames(patch) < 0) - return -1; - - if (delta->old_file.path && - delta->status != GIT_DELTA_DELETED && - !delta->new_file.mode) - delta->new_file.mode = delta->old_file.mode; - - if (delta->status == GIT_DELTA_MODIFIED && - !(delta->flags & GIT_DIFF_FLAG_BINARY) && - delta->new_file.mode == delta->old_file.mode && - git_array_size(patch->base.hunks) == 0) - return git_parse_err("patch with no hunks"); - - if (delta->status == GIT_DELTA_ADDED) { - memset(&delta->old_file.id, 0x0, sizeof(git_oid)); - delta->old_file.id_abbrev = 0; - } - - if (delta->status == GIT_DELTA_DELETED) { - memset(&delta->new_file.id, 0x0, sizeof(git_oid)); - delta->new_file.id_abbrev = 0; - } - - return 0; -} - -git_patch_parse_ctx *git_patch_parse_ctx_init( - const char *content, - size_t content_len, - const git_patch_options *opts) -{ - git_patch_parse_ctx *ctx; - git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; - - if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) - return NULL; - - if ((git_parse_ctx_init(&ctx->parse_ctx, content, content_len)) < 0) { - git__free(ctx); - return NULL; - } - - if (opts) - memcpy(&ctx->opts, opts, sizeof(git_patch_options)); - else - memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options)); - - GIT_REFCOUNT_INC(ctx); - return ctx; -} - -static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) -{ - if (!ctx) - return; - - git_parse_ctx_clear(&ctx->parse_ctx); - git__free(ctx); -} - -void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx) -{ - GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free); -} - -int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) -{ - git_diff_parsed *diff = (git_diff_parsed *)d; - git_patch *p; - - if ((p = git_vector_get(&diff->patches, idx)) == NULL) - return -1; - - GIT_REFCOUNT_INC(p); - *out = p; - - return 0; -} - -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; - - git_patch_parse_ctx_free(patch->ctx); - - 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); - - git__free(patch->old_prefix); - git__free(patch->new_prefix); - git__free(patch->header_old_path); - git__free(patch->header_new_path); - git__free(patch->rename_old_path); - git__free(patch->rename_new_path); - git__free(patch->old_path); - git__free(patch->new_path); - git__free(patch); -} - -int git_patch_parse( - git_patch **out, - git_patch_parse_ctx *ctx) -{ - git_patch_parsed *patch; - size_t start, used; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ctx); - - *out = NULL; - - patch = git__calloc(1, sizeof(git_patch_parsed)); - GIT_ERROR_CHECK_ALLOC(patch); - - patch->ctx = ctx; - GIT_REFCOUNT_INC(patch->ctx); - - patch->base.free_fn = patch_parsed__free; - - patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); - GIT_ERROR_CHECK_ALLOC(patch->base.delta); - - patch->base.delta->status = GIT_DELTA_MODIFIED; - patch->base.delta->nfiles = 2; - - start = ctx->parse_ctx.remain_len; - - if ((error = parse_patch_header(patch, ctx)) < 0 || - (error = parse_patch_body(patch, ctx)) < 0 || - (error = check_patch(patch)) < 0) - goto done; - - used = start - ctx->parse_ctx.remain_len; - ctx->parse_ctx.remain += used; - - patch->base.diff_opts.old_prefix = patch->old_prefix; - patch->base.diff_opts.new_prefix = patch->new_prefix; - patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; - - GIT_REFCOUNT_INC(&patch->base); - *out = &patch->base; - -done: - if (error < 0) - patch_parsed__free(&patch->base); - - return error; -} - -int git_patch_from_buffer( - git_patch **out, - const char *content, - size_t content_len, - const git_patch_options *opts) -{ - git_patch_parse_ctx *ctx; - int error; - - ctx = git_patch_parse_ctx_init(content, content_len, opts); - GIT_ERROR_CHECK_ALLOC(ctx); - - error = git_patch_parse(out, ctx); - - git_patch_parse_ctx_free(ctx); - return error; -} - diff --git a/src/patch_parse.h b/src/patch_parse.h deleted file mode 100644 index 140629da8..000000000 --- a/src/patch_parse.h +++ /dev/null @@ -1,51 +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_patch_parse_h__ -#define INCLUDE_patch_parse_h__ - -#include "common.h" - -#include "parse.h" -#include "patch.h" - -typedef struct { - git_refcount rc; - - git_patch_options opts; - - git_parse_ctx parse_ctx; -} git_patch_parse_ctx; - -extern git_patch_parse_ctx *git_patch_parse_ctx_init( - const char *content, - size_t content_len, - const git_patch_options *opts); - -extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx); - -/** - * Create a patch for a single file from the contents of a patch buffer. - * - * @param out The patch to be created - * @param contents The contents of a patch file - * @param contents_len The length of the patch file - * @param opts The git_patch_options - * @return 0 on success, <0 on failure. - */ -extern int git_patch_from_buffer( - git_patch **out, - const char *contents, - size_t contents_len, - const git_patch_options *opts); - -extern int git_patch_parse( - git_patch **out, - git_patch_parse_ctx *ctx); - -extern int git_patch_parsed_from_diff(git_patch **, git_diff *, size_t); - -#endif diff --git a/src/path.c b/src/path.c deleted file mode 100644 index 05a3dc2cf..000000000 --- a/src/path.c +++ /dev/null @@ -1,374 +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 "path.h" - -#include "repository.h" -#include "fs_path.h" - -typedef struct { - git_repository *repo; - uint16_t file_mode; - unsigned int flags; -} repository_path_validate_data; - -static int32_t next_hfs_char(const char **in, size_t *len) -{ - while (*len) { - uint32_t codepoint; - int cp_len = git_utf8_iterate(&codepoint, *in, *len); - if (cp_len < 0) - return -1; - - (*in) += cp_len; - (*len) -= cp_len; - - /* these code points are ignored completely */ - switch (codepoint) { - case 0x200c: /* ZERO WIDTH NON-JOINER */ - case 0x200d: /* ZERO WIDTH JOINER */ - case 0x200e: /* LEFT-TO-RIGHT MARK */ - case 0x200f: /* RIGHT-TO-LEFT MARK */ - case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ - case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ - case 0x202c: /* POP DIRECTIONAL FORMATTING */ - case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ - case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ - case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ - case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ - case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ - case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ - case 0x206e: /* NATIONAL DIGIT SHAPES */ - case 0x206f: /* NOMINAL DIGIT SHAPES */ - case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ - continue; - } - - /* fold into lowercase -- this will only fold characters in - * the ASCII range, which is perfectly fine, because the - * git folder name can only be composed of ascii characters - */ - return git__tolower((int)codepoint); - } - return 0; /* NULL byte -- end of string */ -} - -static bool validate_dotgit_hfs_generic( - const char *path, - size_t len, - const char *needle, - size_t needle_len) -{ - size_t i; - char c; - - if (next_hfs_char(&path, &len) != '.') - return true; - - for (i = 0; i < needle_len; i++) { - c = next_hfs_char(&path, &len); - if (c != needle[i]) - return true; - } - - if (next_hfs_char(&path, &len) != '\0') - return true; - - return false; -} - -static bool validate_dotgit_hfs(const char *path, size_t len) -{ - return validate_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git")); -} - -GIT_INLINE(bool) validate_dotgit_ntfs( - git_repository *repo, - const char *path, - size_t len) -{ - git_str *reserved = git_repository__reserved_names_win32; - size_t reserved_len = git_repository__reserved_names_win32_len; - size_t start = 0, i; - - if (repo) - git_repository__reserved_names(&reserved, &reserved_len, repo, true); - - for (i = 0; i < reserved_len; i++) { - git_str *r = &reserved[i]; - - if (len >= r->size && - strncasecmp(path, r->ptr, r->size) == 0) { - start = r->size; - break; - } - } - - if (!start) - return true; - - /* - * Reject paths that start with Windows-style directory separators - * (".git\") or NTFS alternate streams (".git:") and could be used - * to write to the ".git" directory on Windows platforms. - */ - if (path[start] == '\\' || path[start] == ':') - return false; - - /* Reject paths like '.git ' or '.git.' */ - for (i = start; i < len; i++) { - if (path[i] != ' ' && path[i] != '.') - return true; - } - - return false; -} - -/* - * Windows paths that end with spaces and/or dots are elided to the - * path without them for backward compatibility. That is to say - * that opening file "foo ", "foo." or even "foo . . ." will all - * map to a filename of "foo". This function identifies spaces and - * dots at the end of a filename, whether the proper end of the - * filename (end of string) or a colon (which would indicate a - * Windows alternate data stream.) - */ -GIT_INLINE(bool) ntfs_end_of_filename(const char *path) -{ - const char *c = path; - - for (;; c++) { - if (*c == '\0' || *c == ':') - return true; - if (*c != ' ' && *c != '.') - return false; - } - - return true; -} - -GIT_INLINE(bool) validate_dotgit_ntfs_generic( - const char *name, - size_t len, - const char *dotgit_name, - size_t dotgit_len, - const char *shortname_pfix) -{ - int i, saw_tilde; - - if (name[0] == '.' && len >= dotgit_len && - !strncasecmp(name + 1, dotgit_name, dotgit_len)) { - return !ntfs_end_of_filename(name + dotgit_len + 1); - } - - /* Detect the basic NTFS shortname with the first six chars */ - if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' && - name[7] >= '1' && name[7] <= '4') - return !ntfs_end_of_filename(name + 8); - - /* Catch fallback names */ - for (i = 0, saw_tilde = 0; i < 8; i++) { - if (name[i] == '\0') { - return true; - } else if (saw_tilde) { - if (name[i] < '0' || name[i] > '9') - return true; - } else if (name[i] == '~') { - if (name[i+1] < '1' || name[i+1] > '9') - return true; - saw_tilde = 1; - } else if (i >= 6) { - return true; - } else if ((unsigned char)name[i] > 127) { - return true; - } else if (git__tolower(name[i]) != shortname_pfix[i]) { - return true; - } - } - - return !ntfs_end_of_filename(name + i); -} - -/* - * Return the length of the common prefix between str and prefix, comparing them - * case-insensitively (must be ASCII to match). - */ -GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix) -{ - size_t count = 0; - - while (len > 0 && tolower(*str) == tolower(*prefix)) { - count++; - str++; - prefix++; - len--; - } - - return count; -} - -static bool validate_repo_component( - const char *component, - size_t len, - void *payload) -{ - repository_path_validate_data *data = (repository_path_validate_data *)payload; - - if (data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) { - if (!validate_dotgit_hfs(component, len)) - return false; - - if (S_ISLNK(data->file_mode) && - git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)) - return false; - } - - if (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) { - if (!validate_dotgit_ntfs(data->repo, component, len)) - return false; - - if (S_ISLNK(data->file_mode) && - git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS)) - return false; - } - - /* don't bother rerunning the `.git` test if we ran the HFS or NTFS - * specific tests, they would have already rejected `.git`. - */ - if ((data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && - (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && - (data->flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) { - if (len >= 4 && - component[0] == '.' && - (component[1] == 'g' || component[1] == 'G') && - (component[2] == 'i' || component[2] == 'I') && - (component[3] == 't' || component[3] == 'T')) { - if (len == 4) - return false; - - if (S_ISLNK(data->file_mode) && - common_prefix_icase(component, len, ".gitmodules") == len) - return false; - } - } - - return true; -} - -GIT_INLINE(unsigned int) dotgit_flags( - git_repository *repo, - unsigned int flags) -{ - int protectHFS = 0, protectNTFS = 1; - int error = 0; - - flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; - -#ifdef __APPLE__ - protectHFS = 1; -#endif - - if (repo && !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__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS); - if (!error && protectNTFS) - flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; - - return flags; -} - -GIT_INLINE(unsigned int) length_flags( - git_repository *repo, - unsigned int flags) -{ -#ifdef GIT_WIN32 - int allow = 0; - - if (repo && - git_repository__configmap_lookup(&allow, repo, GIT_CONFIGMAP_LONGPATHS) < 0) - allow = 0; - - if (allow) - flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; - -#else - GIT_UNUSED(repo); - flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; -#endif - - return flags; -} - -bool git_path_str_is_valid( - git_repository *repo, - const git_str *path, - uint16_t file_mode, - unsigned int flags) -{ - repository_path_validate_data data = {0}; - - /* Upgrade the ".git" checks based on platform */ - if ((flags & GIT_PATH_REJECT_DOT_GIT)) - flags = dotgit_flags(repo, flags); - - /* Update the length checks based on platform */ - if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS)) - flags = length_flags(repo, flags); - - data.repo = repo; - data.file_mode = file_mode; - data.flags = flags; - - return git_fs_path_str_is_valid_ext(path, flags, NULL, validate_repo_component, NULL, &data); -} - -static const struct { - const char *file; - const char *hash; - size_t filelen; -} gitfiles[] = { - { "gitignore", "gi250a", CONST_STRLEN("gitignore") }, - { "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") }, - { "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") } -}; - -extern int git_path_is_gitfile( - const char *path, - size_t pathlen, - git_path_gitfile gitfile, - git_path_fs fs) -{ - const char *file, *hash; - size_t filelen; - - if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) { - git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation"); - return -1; - } - - file = gitfiles[gitfile].file; - filelen = gitfiles[gitfile].filelen; - hash = gitfiles[gitfile].hash; - - switch (fs) { - case GIT_PATH_FS_GENERIC: - return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) || - !validate_dotgit_hfs_generic(path, pathlen, file, filelen); - case GIT_PATH_FS_NTFS: - return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash); - case GIT_PATH_FS_HFS: - return !validate_dotgit_hfs_generic(path, pathlen, file, filelen); - default: - git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation"); - return -1; - } -} - diff --git a/src/path.h b/src/path.h deleted file mode 100644 index c4a2c4250..000000000 --- a/src/path.h +++ /dev/null @@ -1,68 +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_path_h__ -#define INCLUDE_path_h__ - -#include "common.h" - -#include "fs_path.h" -#include - -#define GIT_PATH_REJECT_DOT_GIT (GIT_FS_PATH_REJECT_MAX << 1) -#define GIT_PATH_REJECT_DOT_GIT_LITERAL (GIT_FS_PATH_REJECT_MAX << 2) -#define GIT_PATH_REJECT_DOT_GIT_HFS (GIT_FS_PATH_REJECT_MAX << 3) -#define GIT_PATH_REJECT_DOT_GIT_NTFS (GIT_FS_PATH_REJECT_MAX << 4) - -/* Paths that should never be written into the working directory. */ -#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \ - GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT - -/* Paths that should never be written to the index. */ -#define GIT_PATH_REJECT_INDEX_DEFAULTS \ - GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT - -extern bool git_path_str_is_valid( - git_repository *repo, - const git_str *path, - uint16_t file_mode, - unsigned int flags); - -GIT_INLINE(bool) git_path_is_valid( - git_repository *repo, - const char *path, - uint16_t file_mode, - unsigned int flags) -{ - git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_path_str_is_valid(repo, &str, file_mode, flags); -} - -GIT_INLINE(int) git_path_validate_str_length( - git_repository *repo, - const git_str *path) -{ - if (!git_path_str_is_valid(repo, path, 0, GIT_FS_PATH_REJECT_LONG_PATHS)) { - if (path->size == SIZE_MAX) - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path->ptr); - else - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", (int)path->size, path->ptr); - - return -1; - } - - return 0; -} - -GIT_INLINE(int) git_path_validate_length( - git_repository *repo, - const char *path) -{ - git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_path_validate_str_length(repo, &str); -} - -#endif diff --git a/src/pathspec.c b/src/pathspec.c deleted file mode 100644 index 3e44643c6..000000000 --- a/src/pathspec.c +++ /dev/null @@ -1,722 +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 "pathspec.h" - -#include "git2/pathspec.h" -#include "git2/diff.h" -#include "attr_file.h" -#include "iterator.h" -#include "repository.h" -#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) -{ - git_str prefix = GIT_STR_INIT; - const char *scan; - - if (!pathspec || !pathspec->count || - git_str_common_prefix(&prefix, pathspec->strings, pathspec->count) < 0) - return NULL; - - /* diff prefix will only be leading non-wildcards */ - for (scan = prefix.ptr; *scan; ++scan) { - if (git__iswildcard(*scan) && - (scan == prefix.ptr || (*(scan - 1) != '\\'))) - break; - } - git_str_truncate(&prefix, scan - prefix.ptr); - - if (prefix.size <= 0) { - git_str_dispose(&prefix); - return NULL; - } - - git_str_unescape(&prefix); - - return git_str_detach(&prefix); -} - -/* is there anything in the spec that needs to be filtered on */ -bool git_pathspec_is_empty(const git_strarray *pathspec) -{ - size_t i; - - if (pathspec == NULL) - return true; - - for (i = 0; i < pathspec->count; ++i) { - const char *str = pathspec->strings[i]; - - if (str && str[0]) - return false; - } - - return true; -} - -/* build a vector of fnmatch patterns to evaluate efficiently */ -int git_pathspec__vinit( - git_vector *vspec, const git_strarray *strspec, git_pool *strpool) -{ - size_t i; - - memset(vspec, 0, sizeof(*vspec)); - - if (git_pathspec_is_empty(strspec)) - return 0; - - if (git_vector_init(vspec, strspec->count, NULL) < 0) - return -1; - - for (i = 0; i < strspec->count; ++i) { - int ret; - const char *pattern = strspec->strings[i]; - git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); - if (!match) - return -1; - - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; - - ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); - if (ret == GIT_ENOTFOUND) { - git__free(match); - continue; - } else if (ret < 0) { - git__free(match); - return ret; - } - - if (git_vector_insert(vspec, match) < 0) - return -1; - } - - return 0; -} - -/* free data from the pathspec vector */ -void git_pathspec__vfree(git_vector *vspec) -{ - git_vector_free_deep(vspec); -} - -struct pathspec_match_context { - int wildmatch_flags; - int (*strcomp)(const char *, const char *); - int (*strncomp)(const char *, const char *, size_t); -}; - -static void pathspec_match_context_init( - struct pathspec_match_context *ctxt, - bool disable_fnmatch, - bool casefold) -{ - if (disable_fnmatch) - ctxt->wildmatch_flags = -1; - else if (casefold) - ctxt->wildmatch_flags = WM_CASEFOLD; - else - ctxt->wildmatch_flags = 0; - - if (casefold) { - ctxt->strcomp = git__strcasecmp; - ctxt->strncomp = git__strncasecmp; - } else { - ctxt->strcomp = git__strcmp; - ctxt->strncomp = git__strncmp; - } -} - -static int pathspec_match_one( - const git_attr_fnmatch *match, - struct pathspec_match_context *ctxt, - const char *path) -{ - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; - - if (result == WM_NOMATCH) - result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; - - 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 == WM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - ctxt->strncomp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; - - /* if we didn't match and this is a negative match, check for exact - * match of filename with leading '!' - */ - if (result == WM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && - *path == '!' && - ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && - (!path[match->length + 1] || path[match->length + 1] == '/')) - return 1; - - if (result == 0) - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; - return -1; -} - -static int git_pathspec__match_at( - size_t *matched_at, - const git_vector *vspec, - struct pathspec_match_context *ctxt, - const char *path0, - const char *path1) -{ - int result = GIT_ENOTFOUND; - size_t i = 0; - const git_attr_fnmatch *match; - - git_vector_foreach(vspec, i, match) { - if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) - break; - if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) - break; - } - - *matched_at = i; - return result; -} - -/* match a path against the vectorized pathspec */ -bool git_pathspec__match( - const git_vector *vspec, - const char *path, - bool disable_fnmatch, - bool casefold, - const char **matched_pathspec, - size_t *matched_at) -{ - int result; - size_t pos; - struct pathspec_match_context ctxt; - - if (matched_pathspec) - *matched_pathspec = NULL; - if (matched_at) - *matched_at = GIT_PATHSPEC_NOMATCH; - - if (!vspec || !vspec->length) - return true; - - pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); - - result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); - if (result >= 0) { - if (matched_pathspec) { - const git_attr_fnmatch *match = git_vector_get(vspec, pos); - *matched_pathspec = match->pattern; - } - - if (matched_at) - *matched_at = pos; - } - - return (result > 0); -} - - -int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) -{ - int error = 0; - - memset(ps, 0, sizeof(*ps)); - - ps->prefix = git_pathspec_prefix(paths); - - if ((error = git_pool_init(&ps->pool, 1)) < 0 || - (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) - git_pathspec__clear(ps); - - return error; -} - -void git_pathspec__clear(git_pathspec *ps) -{ - git__free(ps->prefix); - git_pathspec__vfree(&ps->pathspec); - git_pool_clear(&ps->pool); - memset(ps, 0, sizeof(*ps)); -} - -int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) -{ - int error = 0; - git_pathspec *ps = git__malloc(sizeof(git_pathspec)); - GIT_ERROR_CHECK_ALLOC(ps); - - if ((error = git_pathspec__init(ps, pathspec)) < 0) { - git__free(ps); - return error; - } - - GIT_REFCOUNT_INC(ps); - *out = ps; - return 0; -} - -static void pathspec_free(git_pathspec *ps) -{ - git_pathspec__clear(ps); - git__free(ps); -} - -void git_pathspec_free(git_pathspec *ps) -{ - if (!ps) - return; - GIT_REFCOUNT_DEC(ps, pathspec_free); -} - -int git_pathspec_matches_path( - const git_pathspec *ps, uint32_t flags, const char *path) -{ - bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; - bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; - - GIT_ASSERT_ARG(ps); - GIT_ASSERT_ARG(path); - - return (0 != git_pathspec__match( - &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); -} - -static void pathspec_match_free(git_pathspec_match_list *m) -{ - if (!m) - return; - - git_pathspec_free(m->pathspec); - m->pathspec = NULL; - - git_array_clear(m->matches); - git_array_clear(m->failures); - git_pool_clear(&m->pool); - git__free(m); -} - -static git_pathspec_match_list *pathspec_match_alloc( - git_pathspec *ps, int datatype) -{ - git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); - if (!m) - return NULL; - - if (git_pool_init(&m->pool, 1) < 0) - return NULL; - - /* need to keep reference to pathspec and increment refcount because - * failures array stores pointers to the pattern strings of the - * pathspec that had no matches - */ - GIT_REFCOUNT_INC(ps); - m->pathspec = ps; - m->datatype = datatype; - - return m; -} - -GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) -{ - if (!git_bitvec_get(used, pos)) { - git_bitvec_set(used, pos, true); - return 1; - } - - return 0; -} - -static size_t pathspec_mark_remaining( - git_bitvec *used, - git_vector *patterns, - struct pathspec_match_context *ctxt, - size_t start, - const char *path0, - const char *path1) -{ - size_t count = 0; - - if (path1 == path0) - path1 = NULL; - - for (; start < patterns->length; ++start) { - const git_attr_fnmatch *pat = git_vector_get(patterns, start); - - if (git_bitvec_get(used, start)) - continue; - - if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) - count += pathspec_mark_pattern(used, start); - else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) - count += pathspec_mark_pattern(used, start); - } - - return count; -} - -static int pathspec_build_failure_array( - git_pathspec_string_array_t *failures, - git_vector *patterns, - git_bitvec *used, - git_pool *pool) -{ - size_t pos; - char **failed; - const git_attr_fnmatch *pat; - - for (pos = 0; pos < patterns->length; ++pos) { - if (git_bitvec_get(used, pos)) - continue; - - if ((failed = git_array_alloc(*failures)) == NULL) - return -1; - - pat = git_vector_get(patterns, pos); - - if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) - return -1; - } - - return 0; -} - -static int pathspec_match_from_iterator( - git_pathspec_match_list **out, - git_iterator *iter, - uint32_t flags, - git_pathspec *ps) -{ - int error = 0; - git_pathspec_match_list *m = NULL; - const git_index_entry *entry = NULL; - struct pathspec_match_context ctxt; - git_vector *patterns = &ps->pathspec; - bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; - bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; - size_t pos, used_ct = 0, found_files = 0; - git_index *index = NULL; - git_bitvec used_patterns; - char **file; - - if (git_bitvec_init(&used_patterns, patterns->length) < 0) - return -1; - - if (out) { - *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); - GIT_ERROR_CHECK_ALLOC(m); - } - - if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) - goto done; - - if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR && - (error = git_repository_index__weakptr( - &index, git_iterator_owner(iter))) < 0) - goto done; - - pathspec_match_context_init( - &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, - git_iterator_ignore_case(iter)); - - while (!(error = git_iterator_advance(&entry, iter))) { - /* search for match with entry->path */ - int result = git_pathspec__match_at( - &pos, patterns, &ctxt, entry->path, NULL); - - /* no matches for this path */ - if (result < 0) - continue; - - /* if result was a negative pattern match, then don't list file */ - if (!result) { - used_ct += pathspec_mark_pattern(&used_patterns, pos); - continue; - } - - /* check if path is ignored and untracked */ - if (index != NULL && - git_iterator_current_is_ignored(iter) && - git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0) - continue; - - /* mark the matched pattern as used */ - used_ct += pathspec_mark_pattern(&used_patterns, pos); - ++found_files; - - /* if find_failures is on, check if any later patterns also match */ - if (find_failures && used_ct < patterns->length) - used_ct += pathspec_mark_remaining( - &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); - - /* if only looking at failures, exit early or just continue */ - if (failures_only || !out) { - if (used_ct == patterns->length) - break; - continue; - } - - /* insert matched path into matches array */ - if ((file = (char **)git_array_alloc(m->matches)) == NULL || - (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { - error = -1; - goto done; - } - } - - if (error < 0 && error != GIT_ITEROVER) - goto done; - error = 0; - - /* insert patterns that had no matches into failures array */ - if (find_failures && used_ct < patterns->length && - (error = pathspec_build_failure_array( - &m->failures, patterns, &used_patterns, &m->pool)) < 0) - goto done; - - /* if every pattern failed to match, then we have failed */ - if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { - git_error_set(GIT_ERROR_INVALID, "no matching files were found"); - error = GIT_ENOTFOUND; - } - -done: - git_bitvec_free(&used_patterns); - - if (error < 0) { - pathspec_match_free(m); - if (out) *out = NULL; - } - - return error; -} - -static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) -{ - git_iterator_flag_t f = 0; - - if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) - f |= GIT_ITERATOR_IGNORE_CASE; - else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) - f |= GIT_ITERATOR_DONT_IGNORE_CASE; - - return f; -} - -int git_pathspec_match_workdir( - git_pathspec_match_list **out, - git_repository *repo, - uint32_t flags, - git_pathspec *ps) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error = 0; - - GIT_ASSERT_ARG(repo); - - iter_opts.flags = pathspec_match_iter_flags(flags); - - if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { - error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); - } - - return error; -} - -int git_pathspec_match_index( - git_pathspec_match_list **out, - git_index *index, - uint32_t flags, - git_pathspec *ps) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error = 0; - - GIT_ASSERT_ARG(index); - - iter_opts.flags = pathspec_match_iter_flags(flags); - - if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) { - error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); - } - - return error; -} - -int git_pathspec_match_tree( - git_pathspec_match_list **out, - git_tree *tree, - uint32_t flags, - git_pathspec *ps) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error = 0; - - GIT_ASSERT_ARG(tree); - - iter_opts.flags = pathspec_match_iter_flags(flags); - - if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { - error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); - } - - return error; -} - -int git_pathspec_match_diff( - git_pathspec_match_list **out, - git_diff *diff, - uint32_t flags, - git_pathspec *ps) -{ - int error = 0; - git_pathspec_match_list *m = NULL; - struct pathspec_match_context ctxt; - git_vector *patterns = &ps->pathspec; - bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; - bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; - size_t i, pos, used_ct = 0, found_deltas = 0; - const git_diff_delta *delta, **match; - git_bitvec used_patterns; - - GIT_ASSERT_ARG(diff); - - if (git_bitvec_init(&used_patterns, patterns->length) < 0) - return -1; - - if (out) { - *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); - GIT_ERROR_CHECK_ALLOC(m); - } - - pathspec_match_context_init( - &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, - git_diff_is_sorted_icase(diff)); - - git_vector_foreach(&diff->deltas, i, delta) { - /* search for match with delta */ - int result = git_pathspec__match_at( - &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); - - /* no matches for this path */ - if (result < 0) - continue; - - /* mark the matched pattern as used */ - used_ct += pathspec_mark_pattern(&used_patterns, pos); - - /* if result was a negative pattern match, then don't list file */ - if (!result) - continue; - - ++found_deltas; - - /* if find_failures is on, check if any later patterns also match */ - if (find_failures && used_ct < patterns->length) - used_ct += pathspec_mark_remaining( - &used_patterns, patterns, &ctxt, pos + 1, - delta->old_file.path, delta->new_file.path); - - /* if only looking at failures, exit early or just continue */ - if (failures_only || !out) { - if (used_ct == patterns->length) - break; - continue; - } - - /* insert matched delta into matches array */ - if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { - error = -1; - goto done; - } else { - *match = delta; - } - } - - /* insert patterns that had no matches into failures array */ - if (find_failures && used_ct < patterns->length && - (error = pathspec_build_failure_array( - &m->failures, patterns, &used_patterns, &m->pool)) < 0) - goto done; - - /* if every pattern failed to match, then we have failed */ - if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { - git_error_set(GIT_ERROR_INVALID, "no matching deltas were found"); - error = GIT_ENOTFOUND; - } - -done: - git_bitvec_free(&used_patterns); - - if (error < 0) { - pathspec_match_free(m); - if (out) *out = NULL; - } - - return error; -} - -void git_pathspec_match_list_free(git_pathspec_match_list *m) -{ - if (m) - pathspec_match_free(m); -} - -size_t git_pathspec_match_list_entrycount( - const git_pathspec_match_list *m) -{ - return m ? git_array_size(m->matches) : 0; -} - -const char *git_pathspec_match_list_entry( - const git_pathspec_match_list *m, size_t pos) -{ - if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || - !git_array_valid_index(m->matches, pos)) - return NULL; - - return *((const char **)git_array_get(m->matches, pos)); -} - -const git_diff_delta *git_pathspec_match_list_diff_entry( - const git_pathspec_match_list *m, size_t pos) -{ - if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || - !git_array_valid_index(m->matches, pos)) - return NULL; - - return *((const git_diff_delta **)git_array_get(m->matches, pos)); -} - -size_t git_pathspec_match_list_failed_entrycount( - const git_pathspec_match_list *m) -{ - return m ? git_array_size(m->failures) : 0; -} - -const char * git_pathspec_match_list_failed_entry( - const git_pathspec_match_list *m, size_t pos) -{ - char **entry = m ? git_array_get(m->failures, pos) : NULL; - - return entry ? *entry : NULL; -} diff --git a/src/pathspec.h b/src/pathspec.h deleted file mode 100644 index 0256cb927..000000000 --- a/src/pathspec.h +++ /dev/null @@ -1,76 +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_pathspec_h__ -#define INCLUDE_pathspec_h__ - -#include "common.h" - -#include "git2/pathspec.h" -#include "str.h" -#include "vector.h" -#include "pool.h" -#include "array.h" - -/* public compiled pathspec */ -struct git_pathspec { - git_refcount rc; - char *prefix; - git_vector pathspec; - git_pool pool; -}; - -enum { - PATHSPEC_DATATYPE_STRINGS = 0, - PATHSPEC_DATATYPE_DIFF = 1 -}; - -typedef git_array_t(char *) git_pathspec_string_array_t; - -/* public interface to pathspec matching */ -struct git_pathspec_match_list { - git_pathspec *pathspec; - git_array_t(void *) matches; - git_pathspec_string_array_t failures; - git_pool pool; - int datatype; -}; - -/* what is the common non-wildcard prefix for all items in the pathspec */ -extern char *git_pathspec_prefix(const git_strarray *pathspec); - -/* is there anything in the spec that needs to be filtered on */ -extern bool git_pathspec_is_empty(const git_strarray *pathspec); - -/* build a vector of fnmatch patterns to evaluate efficiently */ -extern int git_pathspec__vinit( - git_vector *vspec, const git_strarray *strspec, git_pool *strpool); - -/* free data from the pathspec vector */ -extern void git_pathspec__vfree(git_vector *vspec); - -#define GIT_PATHSPEC_NOMATCH ((size_t)-1) - -/* - * Match a path against the vectorized pathspec. - * The matched pathspec is passed back into the `matched_pathspec` parameter, - * unless it is passed as NULL by the caller. - */ -extern bool git_pathspec__match( - const git_vector *vspec, - const char *path, - bool disable_fnmatch, - bool casefold, - const char **matched_pathspec, - size_t *matched_at); - -/* easy pathspec setup */ - -extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); - -extern void git_pathspec__clear(git_pathspec *ps); - -#endif diff --git a/src/pool.c b/src/pool.c deleted file mode 100644 index 16ffa398d..000000000 --- a/src/pool.c +++ /dev/null @@ -1,260 +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 "pool.h" - -#include "posix.h" -#ifndef GIT_WIN32 -#include -#endif - -struct git_pool_page { - git_pool_page *next; - size_t size; - size_t avail; - GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); -}; - -static void *pool_alloc_page(git_pool *pool, size_t size); - -#ifndef GIT_DEBUG_POOL - -static size_t system_page_size = 0; - -int git_pool_global_init(void) -{ - if (git__page_size(&system_page_size) < 0) - system_page_size = 4096; - /* allow space for malloc overhead */ - system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page); - return 0; -} - -int git_pool_init(git_pool *pool, size_t item_size) -{ - GIT_ASSERT_ARG(pool); - GIT_ASSERT_ARG(item_size >= 1); - - memset(pool, 0, sizeof(git_pool)); - pool->item_size = item_size; - pool->page_size = system_page_size; - - return 0; -} - -void git_pool_clear(git_pool *pool) -{ - git_pool_page *scan, *next; - - for (scan = pool->pages; scan != NULL; scan = next) { - next = scan->next; - git__free(scan); - } - - pool->pages = NULL; -} - -static void *pool_alloc_page(git_pool *pool, size_t size) -{ - git_pool_page *page; - 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)) || - !(page = git__malloc(alloc_size))) - return NULL; - - page->size = new_page_size; - page->avail = new_page_size - size; - page->next = pool->pages; - - pool->pages = page; - - return page->data; -} - -static void *pool_alloc(git_pool *pool, size_t size) -{ - git_pool_page *page = pool->pages; - void *ptr = NULL; - - if (!page || page->avail < size) - return pool_alloc_page(pool, size); - - ptr = &page->data[page->size - page->avail]; - page->avail -= size; - - return ptr; -} - -uint32_t git_pool__open_pages(git_pool *pool) -{ - uint32_t ct = 0; - git_pool_page *scan; - for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; - return ct; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - git_pool_page *scan; - for (scan = pool->pages; scan != NULL; scan = scan->next) - if ((void *)scan->data <= ptr && - (void *)(((char *)scan->data) + scan->size) > ptr) - return true; - return false; -} - -#else - -int git_pool_global_init(void) -{ - return 0; -} - -static int git_pool__ptr_cmp(const void * a, const void * b) -{ - if(a > b) { - return 1; - } - if(a < b) { - return -1; - } - else { - return 0; - } -} - -int git_pool_init(git_pool *pool, size_t item_size) -{ - GIT_ASSERT_ARG(pool); - GIT_ASSERT_ARG(item_size >= 1); - - memset(pool, 0, sizeof(git_pool)); - pool->item_size = item_size; - pool->page_size = git_pool__system_page_size(); - git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); - - return 0; -} - -void git_pool_clear(git_pool *pool) -{ - git_vector_free_deep(&pool->allocations); -} - -static void *pool_alloc(git_pool *pool, size_t size) { - void *ptr = NULL; - if((ptr = git__malloc(size)) == NULL) { - return NULL; - } - git_vector_insert_sorted(&pool->allocations, ptr, NULL); - return ptr; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - size_t pos; - return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; -} -#endif - -void git_pool_swap(git_pool *a, git_pool *b) -{ - git_pool temp; - - if (a == b) - return; - - memcpy(&temp, a, sizeof(temp)); - memcpy(a, b, sizeof(temp)); - memcpy(b, &temp, sizeof(temp)); -} - -static size_t alloc_size(git_pool *pool, size_t count) -{ - const size_t align = sizeof(void *) - 1; - - if (pool->item_size > 1) { - 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, size_t items) -{ - return pool_alloc(pool, alloc_size(pool, items)); -} - -void *git_pool_mallocz(git_pool *pool, size_t items) -{ - const size_t size = alloc_size(pool, items); - void *ptr = pool_alloc(pool, size); - if (ptr) - memset(ptr, 0x0, size); - return ptr; -} - -char *git_pool_strndup(git_pool *pool, const char *str, size_t n) -{ - char *ptr = NULL; - - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - if (n == SIZE_MAX) - return NULL; - - if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { - memcpy(ptr, str, n); - ptr[n] = '\0'; - } - - return ptr; -} - -char *git_pool_strdup(git_pool *pool, const char *str) -{ - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - return git_pool_strndup(pool, str, strlen(str)); -} - -char *git_pool_strdup_safe(git_pool *pool, const char *str) -{ - return str ? git_pool_strdup(pool, str) : NULL; -} - -char *git_pool_strcat(git_pool *pool, const char *a, const char *b) -{ - void *ptr; - size_t len_a, len_b, total; - - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - len_a = a ? strlen(a) : 0; - len_b = b ? strlen(b) : 0; - - 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) - memcpy(((char *)ptr) + len_a, b, len_b); - *(((char *)ptr) + len_a + len_b) = '\0'; - } - return ptr; -} diff --git a/src/pool.h b/src/pool.h deleted file mode 100644 index cecb84665..000000000 --- a/src/pool.h +++ /dev/null @@ -1,146 +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_pool_h__ -#define INCLUDE_pool_h__ - -#include "common.h" - -#include "vector.h" - -typedef struct git_pool_page git_pool_page; - -#ifndef GIT_DEBUG_POOL -/** - * Chunked allocator. - * - * A `git_pool` can be used when you want to cheaply allocate - * multiple items of the same type and are willing to free them - * all together with a single call. The two most common cases - * are a set of fixed size items (such as lots of OIDs) or a - * bunch of strings. - * - * Internally, a `git_pool` allocates pages of memory and then - * deals out blocks from the trailing unused portion of each page. - * The pages guarantee that the number of actual allocations done - * will be much smaller than the number of items needed. - * - * For examples of how to set up a `git_pool` see `git_pool_init`. - */ -typedef struct { - git_pool_page *pages; /* allocated pages */ - 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 } - -#else - -/** - * Debug chunked allocator. - * - * Acts just like `git_pool` but instead of actually pooling allocations it - * passes them through to `git__malloc`. This makes it possible to easily debug - * systems that use `git_pool` using valgrind. - * - * In order to track allocations during the lifetime of the pool we use a - * `git_vector`. When the pool is deallocated everything in the vector is - * freed. - * - * `API is exactly the same as the standard `git_pool` with one exception. - * Since we aren't allocating pages to hand out in chunks we can't easily - * implement `git_pool__open_pages`. - */ -typedef struct { - git_vector allocations; - size_t item_size; - size_t page_size; -} git_pool; - -#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } - -#endif - -/** - * Initialize a pool. - * - * To allocation strings, use like this: - * - * git_pool_init(&string_pool, 1); - * my_string = git_pool_strdup(&string_pool, your_string); - * - * To allocate items of fixed size, use like this: - * - * git_pool_init(&pool, sizeof(item)); - * my_item = git_pool_malloc(&pool, 1); - * - * Of course, you can use this in other ways, but those are the - * two most common patterns. - */ -extern int git_pool_init(git_pool *pool, size_t item_size); - -/** - * Free all items in pool - */ -extern void git_pool_clear(git_pool *pool); - -/** - * Swap two pools with one another - */ -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, size_t items); -extern void *git_pool_mallocz(git_pool *pool, size_t items); - -/** - * Allocate space and duplicate string data into it. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); - -/** - * Allocate space and duplicate a string into it. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strdup(git_pool *pool, const char *str); - -/** - * Allocate space and duplicate a string into it, NULL is no error. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strdup_safe(git_pool *pool, const char *str); - -/** - * Allocate space for the concatenation of two strings. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); - -/* - * Misc utilities - */ -#ifndef GIT_DEBUG_POOL -extern uint32_t git_pool__open_pages(git_pool *pool); -#endif -extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); - -/** - * This function is being called by our global setup routines to - * initialize the system pool size. - * - * @return 0 on success, <0 on failure - */ -extern int git_pool_global_init(void); - -#endif diff --git a/src/posix.c b/src/posix.c deleted file mode 100644 index b1f85dc94..000000000 --- a/src/posix.c +++ /dev/null @@ -1,303 +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 "posix.h" - -#include "fs_path.h" -#include -#include - -size_t p_fsync__cnt = 0; - -#ifndef GIT_WIN32 - -#ifdef NO_ADDRINFO - -int p_getaddrinfo( - const char *host, - const char *port, - struct addrinfo *hints, - struct addrinfo **info) -{ - struct addrinfo *ainfo, *ai; - int p = 0; - - GIT_UNUSED(hints); - - if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) - return -1; - - if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { - git__free(ainfo); - return -2; - } - - ainfo->ai_servent = getservbyname(port, 0); - - if (ainfo->ai_servent) - ainfo->ai_port = ainfo->ai_servent->s_port; - else - ainfo->ai_port = htons(atol(port)); - - memcpy(&ainfo->ai_addr_in.sin_addr, - ainfo->ai_hostent->h_addr_list[0], - ainfo->ai_hostent->h_length); - - ainfo->ai_protocol = 0; - ainfo->ai_socktype = hints->ai_socktype; - ainfo->ai_family = ainfo->ai_hostent->h_addrtype; - ainfo->ai_addr_in.sin_family = ainfo->ai_family; - ainfo->ai_addr_in.sin_port = ainfo->ai_port; - ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in; - ainfo->ai_addrlen = sizeof(struct sockaddr_in); - - *info = ainfo; - - if (ainfo->ai_hostent->h_addr_list[1] == NULL) { - ainfo->ai_next = NULL; - return 0; - } - - ai = ainfo; - - for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { - if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { - p_freeaddrinfo(ainfo); - return -1; - } - memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); - memcpy(&ai->ai_next->ai_addr_in.sin_addr, - ainfo->ai_hostent->h_addr_list[p], - ainfo->ai_hostent->h_length); - ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in; - ai = ai->ai_next; - } - - ai->ai_next = NULL; - return 0; -} - -void p_freeaddrinfo(struct addrinfo *info) -{ - struct addrinfo *p, *next; - - p = info; - - while(p != NULL) { - next = p->ai_next; - git__free(p); - p = next; - } -} - -const char *p_gai_strerror(int ret) -{ - switch(ret) { - case -1: return "Out of memory"; break; - case -2: return "Address lookup failed"; break; - default: return "Unknown error"; break; - } -} - -#endif /* NO_ADDRINFO */ - -int p_open(const char *path, volatile int flags, ...) -{ - mode_t mode = 0; - - #ifdef GIT_DEBUG_STRICT_OPEN - if (strstr(path, "//") != NULL) { - errno = EACCES; - return -1; - } - #endif - - if (flags & O_CREAT) { - va_list arg_list; - - va_start(arg_list, flags); - mode = (mode_t)va_arg(arg_list, int); - va_end(arg_list); - } - - return open(path, flags | O_BINARY | O_CLOEXEC, mode); -} - -int p_creat(const char *path, mode_t mode) -{ - return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); -} - -int p_getcwd(char *buffer_out, size_t size) -{ - char *cwd_buffer; - - GIT_ASSERT_ARG(buffer_out); - GIT_ASSERT_ARG(size > 0); - - cwd_buffer = getcwd(buffer_out, size); - - if (cwd_buffer == NULL) - return -1; - - git_fs_path_mkposix(buffer_out); - git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */ - - return 0; -} - -int p_rename(const char *from, const char *to) -{ - if (!link(from, to)) { - p_unlink(from); - return 0; - } - - if (!rename(from, to)) - return 0; - - return -1; -} - -#endif /* GIT_WIN32 */ - -ssize_t p_read(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - - if (!git__is_ssizet(cnt)) { -#ifdef GIT_WIN32 - SetLastError(ERROR_INVALID_PARAMETER); -#endif - errno = EINVAL; - return -1; - } - - while (cnt) { - ssize_t r; -#ifdef GIT_WIN32 - r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt); -#else - r = read(fd, b, cnt); -#endif - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return -1; - } - if (!r) - break; - cnt -= r; - b += r; - } - return (b - (char *)buf); -} - -int p_write(git_file fd, const void *buf, size_t cnt) -{ - const char *b = buf; - - while (cnt) { - ssize_t r; -#ifdef GIT_WIN32 - GIT_ASSERT((size_t)((unsigned int)cnt) == cnt); - r = write(fd, b, (unsigned int)cnt); -#else - r = write(fd, b, cnt); -#endif - if (r < 0) { - if (errno == EINTR || GIT_ISBLOCKED(errno)) - continue; - return -1; - } - if (!r) { - errno = EPIPE; - return -1; - } - cnt -= r; - b += r; - } - return 0; -} - -#ifdef NO_MMAP - -#include "map.h" - -int git__page_size(size_t *page_size) -{ - /* dummy; here we don't need any alignment anyway */ - *page_size = 4096; - return 0; -} - -int git__mmap_alignment(size_t *alignment) -{ - /* dummy; here we don't need any alignment anyway */ - *alignment = 4096; - return 0; -} - - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - const char *ptr; - size_t remaining_len; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - /* writes cannot be emulated without handling pagefaults since write happens by - * writing to mapped memory */ - if (prot & GIT_PROT_WRITE) { - git_error_set(GIT_ERROR_OS, "trying to map %s-writeable", - ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private"); - return -1; - } - - if (!git__is_ssizet(len)) { - errno = EINVAL; - return -1; - } - - out->len = 0; - out->data = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(out->data); - - remaining_len = len; - ptr = (const char *)out->data; - while (remaining_len > 0) { - ssize_t nb; - HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset)); - if (nb <= 0) { - git_error_set(GIT_ERROR_OS, "mmap emulation failed"); - git__free(out->data); - out->data = NULL; - return -1; - } - - ptr += nb; - offset += nb; - remaining_len -= nb; - } - - out->len = len; - return 0; -} - -int p_munmap(git_map *map) -{ - GIT_ASSERT_ARG(map); - git__free(map->data); - - /* Initializing will help debug use-after-free */ - map->len = 0; - map->data = NULL; - - return 0; -} - -#endif diff --git a/src/posix.h b/src/posix.h deleted file mode 100644 index e6f603078..000000000 --- a/src/posix.h +++ /dev/null @@ -1,196 +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_posix_h__ -#define INCLUDE_posix_h__ - -#include "common.h" - -#include -#include -#include - -/* stat: file mode type testing macros */ -#ifndef S_IFGITLINK -#define S_IFGITLINK 0160000 -#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) -#endif - -#ifndef S_IFLNK -#define S_IFLNK 0120000 -#undef _S_IFLNK -#define _S_IFLNK S_IFLNK -#endif - -#ifndef S_IWUSR -#define S_IWUSR 00200 -#endif - -#ifndef S_IXUSR -#define S_IXUSR 00100 -#endif - -#ifndef S_ISLNK -#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) -#endif - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -#endif - -#ifndef S_ISREG -#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) -#endif - -#ifndef S_ISFIFO -#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) -#endif - -/* if S_ISGID is not defined, then don't try to set it */ -#ifndef S_ISGID -#define S_ISGID 0 -#endif - -#ifndef O_BINARY -#define O_BINARY 0 -#endif -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif -#ifndef SOCK_CLOEXEC -#define SOCK_CLOEXEC 0 -#endif - -/* access() mode parameter #defines */ -#ifndef F_OK -#define F_OK 0 /* existence check */ -#endif -#ifndef W_OK -#define W_OK 2 /* write mode check */ -#endif -#ifndef R_OK -#define R_OK 4 /* read mode check */ -#endif - -/* Determine whether an errno value indicates that a read or write failed - * because the descriptor is blocked. - */ -#if defined(EWOULDBLOCK) -#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) -#else -#define GIT_ISBLOCKED(e) ((e) == EAGAIN) -#endif - -/* define some standard errnos that the runtime may be missing. for example, - * mingw lacks EAFNOSUPPORT. */ -#ifndef EAFNOSUPPORT -#define EAFNOSUPPORT (INT_MAX-1) -#endif - -/* Compiler independent macro to handle signal interrpted system calls */ -#define HANDLE_EINTR(result, x) do { \ - result = (x); \ - } while (result == -1 && errno == EINTR); - - -/* 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; - -/** - * Standard POSIX Methods - * - * All the methods starting with the `p_` prefix are - * direct ports of the standard POSIX methods. - * - * Some of the methods are slightly wrapped to provide - * saner defaults. Some of these methods are emulated - * in Windows platforms. - * - * Use your manpages to check the docs on these. - */ - -extern ssize_t p_read(git_file fd, void *buf, size_t cnt); -extern int p_write(git_file fd, const void *buf, size_t cnt); - -extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset); -extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset); - -#define p_close(fd) close(fd) -#define p_umask(m) umask(m) - -extern int p_open(const char *path, int flags, ...); -extern int p_creat(const char *path, mode_t mode); -extern int p_getcwd(char *buffer_out, size_t size); -extern int p_rename(const char *from, const char *to); - -extern int git__page_size(size_t *page_size); -extern int git__mmap_alignment(size_t *page_size); - -/* The number of times `p_fsync` has been called. Note that this is for - * test code only; it it not necessarily thread-safe and should not be - * relied upon in production. - */ -extern size_t p_fsync__cnt; - -/** - * Platform-dependent methods - */ -#ifdef GIT_WIN32 -# include "win32/posix.h" -#else -# include "unix/posix.h" -#endif - -#include "strnlen.h" - -#ifdef NO_READDIR_R -GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) -{ - GIT_UNUSED(entry); - *result = readdir(dirp); - return 0; -} -#else /* NO_READDIR_R */ -# define p_readdir_r(d,e,r) readdir_r(d,e,r) -#endif - -#ifdef NO_ADDRINFO -# include -struct addrinfo { - struct hostent *ai_hostent; - struct servent *ai_servent; - struct sockaddr_in ai_addr_in; - struct sockaddr *ai_addr; - size_t ai_addrlen; - int ai_family; - int ai_socktype; - int ai_protocol; - long ai_port; - struct addrinfo *ai_next; -}; - -extern int p_getaddrinfo(const char *host, const char *port, - struct addrinfo *hints, struct addrinfo **info); -extern void p_freeaddrinfo(struct addrinfo *info); -extern const char *p_gai_strerror(int ret); -#else -# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d) -# define p_freeaddrinfo(a) freeaddrinfo(a) -# define p_gai_strerror(c) gai_strerror(c) -#endif /* NO_ADDRINFO */ - -#endif diff --git a/src/pqueue.c b/src/pqueue.c deleted file mode 100644 index 3820e999c..000000000 --- a/src/pqueue.c +++ /dev/null @@ -1,125 +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 "pqueue.h" - -#include "util.h" - -#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) -#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2) -#define PQUEUE_PARENT_OF(I) (((I)-1)>>1) - -int git_pqueue_init( - git_pqueue *pq, - uint32_t flags, - size_t init_size, - git_vector_cmp cmp) -{ - int error = git_vector_init(pq, init_size, cmp); - - if (!error) { - /* mix in our flags */ - pq->flags |= flags; - - /* if fixed size heap, pretend vector is exactly init_size elements */ - if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0) - pq->_alloc_size = init_size; - } - - return error; -} - -static void pqueue_up(git_pqueue *pq, size_t el) -{ - size_t parent_el = PQUEUE_PARENT_OF(el); - void *kid = git_vector_get(pq, el); - - while (el > 0) { - void *parent = pq->contents[parent_el]; - - if (pq->_cmp(parent, kid) <= 0) - break; - - pq->contents[el] = parent; - - el = parent_el; - parent_el = PQUEUE_PARENT_OF(el); - } - - pq->contents[el] = kid; -} - -static void pqueue_down(git_pqueue *pq, size_t el) -{ - void *parent = git_vector_get(pq, el), *kid, *rkid; - - while (1) { - size_t kid_el = PQUEUE_LCHILD_OF(el); - - if ((kid = git_vector_get(pq, kid_el)) == NULL) - break; - - if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL && - pq->_cmp(kid, rkid) > 0) { - kid = rkid; - kid_el += 1; - } - - if (pq->_cmp(parent, kid) <= 0) - break; - - pq->contents[el] = kid; - el = kid_el; - } - - pq->contents[el] = parent; -} - -int git_pqueue_insert(git_pqueue *pq, void *item) -{ - int error = 0; - - /* if heap is full, pop the top element if new one should replace it */ - if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && - pq->length >= pq->_alloc_size) - { - /* skip this item if below min item in heap or if - * we do not have a comparison function */ - if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) - return 0; - /* otherwise remove the min item before inserting new */ - (void)git_pqueue_pop(pq); - } - - if (!(error = git_vector_insert(pq, item)) && pq->_cmp) - pqueue_up(pq, pq->length - 1); - - return error; -} - -void *git_pqueue_pop(git_pqueue *pq) -{ - void *rval; - - if (!pq->_cmp) { - rval = git_vector_last(pq); - } else { - rval = git_pqueue_get(pq, 0); - } - - if (git_pqueue_size(pq) > 1 && pq->_cmp) { - /* move last item to top of heap, shrink, and push item down */ - pq->contents[0] = git_vector_last(pq); - git_vector_pop(pq); - pqueue_down(pq, 0); - } else { - /* all we need to do is shrink the heap in this case */ - git_vector_pop(pq); - } - - return rval; -} diff --git a/src/pqueue.h b/src/pqueue.h deleted file mode 100644 index 4db74ea03..000000000 --- a/src/pqueue.h +++ /dev/null @@ -1,59 +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_pqueue_h__ -#define INCLUDE_pqueue_h__ - -#include "common.h" - -#include "vector.h" - -typedef git_vector git_pqueue; - -enum { - /* flag meaning: don't grow heap, keep highest values only */ - GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1) -}; - -/** - * Initialize priority queue - * - * @param pq The priority queue struct to initialize - * @param flags Flags (see above) to control queue behavior - * @param init_size The initial queue size - * @param cmp The entry priority comparison function - * @return 0 on success, <0 on error - */ -extern int git_pqueue_init( - git_pqueue *pq, - uint32_t flags, - size_t init_size, - git_vector_cmp cmp); - -#define git_pqueue_free git_vector_free -#define git_pqueue_clear git_vector_clear -#define git_pqueue_size git_vector_length -#define git_pqueue_get git_vector_get -#define git_pqueue_reverse git_vector_reverse - -/** - * Insert a new item into the queue - * - * @param pq The priority queue - * @param item Pointer to the item data - * @return 0 on success, <0 on failure - */ -extern int git_pqueue_insert(git_pqueue *pq, void *item); - -/** - * Remove the top item in the priority queue - * - * @param pq The priority queue - * @return item from heap on success, NULL if queue is empty - */ -extern void *git_pqueue_pop(git_pqueue *pq); - -#endif diff --git a/src/proxy.c b/src/proxy.c deleted file mode 100644 index ef91ad6ea..000000000 --- a/src/proxy.c +++ /dev/null @@ -1,49 +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 "proxy.h" - -#include "git2/proxy.h" - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_proxy_init_options(git_proxy_options *opts, unsigned int version) -{ - return git_proxy_options_init(opts, version); -} -#endif - -int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) -{ - if (!src) { - git_proxy_options_init(tgt, GIT_PROXY_OPTIONS_VERSION); - return 0; - } - - memcpy(tgt, src, sizeof(git_proxy_options)); - if (src->url) { - tgt->url = git__strdup(src->url); - GIT_ERROR_CHECK_ALLOC(tgt->url); - } - - return 0; -} - -void git_proxy_options_dispose(git_proxy_options *opts) -{ - if (!opts) - return; - - git__free((char *) opts->url); - opts->url = NULL; -} diff --git a/src/proxy.h b/src/proxy.h deleted file mode 100644 index 7c0ab598d..000000000 --- a/src/proxy.h +++ /dev/null @@ -1,17 +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_proxy_h__ -#define INCLUDE_proxy_h__ - -#include "common.h" - -#include "git2/proxy.h" - -extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); -extern void git_proxy_options_dispose(git_proxy_options *opts); - -#endif diff --git a/src/push.c b/src/push.c deleted file mode 100644 index da8aebadd..000000000 --- a/src/push.c +++ /dev/null @@ -1,558 +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 "push.h" - -#include "git2.h" - -#include "pack.h" -#include "pack-objects.h" -#include "remote.h" -#include "vector.h" -#include "tree.h" - -static int push_spec_rref_cmp(const void *a, const void *b) -{ - const push_spec *push_spec_a = a, *push_spec_b = b; - - return strcmp(push_spec_a->refspec.dst, push_spec_b->refspec.dst); -} - -static int push_status_ref_cmp(const void *a, const void *b) -{ - const push_status *push_status_a = a, *push_status_b = b; - - return strcmp(push_status_a->ref, push_status_b->ref); -} - -int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts) -{ - git_push *p; - - *out = NULL; - - GIT_ERROR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); - - p = git__calloc(1, sizeof(*p)); - GIT_ERROR_CHECK_ALLOC(p); - - p->repo = remote->repo; - p->remote = remote; - p->report_status = 1; - p->pb_parallelism = opts ? opts->pb_parallelism : 1; - - if (opts) { - GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - memcpy(&p->callbacks, &opts->callbacks, sizeof(git_remote_callbacks)); - } - - if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) { - git__free(p); - return -1; - } - - if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) { - git_vector_free(&p->specs); - git__free(p); - return -1; - } - - if (git_vector_init(&p->updates, 0, NULL) < 0) { - git_vector_free(&p->status); - git_vector_free(&p->specs); - git__free(p); - return -1; - } - - *out = p; - return 0; -} - -static void free_refspec(push_spec *spec) -{ - if (spec == NULL) - return; - - git_refspec__dispose(&spec->refspec); - git__free(spec); -} - -static int check_rref(char *ref) -{ - if (git__prefixcmp(ref, "refs/")) { - git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); - return -1; - } - - return 0; -} - -static int check_lref(git_push *push, char *ref) -{ - /* lref must be resolvable to an existing object */ - git_object *obj; - int error = git_revparse_single(&obj, push->repo, ref); - git_object_free(obj); - - if (!error) - return 0; - - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_REFERENCE, - "src refspec '%s' does not match any existing object", ref); - else - git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); - return -1; -} - -static int parse_refspec(git_push *push, push_spec **spec, const char *str) -{ - push_spec *s; - - *spec = NULL; - - s = git__calloc(1, sizeof(*s)); - GIT_ERROR_CHECK_ALLOC(s); - - if (git_refspec__parse(&s->refspec, str, false) < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid refspec %s", str); - goto on_error; - } - - if (s->refspec.src && s->refspec.src[0] != '\0' && - check_lref(push, s->refspec.src) < 0) { - goto on_error; - } - - if (check_rref(s->refspec.dst) < 0) - goto on_error; - - *spec = s; - return 0; - -on_error: - free_refspec(s); - return -1; -} - -int git_push_add_refspec(git_push *push, const char *refspec) -{ - push_spec *spec; - - if (parse_refspec(push, &spec, refspec) < 0 || - git_vector_insert(&push->specs, spec) < 0) - return -1; - - return 0; -} - -int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) -{ - git_str remote_ref_name = GIT_STR_INIT; - size_t i, j; - git_refspec *fetch_spec; - push_spec *push_spec = NULL; - git_reference *remote_ref; - push_status *status; - int error = 0; - - git_vector_foreach(&push->status, i, status) { - int fire_callback = 1; - - /* Skip unsuccessful updates which have non-empty messages */ - if (status->msg) - continue; - - /* Find the corresponding remote ref */ - fetch_spec = git_remote__matching_refspec(push->remote, status->ref); - if (!fetch_spec) - continue; - - /* Clear the buffer which can be dirty from previous iteration */ - git_str_clear(&remote_ref_name); - - if ((error = git_refspec__transform(&remote_ref_name, fetch_spec, status->ref)) < 0) - goto on_error; - - /* Find matching push ref spec */ - git_vector_foreach(&push->specs, j, push_spec) { - if (!strcmp(push_spec->refspec.dst, status->ref)) - break; - } - - /* Could not find the corresponding push ref spec for this push update */ - if (j == push->specs.length) - continue; - - /* Update the remote ref */ - if (git_oid_is_zero(&push_spec->loid)) { - error = git_reference_lookup(&remote_ref, push->remote->repo, git_str_cstr(&remote_ref_name)); - - if (error >= 0) { - error = git_reference_delete(remote_ref); - git_reference_free(remote_ref); - } - } else { - error = git_reference_create(NULL, push->remote->repo, - git_str_cstr(&remote_ref_name), &push_spec->loid, 1, - "update by push"); - } - - if (error < 0) { - if (error != GIT_ENOTFOUND) - goto on_error; - - git_error_clear(); - fire_callback = 0; - } - - if (fire_callback && callbacks && callbacks->update_tips) { - error = callbacks->update_tips(git_str_cstr(&remote_ref_name), - &push_spec->roid, &push_spec->loid, callbacks->payload); - - if (error < 0) - goto on_error; - } - } - - error = 0; - -on_error: - git_str_dispose(&remote_ref_name); - return error; -} - -/** - * Insert all tags until we find a non-tag object, which is returned - * in `out`. - */ -static int enqueue_tag(git_object **out, git_push *push, git_oid *id) -{ - git_object *obj = NULL, *target = NULL; - int error; - - if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJECT_TAG)) < 0) - return error; - - while (git_object_type(obj) == GIT_OBJECT_TAG) { - if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0) - break; - - if ((error = git_tag_target(&target, (git_tag *) obj)) < 0) - break; - - git_object_free(obj); - obj = target; - } - - if (error < 0) - git_object_free(obj); - else - *out = obj; - - return error; -} - -static int queue_objects(git_push *push) -{ - git_remote_head *head; - push_spec *spec; - git_revwalk *rw; - unsigned int i; - int error = -1; - - if (git_revwalk_new(&rw, push->repo) < 0) - return -1; - - git_revwalk_sorting(rw, GIT_SORT_TIME); - - git_vector_foreach(&push->specs, i, spec) { - git_object_t type; - size_t size; - - if (git_oid_is_zero(&spec->loid)) - /* - * Delete reference on remote side; - * nothing to do here. - */ - continue; - - if (git_oid_equal(&spec->loid, &spec->roid)) - continue; /* up-to-date */ - - if ((error = git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid)) < 0) - goto on_error; - - if (type == GIT_OBJECT_TAG) { - git_object *target; - - if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) - goto on_error; - - if (git_object_type(target) == GIT_OBJECT_COMMIT) { - if ((error = git_revwalk_push(rw, git_object_id(target))) < 0) { - git_object_free(target); - goto on_error; - } - } else { - if ((error = git_packbuilder_insert( - push->pb, git_object_id(target), NULL)) < 0) { - git_object_free(target); - goto on_error; - } - } - git_object_free(target); - } else if ((error = git_revwalk_push(rw, &spec->loid)) < 0) - goto on_error; - - if (!spec->refspec.force) { - git_oid base; - - if (git_oid_is_zero(&spec->roid)) - continue; - - if (!git_odb_exists(push->repo->_odb, &spec->roid)) { - git_error_set(GIT_ERROR_REFERENCE, - "cannot push because a reference that you are trying to update on the remote contains commits that are not present locally."); - error = GIT_ENONFASTFORWARD; - goto on_error; - } - - error = git_merge_base(&base, push->repo, - &spec->loid, &spec->roid); - - if (error == GIT_ENOTFOUND || - (!error && !git_oid_equal(&base, &spec->roid))) { - git_error_set(GIT_ERROR_REFERENCE, - "cannot push non-fastforwardable reference"); - error = GIT_ENONFASTFORWARD; - goto on_error; - } - - if (error < 0) - goto on_error; - } - } - - git_vector_foreach(&push->remote->refs, i, head) { - if (git_oid_is_zero(&head->oid)) - continue; - - 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); - -on_error: - git_revwalk_free(rw); - return error; -} - -static int add_update(git_push *push, push_spec *spec) -{ - git_push_update *u = git__calloc(1, sizeof(git_push_update)); - GIT_ERROR_CHECK_ALLOC(u); - - u->src_refname = git__strdup(spec->refspec.src); - GIT_ERROR_CHECK_ALLOC(u->src_refname); - - u->dst_refname = git__strdup(spec->refspec.dst); - GIT_ERROR_CHECK_ALLOC(u->dst_refname); - - git_oid_cpy(&u->src, &spec->roid); - git_oid_cpy(&u->dst, &spec->loid); - - return git_vector_insert(&push->updates, u); -} - -static int calculate_work(git_push *push) -{ - git_remote_head *head; - push_spec *spec; - unsigned int i, j; - - /* Update local and remote oids*/ - - git_vector_foreach(&push->specs, i, spec) { - if (spec->refspec.src && spec->refspec.src[0]!= '\0') { - /* This is a create or update. Local ref must exist. */ - if (git_reference_name_to_id( - &spec->loid, push->repo, spec->refspec.src) < 0) { - git_error_set(GIT_ERROR_REFERENCE, "no such reference '%s'", spec->refspec.src); - return -1; - } - } - - /* Remote ref may or may not (e.g. during create) already exist. */ - git_vector_foreach(&push->remote->refs, j, head) { - if (!strcmp(spec->refspec.dst, head->name)) { - git_oid_cpy(&spec->roid, &head->oid); - break; - } - } - - if (add_update(push, spec) < 0) - return -1; - } - - return 0; -} - -static int do_push(git_push *push) -{ - int error = 0; - git_transport *transport = push->remote->transport; - git_remote_callbacks *callbacks = &push->callbacks; - - if (!transport->push) { - git_error_set(GIT_ERROR_NET, "remote transport doesn't support push"); - error = -1; - goto on_error; - } - - /* - * A pack-file MUST be sent if either create or update command - * is used, even if the server already has all the necessary - * objects. In this case the client MUST send an empty pack-file. - */ - - if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0) - goto on_error; - - git_packbuilder_set_threads(push->pb, push->pb_parallelism); - - if (callbacks && callbacks->pack_progress) - if ((error = git_packbuilder_set_callbacks(push->pb, callbacks->pack_progress, callbacks->payload)) < 0) - goto on_error; - - if ((error = calculate_work(push)) < 0) - goto on_error; - - if (callbacks && callbacks->push_negotiation && - (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents, - push->updates.length, callbacks->payload)) < 0) - goto on_error; - - if ((error = queue_objects(push)) < 0 || - (error = transport->push(transport, push)) < 0) - goto on_error; - -on_error: - git_packbuilder_free(push->pb); - return error; -} - -static int filter_refs(git_remote *remote) -{ - const git_remote_head **heads; - size_t heads_len, i; - - git_vector_clear(&remote->refs); - - if (git_remote_ls(&heads, &heads_len, remote) < 0) - return -1; - - for (i = 0; i < heads_len; i++) { - if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0) - return -1; - } - - return 0; -} - -int git_push_finish(git_push *push) -{ - int error; - - if (!git_remote_connected(push->remote)) { - git_error_set(GIT_ERROR_NET, "remote is disconnected"); - return -1; - } - - if ((error = filter_refs(push->remote)) < 0 || - (error = do_push(push)) < 0) - return error; - - if (!push->unpack_ok) { - error = -1; - git_error_set(GIT_ERROR_NET, "unpacking the sent packfile failed on the remote"); - } - - return error; -} - -int git_push_status_foreach(git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data) -{ - push_status *status; - unsigned int i; - - git_vector_foreach(&push->status, i, status) { - int error = cb(status->ref, status->msg, data); - if (error) - return git_error_set_after_callback(error); - } - - return 0; -} - -void git_push_status_free(push_status *status) -{ - if (status == NULL) - return; - - git__free(status->msg); - git__free(status->ref); - git__free(status); -} - -void git_push_free(git_push *push) -{ - push_spec *spec; - push_status *status; - git_push_update *update; - unsigned int i; - - if (push == NULL) - return; - - git_vector_foreach(&push->specs, i, spec) { - free_refspec(spec); - } - git_vector_free(&push->specs); - - git_vector_foreach(&push->status, i, status) { - git_push_status_free(status); - } - git_vector_free(&push->status); - - git_vector_foreach(&push->updates, i, update) { - git__free(update->src_refname); - git__free(update->dst_refname); - git__free(update); - } - git_vector_free(&push->updates); - - git__free(push); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_push_init_options(git_push_options *opts, unsigned int version) -{ - return git_push_options_init(opts, version); -} -#endif diff --git a/src/push.h b/src/push.h deleted file mode 100644 index fc72e845e..000000000 --- a/src/push.h +++ /dev/null @@ -1,128 +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_push_h__ -#define INCLUDE_push_h__ - -#include "common.h" - -#include "git2.h" -#include "refspec.h" -#include "remote.h" - -typedef struct push_spec { - struct git_refspec refspec; - - git_oid loid; - git_oid roid; -} push_spec; - -typedef struct push_status { - bool ok; - - char *ref; - char *msg; -} push_status; - -struct git_push { - git_repository *repo; - git_packbuilder *pb; - git_remote *remote; - git_vector specs; - git_vector updates; - bool report_status; - - /* report-status */ - bool unpack_ok; - git_vector status; - - /* options */ - unsigned pb_parallelism; - git_remote_callbacks callbacks; -}; - -/** - * Free the given push status object - * - * @param status The push status object - */ -void git_push_status_free(push_status *status); - -/** - * Create a new push object - * - * @param out New push object - * @param remote Remote instance - * @param opts Push options or NULL - * - * @return 0 or an error code - */ -int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts); - -/** - * Add a refspec to be pushed - * - * @param push The push object - * @param refspec Refspec string - * - * @return 0 or an error code - */ -int git_push_add_refspec(git_push *push, const char *refspec); - -/** - * Update remote tips after a push - * - * @param push The push object - * @param callbacks the callbacks to use for this connection - * - * @return 0 or an error code - */ -int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks); - -/** - * Perform the push - * - * This function will return an error in case of a protocol error or - * the server being unable to unpack the data we sent. - * - * The return value does not reflect whether the server accepted or - * refused any reference updates. Use `git_push_status_foreach()` in - * order to find out which updates were accepted or rejected. - * - * @param push The push object - * - * @return 0 or an error code - */ -int git_push_finish(git_push *push); - -/** - * Invoke callback `cb' on each status entry - * - * For each of the updated references, we receive a status report in the - * form of `ok refs/heads/master` or `ng refs/heads/master `. - * `msg != NULL` means the reference has not been updated for the given - * reason. - * - * Return a non-zero value from the callback to stop the loop. - * - * @param push The push object - * @param cb The callback to call on each object - * @param data The payload passed to the callback - * - * @return 0 on success, non-zero callback return value, or error code - */ -int git_push_status_foreach(git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data); - -/** - * Free the given push object - * - * @param push The push object - */ -void git_push_free(git_push *push); - -#endif diff --git a/src/rand.c b/src/rand.c deleted file mode 100644 index 0a208134e..000000000 --- a/src/rand.c +++ /dev/null @@ -1,226 +0,0 @@ -/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) - -To the extent possible under law, the author has dedicated all copyright -and related and neighboring rights to this software to the public domain -worldwide. This software is distributed without any warranty. - -See . */ - -#include "common.h" -#include "rand.h" -#include "runtime.h" - -#if defined(GIT_RAND_GETENTROPY) -# include -#endif - -static uint64_t state[4]; -static git_mutex state_lock; - -typedef union { - double f; - uint64_t d; -} bits; - -#if defined(GIT_WIN32) -GIT_INLINE(int) getseed(uint64_t *seed) -{ - HCRYPTPROV provider; - SYSTEMTIME systemtime; - FILETIME filetime, idletime, kerneltime, usertime; - bits convert; - - if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { - BOOL success = CryptGenRandom(provider, sizeof(uint64_t), (void *)seed); - CryptReleaseContext(provider, 0); - - if (success) - return 0; - } - - GetSystemTime(&systemtime); - if (!SystemTimeToFileTime(&systemtime, &filetime)) { - git_error_set(GIT_ERROR_OS, "could not get time for random seed"); - return -1; - } - - /* Fall-through: generate a seed from the time and system state */ - *seed = 0; - *seed |= ((uint64_t)filetime.dwLowDateTime << 32); - *seed |= ((uint64_t)filetime.dwHighDateTime); - - GetSystemTimes(&idletime, &kerneltime, &usertime); - - *seed ^= ((uint64_t)idletime.dwLowDateTime << 32); - *seed ^= ((uint64_t)kerneltime.dwLowDateTime); - *seed ^= ((uint64_t)usertime.dwLowDateTime << 32); - - *seed ^= ((uint64_t)idletime.dwHighDateTime); - *seed ^= ((uint64_t)kerneltime.dwHighDateTime << 12); - *seed ^= ((uint64_t)usertime.dwHighDateTime << 24); - - *seed ^= ((uint64_t)GetCurrentProcessId() << 32); - *seed ^= ((uint64_t)GetCurrentThreadId() << 48); - - convert.f = git__timer(); *seed ^= (convert.d); - - /* Mix in the addresses of some functions and variables */ - *seed ^= (((uint64_t)((uintptr_t)seed) << 32)); - *seed ^= (((uint64_t)((uintptr_t)&errno))); - - return 0; -} - -#else - -GIT_INLINE(int) getseed(uint64_t *seed) -{ - struct timeval tv; - double loadavg[3]; - bits convert; - int fd; - -# if defined(GIT_RAND_GETENTROPY) - GIT_UNUSED((fd = 0)); - - if (getentropy(seed, sizeof(uint64_t)) == 0) - return 0; -# else - /* - * Try to read from /dev/urandom; most modern systems will have - * this, but we may be chrooted, etc, so it's not a fatal error - */ - if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) { - ssize_t ret = read(fd, seed, sizeof(uint64_t)); - close(fd); - - if (ret == (ssize_t)sizeof(uint64_t)) - return 0; - } -# endif - - /* Fall-through: generate a seed from the time and system state */ - if (gettimeofday(&tv, NULL) < 0) { - git_error_set(GIT_ERROR_OS, "could get time for random seed"); - return -1; - } - - getloadavg(loadavg, 3); - - *seed = 0; - *seed |= ((uint64_t)tv.tv_usec << 40); - *seed |= ((uint64_t)tv.tv_sec); - - *seed ^= ((uint64_t)getpid() << 48); - *seed ^= ((uint64_t)getppid() << 32); - *seed ^= ((uint64_t)getpgid(0) << 28); - *seed ^= ((uint64_t)getsid(0) << 16); - *seed ^= ((uint64_t)getuid() << 8); - *seed ^= ((uint64_t)getgid()); - - convert.f = loadavg[0]; *seed ^= (convert.d >> 36); - convert.f = loadavg[1]; *seed ^= (convert.d); - convert.f = loadavg[2]; *seed ^= (convert.d >> 16); - - convert.f = git__timer(); *seed ^= (convert.d); - - /* Mix in the addresses of some variables */ - *seed ^= ((uint64_t)((size_t)((void *)seed)) << 32); - *seed ^= ((uint64_t)((size_t)((void *)&errno))); - - return 0; -} -#endif - -static void git_rand_global_shutdown(void) -{ - git_mutex_free(&state_lock); -} - -int git_rand_global_init(void) -{ - uint64_t seed = 0; - - if (git_mutex_init(&state_lock) < 0 || getseed(&seed) < 0) - return -1; - - if (!seed) { - git_error_set(GIT_ERROR_INTERNAL, "failed to generate random seed"); - return -1; - } - - git_rand_seed(seed); - git_runtime_shutdown_register(git_rand_global_shutdown); - - return 0; -} - -/* - * This is splitmix64. xoroshiro256** uses 256 bit seed; this is used - * to generate 256 bits of seed from the given 64, per the author's - * recommendation. - */ -GIT_INLINE(uint64_t) splitmix64(uint64_t *in) -{ - uint64_t z; - - *in += 0x9e3779b97f4a7c15; - - z = *in; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); -} - -void git_rand_seed(uint64_t seed) -{ - uint64_t mixer; - - mixer = seed; - - git_mutex_lock(&state_lock); - state[0] = splitmix64(&mixer); - state[1] = splitmix64(&mixer); - state[2] = splitmix64(&mixer); - state[3] = splitmix64(&mixer); - git_mutex_unlock(&state_lock); -} - -/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid - generators. It has excellent (sub-ns) speed, a state (256 bits) that is - large enough for any parallel application, and it passes all tests we - are aware of. - - For generating just floating-point numbers, xoshiro256+ is even faster. - - The state must be seeded so that it is not everywhere zero. If you have - a 64-bit seed, we suggest to seed a splitmix64 generator and use its - output to fill s. */ - -GIT_INLINE(uint64_t) rotl(const uint64_t x, int k) { - return (x << k) | (x >> (64 - k)); -} - -uint64_t git_rand_next(void) { - uint64_t t, result; - - git_mutex_lock(&state_lock); - - result = rotl(state[1] * 5, 7) * 9; - - t = state[1] << 17; - - state[2] ^= state[0]; - state[3] ^= state[1]; - state[1] ^= state[2]; - state[0] ^= state[3]; - - state[2] ^= t; - - state[3] = rotl(state[3], 45); - - git_mutex_unlock(&state_lock); - - return result; -} diff --git a/src/rand.h b/src/rand.h deleted file mode 100644 index 2e60561e5..000000000 --- a/src/rand.h +++ /dev/null @@ -1,37 +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_rand_h__ -#define INCLUDE_rand_h__ - -#include "common.h" - -/** - * Initialize the random number generation subsystem. This will - * seed the random number generator with the system's entropy pool, - * if available, and will fall back to the current time and - * system information if not. - */ -int git_rand_global_init(void); - -/** - * Seed the pseudo-random number generator. This is not needed to be - * called; the PRNG is seeded by `git_rand_global_init`, but it may - * be useful for testing. When the same seed is specified, the same - * sequence of random numbers from `git_rand_next` is emitted. - * - * @param seed the seed to use - */ -void git_rand_seed(uint64_t seed); - -/** - * Get the next pseudo-random number in the sequence. - * - * @return a 64-bit pseudo-random number - */ -uint64_t git_rand_next(void); - -#endif diff --git a/src/reader.c b/src/reader.c deleted file mode 100644 index ba9775240..000000000 --- a/src/reader.c +++ /dev/null @@ -1,269 +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 "reader.h" - -#include "futils.h" -#include "blob.h" - -#include "git2/tree.h" -#include "git2/blob.h" -#include "git2/index.h" -#include "git2/repository.h" - -/* tree reader */ - -typedef struct { - git_reader reader; - git_tree *tree; -} tree_reader; - -static int tree_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *_reader, - const char *filename) -{ - tree_reader *reader = (tree_reader *)_reader; - git_tree_entry *tree_entry = NULL; - git_blob *blob = NULL; - git_object_size_t blobsize; - int error; - - if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 || - (error = git_blob_lookup(&blob, git_tree_owner(reader->tree), git_tree_entry_id(tree_entry))) < 0) - goto done; - - blobsize = git_blob_rawsize(blob); - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - - if ((error = git_str_set(out, git_blob_rawcontent(blob), (size_t)blobsize)) < 0) - goto done; - - if (out_id) - git_oid_cpy(out_id, git_tree_entry_id(tree_entry)); - - if (out_filemode) - *out_filemode = git_tree_entry_filemode(tree_entry); - -done: - git_blob_free(blob); - git_tree_entry_free(tree_entry); - return error; -} - -int git_reader_for_tree(git_reader **out, git_tree *tree) -{ - tree_reader *reader; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(tree); - - reader = git__calloc(1, sizeof(tree_reader)); - GIT_ERROR_CHECK_ALLOC(reader); - - reader->reader.read = tree_reader_read; - reader->tree = tree; - - *out = (git_reader *)reader; - return 0; -} - -/* workdir reader */ - -typedef struct { - git_reader reader; - git_repository *repo; - git_index *index; -} workdir_reader; - -static int workdir_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *_reader, - const char *filename) -{ - workdir_reader *reader = (workdir_reader *)_reader; - git_str path = GIT_STR_INIT; - struct stat st; - git_filemode_t filemode; - git_filter_list *filters = NULL; - const git_index_entry *idx_entry; - git_oid id; - int error; - - if ((error = git_repository_workdir_path(&path, reader->repo, filename)) < 0) - goto done; - - if ((error = p_lstat(path.ptr, &st)) < 0) { - if (error == -1 && errno == ENOENT) - error = GIT_ENOTFOUND; - - git_error_set(GIT_ERROR_OS, "could not stat '%s'", path.ptr); - goto done; - } - - filemode = git_futils_canonical_mode(st.st_mode); - - /* - * Patch application - for example - uses the filtered version of - * the working directory data to match git. So we will run the - * workdir -> ODB filter on the contents in this workdir reader. - */ - if ((error = git_filter_list_load(&filters, reader->repo, NULL, filename, - GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT)) < 0) - goto done; - - if ((error = git_filter_list__apply_to_file(out, - filters, reader->repo, path.ptr)) < 0) - goto done; - - if (out_id || reader->index) { - if ((error = git_odb_hash(&id, out->ptr, out->size, GIT_OBJECT_BLOB)) < 0) - goto done; - } - - if (reader->index) { - if (!(idx_entry = git_index_get_bypath(reader->index, filename, 0)) || - filemode != idx_entry->mode || - !git_oid_equal(&id, &idx_entry->id)) { - error = GIT_READER_MISMATCH; - goto done; - } - } - - if (out_id) - git_oid_cpy(out_id, &id); - - if (out_filemode) - *out_filemode = filemode; - -done: - git_filter_list_free(filters); - git_str_dispose(&path); - return error; -} - -int git_reader_for_workdir( - git_reader **out, - git_repository *repo, - bool validate_index) -{ - workdir_reader *reader; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - reader = git__calloc(1, sizeof(workdir_reader)); - GIT_ERROR_CHECK_ALLOC(reader); - - reader->reader.read = workdir_reader_read; - reader->repo = repo; - - if (validate_index && - (error = git_repository_index__weakptr(&reader->index, repo)) < 0) { - git__free(reader); - return error; - } - - *out = (git_reader *)reader; - return 0; -} - -/* index reader */ - -typedef struct { - git_reader reader; - git_repository *repo; - git_index *index; -} index_reader; - -static int index_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *_reader, - const char *filename) -{ - index_reader *reader = (index_reader *)_reader; - const git_index_entry *entry; - git_blob *blob; - int error; - - if ((entry = git_index_get_bypath(reader->index, filename, 0)) == NULL) - return GIT_ENOTFOUND; - - if ((error = git_blob_lookup(&blob, reader->repo, &entry->id)) < 0) - goto done; - - if (out_id) - git_oid_cpy(out_id, &entry->id); - - if (out_filemode) - *out_filemode = entry->mode; - - error = git_blob__getbuf(out, blob); - -done: - git_blob_free(blob); - return error; -} - -int git_reader_for_index( - git_reader **out, - git_repository *repo, - git_index *index) -{ - index_reader *reader; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - reader = git__calloc(1, sizeof(index_reader)); - GIT_ERROR_CHECK_ALLOC(reader); - - reader->reader.read = index_reader_read; - reader->repo = repo; - - if (index) { - reader->index = index; - } else if ((error = git_repository_index__weakptr(&reader->index, repo)) < 0) { - git__free(reader); - return error; - } - - *out = (git_reader *)reader; - return 0; -} - -/* generic */ - -int git_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *reader, - const char *filename) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(reader); - GIT_ASSERT_ARG(filename); - - return reader->read(out, out_id, out_filemode, reader, filename); -} - -void git_reader_free(git_reader *reader) -{ - if (!reader) - return; - - git__free(reader); -} diff --git a/src/reader.h b/src/reader.h deleted file mode 100644 index b58dc93f6..000000000 --- a/src/reader.h +++ /dev/null @@ -1,107 +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_reader_h__ -#define INCLUDE_reader_h__ - -#include "common.h" - -/* Returned when the workdir does not match the index */ -#define GIT_READER_MISMATCH 1 - -typedef struct git_reader git_reader; - -/* - * The `git_reader` structure is a generic interface for reading the - * contents of a file by its name, and implementations are provided - * for reading out of a tree, the index, and the working directory. - * - * Note that the reader implementation is meant to have a short - * lifecycle and does not increase the refcount of the object that - * it's reading. Callers should ensure that they do not use a - * reader after disposing the underlying object that it reads. - */ -struct git_reader { - int (*read)(git_str *out, git_oid *out_oid, git_filemode_t *mode, git_reader *reader, const char *filename); -}; - -/** - * Create a `git_reader` that will allow random access to the given - * tree. Paths requested via `git_reader_read` will be rooted at this - * tree, callers are not expected to recurse through tree lookups. Thus, - * you can request to read `/src/foo.c` and the tree provided to this - * function will be searched to find another tree named `src`, which - * will then be opened to find `foo.c`. - * - * @param out The reader for the given tree - * @param tree The tree object to read - * @return 0 on success, or an error code < 0 - */ -extern int git_reader_for_tree( - git_reader **out, - git_tree *tree); - -/** - * Create a `git_reader` that will allow random access to the given - * index, or the repository's index. - * - * @param out The reader for the given index - * @param repo The repository containing the index - * @param index The index to read, or NULL to use the repository's index - * @return 0 on success, or an error code < 0 - */ -extern int git_reader_for_index( - git_reader **out, - git_repository *repo, - git_index *index); - -/** - * Create a `git_reader` that will allow random access to the given - * repository's working directory. Note that the contents are read - * in repository format, meaning any workdir -> odb filters are - * applied. - * - * If `validate_index` is set to true, reads of files will hash the - * on-disk contents and ensure that the resulting object ID matches - * the repository's index. This ensures that the working directory - * is unmodified from the index contents. - * - * @param out The reader for the given working directory - * @param repo The repository containing the working directory - * @param validate_index If true, the working directory contents will - * be compared to the index contents during read to ensure that - * the working directory is unmodified. - * @return 0 on success, or an error code < 0 - */ -extern int git_reader_for_workdir( - git_reader **out, - git_repository *repo, - bool validate_index); - -/** - * Read the given filename from the reader and populate the given buffer - * with the contents and the given oid with the object ID. - * - * @param out The buffer to populate with the file contents - * @param out_id The oid to populate with the object ID - * @param reader The reader to read - * @param filename The filename to read from the reader - */ -extern int git_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *reader, - const char *filename); - -/** - * Free the given reader and any associated objects. - * - * @param reader The reader to free - */ -extern void git_reader_free(git_reader *reader); - -#endif diff --git a/src/rebase.c b/src/rebase.c deleted file mode 100644 index 6f01d3990..000000000 --- a/src/rebase.c +++ /dev/null @@ -1,1470 +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 "common.h" - -#include "str.h" -#include "repository.h" -#include "posix.h" -#include "filebuf.h" -#include "commit.h" -#include "merge.h" -#include "array.h" -#include "config.h" -#include "annotated_commit.h" -#include "index.h" - -#include -#include -#include -#include -#include -#include -#include - -#define REBASE_APPLY_DIR "rebase-apply" -#define REBASE_MERGE_DIR "rebase-merge" - -#define HEAD_NAME_FILE "head-name" -#define ORIG_HEAD_FILE "orig-head" -#define HEAD_FILE "head" -#define ONTO_FILE "onto" -#define ONTO_NAME_FILE "onto_name" -#define QUIET_FILE "quiet" - -#define MSGNUM_FILE "msgnum" -#define END_FILE "end" -#define CMT_FILE_FMT "cmt.%" PRIuZ -#define CURRENT_FILE "current" -#define REWRITTEN_FILE "rewritten" - -#define ORIG_DETACHED_HEAD "detached HEAD" - -#define NOTES_DEFAULT_REF NULL - -#define REBASE_DIR_MODE 0777 -#define REBASE_FILE_MODE 0666 - -typedef enum { - 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_t type; - char *state_path; - - unsigned int head_detached:1, - inmemory:1, - quiet:1, - started:1; - - git_array_t(git_rebase_operation) operations; - size_t current; - - /* Used by in-memory rebase */ - git_index *index; - git_commit *last_commit; - - /* Used by regular (not in-memory) merge-style rebase */ - git_oid orig_head_id; - char *orig_head_name; - - git_oid onto_id; - char *onto_name; -}; - -#define GIT_REBASE_STATE_INIT {0} - -static int rebase_state_type( - git_rebase_t *type_out, - char **path_out, - git_repository *repo) -{ - git_str path = GIT_STR_INIT; - git_rebase_t type = GIT_REBASE_NONE; - - if (git_str_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) - return -1; - - if (git_fs_path_isdir(git_str_cstr(&path))) { - type = GIT_REBASE_APPLY; - goto done; - } - - git_str_clear(&path); - if (git_str_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0) - return -1; - - if (git_fs_path_isdir(git_str_cstr(&path))) { - type = GIT_REBASE_MERGE; - goto done; - } - -done: - *type_out = type; - - if (type != GIT_REBASE_NONE && path_out) - *path_out = git_str_detach(&path); - - git_str_dispose(&path); - - return 0; -} - -GIT_INLINE(int) rebase_readfile( - git_str *out, - git_str *state_path, - const char *filename) -{ - size_t state_path_len = state_path->size; - int error; - - git_str_clear(out); - - if ((error = git_str_joinpath(state_path, state_path->ptr, filename)) < 0 || - (error = git_futils_readbuffer(out, state_path->ptr)) < 0) - goto done; - - git_str_rtrim(out); - -done: - git_str_truncate(state_path, state_path_len); - return error; -} - -GIT_INLINE(int) rebase_readint( - size_t *out, git_str *asc_out, git_str *state_path, const char *filename) -{ - int32_t num; - const char *eol; - int error = 0; - - if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) - return error; - - if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { - git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid numeric value", filename); - return -1; - } - - *out = (size_t) num; - - return 0; -} - -GIT_INLINE(int) rebase_readoid( - git_oid *out, git_str *str_out, git_str *state_path, const char *filename) -{ - int error; - - if ((error = rebase_readfile(str_out, state_path, filename)) < 0) - return error; - - if (str_out->size != GIT_OID_HEXSZ || git_oid_fromstr(out, str_out->ptr) < 0) { - git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid object ID", filename); - return -1; - } - - return 0; -} - -static git_rebase_operation *rebase_operation_alloc( - git_rebase *rebase, - git_rebase_operation_t type, - git_oid *id, - const char *exec) -{ - git_rebase_operation *operation; - - GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !id, NULL); - GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !!exec, NULL); - - if ((operation = git_array_alloc(rebase->operations)) == NULL) - return NULL; - - operation->type = type; - git_oid_cpy((git_oid *)&operation->id, id); - operation->exec = exec; - - return operation; -} - -static int rebase_open_merge(git_rebase *rebase) -{ - git_str state_path = GIT_STR_INIT, buf = GIT_STR_INIT, cmt = GIT_STR_INIT; - git_oid id; - git_rebase_operation *operation; - size_t i, msgnum = 0, end; - int error; - - if ((error = git_str_puts(&state_path, rebase->state_path)) < 0) - goto done; - - /* Read 'msgnum' if it exists (otherwise, let msgnum = 0) */ - if ((error = rebase_readint(&msgnum, &buf, &state_path, MSGNUM_FILE)) < 0 && - error != GIT_ENOTFOUND) - goto done; - - if (msgnum) { - rebase->started = 1; - rebase->current = msgnum - 1; - } - - /* Read 'end' */ - if ((error = rebase_readint(&end, &buf, &state_path, END_FILE)) < 0) - goto done; - - /* Read 'current' if it exists */ - if ((error = rebase_readoid(&id, &buf, &state_path, CURRENT_FILE)) < 0 && - error != GIT_ENOTFOUND) - goto done; - - /* Read cmt.* */ - git_array_init_to_size(rebase->operations, end); - GIT_ERROR_CHECK_ARRAY(rebase->operations); - - for (i = 0; i < end; i++) { - git_str_clear(&cmt); - - if ((error = git_str_printf(&cmt, "cmt.%" PRIuZ, (i+1))) < 0 || - (error = rebase_readoid(&id, &buf, &state_path, cmt.ptr)) < 0) - goto done; - - operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); - GIT_ERROR_CHECK_ALLOC(operation); - } - - /* Read 'onto_name' */ - if ((error = rebase_readfile(&buf, &state_path, ONTO_NAME_FILE)) < 0) - goto done; - - rebase->onto_name = git_str_detach(&buf); - -done: - git_str_dispose(&cmt); - git_str_dispose(&state_path); - git_str_dispose(&buf); - - return error; -} - -static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) -{ - git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); - GIT_ERROR_CHECK_ALLOC(rebase); - - *out = NULL; - - if (rebase_opts) - memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); - else - 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); - GIT_ERROR_CHECK_ALLOC(rebase->options.rewrite_notes_ref); - } - - *out = rebase; - - return 0; -} - -static int rebase_check_versions(const git_rebase_options *given_opts) -{ - GIT_ERROR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); - - if (given_opts) - GIT_ERROR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); - - return 0; -} - -int git_rebase_open( - git_rebase **out, - git_repository *repo, - const git_rebase_options *given_opts) -{ - git_rebase *rebase; - git_str path = GIT_STR_INIT, orig_head_name = GIT_STR_INIT, - orig_head_id = GIT_STR_INIT, onto_id = GIT_STR_INIT; - size_t state_path_len; - int error; - - GIT_ASSERT_ARG(repo); - - if ((error = rebase_check_versions(given_opts)) < 0) - return error; - - if (rebase_alloc(&rebase, given_opts) < 0) - return -1; - - rebase->repo = repo; - - if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0) - goto done; - - if (rebase->type == GIT_REBASE_NONE) { - git_error_set(GIT_ERROR_REBASE, "there is no rebase in progress"); - error = GIT_ENOTFOUND; - goto done; - } - - if ((error = git_str_puts(&path, rebase->state_path)) < 0) - goto done; - - state_path_len = git_str_len(&path); - - if ((error = git_str_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 || - (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0) - goto done; - - git_str_rtrim(&orig_head_name); - - if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0) - rebase->head_detached = 1; - - git_str_truncate(&path, state_path_len); - - if ((error = git_str_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0) - goto done; - - if (!git_fs_path_isfile(path.ptr)) { - /* Previous versions of git.git used 'head' here; support that. */ - git_str_truncate(&path, state_path_len); - - if ((error = git_str_joinpath(&path, path.ptr, HEAD_FILE)) < 0) - goto done; - } - - if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0) - goto done; - - git_str_rtrim(&orig_head_id); - - if ((error = git_oid_fromstr(&rebase->orig_head_id, orig_head_id.ptr)) < 0) - goto done; - - git_str_truncate(&path, state_path_len); - - if ((error = git_str_joinpath(&path, path.ptr, ONTO_FILE)) < 0 || - (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0) - goto done; - - git_str_rtrim(&onto_id); - - if ((error = git_oid_fromstr(&rebase->onto_id, onto_id.ptr)) < 0) - goto done; - - if (!rebase->head_detached) - rebase->orig_head_name = git_str_detach(&orig_head_name); - - switch (rebase->type) { - case GIT_REBASE_INTERACTIVE: - git_error_set(GIT_ERROR_REBASE, "interactive rebase is not supported"); - error = -1; - break; - case GIT_REBASE_MERGE: - error = rebase_open_merge(rebase); - break; - case GIT_REBASE_APPLY: - git_error_set(GIT_ERROR_REBASE, "patch application rebase is not supported"); - error = -1; - break; - default: - abort(); - } - -done: - if (error == 0) - *out = rebase; - else - git_rebase_free(rebase); - - git_str_dispose(&path); - git_str_dispose(&orig_head_name); - git_str_dispose(&orig_head_id); - git_str_dispose(&onto_id); - return error; -} - -static int rebase_cleanup(git_rebase *rebase) -{ - if (!rebase || rebase->inmemory) - return 0; - - return git_fs_path_isdir(rebase->state_path) ? - git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) : - 0; -} - -static int rebase_setupfile(git_rebase *rebase, const char *filename, int flags, const char *fmt, ...) -{ - git_str path = GIT_STR_INIT, - contents = GIT_STR_INIT; - va_list ap; - int error; - - va_start(ap, fmt); - git_str_vprintf(&contents, fmt, ap); - va_end(ap); - - if ((error = git_str_joinpath(&path, rebase->state_path, filename)) == 0) - error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE); - - git_str_dispose(&path); - git_str_dispose(&contents); - - return error; -} - -static const char *rebase_onto_name(const git_annotated_commit *onto) -{ - if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0) - return onto->ref_name + 11; - else if (onto->ref_name) - return onto->ref_name; - else - return onto->id_str; -} - -static int rebase_setupfiles_merge(git_rebase *rebase) -{ - git_str commit_filename = GIT_STR_INIT; - char id_str[GIT_OID_HEXSZ]; - git_rebase_operation *operation; - size_t i; - int error = 0; - - if ((error = rebase_setupfile(rebase, END_FILE, 0, "%" PRIuZ "\n", git_array_size(rebase->operations))) < 0 || - (error = rebase_setupfile(rebase, ONTO_NAME_FILE, 0, "%s\n", rebase->onto_name)) < 0) - goto done; - - for (i = 0; i < git_array_size(rebase->operations); i++) { - operation = git_array_get(rebase->operations, i); - - git_str_clear(&commit_filename); - git_str_printf(&commit_filename, CMT_FILE_FMT, i+1); - - git_oid_fmt(id_str, &operation->id); - - if ((error = rebase_setupfile(rebase, commit_filename.ptr, 0, - "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0) - goto done; - } - -done: - git_str_dispose(&commit_filename); - return error; -} - -static int rebase_setupfiles(git_rebase *rebase) -{ - char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ]; - const char *orig_head_name; - - git_oid_fmt(onto, &rebase->onto_id); - git_oid_fmt(orig_head, &rebase->orig_head_id); - - if (p_mkdir(rebase->state_path, REBASE_DIR_MODE) < 0) { - git_error_set(GIT_ERROR_OS, "failed to create rebase directory '%s'", rebase->state_path); - return -1; - } - - orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD : - rebase->orig_head_name; - - if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 || - rebase_setupfile(rebase, HEAD_NAME_FILE, 0, "%s\n", orig_head_name) < 0 || - rebase_setupfile(rebase, ONTO_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 || - rebase_setupfile(rebase, ORIG_HEAD_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 || - rebase_setupfile(rebase, QUIET_FILE, 0, rebase->quiet ? "t\n" : "\n") < 0) - return -1; - - return rebase_setupfiles_merge(rebase); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_rebase_init_options(git_rebase_options *opts, unsigned int version) -{ - return git_rebase_options_init(opts, version); -} -#endif - -static int rebase_ensure_not_in_progress(git_repository *repo) -{ - int error; - git_rebase_t type; - - if ((error = rebase_state_type(&type, NULL, repo)) < 0) - return error; - - if (type != GIT_REBASE_NONE) { - git_error_set(GIT_ERROR_REBASE, "there is an existing rebase in progress"); - return -1; - } - - return 0; -} - -static int rebase_ensure_not_dirty( - git_repository *repo, - bool check_index, - bool check_workdir, - int fail_with) -{ - git_tree *head = NULL; - git_index *index = NULL; - git_diff *diff = NULL; - int error = 0; - - if (check_index) { - if ((error = git_repository_head_tree(&head, repo)) < 0 || - (error = git_repository_index(&index, repo)) < 0 || - (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - git_error_set(GIT_ERROR_REBASE, "uncommitted changes exist in index"); - error = fail_with; - goto done; - } - - git_diff_free(diff); - diff = NULL; - } - - if (check_workdir) { - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.ignore_submodules = GIT_SUBMODULE_IGNORE_UNTRACKED; - if ((error = git_diff_index_to_workdir(&diff, repo, index, &diff_opts)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - git_error_set(GIT_ERROR_REBASE, "unstaged changes exist in workdir"); - error = fail_with; - goto done; - } - } - -done: - git_diff_free(diff); - git_index_free(index); - git_tree_free(head); - - return error; -} - -static int rebase_init_operations( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - git_revwalk *revwalk = NULL; - git_commit *commit; - git_oid id; - bool merge; - git_rebase_operation *operation; - int error; - - if (!upstream) - upstream = onto; - - if ((error = git_revwalk_new(&revwalk, rebase->repo)) < 0 || - (error = git_revwalk_push(revwalk, git_annotated_commit_id(branch))) < 0 || - (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0) - goto done; - - git_revwalk_sorting(revwalk, GIT_SORT_REVERSE); - - while ((error = git_revwalk_next(&id, revwalk)) == 0) { - if ((error = git_commit_lookup(&commit, repo, &id)) < 0) - goto done; - - merge = (git_commit_parentcount(commit) > 1); - git_commit_free(commit); - - if (merge) - continue; - - operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); - GIT_ERROR_CHECK_ALLOC(operation); - } - - error = 0; - -done: - git_revwalk_free(revwalk); - return error; -} - -static int rebase_init_merge( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - git_reference *head_ref = NULL; - git_commit *onto_commit = NULL; - git_str reflog = GIT_STR_INIT; - git_str state_path = GIT_STR_INIT; - int error; - - GIT_UNUSED(upstream); - - if ((error = git_str_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0) - goto done; - - rebase->state_path = git_str_detach(&state_path); - GIT_ERROR_CHECK_ALLOC(rebase->state_path); - - if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) { - rebase->orig_head_name = git__strdup(branch->ref_name); - GIT_ERROR_CHECK_ALLOC(rebase->orig_head_name); - } else { - rebase->head_detached = 1; - } - - rebase->onto_name = git__strdup(rebase_onto_name(onto)); - GIT_ERROR_CHECK_ALLOC(rebase->onto_name); - - rebase->quiet = rebase->options.quiet; - - git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); - git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); - - if ((error = rebase_setupfiles(rebase)) < 0 || - (error = git_str_printf(&reflog, - "rebase: checkout %s", rebase_onto_name(onto))) < 0 || - (error = git_commit_lookup( - &onto_commit, repo, git_annotated_commit_id(onto))) < 0 || - (error = git_checkout_tree(repo, - (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || - (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, - git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) - goto done; - -done: - git_reference_free(head_ref); - git_commit_free(onto_commit); - git_str_dispose(&reflog); - git_str_dispose(&state_path); - - return error; -} - -static int rebase_init_inmemory( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - GIT_UNUSED(branch); - GIT_UNUSED(upstream); - - return git_commit_lookup( - &rebase->last_commit, repo, git_annotated_commit_id(onto)); -} - -int git_rebase_init( - git_rebase **out, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto, - const git_rebase_options *given_opts) -{ - git_rebase *rebase = NULL; - git_annotated_commit *head_branch = NULL; - git_reference *head_ref = NULL; - bool inmemory = (given_opts && given_opts->inmemory); - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(upstream || onto); - - *out = NULL; - - if (!onto) - onto = upstream; - - if ((error = rebase_check_versions(given_opts)) < 0) - goto done; - - if (!inmemory) { - if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || - (error = rebase_ensure_not_in_progress(repo)) < 0 || - (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0) - goto done; - } - - if (!branch) { - if ((error = git_repository_head(&head_ref, repo)) < 0 || - (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) - goto done; - - branch = head_branch; - } - - if (rebase_alloc(&rebase, given_opts) < 0) - return -1; - - rebase->repo = repo; - rebase->inmemory = inmemory; - rebase->type = GIT_REBASE_MERGE; - - if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) - goto done; - - if (inmemory) - error = rebase_init_inmemory(rebase, repo, branch, upstream, onto); - else - error = rebase_init_merge(rebase, repo, branch ,upstream, onto); - - if (error == 0) - *out = rebase; - -done: - git_reference_free(head_ref); - git_annotated_commit_free(head_branch); - - if (error < 0) { - rebase_cleanup(rebase); - git_rebase_free(rebase); - } - - return error; -} - -static void normalize_checkout_options_for_apply( - git_checkout_options *checkout_opts, - git_rebase *rebase, - git_commit *current_commit) -{ - memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options)); - - if (!checkout_opts->ancestor_label) - checkout_opts->ancestor_label = "ancestor"; - - if (rebase->type == GIT_REBASE_MERGE) { - if (!checkout_opts->our_label) - checkout_opts->our_label = rebase->onto_name; - - if (!checkout_opts->their_label) - checkout_opts->their_label = git_commit_summary(current_commit); - } else { - abort(); - } -} - -GIT_INLINE(int) rebase_movenext(git_rebase *rebase) -{ - size_t next = rebase->started ? rebase->current + 1 : 0; - - if (next == git_array_size(rebase->operations)) - return GIT_ITEROVER; - - rebase->started = 1; - rebase->current = next; - - return 0; -} - -static int rebase_next_merge( - git_rebase_operation **out, - git_rebase *rebase) -{ - git_str path = GIT_STR_INIT; - git_commit *current_commit = NULL, *parent_commit = NULL; - git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - git_rebase_operation *operation; - git_checkout_options checkout_opts; - char current_idstr[GIT_OID_HEXSZ]; - unsigned int parent_count; - int error; - - *out = NULL; - - operation = git_array_get(rebase->operations, rebase->current); - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0 || - (error = git_repository_head_tree(&head_tree, rebase->repo)) < 0) - goto done; - - if ((parent_count = git_commit_parentcount(current_commit)) > 1) { - git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); - error = -1; - goto done; - } else if (parent_count) { - if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0) - goto done; - } - - git_oid_fmt(current_idstr, &operation->id); - - normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit); - - if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || - (error = rebase_setupfile(rebase, MSGNUM_FILE, 0, "%" PRIuZ "\n", rebase->current+1)) < 0 || - (error = rebase_setupfile(rebase, CURRENT_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 || - (error = git_merge__check_result(rebase->repo, index)) < 0 || - (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto done; - - *out = operation; - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_tree_free(current_tree); - git_tree_free(head_tree); - git_tree_free(parent_tree); - git_commit_free(parent_commit); - git_commit_free(current_commit); - git_str_dispose(&path); - - return error; -} - -static int rebase_next_inmemory( - git_rebase_operation **out, - git_rebase *rebase) -{ - git_commit *current_commit = NULL, *parent_commit = NULL; - git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; - git_rebase_operation *operation; - git_index *index = NULL; - unsigned int parent_count; - int error; - - *out = NULL; - - operation = git_array_get(rebase->operations, rebase->current); - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0) - goto done; - - if ((parent_count = git_commit_parentcount(current_commit)) > 1) { - git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); - error = -1; - goto done; - } else if (parent_count) { - if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0) - goto done; - } - - if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) - goto done; - - if (!rebase->index) { - rebase->index = index; - index = NULL; - } else { - if ((error = git_index_read_index(rebase->index, index)) < 0) - goto done; - } - - *out = operation; - -done: - git_commit_free(current_commit); - git_commit_free(parent_commit); - git_tree_free(current_tree); - git_tree_free(head_tree); - git_tree_free(parent_tree); - git_index_free(index); - - return error; -} - -int git_rebase_next( - git_rebase_operation **out, - git_rebase *rebase) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(rebase); - - if ((error = rebase_movenext(rebase)) < 0) - return error; - - if (rebase->inmemory) - error = rebase_next_inmemory(out, rebase); - else if (rebase->type == GIT_REBASE_MERGE) - error = rebase_next_merge(out, rebase); - else - abort(); - - return error; -} - -int git_rebase_inmemory_index( - git_index **out, - git_rebase *rebase) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(rebase); - GIT_ASSERT_ARG(rebase->index); - - GIT_REFCOUNT_INC(rebase->index); - *out = rebase->index; - - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -static int create_signed( - git_oid *out, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - git_tree *tree, - size_t parent_count, - const git_commit **parents) -{ - git_str commit_content = GIT_STR_INIT; - git_buf commit_signature = { NULL, 0, 0 }, - signature_field = { NULL, 0, 0 }; - int error; - - git_error_clear(); - - if ((error = git_commit__create_buffer(&commit_content, - rebase->repo, author, committer, message_encoding, - message, tree, parent_count, parents)) < 0) - goto done; - - error = rebase->options.signing_cb(&commit_signature, - &signature_field, commit_content.ptr, - rebase->options.payload); - - if (error) { - if (error != GIT_PASSTHROUGH) - git_error_set_after_callback_function(error, "signing_cb"); - - goto done; - } - - error = git_commit_create_with_signature(out, rebase->repo, - commit_content.ptr, - commit_signature.size > 0 ? commit_signature.ptr : NULL, - signature_field.size > 0 ? signature_field.ptr : NULL); - -done: - git_buf_dispose(&commit_signature); - git_buf_dispose(&signature_field); - git_str_dispose(&commit_content); - return error; -} -#endif - -static int rebase_commit__create( - git_commit **out, - git_rebase *rebase, - git_index *index, - git_commit *parent_commit, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_rebase_operation *operation; - git_commit *current_commit = NULL, *commit = NULL; - git_tree *parent_tree = NULL, *tree = NULL; - git_oid tree_id, commit_id; - int error; - - operation = git_array_get(rebase->operations, rebase->current); - - if (git_index_has_conflicts(index)) { - git_error_set(GIT_ERROR_REBASE, "conflicts have not been resolved"); - error = GIT_EUNMERGED; - goto done; - } - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || - (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 || - (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) - goto done; - - if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) { - git_error_set(GIT_ERROR_REBASE, "this patch has already been applied"); - error = GIT_EAPPLIED; - goto done; - } - - if (!author) - author = git_commit_author(current_commit); - - if (!message) { - message_encoding = git_commit_message_encoding(current_commit); - message = git_commit_message(current_commit); - } - - git_error_clear(); - error = GIT_PASSTHROUGH; - - if (rebase->options.commit_create_cb) { - error = rebase->options.commit_create_cb(&commit_id, - author, committer, message_encoding, message, - tree, 1, (const git_commit **)&parent_commit, - rebase->options.payload); - - git_error_set_after_callback_function(error, - "commit_create_cb"); - } -#ifndef GIT_DEPRECATE_HARD - else if (rebase->options.signing_cb) { - error = create_signed(&commit_id, rebase, author, - committer, message_encoding, message, tree, - 1, (const git_commit **)&parent_commit); - } -#endif - - if (error == GIT_PASSTHROUGH) - error = git_commit_create(&commit_id, rebase->repo, NULL, - author, committer, message_encoding, message, - tree, 1, (const git_commit **)&parent_commit); - - if (error) - goto done; - - if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) - goto done; - - *out = commit; - -done: - if (error < 0) - git_commit_free(commit); - - git_commit_free(current_commit); - git_tree_free(parent_tree); - git_tree_free(tree); - - return error; -} - -static int rebase_commit_merge( - git_oid *commit_id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_rebase_operation *operation; - git_reference *head = NULL; - git_commit *head_commit = NULL, *commit = NULL; - git_index *index = NULL; - char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; - int error; - - operation = git_array_get(rebase->operations, rebase->current); - GIT_ASSERT(operation); - - if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || - (error = git_repository_head(&head, rebase->repo)) < 0 || - (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)) < 0 || - (error = git_repository_index(&index, rebase->repo)) < 0 || - (error = rebase_commit__create(&commit, rebase, index, head_commit, - author, committer, message_encoding, message)) < 0 || - (error = git_reference__update_for_commit( - rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0) - goto done; - - git_oid_fmt(old_idstr, &operation->id); - git_oid_fmt(new_idstr, git_commit_id(commit)); - - if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, - "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0) - goto done; - - git_oid_cpy(commit_id, git_commit_id(commit)); - -done: - git_index_free(index); - git_reference_free(head); - git_commit_free(head_commit); - git_commit_free(commit); - return error; -} - -static int rebase_commit_inmemory( - git_oid *commit_id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_commit *commit = NULL; - int error = 0; - - GIT_ASSERT_ARG(rebase->index); - GIT_ASSERT_ARG(rebase->last_commit); - GIT_ASSERT_ARG(rebase->current < rebase->operations.size); - - if ((error = rebase_commit__create(&commit, rebase, rebase->index, - rebase->last_commit, author, committer, message_encoding, message)) < 0) - goto done; - - git_commit_free(rebase->last_commit); - rebase->last_commit = commit; - - git_oid_cpy(commit_id, git_commit_id(commit)); - -done: - if (error < 0) - git_commit_free(commit); - - return error; -} - -int git_rebase_commit( - git_oid *id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - int error; - - GIT_ASSERT_ARG(rebase); - GIT_ASSERT_ARG(committer); - - if (rebase->inmemory) - error = rebase_commit_inmemory( - id, rebase, author, committer, message_encoding, message); - else if (rebase->type == GIT_REBASE_MERGE) - error = rebase_commit_merge( - id, rebase, author, committer, message_encoding, message); - else - abort(); - - return error; -} - -int git_rebase_abort(git_rebase *rebase) -{ - git_reference *orig_head_ref = NULL; - git_commit *orig_head_commit = NULL; - int error; - - GIT_ASSERT_ARG(rebase); - - if (rebase->inmemory) - return 0; - - error = rebase->head_detached ? - git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE, - &rebase->orig_head_id, 1, "rebase: aborting") : - git_reference_symbolic_create( - &orig_head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - "rebase: aborting"); - - if (error < 0) - goto done; - - if ((error = git_commit_lookup( - &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 || - (error = git_reset(rebase->repo, (git_object *)orig_head_commit, - GIT_RESET_HARD, &rebase->options.checkout_options)) < 0) - goto done; - - error = rebase_cleanup(rebase); - -done: - git_commit_free(orig_head_commit); - git_reference_free(orig_head_ref); - - return error; -} - -static int notes_ref_lookup(git_str *out, git_rebase *rebase) -{ - git_config *config = NULL; - int do_rewrite, error; - - if (rebase->options.rewrite_notes_ref) { - git_str_attach_notowned(out, - rebase->options.rewrite_notes_ref, - strlen(rebase->options.rewrite_notes_ref)); - return 0; - } - - if ((error = git_repository_config(&config, rebase->repo)) < 0 || - (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) { - - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - do_rewrite = 1; - } - - error = do_rewrite ? - git_config__get_string_buf(out, config, "notes.rewriteref") : - GIT_ENOTFOUND; - -done: - git_config_free(config); - return error; -} - -static int rebase_copy_note( - git_rebase *rebase, - const char *notes_ref, - git_oid *from, - git_oid *to, - const git_signature *committer) -{ - git_note *note = NULL; - git_oid note_id; - git_signature *who = NULL; - int error; - - if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if (!committer) { - if((error = git_signature_default(&who, rebase->repo)) < 0) { - if (error != GIT_ENOTFOUND || - (error = git_signature_now(&who, "unknown", "unknown")) < 0) - goto done; - - git_error_clear(); - } - - committer = who; - } - - error = git_note_create(¬e_id, rebase->repo, notes_ref, - git_note_author(note), committer, to, git_note_message(note), 0); - -done: - git_note_free(note); - git_signature_free(who); - - return error; -} - -static int rebase_copy_notes( - git_rebase *rebase, - const git_signature *committer) -{ - git_str path = GIT_STR_INIT, rewritten = GIT_STR_INIT, notes_ref = GIT_STR_INIT; - char *pair_list, *fromstr, *tostr, *end; - git_oid from, to; - unsigned int linenum = 1; - int error = 0; - - if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if ((error = git_str_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 || - (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0) - goto done; - - pair_list = rewritten.ptr; - - while (*pair_list) { - fromstr = pair_list; - - if ((end = strchr(fromstr, '\n')) == NULL) - goto on_error; - - pair_list = end+1; - *end = '\0'; - - if ((end = strchr(fromstr, ' ')) == NULL) - goto on_error; - - tostr = end+1; - *end = '\0'; - - if (strlen(fromstr) != GIT_OID_HEXSZ || - strlen(tostr) != GIT_OID_HEXSZ || - git_oid_fromstr(&from, fromstr) < 0 || - git_oid_fromstr(&to, tostr) < 0) - goto on_error; - - if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0) - goto done; - - linenum++; - } - - goto done; - -on_error: - git_error_set(GIT_ERROR_REBASE, "invalid rewritten file at line %d", linenum); - error = -1; - -done: - git_str_dispose(&rewritten); - git_str_dispose(&path); - git_str_dispose(¬es_ref); - - return error; -} - -static int return_to_orig_head(git_rebase *rebase) -{ - git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL; - git_commit *terminal_commit = NULL; - git_str branch_msg = GIT_STR_INIT, head_msg = GIT_STR_INIT; - char onto[GIT_OID_HEXSZ]; - int error = 0; - - git_oid_fmt(onto, &rebase->onto_id); - - if ((error = git_str_printf(&branch_msg, - "rebase finished: %s onto %.*s", - rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 && - (error = git_str_printf(&head_msg, - "rebase finished: returning to %s", - rebase->orig_head_name)) == 0 && - (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 && - (error = git_reference_peel((git_object **)&terminal_commit, - terminal_ref, GIT_OBJECT_COMMIT)) == 0 && - (error = git_reference_create_matching(&branch_ref, - rebase->repo, rebase->orig_head_name, - git_commit_id(terminal_commit), 1, - &rebase->orig_head_id, branch_msg.ptr)) == 0) - error = git_reference_symbolic_create(&head_ref, - rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - head_msg.ptr); - - git_str_dispose(&head_msg); - git_str_dispose(&branch_msg); - git_commit_free(terminal_commit); - git_reference_free(head_ref); - git_reference_free(branch_ref); - git_reference_free(terminal_ref); - - return error; -} - -int git_rebase_finish( - git_rebase *rebase, - const git_signature *signature) -{ - int error = 0; - - GIT_ASSERT_ARG(rebase); - - if (rebase->inmemory) - return 0; - - if (!rebase->head_detached) - error = return_to_orig_head(rebase); - - if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0) - error = rebase_cleanup(rebase); - - return error; -} - -const char *git_rebase_orig_head_name(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return rebase->orig_head_name; -} - -const git_oid *git_rebase_orig_head_id(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return &rebase->orig_head_id; -} - -const char *git_rebase_onto_name(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - 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) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); - - return git_array_size(rebase->operations); -} - -size_t git_rebase_operation_current(git_rebase *rebase) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); - - return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION; -} - -git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - - return git_array_get(rebase->operations, idx); -} - -void git_rebase_free(git_rebase *rebase) -{ - if (rebase == NULL) - return; - - git_index_free(rebase->index); - git_commit_free(rebase->last_commit); - git__free(rebase->onto_name); - git__free(rebase->orig_head_name); - git__free(rebase->state_path); - git_array_clear(rebase->operations); - git__free((char *)rebase->options.rewrite_notes_ref); - git__free(rebase); -} diff --git a/src/refdb.c b/src/refdb.c deleted file mode 100644 index ed33de92b..000000000 --- a/src/refdb.c +++ /dev/null @@ -1,424 +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 "refdb.h" - -#include "git2/object.h" -#include "git2/refs.h" -#include "git2/refdb.h" -#include "git2/sys/refdb_backend.h" - -#include "hash.h" -#include "refs.h" -#include "reflog.h" -#include "posix.h" - -#define DEFAULT_NESTING_LEVEL 5 -#define MAX_NESTING_LEVEL 10 - -int git_refdb_new(git_refdb **out, git_repository *repo) -{ - git_refdb *db; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - db = git__calloc(1, sizeof(*db)); - GIT_ERROR_CHECK_ALLOC(db); - - db->repo = repo; - - *out = db; - GIT_REFCOUNT_INC(db); - return 0; -} - -int git_refdb_open(git_refdb **out, git_repository *repo) -{ - git_refdb *db; - git_refdb_backend *dir; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if (git_refdb_new(&db, repo) < 0) - return -1; - - /* Add the default (filesystem) backend */ - if (git_refdb_backend_fs(&dir, repo) < 0) { - git_refdb_free(db); - return -1; - } - - db->repo = repo; - db->backend = dir; - - *out = db; - return 0; -} - -static void refdb_free_backend(git_refdb *db) -{ - if (db->backend) - db->backend->free(db->backend); -} - -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; - - return 0; -} - -int git_refdb_compress(git_refdb *db) -{ - GIT_ASSERT_ARG(db); - - if (db->backend->compress) - return db->backend->compress(db->backend); - - return 0; -} - -void git_refdb__free(git_refdb *db) -{ - refdb_free_backend(db); - git__memzero(db, sizeof(*db)); - git__free(db); -} - -void git_refdb_free(git_refdb *db) -{ - if (db == NULL) - return; - - GIT_REFCOUNT_DEC(db, git_refdb__free); -} - -int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name) -{ - GIT_ASSERT_ARG(exists); - GIT_ASSERT_ARG(refdb); - GIT_ASSERT_ARG(refdb->backend); - - return refdb->backend->exists(exists, refdb->backend, ref_name); -} - -int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) -{ - git_reference *ref; - int error; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref_name); - - error = db->backend->lookup(&ref, db->backend, ref_name); - if (error < 0) - return error; - - GIT_REFCOUNT_INC(db); - ref->db = db; - - *out = ref; - return 0; -} - -int git_refdb_resolve( - git_reference **out, - git_refdb *db, - const char *ref_name, - int max_nesting) -{ - git_reference *ref = NULL; - int error = 0, nesting; - - *out = NULL; - - if (max_nesting > MAX_NESTING_LEVEL) - max_nesting = MAX_NESTING_LEVEL; - else if (max_nesting < 0) - max_nesting = DEFAULT_NESTING_LEVEL; - - if ((error = git_refdb_lookup(&ref, db, ref_name)) < 0) - goto out; - - for (nesting = 0; nesting < max_nesting; nesting++) { - git_reference *resolved; - - if (ref->type == GIT_REFERENCE_DIRECT) - break; - - if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) { - /* If we found a symbolic reference with a nonexistent target, return it. */ - if (error == GIT_ENOTFOUND) { - error = 0; - *out = ref; - ref = NULL; - } - goto out; - } - - git_reference_free(ref); - ref = resolved; - } - - if (ref->type != GIT_REFERENCE_DIRECT && max_nesting != 0) { - git_error_set(GIT_ERROR_REFERENCE, - "cannot resolve reference (>%u levels deep)", max_nesting); - error = -1; - goto out; - } - - *out = ref; - ref = NULL; -out: - git_reference_free(ref); - return error; -} - -int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob) -{ - int error; - - if (!db->backend || !db->backend->iterator) { - git_error_set(GIT_ERROR_REFERENCE, "this backend doesn't support iterators"); - return -1; - } - - if ((error = db->backend->iterator(out, db->backend, glob)) < 0) - return error; - - GIT_REFCOUNT_INC(db); - (*out)->db = db; - - return 0; -} - -int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter) -{ - int error; - - if ((error = iter->next(out, iter)) < 0) - return error; - - GIT_REFCOUNT_INC(iter->db); - (*out)->db = iter->db; - - return 0; -} - -int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter) -{ - return iter->next_name(out, iter); -} - -void git_refdb_iterator_free(git_reference_iterator *iter) -{ - GIT_REFCOUNT_DEC(iter->db, git_refdb__free); - iter->free(iter); -} - -int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - GIT_REFCOUNT_INC(db); - ref->db = db; - - return db->backend->write(db->backend, ref, force, who, message, old_id, old_target); -} - -int git_refdb_rename( - git_reference **out, - git_refdb *db, - const char *old_name, - const char *new_name, - int force, - const git_signature *who, - const char *message) -{ - int error; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message); - if (error < 0) - return error; - - if (out) { - GIT_REFCOUNT_INC(db); - (*out)->db = db; - } - - return 0; -} - -int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - return db->backend->del(db->backend, ref_name, old_id, old_target); -} - -int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) -{ - int error; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) - return error; - - GIT_REFCOUNT_INC(db); - (*out)->db = db; - - return 0; -} - -int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref) -{ - int error, logall; - - error = git_repository__configmap_lookup(&logall, db->repo, GIT_CONFIGMAP_LOGALLREFUPDATES); - if (error < 0) - return error; - - /* Defaults to the opposite of the repo being bare */ - if (logall == GIT_LOGALLREFUPDATES_UNSET) - logall = !git_repository_is_bare(db->repo); - - *out = 0; - switch (logall) { - case GIT_LOGALLREFUPDATES_FALSE: - *out = 0; - break; - - case GIT_LOGALLREFUPDATES_TRUE: - /* Only write if it already has a log, - * or if it's under heads/, remotes/ or notes/ - */ - *out = git_refdb_has_log(db, ref->name) || - !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) || - !git__strcmp(ref->name, GIT_HEAD_FILE) || - !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) || - !git__prefixcmp(ref->name, GIT_REFS_NOTES_DIR); - break; - - case GIT_LOGALLREFUPDATES_ALWAYS: - *out = 1; - break; - } - - return 0; -} - -int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref) -{ - git_reference *head = NULL, *resolved = NULL; - const char *name; - int error; - - *out = 0; - - if (ref->type == GIT_REFERENCE_SYMBOLIC) { - error = 0; - goto out; - } - - if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0) - goto out; - - if (git_reference_type(head) == GIT_REFERENCE_DIRECT) - goto out; - - /* Go down the symref chain until we find the branch */ - if ((error = git_refdb_resolve(&resolved, db, git_reference_symbolic_target(head), -1)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - name = git_reference_symbolic_target(head); - } else if (git_reference_type(resolved) == GIT_REFERENCE_SYMBOLIC) { - name = git_reference_symbolic_target(resolved); - } else { - name = git_reference_name(resolved); - } - - if (strcmp(name, ref->name)) - goto out; - - *out = 1; - -out: - git_reference_free(resolved); - git_reference_free(head); - return error; -} - -int git_refdb_has_log(git_refdb *db, const char *refname) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(refname); - - return db->backend->has_log(db->backend, refname); -} - -int git_refdb_ensure_log(git_refdb *db, const char *refname) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(refname); - - return db->backend->ensure_log(db->backend, refname); -} - -int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT); - return 0; -} - -int git_refdb_lock(void **payload, git_refdb *db, const char *refname) -{ - GIT_ASSERT_ARG(payload); - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(refname); - - if (!db->backend->lock) { - git_error_set(GIT_ERROR_REFERENCE, "backend does not support locking"); - return -1; - } - - return db->backend->lock(payload, db->backend, refname); -} - -int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) -{ - GIT_ASSERT_ARG(db); - - return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message); -} diff --git a/src/refdb.h b/src/refdb.h deleted file mode 100644 index 84e19b1c3..000000000 --- a/src/refdb.h +++ /dev/null @@ -1,128 +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_refdb_h__ -#define INCLUDE_refdb_h__ - -#include "common.h" - -#include "git2/refdb.h" -#include "repository.h" - -struct git_refdb { - git_refcount rc; - git_repository *repo; - git_refdb_backend *backend; -}; - -void git_refdb__free(git_refdb *db); - -int git_refdb_exists( - int *exists, - git_refdb *refdb, - const char *ref_name); - -int git_refdb_lookup( - git_reference **out, - git_refdb *refdb, - const char *ref_name); - -/** - * Resolve the reference by following symbolic references. - * - * Given a reference name, this function will follow any symbolic references up - * to `max_nesting` deep and return the resolved direct reference. If any of - * the intermediate symbolic references points to a non-existing reference, - * then that symbolic reference is returned instead with an error code of `0`. - * If the given reference is a direct reference already, it is returned - * directly. - * - * If `max_nesting` is `0`, the reference will not be resolved. If it's - * negative, it will be set to the default resolve depth which is `5`. - * - * @param out Pointer to store the result in. - * @param db The refdb to use for resolving the reference. - * @param ref_name The reference name to lookup and resolve. - * @param max_nesting The maximum nesting depth. - * @return `0` on success, a negative error code otherwise. - */ -int git_refdb_resolve( - git_reference **out, - git_refdb *db, - const char *ref_name, - int max_nesting); - -int git_refdb_rename( - git_reference **out, - git_refdb *db, - const char *old_name, - const char *new_name, - int force, - const git_signature *who, - const char *message); - -int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob); -int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter); -int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter); -void git_refdb_iterator_free(git_reference_iterator *iter); - -int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target); -int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target); - -int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); -int git_refdb_reflog_write(git_reflog *reflog); - -/** - * Determine whether a reflog entry should be created for the given reference. - * - * Whether or not writing to a reference should create a reflog entry is - * dependent on a number of things. Most importantly, there's the - * "core.logAllRefUpdates" setting that controls in which situations a - * reference should get a corresponding reflog entry. The following values for - * it are understood: - * - * - "false": Do not log reference updates. - * - * - "true": Log normal reference updates. This will write entries for - * references in "refs/heads", "refs/remotes", "refs/notes" and - * "HEAD" or if the reference already has a log entry. - * - * - "always": Always create a reflog entry. - * - * If unset, the value will default to "true" for non-bare repositories and - * "false" for bare ones. - * - * @param out pointer to which the result will be written, `1` means a reflog - * entry should be written, `0` means none should be written. - * @param db The refdb to decide this for. - * @param ref The reference one wants to check. - * @return `0` on success, a negative error code otherwise. - */ -int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref); - -/** - * Determine whether a reflog entry should be created for HEAD if creating one - * for the given reference - * - * In case the given reference is being pointed to by HEAD, then creating a - * reflog entry for this reference also requires us to create a corresponding - * reflog entry for HEAD. This function can be used to determine that scenario. - * - * @param out pointer to which the result will be written, `1` means a reflog - * entry should be written, `0` means none should be written. - * @param db The refdb to decide this for. - * @param ref The reference one wants to check. - * @return `0` on success, a negative error code otherwise. - */ -int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref); - -int git_refdb_has_log(git_refdb *db, const char *refname); -int git_refdb_ensure_log(git_refdb *refdb, const char *refname); - -int git_refdb_lock(void **payload, git_refdb *db, const char *refname); -int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); - -#endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c deleted file mode 100644 index 95bda9404..000000000 --- a/src/refdb_fs.c +++ /dev/null @@ -1,2464 +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 "refs.h" -#include "hash.h" -#include "repository.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 "path.h" - -#include -#include -#include -#include -#include -#include -#include - -#define DEFAULT_NESTING_LEVEL 5 -#define MAX_NESTING_LEVEL 10 - -enum { - PACKREF_HAS_PEEL = 1, - PACKREF_WAS_LOOSE = 2, - PACKREF_CANNOT_PEEL = 4, - PACKREF_SHADOWED = 8 -}; - -enum { - PEELING_NONE = 0, - PEELING_STANDARD, - PEELING_FULL -}; - -struct packref { - git_oid oid; - git_oid peel; - char flags; - char name[GIT_FLEX_ARRAY]; -}; - -typedef struct refdb_fs_backend { - git_refdb_backend parent; - - git_repository *repo; - /* path to git directory */ - char *gitpath; - /* path to common objects' directory */ - char *commonpath; - - git_sortedcache *refcache; - int peeling_mode; - git_iterator_flag_t iterator_flags; - uint32_t direach_flags; - int fsync; - git_map packed_refs_map; - git_mutex prlock; /* protect packed_refs_map */ - git_futils_filestamp packed_refs_stamp; - bool sorted; -} refdb_fs_backend; - -static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name); -static char *packed_set_peeling_mode(char *data, size_t data_sz, refdb_fs_backend *backend); - -GIT_INLINE(int) loose_path( - git_str *out, - const char *base, - const char *refname) -{ - if (git_str_joinpath(out, base, refname) < 0) - return -1; - - return git_fs_path_validate_str_length_with_suffix(out, - CONST_STRLEN(".lock")); -} - -GIT_INLINE(int) reflog_path( - git_str *out, - git_repository *repo, - const char *refname) -{ - const char *base; - int error; - - base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir : - repo->commondir; - - if ((error = git_str_joinpath(out, base, GIT_REFLOG_DIR)) < 0) - return error; - - return loose_path(out, out->ptr, refname); -} - -static int packref_cmp(const void *a_, const void *b_) -{ - const struct packref *a = a_, *b = b_; - return strcmp(a->name, b->name); -} - -static int packed_reload(refdb_fs_backend *backend) -{ - int error; - git_str packedrefs = GIT_STR_INIT; - char *scan, *eof, *eol; - - if (!backend->gitpath) - return 0; - - error = git_sortedcache_lockandload(backend->refcache, &packedrefs); - - /* - * If we can't find the packed-refs, clear table and return. - * Any other error just gets passed through. - * If no error, and file wasn't changed, just return. - * Anything else means we need to refresh the packed refs. - */ - if (error <= 0) { - if (error == GIT_ENOTFOUND) { - GIT_UNUSED(git_sortedcache_clear(backend->refcache, true)); - git_error_clear(); - error = 0; - } - return error; - } - - /* At this point, refresh the packed refs from the loaded buffer. */ - - GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); - - scan = packedrefs.ptr; - eof = scan + packedrefs.size; - - scan = packed_set_peeling_mode(scan, packedrefs.size, backend); - if (!scan) - goto parse_failed; - - while (scan < eof && *scan == '#') { - if (!(eol = strchr(scan, '\n'))) - goto parse_failed; - scan = eol + 1; - } - - while (scan < eof) { - struct packref *ref; - git_oid oid; - - /* parse " \n" */ - - if (git_oid_fromstr(&oid, scan) < 0) - goto parse_failed; - scan += GIT_OID_HEXSZ; - - if (*scan++ != ' ') - goto parse_failed; - if (!(eol = strchr(scan, '\n'))) - goto parse_failed; - *eol = '\0'; - if (eol[-1] == '\r') - eol[-1] = '\0'; - - if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0) - goto parse_failed; - scan = eol + 1; - - git_oid_cpy(&ref->oid, &oid); - - /* look for optional "^\n" */ - - if (*scan == '^') { - if (git_oid_fromstr(&oid, scan + 1) < 0) - goto parse_failed; - scan += GIT_OID_HEXSZ + 1; - - if (scan < eof) { - if (!(eol = strchr(scan, '\n'))) - goto parse_failed; - scan = eol + 1; - } - - git_oid_cpy(&ref->peel, &oid); - ref->flags |= PACKREF_HAS_PEEL; - } - else if (backend->peeling_mode == PEELING_FULL || - (backend->peeling_mode == PEELING_STANDARD && - git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) - ref->flags |= PACKREF_CANNOT_PEEL; - } - - git_sortedcache_wunlock(backend->refcache); - git_str_dispose(&packedrefs); - - return 0; - -parse_failed: - git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); - - GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); - git_sortedcache_wunlock(backend->refcache); - git_str_dispose(&packedrefs); - - return -1; -} - -static int loose_parse_oid( - git_oid *oid, const char *filename, git_str *file_content) -{ - const char *str = git_str_cstr(file_content); - - if (git_str_len(file_content) < GIT_OID_HEXSZ) - goto corrupted; - - /* we need to get 40 OID characters from the file */ - if (git_oid_fromstr(oid, str) < 0) - goto corrupted; - - /* If the file is longer than 40 chars, the 41st must be a space */ - str += GIT_OID_HEXSZ; - if (*str == '\0' || git__isspace(*str)) - return 0; - -corrupted: - git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename); - return -1; -} - -static int loose_readbuffer(git_str *buf, const char *base, const char *path) -{ - int error; - - if ((error = loose_path(buf, base, path)) < 0 || - (error = git_futils_readbuffer(buf, buf->ptr)) < 0) - git_str_dispose(buf); - - return error; -} - -static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) -{ - int error = 0; - git_str ref_file = GIT_STR_INIT; - struct packref *ref = NULL; - git_oid oid; - - /* if we fail to load the loose reference, assume someone changed - * the filesystem under us and skip it... - */ - if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) { - git_error_clear(); - goto done; - } - - /* skip symbolic refs */ - if (!git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF)) - goto done; - - /* parse OID from file */ - if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) - goto done; - - if ((error = git_sortedcache_wlock(backend->refcache)) < 0) - goto done; - - if (!(error = git_sortedcache_upsert( - (void **)&ref, backend->refcache, name))) { - - git_oid_cpy(&ref->oid, &oid); - ref->flags = PACKREF_WAS_LOOSE; - } - - git_sortedcache_wunlock(backend->refcache); - -done: - git_str_dispose(&ref_file); - return error; -} - -static int _dirent_loose_load(void *payload, git_str *full_path) -{ - refdb_fs_backend *backend = payload; - const char *file_path; - - if (git__suffixcmp(full_path->ptr, ".lock") == 0) - return 0; - - if (git_fs_path_isdir(full_path->ptr)) { - int error = git_fs_path_direach( - full_path, backend->direach_flags, _dirent_loose_load, backend); - /* Race with the filesystem, ignore it */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - - return error; - } - - file_path = full_path->ptr + strlen(backend->gitpath); - - return loose_lookup_to_packfile(backend, file_path); -} - -/* - * Load all the loose references from the repository - * into the in-memory Packfile, and build a vector with - * all the references so it can be written back to - * disk. - */ -static int packed_loadloose(refdb_fs_backend *backend) -{ - int error; - git_str refs_path = GIT_STR_INIT; - - if (git_str_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0) - return -1; - - /* - * Load all the loose files from disk into the Packfile table. - * This will overwrite any old packed entries with their - * updated loose versions - */ - error = git_fs_path_direach( - &refs_path, backend->direach_flags, _dirent_loose_load, backend); - - git_str_dispose(&refs_path); - - return error; -} - -static int refdb_fs_backend__exists( - int *exists, - git_refdb_backend *_backend, - const char *ref_name) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_str ref_path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(backend); - - *exists = 0; - - if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0) - goto out; - - if (git_fs_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_str_dispose(&ref_path); - return error; -} - -static const char *loose_parse_symbolic(git_str *file_content) -{ - const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); - const char *refname_start; - - refname_start = (const char *)file_content->ptr; - - if (git_str_len(file_content) < header_len + 1) { - git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file"); - return NULL; - } - - /* - * Assume we have already checked for the header - * before calling this function - */ - refname_start += header_len; - - return refname_start; -} - -/* - * Returns whether a reference is stored per worktree or not. - * Per-worktree references are: - * - * - all pseudorefs, e.g. HEAD and MERGE_HEAD - * - all references stored inside of "refs/bisect/" - */ -static bool is_per_worktree_ref(const char *ref_name) -{ - return git__prefixcmp(ref_name, "refs/") != 0 || - git__prefixcmp(ref_name, "refs/bisect/") == 0; -} - -static int loose_lookup( - git_reference **out, - refdb_fs_backend *backend, - const char *ref_name) -{ - git_str ref_file = GIT_STR_INIT; - int error = 0; - const char *ref_dir; - - if (out) - *out = NULL; - - if (is_per_worktree_ref(ref_name)) - ref_dir = backend->gitpath; - else - ref_dir = backend->commonpath; - - if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0) - /* cannot read loose ref file - gah */; - else if (git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF) == 0) { - const char *target; - - git_str_rtrim(&ref_file); - - if (!(target = loose_parse_symbolic(&ref_file))) - error = -1; - else if (out != NULL) - *out = git_reference__alloc_symbolic(ref_name, target); - } else { - git_oid oid; - - if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) && - out != NULL) - *out = git_reference__alloc(ref_name, &oid, NULL); - } - - git_str_dispose(&ref_file); - return error; -} - -static int ref_error_notfound(const char *name) -{ - git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name); - return GIT_ENOTFOUND; -} - -static char *packed_set_peeling_mode( - char *data, - size_t data_sz, - refdb_fs_backend *backend) -{ - static const char *traits_header = "# pack-refs with:"; - char *eol; - backend->peeling_mode = PEELING_NONE; - - if (git__prefixncmp(data, data_sz, traits_header) == 0) { - size_t hdr_sz = strlen(traits_header); - const char *sorted = " sorted "; - const char *peeled = " peeled "; - const char *fully_peeled = " fully-peeled "; - data += hdr_sz; - data_sz -= hdr_sz; - - eol = memchr(data, '\n', data_sz); - - if (!eol) - return NULL; - - if (git__memmem(data, eol - data, fully_peeled, strlen(fully_peeled))) - backend->peeling_mode = PEELING_FULL; - else if (git__memmem(data, eol - data, peeled, strlen(peeled))) - backend->peeling_mode = PEELING_STANDARD; - - backend->sorted = NULL != git__memmem(data, eol - data, sorted, strlen(sorted)); - - return eol + 1; - } - return data; -} - -static void packed_map_free(refdb_fs_backend *backend) -{ - if (backend->packed_refs_map.data) { -#ifdef GIT_WIN32 - git__free(backend->packed_refs_map.data); -#else - git_futils_mmap_free(&backend->packed_refs_map); -#endif - backend->packed_refs_map.data = NULL; - backend->packed_refs_map.len = 0; - git_futils_filestamp_set(&backend->packed_refs_stamp, NULL); - } -} - -static int packed_map_check(refdb_fs_backend *backend) -{ - int error = 0; - git_file fd = -1; - struct stat st; - - if ((error = git_mutex_lock(&backend->prlock)) < 0) - return error; - - if (backend->packed_refs_map.data && - !git_futils_filestamp_check( - &backend->packed_refs_stamp, backend->refcache->path)) { - git_mutex_unlock(&backend->prlock); - return error; - } - packed_map_free(backend); - - fd = git_futils_open_ro(backend->refcache->path); - if (fd < 0) { - git_mutex_unlock(&backend->prlock); - if (fd == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - return fd; - } - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_mutex_unlock(&backend->prlock); - git_error_set(GIT_ERROR_OS, "unable to stat packed-refs '%s'", backend->refcache->path); - return -1; - } - - if (st.st_size == 0) { - p_close(fd); - git_mutex_unlock(&backend->prlock); - return 0; - } - - git_futils_filestamp_set_from_stat(&backend->packed_refs_stamp, &st); - -#ifdef GIT_WIN32 - /* on windows, we copy the entire file into memory rather than using - * mmap() because using mmap() on windows also locks the file and this - * map is long-lived. */ - backend->packed_refs_map.len = (size_t)st.st_size; - backend->packed_refs_map.data = - git__malloc(backend->packed_refs_map.len); - GIT_ERROR_CHECK_ALLOC(backend->packed_refs_map.data); - { - ssize_t bytesread = - p_read(fd, backend->packed_refs_map.data, - backend->packed_refs_map.len); - error = (bytesread == (ssize_t)backend->packed_refs_map.len) ? 0 : -1; - } -#else - error = git_futils_mmap_ro(&backend->packed_refs_map, fd, 0, (size_t)st.st_size); -#endif - p_close(fd); - if (error < 0) { - git_mutex_unlock(&backend->prlock); - return error; - } - - packed_set_peeling_mode( - backend->packed_refs_map.data, backend->packed_refs_map.len, - backend); - - git_mutex_unlock(&backend->prlock); - return error; -} - -/* - * Find beginning of packed-ref record pointed to by p. - * buf - a lower-bound pointer to some memory buffer - * p - an upper-bound pointer to the same memory buffer - */ -static const char *start_of_record(const char *buf, const char *p) -{ - const char *nl = p; - while (true) { - nl = git__memrchr(buf, '\n', nl - buf); - if (!nl) - return buf; - - if (nl[1] == '^' && nl > buf) - --nl; - else - break; - }; - return nl + 1; -} - -/* - * Find end of packed-ref record pointed to by p. - * end - an upper-bound pointer to some memory buffer - * p - a lower-bound pointer to the same memory buffer - */ -static const char *end_of_record(const char *p, const char *end) -{ - while (1) { - size_t sz = end - p; - p = memchr(p, '\n', sz); - if (!p) - return end; - ++p; - if (p < end && p[0] == '^') - ++p; - else - break; - } - return p; -} - -static int -cmp_record_to_refname(const char *rec, size_t data_end, const char *ref_name) -{ - const size_t ref_len = strlen(ref_name); - int cmp_val; - const char *end; - - rec += GIT_OID_HEXSZ + 1; /* + space */ - if (data_end < GIT_OID_HEXSZ + 3) { - /* an incomplete (corrupt) record is treated as less than ref_name */ - return -1; - } - data_end -= GIT_OID_HEXSZ + 1; - - end = memchr(rec, '\n', data_end); - if (end) - data_end = end - rec; - - cmp_val = memcmp(rec, ref_name, min(ref_len, data_end)); - - if (cmp_val == 0 && data_end != ref_len) - return (data_end > ref_len) ? 1 : -1; - return cmp_val; -} - -static int packed_unsorted_lookup( - git_reference **out, - refdb_fs_backend *backend, - const char *ref_name) -{ - int error = 0; - struct packref *entry; - - if ((error = packed_reload(backend)) < 0) - return error; - - if (git_sortedcache_rlock(backend->refcache) < 0) - return -1; - - entry = git_sortedcache_lookup(backend->refcache, ref_name); - if (!entry) { - error = ref_error_notfound(ref_name); - } else { - *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel); - if (!*out) - error = -1; - } - - git_sortedcache_runlock(backend->refcache); - - return error; -} - -static int packed_lookup( - git_reference **out, - refdb_fs_backend *backend, - const char *ref_name) -{ - int error = 0; - const char *left, *right, *data_end; - - if ((error = packed_map_check(backend)) < 0) - return error; - - if (!backend->sorted) - return packed_unsorted_lookup(out, backend, ref_name); - - left = backend->packed_refs_map.data; - right = data_end = (const char *) backend->packed_refs_map.data + - backend->packed_refs_map.len; - - while (left < right && *left == '#') { - if (!(left = memchr(left, '\n', data_end - left))) - goto parse_failed; - left++; - } - - while (left < right) { - const char *mid, *rec; - int compare; - - mid = left + (right - left) / 2; - rec = start_of_record(left, mid); - compare = cmp_record_to_refname(rec, data_end - rec, ref_name); - - if (compare < 0) { - left = end_of_record(mid, right); - } else if (compare > 0) { - right = rec; - } else { - const char *eol; - git_oid oid, peel, *peel_ptr = NULL; - - if (data_end - rec < GIT_OID_HEXSZ || - git_oid_fromstr(&oid, rec) < 0) { - goto parse_failed; - } - rec += GIT_OID_HEXSZ + 1; - if (!(eol = memchr(rec, '\n', data_end - rec))) { - goto parse_failed; - } - - /* look for optional "^\n" */ - - if (eol + 1 < data_end) { - rec = eol + 1; - - if (*rec == '^') { - rec++; - if (data_end - rec < GIT_OID_HEXSZ || - git_oid_fromstr(&peel, rec) < 0) { - goto parse_failed; - } - peel_ptr = &peel; - } - } - - *out = git_reference__alloc(ref_name, &oid, peel_ptr); - if (!*out) { - return -1; - } - - return 0; - } - } - return GIT_ENOTFOUND; - -parse_failed: - git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); - return -1; -} - -static int refdb_fs_backend__lookup( - git_reference **out, - git_refdb_backend *_backend, - const char *ref_name) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - int error; - - GIT_ASSERT_ARG(backend); - - if (!(error = loose_lookup(out, backend, ref_name))) - return 0; - - /* only try to lookup this reference on the packfile if it - * wasn't found on the loose refs; not if there was a critical error */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = packed_lookup(out, backend, ref_name); - } - return error; -} - -typedef struct { - git_reference_iterator parent; - - char *glob; - - git_pool pool; - git_vector loose; - - git_sortedcache *cache; - size_t loose_pos; - size_t packed_pos; -} refdb_fs_iter; - -static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) -{ - refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); - - git_vector_free(&iter->loose); - git_pool_clear(&iter->pool); - git_sortedcache_free(iter->cache); - git__free(iter); -} - -static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) -{ - int error = 0; - git_str path = GIT_STR_INIT; - git_iterator *fsit = NULL; - git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry = NULL; - const char *ref_prefix = GIT_REFS_DIR; - size_t ref_prefix_len = strlen(ref_prefix); - - if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ - return 0; - - fsit_opts.flags = backend->iterator_flags; - - if (iter->glob) { - const char *last_sep = NULL; - const char *pos; - for (pos = iter->glob; *pos; ++pos) { - switch (*pos) { - case '?': - case '*': - case '[': - case '\\': - break; - case '/': - last_sep = pos; - /* FALLTHROUGH */ - default: - continue; - } - break; - } - if (last_sep) { - ref_prefix = iter->glob; - ref_prefix_len = (last_sep - ref_prefix) + 1; - } - } - - if ((error = git_str_puts(&path, backend->commonpath)) < 0 || - (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) { - git_str_dispose(&path); - return error; - } - - if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { - git_str_dispose(&path); - return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; - } - - error = git_str_sets(&path, ref_prefix); - - while (!error && !git_iterator_advance(&entry, fsit)) { - const char *ref_name; - char *ref_dup; - - git_str_truncate(&path, ref_prefix_len); - git_str_puts(&path, entry->path); - ref_name = git_str_cstr(&path); - - if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) - continue; - - ref_dup = git_pool_strdup(&iter->pool, ref_name); - if (!ref_dup) - error = -1; - else - error = git_vector_insert(&iter->loose, ref_dup); - } - - git_iterator_free(fsit); - git_str_dispose(&path); - - return error; -} - -static int refdb_fs_backend__iterator_next( - git_reference **out, git_reference_iterator *_iter) -{ - int error = GIT_ITEROVER; - 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) { - ref = git_sortedcache_lookup(iter->cache, path); - if (ref) - ref->flags |= PACKREF_SHADOWED; - - return 0; - } - - git_error_clear(); - } - - error = GIT_ITEROVER; - while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { - ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); - if (!ref) /* stop now if another thread deleted refs and we past end */ - break; - - if (ref->flags & PACKREF_SHADOWED) - continue; - if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) - continue; - - *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); - error = (*out != NULL) ? 0 : -1; - break; - } - - return error; -} - -static int refdb_fs_backend__iterator_next_name( - const char **out, git_reference_iterator *_iter) -{ - int error = GIT_ITEROVER; - 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; - } - - git_error_clear(); - } - - error = GIT_ITEROVER; - while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { - ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); - if (!ref) /* stop now if another thread deleted refs and we past end */ - break; - - if (ref->flags & PACKREF_SHADOWED) - continue; - if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) - continue; - - *out = ref->name; - error = 0; - break; - } - - return error; -} - -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; - - GIT_ASSERT_ARG(backend); - - iter = git__calloc(1, sizeof(refdb_fs_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - if ((error = git_pool_init(&iter->pool, 1)) < 0) - goto out; - - if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0) - goto out; - - if (glob != NULL && - (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; - - *out = (git_reference_iterator *)iter; -out: - if (error) - refdb_fs_backend__iterator_free((git_reference_iterator *)iter); - return error; -} - -static bool ref_is_available( - const char *old_ref, const char *new_ref, const char *this_ref) -{ - if (old_ref == NULL || strcmp(old_ref, this_ref)) { - size_t reflen = strlen(this_ref); - size_t newlen = strlen(new_ref); - size_t cmplen = reflen < newlen ? reflen : newlen; - const char *lead = reflen < newlen ? new_ref : this_ref; - - if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') { - return false; - } - } - - return true; -} - -static int reference_path_available( - refdb_fs_backend *backend, - const char *new_ref, - const char *old_ref, - int force) -{ - size_t i; - int error; - - if ((error = packed_reload(backend)) < 0) - return error; - - if (!force) { - int exists; - - if ((error = refdb_fs_backend__exists( - &exists, (git_refdb_backend *)backend, new_ref)) < 0) { - return error; - } - - if (exists) { - git_error_set(GIT_ERROR_REFERENCE, - "failed to write reference '%s': a reference with " - "that name already exists.", new_ref); - return GIT_EEXISTS; - } - } - - if ((error = git_sortedcache_rlock(backend->refcache)) < 0) - return error; - - for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { - struct packref *ref = git_sortedcache_entry(backend->refcache, i); - - if (ref && !ref_is_available(old_ref, new_ref, ref->name)) { - git_sortedcache_runlock(backend->refcache); - git_error_set(GIT_ERROR_REFERENCE, - "path to reference '%s' collides with existing one", new_ref); - return -1; - } - } - - git_sortedcache_runlock(backend->refcache); - return 0; -} - -static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name) -{ - int error, filebuf_flags; - git_str ref_path = GIT_STR_INIT; - const char *basedir; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(name); - - if (!git_path_is_valid(backend->repo, name, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { - git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name); - return GIT_EINVALIDSPEC; - } - - if (is_per_worktree_ref(name)) - basedir = backend->gitpath; - else - basedir = backend->commonpath; - - /* Remove a possibly existing empty directory hierarchy - * which name would collide with the reference name - */ - if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0) - return error; - - if ((error = loose_path(&ref_path, basedir, name)) < 0) - return error; - - filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS; - if (backend->fsync) - filebuf_flags |= GIT_FILEBUF_FSYNC; - - error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE); - - if (error == GIT_EDIRECTORY) - git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name); - - git_str_dispose(&ref_path); - return error; -} - -static int loose_commit(git_filebuf *file, const git_reference *ref) -{ - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(ref); - - if (ref->type == GIT_REFERENCE_DIRECT) { - char oid[GIT_OID_HEXSZ + 1]; - git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); - - git_filebuf_printf(file, "%s\n", oid); - } else if (ref->type == GIT_REFERENCE_SYMBOLIC) { - git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic); - } else { - GIT_ASSERT(0); - } - - return git_filebuf_commit(file); -} - -static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname) -{ - int error; - git_filebuf *lock; - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - lock = git__calloc(1, sizeof(git_filebuf)); - GIT_ERROR_CHECK_ALLOC(lock); - - if ((error = loose_lock(lock, backend, refname)) < 0) { - git__free(lock); - return error; - } - - *out = lock; - return 0; -} - -static int refdb_fs_backend__write_tail( - git_refdb_backend *_backend, - const git_reference *ref, - git_filebuf *file, - int update_reflog, - const git_oid *old_id, - 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); - -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) -{ - git_filebuf *lock = (git_filebuf *) payload; - int error = 0; - - 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, NULL, NULL, sig, message); - else - git_filebuf_cleanup(lock); - - git__free(lock); - return error; -} - -/* - * Find out what object this reference resolves to. - * - * For references that point to a 'big' tag (e.g. an - * actual tag object on the repository), we need to - * cache on the packfile the OID of the object to - * which that 'big tag' is pointing to. - */ -static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) -{ - git_object *object; - - if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL) - return 0; - - /* - * Find the tagged object in the repository - */ - if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0) - return -1; - - /* - * If the tagged object is a Tag object, we need to resolve it; - * if the ref is actually a 'weak' ref, we don't need to resolve - * anything. - */ - if (git_object_type(object) == GIT_OBJECT_TAG) { - git_tag *tag = (git_tag *)object; - - /* - * Find the object pointed at by this tag - */ - git_oid_cpy(&ref->peel, git_tag_target_id(tag)); - ref->flags |= PACKREF_HAS_PEEL; - - /* - * The reference has now cached the resolved OID, and is - * marked at such. When written to the packfile, it'll be - * accompanied by this resolved oid - */ - } - - git_object_free(object); - return 0; -} - -/* - * Write a single reference into a packfile - */ -static int packed_write_ref(struct packref *ref, git_filebuf *file) -{ - char oid[GIT_OID_HEXSZ + 1]; - git_oid_nfmt(oid, sizeof(oid), &ref->oid); - - /* - * For references that peel to an object in the repo, we must - * write the resulting peel on a separate line, e.g. - * - * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 - * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 - * - * This obviously only applies to tags. - * The required peels have already been loaded into `ref->peel_target`. - */ - if (ref->flags & PACKREF_HAS_PEEL) { - char peel[GIT_OID_HEXSZ + 1]; - git_oid_nfmt(peel, sizeof(peel), &ref->peel); - - if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) - return -1; - } else { - if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) - return -1; - } - - return 0; -} - -/* - * Remove all loose references - * - * Once we have successfully written a packfile, - * all the loose references that were packed must be - * removed from disk. - * - * This is a dangerous method; make sure the packfile - * is well-written, because we are destructing references - * here otherwise. - */ -static int packed_remove_loose(refdb_fs_backend *backend) -{ - size_t i; - git_filebuf lock = GIT_FILEBUF_INIT; - git_str ref_content = GIT_STR_INIT; - int error = 0; - - /* backend->refcache is already locked when this is called */ - - for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { - struct packref *ref = git_sortedcache_entry(backend->refcache, i); - git_oid current_id; - - if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) - continue; - - git_filebuf_cleanup(&lock); - - /* We need to stop anybody from updating the ref while we try to do a safe delete */ - error = loose_lock(&lock, backend, ref->name); - /* If someone else is updating it, let them do it */ - if (error == GIT_EEXISTS || error == GIT_ENOTFOUND) - continue; - - if (error < 0) { - git_str_dispose(&ref_content); - git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name); - return error; - } - - error = git_futils_readbuffer(&ref_content, lock.path_original); - /* Someone else beat us to cleaning up the ref, let's simply continue */ - if (error == GIT_ENOTFOUND) - continue; - - /* This became a symref between us packing and trying to delete it, so ignore it */ - if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF)) - continue; - - /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */ - if (loose_parse_oid(¤t_id, lock.path_original, &ref_content) < 0) - continue; - - /* If the ref moved since we packed it, we must not delete it */ - if (!git_oid_equal(¤t_id, &ref->oid)) - continue; - - /* - * if we fail to remove a single file, this is *not* good, - * but we should keep going and remove as many as possible. - * If we fail to remove, the ref is still in the old state, so - * we haven't lost information. - */ - p_unlink(lock.path_original); - } - - git_str_dispose(&ref_content); - git_filebuf_cleanup(&lock); - return 0; -} - -/* - * Write all the contents in the in-memory packfile to disk. - */ -static int packed_write(refdb_fs_backend *backend) -{ - git_sortedcache *refcache = backend->refcache; - git_filebuf pack_file = GIT_FILEBUF_INIT; - int error, open_flags = 0; - size_t i; - - /* take lock and close up packed-refs mmap if open */ - if ((error = git_mutex_lock(&backend->prlock)) < 0) { - return error; - } - - packed_map_free(backend); - - git_mutex_unlock(&backend->prlock); - - /* lock the cache to updates while we do this */ - if ((error = git_sortedcache_wlock(refcache)) < 0) - return error; - - if (backend->fsync) - open_flags = GIT_FILEBUF_FSYNC; - - /* Open the file! */ - if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0) - goto fail; - - /* Packfiles have a header... apparently - * This is in fact not required, but we might as well print it - * just for kicks */ - if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0) - goto fail; - - for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { - struct packref *ref = git_sortedcache_entry(refcache, i); - GIT_ASSERT(ref); - - if ((error = packed_find_peel(backend, ref)) < 0) - goto fail; - - if ((error = packed_write_ref(ref, &pack_file)) < 0) - goto fail; - } - - /* if we've written all the references properly, we can commit - * the packfile to make the changes effective */ - if ((error = git_filebuf_commit(&pack_file)) < 0) - goto fail; - - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if ((error = packed_remove_loose(backend)) < 0) - goto fail; - - git_sortedcache_updated(refcache); - git_sortedcache_wunlock(refcache); - - /* we're good now */ - return 0; - -fail: - git_filebuf_cleanup(&pack_file); - git_sortedcache_wunlock(refcache); - - 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 cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name, - const git_oid *old_id, const char *old_target) -{ - int error = 0; - git_reference *old_ref = NULL; - - *cmp = 0; - /* It "matches" if there is no old value to compare against */ - if (!old_id && !old_target) - return 0; - - if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) { - if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id)) - return 0; - goto out; - } - - /* If the types don't match, there's no way the values do */ - if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) { - *cmp = -1; - goto out; - } - if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) { - *cmp = 1; - goto out; - } - - if (old_id && old_ref->type == GIT_REFERENCE_DIRECT) - *cmp = git_oid_cmp(old_id, &old_ref->target.oid); - - if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC) - *cmp = git__strcmp(old_target, old_ref->target.symbolic); - -out: - git_reference_free(old_ref); - - return error; -} - -/* - * The git.git comment regarding this, for your viewing pleasure: - * - * Special hack: If a branch is updated directly and HEAD - * points to it (may happen on the remote side of a push - * for example) then logically the HEAD reflog should be - * updated too. - * A generic solution implies reverse symref information, - * but finding all symrefs pointing to the given branch - * would be rather costly for this rare event (the direct - * update of a branch) to be worth it. So let's cheat and - * check with HEAD only which should cover 99% of all usage - * scenarios (even 100% of the default ones). - */ -static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message) -{ - git_reference *head = NULL; - git_refdb *refdb = NULL; - int error, write_reflog; - git_oid old_id; - - if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 || - (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0) - goto out; - if (!write_reflog) - goto out; - - /* if we can't resolve, we use {0}*40 as old id */ - if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0) - memset(&old_id, 0, sizeof(old_id)); - - if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 || - (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0) - goto out; - -out: - git_reference_free(head); - git_refdb_free(refdb); - return error; -} - -static int refdb_fs_backend__write( - git_refdb_backend *_backend, - const git_reference *ref, - int force, - const git_signature *who, - const char *message, - const git_oid *old_id, - const char *old_target) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_filebuf file = GIT_FILEBUF_INIT; - int error = 0; - - GIT_ASSERT_ARG(backend); - - if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0) - return error; - - /* We need to perform the reflog append and old value check under the ref's lock */ - if ((error = loose_lock(&file, backend, ref->name)) < 0) - return error; - - return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message); -} - -static int refdb_fs_backend__write_tail( - git_refdb_backend *_backend, - const git_reference *ref, - git_filebuf *file, - int update_reflog, - const git_oid *old_id, - const char *old_target, - const git_signature *who, - const char *message) -{ - 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; - - if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0) - goto on_error; - - if (cmp) { - git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); - error = GIT_EMODIFIED; - goto on_error; - } - - if (ref->type == GIT_REFERENCE_SYMBOLIC) - new_target = ref->target.symbolic; - else - new_id = &ref->target.oid; - - error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target); - if (error < 0 && error != GIT_ENOTFOUND) - goto on_error; - - /* Don't update if we have the same value */ - if (!error && !cmp) { - error = 0; - goto on_error; /* not really error */ - } - - if (update_reflog) { - git_refdb *refdb; - - if ((error = git_repository_refdb__weakptr(&refdb, backend->repo)) < 0 || - (error = git_refdb_should_write_reflog(&should_write, refdb, ref)) < 0) - goto on_error; - - if (should_write) { - if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0) - goto on_error; - if ((error = maybe_append_head(backend, ref, who, message)) < 0) - goto on_error; - } - } - - return loose_commit(file, ref); - -on_error: - git_filebuf_cleanup(file); - return error; -} - -static int refdb_fs_backend__prune_refs( - refdb_fs_backend *backend, - const char *ref_name, - const char *prefix) -{ - git_str relative_path = GIT_STR_INIT; - git_str base_path = GIT_STR_INIT; - size_t commonlen; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(ref_name); - - if ((error = git_str_sets(&relative_path, ref_name)) < 0) - goto cleanup; - - git_fs_path_squash_slashes(&relative_path); - if ((commonlen = git_fs_path_common_dirlen("refs/heads/", git_str_cstr(&relative_path))) == strlen("refs/heads/") || - (commonlen = git_fs_path_common_dirlen("refs/tags/", git_str_cstr(&relative_path))) == strlen("refs/tags/") || - (commonlen = git_fs_path_common_dirlen("refs/remotes/", git_str_cstr(&relative_path))) == strlen("refs/remotes/")) { - - git_str_truncate(&relative_path, commonlen); - - if (prefix) - error = git_str_join3(&base_path, '/', - backend->commonpath, prefix, - git_str_cstr(&relative_path)); - else - error = git_str_joinpath(&base_path, - backend->commonpath, - git_str_cstr(&relative_path)); - - if (!error) - error = git_path_validate_str_length(NULL, &base_path); - - if (error < 0) - goto cleanup; - - error = git_futils_rmdir_r(ref_name + commonlen, - git_str_cstr(&base_path), - GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT); - - if (error == GIT_ENOTFOUND) - error = 0; - } - -cleanup: - git_str_dispose(&relative_path); - git_str_dispose(&base_path); - return error; -} - -static int refdb_fs_backend__delete( - git_refdb_backend *_backend, - const char *ref_name, - const git_oid *old_id, const char *old_target) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_filebuf file = GIT_FILEBUF_INIT; - int error = 0; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(ref_name); - - if ((error = loose_lock(&file, backend, ref_name)) < 0) - return error; - - if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) { - git_filebuf_cleanup(&file); - return error; - } - - 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_str path = GIT_STR_INIT; - int error = 0; - - if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0) - return error; - - error = p_unlink(path.ptr); - if (error < 0 && errno == ENOENT) - error = GIT_ENOTFOUND; - else if (error != 0) - error = -1; - - git_str_dispose(&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 = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - int error = 0, cmp = 0; - bool packed_deleted = 0; - - error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); - if (error < 0) - goto cleanup; - - if (cmp) { - git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); - error = GIT_EMODIFIED; - goto cleanup; - } - - /* - * 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 (error == 0) - packed_deleted = 1; - - if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (error == GIT_ENOTFOUND) { - error = packed_deleted ? 0 : ref_error_notfound(ref_name); - goto cleanup; - } - -cleanup: - git_filebuf_cleanup(file); - if (error == 0) - error = refdb_fs_backend__prune_refs(backend, ref_name, ""); - return error; -} - -static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name); - -static int refdb_fs_backend__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) -{ - 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; - - GIT_ASSERT_ARG(backend); - - if ((error = reference_path_available( - backend, new_name, old_name, force)) < 0 || - (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0) - return error; - - if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) { - git_reference_free(old); - return error; - } - - new = git_reference__realloc(&old, new_name); - if (!new) { - git_reference_free(old); - return -1; - } - - if ((error = loose_lock(&file, backend, new->name)) < 0) { - git_reference_free(new); - return error; - } - - /* Try to rename the refog; it's ok if the old doesn't exist */ - error = refdb_reflog_fs__rename(_backend, old_name, new_name); - if (((error == 0) || (error == GIT_ENOTFOUND)) && - ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) { - git_reference_free(new); - git_filebuf_cleanup(&file); - return error; - } - - if (error < 0) { - git_reference_free(new); - git_filebuf_cleanup(&file); - return error; - } - - - if ((error = loose_commit(&file, new)) < 0 || out == NULL) { - git_reference_free(new); - return error; - } - - *out = new; - return 0; -} - -static int refdb_fs_backend__compress(git_refdb_backend *_backend) -{ - int error; - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - GIT_ASSERT_ARG(backend); - - if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */ - (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */ - (error = packed_write(backend)) < 0) /* write back to disk */ - return error; - - return 0; -} - -static void refdb_fs_backend__free(git_refdb_backend *_backend) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - if (!backend) - return; - - git_sortedcache_free(backend->refcache); - - git_mutex_lock(&backend->prlock); - packed_map_free(backend); - git_mutex_unlock(&backend->prlock); - git_mutex_free(&backend->prlock); - - git__free(backend->gitpath); - git__free(backend->commonpath); - git__free(backend); -} - -static char *setup_namespace(git_repository *repo, const char *in) -{ - git_str path = GIT_STR_INIT; - char *parts, *start, *end, *out = NULL; - - if (!in) - goto done; - - git_str_puts(&path, in); - - /* if the repo is not namespaced, nothing else to do */ - if (repo->namespace == NULL) { - out = git_str_detach(&path); - goto done; - } - - parts = end = git__strdup(repo->namespace); - if (parts == NULL) - goto done; - - /* - * From `man gitnamespaces`: - * namespaces which include a / will expand to a hierarchy - * of namespaces; for example, GIT_NAMESPACE=foo/bar will store - * refs under refs/namespaces/foo/refs/namespaces/bar/ - */ - while ((start = git__strsep(&end, "/")) != NULL) - git_str_printf(&path, "refs/namespaces/%s/", start); - - git_str_printf(&path, "refs/namespaces/%s/refs", end); - git__free(parts); - - /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_relative(git_str_cstr(&path), in, 0777, - GIT_MKDIR_PATH, NULL) < 0) - goto done; - - /* Return root of the namespaced gitpath, i.e. without the trailing 'refs' */ - git_str_rtruncate_at_char(&path, '/'); - git_str_putc(&path, '/'); - out = git_str_detach(&path); - -done: - git_str_dispose(&path); - return out; -} - -static int reflog_alloc(git_reflog **reflog, const char *name) -{ - git_reflog *log; - - *reflog = NULL; - - log = git__calloc(1, sizeof(git_reflog)); - GIT_ERROR_CHECK_ALLOC(log); - - log->ref_name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(log->ref_name); - - if (git_vector_init(&log->entries, 0, NULL) < 0) { - git__free(log->ref_name); - git__free(log); - return -1; - } - - *reflog = log; - - return 0; -} - -static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) -{ - git_parse_ctx parser = GIT_PARSE_CTX_INIT; - - if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0) - return -1; - - 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(*entry->committer)); - GIT_ERROR_CHECK_ALLOC(entry->committer); - - 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; - - sig = parser.line; - while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n') - git_parse_advance_chars(&parser, 1); - - if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0) - goto next; - - if (c == '\t') { - size_t len; - git_parse_advance_chars(&parser, 1); - - len = parser.line_len; - if (parser.line[len - 1] == '\n') - len--; - - entry->msg = git__strndup(parser.line, len); - GIT_ERROR_CHECK_ALLOC(entry->msg); - } - - if ((git_vector_insert(&log->entries, entry)) < 0) { - git_reflog_entry__free(entry); - return -1; - } - - continue; - -next: - git_reflog_entry__free(entry); - } - - return 0; -} - -static int create_new_reflog_file(const char *filepath) -{ - int fd, error; - - if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) - return error; - - if ((fd = p_open(filepath, - O_WRONLY | O_CREAT, - GIT_REFLOG_FILE_MODE)) < 0) - return -1; - - return p_close(fd); -} - -static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name) -{ - refdb_fs_backend *backend; - git_repository *repo; - git_str path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(_backend && name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - repo = backend->repo; - - if ((error = reflog_path(&path, repo, name)) < 0) - return error; - - error = create_new_reflog_file(git_str_cstr(&path)); - git_str_dispose(&path); - - return error; -} - -static int has_reflog(git_repository *repo, const char *name) -{ - int ret = 0; - git_str path = GIT_STR_INIT; - - if (reflog_path(&path, repo, name) < 0) - goto cleanup; - - ret = git_fs_path_isfile(git_str_cstr(&path)); - -cleanup: - git_str_dispose(&path); - return ret; -} - -static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name) -{ - refdb_fs_backend *backend; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - return has_reflog(backend->repo, name); -} - -static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) -{ - int error = -1; - git_str log_path = GIT_STR_INIT; - git_str log_file = GIT_STR_INIT; - git_reflog *log = NULL; - git_repository *repo; - refdb_fs_backend *backend; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - repo = backend->repo; - - if (reflog_alloc(&log, name) < 0) - return -1; - - if (reflog_path(&log_path, repo, name) < 0) - goto cleanup; - - error = git_futils_readbuffer(&log_file, git_str_cstr(&log_path)); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if ((error == GIT_ENOTFOUND) && - ((error = create_new_reflog_file(git_str_cstr(&log_path))) < 0)) - goto cleanup; - - if ((error = reflog_parse(log, - git_str_cstr(&log_file), git_str_len(&log_file))) < 0) - goto cleanup; - - *out = log; - goto success; - -cleanup: - git_reflog_free(log); - -success: - git_str_dispose(&log_file); - git_str_dispose(&log_path); - - return error; -} - -static int serialize_reflog_entry( - git_str *buf, - const git_oid *oid_old, - const git_oid *oid_new, - const git_signature *committer, - const char *msg) -{ - char raw_old[GIT_OID_HEXSZ+1]; - char raw_new[GIT_OID_HEXSZ+1]; - - git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); - git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); - - git_str_clear(buf); - - git_str_puts(buf, raw_old); - git_str_putc(buf, ' '); - git_str_puts(buf, raw_new); - - git_signature__writebuf(buf, " ", committer); - - /* drop trailing LF */ - git_str_rtrim(buf); - - if (msg) { - size_t i; - - git_str_putc(buf, '\t'); - git_str_puts(buf, msg); - - for (i = 0; i < buf->size - 2; i++) - if (buf->ptr[i] == '\n') - buf->ptr[i] = ' '; - git_str_rtrim(buf); - } - - git_str_putc(buf, '\n'); - - return git_str_oom(buf); -} - -static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname) -{ - git_repository *repo; - git_str log_path = GIT_STR_INIT; - int error; - - repo = backend->repo; - - if (!git_path_is_valid(backend->repo, refname, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { - git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname); - return GIT_EINVALIDSPEC; - } - - if (reflog_path(&log_path, repo, refname) < 0) - return -1; - - if (!git_fs_path_isfile(git_str_cstr(&log_path))) { - git_error_set(GIT_ERROR_INVALID, - "log file for reference '%s' doesn't exist", refname); - error = -1; - goto cleanup; - } - - error = git_filebuf_open(file, git_str_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE); - -cleanup: - git_str_dispose(&log_path); - - return error; -} - -static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) -{ - int error = -1; - unsigned int i; - git_reflog_entry *entry; - refdb_fs_backend *backend; - git_str log = GIT_STR_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(reflog); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0) - return -1; - - git_vector_foreach(&reflog->entries, i, entry) { - if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) - goto cleanup; - - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - goto cleanup; - } - - error = git_filebuf_commit(&fbuf); - goto success; - -cleanup: - git_filebuf_cleanup(&fbuf); - -success: - git_str_dispose(&log); - - return error; -} - -/* Append to the reflog, must be called under reference lock */ -static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message) -{ - int error, is_symbolic, open_flags; - git_oid old_id = {{0}}, new_id = {{0}}; - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - git_repository *repo = backend->repo; - - is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC; - - /* "normal" symbolic updates do not write */ - if (is_symbolic && - strcmp(ref->name, GIT_HEAD_FILE) && - !(old && new)) - return 0; - - /* From here on is_symbolic also means that it's HEAD */ - - if (old) { - git_oid_cpy(&old_id, old); - } else { - error = git_reference_name_to_id(&old_id, repo, ref->name); - if (error < 0 && error != GIT_ENOTFOUND) - return error; - } - - if (new) { - git_oid_cpy(&new_id, new); - } else { - if (!is_symbolic) { - git_oid_cpy(&new_id, git_reference_target(ref)); - } else { - error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref)); - if (error < 0 && error != GIT_ENOTFOUND) - return error; - /* detaching HEAD does not create an entry */ - if (error == GIT_ENOTFOUND) - return 0; - - git_error_clear(); - } - } - - if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0) - goto cleanup; - - if ((error = reflog_path(&path, repo, ref->name)) < 0) - goto cleanup; - - if (((error = git_futils_mkpath2file(git_str_cstr(&path), 0777)) < 0) && - (error != GIT_EEXISTS)) { - goto cleanup; - } - - /* If the new branch matches part of the namespace of a previously deleted branch, - * there maybe an obsolete/unused directory (or directory hierarchy) in the way. - */ - if (git_fs_path_isdir(git_str_cstr(&path))) { - if ((error = git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - } else if (git_fs_path_isdir(git_str_cstr(&path))) { - git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder", - ref->name); - error = GIT_EDIRECTORY; - } - - if (error != 0) - goto cleanup; - } - - open_flags = O_WRONLY | O_CREAT | O_APPEND; - - if (backend->fsync) - open_flags |= O_FSYNC; - - error = git_futils_writebuffer(&buf, git_str_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE); - -cleanup: - git_str_dispose(&buf); - git_str_dispose(&path); - - return error; -} - -static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) -{ - int error = 0, fd; - git_str old_path = GIT_STR_INIT; - git_str new_path = GIT_STR_INIT; - git_str temp_path = GIT_STR_INIT; - git_str normalized = GIT_STR_INIT; - git_repository *repo; - refdb_fs_backend *backend; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(old_name); - GIT_ASSERT_ARG(new_name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - repo = backend->repo; - - if ((error = git_reference__normalize_name( - &normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0) - return error; - - if (git_str_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0) - return -1; - - if ((error = loose_path(&old_path, git_str_cstr(&temp_path), old_name)) < 0) - return error; - - if ((error = loose_path(&new_path, git_str_cstr(&temp_path), git_str_cstr(&normalized))) < 0) - return error; - - if (!git_fs_path_exists(git_str_cstr(&old_path))) { - error = GIT_ENOTFOUND; - goto cleanup; - } - - /* - * Move the reflog to a temporary place. This two-phase renaming is required - * in order to cope with funny renaming use cases when one tries to move a reference - * to a partially colliding namespace: - * - a/b -> a/b/c - * - a/b/c/d -> a/b/c - */ - if ((error = loose_path(&temp_path, git_str_cstr(&temp_path), "temp_reflog")) < 0) - return error; - - if ((fd = git_futils_mktmp(&temp_path, git_str_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) { - error = -1; - goto cleanup; - } - - p_close(fd); - - if (p_rename(git_str_cstr(&old_path), git_str_cstr(&temp_path)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); - error = -1; - goto cleanup; - } - - if (git_fs_path_isdir(git_str_cstr(&new_path)) && - (git_futils_rmdir_r(git_str_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { - error = -1; - goto cleanup; - } - - if (git_futils_mkpath2file(git_str_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { - error = -1; - goto cleanup; - } - - if (p_rename(git_str_cstr(&temp_path), git_str_cstr(&new_path)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); - error = -1; - } - -cleanup: - git_str_dispose(&temp_path); - git_str_dispose(&old_path); - git_str_dispose(&new_path); - git_str_dispose(&normalized); - - return error; -} - -static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_str path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(name); - - if ((error = reflog_path(&path, backend->repo, name)) < 0) - goto out; - - if (!git_fs_path_exists(path.ptr)) - goto out; - - if ((error = p_unlink(path.ptr)) < 0) - goto out; - - error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR); - -out: - git_str_dispose(&path); - - return error; -} - -int git_refdb_backend_fs( - git_refdb_backend **backend_out, - git_repository *repository) -{ - int t = 0; - git_str gitpath = GIT_STR_INIT; - refdb_fs_backend *backend; - - backend = git__calloc(1, sizeof(refdb_fs_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - if (git_mutex_init(&backend->prlock) < 0) { - git__free(backend); - return -1; - } - - - if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0) - goto fail; - - backend->repo = repository; - - if (repository->gitdir) { - backend->gitpath = setup_namespace(repository, repository->gitdir); - - if (backend->gitpath == NULL) - goto fail; - } - - if (repository->commondir) { - backend->commonpath = setup_namespace(repository, repository->commondir); - - if (backend->commonpath == NULL) - goto fail; - } - - if (git_str_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 || - git_sortedcache_new( - &backend->refcache, offsetof(struct packref, name), - NULL, NULL, packref_cmp, git_str_cstr(&gitpath)) < 0) - goto fail; - - git_str_dispose(&gitpath); - - if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) { - backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; - backend->direach_flags |= GIT_FS_PATH_DIR_IGNORE_CASE; - } - if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) { - backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; - backend->direach_flags |= GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE; - } - 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; - - backend->parent.exists = &refdb_fs_backend__exists; - backend->parent.lookup = &refdb_fs_backend__lookup; - backend->parent.iterator = &refdb_fs_backend__iterator; - backend->parent.write = &refdb_fs_backend__write; - backend->parent.del = &refdb_fs_backend__delete; - backend->parent.rename = &refdb_fs_backend__rename; - backend->parent.compress = &refdb_fs_backend__compress; - backend->parent.lock = &refdb_fs_backend__lock; - backend->parent.unlock = &refdb_fs_backend__unlock; - backend->parent.has_log = &refdb_reflog_fs__has_log; - backend->parent.ensure_log = &refdb_reflog_fs__ensure_log; - backend->parent.free = &refdb_fs_backend__free; - backend->parent.reflog_read = &refdb_reflog_fs__read; - backend->parent.reflog_write = &refdb_reflog_fs__write; - backend->parent.reflog_rename = &refdb_reflog_fs__rename; - backend->parent.reflog_delete = &refdb_reflog_fs__delete; - - *backend_out = (git_refdb_backend *)backend; - return 0; - -fail: - git_mutex_free(&backend->prlock); - git_str_dispose(&gitpath); - git__free(backend->gitpath); - git__free(backend->commonpath); - git__free(backend); - return -1; -} diff --git a/src/reflog.c b/src/reflog.c deleted file mode 100644 index 1e9c0d4f1..000000000 --- a/src/reflog.c +++ /dev/null @@ -1,232 +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 "reflog.h" - -#include "repository.h" -#include "filebuf.h" -#include "signature.h" -#include "refdb.h" - -#include "git2/sys/refdb_backend.h" -#include "git2/sys/reflog.h" - -void git_reflog_entry__free(git_reflog_entry *entry) -{ - git_signature_free(entry->committer); - - git__free(entry->msg); - git__free(entry); -} - -void git_reflog_free(git_reflog *reflog) -{ - size_t i; - git_reflog_entry *entry; - - if (reflog == NULL) - return; - - if (reflog->db) - GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); - - for (i=0; i < reflog->entries.length; i++) { - entry = git_vector_get(&reflog->entries, i); - - git_reflog_entry__free(entry); - } - - git_vector_free(&reflog->entries); - git__free(reflog->ref_name); - git__free(reflog); -} - -int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) -{ - git_refdb *refdb; - int error; - - GIT_ASSERT_ARG(reflog); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return error; - - return git_refdb_reflog_read(reflog, refdb, name); -} - -int git_reflog_write(git_reflog *reflog) -{ - git_refdb *db; - - GIT_ASSERT_ARG(reflog); - GIT_ASSERT_ARG(reflog->db); - - db = reflog->db; - return db->backend->reflog_write(db->backend, reflog); -} - -int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) -{ - const git_reflog_entry *previous; - git_reflog_entry *entry; - - GIT_ASSERT_ARG(reflog); - GIT_ASSERT_ARG(new_oid); - GIT_ASSERT_ARG(committer); - - entry = git__calloc(1, sizeof(git_reflog_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - if ((git_signature_dup(&entry->committer, committer)) < 0) - goto cleanup; - - if (msg != NULL) { - size_t i, msglen = strlen(msg); - - if ((entry->msg = git__strndup(msg, msglen)) == NULL) - goto cleanup; - - /* - * 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); - - if (previous == NULL) - git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); - else - git_oid_cpy(&entry->oid_old, &previous->oid_cur); - - git_oid_cpy(&entry->oid_cur, new_oid); - - if (git_vector_insert(&reflog->entries, entry) < 0) - goto cleanup; - - return 0; - -cleanup: - git_reflog_entry__free(entry); - return -1; -} - -int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) -{ - git_refdb *refdb; - int error; - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return -1; - - return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); -} - -int git_reflog_delete(git_repository *repo, const char *name) -{ - git_refdb *refdb; - int error; - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return -1; - - return refdb->backend->reflog_delete(refdb->backend, name); -} - -size_t git_reflog_entrycount(git_reflog *reflog) -{ - GIT_ASSERT_ARG_WITH_RETVAL(reflog, 0); - return reflog->entries.length; -} - -const git_reflog_entry *git_reflog_entry_byindex(const git_reflog *reflog, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(reflog, NULL); - - if (idx >= reflog->entries.length) - return NULL; - - return git_vector_get( - &reflog->entries, reflog_inverse_index(idx, reflog->entries.length)); -} - -const git_oid *git_reflog_entry_id_old(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return &entry->oid_old; -} - -const git_oid *git_reflog_entry_id_new(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return &entry->oid_cur; -} - -const git_signature *git_reflog_entry_committer(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->committer; -} - -const char *git_reflog_entry_message(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->msg; -} - -int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry) -{ - size_t entrycount; - git_reflog_entry *entry, *previous; - - entrycount = git_reflog_entrycount(reflog); - - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - - if (entry == NULL) { - git_error_set(GIT_ERROR_REFERENCE, "no reflog entry at index %"PRIuZ, idx); - return GIT_ENOTFOUND; - } - - git_reflog_entry__free(entry); - - if (git_vector_remove( - &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) - return -1; - - if (!rewrite_previous_entry) - return 0; - - /* No need to rewrite anything when removing the most recent entry */ - if (idx == 0) - return 0; - - /* Have the latest entry just been dropped? */ - if (entrycount == 1) - return 0; - - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); - - /* If the oldest entry has just been removed... */ - if (idx == entrycount - 1) { - /* ...clear the oid_old member of the "new" oldest entry */ - if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) - return -1; - - return 0; - } - - previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - git_oid_cpy(&entry->oid_old, &previous->oid_cur); - - return 0; -} diff --git a/src/reflog.h b/src/reflog.h deleted file mode 100644 index 8c3895952..000000000 --- a/src/reflog.h +++ /dev/null @@ -1,41 +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_reflog_h__ -#define INCLUDE_reflog_h__ - -#include "common.h" - -#include "git2/reflog.h" -#include "vector.h" - -#define GIT_REFLOG_DIR "logs/" -#define GIT_REFLOG_DIR_MODE 0777 -#define GIT_REFLOG_FILE_MODE 0666 - -#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) - -struct git_reflog_entry { - git_oid oid_old; - git_oid oid_cur; - - git_signature *committer; - - char *msg; -}; - -struct git_reflog { - git_refdb *db; - char *ref_name; - git_vector entries; -}; - -GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) -{ - return (total - 1) - idx; -} - -#endif diff --git a/src/refs.c b/src/refs.c deleted file mode 100644 index 5c875b95b..000000000 --- a/src/refs.c +++ /dev/null @@ -1,1395 +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 "refs.h" - -#include "hash.h" -#include "repository.h" -#include "futils.h" -#include "filebuf.h" -#include "pack.h" -#include "reflog.h" -#include "refdb.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool git_reference__enable_symbolic_ref_target_validation = true; - -enum { - GIT_PACKREF_HAS_PEEL = 1, - GIT_PACKREF_WAS_LOOSE = 2 -}; - -static git_reference *alloc_ref(const char *name) -{ - git_reference *ref = NULL; - size_t namelen = strlen(name), reflen; - - if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && - !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && - (ref = git__calloc(1, reflen)) != NULL) - memcpy(ref->name, name, namelen + 1); - - return ref; -} - -git_reference *git_reference__alloc_symbolic( - const char *name, const char *target) -{ - git_reference *ref; - - GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(target, NULL); - - ref = alloc_ref(name); - if (!ref) - return NULL; - - ref->type = GIT_REFERENCE_SYMBOLIC; - - if ((ref->target.symbolic = git__strdup(target)) == NULL) { - git__free(ref); - return NULL; - } - - return ref; -} - -git_reference *git_reference__alloc( - const char *name, - const git_oid *oid, - const git_oid *peel) -{ - git_reference *ref; - - GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(oid, NULL); - - ref = alloc_ref(name); - if (!ref) - return NULL; - - ref->type = GIT_REFERENCE_DIRECT; - git_oid_cpy(&ref->target.oid, oid); - - if (peel != NULL) - git_oid_cpy(&ref->peel, peel); - - return ref; -} - -git_reference *git_reference__realloc( - git_reference **ptr_to_ref, const char *name) -{ - size_t namelen, reflen; - git_reference *rewrite = NULL; - - GIT_ASSERT_ARG_WITH_RETVAL(ptr_to_ref, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); - - namelen = strlen(name); - - if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && - !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && - (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL) - memcpy(rewrite->name, name, namelen + 1); - - *ptr_to_ref = NULL; - - return rewrite; -} - -int git_reference_dup(git_reference **dest, git_reference *source) -{ - if (source->type == GIT_REFERENCE_SYMBOLIC) - *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic); - else - *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel); - - GIT_ERROR_CHECK_ALLOC(*dest); - - (*dest)->db = source->db; - GIT_REFCOUNT_INC((*dest)->db); - - return 0; -} - -void git_reference_free(git_reference *reference) -{ - if (reference == NULL) - return; - - if (reference->type == GIT_REFERENCE_SYMBOLIC) - git__free(reference->target.symbolic); - - if (reference->db) - GIT_REFCOUNT_DEC(reference->db, git_refdb__free); - - git__free(reference); -} - -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 - old_target = ref->target.symbolic; - - return git_refdb_delete(ref->db, ref->name, old_id, old_target); -} - -int git_reference_remove(git_repository *repo, const char *name) -{ - git_refdb *db; - int error; - - if ((error = git_repository_refdb__weakptr(&db, repo)) < 0) - return error; - - return git_refdb_delete(db, name, NULL, NULL); -} - -int git_reference_lookup(git_reference **ref_out, - git_repository *repo, const char *name) -{ - return git_reference_lookup_resolved(ref_out, repo, name, 0); -} - -int git_reference_name_to_id( - git_oid *out, git_repository *repo, const char *name) -{ - int error; - git_reference *ref; - - if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) - return error; - - git_oid_cpy(out, git_reference_target(ref)); - git_reference_free(ref); - return 0; -} - -static int reference_normalize_for_repo( - git_refname_t out, - git_repository *repo, - const char *name, - bool validate) -{ - int precompose; - unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL; - - if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) && - precompose) - flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE; - - if (!validate) - flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE; - - return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); -} - -int git_reference_lookup_resolved( - git_reference **ref_out, - git_repository *repo, - const char *name, - int max_nesting) -{ - git_refname_t normalized; - git_refdb *refdb; - int error = 0; - - GIT_ASSERT_ARG(ref_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 || - (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 || - (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0) - return error; - - /* - * The resolved reference may be a symbolic reference in case its - * target doesn't exist. If the user asked us to resolve (e.g. - * `max_nesting != 0`), then we need to return an error in case we got - * a symbolic reference back. - */ - if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) { - git_reference_free(*ref_out); - *ref_out = NULL; - return GIT_ENOTFOUND; - } - - return 0; -} - -int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname) -{ - int error = 0, i, valid; - bool fallbackmode = true, foundvalid = false; - git_reference *ref; - git_str refnamebuf = GIT_STR_INIT, name = GIT_STR_INIT; - - static const char *formatters[] = { - "%s", - GIT_REFS_DIR "%s", - GIT_REFS_TAGS_DIR "%s", - GIT_REFS_HEADS_DIR "%s", - GIT_REFS_REMOTES_DIR "%s", - GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE, - NULL - }; - - if (*refname) - git_str_puts(&name, refname); - else { - git_str_puts(&name, GIT_HEAD_FILE); - fallbackmode = false; - } - - for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) { - - git_str_clear(&refnamebuf); - - if ((error = git_str_printf(&refnamebuf, formatters[i], git_str_cstr(&name))) < 0 || - (error = git_reference_name_is_valid(&valid, git_str_cstr(&refnamebuf))) < 0) - goto cleanup; - - if (!valid) { - error = GIT_EINVALIDSPEC; - continue; - } - foundvalid = true; - - error = git_reference_lookup_resolved(&ref, repo, git_str_cstr(&refnamebuf), -1); - - if (!error) { - *out = ref; - error = 0; - goto cleanup; - } - - if (error != GIT_ENOTFOUND) - goto cleanup; - } - -cleanup: - if (error && !foundvalid) { - /* never found a valid reference name */ - git_error_set(GIT_ERROR_REFERENCE, - "could not use '%s' as valid reference name", git_str_cstr(&name)); - } - - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname); - - git_str_dispose(&name); - git_str_dispose(&refnamebuf); - return error; -} - -/** - * Getters - */ -git_reference_t git_reference_type(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return ref->type; -} - -const char *git_reference_name(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - return ref->name; -} - -git_repository *git_reference_owner(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - return ref->db->repo; -} - -const git_oid *git_reference_target(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - - if (ref->type != GIT_REFERENCE_DIRECT) - return NULL; - - return &ref->target.oid; -} - -const git_oid *git_reference_target_peel(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - - if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel)) - return NULL; - - return &ref->peel; -} - -const char *git_reference_symbolic_target(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - - if (ref->type != GIT_REFERENCE_SYMBOLIC) - return NULL; - - return ref->target.symbolic; -} - -static int reference__create( - git_reference **ref_out, - git_repository *repo, - const char *name, - const git_oid *oid, - const char *symbolic, - int force, - const git_signature *signature, - const char *log_message, - const git_oid *old_id, - const char *old_target) -{ - git_refname_t normalized; - git_refdb *refdb; - git_reference *ref = NULL; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(symbolic || signature); - - if (ref_out) - *ref_out = NULL; - - error = reference_normalize_for_repo(normalized, repo, name, true); - if (error < 0) - return error; - - error = git_repository_refdb__weakptr(&refdb, repo); - if (error < 0) - return error; - - if (oid != NULL) { - GIT_ASSERT(symbolic == NULL); - - if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) { - git_error_set(GIT_ERROR_REFERENCE, - "target OID for the reference doesn't exist on the repository"); - return -1; - } - - ref = git_reference__alloc(normalized, oid, NULL); - } else { - git_refname_t normalized_target; - - error = reference_normalize_for_repo(normalized_target, repo, - symbolic, git_reference__enable_symbolic_ref_target_validation); - - if (error < 0) - return error; - - ref = git_reference__alloc_symbolic(normalized, normalized_target); - } - - GIT_ERROR_CHECK_ALLOC(ref); - - if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) { - git_reference_free(ref); - return error; - } - - if (ref_out == NULL) - git_reference_free(ref); - else - *ref_out = ref; - - return 0; -} - -static int refs_configured_ident(git_signature **out, const git_repository *repo) -{ - if (repo->ident_name && repo->ident_email) - return git_signature_now(out, repo->ident_name, repo->ident_email); - - /* if not configured let us fall-through to the next method */ - return -1; -} - -int git_reference__log_signature(git_signature **out, git_repository *repo) -{ - int error; - git_signature *who; - - if(((error = refs_configured_ident(&who, repo)) < 0) && - ((error = git_signature_default(&who, repo)) < 0) && - ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) - return error; - - *out = who; - return 0; -} - -int git_reference_create_matching( - git_reference **ref_out, - git_repository *repo, - const char *name, - const git_oid *id, - int force, - const git_oid *old_id, - const char *log_message) - -{ - int error; - git_signature *who = NULL; - - GIT_ASSERT_ARG(id); - - if ((error = git_reference__log_signature(&who, repo)) < 0) - return error; - - error = reference__create( - ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL); - - git_signature_free(who); - return error; -} - -int git_reference_create( - git_reference **ref_out, - git_repository *repo, - const char *name, - const git_oid *id, - int force, - const char *log_message) -{ - return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message); -} - -int git_reference_symbolic_create_matching( - git_reference **ref_out, - git_repository *repo, - const char *name, - const char *target, - int force, - const char *old_target, - const char *log_message) -{ - int error; - git_signature *who = NULL; - - GIT_ASSERT_ARG(target); - - if ((error = git_reference__log_signature(&who, repo)) < 0) - return error; - - error = reference__create( - ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target); - - git_signature_free(who); - return error; -} - -int git_reference_symbolic_create( - git_reference **ref_out, - git_repository *repo, - const char *name, - const char *target, - int force, - const char *log_message) -{ - return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message); -} - -static int ensure_is_an_updatable_direct_reference(git_reference *ref) -{ - if (ref->type == GIT_REFERENCE_DIRECT) - return 0; - - git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference"); - return -1; -} - -int git_reference_set_target( - git_reference **out, - git_reference *ref, - const git_oid *id, - const char *log_message) -{ - int error; - git_repository *repo; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - GIT_ASSERT_ARG(id); - - repo = ref->db->repo; - - if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) - return error; - - return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message); -} - -static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) -{ - if (ref->type == GIT_REFERENCE_SYMBOLIC) - return 0; - - git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference"); - return -1; -} - -int git_reference_symbolic_set_target( - git_reference **out, - git_reference *ref, - const char *target, - const char *log_message) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - GIT_ASSERT_ARG(target); - - if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0) - return error; - - return git_reference_symbolic_create_matching( - out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message); -} - -typedef struct { - const char *old_name; - git_refname_t new_name; -} refs_update_head_payload; - -static int refs_update_head(git_repository *worktree, void *_payload) -{ - refs_update_head_payload *payload = (refs_update_head_payload *)_payload; - git_reference *head = NULL, *updated = NULL; - int error; - - if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) - goto out; - - if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC || - git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0) - goto out; - - /* Update HEAD if it was pointing to the reference being renamed */ - if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) { - git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference"); - goto out; - } - -out: - git_reference_free(updated); - git_reference_free(head); - return error; -} - -int git_reference_rename( - git_reference **out, - git_reference *ref, - const char *new_name, - int force, - const char *log_message) -{ - refs_update_head_payload payload; - git_signature *signature = NULL; - git_repository *repo; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - - repo = git_reference_owner(ref); - - if ((error = git_reference__log_signature(&signature, repo)) < 0 || - (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 || - (error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0) - goto out; - - payload.old_name = ref->name; - - /* We may have to update any HEAD that was pointing to the renamed reference. */ - if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0) - goto out; - -out: - git_signature_free(signature); - return error; -} - -int git_reference_resolve(git_reference **ref_out, const git_reference *ref) -{ - switch (git_reference_type(ref)) { - case GIT_REFERENCE_DIRECT: - return git_reference_lookup(ref_out, ref->db->repo, ref->name); - - case GIT_REFERENCE_SYMBOLIC: - return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1); - - default: - git_error_set(GIT_ERROR_REFERENCE, "invalid reference"); - return -1; - } -} - -int git_reference_foreach( - git_repository *repo, - git_reference_foreach_cb callback, - void *payload) -{ - git_reference_iterator *iter; - git_reference *ref; - int error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - return error; - - while (!(error = git_reference_next(&ref, iter))) { - if ((error = callback(ref, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); - return error; -} - -int git_reference_foreach_name( - git_repository *repo, - git_reference_foreach_name_cb callback, - void *payload) -{ - git_reference_iterator *iter; - const char *refname; - int error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - return error; - - while (!(error = git_reference_next_name(&refname, iter))) { - if ((error = callback(refname, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); - return error; -} - -int git_reference_foreach_glob( - git_repository *repo, - const char *glob, - git_reference_foreach_name_cb callback, - void *payload) -{ - git_reference_iterator *iter; - const char *refname; - int error; - - if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) - return error; - - while (!(error = git_reference_next_name(&refname, iter))) { - if ((error = callback(refname, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); - return error; -} - -int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo) -{ - git_refdb *refdb; - - if (git_repository_refdb__weakptr(&refdb, repo) < 0) - return -1; - - return git_refdb_iterator(out, refdb, NULL); -} - -int git_reference_iterator_glob_new( - git_reference_iterator **out, git_repository *repo, const char *glob) -{ - git_refdb *refdb; - - if (git_repository_refdb__weakptr(&refdb, repo) < 0) - return -1; - - return git_refdb_iterator(out, refdb, glob); -} - -int git_reference_next(git_reference **out, git_reference_iterator *iter) -{ - return git_refdb_iterator_next(out, iter); -} - -int git_reference_next_name(const char **out, git_reference_iterator *iter) -{ - return git_refdb_iterator_next_name(out, iter); -} - -void git_reference_iterator_free(git_reference_iterator *iter) -{ - if (iter == NULL) - return; - - git_refdb_iterator_free(iter); -} - -static int cb__reflist_add(const char *ref, void *data) -{ - char *name = git__strdup(ref); - GIT_ERROR_CHECK_ALLOC(name); - return git_vector_insert((git_vector *)data, name); -} - -int git_reference_list( - git_strarray *array, - git_repository *repo) -{ - git_vector ref_list; - - GIT_ASSERT_ARG(array); - GIT_ASSERT_ARG(repo); - - array->strings = NULL; - array->count = 0; - - if (git_vector_init(&ref_list, 8, NULL) < 0) - return -1; - - if (git_reference_foreach_name( - repo, &cb__reflist_add, (void *)&ref_list) < 0) { - git_vector_free(&ref_list); - return -1; - } - - array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list); - - return 0; -} - -static int is_valid_ref_char(char ch) -{ - if ((unsigned) ch <= ' ') - return 0; - - switch (ch) { - case '~': - case '^': - case ':': - case '\\': - case '?': - case '[': - return 0; - default: - return 1; - } -} - -static int ensure_segment_validity(const char *name, char may_contain_glob) -{ - const char *current = name; - char prev = '\0'; - const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); - int segment_len; - - if (*current == '.') - return -1; /* Refname starts with "." */ - - for (current = name; ; current++) { - if (*current == '\0' || *current == '/') - break; - - if (!is_valid_ref_char(*current)) - return -1; /* Illegal character in refname */ - - if (prev == '.' && *current == '.') - return -1; /* Refname contains ".." */ - - if (prev == '@' && *current == '{') - return -1; /* Refname contains "@{" */ - - if (*current == '*') { - if (!may_contain_glob) - return -1; - may_contain_glob = 0; - } - - prev = *current; - } - - segment_len = (int)(current - name); - - /* A refname component can not end with ".lock" */ - if (segment_len >= lock_len && - !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) - return -1; - - return segment_len; -} - -static bool is_all_caps_and_underscore(const char *name, size_t len) -{ - size_t i; - char c; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(len > 0); - - for (i = 0; i < len; i++) - { - c = name[i]; - if ((c < 'A' || c > 'Z') && c != '_') - return false; - } - - if (*name == '_' || name[len - 1] == '_') - return false; - - return true; -} - -/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */ -int git_reference__normalize_name( - git_str *buf, - const char *name, - unsigned int flags) -{ - const char *current; - int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; - unsigned int process_flags; - bool normalize = (buf != NULL); - bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; -#endif - - GIT_ASSERT_ARG(name); - - process_flags = flags; - current = (char *)name; - - if (validate && *current == '/') - goto cleanup; - - if (normalize) - git_str_clear(buf); - -#ifdef GIT_USE_ICONV - if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) { - size_t namelen = strlen(current); - if ((error = git_fs_path_iconv_init_precompose(&ic)) < 0 || - (error = git_fs_path_iconv(&ic, ¤t, &namelen)) < 0) - goto cleanup; - error = GIT_EINVALIDSPEC; - } -#endif - - if (!validate) { - git_str_sets(buf, current); - - error = git_str_oom(buf) ? -1 : 0; - goto cleanup; - } - - while (true) { - 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_str_len(buf); - - git_str_joinpath(buf, git_str_cstr(buf), current); - git_str_truncate(buf, - cur_len + segment_len + (segments_count ? 1 : 0)); - - if (git_str_oom(buf)) { - error = -1; - goto cleanup; - } - } - - segments_count++; - } - - /* No empty segment is allowed when not normalizing */ - if (segment_len == 0 && !normalize) - goto cleanup; - - if (current[segment_len] == '\0') - break; - - current += segment_len + 1; - } - - /* A refname can not be empty */ - if (segment_len == 0 && segments_count == 0) - goto cleanup; - - /* A refname can not end with "." */ - if (current[segment_len - 1] == '.') - goto cleanup; - - /* A refname can not end with "/" */ - if (current[segment_len - 1] == '/') - goto cleanup; - - if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) - goto cleanup; - - if ((segments_count == 1 ) && - !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) && - !(is_all_caps_and_underscore(name, (size_t)segment_len) || - ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) - goto cleanup; - - if ((segments_count > 1) - && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) - goto cleanup; - - error = 0; - -cleanup: - if (error == GIT_EINVALIDSPEC) - git_error_set( - GIT_ERROR_REFERENCE, - "the given reference name '%s' is not valid", name); - - if (error && normalize) - git_str_dispose(buf); - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif - - return error; -} - -int git_reference_normalize_name( - char *buffer_out, - size_t buffer_size, - const char *name, - unsigned int flags) -{ - git_str buf = GIT_STR_INIT; - int error; - - if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) - goto cleanup; - - if (git_str_len(&buf) > buffer_size - 1) { - git_error_set( - GIT_ERROR_REFERENCE, - "the provided buffer is too short to hold the normalization of '%s'", name); - error = GIT_EBUFS; - goto cleanup; - } - - if ((error = git_str_copy_cstr(buffer_out, buffer_size, &buf)) < 0) - goto cleanup; - - error = 0; - -cleanup: - git_str_dispose(&buf); - return error; -} - -#define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC) - -int git_reference_cmp( - const git_reference *ref1, - const git_reference *ref2) -{ - git_reference_t type1, type2; - - GIT_ASSERT_ARG(ref1); - GIT_ASSERT_ARG(ref2); - - type1 = git_reference_type(ref1); - type2 = git_reference_type(ref2); - - /* let's put symbolic refs before OIDs */ - if (type1 != type2) - return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1; - - if (type1 == GIT_REFERENCE_SYMBOLIC) - return strcmp(ref1->target.symbolic, ref2->target.symbolic); - - return git_oid__cmp(&ref1->target.oid, &ref2->target.oid); -} - -/* - * Starting with the reference given by `ref_name`, follows symbolic - * references until a direct reference is found and updated the OID - * on that direct reference to `oid`. - */ -int git_reference__update_terminal( - git_repository *repo, - const char *ref_name, - const git_oid *oid, - const git_signature *sig, - const char *log_message) -{ - git_reference *ref = NULL, *ref2 = NULL; - git_signature *who = NULL; - git_refdb *refdb = NULL; - const git_signature *to_use; - int error = 0; - - if (!sig && (error = git_reference__log_signature(&who, repo)) < 0) - goto out; - - to_use = sig ? sig : who; - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - goto out; - - if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use, - log_message, NULL, NULL); - } - goto out; - } - - /* In case the resolved reference is symbolic, then it's a dangling symref. */ - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use, - log_message, NULL, NULL); - } else { - error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use, - log_message, &ref->target.oid, NULL); - } - -out: - git_reference_free(ref2); - git_reference_free(ref); - git_signature_free(who); - return error; -} - -static const char *commit_type(const git_commit *commit) -{ - unsigned int count = git_commit_parentcount(commit); - - if (count >= 2) - return " (merge)"; - else if (count == 0) - return " (initial)"; - else - return ""; -} - -int git_reference__update_for_commit( - git_repository *repo, - git_reference *ref, - const char *ref_name, - const git_oid *id, - const char *operation) -{ - git_reference *ref_new = NULL; - git_commit *commit = NULL; - git_str reflog_msg = GIT_STR_INIT; - const git_signature *who; - int error; - - if ((error = git_commit_lookup(&commit, repo, id)) < 0 || - (error = git_str_printf(&reflog_msg, "%s%s: %s", - operation ? operation : "commit", - commit_type(commit), - git_commit_summary(commit))) < 0) - goto done; - - who = git_commit_committer(commit); - - if (ref) { - if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) - return error; - - error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who, - git_str_cstr(&reflog_msg), &ref->target.oid, NULL); - } - else - error = git_reference__update_terminal( - repo, ref_name, id, who, git_str_cstr(&reflog_msg)); - -done: - git_reference_free(ref_new); - git_str_dispose(&reflog_msg); - git_commit_free(commit); - return error; -} - -int git_reference_has_log(git_repository *repo, const char *refname) -{ - int error; - git_refdb *refdb; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return error; - - return git_refdb_has_log(refdb, refname); -} - -int git_reference_ensure_log(git_repository *repo, const char *refname) -{ - int error; - git_refdb *refdb; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return error; - - return git_refdb_ensure_log(refdb, refname); -} - -int git_reference__is_branch(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0; -} - -int git_reference_is_branch(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_branch(ref->name); -} - -int git_reference__is_remote(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0; -} - -int git_reference_is_remote(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_remote(ref->name); -} - -int git_reference__is_tag(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0; -} - -int git_reference_is_tag(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_tag(ref->name); -} - -int git_reference__is_note(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0; -} - -int git_reference_is_note(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_note(ref->name); -} - -static int peel_error(int error, const git_reference *ref, const char *msg) -{ - git_error_set( - GIT_ERROR_INVALID, - "the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); - return error; -} - -int git_reference_peel( - git_object **peeled, - const git_reference *ref, - git_object_t target_type) -{ - const git_reference *resolved = NULL; - git_reference *allocated = NULL; - git_object *target = NULL; - int error; - - GIT_ASSERT_ARG(ref); - - if (ref->type == GIT_REFERENCE_DIRECT) { - resolved = ref; - } else { - if ((error = git_reference_resolve(&allocated, ref)) < 0) - return peel_error(error, ref, "Cannot resolve reference"); - - resolved = allocated; - } - - /* - * If we try to peel an object to a tag, we cannot use - * the fully peeled object, as that will always resolve - * 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_is_zero(&resolved->peel)) { - error = git_object_lookup(&target, - git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY); - } else { - error = git_object_lookup(&target, - git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY); - } - - if (error < 0) { - peel_error(error, ref, "Cannot retrieve reference target"); - goto cleanup; - } - - if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG) - error = git_object_dup(peeled, target); - else - error = git_object_peel(peeled, target, target_type); - -cleanup: - git_object_free(target); - git_reference_free(allocated); - - return error; -} - -int git_reference__name_is_valid( - int *valid, - const char *refname, - unsigned int flags) -{ - int error; - - GIT_ASSERT(valid && refname); - - *valid = 0; - - error = git_reference__normalize_name(NULL, refname, flags); - - if (!error) - *valid = 1; - else if (error == GIT_EINVALIDSPEC) - error = 0; - - return error; -} - -int git_reference_name_is_valid(int *valid, const char *refname) -{ - return git_reference__name_is_valid(valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); -} - -const char *git_reference__shorthand(const char *name) -{ - if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) - return name + strlen(GIT_REFS_HEADS_DIR); - else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR)) - return name + strlen(GIT_REFS_TAGS_DIR); - else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR)) - return name + strlen(GIT_REFS_REMOTES_DIR); - else if (!git__prefixcmp(name, GIT_REFS_DIR)) - return name + strlen(GIT_REFS_DIR); - - /* No shorthands are available, so just return the name. */ - return name; -} - -const char *git_reference_shorthand(const git_reference *ref) -{ - return git_reference__shorthand(ref->name); -} - -int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo) -{ - int error; - git_reference *tmp_ref; - - GIT_ASSERT_ARG(unborn); - GIT_ASSERT_ARG(ref); - GIT_ASSERT_ARG(repo); - - if (ref->type == GIT_REFERENCE_DIRECT) { - *unborn = 0; - return 0; - } - - error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1); - git_reference_free(tmp_ref); - - if (error != 0 && error != GIT_ENOTFOUND) - return error; - else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0) - *unborn = true; - else - *unborn = false; - - return 0; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD - -int git_reference_is_valid_name(const char *refname) -{ - int valid = 0; - - git_reference__name_is_valid(&valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); - - return valid; -} - -#endif diff --git a/src/refs.h b/src/refs.h deleted file mode 100644 index cb888bf8f..000000000 --- a/src/refs.h +++ /dev/null @@ -1,130 +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_refs_h__ -#define INCLUDE_refs_h__ - -#include "common.h" - -#include "git2/oid.h" -#include "git2/refs.h" -#include "git2/refdb.h" -#include "strmap.h" -#include "str.h" -#include "oid.h" - -extern bool git_reference__enable_symbolic_ref_target_validation; - -#define GIT_REFS_DIR "refs/" -#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" -#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" -#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/" -#define GIT_REFS_NOTES_DIR GIT_REFS_DIR "notes/" -#define GIT_REFS_DIR_MODE 0777 -#define GIT_REFS_FILE_MODE 0666 - -#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF" - -#define GIT_SYMREF "ref: " -#define GIT_PACKEDREFS_FILE "packed-refs" -#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled sorted " -#define GIT_PACKEDREFS_FILE_MODE 0666 - -#define GIT_HEAD_FILE "HEAD" -#define GIT_ORIG_HEAD_FILE "ORIG_HEAD" -#define GIT_FETCH_HEAD_FILE "FETCH_HEAD" -#define GIT_MERGE_HEAD_FILE "MERGE_HEAD" -#define GIT_REVERT_HEAD_FILE "REVERT_HEAD" -#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD" -#define GIT_BISECT_LOG_FILE "BISECT_LOG" -#define GIT_REBASE_MERGE_DIR "rebase-merge/" -#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive" -#define GIT_REBASE_APPLY_DIR "rebase-apply/" -#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing" -#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" - -#define GIT_SEQUENCER_DIR "sequencer/" -#define GIT_SEQUENCER_HEAD_FILE GIT_SEQUENCER_DIR "head" -#define GIT_SEQUENCER_OPTIONS_FILE GIT_SEQUENCER_DIR "options" -#define GIT_SEQUENCER_TODO_FILE GIT_SEQUENCER_DIR "todo" - -#define GIT_STASH_FILE "stash" -#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE - -#define GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE (1u << 16) -#define GIT_REFERENCE_FORMAT__VALIDATION_DISABLE (1u << 15) - -#define GIT_REFNAME_MAX 1024 - -typedef char git_refname_t[GIT_REFNAME_MAX]; - -struct git_reference { - git_refdb *db; - git_reference_t type; - - union { - git_oid oid; - char *symbolic; - } target; - - git_oid peel; - char name[GIT_FLEX_ARRAY]; -}; - -/** - * 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_str *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); -int git_reference__name_is_valid(int *valid, const char *name, unsigned int flags); -int git_reference__is_branch(const char *ref_name); -int git_reference__is_remote(const char *ref_name); -int git_reference__is_tag(const char *ref_name); -int git_reference__is_note(const char *ref_name); -const char *git_reference__shorthand(const char *name); - -/** - * Lookup a reference by name and try to resolve to an OID. - * - * You can control how many dereferences this will attempt to resolve the - * reference with the `max_deref` parameter, or pass -1 to use a sane - * default. If you pass 0 for `max_deref`, this will not attempt to resolve - * the reference. For any value of `max_deref` other than 0, not - * successfully resolving the reference will be reported as an error. - - * The generated reference must be freed by the user. - * - * @param reference_out Pointer to the looked-up reference - * @param repo The repository to look up the reference - * @param name The long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) - * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value - * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref - */ -int git_reference_lookup_resolved( - git_reference **reference_out, - git_repository *repo, - const char *name, - int max_deref); - -int git_reference__log_signature(git_signature **out, git_repository *repo); - -/** Update a reference after a commit. */ -int git_reference__update_for_commit( - git_repository *repo, - git_reference *ref, - const char *ref_name, - const git_oid *id, - const char *operation); - -int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo); - -#endif diff --git a/src/refspec.c b/src/refspec.c deleted file mode 100644 index f0a0c2bfb..000000000 --- a/src/refspec.c +++ /dev/null @@ -1,420 +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 "refspec.h" - -#include "buf.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) -{ - /* Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 */ - - size_t llen; - int is_glob = 0; - const char *lhs, *rhs; - int valid = 0; - unsigned int flags; - - GIT_ASSERT_ARG(refspec); - GIT_ASSERT_ARG(input); - - memset(refspec, 0x0, sizeof(git_refspec)); - refspec->push = !is_fetch; - - lhs = input; - if (*lhs == '+') { - refspec->force = 1; - lhs++; - } - - rhs = strrchr(lhs, ':'); - - /* - * Before going on, special case ":" (or "+:") as a refspec - * for matching refs. - */ - if (!is_fetch && rhs == lhs && rhs[1] == '\0') { - refspec->matching = 1; - refspec->string = git__strdup(input); - GIT_ERROR_CHECK_ALLOC(refspec->string); - refspec->src = git__strdup(""); - GIT_ERROR_CHECK_ALLOC(refspec->src); - refspec->dst = git__strdup(""); - GIT_ERROR_CHECK_ALLOC(refspec->dst); - return 0; - } - - if (rhs) { - size_t rlen = strlen(++rhs); - if (rlen || !is_fetch) { - is_glob = (1 <= rlen && strchr(rhs, '*')); - refspec->dst = git__strndup(rhs, rlen); - } - } - - llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); - if (1 <= llen && memchr(lhs, '*', llen)) { - if ((rhs && !is_glob) || (!rhs && is_fetch)) - goto invalid; - is_glob = 1; - } else if (rhs && is_glob) - goto invalid; - - refspec->pattern = is_glob; - refspec->src = git__strndup(lhs, llen); - flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | - GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND | - (is_glob ? GIT_REFERENCE_FORMAT_REFSPEC_PATTERN : 0); - - if (is_fetch) { - /* - * LHS - * - empty is allowed; it means HEAD. - * - otherwise it must be a valid looking ref. - */ - if (!*refspec->src) - ; /* empty is ok */ - else if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - - /* - * RHS - * - missing is ok, and is same as empty. - * - empty is ok; it means not to store. - * - otherwise it must be a valid looking ref. - */ - if (!refspec->dst) - ; /* ok */ - else if (!*refspec->dst) - ; /* ok */ - else if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } else { - /* - * LHS - * - empty is allowed; it means delete. - * - when wildcarded, it must be a valid looking ref. - * - otherwise, it must be an extended SHA-1, but - * there is no existing way to validate this. - */ - if (!*refspec->src) - ; /* empty is ok */ - else if (is_glob) { - if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } - else { - ; /* anything goes, for now */ - } - - /* - * RHS - * - missing is allowed, but LHS then must be a - * valid looking ref. - * - empty is not allowed. - * - otherwise it must be a valid looking ref. - */ - if (!refspec->dst) { - if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } else if (!*refspec->dst) { - goto invalid; - } else { - if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } - - /* if the RHS is empty, then it's a copy of the LHS */ - if (!refspec->dst) { - refspec->dst = git__strdup(refspec->src); - GIT_ERROR_CHECK_ALLOC(refspec->dst); - } - } - - refspec->string = git__strdup(input); - GIT_ERROR_CHECK_ALLOC(refspec->string); - - return 0; - -invalid: - git_error_set(GIT_ERROR_INVALID, - "'%s' is not a valid refspec.", input); - git_refspec__dispose(refspec); - return GIT_EINVALIDSPEC; - -on_error: - git_refspec__dispose(refspec); - return -1; -} - -void git_refspec__dispose(git_refspec *refspec) -{ - if (refspec == NULL) - return; - - git__free(refspec->src); - git__free(refspec->dst); - git__free(refspec->string); - - memset(refspec, 0x0, sizeof(git_refspec)); -} - -int git_refspec_parse(git_refspec **out_refspec, const char *input, int is_fetch) -{ - git_refspec *refspec; - GIT_ASSERT_ARG(out_refspec); - GIT_ASSERT_ARG(input); - - *out_refspec = NULL; - - refspec = git__malloc(sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(refspec); - - if (git_refspec__parse(refspec, input, !!is_fetch) != 0) { - git__free(refspec); - return -1; - } - - *out_refspec = refspec; - return 0; -} - -void git_refspec_free(git_refspec *refspec) -{ - git_refspec__dispose(refspec); - git__free(refspec); -} - -const char *git_refspec_src(const git_refspec *refspec) -{ - return refspec == NULL ? NULL : refspec->src; -} - -const char *git_refspec_dst(const git_refspec *refspec) -{ - return refspec == NULL ? NULL : refspec->dst; -} - -const char *git_refspec_string(const git_refspec *refspec) -{ - return refspec == NULL ? NULL : refspec->string; -} - -int git_refspec_force(const git_refspec *refspec) -{ - GIT_ASSERT_ARG(refspec); - - return refspec->force; -} - -int git_refspec_src_matches(const git_refspec *refspec, const char *refname) -{ - if (refspec == NULL || refspec->src == NULL) - return false; - - return (wildmatch(refspec->src, refname, 0) == 0); -} - -int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) -{ - if (refspec == NULL || refspec->dst == NULL) - return false; - - return (wildmatch(refspec->dst, refname, 0) == 0); -} - -static int refspec_transform( - git_str *out, const char *from, const char *to, const char *name) -{ - const char *from_star, *to_star; - size_t replacement_len, star_offset; - - git_str_clear(out); - - /* - * There are two parts to each side of a refspec, the bit - * before the star and the bit after it. The star can be in - * the middle of the pattern, so we need to look at each bit - * individually. - */ - from_star = strchr(from, '*'); - to_star = strchr(to, '*'); - - GIT_ASSERT(from_star && to_star); - - /* star offset, both in 'from' and in 'name' */ - star_offset = from_star - from; - - /* the first half is copied over */ - git_str_put(out, to, to_star - to); - - /* - * 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_str_put(out, name + star_offset, replacement_len); - - return git_str_puts(out, to_star + 1); -} - -int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_refspec__transform, spec, name); -} - -int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(name); - - if (!git_refspec_src_matches(spec, name)) { - git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the source", name); - return -1; - } - - if (!spec->pattern) - return git_str_puts(out, spec->dst ? spec->dst : ""); - - return refspec_transform(out, spec->src, spec->dst, name); -} - -int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_refspec__rtransform, spec, name); -} - -int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(name); - - if (!git_refspec_dst_matches(spec, name)) { - git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the destination", name); - return -1; - } - - if (!spec->pattern) - return git_str_puts(out, spec->src); - - return refspec_transform(out, spec->dst, spec->src, name); -} - -int git_refspec__serialize(git_str *out, const git_refspec *refspec) -{ - if (refspec->force) - git_str_putc(out, '+'); - - git_str_printf(out, "%s:%s", - refspec->src != NULL ? refspec->src : "", - refspec->dst != NULL ? refspec->dst : ""); - - return git_str_oom(out) == false; -} - -int git_refspec_is_wildcard(const git_refspec *spec) -{ - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(spec->src); - - return (spec->src[strlen(spec->src) - 1] == '*'); -} - -git_direction git_refspec_direction(const git_refspec *spec) -{ - GIT_ASSERT_ARG(spec); - - return spec->push; -} - -int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) -{ - git_str buf = GIT_STR_INIT; - size_t j, pos; - git_remote_head key; - git_refspec *cur; - - const char *formatters[] = { - GIT_REFS_DIR "%s", - GIT_REFS_TAGS_DIR "%s", - GIT_REFS_HEADS_DIR "%s", - NULL - }; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(refs); - - cur = git__calloc(1, sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(cur); - - cur->force = spec->force; - cur->push = spec->push; - cur->pattern = spec->pattern; - cur->matching = spec->matching; - cur->string = git__strdup(spec->string); - - /* shorthand on the lhs */ - if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { - for (j = 0; formatters[j]; j++) { - git_str_clear(&buf); - git_str_printf(&buf, formatters[j], spec->src); - GIT_ERROR_CHECK_ALLOC_STR(&buf); - - key.name = (char *) git_str_cstr(&buf); - if (!git_vector_search(&pos, refs, &key)) { - /* we found something to match the shorthand, set src to that */ - cur->src = git_str_detach(&buf); - } - } - } - - /* No shorthands found, copy over the name */ - if (cur->src == NULL && spec->src != NULL) { - cur->src = git__strdup(spec->src); - GIT_ERROR_CHECK_ALLOC(cur->src); - } - - if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { - /* if it starts with "remotes" then we just prepend "refs/" */ - if (!git__prefixcmp(spec->dst, "remotes/")) { - git_str_puts(&buf, GIT_REFS_DIR); - } else { - git_str_puts(&buf, GIT_REFS_HEADS_DIR); - } - - git_str_puts(&buf, spec->dst); - GIT_ERROR_CHECK_ALLOC_STR(&buf); - - cur->dst = git_str_detach(&buf); - } - - git_str_dispose(&buf); - - if (cur->dst == NULL && spec->dst != NULL) { - cur->dst = git__strdup(spec->dst); - GIT_ERROR_CHECK_ALLOC(cur->dst); - } - - return git_vector_insert(out, cur); -} diff --git a/src/refspec.h b/src/refspec.h deleted file mode 100644 index bf4f7fcfb..000000000 --- a/src/refspec.h +++ /dev/null @@ -1,54 +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_refspec_h__ -#define INCLUDE_refspec_h__ - -#include "common.h" - -#include "git2/refspec.h" -#include "str.h" -#include "vector.h" - -struct git_refspec { - char *string; - char *src; - char *dst; - unsigned int force :1, - push : 1, - pattern :1, - matching :1; -}; - -#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*" - -int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name); -int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name); - -int git_refspec__parse( - struct git_refspec *refspec, - const char *str, - bool is_fetch); - -void git_refspec__dispose(git_refspec *refspec); - -int git_refspec__serialize(git_str *out, const git_refspec *refspec); - -/** - * Determines if a refspec is a wildcard refspec. - * - * @param spec the refspec - * @return 1 if the refspec is a wildcard, 0 otherwise - */ -int git_refspec_is_wildcard(const git_refspec *spec); - -/** - * DWIM `spec` with `refs` existing on the remote, append the dwim'ed - * result in `out`. - */ -int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs); - -#endif diff --git a/src/regexp.c b/src/regexp.c deleted file mode 100644 index 2569dea0a..000000000 --- a/src/regexp.c +++ /dev/null @@ -1,221 +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 "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 = NULL; - - 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] = {0}, *ovec; - int error; - size_t i; - - /* The ovec array always needs to be a multiple 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 deleted file mode 100644 index 2592ef383..000000000 --- a/src/regexp.h +++ /dev/null @@ -1,97 +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_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 deleted file mode 100644 index 1a79faaab..000000000 --- a/src/remote.c +++ /dev/null @@ -1,3087 +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 "remote.h" - -#include "buf.h" -#include "branch.h" -#include "config.h" -#include "repository.h" -#include "fetch.h" -#include "refs.h" -#include "refspec.h" -#include "fetchhead.h" -#include "push.h" -#include "proxy.h" - -#include "git2/config.h" -#include "git2/types.h" -#include "git2/oid.h" -#include "git2/net.h" - -#define CONFIG_URL_FMT "remote.%s.url" -#define CONFIG_PUSHURL_FMT "remote.%s.pushurl" -#define CONFIG_FETCH_FMT "remote.%s.fetch" -#define CONFIG_PUSH_FMT "remote.%s.push" -#define CONFIG_TAGOPT_FMT "remote.%s.tagopt" - -static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); -static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name); -static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty); - -static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch) -{ - git_refspec *spec; - - spec = git__calloc(1, sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(spec); - - if (git_refspec__parse(spec, string, is_fetch) < 0) { - git__free(spec); - return -1; - } - - spec->push = !is_fetch; - if (git_vector_insert(vector, spec) < 0) { - git_refspec__dispose(spec); - git__free(spec); - return -1; - } - - return 0; -} - -static int add_refspec(git_remote *remote, const char *string, bool is_fetch) -{ - return add_refspec_to(&remote->refspecs, string, is_fetch); -} - -static int download_tags_value(git_remote *remote, git_config *cfg) -{ - git_config_entry *ce; - git_str buf = GIT_STR_INIT; - int error; - - if (git_str_printf(&buf, "remote.%s.tagopt", remote->name) < 0) - return -1; - - error = git_config__lookup_entry(&ce, cfg, git_str_cstr(&buf), false); - git_str_dispose(&buf); - - if (!error && ce && ce->value) { - if (!strcmp(ce->value, "--no-tags")) - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; - else if (!strcmp(ce->value, "--tags")) - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - } - - git_config_entry_free(ce); - return error; -} - -static int ensure_remote_name_is_valid(const char *name) -{ - int valid, error; - - error = git_remote_name_is_valid(&valid, name); - - if (!error && !valid) { - git_error_set( - GIT_ERROR_CONFIG, - "'%s' is not a valid remote name.", name ? name : "(null)"); - error = GIT_EINVALIDSPEC; - } - - return error; -} - -static int write_add_refspec(git_repository *repo, const char *name, const char *refspec, bool fetch) -{ - git_config *cfg; - git_str var = GIT_STR_INIT; - git_refspec spec; - const char *fmt; - int error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - fmt = fetch ? CONFIG_FETCH_FMT : CONFIG_PUSH_FMT; - - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - if ((error = git_refspec__parse(&spec, refspec, fetch)) < 0) - return error; - - git_refspec__dispose(&spec); - - if ((error = git_str_printf(&var, fmt, name)) < 0) - return error; - - /* - * "$^" is an unmatchable regexp: it will not match anything at all, so - * all values will be considered new and we will not replace any - * present value. - */ - if ((error = git_config_set_multivar(cfg, var.ptr, "$^", refspec)) < 0) { - goto cleanup; - } - -cleanup: - git_str_dispose(&var); - return 0; -} - -static int canonicalize_url(git_str *out, const char *in) -{ - if (in == NULL || strlen(in) == 0) { - git_error_set(GIT_ERROR_INVALID, "cannot set empty URL"); - return GIT_EINVALIDSPEC; - } - -#ifdef GIT_WIN32 - /* Given a UNC path like \\server\path, we need to convert this - * to //server/path for compatibility with core git. - */ - if (in[0] == '\\' && in[1] == '\\' && - (git__isalpha(in[2]) || git__isdigit(in[2]))) { - const char *c; - for (c = in; *c; c++) - git_str_putc(out, *c == '\\' ? '/' : *c); - - return git_str_oom(out) ? -1 : 0; - } -#endif - - return git_str_puts(out, in); -} - -static int default_fetchspec_for_name(git_str *buf, const char *name) -{ - if (git_str_printf(buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) - return -1; - - return 0; -} - -static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) -{ - int error; - git_remote *remote; - - error = git_remote_lookup(&remote, repo, name); - - if (error == GIT_ENOTFOUND) - return 0; - - if (error < 0) - return error; - - git_remote_free(remote); - - git_error_set(GIT_ERROR_CONFIG, "remote '%s' already exists", name); - - return GIT_EEXISTS; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version) -{ - return git_remote_create_options_init(opts, version); -} -#endif - -int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts) -{ - git_remote *remote = NULL; - git_config *config_ro = NULL, *config_rw; - git_str canonical_url = GIT_STR_INIT; - git_str var = GIT_STR_INIT; - git_str specbuf = GIT_STR_INIT; - const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - int error = -1; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(url); - - if (!opts) { - opts = &dummy_opts; - } - - GIT_ERROR_CHECK_VERSION(opts, GIT_REMOTE_CREATE_OPTIONS_VERSION, "git_remote_create_options"); - - if (opts->name != NULL) { - if ((error = ensure_remote_name_is_valid(opts->name)) < 0) - return error; - - if (opts->repository && - (error = ensure_remote_doesnot_exist(opts->repository, opts->name)) < 0) - return error; - } - - if (opts->repository) { - if ((error = git_repository_config_snapshot(&config_ro, opts->repository)) < 0) - goto on_error; - } - - remote = git__calloc(1, sizeof(git_remote)); - GIT_ERROR_CHECK_ALLOC(remote); - - remote->repo = opts->repository; - - if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 || - (error = canonicalize_url(&canonical_url, url)) < 0) - goto on_error; - - if (opts->repository && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF)) { - if ((error = apply_insteadof(&remote->url, config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH, true)) < 0 || - (error = apply_insteadof(&remote->pushurl, config_ro, canonical_url.ptr, GIT_DIRECTION_PUSH, false)) < 0) - goto on_error; - } else { - remote->url = git__strdup(canonical_url.ptr); - GIT_ERROR_CHECK_ALLOC(remote->url); - } - - if (opts->name != NULL) { - remote->name = git__strdup(opts->name); - GIT_ERROR_CHECK_ALLOC(remote->name); - - if (opts->repository && - ((error = git_str_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 || - (error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 || - (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)) - goto on_error; - } - - if (opts->fetchspec != NULL || - (opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))) { - const char *fetch = NULL; - if (opts->fetchspec) { - fetch = opts->fetchspec; - } else { - if ((error = default_fetchspec_for_name(&specbuf, opts->name)) < 0) - goto on_error; - - fetch = git_str_cstr(&specbuf); - } - - if ((error = add_refspec(remote, fetch, true)) < 0) - goto on_error; - - /* only write for named remotes with a repository */ - if (opts->repository && opts->name && - ((error = write_add_refspec(opts->repository, opts->name, fetch, true)) < 0 || - (error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0)) - goto on_error; - - /* Move the data over to where the matching functions can find them */ - if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) - goto on_error; - } - - /* A remote without a name doesn't download tags */ - if (!opts->name) - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; - else - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; - - - git_str_dispose(&var); - - *out = remote; - error = 0; - -on_error: - if (error) - git_remote_free(remote); - - git_config_free(config_ro); - git_str_dispose(&specbuf); - git_str_dispose(&canonical_url); - git_str_dispose(&var); - return error; -} - -int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url) -{ - git_str buf = GIT_STR_INIT; - int error; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - /* Those 2 tests are duplicated here because of backward-compatibility */ - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - if (canonicalize_url(&buf, url) < 0) - return GIT_ERROR; - - git_str_clear(&buf); - - opts.repository = repo; - opts.name = name; - - error = git_remote_create_with_opts(out, url, &opts); - - git_str_dispose(&buf); - - return error; -} - -int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) -{ - int error; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - opts.repository = repo; - opts.name = name; - opts.fetchspec = fetch; - opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; - - return git_remote_create_with_opts(out, url, &opts); -} - -int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url) -{ - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.repository = repo; - - return git_remote_create_with_opts(out, url, &opts); -} - -int git_remote_create_detached(git_remote **out, const char *url) -{ - return git_remote_create_with_opts(out, url, NULL); -} - -int git_remote_dup(git_remote **dest, git_remote *source) -{ - size_t i; - int error = 0; - git_refspec *spec; - git_remote *remote = git__calloc(1, sizeof(git_remote)); - GIT_ERROR_CHECK_ALLOC(remote); - - if (source->name != NULL) { - remote->name = git__strdup(source->name); - GIT_ERROR_CHECK_ALLOC(remote->name); - } - - if (source->url != NULL) { - remote->url = git__strdup(source->url); - GIT_ERROR_CHECK_ALLOC(remote->url); - } - - if (source->pushurl != NULL) { - remote->pushurl = git__strdup(source->pushurl); - GIT_ERROR_CHECK_ALLOC(remote->pushurl); - } - - remote->repo = source->repo; - remote->download_tags = source->download_tags; - remote->prune_refs = source->prune_refs; - - if (git_vector_init(&remote->refs, 32, NULL) < 0 || - git_vector_init(&remote->refspecs, 2, NULL) < 0 || - git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { - error = -1; - goto cleanup; - } - - git_vector_foreach(&source->refspecs, i, spec) { - if ((error = add_refspec(remote, spec->string, !spec->push)) < 0) - goto cleanup; - } - - *dest = remote; - -cleanup: - - if (error < 0) - git__free(remote); - - return error; -} - -struct refspec_cb_data { - git_remote *remote; - int fetch; -}; - -static int refspec_cb(const git_config_entry *entry, void *payload) -{ - struct refspec_cb_data *data = (struct refspec_cb_data *)payload; - return add_refspec(data->remote, entry->value, data->fetch); -} - -static int get_optional_config( - bool *found, git_config *config, git_str *buf, - git_config_foreach_cb cb, void *payload) -{ - int error = 0; - const char *key = git_str_cstr(buf); - - if (git_str_oom(buf)) - return -1; - - if (cb != NULL) - error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); - else - error = git_config_get_string(payload, config, key); - - if (found) - *found = !error; - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - return error; -} - -int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) -{ - git_remote *remote = NULL; - git_str buf = GIT_STR_INIT; - const char *val; - int error = 0; - git_config *config; - struct refspec_cb_data data = { NULL }; - bool optional_setting_found = false, found; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - - remote = git__calloc(1, sizeof(git_remote)); - GIT_ERROR_CHECK_ALLOC(remote); - - remote->name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(remote->name); - - if (git_vector_init(&remote->refs, 32, NULL) < 0 || - git_vector_init(&remote->refspecs, 2, NULL) < 0 || - git_vector_init(&remote->passive_refspecs, 2, NULL) < 0 || - git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { - error = -1; - goto cleanup; - } - - if ((error = git_str_printf(&buf, "remote.%s.url", name)) < 0) - goto cleanup; - - if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) - goto cleanup; - - optional_setting_found |= found; - - remote->repo = repo; - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; - - if (found && strlen(val) > 0) { - if ((error = apply_insteadof(&remote->url, config, val, GIT_DIRECTION_FETCH, true)) < 0 || - (error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_PUSH, false)) < 0) - goto cleanup; - } - - val = NULL; - git_str_clear(&buf); - git_str_printf(&buf, "remote.%s.pushurl", name); - - if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) - goto cleanup; - - optional_setting_found |= found; - - if (!optional_setting_found) { - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_CONFIG, "remote '%s' does not exist", name); - goto cleanup; - } - - if (found && strlen(val) > 0) { - if (remote->pushurl) - git__free(remote->pushurl); - - if ((error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_FETCH, true)) < 0) - goto cleanup; - } - - data.remote = remote; - data.fetch = true; - - git_str_clear(&buf); - git_str_printf(&buf, "remote.%s.fetch", name); - - if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) - goto cleanup; - - data.fetch = false; - git_str_clear(&buf); - git_str_printf(&buf, "remote.%s.push", name); - - if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) - goto cleanup; - - if ((error = download_tags_value(remote, config)) < 0) - goto cleanup; - - if ((error = lookup_remote_prune_config(remote, config, name)) < 0) - goto cleanup; - - /* Move the data over to where the matching functions can find them */ - if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) - goto cleanup; - - *out = remote; - -cleanup: - git_config_free(config); - git_str_dispose(&buf); - - if (error < 0) - git_remote_free(remote); - - return error; -} - -static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name) -{ - git_str buf = GIT_STR_INIT; - int error = 0; - - git_str_printf(&buf, "remote.%s.prune", name); - - if ((error = git_config_get_bool(&remote->prune_refs, config, git_str_cstr(&buf))) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - - if ((error = git_config_get_bool(&remote->prune_refs, config, "fetch.prune")) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - } - } - } - - git_str_dispose(&buf); - return error; -} - -const char *git_remote_name(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->name; -} - -git_repository *git_remote_owner(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->repo; -} - -const char *git_remote_url(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->url; -} - -int git_remote_set_instance_url(git_remote *remote, const char *url) -{ - char *tmp; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(url); - - if ((tmp = git__strdup(url)) == NULL) - return -1; - - git__free(remote->url); - remote->url = tmp; - - return 0; -} - -static int set_url(git_repository *repo, const char *remote, const char *pattern, const char *url) -{ - git_config *cfg; - git_str buf = GIT_STR_INIT, canonical_url = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(remote); - - if ((error = ensure_remote_name_is_valid(remote)) < 0) - return error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - if ((error = git_str_printf(&buf, pattern, remote)) < 0) - return error; - - if (url) { - if ((error = canonicalize_url(&canonical_url, url)) < 0) - goto cleanup; - - error = git_config_set_string(cfg, buf.ptr, url); - } else { - error = git_config_delete_entry(cfg, buf.ptr); - } - -cleanup: - git_str_dispose(&canonical_url); - git_str_dispose(&buf); - - return error; -} - -int git_remote_set_url(git_repository *repo, const char *remote, const char *url) -{ - return set_url(repo, remote, CONFIG_URL_FMT, url); -} - -const char *git_remote_pushurl(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->pushurl; -} - -int git_remote_set_instance_pushurl(git_remote *remote, const char *url) -{ - char *tmp; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(url); - - if ((tmp = git__strdup(url)) == NULL) - return -1; - - git__free(remote->pushurl); - remote->pushurl = tmp; - - return 0; -} - -int git_remote_set_pushurl(git_repository *repo, const char *remote, const char *url) -{ - return set_url(repo, remote, CONFIG_PUSHURL_FMT, url); -} - -static int resolve_url( - git_str *resolved_url, - const char *url, - int direction, - const git_remote_callbacks *callbacks) -{ -#ifdef GIT_DEPRECATE_HARD - GIT_UNUSED(direction); - GIT_UNUSED(callbacks); -#else - git_buf buf = GIT_BUF_INIT; - int error; - - if (callbacks && callbacks->resolve_url) { - error = callbacks->resolve_url(&buf, url, direction, callbacks->payload); - - if (error != GIT_PASSTHROUGH) { - git_error_set_after_callback_function(error, "git_resolve_url_cb"); - - git_str_set(resolved_url, buf.ptr, buf.size); - git_buf_dispose(&buf); - - return error; - } - } -#endif - - return git_str_sets(resolved_url, url); -} - -int git_remote__urlfordirection( - git_str *url_out, - struct git_remote *remote, - int direction, - const git_remote_callbacks *callbacks) -{ - const char *url = NULL; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); - - if (callbacks && callbacks->remote_ready) { - int status = callbacks->remote_ready(remote, direction, callbacks->payload); - - if (status != 0 && status != GIT_PASSTHROUGH) { - git_error_set_after_callback_function(status, "git_remote_ready_cb"); - return status; - } - } - - if (direction == GIT_DIRECTION_FETCH) - url = remote->url; - else if (direction == GIT_DIRECTION_PUSH) - url = 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 resolve_url(url_out, url, direction, callbacks); -} - -int git_remote_connect_options_init( - git_remote_connect_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_remote_connect_options, GIT_REMOTE_CONNECT_OPTIONS_INIT); - return 0; -} - -int git_remote_connect_options_dup( - git_remote_connect_options *dst, - const git_remote_connect_options *src) -{ - memcpy(dst, src, sizeof(git_remote_connect_options)); - - if (git_proxy_options_dup(&dst->proxy_opts, &src->proxy_opts) < 0 || - git_strarray_copy(&dst->custom_headers, &src->custom_headers) < 0) - return -1; - - return 0; -} - -void git_remote_connect_options_dispose(git_remote_connect_options *opts) -{ - if (!opts) - return; - - git_strarray_dispose(&opts->custom_headers); - git_proxy_options_dispose(&opts->proxy_opts); -} - -static size_t http_header_name_length(const char *http_header) -{ - const char *colon = strchr(http_header, ':'); - if (!colon) - return 0; - return colon - http_header; -} - -static bool is_malformed_http_header(const char *http_header) -{ - const char *c; - size_t name_len; - - /* Disallow \r and \n */ - if ((c = strchr(http_header, '\r')) != NULL) - return true; - if ((c = strchr(http_header, '\n')) != NULL) - return true; - - /* Require a header name followed by : */ - if ((name_len = http_header_name_length(http_header)) < 1) - return true; - - return false; -} - -static char *forbidden_custom_headers[] = { - "User-Agent", - "Host", - "Accept", - "Content-Type", - "Transfer-Encoding", - "Content-Length", -}; - -static bool is_forbidden_custom_header(const char *custom_header) -{ - unsigned long i; - 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++) - if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) - return true; - - return false; -} - -static int validate_custom_headers(const git_strarray *custom_headers) -{ - size_t i; - - if (!custom_headers) - return 0; - - for (i = 0; i < custom_headers->count; i++) { - if (is_malformed_http_header(custom_headers->strings[i])) { - git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); - return -1; - } - - if (is_forbidden_custom_header(custom_headers->strings[i])) { - git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); - return -1; - } - } - - return 0; -} - -static int lookup_redirect_config( - git_remote_redirect_t *out, - git_repository *repo) -{ - git_config *config; - const char *value; - int bool_value, error = 0; - - if (!repo) { - *out = GIT_REMOTE_REDIRECT_INITIAL; - return 0; - } - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - goto done; - - if ((error = git_config_get_string(&value, config, "http.followRedirects")) < 0) { - if (error == GIT_ENOTFOUND) { - *out = GIT_REMOTE_REDIRECT_INITIAL; - error = 0; - } - - goto done; - } - - if (git_config_parse_bool(&bool_value, value) == 0) { - *out = bool_value ? GIT_REMOTE_REDIRECT_ALL : - GIT_REMOTE_REDIRECT_NONE; - } else if (strcasecmp(value, "initial") == 0) { - *out = GIT_REMOTE_REDIRECT_INITIAL; - } else { - git_error_set(GIT_ERROR_CONFIG, "invalid configuration setting '%s' for 'http.followRedirects'", value); - error = -1; - } - -done: - git_config_free(config); - return error; -} - -int git_remote_connect_options_normalize( - git_remote_connect_options *dst, - git_repository *repo, - const git_remote_connect_options *src) -{ - git_remote_connect_options_dispose(dst); - git_remote_connect_options_init(dst, GIT_REMOTE_CONNECT_OPTIONS_VERSION); - - if (src) { - GIT_ERROR_CHECK_VERSION(src, GIT_REMOTE_CONNECT_OPTIONS_VERSION, "git_remote_connect_options"); - GIT_ERROR_CHECK_VERSION(&src->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - GIT_ERROR_CHECK_VERSION(&src->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - - if (validate_custom_headers(&src->custom_headers) < 0 || - git_remote_connect_options_dup(dst, src) < 0) - return -1; - } - - if (dst->follow_redirects == 0) { - if (lookup_redirect_config(&dst->follow_redirects, repo) < 0) - return -1; - } - - return 0; -} - -int git_remote_connect_ext( - git_remote *remote, - git_direction direction, - const git_remote_connect_options *given_opts) -{ - git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - git_str url = GIT_STR_INIT; - git_transport *t; - int error; - - GIT_ASSERT_ARG(remote); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_remote_connect_options)); - - GIT_ERROR_CHECK_VERSION(&opts.callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - GIT_ERROR_CHECK_VERSION(&opts.proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - - t = remote->transport; - - if ((error = git_remote__urlfordirection(&url, remote, direction, &opts.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 && opts.callbacks.transport && - (error = opts.callbacks.transport(&t, remote, opts.callbacks.payload)) < 0) - 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.ptr)) < 0) - goto on_error; - - if ((error = t->connect(t, url.ptr, direction, &opts)) != 0) - goto on_error; - - remote->transport = t; - - git_str_dispose(&url); - - return 0; - -on_error: - if (t) - t->free(t); - - git_str_dispose(&url); - - if (t == remote->transport) - remote->transport = NULL; - - return error; -} - -int git_remote_connect( - git_remote *remote, - git_direction direction, - const git_remote_callbacks *callbacks, - const git_proxy_options *proxy, - const git_strarray *custom_headers) -{ - git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - - if (callbacks) - memcpy(&opts.callbacks, callbacks, sizeof(git_remote_callbacks)); - - if (proxy) - memcpy(&opts.proxy_opts, proxy, sizeof(git_proxy_options)); - - if (custom_headers) - memcpy(&opts.custom_headers, custom_headers, sizeof(git_strarray)); - - return git_remote_connect_ext(remote, direction, &opts); -} - -int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (!remote->transport) { - git_error_set(GIT_ERROR_NET, "this remote has never connected"); - return -1; - } - - return remote->transport->ls(out, size, remote->transport); -} - -int git_remote_capabilities(unsigned int *out, git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - *out = 0; - - if (!remote->transport) { - git_error_set(GIT_ERROR_NET, "this remote has never connected"); - return -1; - } - - return remote->transport->capabilities(out, remote->transport); -} - -static int lookup_config(char **out, git_config *cfg, const char *name) -{ - git_config_entry *ce = NULL; - int error; - - if ((error = git_config__lookup_entry(&ce, cfg, name, false)) < 0) - return error; - - if (ce && ce->value) { - *out = git__strdup(ce->value); - GIT_ERROR_CHECK_ALLOC(*out); - } else { - error = GIT_ENOTFOUND; - } - - git_config_entry_free(ce); - return error; -} - -static void url_config_trim(git_net_url *url) -{ - size_t len = strlen(url->path); - - if (url->path[len - 1] == '/') { - len--; - } else { - while (len && url->path[len - 1] != '/') - len--; - } - - url->path[len] = '\0'; -} - -static int http_proxy_config(char **out, git_remote *remote, git_net_url *url) -{ - git_config *cfg = NULL; - git_str buf = GIT_STR_INIT; - git_net_url lookup_url = GIT_NET_URL_INIT; - int error; - - if ((error = git_net_url_dup(&lookup_url, url)) < 0) - goto done; - - if (remote->repo) { - if ((error = git_repository_config(&cfg, remote->repo)) < 0) - goto done; - } else { - if ((error = git_config_open_default(&cfg)) < 0) - goto done; - } - - /* remote..proxy config setting */ - if (remote->name && remote->name[0]) { - git_str_clear(&buf); - - if ((error = git_str_printf(&buf, "remote.%s.proxy", remote->name)) < 0 || - (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) - goto done; - } - - while (true) { - git_str_clear(&buf); - - if ((error = git_str_puts(&buf, "http.")) < 0 || - (error = git_net_url_fmt(&buf, &lookup_url)) < 0 || - (error = git_str_puts(&buf, ".proxy")) < 0 || - (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) - goto done; - - if (! lookup_url.path[0]) - break; - - url_config_trim(&lookup_url); - } - - git_str_clear(&buf); - - error = lookup_config(out, cfg, "http.proxy"); - -done: - git_config_free(cfg); - git_str_dispose(&buf); - git_net_url_dispose(&lookup_url); - return error; -} - -static int http_proxy_env(char **out, git_remote *remote, git_net_url *url) -{ - git_str proxy_env = GIT_STR_INIT, no_proxy_env = GIT_STR_INIT; - bool use_ssl = (strcmp(url->scheme, "https") == 0); - int error; - - GIT_UNUSED(remote); - - /* http_proxy / https_proxy environment variables */ - error = git__getenv(&proxy_env, use_ssl ? "https_proxy" : "http_proxy"); - - /* try uppercase environment variables */ - if (error == GIT_ENOTFOUND) - error = git__getenv(&proxy_env, use_ssl ? "HTTPS_PROXY" : "HTTP_PROXY"); - - if (error) - goto done; - - /* no_proxy/NO_PROXY environment variables */ - error = git__getenv(&no_proxy_env, "no_proxy"); - - if (error == GIT_ENOTFOUND) - error = git__getenv(&no_proxy_env, "NO_PROXY"); - - if (error && error != GIT_ENOTFOUND) - goto done; - - if (!git_net_url_matches_pattern_list(url, no_proxy_env.ptr)) - *out = git_str_detach(&proxy_env); - else - error = GIT_ENOTFOUND; - -done: - git_str_dispose(&proxy_env); - git_str_dispose(&no_proxy_env); - return error; -} - -int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(remote); - - *out = NULL; - - /* - * Go through the possible sources for proxy configuration, - * Examine the various git config options first, then - * consult environment variables. - */ - if ((error = http_proxy_config(out, remote, url)) != GIT_ENOTFOUND || - (error = http_proxy_env(out, remote, url)) != GIT_ENOTFOUND) - return error; - - return 0; -} - -/* DWIM `refspecs` based on `refs` and append the output to `out` */ -static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs) -{ - size_t i; - git_refspec *spec; - - git_vector_foreach(refspecs, i, spec) { - if (git_refspec__dwim_one(out, spec, refs) < 0) - return -1; - } - - return 0; -} - -static void free_refspecs(git_vector *vec) -{ - size_t i; - git_refspec *spec; - - git_vector_foreach(vec, i, spec) { - git_refspec__dispose(spec); - git__free(spec); - } - - git_vector_clear(vec); -} - -static int remote_head_cmp(const void *_a, const void *_b) -{ - const git_remote_head *a = (git_remote_head *) _a; - const git_remote_head *b = (git_remote_head *) _b; - - return git__strcmp_cb(a->name, b->name); -} - -static int ls_to_vector(git_vector *out, git_remote *remote) -{ - git_remote_head **heads; - size_t heads_len, i; - - if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) - return -1; - - if (git_vector_init(out, heads_len, remote_head_cmp) < 0) - return -1; - - for (i = 0; i < heads_len; i++) { - if (git_vector_insert(out, heads[i]) < 0) - return -1; - } - - return 0; -} - -#define copy_opts(out, in) \ - if (in) { \ - (out)->callbacks = (in)->callbacks; \ - (out)->proxy_opts = (in)->proxy_opts; \ - (out)->custom_headers = (in)->custom_headers; \ - (out)->follow_redirects = (in)->follow_redirects; \ - } - -GIT_INLINE(int) connect_opts_from_fetch_opts( - git_remote_connect_options *out, - git_remote *remote, - const git_fetch_options *fetch_opts) -{ - git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; - copy_opts(&tmp, fetch_opts); - return git_remote_connect_options_normalize(out, remote->repo, &tmp); -} - -static int connect_or_reset_options( - git_remote *remote, - int direction, - git_remote_connect_options *opts) -{ - if (!git_remote_connected(remote)) { - return git_remote_connect_ext(remote, direction, opts); - } else { - return remote->transport->set_connect_opts(remote->transport, opts); - } -} - -/* Download from an already connected remote. */ -static int git_remote__download( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts) -{ - git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; - size_t i; - int error; - - if (ls_to_vector(&refs, remote) < 0) - return -1; - - if ((error = git_vector_init(&specs, 0, NULL)) < 0) - goto on_error; - - remote->passed_refspecs = 0; - if (!refspecs || !refspecs->count) { - to_active = &remote->refspecs; - } else { - for (i = 0; i < refspecs->count; i++) { - if ((error = add_refspec_to(&specs, refspecs->strings[i], true)) < 0) - goto on_error; - } - - to_active = &specs; - remote->passed_refspecs = 1; - } - - free_refspecs(&remote->passive_refspecs); - if ((error = dwim_refspecs(&remote->passive_refspecs, &remote->refspecs, &refs)) < 0) - goto on_error; - - free_refspecs(&remote->active_refspecs); - error = dwim_refspecs(&remote->active_refspecs, to_active, &refs); - - git_vector_free(&refs); - free_refspecs(&specs); - git_vector_free(&specs); - - if (error < 0) - goto on_error; - - if (remote->push) { - git_push_free(remote->push); - remote->push = NULL; - } - - if ((error = git_fetch_negotiate(remote, opts)) < 0) - goto on_error; - - error = git_fetch_download_pack(remote); - -on_error: - git_vector_free(&refs); - free_refspecs(&specs); - git_vector_free(&specs); - return error; -} - -int git_remote_download( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts) -{ - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) - return -1; - - if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) - return error; - - return git_remote__download(remote, refspecs, opts); -} - -int git_remote_fetch( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts, - const char *reflog_message) -{ - int error, update_fetchhead = 1; - git_remote_autotag_option_t tagopt = remote->download_tags; - bool prune = false; - git_str reflog_msg_buf = GIT_STR_INIT; - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) - return -1; - - if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) - return error; - - if (opts) { - update_fetchhead = opts->update_fetchhead; - tagopt = opts->download_tags; - } - - /* Connect and download everything */ - error = git_remote__download(remote, refspecs, opts); - - /* We don't need to be connected anymore */ - git_remote_disconnect(remote); - - /* If the download failed, return the error */ - if (error != 0) - goto done; - - /* Default reflog message */ - if (reflog_message) - git_str_sets(&reflog_msg_buf, reflog_message); - else { - git_str_printf(&reflog_msg_buf, "fetch %s", - remote->name ? remote->name : remote->url); - } - - /* Create "remote/foo" branches for all remote branches */ - error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); - git_str_dispose(&reflog_msg_buf); - if (error < 0) - goto done; - - if (opts && opts->prune == GIT_FETCH_PRUNE) - prune = true; - else if (opts && opts->prune == GIT_FETCH_PRUNE_UNSPECIFIED && remote->prune_refs) - prune = true; - else if (opts && opts->prune == GIT_FETCH_NO_PRUNE) - prune = false; - else - prune = remote->prune_refs; - - if (prune) - error = git_remote_prune(remote, &connect_opts.callbacks); - -done: - git_remote_connect_options_dispose(&connect_opts); - return error; -} - -static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) -{ - unsigned int i; - git_remote_head *remote_ref; - - GIT_ASSERT_ARG(update_heads); - GIT_ASSERT_ARG(fetchspec_src); - - *out = NULL; - - git_vector_foreach(update_heads, i, remote_ref) { - if (strcmp(remote_ref->name, fetchspec_src) == 0) { - *out = remote_ref; - break; - } - } - - return 0; -} - -static int ref_to_update(int *update, git_str *remote_name, git_remote *remote, git_refspec *spec, const char *ref_name) -{ - int error = 0; - git_repository *repo; - git_str upstream_remote = GIT_STR_INIT; - git_str upstream_name = GIT_STR_INIT; - - repo = git_remote_owner(remote); - - if ((!git_reference__is_branch(ref_name)) || - !git_remote_name(remote) || - (error = git_branch__upstream_remote(&upstream_remote, repo, ref_name) < 0) || - git__strcmp(git_remote_name(remote), git_str_cstr(&upstream_remote)) || - (error = git_branch__upstream_name(&upstream_name, repo, ref_name)) < 0 || - !git_refspec_dst_matches(spec, git_str_cstr(&upstream_name)) || - (error = git_refspec__rtransform(remote_name, spec, upstream_name.ptr)) < 0) { - /* Not an error if there is no upstream */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - *update = 0; - } else { - *update = 1; - } - - git_str_dispose(&upstream_remote); - git_str_dispose(&upstream_name); - return error; -} - -static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_refspec *spec, git_vector *update_heads, git_reference *ref) -{ - git_reference *resolved_ref = NULL; - git_str remote_name = GIT_STR_INIT; - git_config *config = NULL; - const char *ref_name; - int error = 0, update; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(ref); - - *out = NULL; - - error = git_reference_resolve(&resolved_ref, ref); - - /* If we're in an unborn branch, let's pretend nothing happened */ - if (error == GIT_ENOTFOUND && git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - ref_name = git_reference_symbolic_target(ref); - error = 0; - } else { - ref_name = git_reference_name(resolved_ref); - } - - /* - * The ref name may be unresolvable - perhaps it's pointing to - * something invalid. In this case, there is no remote head for - * this ref. - */ - if (!ref_name) { - error = 0; - goto cleanup; - } - - if ((error = ref_to_update(&update, &remote_name, remote, spec, ref_name)) < 0) - goto cleanup; - - if (update) - error = remote_head_for_fetchspec_src(out, update_heads, git_str_cstr(&remote_name)); - -cleanup: - git_str_dispose(&remote_name); - git_reference_free(resolved_ref); - git_config_free(config); - return error; -} - -static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads) -{ - git_reference *head_ref = NULL; - git_fetchhead_ref *fetchhead_ref; - git_remote_head *remote_ref, *merge_remote_ref; - git_vector fetchhead_refs; - bool include_all_fetchheads; - unsigned int i = 0; - int error = 0; - - GIT_ASSERT_ARG(remote); - - /* no heads, nothing to do */ - if (update_heads->length == 0) - return 0; - - if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0) - return -1; - - /* Iff refspec is * (but not subdir slash star), include tags */ - include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0); - - /* Determine what to merge: if refspec was a wildcard, just use HEAD */ - if (git_refspec_is_wildcard(spec)) { - if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 || - (error = remote_head_for_ref(&merge_remote_ref, remote, spec, update_heads, head_ref)) < 0) - goto cleanup; - } else { - /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */ - if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0) - goto cleanup; - } - - /* Create the FETCH_HEAD file */ - git_vector_foreach(update_heads, i, remote_ref) { - int merge_this_fetchhead = (merge_remote_ref == remote_ref); - - if (!include_all_fetchheads && - !git_refspec_src_matches(spec, remote_ref->name) && - !merge_this_fetchhead) - continue; - - if (git_fetchhead_ref_create(&fetchhead_ref, - &remote_ref->oid, - merge_this_fetchhead, - remote_ref->name, - git_remote_url(remote)) < 0) - goto cleanup; - - if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0) - goto cleanup; - } - - git_fetchhead_write(remote->repo, &fetchhead_refs); - -cleanup: - for (i = 0; i < fetchhead_refs.length; ++i) - git_fetchhead_ref_free(fetchhead_refs.contents[i]); - - git_vector_free(&fetchhead_refs); - git_reference_free(head_ref); - - return error; -} - -/** - * Generate a list of candidates for pruning by getting a list of - * references which match the rhs of an active refspec. - */ -static int prune_candidates(git_vector *candidates, git_remote *remote) -{ - git_strarray arr = { 0 }; - size_t i; - int error; - - if ((error = git_reference_list(&arr, remote->repo)) < 0) - return error; - - for (i = 0; i < arr.count; i++) { - const char *refname = arr.strings[i]; - char *refname_dup; - - if (!git_remote__matching_dst_refspec(remote, refname)) - continue; - - refname_dup = git__strdup(refname); - GIT_ERROR_CHECK_ALLOC(refname_dup); - - if ((error = git_vector_insert(candidates, refname_dup)) < 0) - goto out; - } - -out: - git_strarray_dispose(&arr); - return error; -} - -static int find_head(const void *_a, const void *_b) -{ - git_remote_head *a = (git_remote_head *) _a; - git_remote_head *b = (git_remote_head *) _b; - - return strcmp(a->name, b->name); -} - -int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks) -{ - size_t i, j; - git_vector remote_refs = GIT_VECTOR_INIT; - git_vector candidates = GIT_VECTOR_INIT; - const git_refspec *spec; - const char *refname; - int error; - git_oid zero_id = {{ 0 }}; - - if (callbacks) - GIT_ERROR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - - if ((error = ls_to_vector(&remote_refs, remote)) < 0) - goto cleanup; - - git_vector_set_cmp(&remote_refs, find_head); - - if ((error = prune_candidates(&candidates, remote)) < 0) - goto cleanup; - - /* - * Remove those entries from the candidate list for which we - * can find a remote reference in at least one refspec. - */ - git_vector_foreach(&candidates, i, refname) { - git_vector_foreach(&remote->active_refspecs, j, spec) { - git_str buf = GIT_STR_INIT; - size_t pos; - char *src_name; - git_remote_head key = {0}; - - if (!git_refspec_dst_matches(spec, refname)) - continue; - - if ((error = git_refspec__rtransform(&buf, spec, refname)) < 0) - goto cleanup; - - key.name = (char *) git_str_cstr(&buf); - error = git_vector_bsearch(&pos, &remote_refs, &key); - git_str_dispose(&buf); - - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (error == GIT_ENOTFOUND) - continue; - - /* If we did find a source, remove it from the candidates. */ - if ((error = git_vector_set((void **) &src_name, &candidates, i, NULL)) < 0) - goto cleanup; - - git__free(src_name); - break; - } - } - - /* - * For those candidates still left in the list, we need to - * remove them. We do not remove symrefs, as those are for - * stuff like origin/HEAD which will never match, but we do - * not want to remove them. - */ - git_vector_foreach(&candidates, i, refname) { - git_reference *ref; - git_oid id; - - if (refname == NULL) - continue; - - error = git_reference_lookup(&ref, remote->repo, refname); - /* as we want it gone, let's not consider this an error */ - if (error == GIT_ENOTFOUND) - continue; - - if (error < 0) - goto cleanup; - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - git_reference_free(ref); - continue; - } - - git_oid_cpy(&id, git_reference_target(ref)); - error = git_reference_delete(ref); - git_reference_free(ref); - if (error < 0) - goto cleanup; - - if (callbacks && callbacks->update_tips) - error = callbacks->update_tips(refname, &id, &zero_id, callbacks->payload); - - if (error < 0) - goto cleanup; - } - -cleanup: - git_vector_free(&remote_refs); - git_vector_free_deep(&candidates); - return error; -} - -static int update_ref( - const git_remote *remote, - const char *ref_name, - git_oid *id, - const char *msg, - const git_remote_callbacks *callbacks) -{ - git_reference *ref; - git_oid old_id; - int error; - - error = git_reference_name_to_id(&old_id, remote->repo, ref_name); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - else if (error == 0 && git_oid_equal(&old_id, id)) - return 0; - - /* If we did find a current reference, make sure we haven't lost a race */ - if (error) - error = git_reference_create(&ref, remote->repo, ref_name, id, true, msg); - else - error = git_reference_create_matching(&ref, remote->repo, ref_name, id, true, &old_id, msg); - - git_reference_free(ref); - - if (error < 0) - return error; - - if (callbacks && callbacks->update_tips && - (error = callbacks->update_tips(ref_name, &old_id, id, callbacks->payload)) < 0) - return error; - - return 0; -} - -static int update_one_tip( - git_vector *update_heads, - git_remote *remote, - git_refspec *spec, - git_remote_head *head, - git_refspec *tagspec, - git_remote_autotag_option_t tagopt, - const char *log_message, - const git_remote_callbacks *callbacks) -{ - git_odb *odb; - git_str refname = GIT_STR_INIT; - git_reference *ref = NULL; - bool autotag = false; - git_oid old; - int valid; - int error; - - if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) - goto done; - - /* Ignore malformed ref names (which also saves us from tag^{} */ - if ((error = git_reference_name_is_valid(&valid, head->name)) < 0) - goto done; - - if (!valid) - goto done; - - /* If we have a tag, see if the auto-follow rules say to update it */ - if (git_refspec_src_matches(tagspec, head->name)) { - if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO) - autotag = true; - - if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { - if (git_str_puts(&refname, head->name) < 0) - goto done; - } - } - - /* If we didn't want to auto-follow the tag, check if the refspec matches */ - if (!autotag && git_refspec_src_matches(spec, head->name)) { - if (spec->dst) { - if ((error = git_refspec__transform(&refname, spec, head->name)) < 0) - goto done; - } else { - /* - * no rhs means store it in FETCH_HEAD, even if we don't - * update anything else. - */ - error = git_vector_insert(update_heads, head); - goto done; - } - } - - /* If we still don't have a refname, we don't want it */ - if (git_str_len(&refname) == 0) - goto done; - - /* In autotag mode, only create tags for objects already in db */ - if (autotag && !git_odb_exists(odb, &head->oid)) - goto done; - - if (!autotag && (error = git_vector_insert(update_heads, head)) < 0) - goto done; - - error = git_reference_name_to_id(&old, remote->repo, refname.ptr); - - if (error < 0 && error != GIT_ENOTFOUND) - goto done; - - if (!(error || error == GIT_ENOTFOUND) && - !spec->force && - !git_graph_descendant_of(remote->repo, &head->oid, &old)) { - error = 0; - goto done; - } - - if (error == GIT_ENOTFOUND) { - memset(&old, 0, GIT_OID_RAWSZ); - error = 0; - - if (autotag && (error = git_vector_insert(update_heads, head)) < 0) - goto done; - } - - if (!git_oid__cmp(&old, &head->oid)) - goto done; - - /* In autotag mode, don't overwrite any locally-existing tags */ - error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, - log_message); - - if (error < 0) { - if (error == GIT_EEXISTS) - error = 0; - - goto done; - } - - if (callbacks && callbacks->update_tips != NULL && - (error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0) - git_error_set_after_callback_function(error, "git_remote_fetch"); - -done: - git_reference_free(ref); - git_str_dispose(&refname); - return error; -} - -static int update_tips_for_spec( - git_remote *remote, - const git_remote_callbacks *callbacks, - int update_fetchhead, - git_remote_autotag_option_t tagopt, - git_refspec *spec, - git_vector *refs, - const char *log_message) -{ - git_refspec tagspec; - git_remote_head *head, oid_head; - git_vector update_heads; - int error = 0; - size_t i; - - GIT_ASSERT_ARG(remote); - - if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) - return -1; - - /* Make a copy of the transport's refs */ - if (git_vector_init(&update_heads, 16, NULL) < 0) - return -1; - - /* Update tips based on the remote heads */ - git_vector_foreach(refs, i, head) { - if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0) - goto on_error; - } - - /* Handle specified oid sources */ - if (git_oid__is_hexstr(spec->src)) { - git_oid id; - - if ((error = git_oid_fromstr(&id, spec->src)) < 0) - goto on_error; - - if (spec->dst && - (error = update_ref(remote, spec->dst, &id, log_message, callbacks)) < 0) - goto on_error; - - git_oid_cpy(&oid_head.oid, &id); - oid_head.name = spec->src; - - if ((error = git_vector_insert(&update_heads, &oid_head)) < 0) - goto on_error; - } - - if (update_fetchhead && - (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) - goto on_error; - - git_refspec__dispose(&tagspec); - git_vector_free(&update_heads); - return 0; - -on_error: - git_refspec__dispose(&tagspec); - git_vector_free(&update_heads); - return -1; - -} - -/** - * Iteration over the three vectors, with a pause whenever we find a match - * - * On each stop, we store the iteration stat in the inout i,j,k - * parameters, and return the currently matching passive refspec as - * well as the head which we matched. - */ -static int next_head(const git_remote *remote, git_vector *refs, - git_refspec **out_spec, git_remote_head **out_head, - size_t *out_i, size_t *out_j, size_t *out_k) -{ - const git_vector *active, *passive; - git_remote_head *head; - git_refspec *spec, *passive_spec; - size_t i, j, k; - int valid; - - active = &remote->active_refspecs; - passive = &remote->passive_refspecs; - - i = *out_i; - j = *out_j; - k = *out_k; - - for (; i < refs->length; i++) { - head = git_vector_get(refs, i); - - if (git_reference_name_is_valid(&valid, head->name) < 0) - return -1; - - if (!valid) - continue; - - for (; j < active->length; j++) { - spec = git_vector_get(active, j); - - if (!git_refspec_src_matches(spec, head->name)) - continue; - - for (; k < passive->length; k++) { - passive_spec = git_vector_get(passive, k); - - if (!git_refspec_src_matches(passive_spec, head->name)) - continue; - - *out_spec = passive_spec; - *out_head = head; - *out_i = i; - *out_j = j; - *out_k = k + 1; - return 0; - - } - k = 0; - } - j = 0; - } - - return GIT_ITEROVER; -} - -static int opportunistic_updates( - const git_remote *remote, - const git_remote_callbacks *callbacks, - git_vector *refs, - const char *msg) -{ - size_t i, j, k; - git_refspec *spec; - git_remote_head *head; - git_str refname = GIT_STR_INIT; - int error = 0; - - i = j = k = 0; - - /* Handle refspecs matching remote heads */ - while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) { - /* - * If we got here, there is a refspec which was used - * for fetching which matches the source of one of the - * passive refspecs, so we should update that - * remote-tracking branch, but not add it to - * FETCH_HEAD - */ - - git_str_clear(&refname); - if ((error = git_refspec__transform(&refname, spec, head->name)) < 0 || - (error = update_ref(remote, refname.ptr, &head->oid, msg, callbacks)) < 0) - goto cleanup; - } - - if (error != GIT_ITEROVER) - goto cleanup; - - error = 0; - -cleanup: - git_str_dispose(&refname); - return error; -} - -static int truncate_fetch_head(const char *gitdir) -{ - git_str path = GIT_STR_INIT; - int error; - - if ((error = git_str_joinpath(&path, gitdir, GIT_FETCH_HEAD_FILE)) < 0) - return error; - - error = git_futils_truncate(path.ptr, GIT_REFS_FILE_MODE); - git_str_dispose(&path); - - return error; -} - -int git_remote_update_tips( - git_remote *remote, - const git_remote_callbacks *callbacks, - int update_fetchhead, - git_remote_autotag_option_t download_tags, - const char *reflog_message) -{ - git_refspec *spec, tagspec; - git_vector refs = GIT_VECTOR_INIT; - git_remote_autotag_option_t tagopt; - int error; - size_t i; - - /* push has its own logic hidden away in the push object */ - if (remote->push) { - return git_push_update_tips(remote->push, callbacks); - } - - if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) - return -1; - - - if ((error = ls_to_vector(&refs, remote)) < 0) - goto out; - - if (download_tags == GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) - tagopt = remote->download_tags; - else - tagopt = download_tags; - - if ((error = truncate_fetch_head(git_repository_path(remote->repo))) < 0) - goto out; - - if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0) - goto out; - } - - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (spec->push) - continue; - - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0) - goto out; - } - - /* Only try to do opportunistic updates if the refspec lists differ. */ - if (remote->passed_refspecs) - error = opportunistic_updates(remote, callbacks, &refs, reflog_message); - -out: - git_vector_free(&refs); - git_refspec__dispose(&tagspec); - return error; -} - -int git_remote_connected(const git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (!remote->transport || !remote->transport->is_connected) - return 0; - - /* Ask the transport if it's connected. */ - return remote->transport->is_connected(remote->transport); -} - -int git_remote_stop(git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (remote->transport && remote->transport->cancel) - remote->transport->cancel(remote->transport); - - return 0; -} - -int git_remote_disconnect(git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (git_remote_connected(remote)) - remote->transport->close(remote->transport); - - return 0; -} - -static void free_heads(git_vector *heads) -{ - git_remote_head *head; - size_t i; - - git_vector_foreach(heads, i, head) { - git__free(head->name); - git__free(head); - } -} - -void git_remote_free(git_remote *remote) -{ - if (remote == NULL) - return; - - if (remote->transport != NULL) { - git_remote_disconnect(remote); - - remote->transport->free(remote->transport); - remote->transport = NULL; - } - - git_vector_free(&remote->refs); - - free_refspecs(&remote->refspecs); - git_vector_free(&remote->refspecs); - - free_refspecs(&remote->active_refspecs); - git_vector_free(&remote->active_refspecs); - - free_refspecs(&remote->passive_refspecs); - git_vector_free(&remote->passive_refspecs); - - free_heads(&remote->local_heads); - git_vector_free(&remote->local_heads); - - git_push_free(remote->push); - git__free(remote->url); - git__free(remote->pushurl); - git__free(remote->name); - git__free(remote); -} - -static int remote_list_cb(const git_config_entry *entry, void *payload) -{ - git_vector *list = payload; - const char *name = entry->name + strlen("remote."); - size_t namelen = strlen(name); - char *remote_name; - - /* we know name matches "remote..(push)?url" */ - - if (!strcmp(&name[namelen - 4], ".url")) - remote_name = git__strndup(name, namelen - 4); /* strip ".url" */ - else - remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */ - GIT_ERROR_CHECK_ALLOC(remote_name); - - return git_vector_insert(list, remote_name); -} - -int git_remote_list(git_strarray *remotes_list, git_repository *repo) -{ - int error; - git_config *cfg; - git_vector list = GIT_VECTOR_INIT; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - if ((error = git_vector_init(&list, 4, git__strcmp_cb)) < 0) - return error; - - error = git_config_foreach_match( - cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list); - - if (error < 0) { - git_vector_free_deep(&list); - return error; - } - - git_vector_uniq(&list, git__free); - - remotes_list->strings = - (char **)git_vector_detach(&remotes_list->count, NULL, &list); - - return 0; -} - -const git_indexer_progress *git_remote_stats(git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return &remote->stats; -} - -git_remote_autotag_option_t git_remote_autotag(const git_remote *remote) -{ - return remote->download_tags; -} - -int git_remote_set_autotag(git_repository *repo, const char *remote, git_remote_autotag_option_t value) -{ - git_str var = GIT_STR_INIT; - git_config *config; - int error; - - GIT_ASSERT_ARG(repo && remote); - - if ((error = ensure_remote_name_is_valid(remote)) < 0) - return error; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_str_printf(&var, CONFIG_TAGOPT_FMT, remote))) - return error; - - switch (value) { - case GIT_REMOTE_DOWNLOAD_TAGS_NONE: - error = git_config_set_string(config, var.ptr, "--no-tags"); - break; - case GIT_REMOTE_DOWNLOAD_TAGS_ALL: - error = git_config_set_string(config, var.ptr, "--tags"); - break; - case GIT_REMOTE_DOWNLOAD_TAGS_AUTO: - error = git_config_delete_entry(config, var.ptr); - if (error == GIT_ENOTFOUND) - error = 0; - break; - default: - git_error_set(GIT_ERROR_INVALID, "invalid value for the tagopt setting"); - error = -1; - } - - git_str_dispose(&var); - return error; -} - -int git_remote_prune_refs(const git_remote *remote) -{ - return remote->prune_refs; -} - -static int rename_remote_config_section( - git_repository *repo, - const char *old_name, - const char *new_name) -{ - git_str old_section_name = GIT_STR_INIT, - new_section_name = GIT_STR_INIT; - int error = -1; - - if (git_str_printf(&old_section_name, "remote.%s", old_name) < 0) - goto cleanup; - - if (new_name && - (git_str_printf(&new_section_name, "remote.%s", new_name) < 0)) - goto cleanup; - - error = git_config_rename_section( - repo, - git_str_cstr(&old_section_name), - new_name ? git_str_cstr(&new_section_name) : NULL); - -cleanup: - git_str_dispose(&old_section_name); - git_str_dispose(&new_section_name); - - return error; -} - -struct update_data { - git_config *config; - const char *old_remote_name; - const char *new_remote_name; -}; - -static int update_config_entries_cb( - const git_config_entry *entry, - void *payload) -{ - struct update_data *data = (struct update_data *)payload; - - if (strcmp(entry->value, data->old_remote_name)) - return 0; - - return git_config_set_string( - data->config, entry->name, data->new_remote_name); -} - -static int update_branch_remote_config_entry( - git_repository *repo, - const char *old_name, - const char *new_name) -{ - int error; - struct update_data data = { NULL }; - - if ((error = git_repository_config__weakptr(&data.config, repo)) < 0) - return error; - - data.old_remote_name = old_name; - data.new_remote_name = new_name; - - return git_config_foreach_match( - data.config, "branch\\..+\\.remote", update_config_entries_cb, &data); -} - -static int rename_one_remote_reference( - git_reference *reference_in, - const char *old_remote_name, - const char *new_remote_name) -{ - int error; - git_reference *ref = NULL, *dummy = NULL; - git_str namespace = GIT_STR_INIT, old_namespace = GIT_STR_INIT; - git_str new_name = GIT_STR_INIT; - git_str log_message = GIT_STR_INIT; - size_t pfx_len; - const char *target; - - if ((error = git_str_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0) - return error; - - pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1; - git_str_puts(&new_name, namespace.ptr); - if ((error = git_str_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0) - goto cleanup; - - if ((error = git_str_printf(&log_message, - "renamed remote %s to %s", - old_remote_name, new_remote_name)) < 0) - goto cleanup; - - if ((error = git_reference_rename(&ref, reference_in, git_str_cstr(&new_name), 1, - git_str_cstr(&log_message))) < 0) - goto cleanup; - - if (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC) - goto cleanup; - - /* Handle refs like origin/HEAD -> origin/master */ - target = git_reference_symbolic_target(ref); - if ((error = git_str_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0) - goto cleanup; - - if (git__prefixcmp(target, old_namespace.ptr)) - goto cleanup; - - git_str_clear(&new_name); - git_str_puts(&new_name, namespace.ptr); - if ((error = git_str_puts(&new_name, target + pfx_len)) < 0) - goto cleanup; - - error = git_reference_symbolic_set_target(&dummy, ref, git_str_cstr(&new_name), - git_str_cstr(&log_message)); - - git_reference_free(dummy); - -cleanup: - git_reference_free(reference_in); - git_reference_free(ref); - git_str_dispose(&namespace); - git_str_dispose(&old_namespace); - git_str_dispose(&new_name); - git_str_dispose(&log_message); - return error; -} - -static int rename_remote_references( - git_repository *repo, - const char *old_name, - const char *new_name) -{ - int error; - git_str buf = GIT_STR_INIT; - git_reference *ref; - git_reference_iterator *iter; - - if ((error = git_str_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0) - return error; - - error = git_reference_iterator_glob_new(&iter, repo, git_str_cstr(&buf)); - git_str_dispose(&buf); - - if (error < 0) - return error; - - while ((error = git_reference_next(&ref, iter)) == 0) { - if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) - break; - } - - git_reference_iterator_free(iter); - - return (error == GIT_ITEROVER) ? 0 : error; -} - -static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name) -{ - git_config *config; - git_str base = GIT_STR_INIT, var = GIT_STR_INIT, val = GIT_STR_INIT; - const git_refspec *spec; - size_t i; - int error = 0; - - if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0) - return error; - - if ((error = git_vector_init(problems, 1, NULL)) < 0) - return error; - - if ((error = default_fetchspec_for_name(&base, remote->name)) < 0) - return error; - - git_vector_foreach(&remote->refspecs, i, spec) { - if (spec->push) - continue; - - /* Does the dst part of the refspec follow the expected format? */ - if (strcmp(git_str_cstr(&base), spec->string)) { - char *dup; - - dup = git__strdup(spec->string); - GIT_ERROR_CHECK_ALLOC(dup); - - if ((error = git_vector_insert(problems, dup)) < 0) - break; - - continue; - } - - /* If we do want to move it to the new section */ - - git_str_clear(&val); - git_str_clear(&var); - - if (default_fetchspec_for_name(&val, new_name) < 0 || - git_str_printf(&var, "remote.%s.fetch", new_name) < 0) - { - error = -1; - break; - } - - if ((error = git_config_set_string( - config, git_str_cstr(&var), git_str_cstr(&val))) < 0) - break; - } - - git_str_dispose(&base); - git_str_dispose(&var); - git_str_dispose(&val); - - if (error < 0) { - char *str; - git_vector_foreach(problems, i, str) - git__free(str); - - git_vector_free(problems); - } - - return error; -} - -int git_remote_rename(git_strarray *out, git_repository *repo, const char *name, const char *new_name) -{ - int error; - git_vector problem_refspecs = GIT_VECTOR_INIT; - git_remote *remote = NULL; - - GIT_ASSERT_ARG(out && repo && name && new_name); - - if ((error = git_remote_lookup(&remote, repo, name)) < 0) - return error; - - if ((error = ensure_remote_name_is_valid(new_name)) < 0) - goto cleanup; - - if ((error = ensure_remote_doesnot_exist(repo, new_name)) < 0) - goto cleanup; - - if ((error = rename_remote_config_section(repo, name, new_name)) < 0) - goto cleanup; - - if ((error = update_branch_remote_config_entry(repo, name, new_name)) < 0) - goto cleanup; - - if ((error = rename_remote_references(repo, name, new_name)) < 0) - goto cleanup; - - if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0) - goto cleanup; - - out->count = problem_refspecs.length; - out->strings = (char **) problem_refspecs.contents; - -cleanup: - if (error < 0) - git_vector_free(&problem_refspecs); - - git_remote_free(remote); - return error; -} - -int git_remote_name_is_valid(int *valid, const char *remote_name) -{ - git_str buf = GIT_STR_INIT; - git_refspec refspec = {0}; - int error; - - GIT_ASSERT(valid); - - *valid = 0; - - if (!remote_name || *remote_name == '\0') - return 0; - - if ((error = git_str_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name)) < 0) - goto done; - - error = git_refspec__parse(&refspec, git_str_cstr(&buf), true); - - if (!error) - *valid = 1; - else if (error == GIT_EINVALIDSPEC) - error = 0; - -done: - git_str_dispose(&buf); - git_refspec__dispose(&refspec); - - return error; -} - -git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) -{ - git_refspec *spec; - size_t i; - - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (spec->push) - continue; - - if (git_refspec_src_matches(spec, refname)) - return spec; - } - - return NULL; -} - -git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) -{ - git_refspec *spec; - size_t i; - - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (spec->push) - continue; - - if (git_refspec_dst_matches(spec, refname)) - return spec; - } - - return NULL; -} - -int git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec) -{ - return write_add_refspec(repo, remote, refspec, true); -} - -int git_remote_add_push(git_repository *repo, const char *remote, const char *refspec) -{ - return write_add_refspec(repo, remote, refspec, false); -} - -static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push) -{ - size_t i; - git_vector refspecs; - git_refspec *spec; - char *dup; - - if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0) - return -1; - - git_vector_foreach(&remote->refspecs, i, spec) { - if (spec->push != push) - continue; - - if ((dup = git__strdup(spec->string)) == NULL) - goto on_error; - - if (git_vector_insert(&refspecs, dup) < 0) { - git__free(dup); - goto on_error; - } - } - - array->strings = (char **)refspecs.contents; - array->count = refspecs.length; - - return 0; - -on_error: - git_vector_free_deep(&refspecs); - - return -1; -} - -int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote) -{ - return copy_refspecs(array, remote, false); -} - -int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote) -{ - return copy_refspecs(array, remote, true); -} - -size_t git_remote_refspec_count(const git_remote *remote) -{ - return remote->refspecs.length; -} - -const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n) -{ - return git_vector_get(&remote->refspecs, n); -} - -int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT); - return 0; -} - -/* asserts a branch..remote format */ -static const char *name_offset(size_t *len_out, const char *name) -{ - size_t prefix_len; - const char *dot; - - prefix_len = strlen("remote."); - dot = strchr(name + prefix_len, '.'); - - GIT_ASSERT_ARG_WITH_RETVAL(dot, NULL); - - *len_out = dot - name - prefix_len; - return name + prefix_len; -} - -static int remove_branch_config_related_entries( - git_repository *repo, - const char *remote_name) -{ - int error; - git_config *config; - git_config_entry *entry; - git_config_iterator *iter; - git_str buf = GIT_STR_INIT; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) - return error; - - /* find any branches with us as upstream and remove that config */ - while ((error = git_config_next(&entry, iter)) == 0) { - const char *branch; - size_t branch_len; - - if (strcmp(remote_name, entry->value)) - continue; - - if ((branch = name_offset(&branch_len, entry->name)) == NULL) { - error = -1; - break; - } - - git_str_clear(&buf); - if ((error = git_str_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch)) < 0) - break; - - if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { - if (error != GIT_ENOTFOUND) - break; - git_error_clear(); - } - - git_str_clear(&buf); - if ((error = git_str_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch)) < 0) - break; - - if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { - if (error != GIT_ENOTFOUND) - break; - git_error_clear(); - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_str_dispose(&buf); - git_config_iterator_free(iter); - return error; -} - -static int remove_refs(git_repository *repo, const git_refspec *spec) -{ - git_reference_iterator *iter = NULL; - git_vector refs; - const char *name; - char *dup; - int error; - size_t i; - - if ((error = git_vector_init(&refs, 8, NULL)) < 0) - return error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - goto cleanup; - - while ((error = git_reference_next_name(&name, iter)) == 0) { - if (!git_refspec_dst_matches(spec, name)) - continue; - - dup = git__strdup(name); - if (!dup) { - error = -1; - goto cleanup; - } - - if ((error = git_vector_insert(&refs, dup)) < 0) - goto cleanup; - } - if (error == GIT_ITEROVER) - error = 0; - if (error < 0) - goto cleanup; - - git_vector_foreach(&refs, i, name) { - if ((error = git_reference_remove(repo, name)) < 0) - break; - } - -cleanup: - git_reference_iterator_free(iter); - git_vector_foreach(&refs, i, dup) { - git__free(dup); - } - git_vector_free(&refs); - return error; -} - -static int remove_remote_tracking(git_repository *repo, const char *remote_name) -{ - git_remote *remote; - int error; - size_t i, count; - - /* we want to use what's on the config, regardless of changes to the instance in memory */ - if ((error = git_remote_lookup(&remote, repo, remote_name)) < 0) - return error; - - count = git_remote_refspec_count(remote); - for (i = 0; i < count; i++) { - const git_refspec *refspec = git_remote_get_refspec(remote, i); - - /* shouldn't ever actually happen */ - if (refspec == NULL) - continue; - - if ((error = remove_refs(repo, refspec)) < 0) - break; - } - - git_remote_free(remote); - return error; -} - -int git_remote_delete(git_repository *repo, const char *name) -{ - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = remove_branch_config_related_entries(repo, name)) < 0 || - (error = remove_remote_tracking(repo, name)) < 0 || - (error = rename_remote_config_section(repo, name, NULL)) < 0) - return error; - - return 0; -} - -int git_remote_default_branch(git_buf *out, git_remote *remote) -{ - GIT_BUF_WRAP_PRIVATE(out, git_remote__default_branch, remote); -} - -int git_remote__default_branch(git_str *out, git_remote *remote) -{ - const git_remote_head **heads; - const git_remote_head *guess = NULL; - const git_oid *head_id; - size_t heads_len, i; - git_str local_default = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - - if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) - goto done; - - if (heads_len == 0 || strcmp(heads[0]->name, GIT_HEAD_FILE)) { - error = GIT_ENOTFOUND; - goto done; - } - - /* the first one must be HEAD so if that has the symref info, we're done */ - if (heads[0]->symref_target) { - error = git_str_puts(out, heads[0]->symref_target); - goto done; - } - - /* - * If there's no symref information, we have to look over them - * and guess. We return the first match unless the default - * branch is a candidate. Then we return the default branch. - */ - - if ((error = git_repository_initialbranch(&local_default, remote->repo)) < 0) - goto done; - - head_id = &heads[0]->oid; - - for (i = 1; i < heads_len; i++) { - if (git_oid_cmp(head_id, &heads[i]->oid)) - continue; - - if (git__prefixcmp(heads[i]->name, GIT_REFS_HEADS_DIR)) - continue; - - if (!guess) { - guess = heads[i]; - continue; - } - - if (!git__strcmp(local_default.ptr, heads[i]->name)) { - guess = heads[i]; - break; - } - } - - if (!guess) { - error = GIT_ENOTFOUND; - goto done; - } - - error = git_str_puts(out, guess->name); - -done: - git_str_dispose(&local_default); - return error; -} - -GIT_INLINE(int) connect_opts_from_push_opts( - git_remote_connect_options *out, - git_remote *remote, - const git_push_options *push_opts) -{ - git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; - copy_opts(&tmp, push_opts); - return git_remote_connect_options_normalize(out, remote->repo, &tmp); -} - -int git_remote_upload( - git_remote *remote, - const git_strarray *refspecs, - const git_push_options *opts) -{ - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - git_push *push; - git_refspec *spec; - size_t i; - int error; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if ((error = connect_opts_from_push_opts(&connect_opts, remote, opts)) < 0) - goto cleanup; - - if ((error = connect_or_reset_options(remote, GIT_DIRECTION_PUSH, &connect_opts)) < 0) - goto cleanup; - - free_refspecs(&remote->active_refspecs); - if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) - goto cleanup; - - if (remote->push) { - git_push_free(remote->push); - remote->push = NULL; - } - - if ((error = git_push_new(&remote->push, remote, opts)) < 0) - goto cleanup; - - push = remote->push; - - if (refspecs && refspecs->count > 0) { - for (i = 0; i < refspecs->count; i++) { - if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0) - goto cleanup; - } - } else { - git_vector_foreach(&remote->refspecs, i, spec) { - if (!spec->push) - continue; - if ((error = git_push_add_refspec(push, spec->string)) < 0) - goto cleanup; - } - } - - if ((error = git_push_finish(push)) < 0) - goto cleanup; - - if (connect_opts.callbacks.push_update_reference && - (error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload)) < 0) - goto cleanup; - -cleanup: - git_remote_connect_options_dispose(&connect_opts); - return error; -} - -int git_remote_push( - git_remote *remote, - const git_strarray *refspecs, - const git_push_options *opts) -{ - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if (connect_opts_from_push_opts(&connect_opts, remote, opts) < 0) - return -1; - - if ((error = git_remote_upload(remote, refspecs, opts)) < 0) - goto done; - - error = git_remote_update_tips(remote, &connect_opts.callbacks, 0, 0, NULL); - -done: - git_remote_disconnect(remote); - git_remote_connect_options_dispose(&connect_opts); - return error; -} - -#define PREFIX "url" -#define SUFFIX_FETCH "insteadof" -#define SUFFIX_PUSH "pushinsteadof" - -static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty) -{ - size_t match_length, prefix_length, suffix_length; - char *replacement = NULL; - const char *regexp; - - git_str result = GIT_STR_INIT; - git_config_entry *entry; - git_config_iterator *iter; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(config); - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); - - /* Add 1 to prefix/suffix length due to the additional escaped dot */ - prefix_length = strlen(PREFIX) + 1; - if (direction == GIT_DIRECTION_FETCH) { - regexp = PREFIX "\\..*\\." SUFFIX_FETCH; - suffix_length = strlen(SUFFIX_FETCH) + 1; - } else { - regexp = PREFIX "\\..*\\." SUFFIX_PUSH; - suffix_length = strlen(SUFFIX_PUSH) + 1; - } - - if (git_config_iterator_glob_new(&iter, config, regexp) < 0) - return -1; - - match_length = 0; - while (git_config_next(&entry, iter) == 0) { - size_t n, replacement_length; - - /* Check if entry value is a prefix of URL */ - if (git__prefixcmp(url, entry->value)) - continue; - - /* Check if entry value is longer than previous - * prefixes */ - if ((n = strlen(entry->value)) <= match_length) - continue; - - git__free(replacement); - match_length = n; - - /* Cut off prefix and suffix of the value */ - replacement_length = - strlen(entry->name) - (prefix_length + suffix_length); - replacement = git__strndup(entry->name + prefix_length, - replacement_length); - } - - git_config_iterator_free(iter); - - if (match_length == 0 && use_default_if_empty) { - *out = git__strdup(url); - return *out ? 0 : -1; - } else if (match_length == 0) { - *out = NULL; - return 0; - } - - git_str_printf(&result, "%s%s", replacement, url + match_length); - - git__free(replacement); - - *out = git_str_detach(&result); - return 0; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD - -int git_remote_is_valid_name(const char *remote_name) -{ - int valid = 0; - - git_remote_name_is_valid(&valid, remote_name); - return valid; -} - -#endif diff --git a/src/remote.h b/src/remote.h deleted file mode 100644 index ea9c7d17f..000000000 --- a/src/remote.h +++ /dev/null @@ -1,60 +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_remote_h__ -#define INCLUDE_remote_h__ - -#include "common.h" - -#include "git2/remote.h" -#include "git2/transport.h" -#include "git2/sys/transport.h" - -#include "refspec.h" -#include "vector.h" -#include "net.h" - -#define GIT_REMOTE_ORIGIN "origin" - -struct git_remote { - char *name; - char *url; - char *pushurl; - git_vector refs; - git_vector refspecs; - git_vector active_refspecs; - git_vector passive_refspecs; - git_vector local_heads; - git_transport *transport; - git_repository *repo; - git_push *push; - git_indexer_progress stats; - unsigned int need_pack; - git_remote_autotag_option_t download_tags; - int prune_refs; - int passed_refspecs; -}; - -int git_remote__urlfordirection(git_str *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks); -int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url); - -git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname); -git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname); - -int git_remote__default_branch(git_str *out, git_remote *remote); - -int git_remote_connect_options_dup( - git_remote_connect_options *dst, - const git_remote_connect_options *src); -int git_remote_connect_options_normalize( - git_remote_connect_options *dst, - git_repository *repo, - const git_remote_connect_options *src); -void git_remote_connect_options_dispose(git_remote_connect_options *opts); - -int git_remote_capabilities(unsigned int *out, git_remote *remote); - -#endif diff --git a/src/repo_template.h b/src/repo_template.h deleted file mode 100644 index 099279aa7..000000000 --- a/src/repo_template.h +++ /dev/null @@ -1,58 +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_repo_template_h__ -#define INCLUDE_repo_template_h__ - -#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" -#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" - -#define GIT_HOOKS_DIR "hooks/" -#define GIT_HOOKS_DIR_MODE 0777 - -#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" -#define GIT_HOOKS_README_MODE 0777 -#define GIT_HOOKS_README_CONTENT \ -"#!/bin/sh\n"\ -"#\n"\ -"# Place appropriately named executable hook scripts into this directory\n"\ -"# to intercept various actions that git takes. See `git help hooks` for\n"\ -"# more information.\n" - -#define GIT_INFO_DIR "info/" -#define GIT_INFO_DIR_MODE 0777 - -#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" -#define GIT_INFO_EXCLUDE_MODE 0666 -#define GIT_INFO_EXCLUDE_CONTENT \ -"# File patterns to ignore; see `git help ignore` for more information.\n"\ -"# Lines that start with '#' are comments.\n" - -#define GIT_DESC_FILE "description" -#define GIT_DESC_MODE 0666 -#define GIT_DESC_CONTENT \ -"Unnamed repository; edit this file 'description' to name the repository.\n" - -typedef struct { - const char *path; - mode_t mode; - const char *content; -} repo_template_item; - -static repo_template_item repo_template[] = { - { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */ - { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */ - { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */ - { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */ - { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */ - { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */ - { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, - { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, - { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, - { NULL, 0, NULL } -}; - -#endif diff --git a/src/repository.c b/src/repository.c deleted file mode 100644 index 6119c1c70..000000000 --- a/src/repository.c +++ /dev/null @@ -1,3334 +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 "repository.h" - -#include - -#include "git2/object.h" -#include "git2/sys/repository.h" - -#include "buf.h" -#include "common.h" -#include "commit.h" -#include "tag.h" -#include "blob.h" -#include "futils.h" -#include "sysdir.h" -#include "filebuf.h" -#include "index.h" -#include "config.h" -#include "refs.h" -#include "filter.h" -#include "odb.h" -#include "refdb.h" -#include "remote.h" -#include "merge.h" -#include "diff_driver.h" -#include "annotated_commit.h" -#include "submodule.h" -#include "worktree.h" -#include "path.h" -#include "strmap.h" - -#ifdef GIT_WIN32 -# include "win32/w32_util.h" -#endif - -bool git_repository__validate_ownership = true; -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, 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(int *version, git_config *config); -static int check_extensions(git_config *config, int version); -static int load_global_config(git_config **config); - -#define GIT_COMMONDIR_FILE "commondir" -#define GIT_GITDIR_FILE "gitdir" - -#define GIT_FILE_CONTENT_PREFIX "gitdir:" - -#define GIT_BRANCH_DEFAULT "master" - -#define GIT_REPO_VERSION 0 -#define GIT_REPO_MAX_VERSION 1 - -git_str git_repository__reserved_names_win32[] = { - { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, - { GIT_DIR_SHORTNAME, 0, CONST_STRLEN(GIT_DIR_SHORTNAME) } -}; -size_t git_repository__reserved_names_win32_len = 2; - -git_str git_repository__reserved_names_posix[] = { - { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, -}; -size_t git_repository__reserved_names_posix_len = 1; - -static void set_odb(git_repository *repo, git_odb *odb) -{ - if (odb) { - GIT_REFCOUNT_OWN(odb, repo); - GIT_REFCOUNT_INC(odb); - } - - if ((odb = git_atomic_swap(repo->_odb, odb)) != NULL) { - GIT_REFCOUNT_OWN(odb, NULL); - git_odb_free(odb); - } -} - -static void set_refdb(git_repository *repo, git_refdb *refdb) -{ - if (refdb) { - GIT_REFCOUNT_OWN(refdb, repo); - GIT_REFCOUNT_INC(refdb); - } - - if ((refdb = git_atomic_swap(repo->_refdb, refdb)) != NULL) { - GIT_REFCOUNT_OWN(refdb, NULL); - git_refdb_free(refdb); - } -} - -static void set_config(git_repository *repo, git_config *config) -{ - if (config) { - GIT_REFCOUNT_OWN(config, repo); - GIT_REFCOUNT_INC(config); - } - - if ((config = git_atomic_swap(repo->_config, config)) != NULL) { - GIT_REFCOUNT_OWN(config, NULL); - git_config_free(config); - } - - git_repository__configmap_lookup_cache_clear(repo); -} - -static void set_index(git_repository *repo, git_index *index) -{ - if (index) { - GIT_REFCOUNT_OWN(index, repo); - GIT_REFCOUNT_INC(index); - } - - if ((index = git_atomic_swap(repo->_index, index)) != NULL) { - GIT_REFCOUNT_OWN(index, NULL); - git_index_free(index); - } -} - -int git_repository__cleanup(git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - git_repository_submodule_cache_clear(repo); - git_cache_clear(&repo->objects); - git_attr_cache_flush(repo); - - set_config(repo, NULL); - set_index(repo, NULL); - set_odb(repo, NULL); - set_refdb(repo, NULL); - - return 0; -} - -void git_repository_free(git_repository *repo) -{ - size_t i; - - if (repo == NULL) - return; - - git_repository__cleanup(repo); - - git_cache_dispose(&repo->objects); - - git_diff_driver_registry_free(repo->diff_drivers); - repo->diff_drivers = NULL; - - for (i = 0; i < repo->reserved_names.size; i++) - git_str_dispose(git_array_get(repo->reserved_names, i)); - git_array_clear(repo->reserved_names); - - git__free(repo->gitlink); - git__free(repo->gitdir); - git__free(repo->commondir); - git__free(repo->workdir); - git__free(repo->namespace); - git__free(repo->ident_name); - git__free(repo->ident_email); - - git__memzero(repo, sizeof(*repo)); - git__free(repo); -} - -/* Check if we have a separate commondir (e.g. we have a worktree) */ -static int lookup_commondir(bool *separate, git_str *commondir, git_str *repository_path) -{ - git_str common_link = GIT_STR_INIT; - int error; - - /* - * If there's no commondir file, the repository path is the - * common path, but it needs a trailing slash. - */ - if (!git_fs_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { - if ((error = git_str_set(commondir, repository_path->ptr, repository_path->size)) == 0) - error = git_fs_path_to_dir(commondir); - - *separate = false; - goto done; - } - - *separate = true; - - if ((error = git_str_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 || - (error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0) - goto done; - - git_str_rtrim(&common_link); - if (git_fs_path_is_relative(common_link.ptr)) { - if ((error = git_str_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0) - goto done; - } else { - git_str_swap(commondir, &common_link); - } - - git_str_dispose(&common_link); - - /* Make sure the commondir path always has a trailing slash */ - error = git_fs_path_prettify_dir(commondir, commondir->ptr, NULL); - -done: - return error; -} - -GIT_INLINE(int) validate_repo_path(git_str *path) -{ - /* - * The longest static path in a repository (or commondir) is the - * packed refs file. (Loose refs may be longer since they - * include the reference name, but will be validated when the - * path is constructed.) - */ - static size_t suffix_len = - CONST_STRLEN("objects/pack/pack-.pack.lock") + - GIT_OID_HEXSZ; - - return git_fs_path_validate_str_length_with_suffix( - path, suffix_len); -} - -/* - * Git repository open methods - * - * Open a repository object from its path - */ -static int is_valid_repository_path(bool *out, git_str *repository_path, git_str *common_path) -{ - bool separate_commondir = false; - int error; - - *out = false; - - if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0) - return error; - - /* Ensure HEAD file exists */ - if (git_fs_path_contains_file(repository_path, GIT_HEAD_FILE) == false) - return 0; - - /* Check files in common dir */ - if (git_fs_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) - return 0; - if (git_fs_path_contains_dir(common_path, GIT_REFS_DIR) == false) - return 0; - - /* Ensure the repo (and commondir) are valid paths */ - if ((error = validate_repo_path(common_path)) < 0 || - (separate_commondir && - (error = validate_repo_path(repository_path)) < 0)) - return error; - - *out = true; - return 0; -} - -static git_repository *repository_alloc(void) -{ - git_repository *repo = git__calloc(1, sizeof(git_repository)); - - if (repo == NULL || - git_cache_init(&repo->objects) < 0) - goto on_error; - - git_array_init_to_size(repo->reserved_names, 4); - if (!repo->reserved_names.ptr) - goto on_error; - - /* 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_dispose(&repo->objects); - - git__free(repo); - return NULL; -} - -int git_repository_new(git_repository **out) -{ - git_repository *repo; - - *out = repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - repo->is_bare = 1; - repo->is_worktree = 0; - - return 0; -} - -static int load_config_data(git_repository *repo, const git_config *config) -{ - int is_bare; - - int err = git_config_get_bool(&is_bare, config, "core.bare"); - if (err < 0 && err != GIT_ENOTFOUND) - return err; - - /* Try to figure out if it's bare, default to non-bare if it's not set */ - if (err != GIT_ENOTFOUND) - repo->is_bare = is_bare && !repo->is_worktree; - else - repo->is_bare = 0; - - return 0; -} - -static int load_workdir(git_repository *repo, git_config *config, git_str *parent_path) -{ - int error; - git_config_entry *ce; - git_str worktree = GIT_STR_INIT; - git_str path = GIT_STR_INIT; - - if (repo->is_bare) - return 0; - - if ((error = git_config__lookup_entry( - &ce, config, "core.worktree", false)) < 0) - return error; - - if (repo->is_worktree) { - char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); - if (!gitlink) { - error = -1; - goto cleanup; - } - - git_str_attach(&worktree, gitlink, 0); - - if ((git_fs_path_dirname_r(&worktree, worktree.ptr)) < 0 || - git_fs_path_to_dir(&worktree) < 0) { - error = -1; - goto cleanup; - } - - repo->workdir = git_str_detach(&worktree); - } - else if (ce && ce->value) { - if ((error = git_fs_path_prettify_dir( - &worktree, ce->value, repo->gitdir)) < 0) - goto cleanup; - - repo->workdir = git_str_detach(&worktree); - } - else if (parent_path && git_fs_path_isdir(parent_path->ptr)) - repo->workdir = git_str_detach(parent_path); - else { - if (git_fs_path_dirname_r(&worktree, repo->gitdir) < 0 || - git_fs_path_to_dir(&worktree) < 0) { - error = -1; - goto cleanup; - } - - repo->workdir = git_str_detach(&worktree); - } - - GIT_ERROR_CHECK_ALLOC(repo->workdir); -cleanup: - git_str_dispose(&path); - git_config_entry_free(ce); - return error; -} - -/* - * This function returns furthest offset into path where a ceiling dir - * is found, so we can stop processing the path at that point. - * - * Note: converting this to use git_strs instead of GIT_PATH_MAX buffers on - * the stack could remove directories name limits, but at the cost of doing - * repeated malloc/frees inside the loop below, so let's not do it now. - */ -static size_t find_ceiling_dir_offset( - const char *path, - const char *ceiling_directories) -{ - char buf[GIT_PATH_MAX + 1]; - char buf2[GIT_PATH_MAX + 1]; - const char *ceil, *sep; - size_t len, max_len = 0, min_len; - - GIT_ASSERT_ARG(path); - - min_len = (size_t)(git_fs_path_root(path) + 1); - - if (ceiling_directories == NULL || min_len == 0) - return min_len; - - for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { - for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); - len = sep - ceil; - - if (len == 0 || len >= sizeof(buf) || git_fs_path_root(ceil) == -1) - continue; - - strncpy(buf, ceil, len); - buf[len] = '\0'; - - if (p_realpath(buf, buf2) == NULL) - continue; - - len = strlen(buf2); - if (len > 0 && buf2[len-1] == '/') - buf[--len] = '\0'; - - if (!strncmp(path, buf2, len) && - (path[len] == '/' || !path[len]) && - len > max_len) - { - max_len = len; - } - } - - return (max_len <= min_len ? min_len : max_len); -} - -/* - * Read the contents of `file_path` and set `path_out` to the repo dir that - * it points to. Before calling, set `path_out` to the base directory that - * should be used if the contents of `file_path` are a relative path. - */ -static int read_gitfile(git_str *path_out, const char *file_path) -{ - int error = 0; - git_str file = GIT_STR_INIT; - size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(file_path); - - if (git_futils_readbuffer(&file, file_path) < 0) - return -1; - - git_str_rtrim(&file); - /* apparently on Windows, some people use backslashes in paths */ - git_fs_path_mkposix(file.ptr); - - if (git_str_len(&file) <= prefix_len || - memcmp(git_str_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) - { - git_error_set(GIT_ERROR_REPOSITORY, - "the `.git` file at '%s' is malformed", file_path); - error = -1; - } - else if ((error = git_fs_path_dirname_r(path_out, file_path)) >= 0) { - const char *gitlink = git_str_cstr(&file) + prefix_len; - while (*gitlink && git__isspace(*gitlink)) gitlink++; - - error = git_fs_path_prettify_dir( - path_out, gitlink, git_str_cstr(path_out)); - } - - git_str_dispose(&file); - return error; -} - -typedef struct { - const char *repo_path; - git_str tmp; - bool is_safe; -} validate_ownership_data; - -static int validate_ownership_cb(const git_config_entry *entry, void *payload) -{ - validate_ownership_data *data = payload; - - if (strcmp(entry->value, "") == 0) - data->is_safe = false; - - if (git_fs_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 && - strcmp(data->tmp.ptr, data->repo_path) == 0) - data->is_safe = true; - - return 0; -} - -static int validate_ownership(const char *repo_path) -{ - git_config *config = NULL; - validate_ownership_data data = { repo_path, GIT_STR_INIT, false }; - bool is_safe; - int error; - - if ((error = git_fs_path_owner_is_current_user(&is_safe, repo_path)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - - goto done; - } - - if (is_safe) { - error = 0; - goto done; - } - - if (load_global_config(&config) == 0) { - error = git_config_get_multivar_foreach(config, "safe.directory", NULL, validate_ownership_cb, &data); - - if (!error && data.is_safe) - goto done; - } - - git_error_set(GIT_ERROR_CONFIG, - "repository path '%s' is not owned by current user", - repo_path); - error = GIT_EOWNER; - -done: - git_config_free(config); - git_str_dispose(&data.tmp); - return error; -} - -static int find_repo( - git_str *gitdir_path, - git_str *workdir_path, - git_str *gitlink_path, - git_str *commondir_path, - const char *start_path, - uint32_t flags, - const char *ceiling_dirs) -{ - git_str path = GIT_STR_INIT; - git_str repo_link = GIT_STR_INIT; - git_str common_link = GIT_STR_INIT; - struct stat st; - dev_t initial_device = 0; - int min_iterations; - bool in_dot_git, is_valid; - size_t ceiling_offset = 0; - int error; - - git_str_clear(gitdir_path); - - error = git_fs_path_prettify(&path, start_path, NULL); - if (error < 0) - return error; - - /* in_dot_git toggles each loop: - * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a - * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we - * assume we started with /a/b/c.git and don't append .git the first - * time through. - * min_iterations indicates the number of iterations left before going - * further counts as a search. */ - if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { - in_dot_git = true; - min_iterations = 1; - } else { - in_dot_git = false; - min_iterations = 2; - } - - for (;;) { - if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) { - if (!in_dot_git) { - if ((error = git_str_joinpath(&path, path.ptr, DOT_GIT)) < 0) - goto out; - } - in_dot_git = !in_dot_git; - } - - if (p_stat(path.ptr, &st) == 0) { - /* check that we have not crossed device boundaries */ - if (initial_device == 0) - initial_device = st.st_dev; - else if (st.st_dev != initial_device && - !(flags & GIT_REPOSITORY_OPEN_CROSS_FS)) - break; - - if (S_ISDIR(st.st_mode)) { - if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) - goto out; - - if (is_valid) { - if ((error = git_fs_path_to_dir(&path)) < 0 || - (error = git_str_set(gitdir_path, path.ptr, path.size)) < 0) - goto out; - - if (gitlink_path) - if ((error = git_str_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); - - break; - } - } 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_str_swap(gitdir_path, &repo_link); - - if (gitlink_path) - if ((error = git_str_put(gitlink_path, path.ptr, path.size)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); - } - break; - } - } - - /* 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 ((error = git_fs_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. */ - if (min_iterations && (--min_iterations == 0)) - 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))) - break; - } - - if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { - if (!git_str_len(gitdir_path)) - git_str_clear(workdir_path); - else if ((error = git_fs_path_dirname_r(workdir_path, path.ptr)) < 0 || - (error = git_fs_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_str_len(gitdir_path)) { - git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); - error = GIT_ENOTFOUND; - goto out; - } - -out: - git_str_dispose(&path); - git_str_dispose(&repo_link); - git_str_dispose(&common_link); - return error; -} - -int git_repository_open_bare( - git_repository **repo_ptr, - const char *bare_path) -{ - git_str path = GIT_STR_INIT, common_path = GIT_STR_INIT; - git_repository *repo = NULL; - bool is_valid; - int error; - - if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 || - (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) - return error; - - if (!is_valid) { - git_str_dispose(&path); - git_str_dispose(&common_path); - git_error_set(GIT_ERROR_REPOSITORY, "path is not a repository: %s", bare_path); - return GIT_ENOTFOUND; - } - - repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - repo->gitdir = git_str_detach(&path); - GIT_ERROR_CHECK_ALLOC(repo->gitdir); - repo->commondir = git_str_detach(&common_path); - GIT_ERROR_CHECK_ALLOC(repo->commondir); - - /* of course we're bare! */ - repo->is_bare = 1; - repo->is_worktree = 0; - repo->workdir = NULL; - - *repo_ptr = repo; - return 0; -} - -static int _git_repository_open_ext_from_env( - git_repository **out, - const char *start_path) -{ - git_repository *repo = NULL; - git_index *index = NULL; - git_odb *odb = NULL; - git_str dir_buf = GIT_STR_INIT; - git_str ceiling_dirs_buf = GIT_STR_INIT; - git_str across_fs_buf = GIT_STR_INIT; - git_str index_file_buf = GIT_STR_INIT; - git_str namespace_buf = GIT_STR_INIT; - git_str object_dir_buf = GIT_STR_INIT; - git_str alts_buf = GIT_STR_INIT; - git_str work_tree_buf = GIT_STR_INIT; - git_str common_dir_buf = GIT_STR_INIT; - const char *ceiling_dirs = NULL; - unsigned flags = 0; - int error; - - if (!start_path) { - error = git__getenv(&dir_buf, "GIT_DIR"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - start_path = "."; - } else if (error < 0) - goto error; - else { - start_path = git_str_cstr(&dir_buf); - flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; - flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; - } - } - - error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else - ceiling_dirs = git_str_cstr(&ceiling_dirs_buf); - - error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - int across_fs = 0; - error = git_config_parse_bool(&across_fs, git_str_cstr(&across_fs_buf)); - if (error < 0) - goto error; - if (across_fs) - flags |= GIT_REPOSITORY_OPEN_CROSS_FS; - } - - error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_index_open(&index, git_str_cstr(&index_file_buf)); - if (error < 0) - goto error; - } - - error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - - error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_odb_open(&odb, git_str_cstr(&object_dir_buf)); - if (error < 0) - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_WORK_TREE unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_COMMON_DIR unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); - if (error < 0) - goto error; - - if (odb) - git_repository_set_odb(repo, odb); - - error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } else if (error < 0) - goto error; - else { - const char *end; - char *alt, *sep; - if (!odb) { - error = git_repository_odb(&odb, repo); - if (error < 0) - goto error; - } - - end = git_str_cstr(&alts_buf) + git_str_len(&alts_buf); - for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { - for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) - ; - if (*sep) - *sep = '\0'; - error = git_odb_add_disk_alternate(odb, alt); - if (error < 0) - goto error; - } - } - - if (git_str_len(&namespace_buf)) { - error = git_repository_set_namespace(repo, git_str_cstr(&namespace_buf)); - if (error < 0) - goto error; - } - - git_repository_set_index(repo, index); - - if (out) { - *out = repo; - goto success; - } -error: - git_repository_free(repo); -success: - git_odb_free(odb); - git_index_free(index); - git_str_dispose(&common_dir_buf); - git_str_dispose(&work_tree_buf); - git_str_dispose(&alts_buf); - git_str_dispose(&object_dir_buf); - git_str_dispose(&namespace_buf); - git_str_dispose(&index_file_buf); - git_str_dispose(&across_fs_buf); - git_str_dispose(&ceiling_dirs_buf); - git_str_dispose(&dir_buf); - return error; -} - -static int repo_is_worktree(unsigned *out, const git_repository *repo) -{ - git_str gitdir_link = GIT_STR_INIT; - int error; - - /* Worktrees cannot have the same commondir and gitdir */ - if (repo->commondir && repo->gitdir - && !strcmp(repo->commondir, repo->gitdir)) { - *out = 0; - return 0; - } - - if ((error = git_str_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0) - return -1; - - /* A 'gitdir' file inside a git directory is currently - * only used when the repository is a working tree. */ - *out = !!git_fs_path_exists(gitdir_link.ptr); - - git_str_dispose(&gitdir_link); - return error; -} - -int git_repository_open_ext( - git_repository **repo_ptr, - const char *start_path, - unsigned int flags, - const char *ceiling_dirs) -{ - int error; - unsigned is_worktree; - git_str gitdir = GIT_STR_INIT, workdir = GIT_STR_INIT, - gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT; - git_repository *repo = NULL; - git_config *config = NULL; - const char *validation_path; - int version = 0; - - if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) - return _git_repository_open_ext_from_env(repo_ptr, start_path); - - if (repo_ptr) - *repo_ptr = NULL; - - error = find_repo( - &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); - - if (error < 0 || !repo_ptr) - goto cleanup; - - repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - repo->gitdir = git_str_detach(&gitdir); - GIT_ERROR_CHECK_ALLOC(repo->gitdir); - - if (gitlink.size) { - repo->gitlink = git_str_detach(&gitlink); - GIT_ERROR_CHECK_ALLOC(repo->gitlink); - } - if (commondir.size) { - repo->commondir = git_str_detach(&commondir); - GIT_ERROR_CHECK_ALLOC(repo->commondir); - } - - if ((error = repo_is_worktree(&is_worktree, repo)) < 0) - goto cleanup; - repo->is_worktree = is_worktree; - - /* - * We'd like to have the config, but git doesn't particularly - * care if it's not there, so we need to deal with that. - */ - - error = git_repository_config_snapshot(&config, repo); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (config && (error = check_repositoryformatversion(&version, config)) < 0) - goto cleanup; - - if ((error = check_extensions(config, version)) < 0) - goto cleanup; - - if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) { - repo->is_bare = 1; - } else { - if (config && - ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &workdir)) < 0)) - goto cleanup; - } - - /* - * Ensure that the git directory is owned by the current user. - */ - validation_path = repo->is_bare ? repo->gitdir : repo->workdir; - - if (git_repository__validate_ownership && - (error = validate_ownership(validation_path)) < 0) - goto cleanup; - -cleanup: - git_str_dispose(&gitdir); - git_str_dispose(&workdir); - git_str_dispose(&gitlink); - git_str_dispose(&commondir); - git_config_free(config); - - if (error < 0) - git_repository_free(repo); - else if (repo_ptr) - *repo_ptr = repo; - - return error; -} - -int git_repository_open(git_repository **repo_out, const char *path) -{ - return git_repository_open_ext( - repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); -} - -int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *wt) -{ - git_str path = GIT_STR_INIT; - git_repository *repo = NULL; - size_t len; - int err; - - GIT_ASSERT_ARG(repo_out); - GIT_ASSERT_ARG(wt); - - *repo_out = NULL; - len = strlen(wt->gitlink_path); - - if (len <= 4 || strcasecmp(wt->gitlink_path + len - 4, ".git")) { - err = -1; - goto out; - } - - if ((err = git_str_set(&path, wt->gitlink_path, len - 4)) < 0) - goto out; - - if ((err = git_repository_open(&repo, path.ptr)) < 0) - goto out; - - *repo_out = repo; - -out: - git_str_dispose(&path); - - return err; -} - -int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb) -{ - git_repository *repo; - - repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - git_repository_set_odb(repo, odb); - *repo_out = repo; - - return 0; -} - -int git_repository_discover( - git_buf *out, - const char *start_path, - int across_fs, - const char *ceiling_dirs) -{ - uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; - - GIT_ASSERT_ARG(start_path); - - GIT_BUF_WRAP_PRIVATE(out, find_repo, NULL, NULL, NULL, start_path, flags, ceiling_dirs); -} - -static int load_config( - git_config **out, - git_repository *repo, - const char *global_config_path, - const char *xdg_config_path, - const char *system_config_path, - const char *programdata_path) -{ - int error; - git_str config_path = GIT_STR_INIT; - git_config *cfg = NULL; - - GIT_ASSERT_ARG(out); - - if ((error = git_config_new(&cfg)) < 0) - return error; - - if (repo) { - if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) - error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0); - - if (error && error != GIT_ENOTFOUND) - goto on_error; - - git_str_dispose(&config_path); - } - - if (global_config_path != NULL && - (error = git_config_add_file_ondisk( - cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - if (xdg_config_path != NULL && - (error = git_config_add_file_ondisk( - cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - if (system_config_path != NULL && - (error = git_config_add_file_ondisk( - cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - if (programdata_path != NULL && - (error = git_config_add_file_ondisk( - cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - git_error_clear(); /* clear any lingering ENOTFOUND errors */ - - *out = cfg; - return 0; - -on_error: - git_str_dispose(&config_path); - git_config_free(cfg); - *out = NULL; - return error; -} - -static const char *path_unless_empty(git_str *buf) -{ - return git_str_len(buf) > 0 ? git_str_cstr(buf) : NULL; -} - -int git_repository_config__weakptr(git_config **out, git_repository *repo) -{ - int error = 0; - - if (repo->_config == NULL) { - git_str global_buf = GIT_STR_INIT; - git_str xdg_buf = GIT_STR_INIT; - git_str system_buf = GIT_STR_INIT; - git_str programdata_buf = GIT_STR_INIT; - git_config *config; - - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); - - /* If there is no global file, open a backend for it anyway */ - if (git_str_len(&global_buf) == 0) - git_config__global_location(&global_buf); - - error = load_config( - &config, repo, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); - if (!error) { - GIT_REFCOUNT_OWN(config, repo); - - if (git_atomic_compare_and_swap(&repo->_config, NULL, config) != NULL) { - GIT_REFCOUNT_OWN(config, NULL); - git_config_free(config); - } - } - - git_str_dispose(&global_buf); - git_str_dispose(&xdg_buf); - git_str_dispose(&system_buf); - git_str_dispose(&programdata_buf); - } - - *out = repo->_config; - return error; -} - -int git_repository_config(git_config **out, git_repository *repo) -{ - if (git_repository_config__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_config_snapshot(git_config **out, git_repository *repo) -{ - int error; - git_config *weak; - - if ((error = git_repository_config__weakptr(&weak, repo)) < 0) - return error; - - return git_config_snapshot(out, weak); -} - -int git_repository_set_config(git_repository *repo, git_config *config) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(config); - - set_config(repo, config); - return 0; -} - -int git_repository_odb__weakptr(git_odb **out, git_repository *repo) -{ - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(out); - - *out = git_atomic_load(repo->_odb); - if (*out == NULL) { - git_str odb_path = GIT_STR_INIT; - git_odb *odb; - - if ((error = git_repository__item_path(&odb_path, repo, - GIT_REPOSITORY_ITEM_OBJECTS)) < 0 || - (error = git_odb_new(&odb)) < 0) - return error; - - GIT_REFCOUNT_OWN(odb, repo); - - if ((error = git_odb__set_caps(odb, GIT_ODB_CAP_FROM_OWNER)) < 0 || - (error = git_odb__add_default_backends(odb, odb_path.ptr, 0, 0)) < 0) { - git_odb_free(odb); - return error; - } - - if (git_atomic_compare_and_swap(&repo->_odb, NULL, odb) != NULL) { - GIT_REFCOUNT_OWN(odb, NULL); - git_odb_free(odb); - } - - git_str_dispose(&odb_path); - *out = git_atomic_load(repo->_odb); - } - - return error; -} - -int git_repository_odb(git_odb **out, git_repository *repo) -{ - if (git_repository_odb__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_set_odb(git_repository *repo, git_odb *odb) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(odb); - - set_odb(repo, odb); - return 0; -} - -int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) -{ - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if (repo->_refdb == NULL) { - git_refdb *refdb; - - error = git_refdb_open(&refdb, repo); - if (!error) { - GIT_REFCOUNT_OWN(refdb, repo); - - if (git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb) != NULL) { - GIT_REFCOUNT_OWN(refdb, NULL); - git_refdb_free(refdb); - } - } - } - - *out = repo->_refdb; - return error; -} - -int git_repository_refdb(git_refdb **out, git_repository *repo) -{ - if (git_repository_refdb__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refdb); - - set_refdb(repo, refdb); - return 0; -} - -int git_repository_index__weakptr(git_index **out, git_repository *repo) -{ - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if (repo->_index == NULL) { - git_str index_path = GIT_STR_INIT; - git_index *index; - - if ((error = git_str_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) - return error; - - error = git_index_open(&index, index_path.ptr); - if (!error) { - GIT_REFCOUNT_OWN(index, repo); - - if (git_atomic_compare_and_swap(&repo->_index, NULL, index) != NULL) { - GIT_REFCOUNT_OWN(index, NULL); - git_index_free(index); - } - - error = git_index_set_caps(repo->_index, - GIT_INDEX_CAPABILITY_FROM_OWNER); - } - - git_str_dispose(&index_path); - } - - *out = repo->_index; - return error; -} - -int git_repository_index(git_index **out, git_repository *repo) -{ - if (git_repository_index__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_set_index(git_repository *repo, git_index *index) -{ - GIT_ASSERT_ARG(repo); - set_index(repo, index); - return 0; -} - -int git_repository_set_namespace(git_repository *repo, const char *namespace) -{ - git__free(repo->namespace); - - if (namespace == NULL) { - repo->namespace = NULL; - return 0; - } - - return (repo->namespace = git__strdup(namespace)) ? 0 : -1; -} - -const char *git_repository_get_namespace(git_repository *repo) -{ - return repo->namespace; -} - -#ifdef GIT_WIN32 -static int reserved_names_add8dot3(git_repository *repo, const char *path) -{ - char *name = git_win32_path_8dot3_name(path); - const char *def = GIT_DIR_SHORTNAME; - const char *def_dot_git = DOT_GIT; - size_t name_len, def_len = CONST_STRLEN(GIT_DIR_SHORTNAME); - size_t def_dot_git_len = CONST_STRLEN(DOT_GIT); - git_str *buf; - - if (!name) - return 0; - - name_len = strlen(name); - - if ((name_len == def_len && memcmp(name, def, def_len) == 0) || - (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { - git__free(name); - return 0; - } - - if ((buf = git_array_alloc(repo->reserved_names)) == NULL) - return -1; - - git_str_attach(buf, name, name_len); - return true; -} - -bool git_repository__reserved_names( - git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) -{ - GIT_UNUSED(include_ntfs); - - if (repo->reserved_names.size == 0) { - git_str *buf; - size_t i; - - /* Add the static defaults */ - for (i = 0; i < git_repository__reserved_names_win32_len; i++) { - if ((buf = git_array_alloc(repo->reserved_names)) == NULL) - goto on_error; - - buf->ptr = git_repository__reserved_names_win32[i].ptr; - buf->size = git_repository__reserved_names_win32[i].size; - } - - /* Try to add any repo-specific reserved names - the gitlink file - * within a submodule or the repository (if the repository directory - * is beneath the workdir). These are typically `.git`, but should - * be protected in case they are not. Note, repo and workdir paths - * are always prettified to end in `/`, so a prefixcmp is safe. - */ - if (!repo->is_bare) { - int (*prefixcmp)(const char *, const char *); - int error, ignorecase; - - error = git_repository__configmap_lookup( - &ignorecase, repo, GIT_CONFIGMAP_IGNORECASE); - prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : - git__prefixcmp; - - if (repo->gitlink && - reserved_names_add8dot3(repo, repo->gitlink) < 0) - goto on_error; - - if (repo->gitdir && - prefixcmp(repo->gitdir, repo->workdir) == 0 && - reserved_names_add8dot3(repo, repo->gitdir) < 0) - goto on_error; - } - } - - *out = repo->reserved_names.ptr; - *outlen = repo->reserved_names.size; - - return true; - - /* Always give good defaults, even on OOM */ -on_error: - *out = git_repository__reserved_names_win32; - *outlen = git_repository__reserved_names_win32_len; - - return false; -} -#else -bool git_repository__reserved_names( - git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) -{ - GIT_UNUSED(repo); - - if (include_ntfs) { - *out = git_repository__reserved_names_win32; - *outlen = git_repository__reserved_names_win32_len; - } else { - *out = git_repository__reserved_names_posix; - *outlen = git_repository__reserved_names_posix_len; - } - - return true; -} -#endif - -static int check_repositoryformatversion(int *version, git_config *config) -{ - int error; - - error = git_config_get_int32(version, config, "core.repositoryformatversion"); - /* git ignores this if the config variable isn't there */ - if (error == GIT_ENOTFOUND) - return 0; - - if (error < 0) - return -1; - - if (GIT_REPO_MAX_VERSION < *version) { - git_error_set(GIT_ERROR_REPOSITORY, - "unsupported repository version %d; only versions up to %d are supported", - *version, GIT_REPO_MAX_VERSION); - return -1; - } - - return 0; -} - -static const char *builtin_extensions[] = { - "noop" -}; - -static git_vector user_extensions = GIT_VECTOR_INIT; - -static int check_valid_extension(const git_config_entry *entry, void *payload) -{ - git_str cfg = GIT_STR_INIT; - bool reject; - const char *extension; - size_t i; - int error = 0; - - GIT_UNUSED(payload); - - git_vector_foreach (&user_extensions, i, extension) { - git_str_clear(&cfg); - - /* - * Users can specify that they don't want to support an - * extension with a '!' prefix. - */ - if ((reject = (extension[0] == '!')) == true) - extension = &extension[1]; - - if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) - goto done; - - if (strcmp(entry->name, cfg.ptr) == 0) { - if (reject) - goto fail; - - goto done; - } - } - - for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { - extension = builtin_extensions[i]; - - if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) - goto done; - - if (strcmp(entry->name, cfg.ptr) == 0) - goto done; - } - -fail: - git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name); - error = -1; - -done: - git_str_dispose(&cfg); - return error; -} - -static int check_extensions(git_config *config, int version) -{ - if (version < 1) - return 0; - - return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL); -} - -int git_repository__extensions(char ***out, size_t *out_len) -{ - git_vector extensions; - const char *builtin, *user; - char *extension; - size_t i, j; - - if (git_vector_init(&extensions, 8, NULL) < 0) - return -1; - - for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { - bool match = false; - - builtin = builtin_extensions[i]; - - git_vector_foreach (&user_extensions, j, user) { - if (user[0] == '!' && strcmp(builtin, &user[1]) == 0) { - match = true; - break; - } - } - - if (match) - continue; - - if ((extension = git__strdup(builtin)) == NULL || - git_vector_insert(&extensions, extension) < 0) - return -1; - } - - git_vector_foreach (&user_extensions, i, user) { - if (user[0] == '!') - continue; - - if ((extension = git__strdup(user)) == NULL || - git_vector_insert(&extensions, extension) < 0) - return -1; - } - - *out = (char **)git_vector_detach(out_len, NULL, &extensions); - return 0; -} - -int git_repository__set_extensions(const char **extensions, size_t len) -{ - char *extension; - size_t i; - - git_repository__free_extensions(); - - for (i = 0; i < len; i++) { - if ((extension = git__strdup(extensions[i])) == NULL || - git_vector_insert(&user_extensions, extension) < 0) - return -1; - } - - return 0; -} - -void git_repository__free_extensions(void) -{ - git_vector_free_deep(&user_extensions); -} - -int git_repository_create_head(const char *git_dir, const char *ref_name) -{ - git_str ref_path = GIT_STR_INIT; - git_filebuf ref = GIT_FILEBUF_INIT; - const char *fmt; - int error; - - if ((error = git_str_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 (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0) - fmt = "ref: %s\n"; - else - fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; - - if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 || - (error = git_filebuf_commit(&ref)) < 0) - goto out; - -out: - git_str_dispose(&ref_path); - git_filebuf_cleanup(&ref); - return error; -} - -static bool is_chmod_supported(const char *file_path) -{ - struct stat st1, st2; - - if (p_stat(file_path, &st1) < 0) - return false; - - if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) - return false; - - if (p_stat(file_path, &st2) < 0) - return false; - - return (st1.st_mode != st2.st_mode); -} - -static bool is_filesystem_case_insensitive(const char *gitdir_path) -{ - git_str path = GIT_STR_INIT; - int is_insensitive = -1; - - if (!git_str_joinpath(&path, gitdir_path, "CoNfIg")) - is_insensitive = git_fs_path_exists(git_str_cstr(&path)); - - git_str_dispose(&path); - return is_insensitive; -} - -/* - * Return a configuration object with only the global and system - * configurations; no repository-level configuration. - */ -static int load_global_config(git_config **config) -{ - git_str global_buf = GIT_STR_INIT; - git_str xdg_buf = GIT_STR_INIT; - git_str system_buf = GIT_STR_INIT; - git_str programdata_buf = GIT_STR_INIT; - int error; - - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); - - error = load_config(config, NULL, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); - - git_str_dispose(&global_buf); - git_str_dispose(&xdg_buf); - git_str_dispose(&system_buf); - git_str_dispose(&programdata_buf); - - return error; -} - -static bool are_symlinks_supported(const char *wd_path) -{ - git_config *config = NULL; - int symlinks = 0; - - /* - * To emulate Git for Windows, symlinks on Windows must be explicitly - * opted-in. We examine the system configuration for a core.symlinks - * set to true. If found, we then examine the filesystem to see if - * symlinks are _actually_ supported by the current user. If that is - * _not_ set, then we do not test or enable symlink support. - */ -#ifdef GIT_WIN32 - if (load_global_config(&config) < 0 || - git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || - !symlinks) - goto done; -#endif - - if (!(symlinks = git_fs_path_supports_symlinks(wd_path))) - goto done; - -done: - git_config_free(config); - return symlinks != 0; -} - -static int create_empty_file(const char *path, mode_t mode) -{ - int fd; - - if ((fd = p_creat(path, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "error while creating '%s'", path); - return -1; - } - - if (p_close(fd) < 0) { - git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); - return -1; - } - - return 0; -} - -static int repo_local_config( - git_config **out, - git_str *config_dir, - git_repository *repo, - const char *repo_dir) -{ - int error = 0; - git_config *parent; - const char *cfg_path; - - if (git_str_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) - return -1; - cfg_path = git_str_cstr(config_dir); - - /* make LOCAL config if missing */ - if (!git_fs_path_isfile(cfg_path) && - (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0) - return error; - - /* if no repo, just open that file directly */ - if (!repo) - return git_config_open_ondisk(out, cfg_path); - - /* otherwise, open parent config and get that level */ - if ((error = git_repository_config__weakptr(&parent, repo)) < 0) - return error; - - if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) { - git_error_clear(); - - if (!(error = git_config_add_file_ondisk( - parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, repo, false))) - error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); - } - - git_config_free(parent); - - return error; -} - -static int repo_init_fs_configs( - git_config *cfg, - const char *cfg_path, - const char *repo_dir, - const char *work_dir, - bool update_ignorecase) -{ - int error = 0; - - if (!work_dir) - work_dir = repo_dir; - - if ((error = git_config_set_bool( - cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) - return error; - - if (!are_symlinks_supported(work_dir)) { - if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) - return error; - } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) - git_error_clear(); - - if (update_ignorecase) { - if (is_filesystem_case_insensitive(repo_dir)) { - if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0) - return error; - } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0) - git_error_clear(); - } - -#ifdef GIT_USE_ICONV - if ((error = git_config_set_bool( - cfg, "core.precomposeunicode", - git_fs_path_does_decompose_unicode(work_dir))) < 0) - return error; - /* on non-iconv platforms, don't even set core.precomposeunicode */ -#endif - - return 0; -} - -static int repo_init_config( - const char *repo_dir, - const char *work_dir, - uint32_t flags, - uint32_t mode) -{ - int error = 0; - git_str cfg_path = GIT_STR_INIT, worktree_path = GIT_STR_INIT; - git_config *config = NULL; - bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); - bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); - int version = 0; - - if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) - goto cleanup; - - if (is_reinit && (error = check_repositoryformatversion(&version, config)) < 0) - goto cleanup; - - if ((error = check_extensions(config, version)) < 0) - goto cleanup; - -#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \ - if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ - goto cleanup; } while (0) - - SET_REPO_CONFIG(bool, "core.bare", is_bare); - SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); - - if ((error = repo_init_fs_configs( - config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) - goto cleanup; - - if (!is_bare) { - SET_REPO_CONFIG(bool, "core.logallrefupdates", true); - - if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { - if ((error = git_str_sets(&worktree_path, work_dir)) < 0) - goto cleanup; - - if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK)) - if ((error = git_fs_path_make_relative(&worktree_path, repo_dir)) < 0) - goto cleanup; - - SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr); - } else if (is_reinit) { - if (git_config_delete_entry(config, "core.worktree") < 0) - git_error_clear(); - } - } - - if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { - SET_REPO_CONFIG(int32, "core.sharedrepository", 1); - SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); - } - else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) { - SET_REPO_CONFIG(int32, "core.sharedrepository", 2); - SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); - } - -cleanup: - git_str_dispose(&cfg_path); - git_str_dispose(&worktree_path); - git_config_free(config); - - return error; -} - -static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p) -{ - git_repository *smrepo = NULL; - GIT_UNUSED(n); GIT_UNUSED(p); - - if (git_submodule_open(&smrepo, sm) < 0 || - git_repository_reinit_filesystem(smrepo, true) < 0) - git_error_clear(); - git_repository_free(smrepo); - - return 0; -} - -int git_repository_reinit_filesystem(git_repository *repo, int recurse) -{ - int error = 0; - git_str path = GIT_STR_INIT; - git_config *config = NULL; - const char *repo_dir = git_repository_path(repo); - - if (!(error = repo_local_config(&config, &path, repo, repo_dir))) - error = repo_init_fs_configs( - config, path.ptr, repo_dir, git_repository_workdir(repo), true); - - git_config_free(config); - git_str_dispose(&path); - - git_repository__configmap_lookup_cache_clear(repo); - - if (!repo->is_bare && recurse) - (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); - - return error; -} - -static int repo_write_template( - const char *git_dir, - bool allow_overwrite, - const char *file, - mode_t mode, - bool hidden, - const char *content) -{ - git_str path = GIT_STR_INIT; - int fd, error = 0, flags; - - if (git_str_joinpath(&path, git_dir, file) < 0) - return -1; - - if (allow_overwrite) - flags = O_WRONLY | O_CREAT | O_TRUNC; - else - flags = O_WRONLY | O_CREAT | O_EXCL; - - fd = p_open(git_str_cstr(&path), flags, mode); - - if (fd >= 0) { - error = p_write(fd, content, strlen(content)); - - p_close(fd); - } - else if (errno != EEXIST) - error = fd; - -#ifdef GIT_WIN32 - if (!error && hidden) { - if (git_win32__set_hidden(path.ptr, true) < 0) - error = -1; - } -#else - GIT_UNUSED(hidden); -#endif - - git_str_dispose(&path); - - if (error) - git_error_set(GIT_ERROR_OS, - "failed to initialize repository with template '%s'", file); - - return error; -} - -static int repo_write_gitlink( - const char *in_dir, const char *to_repo, bool use_relative_path) -{ - int error; - git_str buf = GIT_STR_INIT; - git_str path_to_repo = GIT_STR_INIT; - struct stat st; - - git_fs_path_dirname_r(&buf, to_repo); - git_fs_path_to_dir(&buf); - if (git_str_oom(&buf)) - return -1; - - /* don't write gitlink to natural workdir */ - if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 && - strcmp(in_dir, buf.ptr) == 0) - { - error = GIT_PASSTHROUGH; - goto cleanup; - } - - if ((error = git_str_joinpath(&buf, in_dir, DOT_GIT)) < 0) - goto cleanup; - - if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { - git_error_set(GIT_ERROR_REPOSITORY, - "cannot overwrite gitlink file into path '%s'", in_dir); - error = GIT_EEXISTS; - goto cleanup; - } - - git_str_clear(&buf); - - error = git_str_sets(&path_to_repo, to_repo); - - if (!error && use_relative_path) - error = git_fs_path_make_relative(&path_to_repo, in_dir); - - if (!error) - error = git_str_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); - - if (!error) - error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); - -cleanup: - git_str_dispose(&buf); - git_str_dispose(&path_to_repo); - return error; -} - -static mode_t pick_dir_mode(git_repository_init_options *opts) -{ - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK) - return 0777; - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) - return (0775 | S_ISGID); - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) - return (0777 | S_ISGID); - return opts->mode; -} - -#include "repo_template.h" - -static int repo_init_structure( - const char *repo_dir, - const char *work_dir, - git_repository_init_options *opts) -{ - int error = 0; - repo_template_item *tpl; - bool external_tpl = - ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); - mode_t dmode = pick_dir_mode(opts); - bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; - - /* Hide the ".git" directory */ -#ifdef GIT_WIN32 - if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { - if (git_win32__set_hidden(repo_dir, true) < 0) { - git_error_set(GIT_ERROR_OS, - "failed to mark Git repository folder as hidden"); - return -1; - } - } -#endif - - /* Create the .git gitlink if appropriate */ - if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && - (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0) - { - if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0) - return -1; - } - - /* Copy external template if requested */ - if (external_tpl) { - git_config *cfg = NULL; - const char *tdir = NULL; - bool default_template = false; - git_str template_buf = GIT_STR_INIT; - - if (opts->template_path) - tdir = opts->template_path; - else if ((error = git_config_open_default(&cfg)) >= 0) { - if (!git_config__get_path(&template_buf, cfg, "init.templatedir")) - tdir = template_buf.ptr; - git_error_clear(); - } - - if (!tdir) { - if (!(error = git_sysdir_find_template_dir(&template_buf))) - tdir = template_buf.ptr; - default_template = true; - } - - /* - * If tdir was the empty string, treat it like tdir was a path to an - * empty directory (so, don't do any copying). This is the behavior - * that git(1) exhibits, although it doesn't seem to be officially - * documented. - */ - if (tdir && git__strcmp(tdir, "") != 0) { - uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | - GIT_CPDIR_SIMPLE_TO_MODE | - GIT_CPDIR_COPY_DOTFILES; - if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) - cpflags |= GIT_CPDIR_CHMOD_DIRS; - error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); - } - - git_str_dispose(&template_buf); - git_config_free(cfg); - - /* If tdir does not exist, then do not error out. This matches the - * behaviour of git(1), which just prints a warning and continues. - * TODO: issue warning when warning API is available. - * `git` prints to stderr: 'warning: templates not found in /path/to/tdir' - */ - if (error < 0) { - if (!default_template && error != GIT_ENOTFOUND) - return error; - - /* if template was default, ignore error and use internal */ - git_error_clear(); - external_tpl = false; - error = 0; - } - } - - /* Copy internal template - * - always ensure existence of dirs - * - only create files if no external template was specified - */ - for (tpl = repo_template; !error && tpl->path; ++tpl) { - if (!tpl->content) { - uint32_t mkdir_flags = GIT_MKDIR_PATH; - if (chmod) - mkdir_flags |= GIT_MKDIR_CHMOD; - - error = git_futils_mkdir_relative( - tpl->path, repo_dir, dmode, mkdir_flags, NULL); - } - else if (!external_tpl) { - const char *content = tpl->content; - - if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0) - content = opts->description; - - error = repo_write_template( - repo_dir, false, tpl->path, tpl->mode, false, content); - } - } - - return error; -} - -static int mkdir_parent(git_str *buf, uint32_t mode, bool skip2) -{ - /* When making parent directories during repository initialization - * don't try to set gid or grant world write access - */ - return git_futils_mkdir( - buf->ptr, mode & ~(S_ISGID | 0002), - GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | - (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); -} - -static int repo_init_directories( - git_str *repo_path, - git_str *wd_path, - const char *given_repo, - git_repository_init_options *opts) -{ - int error = 0; - bool is_bare, add_dotgit, has_dotgit, natural_wd; - mode_t dirmode; - - /* There are three possible rules for what we are allowed to create: - * - MKPATH means anything we need - * - MKDIR means just the .git directory and its parent and the workdir - * - Neither means only the .git directory can be created - * - * There are 5 "segments" of path that we might need to deal with: - * 1. The .git directory - * 2. The parent of the .git directory - * 3. Everything above the parent of the .git directory - * 4. The working directory (often the same as #2) - * 5. Everything above the working directory (often the same as #3) - * - * For all directories created, we start with the init_mode value for - * permissions and then strip off bits in some cases: - * - * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH - * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID - * For all rules, we create #1 using the untouched init_mode - */ - - /* set up repo path */ - - is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); - - add_dotgit = - (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 && - !is_bare && - git__suffixcmp(given_repo, "/" DOT_GIT) != 0 && - git__suffixcmp(given_repo, "/" GIT_DIR) != 0; - - if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) - return -1; - - has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); - if (has_dotgit) - opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; - - /* set up workdir path */ - - if (!is_bare) { - if (opts->workdir_path) { - if (git_fs_path_join_unrooted( - wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0) - return -1; - } else if (has_dotgit) { - if (git_fs_path_dirname_r(wd_path, repo_path->ptr) < 0) - return -1; - } else { - git_error_set(GIT_ERROR_REPOSITORY, "cannot pick working directory" - " for non-bare repository that isn't a '.git' directory"); - return -1; - } - - if (git_fs_path_to_dir(wd_path) < 0) - return -1; - } else { - git_str_clear(wd_path); - } - - natural_wd = - has_dotgit && - wd_path->size > 0 && - wd_path->size + strlen(GIT_DIR) == repo_path->size && - memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0; - if (natural_wd) - opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD; - - /* create directories as needed / requested */ - - dirmode = pick_dir_mode(opts); - - if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) { - /* create path #5 */ - if (wd_path->size > 0 && - (error = mkdir_parent(wd_path, dirmode, false)) < 0) - return error; - - /* create path #3 (if not the same as #5) */ - if (!natural_wd && - (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0) - return error; - } - - if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || - (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) - { - /* create path #4 */ - if (wd_path->size > 0 && - (error = git_futils_mkdir( - wd_path->ptr, dirmode & ~S_ISGID, - GIT_MKDIR_VERIFY_DIR)) < 0) - return error; - - /* create path #2 (if not the same as #4) */ - if (!natural_wd && - (error = git_futils_mkdir( - repo_path->ptr, dirmode & ~S_ISGID, - GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) - return error; - } - - if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || - (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 || - has_dotgit) - { - /* create path #1 */ - error = git_futils_mkdir(repo_path->ptr, dirmode, - GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); - } - - /* prettify both directories now that they are created */ - - if (!error) { - error = git_fs_path_prettify_dir(repo_path, repo_path->ptr, NULL); - - if (!error && wd_path->size > 0) - error = git_fs_path_prettify_dir(wd_path, wd_path->ptr, NULL); - } - - return error; -} - -static int repo_init_head(const char *repo_dir, const char *given) -{ - git_config *cfg = NULL; - git_str head_path = GIT_STR_INIT, cfg_branch = GIT_STR_INIT; - const char *initial_head = NULL; - int error; - - if ((error = git_str_joinpath(&head_path, repo_dir, GIT_HEAD_FILE)) < 0) - goto out; - - /* - * A template may have set a HEAD; use that unless it's been - * overridden by the caller's given initial head setting. - */ - if (git_fs_path_exists(head_path.ptr) && !given) - goto out; - - if (given) { - initial_head = given; - } else if ((error = git_config_open_default(&cfg)) >= 0 && - (error = git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch")) >= 0 && - *cfg_branch.ptr) { - initial_head = cfg_branch.ptr; - } - - if (!initial_head) - initial_head = GIT_BRANCH_DEFAULT; - - error = git_repository_create_head(repo_dir, initial_head); - -out: - git_config_free(cfg); - git_str_dispose(&head_path); - git_str_dispose(&cfg_branch); - - return error; -} - -static int repo_init_create_origin(git_repository *repo, const char *url) -{ - int error; - git_remote *remote; - - if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) { - git_remote_free(remote); - } - - return error; -} - -int git_repository_init( - git_repository **repo_out, const char *path, unsigned is_bare) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */ - if (is_bare) - opts.flags |= GIT_REPOSITORY_INIT_BARE; - - return git_repository_init_ext(repo_out, path, &opts); -} - -int git_repository_init_ext( - git_repository **out, - const char *given_repo, - git_repository_init_options *opts) -{ - git_str repo_path = GIT_STR_INIT, wd_path = GIT_STR_INIT, - common_path = GIT_STR_INIT; - const char *wd; - bool is_valid; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(given_repo); - GIT_ASSERT_ARG(opts); - - GIT_ERROR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options"); - - if ((error = repo_init_directories(&repo_path, &wd_path, given_repo, opts)) < 0) - goto out; - - wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_str_cstr(&wd_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 out; - } - - opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; - - 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 = repo_init_head(repo_path.ptr, opts->initial_head)) < 0) - goto out; - } - - if ((error = git_repository_open(out, repo_path.ptr)) < 0) - goto out; - - if (opts->origin_url && - (error = repo_init_create_origin(*out, opts->origin_url)) < 0) - goto out; - -out: - git_str_dispose(&common_path); - git_str_dispose(&repo_path); - git_str_dispose(&wd_path); - - return error; -} - -int git_repository_head_detached(git_repository *repo) -{ - git_reference *ref; - git_odb *odb = NULL; - int exists; - - if (git_repository_odb__weakptr(&odb, repo) < 0) - return -1; - - if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) - return -1; - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - git_reference_free(ref); - return 0; - } - - exists = git_odb_exists(odb, git_reference_target(ref)); - - git_reference_free(ref); - return exists; -} - -int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) -{ - git_reference *ref = NULL; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0) - goto out; - - error = (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC); -out: - git_reference_free(ref); - - return error; -} - -int git_repository_head(git_reference **head_out, git_repository *repo) -{ - git_reference *head; - int error; - - GIT_ASSERT_ARG(head_out); - - if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) - return error; - - if (git_reference_type(head) == GIT_REFERENCE_DIRECT) { - *head_out = head; - return 0; - } - - error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1); - git_reference_free(head); - - return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; -} - -int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) -{ - git_repository *worktree_repo = NULL; - git_worktree *worktree = NULL; - git_reference *head = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - *out = NULL; - - if ((error = git_worktree_lookup(&worktree, repo, name)) < 0 || - (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0 || - (error = git_reference_lookup(&head, worktree_repo, GIT_HEAD_FILE)) < 0) - goto out; - - if (git_reference_type(head) != GIT_REFERENCE_DIRECT) { - if ((error = git_reference_lookup_resolved(out, worktree_repo, git_reference_symbolic_target(head), -1)) < 0) - goto out; - } else { - *out = head; - head = NULL; - } - -out: - git_reference_free(head); - git_worktree_free(worktree); - git_repository_free(worktree_repo); - return error; -} - -int git_repository_foreach_worktree(git_repository *repo, - git_repository_foreach_worktree_cb cb, - void *payload) -{ - git_strarray worktrees = {0}; - git_repository *worktree_repo = NULL; - git_worktree *worktree = NULL; - int error; - size_t i; - - /* apply operation to repository supplied when commondir is empty, implying there's - * no linked worktrees to iterate, which can occur when using custom odb/refdb - */ - if (!repo->commondir) - return cb(repo, payload); - - if ((error = git_repository_open(&worktree_repo, repo->commondir)) < 0 || - (error = cb(worktree_repo, payload) != 0)) - goto out; - - git_repository_free(worktree_repo); - worktree_repo = NULL; - - if ((error = git_worktree_list(&worktrees, repo)) < 0) - goto out; - - for (i = 0; i < worktrees.count; i++) { - git_repository_free(worktree_repo); - worktree_repo = NULL; - git_worktree_free(worktree); - worktree = NULL; - - if ((error = git_worktree_lookup(&worktree, repo, worktrees.strings[i]) < 0) || - (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - continue; - } - - if ((error = cb(worktree_repo, payload)) != 0) - goto out; - } - -out: - git_strarray_dispose(&worktrees); - git_repository_free(worktree_repo); - git_worktree_free(worktree); - return error; -} - -int git_repository_head_unborn(git_repository *repo) -{ - git_reference *ref = NULL; - int error; - - error = git_repository_head(&ref, repo); - git_reference_free(ref); - - if (error == GIT_EUNBORNBRANCH) { - git_error_clear(); - return 1; - } - - if (error < 0) - return -1; - - return 0; -} - -static int repo_contains_no_reference(git_repository *repo) -{ - git_reference_iterator *iter; - const char *refname; - int error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - return error; - - error = git_reference_next_name(&refname, iter); - git_reference_iterator_free(iter); - - if (error == GIT_ITEROVER) - return 1; - - return error; -} - -int git_repository_initialbranch(git_str *out, git_repository *repo) -{ - git_config *config; - git_config_entry *entry = NULL; - const char *branch; - int valid, error; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_config_get_entry(&entry, config, "init.defaultbranch")) == 0 && - *entry->value) { - branch = entry->value; - } - else if (!error || error == GIT_ENOTFOUND) { - branch = GIT_BRANCH_DEFAULT; - } - else { - goto done; - } - - if ((error = git_str_puts(out, GIT_REFS_HEADS_DIR)) < 0 || - (error = git_str_puts(out, branch)) < 0 || - (error = git_reference_name_is_valid(&valid, out->ptr)) < 0) - goto done; - - if (!valid) { - git_error_set(GIT_ERROR_INVALID, "the value of init.defaultBranch is not a valid branch name"); - error = -1; - } - -done: - git_config_entry_free(entry); - return error; -} - -int git_repository_is_empty(git_repository *repo) -{ - git_reference *head = NULL; - git_str initialbranch = GIT_STR_INIT; - int result = 0; - - if ((result = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0 || - (result = git_repository_initialbranch(&initialbranch, repo)) < 0) - goto done; - - result = (git_reference_type(head) == GIT_REFERENCE_SYMBOLIC && - strcmp(git_reference_symbolic_target(head), initialbranch.ptr) == 0 && - repo_contains_no_reference(repo)); - -done: - git_reference_free(head); - git_str_dispose(&initialbranch); - - return result; -} - -static const char *resolved_parent_path(const git_repository *repo, git_repository_item_t item, git_repository_item_t fallback) -{ - const char *parent; - - switch (item) { - case GIT_REPOSITORY_ITEM_GITDIR: - parent = git_repository_path(repo); - break; - case GIT_REPOSITORY_ITEM_WORKDIR: - parent = git_repository_workdir(repo); - break; - case GIT_REPOSITORY_ITEM_COMMONDIR: - parent = git_repository_commondir(repo); - break; - default: - git_error_set(GIT_ERROR_INVALID, "invalid item directory"); - 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) -{ - GIT_BUF_WRAP_PRIVATE(out, git_repository__item_path, repo, item); -} - -int git_repository__item_path( - git_str *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; - } - - if (git_str_sets(out, parent) < 0) - return -1; - - if (items[item].name) { - if (git_str_joinpath(out, parent, items[item].name) < 0) - return -1; - } - - if (items[item].directory) { - if (git_fs_path_to_dir(out) < 0) - return -1; - } - - return 0; -} - -const char *git_repository_path(const git_repository *repo) -{ - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - return repo->gitdir; -} - -const char *git_repository_workdir(const git_repository *repo) -{ - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - - if (repo->is_bare) - return NULL; - - return repo->workdir; -} - -int git_repository_workdir_path( - git_str *out, git_repository *repo, const char *path) -{ - int error; - - if (!repo->workdir) { - git_error_set(GIT_ERROR_REPOSITORY, "repository has no working directory"); - return GIT_EBAREREPO; - } - - if (!(error = git_str_joinpath(out, repo->workdir, path))) - error = git_path_validate_str_length(repo, out); - - return error; -} - -const char *git_repository_commondir(const git_repository *repo) -{ - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - return repo->commondir; -} - -int git_repository_set_workdir( - git_repository *repo, const char *workdir, int update_gitlink) -{ - int error = 0; - git_str path = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(workdir); - - if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) - return -1; - - if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) - return 0; - - if (update_gitlink) { - git_config *config; - - if (git_repository_config__weakptr(&config, repo) < 0) - return -1; - - error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); - - /* passthrough error means gitlink is unnecessary */ - if (error == GIT_PASSTHROUGH) - error = git_config_delete_entry(config, "core.worktree"); - else if (!error) - error = git_config_set_string(config, "core.worktree", path.ptr); - - if (!error) - error = git_config_set_bool(config, "core.bare", false); - } - - if (!error) { - char *old_workdir = repo->workdir; - - repo->workdir = git_str_detach(&path); - repo->is_bare = 0; - - git__free(old_workdir); - } - - return error; -} - -int git_repository_is_bare(const git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - return repo->is_bare; -} - -int git_repository_is_worktree(const git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - return repo->is_worktree; -} - -int git_repository_set_bare(git_repository *repo) -{ - int error; - git_config *config; - - GIT_ASSERT_ARG(repo); - - if (repo->is_bare) - return 0; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_config_set_bool(config, "core.bare", true)) < 0) - return error; - - if ((error = git_config__update_entry(config, "core.worktree", NULL, true, true)) < 0) - return error; - - git__free(repo->workdir); - repo->workdir = NULL; - repo->is_bare = 1; - - return 0; -} - -int git_repository_head_tree(git_tree **tree, git_repository *repo) -{ - git_reference *head; - git_object *obj; - int error; - - if ((error = git_repository_head(&head, repo)) < 0) - return error; - - if ((error = git_reference_peel(&obj, head, GIT_OBJECT_TREE)) < 0) - goto cleanup; - - *tree = (git_tree *)obj; - -cleanup: - git_reference_free(head); - return error; -} - -int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - char orig_head_str[GIT_OID_HEXSZ]; - int error = 0; - - git_oid_fmt(orig_head_str, orig_head); - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 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); - - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int git_repository__message(git_str *out, git_repository *repo) -{ - git_str path = GIT_STR_INIT; - struct stat st; - int error; - - if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) - return -1; - - if ((error = p_stat(git_str_cstr(&path), &st)) < 0) { - if (errno == ENOENT) - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_OS, "could not access message file"); - } else { - error = git_futils_readbuffer(out, git_str_cstr(&path)); - } - - git_str_dispose(&path); - - return error; -} - -int git_repository_message(git_buf *out, git_repository *repo) -{ - GIT_BUF_WRAP_PRIVATE(out, git_repository__message, repo); -} - -int git_repository_message_remove(git_repository *repo) -{ - git_str path = GIT_STR_INIT; - int error; - - if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) - return -1; - - error = p_unlink(git_str_cstr(&path)); - git_str_dispose(&path); - - return error; -} - -int git_repository_hashfile( - git_oid *out, - git_repository *repo, - const char *path, - git_object_t type, - const char *as_path) -{ - int error; - git_filter_list *fl = NULL; - git_file fd = -1; - uint64_t len; - git_str full_path = GIT_STR_INIT; - const char *workdir = git_repository_workdir(repo); - - /* as_path can be NULL */ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(repo); - - if ((error = git_fs_path_join_unrooted(&full_path, path, workdir, NULL)) < 0 || - (error = git_path_validate_str_length(repo, &full_path)) < 0) - return error; - - /* - * NULL as_path means that we should derive it from the - * given path. - */ - if (!as_path) { - if (workdir && !git__prefixcmp(full_path.ptr, workdir)) - as_path = full_path.ptr + strlen(workdir); - else - as_path = ""; - } - - /* passing empty string for "as_path" indicated --no-filters */ - if (strlen(as_path) > 0) { - error = git_filter_list_load( - &fl, repo, NULL, as_path, - GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); - - if (error < 0) - return error; - } - - /* at this point, error is a count of the number of loaded filters */ - - fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) { - error = fd; - goto cleanup; - } - - 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"); - error = -1; - goto cleanup; - } - - error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl); - -cleanup: - if (fd >= 0) - p_close(fd); - git_filter_list_free(fl); - git_str_dispose(&full_path); - - return error; -} - -static int checkout_message(git_str *out, git_reference *old, const char *new) -{ - git_str_puts(out, "checkout: moving from "); - - if (git_reference_type(old) == GIT_REFERENCE_SYMBOLIC) - git_str_puts(out, git_reference__shorthand(git_reference_symbolic_target(old))); - else - git_str_puts(out, git_oid_tostr_s(git_reference_target(old))); - - git_str_puts(out, " to "); - - if (git_reference__is_branch(new) || - git_reference__is_tag(new) || - git_reference__is_remote(new)) - git_str_puts(out, git_reference__shorthand(new)); - else - git_str_puts(out, new); - - if (git_str_oom(out)) - return -1; - - return 0; -} - -static int detach(git_repository *repo, const git_oid *id, const char *new) -{ - int error; - git_str log_message = GIT_STR_INIT; - git_object *object = NULL, *peeled = NULL; - git_reference *new_head = NULL, *current = NULL; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(id); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_object_lookup(&object, repo, id, GIT_OBJECT_ANY)) < 0) - goto cleanup; - - if ((error = git_object_peel(&peeled, object, GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - - if (new == NULL) - new = git_oid_tostr_s(git_object_id(peeled)); - - if ((error = checkout_message(&log_message, current, new)) < 0) - goto cleanup; - - error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_str_cstr(&log_message)); - -cleanup: - git_str_dispose(&log_message); - git_object_free(object); - git_object_free(peeled); - git_reference_free(current); - git_reference_free(new_head); - return error; -} - -int git_repository_set_head( - git_repository *repo, - const char *refname) -{ - git_reference *ref = NULL, *current = NULL, *new_head = NULL; - git_str log_message = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = checkout_message(&log_message, current, refname)) < 0) - goto cleanup; - - error = git_reference_lookup(&ref, repo, refname); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (ref && current->type == GIT_REFERENCE_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) && - git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) { - git_error_set(GIT_ERROR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD " - "of a linked repository.", git_reference_name(ref)); - error = -1; - goto cleanup; - } - - if (!error) { - if (git_reference_is_branch(ref)) { - error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, - git_reference_name(ref), true, git_str_cstr(&log_message)); - } else { - error = detach(repo, git_reference_target(ref), - git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL); - } - } else if (git_reference__is_branch(refname)) { - error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, - true, git_str_cstr(&log_message)); - } - -cleanup: - git_str_dispose(&log_message); - git_reference_free(current); - git_reference_free(ref); - git_reference_free(new_head); - return error; -} - -int git_repository_set_head_detached( - git_repository *repo, - const git_oid *committish) -{ - return detach(repo, committish, NULL); -} - -int git_repository_set_head_detached_from_annotated( - git_repository *repo, - const git_annotated_commit *committish) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(committish); - - return detach(repo, git_annotated_commit_id(committish), committish->description); -} - -int git_repository_detach_head(git_repository *repo) -{ - git_reference *old_head = NULL, *new_head = NULL, *current = NULL; - git_object *object = NULL; - git_str log_message = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(repo); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_repository_head(&old_head, repo)) < 0) - goto cleanup; - - if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - - if ((error = checkout_message(&log_message, current, git_oid_tostr_s(git_object_id(object)))) < 0) - goto cleanup; - - error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head), - 1, git_str_cstr(&log_message)); - -cleanup: - git_str_dispose(&log_message); - git_object_free(object); - git_reference_free(old_head); - git_reference_free(new_head); - git_reference_free(current); - return error; -} - -/** - * Loosely ported from git.git - * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289 - */ -int git_repository_state(git_repository *repo) -{ - git_str repo_path = GIT_STR_INIT; - int state = GIT_REPOSITORY_STATE_NONE; - - GIT_ASSERT_ARG(repo); - - if (git_str_puts(&repo_path, repo->gitdir) < 0) - return -1; - - if (git_fs_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE)) - state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE; - else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR)) - state = GIT_REPOSITORY_STATE_REBASE_MERGE; - else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE)) - state = GIT_REPOSITORY_STATE_REBASE; - else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE)) - state = GIT_REPOSITORY_STATE_APPLY_MAILBOX; - else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR)) - state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE; - else if (git_fs_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE)) - state = GIT_REPOSITORY_STATE_MERGE; - else if (git_fs_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) { - state = GIT_REPOSITORY_STATE_REVERT; - if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { - state = GIT_REPOSITORY_STATE_REVERT_SEQUENCE; - } - } else if (git_fs_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) { - state = GIT_REPOSITORY_STATE_CHERRYPICK; - if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { - state = GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE; - } - } else if (git_fs_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) - state = GIT_REPOSITORY_STATE_BISECT; - - git_str_dispose(&repo_path); - return state; -} - -int git_repository__cleanup_files( - git_repository *repo, const char *files[], size_t files_len) -{ - git_str buf = GIT_STR_INIT; - size_t i; - int error; - - for (error = 0, i = 0; !error && i < files_len; ++i) { - const char *path; - - if (git_str_joinpath(&buf, repo->gitdir, files[i]) < 0) - return -1; - - path = git_str_cstr(&buf); - - if (git_fs_path_isfile(path)) { - error = p_unlink(path); - } else if (git_fs_path_isdir(path)) { - error = git_futils_rmdir_r(path, NULL, - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); - } - - git_str_clear(&buf); - } - - git_str_dispose(&buf); - return error; -} - -static const char *state_files[] = { - GIT_MERGE_HEAD_FILE, - GIT_MERGE_MODE_FILE, - GIT_MERGE_MSG_FILE, - GIT_REVERT_HEAD_FILE, - GIT_CHERRYPICK_HEAD_FILE, - GIT_BISECT_LOG_FILE, - GIT_REBASE_MERGE_DIR, - GIT_REBASE_APPLY_DIR, - GIT_SEQUENCER_DIR, -}; - -int git_repository_state_cleanup(git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -int git_repository_is_shallow(git_repository *repo) -{ - git_str path = GIT_STR_INIT; - struct stat st; - int error; - - if ((error = git_str_joinpath(&path, repo->gitdir, "shallow")) < 0) - return error; - - error = git_fs_path_lstat(path.ptr, &st); - git_str_dispose(&path); - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - - if (error < 0) - return error; - return st.st_size == 0 ? 0 : 1; -} - -int git_repository_init_options_init( - git_repository_init_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_repository_init_options, - GIT_REPOSITORY_INIT_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_repository_init_init_options( - git_repository_init_options *opts, unsigned int version) -{ - return git_repository_init_options_init(opts, version); -} -#endif - -int git_repository_ident(const char **name, const char **email, const git_repository *repo) -{ - *name = repo->ident_name; - *email = repo->ident_email; - - return 0; -} - -int git_repository_set_ident(git_repository *repo, const char *name, const char *email) -{ - char *tmp_name = NULL, *tmp_email = NULL; - - if (name) { - tmp_name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(tmp_name); - } - - if (email) { - tmp_email = git__strdup(email); - GIT_ERROR_CHECK_ALLOC(tmp_email); - } - - tmp_name = git_atomic_swap(repo->ident_name, tmp_name); - tmp_email = git_atomic_swap(repo->ident_email, tmp_email); - - git__free(tmp_name); - git__free(tmp_email); - - return 0; -} - -int git_repository_submodule_cache_all(git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - return git_submodule_cache_init(&repo->submodule_cache, repo); -} - -int git_repository_submodule_cache_clear(git_repository *repo) -{ - int error = 0; - GIT_ASSERT_ARG(repo); - - error = git_submodule_cache_free(repo->submodule_cache); - repo->submodule_cache = NULL; - return error; -} diff --git a/src/repository.h b/src/repository.h deleted file mode 100644 index a488f2bf2..000000000 --- a/src/repository.h +++ /dev/null @@ -1,259 +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_repository_h__ -#define INCLUDE_repository_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/config.h" - -#include "array.h" -#include "cache.h" -#include "refs.h" -#include "str.h" -#include "object.h" -#include "attrcache.h" -#include "submodule.h" -#include "diff_driver.h" - -#define DOT_GIT ".git" -#define GIT_DIR DOT_GIT "/" -#define GIT_DIR_MODE 0755 -#define GIT_BARE_DIR_MODE 0777 - -/* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */ -#define GIT_DIR_SHORTNAME "GIT~1" - -extern bool git_repository__fsync_gitdir; -extern bool git_repository__validate_ownership; - -/** Cvar cache identifiers */ -typedef enum { - 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_LONGPATHS, /* core.longpaths */ - GIT_CONFIGMAP_CACHE_MAX -} git_configmap_item; - -/** - * Configuration map value enumerations - * - * 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_CONFIGMAP_NOT_CACHED = -1, - - /* core.safecrlf: false, 'fail', 'warn' */ - GIT_SAFE_CRLF_FALSE = 0, - GIT_SAFE_CRLF_FAIL = 1, - GIT_SAFE_CRLF_WARN = 2, - - /* core.autocrlf: false, true, 'input; */ - GIT_AUTO_CRLF_FALSE = 0, - GIT_AUTO_CRLF_TRUE = 1, - GIT_AUTO_CRLF_INPUT = 2, - GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, - - /* core.eol: unset, 'crlf', 'lf', 'native' */ - GIT_EOL_UNSET = 0, - GIT_EOL_CRLF = 1, - GIT_EOL_LF = 2, -#ifdef GIT_WIN32 - GIT_EOL_NATIVE = GIT_EOL_CRLF, -#else - GIT_EOL_NATIVE = GIT_EOL_LF, -#endif - GIT_EOL_DEFAULT = GIT_EOL_NATIVE, - - /* core.symlinks: bool */ - GIT_SYMLINKS_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.ignorecase */ - GIT_IGNORECASE_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.filemode */ - GIT_FILEMODE_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.ignorestat */ - GIT_IGNORESTAT_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.trustctime */ - GIT_TRUSTCTIME_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.abbrev */ - GIT_ABBREV_DEFAULT = 7, - /* core.precomposeunicode */ - GIT_PRECOMPOSE_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.safecrlf */ - GIT_SAFE_CRLF_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.logallrefupdates */ - 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_CONFIGMAP_FALSE, - /* core.protectNTFS */ - GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.fsyncObjectFiles */ - GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.longpaths */ - GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE -} git_configmap_value; - -/* internal repository init flags */ -enum { - GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16), - GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17), - GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18) -}; - -/** Internal structure for repository object */ -struct git_repository { - git_odb *_odb; - git_refdb *_refdb; - git_config *_config; - git_index *_index; - - git_cache objects; - git_attr_cache *attrcache; - git_diff_driver_registry *diff_drivers; - - char *gitlink; - char *gitdir; - char *commondir; - char *workdir; - char *namespace; - - char *ident_name; - char *ident_email; - - git_array_t(git_str) reserved_names; - - unsigned is_bare:1; - unsigned is_worktree:1; - - unsigned int lru_counter; - - git_atomic32 attr_session_key; - - intptr_t configmap_cache[GIT_CONFIGMAP_CACHE_MAX]; - git_strmap *submodule_cache; -}; - -GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) -{ - return repo->attrcache; -} - -int git_repository_head_tree(git_tree **tree, git_repository *repo); -int git_repository_create_head(const char *git_dir, const char *ref_name); - -typedef int (*git_repository_foreach_worktree_cb)(git_repository *, void *); - -int git_repository_foreach_worktree(git_repository *repo, - git_repository_foreach_worktree_cb cb, - void *payload); - -/* - * Weak pointers to repository internals. - * - * The returned pointers do not need to be freed. Do not keep - * permanent references to these (i.e. between API calls), since they may - * become invalidated if the user replaces a repository internal. - */ -int git_repository_config__weakptr(git_config **out, git_repository *repo); -int git_repository_odb__weakptr(git_odb **out, git_repository *repo); -int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); -int git_repository_index__weakptr(git_index **out, git_repository *repo); - -/* - * 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__configmap_lookup(int *out, git_repository *repo, git_configmap_item item); -void git_repository__configmap_lookup_cache_clear(git_repository *repo); - -int git_repository__item_path(git_str *out, const git_repository *repo, git_repository_item_t item); - -GIT_INLINE(int) git_repository__ensure_not_bare( - git_repository *repo, - const char *operation_name) -{ - if (!git_repository_is_bare(repo)) - return 0; - - git_error_set( - GIT_ERROR_REPOSITORY, - "cannot %s. This operation is not allowed against bare repositories.", - operation_name); - - return GIT_EBAREREPO; -} - -int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head); - -int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len); - -/* The default "reserved names" for a repository */ -extern git_str git_repository__reserved_names_win32[]; -extern size_t git_repository__reserved_names_win32_len; - -extern git_str git_repository__reserved_names_posix[]; -extern size_t git_repository__reserved_names_posix_len; - -/* - * Gets any "reserved names" in the repository. This will return paths - * that should not be allowed in the repository (like ".git") to avoid - * conflicting with the repository path, or with alternate mechanisms to - * the repository path (eg, "GIT~1"). Every attempt will be made to look - * up all possible reserved names - if there was a conflict for the shortname - * GIT~1, for example, this function will try to look up the alternate - * shortname. If that fails, this function returns false, but out and outlen - * will still be populated with good defaults. - */ -bool git_repository__reserved_names( - git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs); - -/* - * The default branch for the repository; the `init.defaultBranch` - * configuration option, if set, or `master` if it is not. - */ -int git_repository_initialbranch(git_str *out, git_repository *repo); - -/* - * Given a relative `path`, this makes it absolute based on the - * repository's working directory. This will perform validation - * to ensure that the path is not longer than MAX_PATH on Windows - * (unless `core.longpaths` is set in the repo config). - */ -int git_repository_workdir_path(git_str *out, git_repository *repo, const char *path); - -int git_repository__extensions(char ***out, size_t *out_len); -int git_repository__set_extensions(const char **extensions, size_t len); -void git_repository__free_extensions(void); - -#endif diff --git a/src/reset.c b/src/reset.c deleted file mode 100644 index e0d942e5e..000000000 --- a/src/reset.c +++ /dev/null @@ -1,204 +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 "common.h" - -#include "commit.h" -#include "tag.h" -#include "merge.h" -#include "diff.h" -#include "annotated_commit.h" -#include "git2/reset.h" -#include "git2/checkout.h" -#include "git2/merge.h" -#include "git2/refs.h" - -#define ERROR_MSG "Cannot perform reset" - -int git_reset_default( - git_repository *repo, - const git_object *target, - const git_strarray *pathspecs) -{ - git_object *commit = NULL; - git_tree *tree = NULL; - git_diff *diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - size_t i, max_i; - git_index_entry entry; - int error; - git_index *index = NULL; - - GIT_ASSERT_ARG(pathspecs && pathspecs->count > 0); - - memset(&entry, 0, sizeof(git_index_entry)); - - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; - - if (target) { - if (git_object_owner(target) != repo) { - git_error_set(GIT_ERROR_OBJECT, - "%s_default - The given target does not belong to this repository.", ERROR_MSG); - return -1; - } - - if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || - (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) - goto cleanup; - } - - opts.pathspec = *pathspecs; - opts.flags = GIT_DIFF_REVERSE; - - if ((error = git_diff_tree_to_index( - &diff, repo, tree, index, &opts)) < 0) - goto cleanup; - - for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) { - const git_diff_delta *delta = git_diff_get_delta(diff, i); - - GIT_ASSERT(delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_MODIFIED || - delta->status == GIT_DELTA_CONFLICTED || - delta->status == GIT_DELTA_DELETED); - - error = git_index_conflict_remove(index, delta->old_file.path); - if (error < 0) { - if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND) - git_error_clear(); - else - goto cleanup; - } - - if (delta->status == GIT_DELTA_DELETED) { - if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) - goto cleanup; - } else { - entry.mode = delta->new_file.mode; - git_oid_cpy(&entry.id, &delta->new_file.id); - entry.path = (char *)delta->new_file.path; - - if ((error = git_index_add(index, &entry)) < 0) - goto cleanup; - } - } - - error = git_index_write(index); - -cleanup: - git_object_free(commit); - git_tree_free(tree); - git_index_free(index); - git_diff_free(diff); - - return error; -} - -static int reset( - git_repository *repo, - const git_object *target, - const char *to, - git_reset_t reset_type, - const git_checkout_options *checkout_opts) -{ - git_object *commit = NULL; - git_index *index = NULL; - git_tree *tree = NULL; - int error = 0; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str log_message = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(target); - - if (checkout_opts) - opts = *checkout_opts; - - if (git_object_owner(target) != repo) { - git_error_set(GIT_ERROR_OBJECT, - "%s - The given target does not belong to this repository.", ERROR_MSG); - return -1; - } - - if (reset_type != GIT_RESET_SOFT && - (error = git_repository__ensure_not_bare(repo, - reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0) - return error; - - if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || - (error = git_repository_index(&index, repo)) < 0 || - (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) - goto cleanup; - - if (reset_type == GIT_RESET_SOFT && - (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE || - git_index_has_conflicts(index))) - { - git_error_set(GIT_ERROR_OBJECT, "%s (soft) in the middle of a merge", ERROR_MSG); - error = GIT_EUNMERGED; - goto cleanup; - } - - if ((error = git_str_printf(&log_message, "reset: moving to %s", to)) < 0) - return error; - - if (reset_type == GIT_RESET_HARD) { - /* overwrite working directory with the new tree */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) - goto cleanup; - } - - /* move HEAD to the new target */ - if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, - git_object_id(commit), NULL, git_str_cstr(&log_message))) < 0) - goto cleanup; - - if (reset_type > GIT_RESET_SOFT) { - /* reset index to the target content */ - - if ((error = git_index_read_tree(index, tree)) < 0 || - (error = git_index_write(index)) < 0) - goto cleanup; - - if ((error = git_repository_state_cleanup(repo)) < 0) { - git_error_set(GIT_ERROR_INDEX, "%s - failed to clean up merge data", ERROR_MSG); - goto cleanup; - } - } - -cleanup: - git_object_free(commit); - git_index_free(index); - git_tree_free(tree); - git_str_dispose(&log_message); - - return error; -} - -int git_reset( - git_repository *repo, - const git_object *target, - git_reset_t reset_type, - const git_checkout_options *checkout_opts) -{ - char to[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(to, GIT_OID_HEXSZ + 1, git_object_id(target)); - return reset(repo, target, to, reset_type, checkout_opts); -} - -int git_reset_from_annotated( - git_repository *repo, - const git_annotated_commit *commit, - git_reset_t reset_type, - const git_checkout_options *checkout_opts) -{ - return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts); -} diff --git a/src/revert.c b/src/revert.c deleted file mode 100644 index d6ab6ae3c..000000000 --- a/src/revert.c +++ /dev/null @@ -1,243 +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 "common.h" - -#include "repository.h" -#include "filebuf.h" -#include "merge.h" -#include "index.h" - -#include "git2/types.h" -#include "git2/merge.h" -#include "git2/revert.h" -#include "git2/commit.h" -#include "git2/sys/commit.h" - -#define GIT_REVERT_FILE_MODE 0666 - -static int write_revert_head( - git_repository *repo, - const char *commit_oidstr) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 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); - - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int write_merge_msg( - git_repository *repo, - const char *commit_oidstr, - const char *commit_msgline) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 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; - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int revert_normalize_opts( - git_repository *repo, - git_revert_options *opts, - const git_revert_options *given, - const char *their_label) -{ - int error = 0; - unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; - - GIT_UNUSED(repo); - - if (given != NULL) - memcpy(opts, given, sizeof(git_revert_options)); - else { - git_revert_options default_opts = GIT_REVERT_OPTIONS_INIT; - memcpy(opts, &default_opts, sizeof(git_revert_options)); - } - - if (!opts->checkout_opts.checkout_strategy) - opts->checkout_opts.checkout_strategy = default_checkout_strategy; - - if (!opts->checkout_opts.our_label) - opts->checkout_opts.our_label = "HEAD"; - - if (!opts->checkout_opts.their_label) - opts->checkout_opts.their_label = their_label; - - return error; -} - -static int revert_state_cleanup(git_repository *repo) -{ - const char *state_files[] = { GIT_REVERT_HEAD_FILE, GIT_MERGE_MSG_FILE }; - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -static int revert_seterr(git_commit *commit, const char *fmt) -{ - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(commit_oidstr, git_commit_id(commit)); - commit_oidstr[GIT_OID_HEXSZ] = '\0'; - - git_error_set(GIT_ERROR_REVERT, fmt, commit_oidstr); - - return -1; -} - -int git_revert_commit( - git_index **out, - git_repository *repo, - git_commit *revert_commit, - git_commit *our_commit, - unsigned int mainline, - const git_merge_options *merge_opts) -{ - git_commit *parent_commit = NULL; - git_tree *parent_tree = NULL, *our_tree = NULL, *revert_tree = NULL; - int parent = 0, error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(revert_commit); - GIT_ASSERT_ARG(our_commit); - - if (git_commit_parentcount(revert_commit) > 1) { - if (!mainline) - return revert_seterr(revert_commit, - "mainline branch is not specified but %s is a merge commit"); - - parent = mainline; - } else { - if (mainline) - return revert_seterr(revert_commit, - "mainline branch specified but %s is not a merge commit"); - - parent = git_commit_parentcount(revert_commit); - } - - if (parent && - ((error = git_commit_parent(&parent_commit, revert_commit, (parent - 1))) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) - goto done; - - if ((error = git_commit_tree(&revert_tree, revert_commit)) < 0 || - (error = git_commit_tree(&our_tree, our_commit)) < 0) - goto done; - - error = git_merge_trees(out, repo, revert_tree, our_tree, parent_tree, merge_opts); - -done: - git_tree_free(parent_tree); - git_tree_free(our_tree); - git_tree_free(revert_tree); - git_commit_free(parent_commit); - - return error; -} - -int git_revert( - git_repository *repo, - git_commit *commit, - const git_revert_options *given_opts) -{ - git_revert_options opts; - git_reference *our_ref = NULL; - git_commit *our_commit = NULL; - char commit_oidstr[GIT_OID_HEXSZ + 1]; - const char *commit_msg; - git_str their_label = GIT_STR_INIT; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(commit); - - GIT_ERROR_CHECK_VERSION(given_opts, GIT_REVERT_OPTIONS_VERSION, "git_revert_options"); - - if ((error = git_repository__ensure_not_bare(repo, "revert")) < 0) - return error; - - git_oid_fmt(commit_oidstr, git_commit_id(commit)); - commit_oidstr[GIT_OID_HEXSZ] = '\0'; - - if ((commit_msg = git_commit_summary(commit)) == NULL) { - error = -1; - goto on_error; - } - - if ((error = git_str_printf(&their_label, "parent of %.7s... %s", commit_oidstr, commit_msg)) < 0 || - (error = revert_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || - (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || - (error = write_revert_head(repo, commit_oidstr)) < 0 || - (error = write_merge_msg(repo, commit_oidstr, commit_msg)) < 0 || - (error = git_repository_head(&our_ref, repo)) < 0 || - (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || - (error = git_revert_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || - (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || - (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto on_error; - - goto done; - -on_error: - revert_state_cleanup(repo); - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_commit_free(our_commit); - git_reference_free(our_ref); - git_str_dispose(&their_label); - - return error; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_revert_init_options(git_revert_options *opts, unsigned int version) -{ - return git_revert_options_init(opts, version); -} -#endif diff --git a/src/revparse.c b/src/revparse.c deleted file mode 100644 index 9bc28e9fc..000000000 --- a/src/revparse.c +++ /dev/null @@ -1,949 +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 "common.h" - -#include "str.h" -#include "tree.h" -#include "refdb.h" -#include "regexp.h" -#include "date.h" - -#include "git2.h" - -static int maybe_sha_or_abbrev(git_object **out, git_repository *repo, const char *spec, size_t speclen) -{ - git_oid oid; - - if (git_oid_fromstrn(&oid, spec, speclen) < 0) - return GIT_ENOTFOUND; - - return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJECT_ANY); -} - -static int maybe_sha(git_object **out, git_repository *repo, const char *spec) -{ - size_t speclen = strlen(spec); - - if (speclen != GIT_OID_HEXSZ) - return GIT_ENOTFOUND; - - return maybe_sha_or_abbrev(out, repo, spec, speclen); -} - -static int maybe_abbrev(git_object **out, git_repository *repo, const char *spec) -{ - size_t speclen = strlen(spec); - - return maybe_sha_or_abbrev(out, repo, spec, speclen); -} - -static int build_regex(git_regexp *regex, const char *pattern) -{ - int error; - - if (*pattern == '\0') { - git_error_set(GIT_ERROR_REGEX, "empty pattern"); - return GIT_EINVALIDSPEC; - } - - error = git_regexp_compile(regex, pattern, 0); - if (!error) - return 0; - - git_regexp_dispose(regex); - - return error; -} - -static int maybe_describe(git_object**out, git_repository *repo, const char *spec) -{ - const char *substr; - int error; - git_regexp regex; - - substr = strstr(spec, "-g"); - - if (substr == NULL) - return GIT_ENOTFOUND; - - if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) - return -1; - - error = git_regexp_match(®ex, spec); - git_regexp_dispose(®ex); - - if (error) - return GIT_ENOTFOUND; - - return maybe_abbrev(out, repo, substr+2); -} - -static int revparse_lookup_object( - git_object **object_out, - git_reference **reference_out, - git_repository *repo, - const char *spec) -{ - int error; - git_reference *ref; - - if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND) - return error; - - error = git_reference_dwim(&ref, repo, spec); - if (!error) { - - error = git_object_lookup( - object_out, repo, git_reference_target(ref), GIT_OBJECT_ANY); - - if (!error) - *reference_out = ref; - - return error; - } - - if (error != GIT_ENOTFOUND) - return error; - - if ((strlen(spec) < GIT_OID_HEXSZ) && - ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) - return error; - - if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND) - return error; - - git_error_set(GIT_ERROR_REFERENCE, "revspec '%s' not found", spec); - return GIT_ENOTFOUND; -} - -static int try_parse_numeric(int *n, const char *curly_braces_content) -{ - int32_t content; - const char *end_ptr; - - if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content), - &end_ptr, 10) < 0) - return -1; - - if (*end_ptr != '\0') - return -1; - - *n = (int)content; - return 0; -} - -static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) -{ - git_reference *ref = NULL; - git_reflog *reflog = NULL; - git_regexp preg; - int error = -1; - size_t i, numentries, cur; - const git_reflog_entry *entry; - const char *msg; - git_str buf = GIT_STR_INIT; - - cur = position; - - if (*identifier != '\0' || *base_ref != NULL) - return GIT_EINVALIDSPEC; - - if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) - return -1; - - if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) - goto cleanup; - - if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) - goto cleanup; - - 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 (git_regexp_search(&preg, msg, 2, regexmatches) < 0) - continue; - - cur--; - - if (cur > 0) - continue; - - if ((git_str_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0) - goto cleanup; - - if ((error = git_reference_dwim(base_ref, repo, git_str_cstr(&buf))) == 0) - goto cleanup; - - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - error = maybe_abbrev(out, repo, git_str_cstr(&buf)); - - goto cleanup; - } - - error = GIT_ENOTFOUND; - -cleanup: - git_reference_free(ref); - git_str_dispose(&buf); - git_regexp_dispose(&preg); - git_reflog_free(reflog); - return error; -} - -static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier) -{ - git_reflog *reflog; - size_t numentries; - const git_reflog_entry *entry = NULL; - bool search_by_pos = (identifier <= 100000000); - - if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) - return -1; - - numentries = git_reflog_entrycount(reflog); - - if (search_by_pos) { - if (numentries < identifier + 1) - goto notfound; - - entry = git_reflog_entry_byindex(reflog, identifier); - git_oid_cpy(oid, git_reflog_entry_id_new(entry)); - } else { - size_t i; - git_time commit_time; - - for (i = 0; i < numentries; i++) { - entry = git_reflog_entry_byindex(reflog, i); - commit_time = git_reflog_entry_committer(entry)->when; - - if (commit_time.time > (git_time_t)identifier) - continue; - - git_oid_cpy(oid, git_reflog_entry_id_new(entry)); - break; - } - - if (i == numentries) { - if (entry == NULL) - goto notfound; - - /* - * TODO: emit a warning (log for 'branch' only goes back to ...) - */ - git_oid_cpy(oid, git_reflog_entry_id_new(entry)); - } - } - - git_reflog_free(reflog); - return 0; - -notfound: - git_error_set( - GIT_ERROR_REFERENCE, - "reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ, - git_reference_name(ref), numentries, identifier); - - git_reflog_free(reflog); - return GIT_ENOTFOUND; -} - -static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) -{ - git_reference *ref; - git_oid oid; - int error = -1; - - if (*base_ref == NULL) { - if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) - return error; - } else { - ref = *base_ref; - *base_ref = NULL; - } - - if (position == 0) { - error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJECT_ANY); - goto cleanup; - } - - if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) - goto cleanup; - - error = git_object_lookup(out, repo, &oid, GIT_OBJECT_ANY); - -cleanup: - git_reference_free(ref); - return error; -} - -static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) -{ - git_reference *tracking, *ref; - int error = -1; - - if (*base_ref == NULL) { - if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) - return error; - } else { - ref = *base_ref; - *base_ref = NULL; - } - - if (!git_reference_is_branch(ref)) { - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - if ((error = git_branch_upstream(&tracking, ref)) < 0) - goto cleanup; - - *base_ref = tracking; - -cleanup: - git_reference_free(ref); - return error; -} - -static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository *repo, const char *curly_braces_content) -{ - bool is_numeric; - int parsed = 0, error = -1; - git_str identifier = GIT_STR_INIT; - git_time_t timestamp; - - GIT_ASSERT(*out == NULL); - - if (git_str_put(&identifier, spec, identifier_len) < 0) - return -1; - - is_numeric = !try_parse_numeric(&parsed, curly_braces_content); - - if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - if (is_numeric) { - if (parsed < 0) - error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_str_cstr(&identifier), -parsed); - else - error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), parsed); - - goto cleanup; - } - - if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { - error = retrieve_remote_tracking_reference(ref, git_str_cstr(&identifier), repo); - - goto cleanup; - } - - if (git_date_parse(×tamp, curly_braces_content) < 0) { - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), (size_t)timestamp); - -cleanup: - git_str_dispose(&identifier); - return error; -} - -static git_object_t parse_obj_type(const char *str) -{ - if (!strcmp(str, "commit")) - return GIT_OBJECT_COMMIT; - - if (!strcmp(str, "tree")) - return GIT_OBJECT_TREE; - - if (!strcmp(str, "blob")) - return GIT_OBJECT_BLOB; - - if (!strcmp(str, "tag")) - return GIT_OBJECT_TAG; - - return GIT_OBJECT_INVALID; -} - -static int dereference_to_non_tag(git_object **out, git_object *obj) -{ - if (git_object_type(obj) == GIT_OBJECT_TAG) - return git_tag_peel(out, (git_tag *)obj); - - return git_object_dup(out, obj); -} - -static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) -{ - git_object *temp_commit = NULL; - int error; - - if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) - return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? - GIT_EINVALIDSPEC : error; - - if (n == 0) { - *out = temp_commit; - return 0; - } - - error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1); - - git_object_free(temp_commit); - return error; -} - -static int handle_linear_syntax(git_object **out, git_object *obj, int n) -{ - git_object *temp_commit = NULL; - int error; - - if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) - return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? - GIT_EINVALIDSPEC : error; - - error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); - - git_object_free(temp_commit); - return error; -} - -static int handle_colon_syntax( - git_object **out, - git_object *obj, - const char *path) -{ - git_object *tree; - int error = -1; - git_tree_entry *entry = NULL; - - if ((error = git_object_peel(&tree, obj, GIT_OBJECT_TREE)) < 0) - return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error; - - if (*path == '\0') { - *out = tree; - return 0; - } - - /* - * TODO: Handle the relative path syntax - * (:./relative/path and :../relative/path) - */ - if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) - goto cleanup; - - error = git_tree_entry_to_object(out, git_object_owner(tree), entry); - -cleanup: - git_tree_entry_free(entry); - git_object_free(tree); - - return error; -} - -static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex) -{ - int error; - git_oid oid; - git_object *obj; - - while (!(error = git_revwalk_next(&oid, walk))) { - - error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJECT_COMMIT); - if ((error < 0) && (error != GIT_ENOTFOUND)) - return -1; - - if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) { - *out = obj; - return 0; - } - - git_object_free(obj); - } - - if (error < 0 && error == GIT_ITEROVER) - error = GIT_ENOTFOUND; - - return error; -} - -static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) -{ - git_regexp preg; - git_revwalk *walk = NULL; - int error; - - if ((error = build_regex(&preg, pattern)) < 0) - return error; - - if ((error = git_revwalk_new(&walk, repo)) < 0) - goto cleanup; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - if (spec_oid == NULL) { - if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0) - goto cleanup; - } else if ((error = git_revwalk_push(walk, spec_oid)) < 0) - goto cleanup; - - error = walk_and_search(out, walk, &preg); - -cleanup: - git_regexp_dispose(&preg); - git_revwalk_free(walk); - - return error; -} - -static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content) -{ - git_object_t expected_type; - - if (*curly_braces_content == '\0') - return dereference_to_non_tag(out, obj); - - if (*curly_braces_content == '/') - return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1); - - expected_type = parse_obj_type(curly_braces_content); - - if (expected_type == GIT_OBJECT_INVALID) - return GIT_EINVALIDSPEC; - - return git_object_peel(out, obj, expected_type); -} - -static int extract_curly_braces_content(git_str *buf, const char *spec, size_t *pos) -{ - git_str_clear(buf); - - GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '@'); - - (*pos)++; - - if (spec[*pos] == '\0' || spec[*pos] != '{') - return GIT_EINVALIDSPEC; - - (*pos)++; - - while (spec[*pos] != '}') { - if (spec[*pos] == '\0') - return GIT_EINVALIDSPEC; - - if (git_str_putc(buf, spec[(*pos)++]) < 0) - return -1; - } - - (*pos)++; - - return 0; -} - -static int extract_path(git_str *buf, const char *spec, size_t *pos) -{ - git_str_clear(buf); - - GIT_ASSERT_ARG(spec[*pos] == ':'); - - (*pos)++; - - if (git_str_puts(buf, spec + *pos) < 0) - return -1; - - *pos += git_str_len(buf); - - return 0; -} - -static int extract_how_many(int *n, const char *spec, size_t *pos) -{ - const char *end_ptr; - int parsed, accumulated; - char kind = spec[*pos]; - - GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '~'); - - accumulated = 0; - - do { - do { - (*pos)++; - accumulated++; - } while (spec[(*pos)] == kind && kind == '~'); - - if (git__isdigit(spec[*pos])) { - if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0) - return GIT_EINVALIDSPEC; - - accumulated += (parsed - 1); - *pos = end_ptr - spec; - } - - } while (spec[(*pos)] == kind && kind == '~'); - - *n = accumulated; - - return 0; -} - -static int object_from_reference(git_object **object, git_reference *reference) -{ - git_reference *resolved = NULL; - int error; - - if (git_reference_resolve(&resolved, reference) < 0) - return -1; - - error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJECT_ANY); - git_reference_free(resolved); - - return error; -} - -static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier) -{ - int error; - git_str identifier = GIT_STR_INIT; - - if (*object != NULL) - return 0; - - if (*reference != NULL) - return object_from_reference(object, *reference); - - if (!allow_empty_identifier && identifier_len == 0) - return GIT_EINVALIDSPEC; - - if (git_str_put(&identifier, spec, identifier_len) < 0) - return -1; - - error = revparse_lookup_object(object, reference, repo, git_str_cstr(&identifier)); - git_str_dispose(&identifier); - - return error; -} - -static int ensure_base_rev_is_not_known_yet(git_object *object) -{ - if (object == NULL) - return 0; - - return GIT_EINVALIDSPEC; -} - -static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len) -{ - if (object != NULL) - return true; - - if (reference != NULL) - return true; - - if (identifier_len > 0) - return true; - - return false; -} - -static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference) -{ - if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL) - return 0; - - return GIT_EINVALIDSPEC; -} - -static int revparse( - git_object **object_out, - git_reference **reference_out, - size_t *identifier_len_out, - git_repository *repo, - const char *spec) -{ - size_t pos = 0, identifier_len = 0; - int error = -1, n; - git_str buf = GIT_STR_INIT; - - git_reference *reference = NULL; - git_object *base_rev = NULL; - - bool should_return_reference = true; - - GIT_ASSERT_ARG(object_out); - GIT_ASSERT_ARG(reference_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(spec); - - *object_out = NULL; - *reference_out = NULL; - - while (spec[pos]) { - switch (spec[pos]) { - case '^': - should_return_reference = false; - - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) - goto cleanup; - - if (spec[pos+1] == '{') { - git_object *temp_object = NULL; - - if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) - goto cleanup; - - if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) - goto cleanup; - - git_object_free(base_rev); - base_rev = temp_object; - } else { - git_object *temp_object = NULL; - - if ((error = extract_how_many(&n, spec, &pos)) < 0) - goto cleanup; - - if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0) - goto cleanup; - - git_object_free(base_rev); - base_rev = temp_object; - } - break; - - case '~': - { - git_object *temp_object = NULL; - - should_return_reference = false; - - if ((error = extract_how_many(&n, spec, &pos)) < 0) - goto cleanup; - - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) - goto cleanup; - - if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) - goto cleanup; - - git_object_free(base_rev); - base_rev = temp_object; - break; - } - - case ':': - { - git_object *temp_object = NULL; - - should_return_reference = false; - - if ((error = extract_path(&buf, spec, &pos)) < 0) - goto cleanup; - - if (any_left_hand_identifier(base_rev, reference, identifier_len)) { - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) - goto cleanup; - - if ((error = handle_colon_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) - goto cleanup; - } else { - if (*git_str_cstr(&buf) == '/') { - if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_str_cstr(&buf) + 1)) < 0) - goto cleanup; - } else { - - /* - * TODO: support merge-stage path lookup (":2:Makefile") - * and plain index blob lookup (:i-am/a/blob) - */ - git_error_set(GIT_ERROR_INVALID, "unimplemented"); - error = GIT_ERROR; - goto cleanup; - } - } - - git_object_free(base_rev); - base_rev = temp_object; - break; - } - - case '@': - if (spec[pos+1] == '{') { - git_object *temp_object = NULL; - - if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) - goto cleanup; - - if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0) - goto cleanup; - - if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_str_cstr(&buf))) < 0) - goto cleanup; - - if (temp_object != NULL) - base_rev = temp_object; - break; - } else if (spec[pos+1] == '\0') { - spec = "HEAD"; - break; - } - /* fall through */ - - default: - if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0) - goto cleanup; - - pos++; - identifier_len++; - } - } - - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) - goto cleanup; - - if (!should_return_reference) { - git_reference_free(reference); - reference = NULL; - } - - *object_out = base_rev; - *reference_out = reference; - *identifier_len_out = identifier_len; - error = 0; - -cleanup: - if (error) { - if (error == GIT_EINVALIDSPEC) - git_error_set(GIT_ERROR_INVALID, - "failed to parse revision specifier - Invalid pattern '%s'", spec); - - git_object_free(base_rev); - git_reference_free(reference); - } - - git_str_dispose(&buf); - return error; -} - -int git_revparse_ext( - git_object **object_out, - git_reference **reference_out, - git_repository *repo, - const char *spec) -{ - int error; - size_t identifier_len; - git_object *obj = NULL; - git_reference *ref = NULL; - - if ((error = revparse(&obj, &ref, &identifier_len, repo, spec)) < 0) - goto cleanup; - - *object_out = obj; - *reference_out = ref; - GIT_UNUSED(identifier_len); - - return 0; - -cleanup: - git_object_free(obj); - git_reference_free(ref); - return error; -} - -int git_revparse_single(git_object **out, git_repository *repo, const char *spec) -{ - int error; - git_object *obj = NULL; - git_reference *ref = NULL; - - *out = NULL; - - if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0) - goto cleanup; - - git_reference_free(ref); - - *out = obj; - - return 0; - -cleanup: - git_object_free(obj); - git_reference_free(ref); - return error; -} - -int git_revparse( - git_revspec *revspec, - git_repository *repo, - const char *spec) -{ - const char *dotdot; - int error = 0; - - GIT_ASSERT_ARG(revspec); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(spec); - - memset(revspec, 0x0, sizeof(*revspec)); - - if ((dotdot = strstr(spec, "..")) != NULL) { - char *lstr; - const char *rstr; - revspec->flags = GIT_REVSPEC_RANGE; - - /* - * Following git.git, don't allow '..' because it makes command line - * arguments which can be either paths or revisions ambiguous when the - * path is almost certainly intended. The empty range '...' is still - * allowed. - */ - if (!git__strcmp(spec, "..")) { - git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); - return GIT_EINVALIDSPEC; - } - - lstr = git__substrdup(spec, dotdot - spec); - rstr = dotdot + 2; - if (dotdot[2] == '.') { - revspec->flags |= GIT_REVSPEC_MERGE_BASE; - rstr++; - } - - error = git_revparse_single( - &revspec->from, - repo, - *lstr == '\0' ? "HEAD" : lstr); - - if (!error) { - error = git_revparse_single( - &revspec->to, - repo, - *rstr == '\0' ? "HEAD" : rstr); - } - - git__free((void*)lstr); - } else { - revspec->flags = GIT_REVSPEC_SINGLE; - error = git_revparse_single(&revspec->from, repo, spec); - } - - return error; -} diff --git a/src/revwalk.c b/src/revwalk.c deleted file mode 100644 index 553e0497a..000000000 --- a/src/revwalk.c +++ /dev/null @@ -1,820 +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 "revwalk.h" - -#include "commit.h" -#include "odb.h" -#include "pool.h" - -#include "git2/revparse.h" -#include "merge.h" -#include "vector.h" - -static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list); - -git_commit_list_node *git_revwalk__commit_lookup( - git_revwalk *walk, const git_oid *oid) -{ - git_commit_list_node *commit; - - /* lookup and reserve space if not already present */ - if ((commit = git_oidmap_get(walk->commits, oid)) != NULL) - return commit; - - commit = git_commit_list_alloc_node(walk); - if (commit == NULL) - return NULL; - - git_oid_cpy(&commit->oid, oid); - - if ((git_oidmap_set(walk->commits, &commit->oid, commit)) < 0) - return NULL; - - return commit; -} - -int git_revwalk__push_commit(git_revwalk *walk, const git_oid *oid, const git_revwalk__push_options *opts) -{ - git_oid commit_id; - int error; - git_object *obj, *oobj; - git_commit_list_node *commit; - git_commit_list *list; - - if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJECT_ANY)) < 0) - return error; - - error = git_object_peel(&obj, oobj, GIT_OBJECT_COMMIT); - git_object_free(oobj); - - if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) { - /* If this comes from e.g. push_glob("tags"), ignore this */ - if (opts->from_glob) - return 0; - - git_error_set(GIT_ERROR_INVALID, "object is not a committish"); - return error; - } - if (error < 0) - return error; - - git_oid_cpy(&commit_id, git_object_id(obj)); - git_object_free(obj); - - commit = git_revwalk__commit_lookup(walk, &commit_id); - if (commit == NULL) - return -1; /* error already reported by failed lookup */ - - /* A previous hide already told us we don't want this commit */ - if (commit->uninteresting) - return 0; - - if (opts->uninteresting) { - walk->limited = 1; - walk->did_hide = 1; - } else { - walk->did_push = 1; - } - - commit->uninteresting = opts->uninteresting; - list = walk->user_input; - 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; - } - - walk->user_input = list; - - return 0; -} - -int git_revwalk_push(git_revwalk *walk, const git_oid *oid) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(oid); - - 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; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(oid); - - opts.uninteresting = 1; - return git_revwalk__push_commit(walk, oid, &opts); -} - -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 git_revwalk__push_commit(walk, &oid, opts); -} - -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_str buf = GIT_STR_INIT; - git_reference *ref; - git_reference_iterator *iter; - size_t wildcard; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(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_str_joinpath(&buf, GIT_REFS_DIR, glob); - else - git_str_puts(&buf, glob); - GIT_ERROR_CHECK_ALLOC_STR(&buf); - - /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ - wildcard = strcspn(glob, "?*["); - if (!glob[wildcard]) - git_str_put(&buf, "/*", 2); - - 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 = git_revwalk__push_ref(walk, git_reference_name(ref), &opts); - git_reference_free(ref); - if (error < 0) - break; - } - git_reference_iterator_free(iter); - - if (error == GIT_ITEROVER) - error = 0; -out: - git_str_dispose(&buf); - return error; -} - -int git_revwalk_push_glob(git_revwalk *walk, const char *glob) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(glob); - - 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; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(glob); - - 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; - - GIT_ASSERT_ARG(walk); - - 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; - - GIT_ASSERT_ARG(walk); - - 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; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(refname); - - 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_REVSPEC_MERGE_BASE) { - /* TODO: support "..." */ - git_error_set(GIT_ERROR_INVALID, "symmetric differences not implemented in revwalk"); - error = GIT_EINVALIDSPEC; - goto out; - } - - opts.uninteresting = 1; - if ((error = git_revwalk__push_commit(walk, git_object_id(revspec.from), &opts))) - goto out; - - opts.uninteresting = 0; - error = git_revwalk__push_commit(walk, git_object_id(revspec.to), &opts); - -out: - git_object_free(revspec.from); - git_object_free(revspec.to); - return error; -} - -int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(refname); - - opts.uninteresting = 1; - return git_revwalk__push_ref(walk, refname, &opts); -} - -static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) -{ - return git_pqueue_insert(&walk->iterator_time, commit); -} - -static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit) -{ - return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; -} - -static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk) -{ - git_commit_list_node *next; - - while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { - /* Some commits might become uninteresting after being added to the list */ - if (!next->uninteresting) { - *object_out = next; - return 0; - } - } - - git_error_clear(); - return GIT_ITEROVER; -} - -static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk) -{ - int error; - git_commit_list_node *next; - - while (!(error = get_revision(&next, walk, &walk->iterator_rand))) { - /* Some commits might become uninteresting after being added to the list */ - if (!next->uninteresting) { - *object_out = next; - return 0; - } - } - - return error; -} - -static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) -{ - int error; - git_commit_list_node *next; - - while (!(error = get_revision(&next, walk, &walk->iterator_topo))) { - /* Some commits might become uninteresting after being added to the list */ - if (!next->uninteresting) { - *object_out = next; - return 0; - } - } - - return error; -} - -static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk) -{ - *object_out = git_commit_list_pop(&walk->iterator_reverse); - return *object_out ? 0 : GIT_ITEROVER; -} - -static void mark_parents_uninteresting(git_commit_list_node *commit) -{ - unsigned short i; - git_commit_list *parents = NULL; - - for (i = 0; i < commit->out_degree; i++) - git_commit_list_insert(commit->parents[i], &parents); - - - while (parents) { - commit = git_commit_list_pop(&parents); - - while (commit) { - if (commit->uninteresting) - break; - - commit->uninteresting = 1; - /* - * If we've reached this commit some other way - * already, we need to mark its parents uninteresting - * as well. - */ - if (!commit->parents) - break; - - for (i = 0; i < commit->out_degree; i++) - git_commit_list_insert(commit->parents[i], &parents); - commit = commit->parents[0]; - } - } -} - -static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list) -{ - unsigned short i; - int error; - - if (commit->added) - return 0; - - commit->added = 1; - - /* - * Go full on in the uninteresting case as we want to include - * as many of these as we can. - * - * Usually we haven't parsed the parent of a parent, but if we - * have it we reached it via other means so we want to mark - * its parents recursively too. - */ - if (commit->uninteresting) { - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - p->uninteresting = 1; - - /* git does it gently here, but we don't like missing objects */ - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - - if (p->parents) - mark_parents_uninteresting(p); - - p->seen = 1; - git_commit_list_insert_by_date(p, list); - } - - return 0; - } - - /* - * Now on to what we do if the commit is indeed - * interesting. Here we do want things like first-parent take - * effect as this is what we'll be showing. - */ - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - - if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload)) - continue; - - if (!p->seen) { - p->seen = 1; - git_commit_list_insert_by_date(p, list); - } - - if (walk->first_parent) - break; - } - return 0; -} - -/* How many uninteresting commits we want to look at after we run out of interesting ones */ -#define SLOP 5 - -static int still_interesting(git_commit_list *list, int64_t time, int slop) -{ - /* The empty list is pretty boring */ - if (!list) - return 0; - - /* - * If the destination list has commits with an earlier date than our - * source, we want to reset the slop counter as we're not done. - */ - if (time <= list->item->time) - return SLOP; - - for (; list; list = list->next) { - /* - * If the destination list still contains interesting commits we - * want to continue looking. - */ - if (!list->item->uninteresting || list->item->time > time) - return SLOP; - } - - /* Everything's uninteresting, reduce the count */ - return slop - 1; -} - -static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits) -{ - int error, slop = SLOP; - int64_t time = INT64_MAX; - git_commit_list *list = commits; - git_commit_list *newlist = NULL; - git_commit_list **p = &newlist; - - while (list) { - git_commit_list_node *commit = git_commit_list_pop(&list); - - if ((error = add_parents_to_list(walk, commit, &list)) < 0) - return error; - - if (commit->uninteresting) { - mark_parents_uninteresting(commit); - - slop = still_interesting(list, time, slop); - if (slop) - continue; - - break; - } - - if (walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload)) - continue; - - time = commit->time; - p = &git_commit_list_insert(commit, p)->next; - } - - git_commit_list_free(&list); - *out = newlist; - return 0; -} - -static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list) -{ - int error; - git_commit_list_node *commit; - - commit = git_commit_list_pop(list); - if (!commit) { - git_error_clear(); - return GIT_ITEROVER; - } - - /* - * If we did not run limit_list and we must add parents to the - * list ourselves. - */ - if (!walk->limited) { - if ((error = add_parents_to_list(walk, commit, list)) < 0) - return error; - } - - *out = commit; - return 0; -} - -static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list) -{ - git_commit_list *ll = NULL, *newlist, **pptr; - git_commit_list_node *next; - git_pqueue queue; - git_vector_cmp queue_cmp = NULL; - unsigned short i; - int error; - - if (walk->sorting & GIT_SORT_TIME) - queue_cmp = git_commit_list_time_cmp; - - if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp))) - return error; - - /* - * Start by resetting the in-degree to 1 for the commits in - * our list. We want to go through this list again, so we - * store it in the commit list as we extract it from the lower - * machinery. - */ - for (ll = list; ll; ll = ll->next) { - ll->item->in_degree = 1; - } - - /* - * Count up how many children each commit has. We limit - * ourselves to those commits in the original list (in-degree - * of 1) avoiding setting it for any parent that was hidden. - */ - for(ll = list; ll; ll = ll->next) { - for (i = 0; i < ll->item->out_degree; ++i) { - git_commit_list_node *parent = ll->item->parents[i]; - if (parent->in_degree) - parent->in_degree++; - } - } - - /* - * Now we find the tips i.e. those not reachable from any other node - * i.e. those which still have an in-degree of 1. - */ - for(ll = list; ll; ll = ll->next) { - if (ll->item->in_degree == 1) { - if ((error = git_pqueue_insert(&queue, ll->item))) - goto cleanup; - } - } - - /* - * We need to output the tips in the order that they came out of the - * traversal, so if we're not doing time-sorting, we need to reverse the - * pqueue in order to get them to come out as we inserted them. - */ - if ((walk->sorting & GIT_SORT_TIME) == 0) - git_pqueue_reverse(&queue); - - - pptr = &newlist; - newlist = NULL; - while ((next = git_pqueue_pop(&queue)) != NULL) { - for (i = 0; i < next->out_degree; ++i) { - git_commit_list_node *parent = next->parents[i]; - if (parent->in_degree == 0) - continue; - - if (--parent->in_degree == 1) { - if ((error = git_pqueue_insert(&queue, parent))) - goto cleanup; - } - } - - /* All the children of 'item' have been emitted (since we got to it via the priority queue) */ - next->in_degree = 0; - - pptr = &git_commit_list_insert(next, pptr)->next; - } - - *out = newlist; - error = 0; - -cleanup: - git_pqueue_free(&queue); - return error; -} - -static int prepare_walk(git_revwalk *walk) -{ - int error = 0; - git_commit_list *list, *commits = NULL; - git_commit_list_node *next; - - /* If there were no pushes, we know that the walk is already over */ - if (!walk->did_push) { - git_error_clear(); - return GIT_ITEROVER; - } - - for (list = walk->user_input; list; list = list->next) { - git_commit_list_node *commit = list->item; - if ((error = git_commit_list_parse(walk, commit)) < 0) - return error; - - if (commit->uninteresting) - mark_parents_uninteresting(commit); - - if (!commit->seen) { - commit->seen = 1; - git_commit_list_insert(commit, &commits); - } - } - - if (walk->limited && (error = limit_list(&commits, walk, commits)) < 0) - return error; - - if (walk->sorting & GIT_SORT_TOPOLOGICAL) { - error = sort_in_topological_order(&walk->iterator_topo, walk, commits); - git_commit_list_free(&commits); - - if (error < 0) - return error; - - walk->get_next = &revwalk_next_toposort; - } else if (walk->sorting & GIT_SORT_TIME) { - for (list = commits; list && !error; list = list->next) - error = walk->enqueue(walk, list->item); - - git_commit_list_free(&commits); - - if (error < 0) - return error; - } else { - walk->iterator_rand = commits; - walk->get_next = revwalk_next_unsorted; - } - - if (walk->sorting & GIT_SORT_REVERSE) { - - while ((error = walk->get_next(&next, walk)) == 0) - if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL) - return -1; - - if (error != GIT_ITEROVER) - return error; - - walk->get_next = &revwalk_next_reverse; - } - - walk->walking = 1; - return 0; -} - - -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); - - if (git_oidmap_new(&walk->commits) < 0 || - git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || - git_pool_init(&walk->commit_pool, COMMIT_ALLOC) < 0) - return -1; - - walk->get_next = &revwalk_next_unsorted; - walk->enqueue = &revwalk_enqueue_unsorted; - - walk->repo = repo; - - if (git_repository_odb(&walk->odb, repo) < 0) { - git_revwalk_free(walk); - return -1; - } - - *revwalk_out = walk; - return 0; -} - -void git_revwalk_free(git_revwalk *walk) -{ - if (walk == NULL) - return; - - git_revwalk_reset(walk); - git_odb_free(walk->odb); - - git_oidmap_free(walk->commits); - git_pool_clear(&walk->commit_pool); - git_pqueue_free(&walk->iterator_time); - git__free(walk); -} - -git_repository *git_revwalk_repository(git_revwalk *walk) -{ - GIT_ASSERT_ARG_WITH_RETVAL(walk, NULL); - - return walk->repo; -} - -int git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) -{ - GIT_ASSERT_ARG(walk); - - if (walk->walking) - git_revwalk_reset(walk); - - walk->sorting = sort_mode; - - if (walk->sorting & GIT_SORT_TIME) { - walk->get_next = &revwalk_next_timesort; - walk->enqueue = &revwalk_enqueue_timesort; - } else { - walk->get_next = &revwalk_next_unsorted; - walk->enqueue = &revwalk_enqueue_unsorted; - } - - if (walk->sorting != GIT_SORT_NONE) - walk->limited = 1; - - return 0; -} - -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) -{ - int error; - git_commit_list_node *next; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(oid); - - if (!walk->walking) { - if ((error = prepare_walk(walk)) < 0) - return error; - } - - error = walk->get_next(&next, walk); - - if (error == GIT_ITEROVER) { - git_revwalk_reset(walk); - git_error_clear(); - return GIT_ITEROVER; - } - - if (!error) - git_oid_cpy(oid, &next->oid); - - return error; -} - -int git_revwalk_reset(git_revwalk *walk) -{ - git_commit_list_node *commit; - - GIT_ASSERT_ARG(walk); - - git_oidmap_foreach_value(walk->commits, commit, { - commit->seen = 0; - commit->in_degree = 0; - commit->topo_delay = 0; - commit->uninteresting = 0; - commit->added = 0; - commit->flags = 0; - }); - - git_pqueue_clear(&walk->iterator_time); - git_commit_list_free(&walk->iterator_topo); - git_commit_list_free(&walk->iterator_rand); - git_commit_list_free(&walk->iterator_reverse); - git_commit_list_free(&walk->user_input); - walk->first_parent = 0; - walk->walking = 0; - walk->limited = 0; - walk->did_push = walk->did_hide = 0; - walk->sorting = GIT_SORT_NONE; - - return 0; -} - -int git_revwalk_add_hide_cb( - git_revwalk *walk, - git_revwalk_hide_cb hide_cb, - void *payload) -{ - GIT_ASSERT_ARG(walk); - - if (walk->walking) - git_revwalk_reset(walk); - - walk->hide_cb = hide_cb; - walk->hide_cb_payload = payload; - - if (hide_cb) - walk->limited = 1; - - return 0; -} - diff --git a/src/revwalk.h b/src/revwalk.h deleted file mode 100644 index 94b8a6fb1..000000000 --- a/src/revwalk.h +++ /dev/null @@ -1,73 +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_revwalk_h__ -#define INCLUDE_revwalk_h__ - -#include "common.h" - -#include "git2/revwalk.h" -#include "oidmap.h" -#include "commit_list.h" -#include "pqueue.h" -#include "pool.h" -#include "vector.h" - -#include "oidmap.h" - -struct git_revwalk { - git_repository *repo; - git_odb *odb; - - git_oidmap *commits; - git_pool commit_pool; - - git_commit_list *iterator_topo; - git_commit_list *iterator_rand; - git_commit_list *iterator_reverse; - git_pqueue iterator_time; - - int (*get_next)(git_commit_list_node **, git_revwalk *); - int (*enqueue)(git_revwalk *, git_commit_list_node *); - - unsigned walking:1, - first_parent: 1, - did_hide: 1, - did_push: 1, - limited: 1; - unsigned int sorting; - - /* the pushes and hides */ - git_commit_list *user_input; - - /* hide callback */ - git_revwalk_hide_cb hide_cb; - void *hide_cb_payload; -}; - -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/runtime.c b/src/runtime.c deleted file mode 100644 index c05dee8b9..000000000 --- a/src/runtime.c +++ /dev/null @@ -1,162 +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 "common.h" -#include "runtime.h" - -static git_runtime_shutdown_fn shutdown_callback[32]; -static git_atomic32 shutdown_callback_count; - -static git_atomic32 init_count; - -static int init_common(git_runtime_init_fn init_fns[], size_t cnt) -{ - size_t i; - int ret; - - /* Initialize subsystems that have global state */ - for (i = 0; i < cnt; i++) { - if ((ret = init_fns[i]()) != 0) - break; - } - - GIT_MEMORY_BARRIER; - - return ret; -} - -static void shutdown_common(void) -{ - git_runtime_shutdown_fn cb; - int pos; - - for (pos = git_atomic32_get(&shutdown_callback_count); - pos > 0; - pos = git_atomic32_dec(&shutdown_callback_count)) { - cb = git_atomic_swap(shutdown_callback[pos - 1], NULL); - - if (cb != NULL) - cb(); - } -} - -int git_runtime_shutdown_register(git_runtime_shutdown_fn callback) -{ - int count = git_atomic32_inc(&shutdown_callback_count); - - if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) { - git_error_set(GIT_ERROR_INVALID, - "too many shutdown callbacks registered"); - git_atomic32_dec(&shutdown_callback_count); - return -1; - } - - shutdown_callback[count - 1] = callback; - - return 0; -} - -#if defined(GIT_THREADS) && defined(GIT_WIN32) - -/* - * On Win32, we use a spinlock to provide locking semantics. This is - * lighter-weight than a proper critical section. - */ -static volatile LONG init_spinlock = 0; - -GIT_INLINE(int) init_lock(void) -{ - while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); } - return 0; -} - -GIT_INLINE(int) init_unlock(void) -{ - InterlockedExchange(&init_spinlock, 0); - return 0; -} - -#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) - -/* - * On POSIX, we need to use a proper mutex for locking. We might prefer - * a spinlock here, too, but there's no static initializer for a - * pthread_spinlock_t. - */ -static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; - -GIT_INLINE(int) init_lock(void) -{ - return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1; -} - -GIT_INLINE(int) init_unlock(void) -{ - return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1; -} - -#elif defined(GIT_THREADS) -# error unknown threading model -#else - -# define init_lock() git__noop() -# define init_unlock() git__noop() - -#endif - -int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt) -{ - int ret; - - if (init_lock() < 0) - return -1; - - /* Only do work on a 0 -> 1 transition of the refcount */ - if ((ret = git_atomic32_inc(&init_count)) == 1) { - if (init_common(init_fns, cnt) < 0) - ret = -1; - } - - if (init_unlock() < 0) - return -1; - - return ret; -} - -int git_runtime_init_count(void) -{ - int ret; - - if (init_lock() < 0) - return -1; - - ret = git_atomic32_get(&init_count); - - if (init_unlock() < 0) - return -1; - - return ret; -} - -int git_runtime_shutdown(void) -{ - int ret; - - /* Enter the lock */ - if (init_lock() < 0) - return -1; - - /* Only do work on a 1 -> 0 transition of the refcount */ - if ((ret = git_atomic32_dec(&init_count)) == 0) - shutdown_common(); - - /* Exit the lock */ - if (init_unlock() < 0) - return -1; - - return ret; -} diff --git a/src/runtime.h b/src/runtime.h deleted file mode 100644 index 24ac58ee9..000000000 --- a/src/runtime.h +++ /dev/null @@ -1,62 +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_runtime_h__ -#define INCLUDE_runtime_h__ - -#include "common.h" - -typedef int (*git_runtime_init_fn)(void); -typedef void (*git_runtime_shutdown_fn)(void); - -/** - * Start up a new runtime. If this is the first time that this - * function is called within the context of the current library - * or executable, then the given `init_fns` will be invoked. If - * it is not the first time, they will be ignored. - * - * The given initialization functions _may_ register shutdown - * handlers using `git_runtime_shutdown_register` to be notified - * when the runtime is shutdown. - * - * @param init_fns The list of initialization functions to call - * @param cnt The number of init_fns - * @return The number of initializations performed (including this one) or an error - */ -int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt); - -/* - * Returns the number of initializations active (the number of calls to - * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`). - * If 0, the runtime is not currently initialized. - * - * @return The number of initializations performed or an error - */ -int git_runtime_init_count(void); - -/** - * Shut down the runtime. If this is the last shutdown call, - * such that there are no remaining `init` calls, then any - * shutdown hooks that have been registered will be invoked. - * - * The number of outstanding initializations will be returned. - * If this number is 0, then the runtime is shutdown. - * - * @return The number of outstanding initializations (after this one) or an error - */ -int git_runtime_shutdown(void); - -/** - * Register a shutdown handler for this runtime. This should be done - * by a function invoked by `git_runtime_init` to ensure that the - * appropriate locks are taken. - * - * @param callback The shutdown handler callback - * @return 0 or an error code - */ -int git_runtime_shutdown_register(git_runtime_shutdown_fn callback); - -#endif diff --git a/src/settings.h b/src/settings.h deleted file mode 100644 index dc42ce939..000000000 --- a/src/settings.h +++ /dev/null @@ -1,11 +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. - */ - -extern int git_settings_global_init(void); - -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); diff --git a/src/signature.c b/src/signature.c deleted file mode 100644 index 5d6ab572c..000000000 --- a/src/signature.c +++ /dev/null @@ -1,339 +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 "signature.h" - -#include "repository.h" -#include "git2/common.h" -#include "posix.h" - -void git_signature_free(git_signature *sig) -{ - if (sig == NULL) - return; - - git__free(sig->name); - sig->name = NULL; - git__free(sig->email); - sig->email = NULL; - git__free(sig); -} - -static int signature_parse_error(const char *msg) -{ - git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); - return GIT_EINVALID; -} - -static int signature_error(const char *msg) -{ - git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); - return -1; -} - -static bool contains_angle_brackets(const char *input) -{ - return strchr(input, '<') != NULL || strchr(input, '>') != NULL; -} - -static bool is_crud(unsigned char c) -{ - return c <= 32 || - c == '.' || - c == ',' || - c == ':' || - c == ';' || - c == '<' || - c == '>' || - c == '"' || - c == '\\' || - c == '\''; -} - -static char *extract_trimmed(const char *ptr, size_t len) -{ - while (len && is_crud((unsigned char)ptr[0])) { - ptr++; len--; - } - - while (len && is_crud((unsigned char)ptr[len - 1])) { - len--; - } - - return git__substrdup(ptr, len); -} - -int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) -{ - git_signature *p = NULL; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(email); - - *sig_out = NULL; - - if (contains_angle_brackets(name) || - contains_angle_brackets(email)) { - return signature_error( - "Neither `name` nor `email` should contain angle brackets chars."); - } - - p = git__calloc(1, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(p); - - p->name = extract_trimmed(name, strlen(name)); - GIT_ERROR_CHECK_ALLOC(p->name); - p->email = extract_trimmed(email, strlen(email)); - GIT_ERROR_CHECK_ALLOC(p->email); - - if (p->name[0] == '\0' || p->email[0] == '\0') { - git_signature_free(p); - return signature_error("Signature cannot have an empty name or email"); - } - - p->when.time = time; - p->when.offset = offset; - p->when.sign = (offset < 0) ? '-' : '+'; - - *sig_out = p; - return 0; -} - -int git_signature_dup(git_signature **dest, const git_signature *source) -{ - git_signature *signature; - - if (source == NULL) - return 0; - - signature = git__calloc(1, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(signature); - - signature->name = git__strdup(source->name); - GIT_ERROR_CHECK_ALLOC(signature->name); - - signature->email = git__strdup(source->email); - GIT_ERROR_CHECK_ALLOC(signature->email); - - signature->when.time = source->when.time; - signature->when.offset = source->when.offset; - signature->when.sign = source->when.sign; - - *dest = signature; - - return 0; -} - -int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool) -{ - git_signature *signature; - - if (source == NULL) - return 0; - - signature = git_pool_mallocz(pool, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(signature); - - signature->name = git_pool_strdup(pool, source->name); - GIT_ERROR_CHECK_ALLOC(signature->name); - - signature->email = git_pool_strdup(pool, source->email); - GIT_ERROR_CHECK_ALLOC(signature->email); - - signature->when.time = source->when.time; - signature->when.offset = source->when.offset; - signature->when.sign = source->when.sign; - - *dest = signature; - - return 0; -} - -int git_signature_now(git_signature **sig_out, const char *name, const char *email) -{ - time_t now; - time_t offset; - struct tm *utc_tm; - git_signature *sig; - struct tm _utc; - - *sig_out = NULL; - - /* - * Get the current time as seconds since the epoch and - * transform that into a tm struct containing the time at - * UTC. Give that to mktime which considers it a local time - * (tm_isdst = -1 asks it to take DST into account) and gives - * us that time as seconds since the epoch. The difference - * between its return value and 'now' is our offset to UTC. - */ - time(&now); - utc_tm = p_gmtime_r(&now, &_utc); - utc_tm->tm_isdst = -1; - offset = (time_t)difftime(now, mktime(utc_tm)); - offset /= 60; - - if (git_signature_new(&sig, name, email, now, (int)offset) < 0) - return -1; - - *sig_out = sig; - - return 0; -} - -int git_signature_default(git_signature **out, git_repository *repo) -{ - int error; - git_config *cfg; - const char *user_name, *user_email; - - if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) - return error; - - if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && - !(error = git_config_get_string(&user_email, cfg, "user.email"))) - error = git_signature_now(out, user_name, user_email); - - git_config_free(cfg); - return error; -} - -int git_signature__parse(git_signature *sig, const char **buffer_out, - const char *buffer_end, const char *header, char ender) -{ - const char *buffer = *buffer_out; - const char *email_start, *email_end; - - memset(sig, 0, sizeof(git_signature)); - - if (ender && - (buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) - return signature_parse_error("no newline given"); - - if (header) { - const size_t header_len = strlen(header); - - if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0) - return signature_parse_error("expected prefix doesn't match actual"); - - buffer += header_len; - } - - email_start = git__memrchr(buffer, '<', buffer_end - buffer); - email_end = git__memrchr(buffer, '>', buffer_end - buffer); - - if (!email_start || !email_end || email_end <= email_start) - return signature_parse_error("malformed e-mail"); - - email_start += 1; - sig->name = extract_trimmed(buffer, email_start - buffer - 1); - sig->email = extract_trimmed(email_start, email_end - email_start); - - /* Do we even have a time at the end of the signature? */ - if (email_end + 2 < buffer_end) { - const char *time_start = email_end + 2; - const char *time_end; - - if (git__strntol64(&sig->when.time, time_start, - buffer_end - time_start, &time_end, 10) < 0) { - git__free(sig->name); - git__free(sig->email); - sig->name = sig->email = NULL; - return signature_parse_error("invalid Unix timestamp"); - } - - /* do we have a timezone? */ - if (time_end + 1 < buffer_end) { - int offset, hours, mins; - const char *tz_start, *tz_end; - - tz_start = time_end + 1; - - if ((tz_start[0] != '-' && tz_start[0] != '+') || - git__strntol32(&offset, tz_start + 1, - buffer_end - tz_start - 1, &tz_end, 10) < 0) { - /* malformed timezone, just assume it's zero */ - offset = 0; - } - - hours = offset / 100; - mins = offset % 100; - - /* - * only store timezone if it's not overflowing; - * see http://www.worldtimezone.com/faq.html - */ - if (hours <= 14 && mins <= 59) { - sig->when.offset = (hours * 60) + mins; - sig->when.sign = tz_start[0]; - if (tz_start[0] == '-') - sig->when.offset = -sig->when.offset; - } - } - } - - *buffer_out = buffer_end + 1; - return 0; -} - -int git_signature_from_buffer(git_signature **out, const char *buf) -{ - git_signature *sig; - const char *buf_end; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(buf); - - *out = NULL; - - sig = git__calloc(1, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(sig); - - buf_end = buf + strlen(buf); - error = git_signature__parse(sig, &buf, buf_end, NULL, '\0'); - - if (error) - git__free(sig); - else - *out = sig; - - return error; -} - -void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig) -{ - int offset, hours, mins; - char sign; - - offset = sig->when.offset; - sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+'; - - if (offset < 0) - offset = -offset; - - hours = offset / 60; - mins = offset % 60; - - git_str_printf(buf, "%s%s <%s> %u %c%02d%02d\n", - header ? header : "", sig->name, sig->email, - (unsigned)sig->when.time, sign, hours, mins); -} - -bool git_signature__equal(const git_signature *one, const git_signature *two) -{ - GIT_ASSERT_ARG(one); - GIT_ASSERT_ARG(two); - - return - git__strcmp(one->name, two->name) == 0 && - git__strcmp(one->email, two->email) == 0 && - one->when.time == two->when.time && - one->when.offset == two->when.offset && - one->when.sign == two->when.sign; -} - diff --git a/src/signature.h b/src/signature.h deleted file mode 100644 index 5c8270954..000000000 --- a/src/signature.h +++ /dev/null @@ -1,23 +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_signature_h__ -#define INCLUDE_signature_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/signature.h" -#include "repository.h" -#include - -int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); -void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig); -bool git_signature__equal(const git_signature *one, const git_signature *two); - -int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); - -#endif diff --git a/src/sortedcache.c b/src/sortedcache.c deleted file mode 100644 index 7ff900efe..000000000 --- a/src/sortedcache.c +++ /dev/null @@ -1,380 +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 "sortedcache.h" - -int git_sortedcache_new( - git_sortedcache **out, - size_t item_path_offset, - git_sortedcache_free_item_fn free_item, - void *free_item_payload, - git_vector_cmp item_cmp, - const char *path) -{ - git_sortedcache *sc; - size_t pathlen, alloclen; - - pathlen = path ? strlen(path) : 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - sc = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(sc); - - if (git_pool_init(&sc->pool, 1) < 0 || - git_vector_init(&sc->items, 4, item_cmp) < 0 || - git_strmap_new(&sc->map) < 0) - goto fail; - - if (git_rwlock_init(&sc->lock)) { - git_error_set(GIT_ERROR_OS, "failed to initialize lock"); - goto fail; - } - - sc->item_path_offset = item_path_offset; - sc->free_item = free_item; - sc->free_item_payload = free_item_payload; - GIT_REFCOUNT_INC(sc); - if (pathlen) - memcpy(sc->path, path, pathlen); - - *out = sc; - return 0; - -fail: - git_strmap_free(sc->map); - git_vector_free(&sc->items); - git_pool_clear(&sc->pool); - git__free(sc); - return -1; -} - -void git_sortedcache_incref(git_sortedcache *sc) -{ - GIT_REFCOUNT_INC(sc); -} - -const char *git_sortedcache_path(git_sortedcache *sc) -{ - return sc->path; -} - -static void sortedcache_clear(git_sortedcache *sc) -{ - git_strmap_clear(sc->map); - - if (sc->free_item) { - size_t i; - void *item; - - git_vector_foreach(&sc->items, i, item) { - sc->free_item(sc->free_item_payload, item); - } - } - - git_vector_clear(&sc->items); - - git_pool_clear(&sc->pool); -} - -static void sortedcache_free(git_sortedcache *sc) -{ - /* acquire write lock to make sure everyone else is done */ - if (git_sortedcache_wlock(sc) < 0) - return; - - sortedcache_clear(sc); - git_vector_free(&sc->items); - git_strmap_free(sc->map); - - git_sortedcache_wunlock(sc); - - git_rwlock_free(&sc->lock); - git__free(sc); -} - -void git_sortedcache_free(git_sortedcache *sc) -{ - if (!sc) - return; - GIT_REFCOUNT_DEC(sc, sortedcache_free); -} - -static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) -{ - git_sortedcache *sc = payload; - /* path will already have been copied by upsert */ - memcpy(tgt_item, src_item, sc->item_path_offset); - return 0; -} - -/* copy a sorted cache */ -int git_sortedcache_copy( - git_sortedcache **out, - git_sortedcache *src, - bool lock, - int (*copy_item)(void *payload, void *tgt_item, void *src_item), - void *payload) -{ - int error = 0; - git_sortedcache *tgt; - size_t i; - void *src_item, *tgt_item; - - /* just use memcpy if no special copy fn is passed in */ - if (!copy_item) { - copy_item = sortedcache_copy_item; - payload = src; - } - - if ((error = git_sortedcache_new( - &tgt, src->item_path_offset, - src->free_item, src->free_item_payload, - src->items._cmp, src->path)) < 0) - return error; - - if (lock && git_sortedcache_rlock(src) < 0) { - git_sortedcache_free(tgt); - return -1; - } - - git_vector_foreach(&src->items, i, src_item) { - char *path = ((char *)src_item) + src->item_path_offset; - - if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || - (error = copy_item(payload, tgt_item, src_item)) < 0) - break; - } - - if (lock) - git_sortedcache_runlock(src); - if (error) - git_sortedcache_free(tgt); - - *out = !error ? tgt : NULL; - - return error; -} - -/* lock sortedcache while making modifications */ -int git_sortedcache_wlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - - if (git_rwlock_wrlock(&sc->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache"); - return -1; - } - return 0; -} - -/* unlock sorted cache when done with modifications */ -void git_sortedcache_wunlock(git_sortedcache *sc) -{ - git_vector_sort(&sc->items); - git_rwlock_wrunlock(&sc->lock); -} - -/* lock sortedcache for read */ -int git_sortedcache_rlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - - if (git_rwlock_rdlock(&sc->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache"); - return -1; - } - return 0; -} - -/* unlock sorted cache when done reading */ -void git_sortedcache_runlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - git_rwlock_rdunlock(&sc->lock); -} - -/* if the file has changed, lock cache and load file contents into buf; - * returns <0 on error, >0 if file has not changed - */ -int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf) -{ - int error, fd; - struct stat st; - - if ((error = git_sortedcache_wlock(sc)) < 0) - return error; - - if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) - goto unlock; - - if ((fd = git_futils_open_ro(sc->path)) < 0) { - error = fd; - goto unlock; - } - - if (p_fstat(fd, &st) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat file"); - error = -1; - (void)p_close(fd); - goto unlock; - } - - if (!git__is_sizet(st.st_size)) { - git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t"); - error = -1; - (void)p_close(fd); - goto unlock; - } - - if (buf) - error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); - - (void)p_close(fd); - - if (error < 0) - goto unlock; - - return 1; /* return 1 -> file needs reload and was successfully loaded */ - -unlock: - git_sortedcache_wunlock(sc); - return error; -} - -void git_sortedcache_updated(git_sortedcache *sc) -{ - /* update filestamp to latest value */ - git_futils_filestamp_check(&sc->stamp, sc->path); -} - -/* release all items in sorted cache */ -int git_sortedcache_clear(git_sortedcache *sc, bool wlock) -{ - if (wlock && git_sortedcache_wlock(sc) < 0) - return -1; - - sortedcache_clear(sc); - - if (wlock) - git_sortedcache_wunlock(sc); - - return 0; -} - -/* find and/or insert item, returning pointer to item data */ -int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) -{ - size_t keylen, itemlen; - int error = 0; - char *item_key; - void *item; - - 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, itemlen)) == NULL) { - /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ - error = -1; - goto done; - } - - /* one strange thing is that even if the vector or hash table insert - * fail, there is no way to free the pool item so we just abandon it - */ - - item_key = ((char *)item) + sc->item_path_offset; - memcpy(item_key, key, keylen); - - if ((error = git_strmap_set(sc->map, item_key, item)) < 0) - goto done; - - if ((error = git_vector_insert(&sc->items, item)) < 0) - git_strmap_delete(sc->map, item_key); - -done: - if (out) - *out = !error ? item : NULL; - return error; -} - -/* lookup item by key */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) -{ - return git_strmap_get(sc->map, key); -} - -/* find out how many items are in the cache */ -size_t git_sortedcache_entrycount(const git_sortedcache *sc) -{ - return git_vector_length(&sc->items); -} - -/* lookup item by index */ -void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) -{ - /* make sure the items are sorted so this gets the correct item */ - if (!git_vector_is_sorted(&sc->items)) - git_vector_sort(&sc->items); - - return git_vector_get(&sc->items, pos); -} - -/* helper struct so bsearch callback can know offset + key value for cmp */ -struct sortedcache_magic_key { - size_t offset; - const char *key; -}; - -static int sortedcache_magic_cmp(const void *key, const void *value) -{ - const struct sortedcache_magic_key *magic = key; - const char *value_key = ((const char *)value) + magic->offset; - return strcmp(magic->key, value_key); -} - -/* lookup index of item by key */ -int git_sortedcache_lookup_index( - size_t *out, git_sortedcache *sc, const char *key) -{ - struct sortedcache_magic_key magic; - - magic.offset = sc->item_path_offset; - magic.key = key; - - return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); -} - -/* remove entry from cache */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos) -{ - char *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. - */ - - if ((item = git_vector_get(&sc->items, pos)) == NULL) { - git_error_set(GIT_ERROR_INVALID, "removing item out of range"); - return GIT_ENOTFOUND; - } - - (void)git_vector_remove(&sc->items, pos); - - git_strmap_delete(sc->map, item + sc->item_path_offset); - - if (sc->free_item) - sc->free_item(sc->free_item_payload, item); - - return 0; -} - diff --git a/src/sortedcache.h b/src/sortedcache.h deleted file mode 100644 index ef260a093..000000000 --- a/src/sortedcache.h +++ /dev/null @@ -1,182 +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_sorted_cache_h__ -#define INCLUDE_sorted_cache_h__ - -#include "common.h" - -#include "util.h" -#include "futils.h" -#include "vector.h" -#include "thread.h" -#include "pool.h" -#include "strmap.h" - -#include - -/* - * The purpose of this data structure is to cache the parsed contents of a - * file (a.k.a. the backing file) where each item in the file can be - * identified by a key string and you want to both look them up by name - * and traverse them in sorted order. Each item is assumed to itself end - * in a GIT_FLEX_ARRAY. - */ - -typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); - -typedef struct { - git_refcount rc; - git_rwlock lock; - size_t item_path_offset; - git_sortedcache_free_item_fn free_item; - void *free_item_payload; - git_pool pool; - git_vector items; - git_strmap *map; - git_futils_filestamp stamp; - char path[GIT_FLEX_ARRAY]; -} git_sortedcache; - -/* Create a new sortedcache - * - * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at - * the end containing their key string, you have to provide the item_cmp - * sorting function because the sorting function doesn't get a payload - * and therefore can't know the offset to the item key string. :-( - * - * @param out The allocated git_sortedcache - * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the - * struct - use offsetof(struct mine, key-field) to get this - * @param free_item Optional callback to free each item - * @param free_item_payload Optional payload passed to free_item callback - * @param item_cmp Compare the keys of two items - * @param path The path to the backing store file for this cache; this - * may be NULL. The cache makes it easy to load this and check - * if it has been modified since the last load and/or write. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_new( - git_sortedcache **out, - size_t item_path_offset, /* use offsetof(struct, path-field) macro */ - git_sortedcache_free_item_fn free_item, - void *free_item_payload, - git_vector_cmp item_cmp, - const char *path); - -/* Copy a sorted cache - * - * - `copy_item` can be NULL to just use memcpy - * - if `lock`, grabs read lock on `src` during copy and releases after - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_copy( - git_sortedcache **out, - git_sortedcache *src, - bool lock, - int (*copy_item)(void *payload, void *tgt_item, void *src_item), - void *payload); - -/* Free sorted cache (first calling `free_item` callbacks) - * - * Don't call on a locked collection - it may acquire a write lock - */ -void git_sortedcache_free(git_sortedcache *sc); - -/* Increment reference count - balance with call to free */ -void git_sortedcache_incref(git_sortedcache *sc); - -/* Get the pathname associated with this cache at creation time */ -const char *git_sortedcache_path(git_sortedcache *sc); - -/* - * CACHE WRITE FUNCTIONS - * - * The following functions require you to have a writer lock to make the - * modification. Some of the functions take a `wlock` parameter and - * will optionally lock and unlock for you if that is passed as true. - * - */ - -/* Lock sortedcache for write */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc); - -/* Unlock sorted cache when done with write */ -void git_sortedcache_wunlock(git_sortedcache *sc); - -/* Lock cache and load backing file into a buffer. - * - * This grabs a write lock on the cache then looks at the modification - * time and size of the file on disk. - * - * If the file appears to have changed, this loads the file contents into - * the buffer and returns a positive value leaving the cache locked - the - * caller should parse the file content, update the cache as needed, then - * release the lock. NOTE: In this case, the caller MUST unlock the cache. - * - * If the file appears to be unchanged, then this automatically releases - * the lock on the cache, clears the buffer, and returns 0. - * - * @return 0 if up-to-date, 1 if out-of-date, <0 on error - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload( - git_sortedcache *sc, git_str *buf); - -/* Refresh file timestamp after write completes - * You should already be holding the write lock when you call this. - */ -void git_sortedcache_updated(git_sortedcache *sc); - -/* Release all items in sorted cache - * - * If `wlock` is true, grabs write lock and releases when done, otherwise - * you should already be holding a write lock when you call this. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_clear( - git_sortedcache *sc, bool wlock); - -/* Find and/or insert item, returning pointer to item data. - * You should already be holding the write lock when you call this. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert( - void **out, git_sortedcache *sc, const char *key); - -/* Removes entry at pos from cache - * You should already be holding the write lock when you call this. - */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos); - -/* - * CACHE READ FUNCTIONS - * - * The following functions access items in the cache. To prevent the - * results from being invalidated before they can be used, you should be - * holding either a read lock or a write lock when using these functions. - * - */ - -/* Lock sortedcache for read */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); - -/* Unlock sorted cache when done with read */ -void git_sortedcache_runlock(git_sortedcache *sc); - -/* Lookup item by key - returns NULL if not found */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); - -/* Get how many items are in the cache - * - * You can call this function without holding a lock, but be aware - * that it may change before you use it. - */ -size_t git_sortedcache_entrycount(const git_sortedcache *sc); - -/* Lookup item by index - returns NULL if out of range */ -void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); - -/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ -int git_sortedcache_lookup_index( - size_t *out, git_sortedcache *sc, const char *key); - -#endif diff --git a/src/stash.c b/src/stash.c deleted file mode 100644 index 5fc01ac36..000000000 --- a/src/stash.c +++ /dev/null @@ -1,1110 +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 "common.h" - -#include "repository.h" -#include "commit.h" -#include "tree.h" -#include "reflog.h" -#include "blob.h" -#include "git2/diff.h" -#include "git2/stash.h" -#include "git2/status.h" -#include "git2/checkout.h" -#include "git2/index.h" -#include "git2/transaction.h" -#include "git2/merge.h" -#include "index.h" -#include "signature.h" -#include "iterator.h" -#include "merge.h" -#include "diff.h" -#include "diff_generate.h" - -static int create_error(int error, const char *msg) -{ - git_error_set(GIT_ERROR_STASH, "cannot stash changes - %s", msg); - return error; -} - -static int retrieve_head(git_reference **out, git_repository *repo) -{ - int error = git_repository_head(out, repo); - - if (error == GIT_EUNBORNBRANCH) - return create_error(error, "you do not have the initial commit yet."); - - return error; -} - -static int append_abbreviated_oid(git_str *out, const git_oid *b_commit) -{ - char *formatted_oid; - - formatted_oid = git_oid_allocfmt(b_commit); - GIT_ERROR_CHECK_ALLOC(formatted_oid); - - git_str_put(out, formatted_oid, 7); - git__free(formatted_oid); - - return git_str_oom(out) ? -1 : 0; -} - -static int append_commit_description(git_str *out, git_commit *commit) -{ - const char *summary = git_commit_summary(commit); - GIT_ERROR_CHECK_ALLOC(summary); - - if (append_abbreviated_oid(out, git_commit_id(commit)) < 0) - return -1; - - git_str_putc(out, ' '); - git_str_puts(out, summary); - git_str_putc(out, '\n'); - - return git_str_oom(out) ? -1 : 0; -} - -static int retrieve_base_commit_and_message( - git_commit **b_commit, - git_str *stash_message, - git_repository *repo) -{ - git_reference *head = NULL; - int error; - - if ((error = retrieve_head(&head, repo)) < 0) - return error; - - if (strcmp("HEAD", git_reference_name(head)) == 0) - error = git_str_puts(stash_message, "(no branch): "); - else - error = git_str_printf( - stash_message, - "%s: ", - git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR)); - if (error < 0) - goto cleanup; - - if ((error = git_commit_lookup( - b_commit, repo, git_reference_target(head))) < 0) - goto cleanup; - - if ((error = append_commit_description(stash_message, *b_commit)) < 0) - goto cleanup; - -cleanup: - git_reference_free(head); - return error; -} - -static int build_tree_from_index( - git_tree **out, - git_repository *repo, - git_index *index) -{ - int error; - git_oid i_tree_oid; - - if ((error = git_index_write_tree_to(&i_tree_oid, index, repo)) < 0) - return error; - - return git_tree_lookup(out, repo, &i_tree_oid); -} - -static int commit_index( - git_commit **i_commit, - git_repository *repo, - git_index *index, - const git_signature *stasher, - const char *message, - const git_commit *parent) -{ - git_tree *i_tree = NULL; - git_oid i_commit_oid; - git_str msg = GIT_STR_INIT; - int error; - - if ((error = build_tree_from_index(&i_tree, repo, index)) < 0) - goto cleanup; - - if ((error = git_str_printf(&msg, "index on %s\n", message)) < 0) - goto cleanup; - - if ((error = git_commit_create( - &i_commit_oid, - git_index_owner(index), - NULL, - stasher, - stasher, - NULL, - git_str_cstr(&msg), - i_tree, - 1, - &parent)) < 0) - goto cleanup; - - error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid); - -cleanup: - git_tree_free(i_tree); - git_str_dispose(&msg); - return error; -} - -struct stash_update_rules { - bool include_changed; - bool include_untracked; - bool include_ignored; -}; - -/* - * Similar to git_index_add_bypath but able to operate on any - * index without making assumptions about the repository's index - */ -static int stash_to_index( - git_repository *repo, - git_index *index, - const char *path) -{ - git_index *repo_index = NULL; - git_index_entry entry = {{0}}; - struct stat st; - int error; - - if (!git_repository_is_bare(repo) && - (error = git_repository_index__weakptr(&repo_index, repo)) < 0) - return error; - - if ((error = git_blob__create_from_paths( - &entry.id, &st, repo, NULL, path, 0, true)) < 0) - return error; - - git_index_entry__init_from_stat(&entry, &st, - (repo_index == NULL || !repo_index->distrust_filemode)); - - entry.path = path; - - return git_index_add(index, &entry); -} - -static int stash_update_index_from_diff( - git_repository *repo, - git_index *index, - const git_diff *diff, - struct stash_update_rules *data) -{ - int error = 0; - size_t d, max_d = git_diff_num_deltas(diff); - - for (d = 0; !error && d < max_d; ++d) { - const char *add_path = NULL; - const git_diff_delta *delta = git_diff_get_delta(diff, d); - - switch (delta->status) { - case GIT_DELTA_IGNORED: - if (data->include_ignored) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_UNTRACKED: - if (data->include_untracked && - delta->new_file.mode != GIT_FILEMODE_TREE) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_ADDED: - case GIT_DELTA_MODIFIED: - if (data->include_changed) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_DELETED: - if (data->include_changed && - !git_index_find(NULL, index, delta->old_file.path)) - error = git_index_remove(index, delta->old_file.path, 0); - break; - - default: - /* Unimplemented */ - git_error_set( - GIT_ERROR_INVALID, - "cannot update index. Unimplemented status (%d)", - delta->status); - return -1; - } - - if (add_path != NULL) - error = stash_to_index(repo, index, add_path); - } - - return error; -} - -static int build_untracked_tree( - git_tree **tree_out, - git_repository *repo, - git_commit *i_commit, - uint32_t flags) -{ - git_index *i_index = NULL; - git_tree *i_tree = NULL; - git_diff *diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct stash_update_rules data = {0}; - int error; - - if ((error = git_index_new(&i_index)) < 0) - goto cleanup; - - if (flags & GIT_STASH_INCLUDE_UNTRACKED) { - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS; - data.include_untracked = true; - } - - if (flags & GIT_STASH_INCLUDE_IGNORED) { - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_RECURSE_IGNORED_DIRS; - data.include_ignored = true; - } - - if ((error = git_commit_tree(&i_tree, i_commit)) < 0) - goto cleanup; - - if ((error = git_diff_tree_to_workdir(&diff, repo, i_tree, &opts)) < 0) - goto cleanup; - - if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) - goto cleanup; - - error = build_tree_from_index(tree_out, repo, i_index); - -cleanup: - git_diff_free(diff); - git_tree_free(i_tree); - git_index_free(i_index); - return error; -} - -static int commit_untracked( - git_commit **u_commit, - git_repository *repo, - const git_signature *stasher, - const char *message, - git_commit *i_commit, - uint32_t flags) -{ - git_tree *u_tree = NULL; - git_oid u_commit_oid; - git_str msg = GIT_STR_INIT; - int error; - - if ((error = build_untracked_tree(&u_tree, repo, i_commit, flags)) < 0) - goto cleanup; - - if ((error = git_str_printf(&msg, "untracked files on %s\n", message)) < 0) - goto cleanup; - - if ((error = git_commit_create( - &u_commit_oid, - repo, - NULL, - stasher, - stasher, - NULL, - git_str_cstr(&msg), - u_tree, - 0, - NULL)) < 0) - goto cleanup; - - error = git_commit_lookup(u_commit, repo, &u_commit_oid); - -cleanup: - git_tree_free(u_tree); - git_str_dispose(&msg); - return error; -} - -static git_diff_delta *stash_delta_merge( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool) -{ - /* Special case for stash: if a file is deleted in the index, but exists - * in the working tree, we need to stash the workdir copy for the workdir. - */ - if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) { - git_diff_delta *dup = git_diff__delta_dup(b, pool); - - if (dup) - dup->status = GIT_DELTA_MODIFIED; - return dup; - } - - return git_diff__merge_like_cgit(a, b, pool); -} - -static int build_workdir_tree( - git_tree **tree_out, - git_repository *repo, - git_index *i_index, - git_commit *b_commit) -{ - git_tree *b_tree = NULL; - git_diff *diff = NULL, *idx_to_wd = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct stash_update_rules data = {0}; - int error; - - opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED; - - if ((error = git_commit_tree(&b_tree, b_commit)) < 0) - goto cleanup; - - if ((error = git_diff_tree_to_index(&diff, repo, b_tree, i_index, &opts)) < 0 || - (error = git_diff_index_to_workdir(&idx_to_wd, repo, i_index, &opts)) < 0 || - (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0) - goto cleanup; - - data.include_changed = true; - - if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) - goto cleanup; - - error = build_tree_from_index(tree_out, repo, i_index); - -cleanup: - git_diff_free(idx_to_wd); - git_diff_free(diff); - git_tree_free(b_tree); - - return error; -} - -static int commit_worktree( - git_oid *w_commit_oid, - git_repository *repo, - const git_signature *stasher, - const char *message, - git_commit *i_commit, - git_commit *b_commit, - git_commit *u_commit) -{ - 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_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 = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0) - goto cleanup; - - error = git_commit_create( - w_commit_oid, - repo, - NULL, - stasher, - stasher, - NULL, - message, - w_tree, - u_commit ? 3 : 2, - parents); - -cleanup: - git_tree_free(w_tree); - git_index_free(i_index); - git_index_free(r_index); - return error; -} - -static int prepare_worktree_commit_message(git_str *out, const char *user_message) -{ - git_str buf = GIT_STR_INIT; - int error = 0; - - if (!user_message) { - git_str_printf(&buf, "WIP on %s", git_str_cstr(out)); - } else { - const char *colon; - - if ((colon = strchr(git_str_cstr(out), ':')) == NULL) - goto cleanup; - - git_str_puts(&buf, "On "); - git_str_put(&buf, git_str_cstr(out), colon - out->ptr); - git_str_printf(&buf, ": %s\n", user_message); - } - - if (git_str_oom(&buf)) { - error = -1; - goto cleanup; - } - - git_str_swap(out, &buf); - -cleanup: - git_str_dispose(&buf); - return error; -} - -static int update_reflog( - git_oid *w_commit_oid, - git_repository *repo, - const char *message) -{ - git_reference *stash; - int error; - - if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0) - return error; - - error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, message); - - git_reference_free(stash); - - return error; -} - -static int is_dirty_cb(const char *path, unsigned int status, void *payload) -{ - GIT_UNUSED(path); - GIT_UNUSED(status); - GIT_UNUSED(payload); - - return GIT_PASSTHROUGH; -} - -static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flags) -{ - int error; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - - if (flags & GIT_STASH_INCLUDE_UNTRACKED) - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - if (flags & GIT_STASH_INCLUDE_IGNORED) - opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); - - if (error == GIT_PASSTHROUGH) - return 0; - - if (!error) - return create_error(GIT_ENOTFOUND, "there is nothing to stash."); - - return error; -} - -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 (flags & GIT_STASH_INCLUDE_UNTRACKED) - opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; - if (flags & GIT_STASH_INCLUDE_IGNORED) - opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED; - - return git_checkout_tree(repo, (git_object *)commit, &opts); -} - -int git_stash_save( - git_oid *out, - git_repository *repo, - const git_signature *stasher, - const char *message, - uint32_t flags) -{ - git_index *index = NULL; - git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; - git_str msg = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(stasher); - - if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) - return error; - - if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) - goto cleanup; - - 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_str_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_str_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_str_cstr(&msg), - i_commit, b_commit, u_commit)) < 0) - goto cleanup; - - git_str_rtrim(&msg); - - if ((error = update_reflog(out, repo, git_str_cstr(&msg))) < 0) - goto cleanup; - - if ((error = reset_index_and_workdir(repo, (flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, - flags)) < 0) - goto cleanup; - -cleanup: - - git_str_dispose(&msg); - git_commit_free(i_commit); - git_commit_free(b_commit); - git_commit_free(u_commit); - git_index_free(index); - - return error; -} - -static int retrieve_stash_commit( - git_commit **commit, - git_repository *repo, - size_t index) -{ - git_reference *stash = NULL; - git_reflog *reflog = NULL; - int error; - size_t max; - const git_reflog_entry *entry; - - if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - max = git_reflog_entrycount(reflog); - if (!max || index > max - 1) { - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); - goto cleanup; - } - - entry = git_reflog_entry_byindex(reflog, index); - if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0) - goto cleanup; - -cleanup: - git_reference_free(stash); - git_reflog_free(reflog); - return error; -} - -static int retrieve_stash_trees( - git_tree **out_stash_tree, - git_tree **out_base_tree, - git_tree **out_index_tree, - git_tree **out_index_parent_tree, - git_tree **out_untracked_tree, - git_commit *stash_commit) -{ - git_tree *stash_tree = NULL; - git_commit *base_commit = NULL; - git_tree *base_tree = NULL; - git_commit *index_commit = NULL; - git_tree *index_tree = NULL; - git_commit *index_parent_commit = NULL; - git_tree *index_parent_tree = NULL; - git_commit *untracked_commit = NULL; - git_tree *untracked_tree = NULL; - int error; - - if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0) - goto cleanup; - - if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0) - goto cleanup; - if ((error = git_commit_tree(&base_tree, base_commit)) < 0) - goto cleanup; - - if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0) - goto cleanup; - if ((error = git_commit_tree(&index_tree, index_commit)) < 0) - goto cleanup; - - if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0) - goto cleanup; - if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0) - goto cleanup; - - if (git_commit_parentcount(stash_commit) == 3) { - if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0) - goto cleanup; - if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0) - goto cleanup; - } - - *out_stash_tree = stash_tree; - *out_base_tree = base_tree; - *out_index_tree = index_tree; - *out_index_parent_tree = index_parent_tree; - *out_untracked_tree = untracked_tree; - -cleanup: - git_commit_free(untracked_commit); - git_commit_free(index_parent_commit); - git_commit_free(index_commit); - git_commit_free(base_commit); - if (error < 0) { - git_tree_free(stash_tree); - git_tree_free(base_tree); - git_tree_free(index_tree); - git_tree_free(index_parent_tree); - git_tree_free(untracked_tree); - } - return error; -} - -static int merge_indexes( - git_index **out, - git_repository *repo, - git_tree *ancestor_tree, - git_index *ours_index, - git_index *theirs_index) -{ - git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || - (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || - (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0) - goto done; - - error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); - -done: - git_iterator_free(ancestor); - git_iterator_free(ours); - git_iterator_free(theirs); - return error; -} - -static int merge_index_and_tree( - git_index **out, - git_repository *repo, - git_tree *ancestor_tree, - git_index *ours_index, - git_tree *theirs_tree) -{ - git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || - (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || - (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) - goto done; - - error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); - -done: - git_iterator_free(ancestor); - git_iterator_free(ours); - git_iterator_free(theirs); - return error; -} - -static void normalize_apply_options( - git_stash_apply_options *opts, - const git_stash_apply_options *given_apply_opts) -{ - if (given_apply_opts != NULL) { - memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options)); - } else { - git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT; - memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options)); - } - - opts->checkout_options.checkout_strategy |= GIT_CHECKOUT_NO_REFRESH; - - if (!opts->checkout_options.our_label) - opts->checkout_options.our_label = "Updated upstream"; - - if (!opts->checkout_options.their_label) - opts->checkout_options.their_label = "Stashed changes"; -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) -{ - return git_stash_apply_options_init(opts, version); -} -#endif - -#define NOTIFY_PROGRESS(opts, progress_type) \ - do { \ - if ((opts).progress_cb && \ - (error = (opts).progress_cb((progress_type), (opts).progress_payload))) { \ - error = (error < 0) ? error : -1; \ - goto cleanup; \ - } \ - } while(false); - -static int ensure_clean_index(git_repository *repo, git_index *index) -{ - git_tree *head_tree = NULL; - git_diff *index_diff = NULL; - int error = 0; - - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_diff_tree_to_index( - &index_diff, repo, head_tree, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(index_diff) > 0) { - git_error_set(GIT_ERROR_STASH, "%" PRIuZ " uncommitted changes exist in the index", - git_diff_num_deltas(index_diff)); - error = GIT_EUNCOMMITTED; - } - -done: - git_diff_free(index_diff); - git_tree_free(head_tree); - return error; -} - -static int stage_new_file(const git_index_entry **entries, void *data) -{ - git_index *index = data; - - if(entries[0] == NULL) - return git_index_add(index, entries[1]); - else - return git_index_add(index, entries[0]); -} - -static int stage_new_files( - git_index **out, - git_tree *parent_tree, - git_tree *tree) -{ - git_iterator *iterators[2] = { NULL, NULL }; - git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; - git_index *index = NULL; - int error; - - if ((error = git_index_new(&index)) < 0 || - (error = git_iterator_for_tree( - &iterators[0], parent_tree, &iterator_options)) < 0 || - (error = git_iterator_for_tree( - &iterators[1], tree, &iterator_options)) < 0) - goto done; - - error = git_iterator_walk(iterators, 2, stage_new_file, index); - -done: - if (error < 0) - git_index_free(index); - else - *out = index; - - git_iterator_free(iterators[0]); - git_iterator_free(iterators[1]); - - return error; -} - -int git_stash_apply( - git_repository *repo, - size_t index, - const git_stash_apply_options *given_opts) -{ - git_stash_apply_options opts; - unsigned int checkout_strategy; - git_commit *stash_commit = NULL; - git_tree *stash_tree = NULL; - git_tree *stash_parent_tree = NULL; - git_tree *index_tree = NULL; - git_tree *index_parent_tree = NULL; - git_tree *untracked_tree = NULL; - git_index *stash_adds = NULL; - git_index *repo_index = NULL; - git_index *unstashed_index = NULL; - git_index *modified_index = NULL; - git_index *untracked_index = NULL; - int error; - - GIT_ERROR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options"); - - normalize_apply_options(&opts, given_opts); - checkout_strategy = opts.checkout_options.checkout_strategy; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH); - - /* Retrieve commit corresponding to the given stash */ - if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0) - goto cleanup; - - /* Retrieve all trees in the stash */ - if ((error = retrieve_stash_trees( - &stash_tree, &stash_parent_tree, &index_tree, - &index_parent_tree, &untracked_tree, stash_commit)) < 0) - goto cleanup; - - /* Load repo index */ - if ((error = git_repository_index(&repo_index, repo)) < 0) - goto cleanup; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX); - - if ((error = ensure_clean_index(repo, repo_index)) < 0) - goto cleanup; - - /* Restore index if required */ - if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) && - git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) { - - if ((error = merge_index_and_tree( - &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0) - goto cleanup; - - if (git_index_has_conflicts(unstashed_index)) { - error = GIT_ECONFLICT; - goto cleanup; - } - - /* Otherwise, stage any new files in the stash tree. (Note: their - * previously unstaged contents are staged, not the previously staged.) - */ - } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) { - if ((error = stage_new_files( - &stash_adds, stash_parent_tree, stash_tree)) < 0 || - (error = merge_indexes( - &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0) - goto cleanup; - } - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED); - - /* Restore modified files in workdir */ - if ((error = merge_index_and_tree( - &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0) - goto cleanup; - - /* If applicable, restore untracked / ignored files in workdir */ - if (untracked_tree) { - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED); - - if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0) - goto cleanup; - } - - if (untracked_index) { - opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED); - - if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0) - goto cleanup; - - opts.checkout_options.checkout_strategy = checkout_strategy; - } - - - /* If there are conflicts in the modified index, then we need to actually - * check that out as the repo's index. Otherwise, we don't update the - * index. - */ - - if (!git_index_has_conflicts(modified_index)) - opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; - - /* Check out the modified index using the existing repo index as baseline, - * so that existing modifications in the index can be rewritten even when - * checking out safely. - */ - opts.checkout_options.baseline_index = repo_index; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED); - - if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0) - goto cleanup; - - if (unstashed_index && !git_index_has_conflicts(modified_index)) { - if ((error = git_index_read_index(repo_index, unstashed_index)) < 0) - goto cleanup; - } - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE); - - error = git_index_write(repo_index); - -cleanup: - git_index_free(untracked_index); - git_index_free(modified_index); - git_index_free(unstashed_index); - git_index_free(stash_adds); - git_index_free(repo_index); - git_tree_free(untracked_tree); - git_tree_free(index_parent_tree); - git_tree_free(index_tree); - git_tree_free(stash_parent_tree); - git_tree_free(stash_tree); - git_commit_free(stash_commit); - return error; -} - -int git_stash_foreach( - git_repository *repo, - git_stash_cb callback, - void *payload) -{ - git_reference *stash; - git_reflog *reflog = NULL; - int error; - size_t i, max; - const git_reflog_entry *entry; - - error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - if (error < 0) - goto cleanup; - - if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - max = git_reflog_entrycount(reflog); - for (i = 0; i < max; i++) { - entry = git_reflog_entry_byindex(reflog, i); - - error = callback(i, - git_reflog_entry_message(entry), - git_reflog_entry_id_new(entry), - payload); - - if (error) { - git_error_set_after_callback(error); - break; - } - } - -cleanup: - git_reference_free(stash); - git_reflog_free(reflog); - return error; -} - -int git_stash_drop( - git_repository *repo, - size_t index) -{ - git_transaction *tx; - git_reference *stash = NULL; - git_reflog *reflog = NULL; - size_t max; - int error; - - if ((error = git_transaction_new(&tx, repo)) < 0) - return error; - - if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - max = git_reflog_entrycount(reflog); - - if (!max || index > max - 1) { - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); - goto cleanup; - } - - if ((error = git_reflog_drop(reflog, index, true)) < 0) - goto cleanup; - - if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0) - goto cleanup; - - if (max == 1) { - if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - } else if (index == 0) { - const git_reflog_entry *entry; - - entry = git_reflog_entry_byindex(reflog, 0); - if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0) - goto cleanup; - } - - error = git_transaction_commit(tx); - -cleanup: - git_reference_free(stash); - git_transaction_free(tx); - git_reflog_free(reflog); - return error; -} - -int git_stash_pop( - git_repository *repo, - size_t index, - const git_stash_apply_options *options) -{ - int error; - - if ((error = git_stash_apply(repo, index, options)) < 0) - return error; - - return git_stash_drop(repo, index); -} diff --git a/src/status.c b/src/status.c deleted file mode 100644 index df0f74507..000000000 --- a/src/status.c +++ /dev/null @@ -1,584 +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 "status.h" - -#include "git2.h" -#include "futils.h" -#include "hash.h" -#include "vector.h" -#include "tree.h" -#include "git2/status.h" -#include "repository.h" -#include "ignore.h" -#include "index.h" -#include "wildmatch.h" - -#include "git2/diff.h" -#include "diff.h" -#include "diff_generate.h" - -static unsigned int index_delta2status(const git_diff_delta *head2idx) -{ - git_status_t st = GIT_STATUS_CURRENT; - - switch (head2idx->status) { - case GIT_DELTA_ADDED: - case GIT_DELTA_COPIED: - st = GIT_STATUS_INDEX_NEW; - break; - case GIT_DELTA_DELETED: - st = GIT_STATUS_INDEX_DELETED; - break; - case GIT_DELTA_MODIFIED: - st = GIT_STATUS_INDEX_MODIFIED; - break; - case GIT_DELTA_RENAMED: - st = GIT_STATUS_INDEX_RENAMED; - - if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id)) - st |= GIT_STATUS_INDEX_MODIFIED; - break; - case GIT_DELTA_TYPECHANGE: - st = GIT_STATUS_INDEX_TYPECHANGE; - break; - case GIT_DELTA_CONFLICTED: - st = GIT_STATUS_CONFLICTED; - break; - default: - break; - } - - return st; -} - -static unsigned int workdir_delta2status( - git_diff *diff, git_diff_delta *idx2wd) -{ - git_status_t st = GIT_STATUS_CURRENT; - - switch (idx2wd->status) { - case GIT_DELTA_ADDED: - case GIT_DELTA_COPIED: - case GIT_DELTA_UNTRACKED: - st = GIT_STATUS_WT_NEW; - break; - case GIT_DELTA_UNREADABLE: - st = GIT_STATUS_WT_UNREADABLE; - break; - case GIT_DELTA_DELETED: - st = GIT_STATUS_WT_DELETED; - break; - case GIT_DELTA_MODIFIED: - st = GIT_STATUS_WT_MODIFIED; - break; - case GIT_DELTA_IGNORED: - st = GIT_STATUS_IGNORED; - break; - case GIT_DELTA_RENAMED: - st = GIT_STATUS_WT_RENAMED; - - if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) { - /* if OIDs don't match, we might need to calculate them now to - * discern between RENAMED vs RENAMED+MODIFIED - */ - 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_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)) - idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) - st |= GIT_STATUS_WT_MODIFIED; - } - break; - case GIT_DELTA_TYPECHANGE: - st = GIT_STATUS_WT_TYPECHANGE; - break; - case GIT_DELTA_CONFLICTED: - st = GIT_STATUS_CONFLICTED; - break; - default: - break; - } - - return st; -} - -static bool status_is_included( - git_status_list *status, - git_diff_delta *head2idx, - git_diff_delta *idx2wd) -{ - if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES)) - return 1; - - /* if excluding submodules and this is a submodule everywhere */ - if (head2idx) { - if (head2idx->status != GIT_DELTA_ADDED && - head2idx->old_file.mode != GIT_FILEMODE_COMMIT) - return 1; - if (head2idx->status != GIT_DELTA_DELETED && - head2idx->new_file.mode != GIT_FILEMODE_COMMIT) - return 1; - } - if (idx2wd) { - if (idx2wd->status != GIT_DELTA_ADDED && - idx2wd->old_file.mode != GIT_FILEMODE_COMMIT) - return 1; - if (idx2wd->status != GIT_DELTA_DELETED && - idx2wd->new_file.mode != GIT_FILEMODE_COMMIT) - return 1; - } - - /* only get here if every valid mode is GIT_FILEMODE_COMMIT */ - return 0; -} - -static git_status_t status_compute( - git_status_list *status, - git_diff_delta *head2idx, - git_diff_delta *idx2wd) -{ - git_status_t st = GIT_STATUS_CURRENT; - - if (head2idx) - st |= index_delta2status(head2idx); - - if (idx2wd) - st |= workdir_delta2status(status->idx2wd, idx2wd); - - return st; -} - -static int status_collect( - git_diff_delta *head2idx, - git_diff_delta *idx2wd, - void *payload) -{ - git_status_list *status = payload; - git_status_entry *status_entry; - - if (!status_is_included(status, head2idx, idx2wd)) - return 0; - - status_entry = git__malloc(sizeof(git_status_entry)); - GIT_ERROR_CHECK_ALLOC(status_entry); - - status_entry->status = status_compute(status, head2idx, idx2wd); - status_entry->head_to_index = head2idx; - status_entry->index_to_workdir = idx2wd; - - return git_vector_insert(&status->paired, status_entry); -} - -GIT_INLINE(int) status_entry_cmp_base( - const void *a, - const void *b, - int (*strcomp)(const char *a, const char *b)) -{ - const git_status_entry *entry_a = a; - const git_status_entry *entry_b = b; - const git_diff_delta *delta_a, *delta_b; - - delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir : - entry_a->head_to_index; - delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir : - entry_b->head_to_index; - - if (!delta_a && delta_b) - return -1; - if (delta_a && !delta_b) - return 1; - if (!delta_a && !delta_b) - return 0; - - return strcomp(delta_a->new_file.path, delta_b->new_file.path); -} - -static int status_entry_icmp(const void *a, const void *b) -{ - return status_entry_cmp_base(a, b, git__strcasecmp); -} - -static int status_entry_cmp(const void *a, const void *b) -{ - return status_entry_cmp_base(a, b, git__strcmp); -} - -static git_status_list *git_status_list_alloc(git_index *index) -{ - git_status_list *status = NULL; - int (*entrycmp)(const void *a, const void *b); - - if (!(status = git__calloc(1, sizeof(git_status_list)))) - return NULL; - - entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; - - if (git_vector_init(&status->paired, 0, entrycmp) < 0) { - git__free(status); - return NULL; - } - - return status; -} - -static int status_validate_options(const git_status_options *opts) -{ - if (!opts) - return 0; - - GIT_ERROR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - - if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) { - git_error_set(GIT_ERROR_INVALID, "unknown status 'show' option"); - return -1; - } - - if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 && - (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) { - git_error_set(GIT_ERROR_INVALID, "updating index from status " - "is not allowed when index refresh is disabled"); - return -1; - } - - return 0; -} - -int git_status_list_new( - git_status_list **out, - git_repository *repo, - const git_status_options *opts) -{ - git_index *index = NULL; - git_status_list *status = NULL; - git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT; - git_tree *head = NULL; - git_status_show_t show = - opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - int error = 0; - unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; - - *out = NULL; - - if (status_validate_options(opts) < 0) - return -1; - - 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 { - /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if ((error = git_repository_head_tree(&head, repo)) < 0) { - if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) - goto done; - git_error_clear(); - } - } - - /* refresh index from disk unless prevented */ - if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 && - git_index_read_safely(index) < 0) - git_error_clear(); - - status = git_status_list_alloc(index); - GIT_ERROR_CHECK_ALLOC(status); - - if (opts) { - memcpy(&status->opts, opts, sizeof(git_status_options)); - memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); - } - - diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED; - - if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; - if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; - if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; - if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS; - if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX; - if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE; - if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED; - - if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) - findopt.flags = findopt.flags | - GIT_DIFF_FIND_AND_BREAK_REWRITES | - GIT_DIFF_FIND_RENAMES_FROM_REWRITES | - GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; - - if (opts != NULL && opts->rename_threshold != 0) - findopt.rename_threshold = opts->rename_threshold; - - if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - if ((error = git_diff_tree_to_index( - &status->head2idx, repo, head, index, &diffopt)) < 0) - goto done; - - if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(status->head2idx, &findopt)) < 0) - goto done; - } - - if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - if ((error = git_diff_index_to_workdir( - &status->idx2wd, repo, index, &diffopt)) < 0) { - goto done; - } - - if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && - (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) - goto done; - } - - error = git_diff__paired_foreach( - status->head2idx, status->idx2wd, status_collect, status); - if (error < 0) - goto done; - - if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) - git_vector_set_cmp(&status->paired, status_entry_cmp); - if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY) - git_vector_set_cmp(&status->paired, status_entry_icmp); - - if ((flags & - (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY | - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0) - git_vector_sort(&status->paired); - -done: - if (error < 0) { - git_status_list_free(status); - status = NULL; - } - - *out = status; - - if (opts == NULL || opts->baseline != head) - git_tree_free(head); - git_index_free(index); - - return error; -} - -size_t git_status_list_entrycount(git_status_list *status) -{ - GIT_ASSERT_ARG_WITH_RETVAL(status, 0); - - return status->paired.length; -} - -const git_status_entry *git_status_byindex(git_status_list *status, size_t i) -{ - GIT_ASSERT_ARG_WITH_RETVAL(status, NULL); - - return git_vector_get(&status->paired, i); -} - -void git_status_list_free(git_status_list *status) -{ - if (status == NULL) - return; - - git_diff_free(status->head2idx); - git_diff_free(status->idx2wd); - - git_vector_free_deep(&status->paired); - - git__memzero(status, sizeof(*status)); - git__free(status); -} - -int git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb cb, - void *payload) -{ - git_status_list *status; - const git_status_entry *status_entry; - size_t i; - int error = 0; - - if ((error = git_status_list_new(&status, repo, opts)) < 0) { - return error; - } - - git_vector_foreach(&status->paired, i, status_entry) { - const char *path = status_entry->head_to_index ? - status_entry->head_to_index->old_file.path : - status_entry->index_to_workdir->old_file.path; - - if ((error = cb(path, status_entry->status, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - git_status_list_free(status); - - return error; -} - -int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload) -{ - return git_status_foreach_ext(repo, NULL, cb, payload); -} - -struct status_file_info { - char *expected; - unsigned int count; - unsigned int status; - int wildmatch_flags; - int ambiguous; -}; - -static int get_one_status(const char *path, unsigned int status, void *data) -{ - struct status_file_info *sfi = data; - int (*strcomp)(const char *a, const char *b); - - sfi->count++; - sfi->status = status; - - strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp; - - if (sfi->count > 1 || - (strcomp(sfi->expected, path) != 0 && - wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0)) - { - sfi->ambiguous = true; - return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */ - } - - return 0; -} - -int git_status_file( - unsigned int *status_flags, - git_repository *repo, - const char *path) -{ - int error; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_file_info sfi = {0}; - git_index *index; - - GIT_ASSERT_ARG(status_flags); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(path); - - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - return error; - - if ((sfi.expected = git__strdup(path)) == NULL) - return -1; - if (index->ignore_case) - sfi.wildmatch_flags = WM_CASEFOLD; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | - GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; - opts.pathspec.count = 1; - opts.pathspec.strings = &sfi.expected; - - error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); - - if (error < 0 && sfi.ambiguous) { - git_error_set(GIT_ERROR_INVALID, - "ambiguous path '%s' given to git_status_file", sfi.expected); - error = GIT_EAMBIGUOUS; - } - - if (!error && !sfi.count) { - git_error_set(GIT_ERROR_INVALID, - "attempt to get status of nonexistent file '%s'", path); - error = GIT_ENOTFOUND; - } - - *status_flags = sfi.status; - - git__free(sfi.expected); - - return error; -} - -int git_status_should_ignore( - int *ignored, - git_repository *repo, - const char *path) -{ - return git_ignore_path_is_ignored(ignored, repo, path); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_status_init_options(git_status_options *opts, unsigned int version) -{ - return git_status_options_init(opts, version); -} -#endif - -int git_status_list_get_perfdata( - git_diff_perfdata *out, const git_status_list *status) -{ - GIT_ASSERT_ARG(out); - - GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); - - out->stat_calls = 0; - out->oid_calculations = 0; - - if (status->head2idx) { - out->stat_calls += status->head2idx->perf.stat_calls; - out->oid_calculations += status->head2idx->perf.oid_calculations; - } - if (status->idx2wd) { - out->stat_calls += status->idx2wd->perf.stat_calls; - out->oid_calculations += status->idx2wd->perf.oid_calculations; - } - - return 0; -} - diff --git a/src/status.h b/src/status.h deleted file mode 100644 index 907479a22..000000000 --- a/src/status.h +++ /dev/null @@ -1,25 +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_status_h__ -#define INCLUDE_status_h__ - -#include "common.h" - -#include "diff.h" -#include "git2/status.h" -#include "git2/diff.h" - -struct git_status_list { - git_status_options opts; - - git_diff *head2idx; - git_diff *idx2wd; - - git_vector paired; -}; - -#endif diff --git a/src/str.c b/src/str.c deleted file mode 100644 index 0d405bfda..000000000 --- a/src/str.c +++ /dev/null @@ -1,1372 +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 "str.h" -#include "posix.h" -#include - -/* Used as default value for git_str->ptr so that people can always - * assume ptr is non-NULL and zero terminated even for new git_strs. - */ -char git_str__initstr[1]; - -char git_str__oom[1]; - -#define ENSURE_SIZE(b, d) \ - if ((b)->ptr == git_str__oom || \ - ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\ - return -1; - - -int git_str_init(git_str *buf, size_t initial_size) -{ - buf->asize = 0; - buf->size = 0; - buf->ptr = git_str__initstr; - - ENSURE_SIZE(buf, initial_size); - - return 0; -} - -int git_str_try_grow( - git_str *buf, size_t target_size, bool mark_oom) -{ - char *new_ptr; - size_t new_size; - - if (buf->ptr == git_str__oom) - return -1; - - if (buf->asize == 0 && buf->size != 0) { - git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer"); - return GIT_EINVALID; - } - - if (!target_size) - target_size = buf->size; - - if (target_size <= buf->asize) - return 0; - - if (buf->asize == 0) { - new_size = target_size; - 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; - } - - /* round allocation up to multiple of 8 */ - new_size = (new_size + 7) & ~7; - - if (new_size < buf->size) { - if (mark_oom) { - if (buf->ptr && buf->ptr != git_str__initstr) - git__free(buf->ptr); - buf->ptr = git_str__oom; - } - - git_error_set_oom(); - return -1; - } - - new_ptr = git__realloc(new_ptr, new_size); - - if (!new_ptr) { - if (mark_oom) { - if (buf->ptr && (buf->ptr != git_str__initstr)) - git__free(buf->ptr); - buf->ptr = git_str__oom; - } - return -1; - } - - buf->asize = new_size; - buf->ptr = new_ptr; - - /* truncate the existing buffer size if necessary */ - if (buf->size >= buf->asize) - buf->size = buf->asize - 1; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_grow(git_str *buffer, size_t target_size) -{ - return git_str_try_grow(buffer, target_size, true); -} - -int git_str_grow_by(git_str *buffer, size_t additional_size) -{ - size_t newsize; - - if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) { - buffer->ptr = git_str__oom; - return -1; - } - - return git_str_try_grow(buffer, newsize, true); -} - -void git_str_dispose(git_str *buf) -{ - if (!buf) return; - - if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom) - git__free(buf->ptr); - - git_str_init(buf, 0); -} - -void git_str_clear(git_str *buf) -{ - buf->size = 0; - - if (!buf->ptr) { - buf->ptr = git_str__initstr; - buf->asize = 0; - } - - if (buf->asize > 0) - buf->ptr[0] = '\0'; -} - -int git_str_set(git_str *buf, const void *data, size_t len) -{ - size_t alloclen; - - if (len == 0 || data == NULL) { - git_str_clear(buf); - } else { - if (data != buf->ptr) { - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - ENSURE_SIZE(buf, alloclen); - memmove(buf->ptr, data, len); - } - - buf->size = len; - if (buf->asize > buf->size) - buf->ptr[buf->size] = '\0'; - - } - return 0; -} - -int git_str_sets(git_str *buf, const char *string) -{ - return git_str_set(buf, string, string ? strlen(string) : 0); -} - -int git_str_putc(git_str *buf, char c) -{ - size_t new_size; - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2); - ENSURE_SIZE(buf, new_size); - buf->ptr[buf->size++] = c; - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_putcn(git_str *buf, char c, size_t len) -{ - size_t new_size; - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - memset(buf->ptr + buf->size, c, len); - buf->size += len; - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_put(git_str *buf, const char *data, size_t len) -{ - if (len) { - size_t new_size; - - GIT_ASSERT_ARG(data); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - memmove(buf->ptr + buf->size, data, len); - buf->size += len; - buf->ptr[buf->size] = '\0'; - } - return 0; -} - -int git_str_puts(git_str *buf, const char *string) -{ - GIT_ASSERT_ARG(string); - - return git_str_put(buf, string, strlen(string)); -} - -static char hex_encode[] = "0123456789abcdef"; - -int git_str_encode_hexstr(git_str *str, const char *data, size_t len) -{ - size_t new_size, i; - char *s; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - - if (git_str_grow_by(str, new_size) < 0) - return -1; - - s = str->ptr + str->size; - - for (i = 0; i < len; i++) { - *s++ = hex_encode[(data[i] & 0xf0) >> 4]; - *s++ = hex_encode[(data[i] & 0x0f)]; - } - - str->size += (len * 2); - str->ptr[str->size] = '\0'; - - return 0; -} - -static const char base64_encode[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -int git_str_encode_base64(git_str *buf, const char *data, size_t len) -{ - size_t extra = len % 3; - uint8_t *write, a, b, c; - const uint8_t *read = (const uint8_t *)data; - size_t blocks = (len / 3) + !!extra, alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); - - ENSURE_SIZE(buf, alloclen); - write = (uint8_t *)&buf->ptr[buf->size]; - - /* convert each run of 3 bytes into 4 output bytes */ - for (len -= extra; len > 0; len -= 3) { - a = *read++; - b = *read++; - c = *read++; - - *write++ = base64_encode[a >> 2]; - *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; - *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; - *write++ = base64_encode[c & 0x3f]; - } - - if (extra > 0) { - a = *read++; - b = (extra > 1) ? *read++ : 0; - - *write++ = base64_encode[a >> 2]; - *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; - *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; - *write++ = '='; - } - - buf->size = ((char *)write) - buf->ptr; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -/* The inverse of base64_encode */ -static const int8_t base64_decode[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; - -int git_str_decode_base64(git_str *buf, const char *base64, size_t len) -{ - size_t i; - int8_t a, b, c, d; - size_t orig_size = buf->size, new_size; - - if (len % 4) { - git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); - return -1; - } - - GIT_ASSERT_ARG(len % 4 == 0); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - for (i = 0; i < len; i += 4) { - if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || - (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || - (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || - (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { - buf->size = orig_size; - buf->ptr[buf->size] = '\0'; - - git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); - return -1; - } - - buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); - buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); - buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); - } - - buf->ptr[buf->size] = '\0'; - return 0; -} - -static const char base85_encode[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; - -int git_str_encode_base85(git_str *buf, const char *data, size_t len) -{ - size_t blocks = (len / 4) + !!(len % 4), alloclen; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - ENSURE_SIZE(buf, alloclen); - - while (len) { - uint32_t acc = 0; - char b85[5]; - int i; - - for (i = 24; i >= 0; i -= 8) { - uint8_t ch = *data++; - acc |= (uint32_t)ch << i; - - if (--len == 0) - break; - } - - for (i = 4; i >= 0; i--) { - int val = acc % 85; - acc /= 85; - - b85[i] = base85_encode[val]; - } - - for (i = 0; i < 5; i++) - buf->ptr[buf->size++] = b85[i]; - } - - buf->ptr[buf->size] = '\0'; - - return 0; -} - -/* The inverse of base85_encode */ -static const int8_t base85_decode[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, - 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, - 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; - -int git_str_decode_base85( - git_str *buf, - const char *base85, - size_t base85_len, - size_t output_len) -{ - size_t orig_size = buf->size, new_size; - - if (base85_len % 5 || - output_len > base85_len * 4 / 5) { - git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - while (output_len) { - unsigned acc = 0; - int de, cnt = 4; - unsigned char ch; - do { - ch = *base85++; - de = base85_decode[ch]; - if (--de < 0) - goto on_error; - - acc = acc * 85 + de; - } while (--cnt); - ch = *base85++; - de = base85_decode[ch]; - if (--de < 0) - goto on_error; - - /* Detect overflow. */ - if (0xffffffff / 85 < acc || - 0xffffffff - de < (acc *= 85)) - goto on_error; - - acc += de; - - cnt = (output_len < 4) ? (int)output_len : 4; - output_len -= cnt; - do { - acc = (acc << 8) | (acc >> 24); - buf->ptr[buf->size++] = acc; - } while (--cnt); - } - - buf->ptr[buf->size] = 0; - - return 0; - -on_error: - buf->size = orig_size; - buf->ptr[buf->size] = '\0'; - - git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); - return -1; -} - -#define HEX_DECODE(c) ((c | 32) % 39 - 9) - -int git_str_decode_percent( - git_str *buf, - const char *str, - size_t str_len) -{ - size_t str_pos, new_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { - if (str[str_pos] == '%' && - str_len > str_pos + 2 && - isxdigit(str[str_pos + 1]) && - isxdigit(str[str_pos + 2])) { - buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + - HEX_DECODE(str[str_pos + 2]); - str_pos += 2; - } else { - buf->ptr[buf->size] = str[str_pos]; - } - } - - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_vprintf(git_str *buf, const char *format, va_list ap) -{ - size_t expected_size, new_size; - int len; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2); - GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size); - ENSURE_SIZE(buf, expected_size); - - while (1) { - va_list args; - va_copy(args, ap); - - len = p_vsnprintf( - buf->ptr + buf->size, - buf->asize - buf->size, - format, args - ); - - va_end(args); - - if (len < 0) { - git__free(buf->ptr); - buf->ptr = git_str__oom; - return -1; - } - - if ((size_t)len + 1 <= buf->asize - buf->size) { - buf->size += len; - break; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - } - - return 0; -} - -int git_str_printf(git_str *buf, const char *format, ...) -{ - int r; - va_list ap; - - va_start(ap, format); - r = git_str_vprintf(buf, format, ap); - va_end(ap); - - return r; -} - -int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf) -{ - size_t copylen; - - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(datasize); - GIT_ASSERT_ARG(buf); - - data[0] = '\0'; - - if (buf->size == 0 || buf->asize <= 0) - return 0; - - copylen = buf->size; - if (copylen > datasize - 1) - copylen = datasize - 1; - memmove(data, buf->ptr, copylen); - data[copylen] = '\0'; - - return 0; -} - -void git_str_consume_bytes(git_str *buf, size_t len) -{ - git_str_consume(buf, buf->ptr + len); -} - -void git_str_consume(git_str *buf, const char *end) -{ - if (end > buf->ptr && end <= buf->ptr + buf->size) { - size_t consumed = end - buf->ptr; - memmove(buf->ptr, end, buf->size - consumed); - buf->size -= consumed; - buf->ptr[buf->size] = '\0'; - } -} - -void git_str_truncate(git_str *buf, size_t len) -{ - if (len >= buf->size) - return; - - buf->size = len; - if (buf->size < buf->asize) - buf->ptr[buf->size] = '\0'; -} - -void git_str_shorten(git_str *buf, size_t amount) -{ - if (buf->size > amount) - git_str_truncate(buf, buf->size - amount); - else - git_str_clear(buf); -} - -void git_str_truncate_at_char(git_str *buf, char separator) -{ - ssize_t idx = git_str_find(buf, separator); - if (idx >= 0) - git_str_truncate(buf, (size_t)idx); -} - -void git_str_rtruncate_at_char(git_str *buf, char separator) -{ - ssize_t idx = git_str_rfind_next(buf, separator); - git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx); -} - -void git_str_swap(git_str *str_a, git_str *str_b) -{ - git_str t = *str_a; - *str_a = *str_b; - *str_b = t; -} - -char *git_str_detach(git_str *buf) -{ - char *data = buf->ptr; - - if (buf->asize == 0 || buf->ptr == git_str__oom) - return NULL; - - git_str_init(buf, 0); - - return data; -} - -int git_str_attach(git_str *buf, char *ptr, size_t asize) -{ - git_str_dispose(buf); - - if (ptr) { - buf->ptr = ptr; - buf->size = strlen(ptr); - if (asize) - buf->asize = (asize < buf->size) ? buf->size + 1 : asize; - else /* pass 0 to fall back on strlen + 1 */ - buf->asize = buf->size + 1; - } - - ENSURE_SIZE(buf, asize); - return 0; -} - -void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size) -{ - if (git_str_is_allocated(buf)) - git_str_dispose(buf); - - if (!size) { - git_str_init(buf, 0); - } else { - buf->ptr = (char *)ptr; - buf->asize = 0; - buf->size = size; - } -} - -int git_str_join_n(git_str *buf, char separator, int nbuf, ...) -{ - va_list ap; - int i; - size_t total_size = 0, original_size = buf->size; - char *out, *original = buf->ptr; - - if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) - ++total_size; /* space for initial separator */ - - /* Make two passes to avoid multiple reallocation */ - - va_start(ap, nbuf); - for (i = 0; i < nbuf; ++i) { - const char *segment; - size_t segment_len; - - segment = va_arg(ap, const char *); - if (!segment) - continue; - - segment_len = strlen(segment); - - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len); - - if (segment_len == 0 || segment[segment_len - 1] != separator) - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - } - va_end(ap); - - /* expand buffer if needed */ - if (total_size == 0) - return 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - if (git_str_grow_by(buf, total_size) < 0) - return -1; - - out = buf->ptr + buf->size; - - /* append separator to existing buf if needed */ - if (buf->size > 0 && out[-1] != separator) - *out++ = separator; - - va_start(ap, nbuf); - for (i = 0; i < nbuf; ++i) { - const char *segment; - size_t segment_len; - - segment = va_arg(ap, const char *); - if (!segment) - continue; - - /* deal with join that references buffer's original content */ - if (segment >= original && segment < original + original_size) { - size_t offset = (segment - original); - segment = buf->ptr + offset; - segment_len = original_size - offset; - } else { - segment_len = strlen(segment); - } - - /* skip leading separators */ - if (out > buf->ptr && out[-1] == separator) - while (segment_len > 0 && *segment == separator) { - segment++; - segment_len--; - } - - /* copy over next buffer */ - if (segment_len > 0) { - memmove(out, segment, segment_len); - out += segment_len; - } - - /* append trailing separator (except for last item) */ - if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) - *out++ = separator; - } - va_end(ap); - - /* set size based on num characters actually written */ - buf->size = out - buf->ptr; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_join( - git_str *buf, - char separator, - const char *str_a, - const char *str_b) -{ - size_t strlen_a = str_a ? strlen(str_a) : 0; - size_t strlen_b = strlen(str_b); - size_t alloc_len; - int need_sep = 0; - ssize_t offset_a = -1; - - /* not safe to have str_b point internally to the buffer */ - if (buf->size) - GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size); - - /* figure out if we need to insert a separator */ - if (separator && strlen_a) { - while (*str_b == separator) { str_b++; strlen_b--; } - if (str_a[strlen_a - 1] != separator) - need_sep = 1; - } - - /* str_a could be part of the buffer */ - if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size) - offset_a = str_a - buf->ptr; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - ENSURE_SIZE(buf, alloc_len); - - /* fix up internal pointers */ - if (offset_a >= 0) - str_a = buf->ptr + offset_a; - - /* do the actual copying */ - if (offset_a != 0 && str_a) - memmove(buf->ptr, str_a, strlen_a); - if (need_sep) - buf->ptr[strlen_a] = separator; - memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); - - buf->size = strlen_a + strlen_b + need_sep; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_join3( - git_str *buf, - char separator, - const char *str_a, - const char *str_b, - const char *str_c) -{ - size_t len_a = strlen(str_a), - len_b = strlen(str_b), - len_c = strlen(str_c), - len_total; - int sep_a = 0, sep_b = 0; - char *tgt; - - /* for this function, disallow pointers into the existing buffer */ - GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size); - GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size); - GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size); - - if (separator) { - if (len_a > 0) { - while (*str_b == separator) { str_b++; len_b--; } - sep_a = (str_a[len_a - 1] != separator); - } - if (len_a > 0 || len_b > 0) - while (*str_c == separator) { str_c++; len_c--; } - if (len_b > 0) - sep_b = (str_b[len_b - 1] != separator); - } - - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1); - ENSURE_SIZE(buf, len_total); - - tgt = buf->ptr; - - if (len_a) { - memcpy(tgt, str_a, len_a); - tgt += len_a; - } - if (sep_a) - *tgt++ = separator; - if (len_b) { - memcpy(tgt, str_b, len_b); - tgt += len_b; - } - if (sep_b) - *tgt++ = separator; - if (len_c) - memcpy(tgt, str_c, len_c); - - buf->size = len_a + sep_a + len_b + sep_b + len_c; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -void git_str_rtrim(git_str *buf) -{ - while (buf->size > 0) { - if (!git__isspace(buf->ptr[buf->size - 1])) - break; - - buf->size--; - } - - if (buf->asize > buf->size) - buf->ptr[buf->size] = '\0'; -} - -int git_str_cmp(const git_str *a, const git_str *b) -{ - int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); - return (result != 0) ? result : - (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; -} - -int git_str_splice( - git_str *buf, - size_t where, - size_t nb_to_remove, - const char *data, - size_t nb_to_insert) -{ - char *splice_loc; - size_t new_size, alloc_size; - - GIT_ASSERT(buf); - GIT_ASSERT(where <= buf->size); - GIT_ASSERT(nb_to_remove <= buf->size - where); - - splice_loc = buf->ptr + where; - - /* Ported from git.git - * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 - */ - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1); - ENSURE_SIZE(buf, alloc_size); - - memmove(splice_loc + nb_to_insert, - splice_loc + nb_to_remove, - buf->size - where - nb_to_remove); - - memcpy(splice_loc, data, nb_to_insert); - - buf->size = new_size; - buf->ptr[buf->size] = '\0'; - return 0; -} - -/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ -int git_str_quote(git_str *buf) -{ - const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; - git_str quoted = GIT_STR_INIT; - size_t i = 0; - bool quote = false; - int error = 0; - - /* walk to the first char that needs quoting */ - if (buf->size && buf->ptr[0] == '!') - quote = true; - - for (i = 0; !quote && i < buf->size; i++) { - if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || - buf->ptr[i] < ' ' || buf->ptr[i] > '~') { - quote = true; - break; - } - } - - if (!quote) - goto done; - - git_str_putc("ed, '"'); - git_str_put("ed, buf->ptr, i); - - for (; i < buf->size; i++) { - /* whitespace - use the map above, which is ordered by ascii value */ - if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { - git_str_putc("ed, '\\'); - git_str_putc("ed, whitespace[buf->ptr[i] - '\a']); - } - - /* double quote and backslash must be escaped */ - else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { - git_str_putc("ed, '\\'); - git_str_putc("ed, buf->ptr[i]); - } - - /* escape anything unprintable as octal */ - else if (buf->ptr[i] != ' ' && - (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { - git_str_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); - } - - /* yay, printable! */ - else { - git_str_putc("ed, buf->ptr[i]); - } - } - - git_str_putc("ed, '"'); - - if (git_str_oom("ed)) { - error = -1; - goto done; - } - - git_str_swap("ed, buf); - -done: - git_str_dispose("ed); - return error; -} - -/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ -int git_str_unquote(git_str *buf) -{ - size_t i, j; - char ch; - - git_str_rtrim(buf); - - if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') - goto invalid; - - for (i = 0, j = 1; j < buf->size-1; i++, j++) { - ch = buf->ptr[j]; - - if (ch == '\\') { - if (j == buf->size-2) - goto invalid; - - ch = buf->ptr[++j]; - - switch (ch) { - /* \" or \\ simply copy the char in */ - case '"': case '\\': - break; - - /* add the appropriate escaped char */ - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - - /* \xyz digits convert to the char*/ - case '0': case '1': case '2': case '3': - if (j == buf->size-3) { - git_error_set(GIT_ERROR_INVALID, - "truncated quoted character \\%c", ch); - return -1; - } - - if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || - buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { - git_error_set(GIT_ERROR_INVALID, - "truncated quoted character \\%c%c%c", - buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); - return -1; - } - - ch = ((buf->ptr[j] - '0') << 6) | - ((buf->ptr[j+1] - '0') << 3) | - (buf->ptr[j+2] - '0'); - j += 2; - break; - - default: - git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch); - return -1; - } - } - - buf->ptr[i] = ch; - } - - buf->ptr[i] = '\0'; - buf->size = i; - - return 0; - -invalid: - git_error_set(GIT_ERROR_INVALID, "invalid quoted line"); - return -1; -} - -int git_str_puts_escaped( - git_str *buf, - const char *string, - const char *esc_chars, - const char *esc_with) -{ - const char *scan; - size_t total = 0, esc_len = strlen(esc_with), count, alloclen; - - if (!string) - return 0; - - for (scan = string; *scan; ) { - /* count run of non-escaped characters */ - count = strcspn(scan, esc_chars); - total += count; - scan += count; - /* count run of escaped characters */ - count = strspn(scan, esc_chars); - total += count * (esc_len + 1); - scan += count; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1); - if (git_str_grow_by(buf, alloclen) < 0) - return -1; - - for (scan = string; *scan; ) { - count = strcspn(scan, esc_chars); - - memmove(buf->ptr + buf->size, scan, count); - scan += count; - buf->size += count; - - for (count = strspn(scan, esc_chars); count > 0; --count) { - /* copy escape sequence */ - memmove(buf->ptr + buf->size, esc_with, esc_len); - buf->size += esc_len; - /* copy character to be escaped */ - buf->ptr[buf->size] = *scan; - buf->size++; - scan++; - } - } - - buf->ptr[buf->size] = '\0'; - - return 0; -} - -void git_str_unescape(git_str *buf) -{ - buf->size = git__unescape(buf->ptr); -} - -int git_str_crlf_to_lf(git_str *tgt, const git_str *src) -{ - const char *scan = src->ptr; - const char *scan_end = src->ptr + src->size; - const char *next = memchr(scan, '\r', src->size); - size_t new_size; - char *out; - - GIT_ASSERT(tgt != src); - - if (!next) - return git_str_set(tgt, src->ptr, src->size); - - /* reduce reallocs while in the loop */ - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1); - if (git_str_grow(tgt, new_size) < 0) - return -1; - - out = tgt->ptr; - tgt->size = 0; - - /* Find the next \r and copy whole chunk up to there to tgt */ - for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { - if (next > scan) { - size_t copylen = (size_t)(next - scan); - memcpy(out, scan, copylen); - out += copylen; - } - - /* Do not drop \r unless it is followed by \n */ - if (next + 1 == scan_end || next[1] != '\n') - *out++ = '\r'; - } - - /* Copy remaining input into dest */ - if (scan < scan_end) { - size_t remaining = (size_t)(scan_end - scan); - memcpy(out, scan, remaining); - out += remaining; - } - - tgt->size = (size_t)(out - tgt->ptr); - tgt->ptr[tgt->size] = '\0'; - - return 0; -} - -int git_str_lf_to_crlf(git_str *tgt, const git_str *src) -{ - const char *start = src->ptr; - const char *end = start + src->size; - const char *scan = start; - const char *next = memchr(scan, '\n', src->size); - size_t alloclen; - - GIT_ASSERT(tgt != src); - - if (!next) - return git_str_set(tgt, src->ptr, src->size); - - /* attempt to reduce reallocs while in the loop */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - if (git_str_grow(tgt, alloclen) < 0) - return -1; - tgt->size = 0; - - for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { - size_t copylen = next - scan; - - /* if we find mixed line endings, carry on */ - if (copylen && next[-1] == '\r') - copylen--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3); - if (git_str_grow_by(tgt, alloclen) < 0) - return -1; - - if (copylen) { - memcpy(tgt->ptr + tgt->size, scan, copylen); - tgt->size += copylen; - } - - tgt->ptr[tgt->size++] = '\r'; - tgt->ptr[tgt->size++] = '\n'; - } - - tgt->ptr[tgt->size] = '\0'; - return git_str_put(tgt, scan, end - scan); -} - -int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count) -{ - size_t i; - const char *str, *pfx; - - git_str_clear(buf); - - if (!strings || !count) - return 0; - - /* initialize common prefix to first string */ - if (git_str_sets(buf, strings[0]) < 0) - return -1; - - /* go through the rest of the strings, truncating to shared prefix */ - for (i = 1; i < count; ++i) { - - for (str = strings[i], pfx = buf->ptr; - *str && *str == *pfx; - str++, pfx++) - /* scanning */; - - git_str_truncate(buf, pfx - buf->ptr); - - if (!buf->size) - break; - } - - return 0; -} - -int git_str_is_binary(const git_str *buf) -{ - const char *scan = buf->ptr, *end = buf->ptr + buf->size; - git_str_bom_t bom; - int printable = 0, nonprintable = 0; - - scan += git_str_detect_bom(&bom, buf); - - if (bom > GIT_STR_BOM_UTF8) - return 1; - - while (scan < end) { - unsigned char c = *scan++; - - /* Printable characters are those above SPACE (0x1F) excluding DEL, - * and including BS, ESC and FF. - */ - if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014') - printable++; - else if (c == '\0') - return true; - else if (!git__isspace(c)) - nonprintable++; - } - - return ((printable >> 7) < nonprintable); -} - -int git_str_contains_nul(const git_str *buf) -{ - return (memchr(buf->ptr, '\0', buf->size) != NULL); -} - -int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf) -{ - const char *ptr; - size_t len; - - *bom = GIT_STR_BOM_NONE; - /* need at least 2 bytes to look for any BOM */ - if (buf->size < 2) - return 0; - - ptr = buf->ptr; - len = buf->size; - - switch (*ptr++) { - case 0: - if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') { - *bom = GIT_STR_BOM_UTF32_BE; - return 4; - } - break; - case '\xEF': - if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') { - *bom = GIT_STR_BOM_UTF8; - return 3; - } - break; - case '\xFE': - if (*ptr == '\xFF') { - *bom = GIT_STR_BOM_UTF16_BE; - return 2; - } - break; - case '\xFF': - if (*ptr != '\xFE') - break; - if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) { - *bom = GIT_STR_BOM_UTF32_LE; - return 4; - } else { - *bom = GIT_STR_BOM_UTF16_LE; - return 2; - } - break; - default: - break; - } - - return 0; -} - -bool git_str_gather_text_stats( - git_str_text_stats *stats, const git_str *buf, bool skip_bom) -{ - const char *scan = buf->ptr, *end = buf->ptr + buf->size; - int skip; - - memset(stats, 0, sizeof(*stats)); - - /* BOM detection */ - skip = git_str_detect_bom(&stats->bom, buf); - if (skip_bom) - scan += skip; - - /* Ignore EOF character */ - if (buf->size > 0 && end[-1] == '\032') - end--; - - /* Counting loop */ - while (scan < end) { - unsigned char c = *scan++; - - if (c > 0x1F && c != 0x7F) - stats->printable++; - else switch (c) { - case '\0': - stats->nul++; - stats->nonprintable++; - break; - case '\n': - stats->lf++; - break; - case '\r': - stats->cr++; - if (scan < end && *scan == '\n') - stats->crlf++; - break; - case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ - stats->printable++; - break; - default: - stats->nonprintable++; - break; - } - } - - /* Treat files with a bare CR as binary */ - return (stats->cr != stats->crlf || stats->nul > 0 || - ((stats->printable >> 7) < stats->nonprintable)); -} diff --git a/src/str.h b/src/str.h deleted file mode 100644 index ef769ce2f..000000000 --- a/src/str.h +++ /dev/null @@ -1,357 +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_str_h__ -#define INCLUDE_str_h__ - -#include "common.h" - -struct git_str { - char *ptr; - size_t asize; - size_t size; -}; - -typedef enum { - GIT_STR_BOM_NONE = 0, - GIT_STR_BOM_UTF8 = 1, - GIT_STR_BOM_UTF16_LE = 2, - GIT_STR_BOM_UTF16_BE = 3, - GIT_STR_BOM_UTF32_LE = 4, - GIT_STR_BOM_UTF32_BE = 5 -} git_str_bom_t; - -typedef struct { - git_str_bom_t bom; /* BOM found at head of text */ - unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */ - unsigned int printable, nonprintable; /* These are just approximations! */ -} git_str_text_stats; - -extern char git_str__initstr[]; -extern char git_str__oom[]; - -/* Use to initialize string buffer structure when git_str is on stack */ -#define GIT_STR_INIT { git_str__initstr, 0, 0 } - -/** - * Static initializer for git_str from static string buffer - */ -#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) } - -GIT_INLINE(bool) git_str_is_allocated(const git_str *str) -{ - return (str->ptr != NULL && str->asize > 0); -} - -/** - * Initialize a git_str structure. - * - * For the cases where GIT_STR_INIT cannot be used to do static - * initialization. - */ -extern int git_str_init(git_str *str, size_t initial_size); - -extern void git_str_dispose(git_str *str); - -/** - * Resize the string buffer allocation to make more space. - * - * This will attempt to grow the string buffer to accommodate the target - * size. The bstring buffer's `ptr` will be replaced with a newly - * allocated block of data. Be careful so that memory allocated by the - * caller is not lost. As a special variant, if you pass `target_size` as - * 0 and the memory is not allocated by libgit2, this will allocate a new - * buffer of size `size` and copy the external data into it. - * - * Currently, this will never shrink a buffer, only expand it. - * - * If the allocation fails, this will return an error and the buffer will be - * marked as invalid for future operations, invaliding the contents. - * - * @param str The buffer to be resized; may or may not be allocated yet - * @param target_size The desired available size - * @return 0 on success, -1 on allocation failure - */ -int git_str_grow(git_str *str, size_t target_size); - -/** - * Resize the buffer allocation to make more space. - * - * This will attempt to grow the string buffer to accommodate the - * additional size. It is similar to `git_str_grow`, but performs the - * new size calculation, checking for overflow. - * - * Like `git_str_grow`, if this is a user-supplied string buffer, - * this will allocate a new string uffer. - */ -extern int git_str_grow_by(git_str *str, size_t additional_size); - -/** - * Attempt to grow the buffer to hold at least `target_size` bytes. - * - * If the allocation fails, this will return an error. If `mark_oom` is - * true, this will mark the string buffer as invalid for future - * operations; if false, existing string buffer content will be preserved, - * but calling code must handle that string buffer was not expanded. If - * `preserve_external` is true, then any existing data pointed to be - * `ptr` even if `asize` is zero will be copied into the newly allocated - * string buffer. - */ -extern int git_str_try_grow( - git_str *str, size_t target_size, bool mark_oom); - -extern void git_str_swap(git_str *str_a, git_str *str_b); -extern char *git_str_detach(git_str *str); -extern int git_str_attach(git_str *str, char *ptr, size_t asize); - -/* Populates a `git_str` where the contents are not "owned" by the string - * buffer, and calls to `git_str_dispose` will not free the given str. - */ -extern void git_str_attach_notowned( - git_str *str, const char *ptr, size_t size); - -/** - * Test if there have been any reallocation failures with this git_str. - * - * Any function that writes to a git_str can fail due to memory allocation - * issues. If one fails, the git_str will be marked with an OOM error and - * further calls to modify the string buffer will fail. Check - * git_str_oom() at the end of your sequence and it will be true if you - * ran out of memory at any point with that string buffer. - * - * @return false if no error, true if allocation error - */ -GIT_INLINE(bool) git_str_oom(const git_str *str) -{ - return (str->ptr == git_str__oom); -} - -/* - * Functions below that return int value error codes will return 0 on - * success or -1 on failure (which generally means an allocation failed). - * Using a git_str where the allocation has failed with result in -1 from - * all further calls using that string buffer. As a result, you can - * ignore the return code of these functions and call them in a series - * then just call git_str_oom at the end. - */ - -int git_str_set(git_str *str, const void *data, size_t datalen); - -int git_str_sets(git_str *str, const char *string); -int git_str_putc(git_str *str, char c); -int git_str_putcn(git_str *str, char c, size_t len); -int git_str_put(git_str *str, const char *data, size_t len); -int git_str_puts(git_str *str, const char *string); -int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); -int git_str_vprintf(git_str *str, const char *format, va_list ap); -void git_str_clear(git_str *str); -void git_str_consume_bytes(git_str *str, size_t len); -void git_str_consume(git_str *str, const char *end); -void git_str_truncate(git_str *str, size_t len); -void git_str_shorten(git_str *str, size_t amount); -void git_str_truncate_at_char(git_str *path, char separator); -void git_str_rtruncate_at_char(git_str *path, char separator); - -/** General join with separator */ -int git_str_join_n(git_str *str, char separator, int len, ...); -/** Fast join of two strings - first may legally point into `str` data */ -int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b); -/** Fast join of three strings - cannot reference `str` data */ -int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c); - -/** - * Join two strings as paths, inserting a slash between as needed. - * @return 0 on success, -1 on failure - */ -GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b) -{ - return git_str_join(str, '/', a, b); -} - -GIT_INLINE(const char *) git_str_cstr(const git_str *str) -{ - return str->ptr; -} - -GIT_INLINE(size_t) git_str_len(const git_str *str) -{ - return str->size; -} - -int git_str_copy_cstr(char *data, size_t datasize, const git_str *str); - -#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1) - -GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch) -{ - ssize_t idx = (ssize_t)str->size - 1; - while (idx >= 0 && str->ptr[idx] == ch) idx--; - while (idx >= 0 && str->ptr[idx] != ch) idx--; - return idx; -} - -GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch) -{ - ssize_t idx = (ssize_t)str->size - 1; - while (idx >= 0 && str->ptr[idx] != ch) idx--; - return idx; -} - -GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch) -{ - void *found = memchr(str->ptr, ch, str->size); - return found ? (ssize_t)((const char *)found - str->ptr) : -1; -} - -/* Remove whitespace from the end of the string buffer */ -void git_str_rtrim(git_str *str); - -int git_str_cmp(const git_str *a, const git_str *b); - -/* Quote and unquote a string buffer as specified in - * http://marc.info/?l=git&m=112927316408690&w=2 - */ -int git_str_quote(git_str *str); -int git_str_unquote(git_str *str); - -/* Write data as a hex string */ -int git_str_encode_hexstr(git_str *str, const char *data, size_t len); - -/* Write data as base64 encoded in string buffer */ -int git_str_encode_base64(git_str *str, const char *data, size_t len); -/* Decode the given bas64 and write the result to the string buffer */ -int git_str_decode_base64(git_str *str, const char *base64, size_t len); - -/* Write data as "base85" encoded in string buffer */ -int git_str_encode_base85(git_str *str, const char *data, size_t len); -/* Decode the given "base85" and write the result to the string buffer */ -int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len); - -/* - * Decode the given percent-encoded string and write the result to the - * string buffer. - */ -int git_str_decode_percent(git_str *str, const char *encoded, size_t len); - -/* - * Insert, remove or replace a portion of the string buffer. - * - * @param str The string buffer to work with - * - * @param where The location in the string buffer where the transformation - * should be applied. - * - * @param nb_to_remove The number of chars to be removed. 0 to not - * remove any character in the string buffer. - * - * @param data A pointer to the data which should be inserted. - * - * @param nb_to_insert The number of chars to be inserted. 0 to not - * insert any character from the string buffer. - * - * @return 0 or an error code. - */ -int git_str_splice( - git_str *str, - size_t where, - size_t nb_to_remove, - const char *data, - size_t nb_to_insert); - -/** - * Append string to string buffer, prefixing each character from - * `esc_chars` with `esc_with` string. - * - * @param str String buffer to append data to - * @param string String to escape and append - * @param esc_chars Characters to be escaped - * @param esc_with String to insert in from of each found character - * @return 0 on success, <0 on failure (probably allocation problem) - */ -extern int git_str_puts_escaped( - git_str *str, - const char *string, - const char *esc_chars, - const char *esc_with); - -/** - * Append string escaping characters that are regex special - */ -GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) -{ - return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); -} - -/** - * Unescape all characters in a string buffer in place - * - * I.e. remove backslashes - */ -extern void git_str_unescape(git_str *str); - -/** - * Replace all \r\n with \n. - * - * @return 0 on success, -1 on memory error - */ -extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src); - -/** - * Replace all \n with \r\n. Does not modify existing \r\n. - * - * @return 0 on success, -1 on memory error - */ -extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src); - -/** - * Fill string buffer with the common prefix of a array of strings - * - * String buffer will be set to empty if there is no common prefix - */ -extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count); - -/** - * Check if a string buffer begins with a UTF BOM - * - * @param bom Set to the type of BOM detected or GIT_BOM_NONE - * @param str String buffer in which to check the first bytes for a BOM - * @return Number of bytes of BOM data (or 0 if no BOM found) - */ -extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str); - -/** - * Gather stats for a piece of text - * - * Fill the `stats` structure with counts of unreadable characters, carriage - * returns, etc, so it can be used in heuristics. This automatically skips - * a trailing EOF (\032 character). Also it will look for a BOM at the - * start of the text and can be told to skip that as well. - * - * @param stats Structure to be filled in - * @param str Text to process - * @param skip_bom Exclude leading BOM from stats if true - * @return Does the string buffer heuristically look like binary data - */ -extern bool git_str_gather_text_stats( - git_str_text_stats *stats, const git_str *str, bool skip_bom); - -/** -* Check quickly if string buffer looks like it contains binary data -* -* @param str string buffer to check -* @return 1 if string buffer looks like non-text data -*/ -int git_str_is_binary(const git_str *str); - -/** -* Check quickly if buffer contains a NUL byte -* -* @param str string buffer to check -* @return 1 if string buffer contains a NUL byte -*/ -int git_str_contains_nul(const git_str *str); - -#endif diff --git a/src/strarray.c b/src/strarray.c deleted file mode 100644 index 2f9b77cc2..000000000 --- a/src/strarray.c +++ /dev/null @@ -1,64 +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 "util.h" - -#include "common.h" - -int git_strarray_copy(git_strarray *tgt, const git_strarray *src) -{ - size_t i; - - GIT_ASSERT_ARG(tgt); - GIT_ASSERT_ARG(src); - - memset(tgt, 0, sizeof(*tgt)); - - if (!src->count) - return 0; - - tgt->strings = git__calloc(src->count, sizeof(char *)); - GIT_ERROR_CHECK_ALLOC(tgt->strings); - - for (i = 0; i < src->count; ++i) { - if (!src->strings[i]) - continue; - - tgt->strings[tgt->count] = git__strdup(src->strings[i]); - if (!tgt->strings[tgt->count]) { - git_strarray_dispose(tgt); - memset(tgt, 0, sizeof(*tgt)); - return -1; - } - - tgt->count++; - } - - return 0; -} - -void git_strarray_dispose(git_strarray *array) -{ - size_t i; - - if (array == NULL) - return; - - for (i = 0; i < array->count; ++i) - git__free(array->strings[i]); - - git__free(array->strings); - - memset(array, 0, sizeof(*array)); -} - -#ifndef GIT_DEPRECATE_HARD -void git_strarray_free(git_strarray *array) -{ - git_strarray_dispose(array); -} -#endif diff --git a/src/stream.h b/src/stream.h deleted file mode 100644 index f16b026fb..000000000 --- a/src/stream.h +++ /dev/null @@ -1,86 +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_stream_h__ -#define INCLUDE_stream_h__ - -#include "common.h" -#include "git2/sys/stream.h" - -GIT_INLINE(int) git_stream_connect(git_stream *st) -{ - return st->connect(st); -} - -GIT_INLINE(int) git_stream_is_encrypted(git_stream *st) -{ - return st->encrypted; -} - -GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) -{ - if (!st->encrypted) { - git_error_set(GIT_ERROR_INVALID, "an unencrypted stream does not have a certificate"); - return -1; - } - - return st->certificate(out, st); -} - -GIT_INLINE(int) git_stream_supports_proxy(git_stream *st) -{ - return st->proxy_support; -} - -GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const git_proxy_options *proxy_opts) -{ - if (!st->proxy_support) { - git_error_set(GIT_ERROR_INVALID, "proxy not supported on this stream"); - return -1; - } - - return st->set_proxy(st, proxy_opts); -} - -GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) -{ - return st->read(st, data, len); -} - -GIT_INLINE(ssize_t) git_stream_write(git_stream *st, const char *data, size_t len, int flags) -{ - return st->write(st, data, len, flags); -} - -GIT_INLINE(int) git_stream__write_full(git_stream *st, const char *data, size_t len, int flags) -{ - size_t total_written = 0; - - while (total_written < len) { - ssize_t written = git_stream_write(st, data + total_written, len - total_written, flags); - if (written <= 0) - return -1; - - total_written += written; - } - - return 0; -} - -GIT_INLINE(int) git_stream_close(git_stream *st) -{ - return st->close(st); -} - -GIT_INLINE(void) git_stream_free(git_stream *st) -{ - if (!st) - return; - - st->free(st); -} - -#endif diff --git a/src/streams/mbedtls.c b/src/streams/mbedtls.c deleted file mode 100644 index 0cf5c8af1..000000000 --- a/src/streams/mbedtls.c +++ /dev/null @@ -1,482 +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 "streams/mbedtls.h" - -#ifdef GIT_MBEDTLS - -#include - -#include "runtime.h" -#include "stream.h" -#include "streams/socket.h" -#include "netops.h" -#include "git2/transport.h" -#include "util.h" - -#ifndef GIT_DEFAULT_CERT_LOCATION -#define GIT_DEFAULT_CERT_LOCATION NULL -#endif - -/* Work around C90-conformance issues */ -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# if defined(_MSC_VER) -# define inline __inline -# elif defined(__GNUC__) -# define inline __inline__ -# else -# define inline -# endif -#endif - -#include -#include -#include -#include -#include - -#undef inline - -#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" -#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30 - -static mbedtls_ssl_config *git__ssl_conf; -static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT]; -static mbedtls_entropy_context *mbedtls_entropy; - -/** - * This function aims to clean-up the SSL context which - * we allocated. - */ -static void shutdown_ssl(void) -{ - if (git__ssl_conf) { - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - mbedtls_ctr_drbg_free(git__ssl_conf->p_rng); - git__free(git__ssl_conf->p_rng); - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; - } - if (mbedtls_entropy) { - mbedtls_entropy_free(mbedtls_entropy); - git__free(mbedtls_entropy); - mbedtls_entropy = NULL; - } -} - -int git_mbedtls_stream_global_init(void) -{ - int loaded = 0; - char *crtpath = GIT_DEFAULT_CERT_LOCATION; - struct stat statbuf; - mbedtls_ctr_drbg_context *ctr_drbg = NULL; - - size_t ciphers_known = 0; - char *cipher_name = NULL; - char *cipher_string = NULL; - char *cipher_string_tmp = NULL; - - git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config)); - GIT_ERROR_CHECK_ALLOC(git__ssl_conf); - - mbedtls_ssl_config_init(git__ssl_conf); - if (mbedtls_ssl_config_defaults(git__ssl_conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT) != 0) { - git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS"); - goto cleanup; - } - - /* configure TLSv1 */ - mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); - - /* verify_server_cert is responsible for making the check. - * OPTIONAL because REQUIRED drops the certificate as soon as the check - * is made, so we can never see the certificate and override it. */ - mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); - - /* set the list of allowed ciphersuites */ - ciphers_known = 0; - cipher_string = cipher_string_tmp = git__strdup(GIT_SSL_DEFAULT_CIPHERS); - GIT_ERROR_CHECK_ALLOC(cipher_string); - - while ((cipher_name = git__strtok(&cipher_string_tmp, ":")) != NULL) { - int cipherid = mbedtls_ssl_get_ciphersuite_id(cipher_name); - if (cipherid == 0) continue; - - if (ciphers_known >= ARRAY_SIZE(ciphers_list)) { - git_error_set(GIT_ERROR_SSL, "out of cipher list space"); - goto cleanup; - } - - ciphers_list[ciphers_known++] = cipherid; - } - git__free(cipher_string); - - if (!ciphers_known) { - git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); - goto cleanup; - } - mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); - - /* Seeding the random number generator */ - mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context)); - GIT_ERROR_CHECK_ALLOC(mbedtls_entropy); - - mbedtls_entropy_init(mbedtls_entropy); - - ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context)); - GIT_ERROR_CHECK_ALLOC(ctr_drbg); - - mbedtls_ctr_drbg_init(ctr_drbg); - - if (mbedtls_ctr_drbg_seed(ctr_drbg, - mbedtls_entropy_func, - mbedtls_entropy, NULL, 0) != 0) { - git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool"); - goto cleanup; - } - - mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); - - /* load default certificates */ - if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) - loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0); - if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) - loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0); - - return git_runtime_shutdown_register(shutdown_ssl); - -cleanup: - mbedtls_ctr_drbg_free(ctr_drbg); - git__free(ctr_drbg); - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; - - return -1; -} - -static int bio_read(void *b, unsigned char *buf, size_t len) -{ - git_stream *io = (git_stream *) b; - return (int) git_stream_read(io, buf, min(len, INT_MAX)); -} - -static int bio_write(void *b, const unsigned char *buf, size_t len) -{ - git_stream *io = (git_stream *) b; - return (int) git_stream_write(io, (const char *)buf, min(len, INT_MAX), 0); -} - -static int ssl_set_error(mbedtls_ssl_context *ssl, int error) -{ - char errbuf[512]; - int ret = -1; - - GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_READ); - GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_WRITE); - - if (error != 0) - mbedtls_strerror( error, errbuf, 512 ); - - switch(error) { - case 0: - git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); - break; - - case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); - ret = GIT_ECERTIFICATE; - break; - - default: - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); - } - - return ret; -} - -static int ssl_teardown(mbedtls_ssl_context *ssl) -{ - int ret = 0; - - ret = mbedtls_ssl_close_notify(ssl); - if (ret < 0) - ret = ssl_set_error(ssl, ret); - - mbedtls_ssl_free(ssl); - return ret; -} - -static int verify_server_cert(mbedtls_ssl_context *ssl) -{ - int ret = -1; - - if ((ret = mbedtls_ssl_get_verify_result(ssl)) != 0) { - char vrfy_buf[512]; - int len = mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "", ret); - if (len >= 1) vrfy_buf[len - 1] = '\0'; /* Remove trailing \n */ - git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid: %#04x - %s", ret, vrfy_buf); - return GIT_ECERTIFICATE; - } - - return 0; -} - -typedef struct { - git_stream parent; - git_stream *io; - int owned; - bool connected; - char *host; - mbedtls_ssl_context *ssl; - git_cert_x509 cert_info; -} mbedtls_stream; - - -static int mbedtls_connect(git_stream *stream) -{ - int ret; - mbedtls_stream *st = (mbedtls_stream *) stream; - - if (st->owned && (ret = git_stream_connect(st->io)) < 0) - return ret; - - st->connected = true; - - mbedtls_ssl_set_hostname(st->ssl, st->host); - - mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL); - - if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0) - return ssl_set_error(st->ssl, ret); - - return verify_server_cert(st->ssl); -} - -static int mbedtls_certificate(git_cert **out, git_stream *stream) -{ - unsigned char *encoded_cert; - mbedtls_stream *st = (mbedtls_stream *) stream; - - const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(st->ssl); - if (!cert) { - git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); - return -1; - } - - /* Retrieve the length of the certificate first */ - if (cert->raw.len == 0) { - git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); - return -1; - } - - encoded_cert = git__malloc(cert->raw.len); - GIT_ERROR_CHECK_ALLOC(encoded_cert); - memcpy(encoded_cert, cert->raw.p, cert->raw.len); - - st->cert_info.parent.cert_type = GIT_CERT_X509; - st->cert_info.data = encoded_cert; - st->cert_info.len = cert->raw.len; - - *out = &st->cert_info.parent; - - return 0; -} - -static int mbedtls_set_proxy(git_stream *stream, const git_proxy_options *proxy_options) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - - return git_stream_set_proxy(st->io, proxy_options); -} - -static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t len, int flags) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - int written; - - GIT_UNUSED(flags); - - /* - * `mbedtls_ssl_write` can only represent INT_MAX bytes - * written via its return value. We thus need to clamp - * the maximum number of bytes written. - */ - len = min(len, INT_MAX); - - if ((written = mbedtls_ssl_write(st->ssl, (const unsigned char *)data, len)) <= 0) - return ssl_set_error(st->ssl, written); - - return written; -} - -static ssize_t mbedtls_stream_read(git_stream *stream, void *data, size_t len) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - int ret; - - if ((ret = mbedtls_ssl_read(st->ssl, (unsigned char *)data, len)) <= 0) - ssl_set_error(st->ssl, ret); - - return ret; -} - -static int mbedtls_stream_close(git_stream *stream) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - int ret = 0; - - if (st->connected && (ret = ssl_teardown(st->ssl)) != 0) - return -1; - - st->connected = false; - - return st->owned ? git_stream_close(st->io) : 0; -} - -static void mbedtls_stream_free(git_stream *stream) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - - if (st->owned) - git_stream_free(st->io); - - git__free(st->host); - git__free(st->cert_info.data); - mbedtls_ssl_free(st->ssl); - git__free(st->ssl); - git__free(st); -} - -static int mbedtls_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) -{ - mbedtls_stream *st; - int error; - - st = git__calloc(1, sizeof(mbedtls_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->io = in; - st->owned = owned; - - st->ssl = git__malloc(sizeof(mbedtls_ssl_context)); - GIT_ERROR_CHECK_ALLOC(st->ssl); - mbedtls_ssl_init(st->ssl); - if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) { - git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); - error = -1; - goto out_err; - } - - st->host = git__strdup(host); - GIT_ERROR_CHECK_ALLOC(st->host); - - st->parent.version = GIT_STREAM_VERSION; - st->parent.encrypted = 1; - st->parent.proxy_support = git_stream_supports_proxy(st->io); - st->parent.connect = mbedtls_connect; - st->parent.certificate = mbedtls_certificate; - st->parent.set_proxy = mbedtls_set_proxy; - st->parent.read = mbedtls_stream_read; - st->parent.write = mbedtls_stream_write; - st->parent.close = mbedtls_stream_close; - st->parent.free = mbedtls_stream_free; - - *out = (git_stream *) st; - return 0; - -out_err: - mbedtls_ssl_free(st->ssl); - git_stream_close(st->io); - git_stream_free(st->io); - git__free(st); - - return error; -} - -int git_mbedtls_stream_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return mbedtls_stream_wrap(out, in, host, 0); -} - -int git_mbedtls_stream_new( - git_stream **out, - const char *host, - const char *port) -{ - git_stream *stream; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if ((error = git_socket_stream_new(&stream, host, port)) < 0) - return error; - - if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -int git_mbedtls__set_cert_location(const char *file, const char *path) -{ - int ret = 0; - char errbuf[512]; - mbedtls_x509_crt *cacert; - - GIT_ASSERT_ARG(file || path); - - cacert = git__malloc(sizeof(mbedtls_x509_crt)); - GIT_ERROR_CHECK_ALLOC(cacert); - - mbedtls_x509_crt_init(cacert); - if (file) - ret = mbedtls_x509_crt_parse_file(cacert, file); - if (ret >= 0 && path) - ret = mbedtls_x509_crt_parse_path(cacert, path); - /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */ - if (ret < 0) { - mbedtls_x509_crt_free(cacert); - git__free(cacert); - mbedtls_strerror( ret, errbuf, 512 ); - git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); - return -1; - } - - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); - - return 0; -} - -#else - -#include "stream.h" - -int git_mbedtls_stream_global_init(void) -{ - return 0; -} - -#endif diff --git a/src/streams/mbedtls.h b/src/streams/mbedtls.h deleted file mode 100644 index bcca6dd40..000000000 --- a/src/streams/mbedtls.h +++ /dev/null @@ -1,23 +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_steams_mbedtls_h__ -#define INCLUDE_steams_mbedtls_h__ - -#include "common.h" - -#include "git2/sys/stream.h" - -extern int git_mbedtls_stream_global_init(void); - -#ifdef GIT_MBEDTLS -extern int git_mbedtls__set_cert_location(const char *file, const char *path); - -extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port); -extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host); -#endif - -#endif diff --git a/src/streams/openssl.c b/src/streams/openssl.c deleted file mode 100644 index 89c96780c..000000000 --- a/src/streams/openssl.c +++ /dev/null @@ -1,747 +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 "streams/openssl.h" -#include "streams/openssl_legacy.h" -#include "streams/openssl_dynamic.h" - -#ifdef GIT_OPENSSL - -#include - -#include "common.h" -#include "runtime.h" -#include "settings.h" -#include "posix.h" -#include "stream.h" -#include "streams/socket.h" -#include "netops.h" -#include "git2/transport.h" -#include "git2/sys/openssl.h" - -#ifndef GIT_WIN32 -# include -# include -# include -#endif - -#ifndef GIT_OPENSSL_DYNAMIC -# include -# include -# include -# include -#endif - -SSL_CTX *git__ssl_ctx; - -#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" - - -static BIO_METHOD *git_stream_bio_method; -static int init_bio_method(void); - -/** - * This function aims to clean-up the SSL context which - * we allocated. - */ -static void shutdown_ssl(void) -{ - if (git_stream_bio_method) { - BIO_meth_free(git_stream_bio_method); - git_stream_bio_method = NULL; - } - - if (git__ssl_ctx) { - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; - } -} - -#ifdef VALGRIND -# if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) - -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); - git__free(mem); -} -# else /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ -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) -{ - git__free(mem); -} -# endif /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ -#endif /* VALGRIND */ - -static int openssl_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 to avoid spurious valgrind warnings. - * Don't error on failure; many builds of OpenSSL do not - * allow you to set these functions. - */ - if (!allocators_initialized) { - CRYPTO_set_mem_functions(git_openssl_malloc, - git_openssl_realloc, - git_openssl_free); - allocators_initialized = true; - } -#endif - - OPENSSL_init_ssl(0, NULL); - - /* - * Load SSLv{2,3} and TLSv1 so that we can talk with servers - * which use the SSL hellos, which are often used for - * compatibility. We then disable SSL so we only allow OpenSSL - * to speak TLSv1 to perform the encryption itself. - */ - if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method()))) - goto error; - - SSL_CTX_set_options(git__ssl_ctx, ssl_opts); - SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) - goto error; - - if (!ciphers) - ciphers = GIT_SSL_DEFAULT_CIPHERS; - - if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) - goto error; - - if (init_bio_method() < 0) - goto error; - - return git_runtime_shutdown_register(shutdown_ssl); - -error: - git_error_set(GIT_ERROR_NET, "could not initialize openssl: %s", - ERR_error_string(ERR_get_error(), NULL)); - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; - return -1; -} - -/* - * When we use dynamic loading, we defer OpenSSL initialization until - * it's first used. `openssl_ensure_initialized` will do the work - * under a mutex. - */ -git_mutex openssl_mutex; -bool openssl_initialized; - -int git_openssl_stream_global_init(void) -{ -#ifndef GIT_OPENSSL_DYNAMIC - return openssl_init(); -#else - if (git_mutex_init(&openssl_mutex) != 0) - return -1; - - return 0; -#endif -} - -static int openssl_ensure_initialized(void) -{ -#ifdef GIT_OPENSSL_DYNAMIC - int error = 0; - - if (git_mutex_lock(&openssl_mutex) != 0) - return -1; - - if (!openssl_initialized) { - if ((error = git_openssl_stream_dynamic_init()) == 0) - error = openssl_init(); - - openssl_initialized = true; - } - - error |= git_mutex_unlock(&openssl_mutex); - return error; - -#else - return 0; -#endif -} - -#if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) -int git_openssl_set_locking(void) -{ -# ifdef GIT_THREADS - return 0; -# else - git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); - return -1; -# endif -} -#endif - - -static int bio_create(BIO *b) -{ - BIO_set_init(b, 1); - BIO_set_data(b, NULL); - - return 1; -} - -static int bio_destroy(BIO *b) -{ - if (!b) - return 0; - - BIO_set_data(b, NULL); - - return 1; -} - -static int bio_read(BIO *b, char *buf, int len) -{ - git_stream *io = (git_stream *) BIO_get_data(b); - - return (int) git_stream_read(io, buf, 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); -} - -static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) -{ - GIT_UNUSED(b); - GIT_UNUSED(num); - GIT_UNUSED(ptr); - - if (cmd == BIO_CTRL_FLUSH) - return 1; - - return 0; -} - -static int bio_gets(BIO *b, char *buf, int len) -{ - GIT_UNUSED(b); - GIT_UNUSED(buf); - GIT_UNUSED(len); - return -1; -} - -static int bio_puts(BIO *b, const char *str) -{ - return bio_write(b, str, strlen(str)); -} - -static int init_bio_method(void) -{ - /* Set up the BIO_METHOD we use for wrapping our own stream implementations */ - git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream"); - GIT_ERROR_CHECK_ALLOC(git_stream_bio_method); - - BIO_meth_set_write(git_stream_bio_method, bio_write); - BIO_meth_set_read(git_stream_bio_method, bio_read); - BIO_meth_set_puts(git_stream_bio_method, bio_puts); - BIO_meth_set_gets(git_stream_bio_method, bio_gets); - BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl); - BIO_meth_set_create(git_stream_bio_method, bio_create); - BIO_meth_set_destroy(git_stream_bio_method, bio_destroy); - - return 0; -} - -static int ssl_set_error(SSL *ssl, int error) -{ - int err; - unsigned long e; - - err = SSL_get_error(ssl, error); - - GIT_ASSERT(err != SSL_ERROR_WANT_READ); - GIT_ASSERT(err != SSL_ERROR_WANT_WRITE); - - switch (err) { - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_ACCEPT: - git_error_set(GIT_ERROR_SSL, "SSL error: connection failure"); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - git_error_set(GIT_ERROR_SSL, "SSL error: x509 error"); - break; - case SSL_ERROR_SYSCALL: - e = ERR_get_error(); - if (e > 0) { - char errmsg[256]; - ERR_error_string_n(e, errmsg, sizeof(errmsg)); - git_error_set(GIT_ERROR_NET, "SSL error: %s", errmsg); - break; - } else if (error < 0) { - git_error_set(GIT_ERROR_OS, "SSL error: syscall failure"); - break; - } - git_error_set(GIT_ERROR_SSL, "SSL error: received early EOF"); - return GIT_EEOF; - break; - case SSL_ERROR_SSL: - { - char errmsg[256]; - e = ERR_get_error(); - ERR_error_string_n(e, errmsg, sizeof(errmsg)); - git_error_set(GIT_ERROR_SSL, "SSL error: %s", errmsg); - break; - } - case SSL_ERROR_NONE: - case SSL_ERROR_ZERO_RETURN: - default: - git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); - break; - } - return -1; -} - -static int ssl_teardown(SSL *ssl) -{ - int ret; - - ret = SSL_shutdown(ssl); - if (ret < 0) - ret = ssl_set_error(ssl, ret); - else - ret = 0; - - return ret; -} - -static int check_host_name(const char *name, const char *host) -{ - if (!strcasecmp(name, host)) - return 0; - - if (gitno__match_host(name, host) < 0) - return -1; - - return 0; -} - -static int verify_server_cert(SSL *ssl, const char *host) -{ - X509 *cert = NULL; - X509_NAME *peer_name; - ASN1_STRING *str; - unsigned char *peer_cn = NULL; - int matched = -1, type = GEN_DNS; - GENERAL_NAMES *alts; - struct in6_addr addr6; - struct in_addr addr4; - void *addr = NULL; - int i = -1, j, error = 0; - - if (SSL_get_verify_result(ssl) != X509_V_OK) { - git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid"); - return GIT_ECERTIFICATE; - } - - /* Try to parse the host as an IP address to see if it is */ - if (p_inet_pton(AF_INET, host, &addr4)) { - type = GEN_IPADD; - addr = &addr4; - } else { - if (p_inet_pton(AF_INET6, host, &addr6)) { - type = GEN_IPADD; - addr = &addr6; - } - } - - - cert = SSL_get_peer_certificate(ssl); - if (!cert) { - error = -1; - git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); - goto cleanup; - } - - /* Check the alternative names */ - alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); - if (alts) { - int num; - - num = sk_GENERAL_NAME_num(alts); - for (i = 0; i < num && matched != 1; i++) { - const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); - const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5); - size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); - - /* Skip any names of a type we're not looking for */ - if (gn->type != type) - continue; - - if (type == GEN_DNS) { - /* If it contains embedded NULs, don't even try */ - if (memchr(name, '\0', namelen)) - continue; - - if (check_host_name(name, host) < 0) - matched = 0; - else - matched = 1; - } else if (type == GEN_IPADD) { - /* Here name isn't so much a name but a binary representation of the IP */ - matched = addr && !!memcmp(name, addr, namelen); - } - } - } - GENERAL_NAMES_free(alts); - - if (matched == 0) - goto cert_fail_name; - - if (matched == 1) { - goto cleanup; - } - - /* If no alternative names are available, check the common name */ - peer_name = X509_get_subject_name(cert); - if (peer_name == NULL) - goto on_error; - - if (peer_name) { - /* Get the index of the last CN entry */ - while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) - i = j; - } - - if (i < 0) - goto on_error; - - str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); - if (str == NULL) - goto on_error; - - /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ - if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { - int size = ASN1_STRING_length(str); - - if (size > 0) { - peer_cn = OPENSSL_malloc(size + 1); - GIT_ERROR_CHECK_ALLOC(peer_cn); - memcpy(peer_cn, ASN1_STRING_get0_data(str), size); - peer_cn[size] = '\0'; - } else { - goto cert_fail_name; - } - } else { - int size = ASN1_STRING_to_UTF8(&peer_cn, str); - GIT_ERROR_CHECK_ALLOC(peer_cn); - if (memchr(peer_cn, '\0', size)) - goto cert_fail_name; - } - - if (check_host_name((char *)peer_cn, host) < 0) - goto cert_fail_name; - - goto cleanup; - -cert_fail_name: - error = GIT_ECERTIFICATE; - git_error_set(GIT_ERROR_SSL, "hostname does not match certificate"); - goto cleanup; - -on_error: - error = ssl_set_error(ssl, 0); - goto cleanup; - -cleanup: - X509_free(cert); - OPENSSL_free(peer_cn); - return error; -} - -typedef struct { - git_stream parent; - git_stream *io; - int owned; - bool connected; - char *host; - SSL *ssl; - git_cert_x509 cert_info; -} openssl_stream; - -static int openssl_connect(git_stream *stream) -{ - int ret; - BIO *bio; - openssl_stream *st = (openssl_stream *) stream; - - if (st->owned && (ret = git_stream_connect(st->io)) < 0) - return ret; - - bio = BIO_new(git_stream_bio_method); - GIT_ERROR_CHECK_ALLOC(bio); - - BIO_set_data(bio, st->io); - SSL_set_bio(st->ssl, bio, bio); - - /* specify the host in case SNI is needed */ -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - SSL_set_tlsext_host_name(st->ssl, st->host); -#endif - - if ((ret = SSL_connect(st->ssl)) <= 0) - return ssl_set_error(st->ssl, ret); - - st->connected = true; - - return verify_server_cert(st->ssl, st->host); -} - -static int openssl_certificate(git_cert **out, git_stream *stream) -{ - openssl_stream *st = (openssl_stream *) stream; - X509 *cert = SSL_get_peer_certificate(st->ssl); - unsigned char *guard, *encoded_cert = NULL; - int error, len; - - /* Retrieve the length of the certificate first */ - len = i2d_X509(cert, NULL); - if (len < 0) { - git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); - error = -1; - goto out; - } - - encoded_cert = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(encoded_cert); - /* i2d_X509 makes 'guard' point to just after the data */ - guard = encoded_cert; - - len = i2d_X509(cert, &guard); - if (len < 0) { - git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); - error = -1; - goto out; - } - - st->cert_info.parent.cert_type = GIT_CERT_X509; - st->cert_info.data = encoded_cert; - st->cert_info.len = len; - encoded_cert = NULL; - - *out = &st->cert_info.parent; - error = 0; - -out: - git__free(encoded_cert); - X509_free(cert); - return error; -} - -static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) -{ - openssl_stream *st = (openssl_stream *) stream; - - return git_stream_set_proxy(st->io, proxy_opts); -} - -static ssize_t openssl_write(git_stream *stream, const char *data, size_t data_len, int flags) -{ - openssl_stream *st = (openssl_stream *) stream; - int ret, len = min(data_len, INT_MAX); - - GIT_UNUSED(flags); - - if ((ret = SSL_write(st->ssl, data, len)) <= 0) - return ssl_set_error(st->ssl, ret); - - return ret; -} - -static ssize_t openssl_read(git_stream *stream, void *data, size_t len) -{ - openssl_stream *st = (openssl_stream *) stream; - int ret; - - if ((ret = SSL_read(st->ssl, data, len)) <= 0) - return ssl_set_error(st->ssl, ret); - - return ret; -} - -static int openssl_close(git_stream *stream) -{ - openssl_stream *st = (openssl_stream *) stream; - int ret; - - if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) - return -1; - - st->connected = false; - - return st->owned ? git_stream_close(st->io) : 0; -} - -static void openssl_free(git_stream *stream) -{ - openssl_stream *st = (openssl_stream *) stream; - - if (st->owned) - git_stream_free(st->io); - - SSL_free(st->ssl); - git__free(st->host); - git__free(st->cert_info.data); - git__free(st); -} - -static int openssl_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) -{ - openssl_stream *st; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(host); - - st = git__calloc(1, sizeof(openssl_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->io = in; - st->owned = owned; - - st->ssl = SSL_new(git__ssl_ctx); - if (st->ssl == NULL) { - git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); - git__free(st); - return -1; - } - - st->host = git__strdup(host); - GIT_ERROR_CHECK_ALLOC(st->host); - - st->parent.version = GIT_STREAM_VERSION; - st->parent.encrypted = 1; - st->parent.proxy_support = git_stream_supports_proxy(st->io); - st->parent.connect = openssl_connect; - st->parent.certificate = openssl_certificate; - st->parent.set_proxy = openssl_set_proxy; - st->parent.read = openssl_read; - st->parent.write = openssl_write; - st->parent.close = openssl_close; - st->parent.free = openssl_free; - - *out = (git_stream *) st; - return 0; -} - -int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host) -{ - if (openssl_ensure_initialized() < 0) - return -1; - - return openssl_stream_wrap(out, in, host, 0); -} - -int git_openssl_stream_new(git_stream **out, const char *host, const char *port) -{ - git_stream *stream = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if (openssl_ensure_initialized() < 0) - return -1; - - if ((error = git_socket_stream_new(&stream, host, port)) < 0) - return error; - - if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -int git_openssl__set_cert_location(const char *file, const char *path) -{ - if (openssl_ensure_initialized() < 0) - return -1; - - if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) { - char errmsg[256]; - - ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); - git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to load certificates: %s", - errmsg); - - return -1; - } - return 0; -} - -#else - -#include "stream.h" -#include "git2/sys/openssl.h" - -int git_openssl_stream_global_init(void) -{ - return 0; -} - -int git_openssl_set_locking(void) -{ - git_error_set(GIT_ERROR_SSL, "libgit2 was not built with OpenSSL support"); - return -1; -} - -#endif diff --git a/src/streams/openssl.h b/src/streams/openssl.h deleted file mode 100644 index 89fb60a82..000000000 --- a/src/streams/openssl.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_streams_openssl_h__ -#define INCLUDE_streams_openssl_h__ - -#include "common.h" -#include "streams/openssl_legacy.h" -#include "streams/openssl_dynamic.h" - -#include "git2/sys/stream.h" - -extern int git_openssl_stream_global_init(void); - -#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) -# include -# include -# include -# include -# endif - -#ifdef GIT_OPENSSL -extern int git_openssl__set_cert_location(const char *file, const char *path); -extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); -extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host); -#endif - -#endif diff --git a/src/streams/openssl_dynamic.c b/src/streams/openssl_dynamic.c deleted file mode 100644 index da16b6ed7..000000000 --- a/src/streams/openssl_dynamic.c +++ /dev/null @@ -1,309 +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 "streams/openssl.h" -#include "streams/openssl_dynamic.h" - -#if defined(GIT_OPENSSL) && defined(GIT_OPENSSL_DYNAMIC) - -#include "runtime.h" - -#include - -unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); -const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); -int (*ASN1_STRING_length)(const ASN1_STRING *x); -int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); -int (*ASN1_STRING_type)(const ASN1_STRING *x); - -void *(*BIO_get_data)(BIO *a); -int (*BIO_get_new_index)(void); -int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); -void (*BIO_meth_free)(BIO_METHOD *biom); -int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); -int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); -int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); -int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); -int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); -int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); -int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); -BIO_METHOD *(*BIO_meth_new)(int type, const char *name); -BIO *(*BIO_new)(const BIO_METHOD *type); -void (*BIO_set_data)(BIO *a, void *ptr); -void (*BIO_set_init)(BIO *a, int init); - -void (*CRYPTO_free)(void *ptr, const char *file, int line); -void *(*CRYPTO_malloc)(size_t num, const char *file, int line); -int (*CRYPTO_num_locks)(void); -void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); -int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); -int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); -void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); - -char *(*ERR_error_string)(unsigned long e, char *buf); -void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); -unsigned long (*ERR_get_error)(void); - -int (*SSL_connect)(SSL *ssl); -long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); -void (*SSL_free)(SSL *ssl); -int (*SSL_get_error)(SSL *ssl, int ret); -X509 *(*SSL_get_peer_certificate)(const SSL *ssl); -long (*SSL_get_verify_result)(const SSL *ssl); -int (*SSL_library_init)(void); -void (*SSL_load_error_strings)(void); -SSL *(*SSL_new)(SSL_CTX *ctx); -int (*SSL_read)(SSL *ssl, const void *buf, int num); -void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); -int (*SSL_shutdown)(SSL *ssl); -int (*SSL_write)(SSL *ssl, const void *buf, int num); - -long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); -void (*SSL_CTX_free)(SSL_CTX *ctx); -SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); -int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); -int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); -long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); -void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); -int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); - -const SSL_METHOD *(*SSLv23_method)(void); -const SSL_METHOD *(*TLS_method)(void); - -ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); -X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); -int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); -void (*X509_free)(X509 *a); -void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); -X509_NAME *(*X509_get_subject_name)(const X509 *x); - -int (*i2d_X509)(X509 *a, unsigned char **ppout); - -int (*OPENSSL_sk_num)(const void *sk); -void *(*OPENSSL_sk_value)(const void *sk, int i); -void (*OPENSSL_sk_free)(void *sk); - -int (*sk_num)(const void *sk); -void *(*sk_value)(const void *sk, int i); -void (*sk_free)(void *sk); - -void *openssl_handle; - -GIT_INLINE(void *) openssl_sym(int *err, const char *name, bool required) -{ - void *symbol; - - /* if we've seen an err, noop to retain it */ - if (*err) - return NULL; - - - if ((symbol = dlsym(openssl_handle, name)) == NULL && required) { - const char *msg = dlerror(); - git_error_set(GIT_ERROR_SSL, "could not load ssl function '%s': %s", name, msg ? msg : "unknown error"); - *err = -1; - } - - return symbol; -} - -static void dynamic_shutdown(void) -{ - dlclose(openssl_handle); - openssl_handle = NULL; -} - -int git_openssl_stream_dynamic_init(void) -{ - int err = 0; - - if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL) { - git_error_set(GIT_ERROR_SSL, "could not load ssl libraries"); - return -1; - } - - ASN1_STRING_data = (unsigned char *(*)(ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_data", false); - ASN1_STRING_get0_data = (const unsigned char *(*)(const ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_get0_data", false); - ASN1_STRING_length = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_length", true); - ASN1_STRING_to_UTF8 = (int (*)(unsigned char **, const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_to_UTF8", true); - ASN1_STRING_type = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_type", true); - - BIO_get_data = (void *(*)(BIO *))openssl_sym(&err, "BIO_get_data", false); - BIO_get_new_index = (int (*)(void))openssl_sym(&err, "BIO_get_new_index", false); - BIO_meth_free = (void (*)(BIO_METHOD *))openssl_sym(&err, "BIO_meth_free", false); - BIO_meth_new = (BIO_METHOD *(*)(int, const char *))openssl_sym(&err, "BIO_meth_new", false); - BIO_meth_set_create = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_create", false); - BIO_meth_set_ctrl = (int (*)(BIO_METHOD *, long (*)(BIO *, int, long, void *)))openssl_sym(&err, "BIO_meth_set_ctrl", false); - BIO_meth_set_destroy = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_destroy", false); - BIO_meth_set_gets = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_gets", false); - BIO_meth_set_puts = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *)))openssl_sym(&err, "BIO_meth_set_puts", false); - BIO_meth_set_read = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_read", false); - BIO_meth_set_write = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *, int)))openssl_sym(&err, "BIO_meth_set_write", false); - BIO_new = (BIO *(*)(const BIO_METHOD *))openssl_sym(&err, "BIO_new", true); - BIO_set_data = (void (*)(BIO *a, void *))openssl_sym(&err, "BIO_set_data", false); - BIO_set_init = (void (*)(BIO *a, int))openssl_sym(&err, "BIO_set_init", false); - - CRYPTO_free = (void (*)(void *, const char *, int))openssl_sym(&err, "CRYPTO_free", true); - CRYPTO_malloc = (void *(*)(size_t, const char *, int))openssl_sym(&err, "CRYPTO_malloc", true); - CRYPTO_num_locks = (int (*)(void))openssl_sym(&err, "CRYPTO_num_locks", false); - CRYPTO_set_locking_callback = (void (*)(void (*)(int, int, const char *, int)))openssl_sym(&err, "CRYPTO_set_locking_callback", false); - CRYPTO_set_mem_functions = (int (*)(void *(*)(size_t), void *(*)(void *, size_t), void (*f)(void *)))openssl_sym(&err, "CRYPTO_set_mem_functions", true); - - CRYPTO_THREADID_set_callback = (int (*)(void (*)(CRYPTO_THREADID *)))openssl_sym(&err, "CRYPTO_THREADID_set_callback", false); - CRYPTO_THREADID_set_numeric = (void (*)(CRYPTO_THREADID *, unsigned long))openssl_sym(&err, "CRYPTO_THREADID_set_numeric", false); - - ERR_error_string = (char *(*)(unsigned long, char *))openssl_sym(&err, "ERR_error_string", true); - ERR_error_string_n = (void (*)(unsigned long, char *, size_t))openssl_sym(&err, "ERR_error_string_n", true); - ERR_get_error = (unsigned long (*)(void))openssl_sym(&err, "ERR_get_error", true); - - OPENSSL_init_ssl = (int (*)(uint64_t opts, const void *settings))openssl_sym(&err, "OPENSSL_init_ssl", false); - OPENSSL_sk_num = (int (*)(const void *))openssl_sym(&err, "OPENSSL_sk_num", false); - OPENSSL_sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "OPENSSL_sk_value", false); - OPENSSL_sk_free = (void (*)(void *))openssl_sym(&err, "OPENSSL_sk_free", false); - - sk_num = (int (*)(const void *))openssl_sym(&err, "sk_num", false); - sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "sk_value", false); - sk_free = (void (*)(void *))openssl_sym(&err, "sk_free", false); - - SSL_connect = (int (*)(SSL *))openssl_sym(&err, "SSL_connect", true); - SSL_ctrl = (long (*)(SSL *, int, long, void *))openssl_sym(&err, "SSL_ctrl", true); - SSL_get_peer_certificate = (X509 *(*)(const SSL *))openssl_sym(&err, "SSL_get_peer_certificate", true); - SSL_library_init = (int (*)(void))openssl_sym(&err, "SSL_library_init", false); - SSL_free = (void (*)(SSL *))openssl_sym(&err, "SSL_free", true); - SSL_get_error = (int (*)(SSL *, int))openssl_sym(&err, "SSL_get_error", true); - SSL_get_verify_result = (long (*)(const SSL *ssl))openssl_sym(&err, "SSL_get_verify_result", true); - SSL_load_error_strings = (void (*)(void))openssl_sym(&err, "SSL_load_error_strings", false); - SSL_new = (SSL *(*)(SSL_CTX *))openssl_sym(&err, "SSL_new", true); - SSL_read = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_read", true); - SSL_set_bio = (void (*)(SSL *, BIO *, BIO *))openssl_sym(&err, "SSL_set_bio", true); - SSL_shutdown = (int (*)(SSL *ssl))openssl_sym(&err, "SSL_shutdown", true); - SSL_write = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_write", true); - - SSL_CTX_ctrl = (long (*)(SSL_CTX *, int, long, void *))openssl_sym(&err, "SSL_CTX_ctrl", true); - SSL_CTX_free = (void (*)(SSL_CTX *))openssl_sym(&err, "SSL_CTX_free", true); - SSL_CTX_new = (SSL_CTX *(*)(const SSL_METHOD *))openssl_sym(&err, "SSL_CTX_new", true); - SSL_CTX_set_cipher_list = (int (*)(SSL_CTX *, const char *))openssl_sym(&err, "SSL_CTX_set_cipher_list", true); - SSL_CTX_set_default_verify_paths = (int (*)(SSL_CTX *ctx))openssl_sym(&err, "SSL_CTX_set_default_verify_paths", true); - SSL_CTX_set_options = (long (*)(SSL_CTX *, long))openssl_sym(&err, "SSL_CTX_set_options", false); - SSL_CTX_set_verify = (void (*)(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)))openssl_sym(&err, "SSL_CTX_set_verify", true); - SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *))openssl_sym(&err, "SSL_CTX_load_verify_locations", true); - - SSLv23_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "SSLv23_method", false); - TLS_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "TLS_method", false); - - X509_NAME_ENTRY_get_data = (ASN1_STRING *(*)(const X509_NAME_ENTRY *))openssl_sym(&err, "X509_NAME_ENTRY_get_data", true); - X509_NAME_get_entry = (X509_NAME_ENTRY *(*)(X509_NAME *, int))openssl_sym(&err, "X509_NAME_get_entry", true); - X509_NAME_get_index_by_NID = (int (*)(X509_NAME *, int, int))openssl_sym(&err, "X509_NAME_get_index_by_NID", true); - X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true); - X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true); - X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true); - - i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true); - - if (err) - goto on_error; - - /* Add legacy functionality */ - if (!OPENSSL_init_ssl) { - OPENSSL_init_ssl = OPENSSL_init_ssl__legacy; - - if (!SSL_library_init || - !SSL_load_error_strings || - !CRYPTO_num_locks || - !CRYPTO_set_locking_callback || - !CRYPTO_THREADID_set_callback || - !CRYPTO_THREADID_set_numeric) { - git_error_set(GIT_ERROR_SSL, "could not load legacy openssl initialization functions"); - goto on_error; - } - } - - if (!SSL_CTX_set_options) - SSL_CTX_set_options = SSL_CTX_set_options__legacy; - - if (TLS_method) - SSLv23_method = TLS_method; - - if (!BIO_meth_new) { - BIO_meth_new = BIO_meth_new__legacy; - BIO_meth_new = BIO_meth_new__legacy; - BIO_meth_free = BIO_meth_free__legacy; - BIO_meth_set_write = BIO_meth_set_write__legacy; - BIO_meth_set_read = BIO_meth_set_read__legacy; - BIO_meth_set_puts = BIO_meth_set_puts__legacy; - BIO_meth_set_gets = BIO_meth_set_gets__legacy; - BIO_meth_set_ctrl = BIO_meth_set_ctrl__legacy; - BIO_meth_set_create = BIO_meth_set_create__legacy; - BIO_meth_set_destroy = BIO_meth_set_destroy__legacy; - BIO_get_new_index = BIO_get_new_index__legacy; - BIO_set_data = BIO_set_data__legacy; - BIO_set_init = BIO_set_init__legacy; - BIO_get_data = BIO_get_data__legacy; - } - - if (!ASN1_STRING_get0_data) { - if (!ASN1_STRING_data) { - git_error_set(GIT_ERROR_SSL, "could not load legacy openssl string function"); - goto on_error; - } - - ASN1_STRING_get0_data = ASN1_STRING_get0_data__legacy; - } - - if ((!OPENSSL_sk_num && !sk_num) || - (!OPENSSL_sk_value && !sk_value) || - (!OPENSSL_sk_free && !sk_free)) { - git_error_set(GIT_ERROR_SSL, "could not load legacy openssl stack functions"); - goto on_error; - } - - if (git_runtime_shutdown_register(dynamic_shutdown) != 0) - goto on_error; - - return 0; - -on_error: - dlclose(openssl_handle); - return -1; -} - - -int sk_GENERAL_NAME_num(const GENERAL_NAME *sk) -{ - if (OPENSSL_sk_num) - return OPENSSL_sk_num(sk); - else if (sk_num) - return sk_num(sk); - - GIT_ASSERT_WITH_RETVAL(false, 0); - return 0; -} - -GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i) -{ - if (OPENSSL_sk_value) - return OPENSSL_sk_value(sk, i); - else if (sk_value) - return sk_value(sk, i); - - GIT_ASSERT_WITH_RETVAL(false, NULL); - return NULL; -} - -void GENERAL_NAMES_free(GENERAL_NAME *sk) -{ - if (OPENSSL_sk_free) - OPENSSL_sk_free(sk); - else if (sk_free) - sk_free(sk); -} - -#endif /* GIT_OPENSSL && GIT_OPENSSL_DYNAMIC */ diff --git a/src/streams/openssl_dynamic.h b/src/streams/openssl_dynamic.h deleted file mode 100644 index a99691910..000000000 --- a/src/streams/openssl_dynamic.h +++ /dev/null @@ -1,348 +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. - */ - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are adhered to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the routines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 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. - * - * The licence and distribution terms for any publicly available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ -/* ==================================================================== - * Copyright (c) 1998-2007 The OpenSSL 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: - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. - */ -/* ==================================================================== - * Copyright 2005 Nokia. All rights reserved. - * - * The portions of the attached software ("Contribution") is developed by - * Nokia Corporation and is licensed pursuant to the OpenSSL open source - * license. - * - * The Contribution, originally written by Mika Kousa and Pasi Eronen of - * Nokia Corporation, consists of the "PSK" (Pre-Shared Key) ciphersuites - * support (see RFC 4279) to OpenSSL. - * - * No patent licenses or other rights except those expressly stated in - * the OpenSSL open source license shall be deemed granted or received - * expressly, by implication, estoppel, or otherwise. - * - * No assurances are provided by Nokia that the Contribution does not - * infringe the patent or other intellectual property rights of any third - * party or that the license provides you with all the necessary rights - * to make use of the Contribution. - * - * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN - * ADDITION TO THE DISCLAIMERS INCLUDED IN THE LICENSE, NOKIA - * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ - -#ifndef INCLUDE_streams_openssl_dynamic_h__ -#define INCLUDE_streams_openssl_dynamic_h__ - -#ifdef GIT_OPENSSL_DYNAMIC - -# define BIO_CTRL_FLUSH 11 - -# define BIO_TYPE_SOURCE_SINK 0x0400 - -# define CRYPTO_LOCK 1 - -# define GEN_DNS 2 -# define GEN_IPADD 7 - -# define NID_commonName 13 -# define NID_subject_alt_name 85 - -# define SSL_VERIFY_NONE 0x00 - -# define SSL_CTRL_OPTIONS 32 -# define SSL_CTRL_MODE 33 -# define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 - -# define SSL_ERROR_NONE 0 -# define SSL_ERROR_SSL 1 -# define SSL_ERROR_WANT_READ 2 -# define SSL_ERROR_WANT_WRITE 3 -# define SSL_ERROR_WANT_X509_LOOKUP 4 -# define SSL_ERROR_SYSCALL 5 -# define SSL_ERROR_ZERO_RETURN 6 -# define SSL_ERROR_WANT_CONNECT 7 -# define SSL_ERROR_WANT_ACCEPT 8 - -# define SSL_OP_NO_COMPRESSION 0x00020000L -# define SSL_OP_NO_SSLv2 0x01000000L -# define SSL_OP_NO_SSLv3 0x02000000L - -# define SSL_MODE_AUTO_RETRY 0x00000004L - -# define TLSEXT_NAMETYPE_host_name 0 - -# define V_ASN1_UTF8STRING 12 - -# define X509_V_OK 0 - -/* Most of the OpenSSL types are mercifully opaque, so we can treat them like `void *` */ -typedef struct bio_st BIO; -typedef struct bio_method_st BIO_METHOD; -typedef void bio_info_cb; -typedef void * CRYPTO_EX_DATA; -typedef void CRYPTO_THREADID; -typedef void GENERAL_NAMES; -typedef void SSL; -typedef void SSL_CTX; -typedef void SSL_METHOD; -typedef void X509; -typedef void X509_NAME; -typedef void X509_NAME_ENTRY; -typedef void X509_STORE_CTX; - -typedef struct { - int length; - int type; - unsigned char *data; - long flags; -} ASN1_STRING; - -typedef struct { - int type; - union { - char *ptr; - ASN1_STRING *ia5; - } d; -} GENERAL_NAME; - -struct bio_st { - BIO_METHOD *method; - /* bio, mode, argp, argi, argl, ret */ - long (*callback) (struct bio_st *, int, const char *, int, long, long); - char *cb_arg; /* first argument for the callback */ - int init; - int shutdown; - int flags; /* extra storage */ - int retry_reason; - int num; - void *ptr; - struct bio_st *next_bio; /* used by filter BIOs */ - struct bio_st *prev_bio; /* used by filter BIOs */ - int references; - unsigned long num_read; - unsigned long num_write; - CRYPTO_EX_DATA ex_data; -}; - -struct bio_method_st { - int type; - const char *name; - int (*bwrite) (BIO *, const char *, int); - int (*bread) (BIO *, char *, int); - int (*bputs) (BIO *, const char *); - int (*bgets) (BIO *, char *, int); - long (*ctrl) (BIO *, int, long, void *); - int (*create) (BIO *); - int (*destroy) (BIO *); - long (*callback_ctrl) (BIO *, int, bio_info_cb *); -}; - -extern unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); -extern const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); -extern int (*ASN1_STRING_length)(const ASN1_STRING *x); -extern int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); -extern int (*ASN1_STRING_type)(const ASN1_STRING *x); - -extern void *(*BIO_get_data)(BIO *a); -extern int (*BIO_get_new_index)(void); -extern int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); -extern void (*BIO_meth_free)(BIO_METHOD *biom); -extern int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); -extern int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); -extern int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); -extern int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); -extern int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); -extern int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); -extern int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); -extern BIO_METHOD *(*BIO_meth_new)(int type, const char *name); -extern BIO *(*BIO_new)(const BIO_METHOD *type); -extern void (*BIO_set_data)(BIO *a, void *ptr); -extern void (*BIO_set_init)(BIO *a, int init); - -extern void (*CRYPTO_free)(void *ptr, const char *file, int line); -extern void *(*CRYPTO_malloc)(size_t num, const char *file, int line); -extern int (*CRYPTO_num_locks)(void); -extern void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); -extern int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); -extern int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); -extern void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); - -extern char *(*ERR_error_string)(unsigned long e, char *buf); -extern void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); -extern unsigned long (*ERR_get_error)(void); - -# define OPENSSL_malloc(num) CRYPTO_malloc(num, __FILE__, __LINE__) -# define OPENSSL_free(addr) CRYPTO_free(addr, __FILE__, __LINE__) - -extern int (*SSL_connect)(SSL *ssl); -extern long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); -extern void (*SSL_free)(SSL *ssl); -extern int (*SSL_get_error)(SSL *ssl, int ret); -extern X509 *(*SSL_get_peer_certificate)(const SSL *ssl); -extern long (*SSL_get_verify_result)(const SSL *ssl); -extern int (*SSL_library_init)(void); -extern void (*SSL_load_error_strings)(void); -extern SSL *(*SSL_new)(SSL_CTX *ctx); -extern int (*SSL_read)(SSL *ssl, const void *buf, int num); -extern void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); -extern int (*SSL_shutdown)(SSL *ssl); -extern int (*SSL_write)(SSL *ssl, const void *buf, int num); - -# define SSL_set_tlsext_host_name(s, name) SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (char *)(name)); - -extern long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); -extern void (*SSL_CTX_free)(SSL_CTX *ctx); -extern SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); -extern int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); -extern int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); -extern long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); -extern void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); -extern int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); - -# define SSL_CTX_set_mode(ctx, mode) SSL_CTX_ctrl((ctx), SSL_CTRL_MODE, (mode), NULL); - -extern const SSL_METHOD *(*SSLv23_method)(void); -extern const SSL_METHOD *(*TLS_method)(void); - -extern ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); -extern X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); -extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); -extern void (*X509_free)(X509 *a); -extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); -extern X509_NAME *(*X509_get_subject_name)(const X509 *x); - -extern int (*i2d_X509)(X509 *a, unsigned char **ppout); - -extern int (*OPENSSL_sk_num)(const void *sk); -extern void *(*OPENSSL_sk_value)(const void *sk, int i); -extern void (*OPENSSL_sk_free)(void *sk); - -extern int (*sk_num)(const void *sk); -extern void *(*sk_value)(const void *sk, int i); -extern void (*sk_free)(void *sk); - -extern int sk_GENERAL_NAME_num(const GENERAL_NAME *sk); -extern GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i); -extern void GENERAL_NAMES_free(GENERAL_NAME *sk); - -extern int git_openssl_stream_dynamic_init(void); - -#endif /* GIT_OPENSSL_DYNAMIC */ - -#endif diff --git a/src/streams/openssl_legacy.c b/src/streams/openssl_legacy.c deleted file mode 100644 index e61e6efbb..000000000 --- a/src/streams/openssl_legacy.c +++ /dev/null @@ -1,203 +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 "streams/openssl.h" -#include "streams/openssl_legacy.h" - -#include "runtime.h" -#include "git2/sys/openssl.h" - -#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) -# include -# include -# include -# include -#endif - -#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) - -/* - * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it - * which do not exist in previous versions. We define these inline functions so - * we can program against the interface instead of littering the implementation - * with ifdefs. We do the same for OPENSSL_init_ssl. - */ - -int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings) -{ - GIT_UNUSED(opts); - GIT_UNUSED(settings); - SSL_load_error_strings(); - SSL_library_init(); - return 0; -} - -BIO_METHOD *BIO_meth_new__legacy(int type, const char *name) -{ - BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD)); - if (!meth) { - return NULL; - } - - meth->type = type; - meth->name = name; - - return meth; -} - -void BIO_meth_free__legacy(BIO_METHOD *biom) -{ - git__free(biom); -} - -int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)) -{ - biom->bwrite = write; - return 1; -} - -int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)) -{ - biom->bread = read; - return 1; -} - -int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)) -{ - biom->bputs = puts; - return 1; -} - -int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)) - -{ - biom->bgets = gets; - return 1; -} - -int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)) -{ - biom->ctrl = ctrl; - return 1; -} - -int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)) -{ - biom->create = create; - return 1; -} - -int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)) -{ - biom->destroy = destroy; - return 1; -} - -int BIO_get_new_index__legacy(void) -{ - /* This exists as of 1.1 so before we'd just have 0 */ - return 0; -} - -void BIO_set_init__legacy(BIO *b, int init) -{ - b->init = init; -} - -void BIO_set_data__legacy(BIO *a, void *ptr) -{ - a->ptr = ptr; -} - -void *BIO_get_data__legacy(BIO *a) -{ - return a->ptr; -} - -const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x) -{ - return ASN1_STRING_data((ASN1_STRING *)x); -} - -long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op) -{ - return SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, NULL); -} - -# if defined(GIT_THREADS) -static git_mutex *openssl_locks; - -static void openssl_locking_function(int mode, int n, const char *file, int line) -{ - int lock; - - GIT_UNUSED(file); - GIT_UNUSED(line); - - lock = mode & CRYPTO_LOCK; - - if (lock) - (void)git_mutex_lock(&openssl_locks[n]); - else - git_mutex_unlock(&openssl_locks[n]); -} - -static void shutdown_ssl_locking(void) -{ - int num_locks, i; - - num_locks = CRYPTO_num_locks(); - CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < num_locks; ++i) - git_mutex_free(&openssl_locks[i]); - git__free(openssl_locks); -} - -static void threadid_cb(CRYPTO_THREADID *threadid) -{ - GIT_UNUSED(threadid); - CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid()); -} - -int git_openssl_set_locking(void) -{ - int num_locks, i; - -#ifndef GIT_THREADS - git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); - return -1; -#endif - -#ifdef GIT_OPENSSL_DYNAMIC - /* - * This function is required on legacy versions of OpenSSL; when building - * with dynamically-loaded OpenSSL, we detect whether we loaded it or not. - */ - if (!CRYPTO_set_locking_callback) - return 0; -#endif - - CRYPTO_THREADID_set_callback(threadid_cb); - - num_locks = CRYPTO_num_locks(); - openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); - GIT_ERROR_CHECK_ALLOC(openssl_locks); - - for (i = 0; i < num_locks; i++) { - if (git_mutex_init(&openssl_locks[i]) != 0) { - git_error_set(GIT_ERROR_SSL, "failed to initialize openssl locks"); - return -1; - } - } - - CRYPTO_set_locking_callback(openssl_locking_function); - return git_runtime_shutdown_register(shutdown_ssl_locking); -} -#endif /* GIT_THREADS */ - -#endif /* GIT_OPENSSL_LEGACY || GIT_OPENSSL_DYNAMIC */ diff --git a/src/streams/openssl_legacy.h b/src/streams/openssl_legacy.h deleted file mode 100644 index e6dae9572..000000000 --- a/src/streams/openssl_legacy.h +++ /dev/null @@ -1,63 +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_streams_openssl_legacy_h__ -#define INCLUDE_streams_openssl_legacy_h__ - -#include "streams/openssl_dynamic.h" - -#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) -# include -# include -# include -# include - -# if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || \ - (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) -# define GIT_OPENSSL_LEGACY -# endif -#endif - -#if defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) -# define OPENSSL_init_ssl OPENSSL_init_ssl__legacy -# define BIO_meth_new BIO_meth_new__legacy -# define BIO_meth_free BIO_meth_free__legacy -# define BIO_meth_set_write BIO_meth_set_write__legacy -# define BIO_meth_set_read BIO_meth_set_read__legacy -# define BIO_meth_set_puts BIO_meth_set_puts__legacy -# define BIO_meth_set_gets BIO_meth_set_gets__legacy -# define BIO_meth_set_ctrl BIO_meth_set_ctrl__legacy -# define BIO_meth_set_create BIO_meth_set_create__legacy -# define BIO_meth_set_destroy BIO_meth_set_destroy__legacy -# define BIO_get_new_index BIO_get_new_index__legacy -# define BIO_set_data BIO_set_data__legacy -# define BIO_set_init BIO_set_init__legacy -# define BIO_get_data BIO_get_data__legacy -# define ASN1_STRING_get0_data ASN1_STRING_get0_data__legacy -#endif - -#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) - -extern int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings); -extern BIO_METHOD *BIO_meth_new__legacy(int type, const char *name); -extern void BIO_meth_free__legacy(BIO_METHOD *biom); -extern int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); -extern int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); -extern int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); -extern int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); -extern int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); -extern int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)); -extern int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)); -extern int BIO_get_new_index__legacy(void); -extern void BIO_set_data__legacy(BIO *a, void *ptr); -extern void BIO_set_init__legacy(BIO *b, int init); -extern void *BIO_get_data__legacy(BIO *a); -extern const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x); -extern long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op); - -#endif - -#endif diff --git a/src/streams/registry.c b/src/streams/registry.c deleted file mode 100644 index e60e1cd63..000000000 --- a/src/streams/registry.c +++ /dev/null @@ -1,119 +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 "common.h" - -#include "streams/registry.h" - -#include "runtime.h" -#include "streams/tls.h" -#include "streams/mbedtls.h" -#include "streams/openssl.h" -#include "streams/stransport.h" - -struct stream_registry { - git_rwlock lock; - git_stream_registration callbacks; - git_stream_registration tls_callbacks; -}; - -static struct stream_registry stream_registry; - -static void shutdown_stream_registry(void) -{ - git_rwlock_free(&stream_registry.lock); -} - -int git_stream_registry_global_init(void) -{ - if (git_rwlock_init(&stream_registry.lock) < 0) - return -1; - - return git_runtime_shutdown_register(shutdown_stream_registry); -} - -GIT_INLINE(void) stream_registration_cpy( - git_stream_registration *target, - git_stream_registration *src) -{ - if (src) - memcpy(target, src, sizeof(git_stream_registration)); - else - memset(target, 0, sizeof(git_stream_registration)); -} - -int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type) -{ - git_stream_registration *target; - int error = GIT_ENOTFOUND; - - GIT_ASSERT_ARG(out); - - switch(type) { - case GIT_STREAM_STANDARD: - target = &stream_registry.callbacks; - break; - case GIT_STREAM_TLS: - target = &stream_registry.tls_callbacks; - break; - default: - git_error_set(GIT_ERROR_INVALID, "invalid stream type"); - return -1; - } - - if (git_rwlock_rdlock(&stream_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); - return -1; - } - - if (target->init) { - stream_registration_cpy(out, target); - error = 0; - } - - git_rwlock_rdunlock(&stream_registry.lock); - return error; -} - -int git_stream_register(git_stream_t type, git_stream_registration *registration) -{ - GIT_ASSERT(!registration || registration->init); - - GIT_ERROR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration"); - - if (git_rwlock_wrlock(&stream_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); - return -1; - } - - if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD) - stream_registration_cpy(&stream_registry.callbacks, registration); - - if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS) - stream_registration_cpy(&stream_registry.tls_callbacks, registration); - - git_rwlock_wrunlock(&stream_registry.lock); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_stream_register_tls( - int GIT_CALLBACK(ctor)(git_stream **out, const char *host, const char *port)) -{ - git_stream_registration registration = {0}; - - if (ctor) { - registration.version = GIT_STREAM_VERSION; - registration.init = ctor; - registration.wrap = NULL; - - return git_stream_register(GIT_STREAM_TLS, ®istration); - } else { - return git_stream_register(GIT_STREAM_TLS, NULL); - } -} -#endif diff --git a/src/streams/registry.h b/src/streams/registry.h deleted file mode 100644 index adc2b8bdf..000000000 --- a/src/streams/registry.h +++ /dev/null @@ -1,19 +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_streams_registry_h__ -#define INCLUDE_streams_registry_h__ - -#include "common.h" -#include "git2/sys/stream.h" - -/** Configure stream registry. */ -int git_stream_registry_global_init(void); - -/** Lookup a stream registration. */ -extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type); - -#endif diff --git a/src/streams/socket.c b/src/streams/socket.c deleted file mode 100644 index 9415fe892..000000000 --- a/src/streams/socket.c +++ /dev/null @@ -1,239 +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 "streams/socket.h" - -#include "posix.h" -#include "netops.h" -#include "registry.h" -#include "stream.h" - -#ifndef _WIN32 -# include -# include -# include -# include -# include -# include -# include -#else -# include -# include -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif -#endif - -#ifdef GIT_WIN32 -static void net_set_error(const char *str) -{ - int error = WSAGetLastError(); - char * win32_error = git_win32_get_error_message(error); - - if (win32_error) { - git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error); - git__free(win32_error); - } else { - git_error_set(GIT_ERROR_NET, "%s", str); - } -} -#else -static void net_set_error(const char *str) -{ - git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno)); -} -#endif - -static int close_socket(GIT_SOCKET s) -{ - if (s == INVALID_SOCKET) - return 0; - -#ifdef GIT_WIN32 - if (SOCKET_ERROR == closesocket(s)) - return -1; - - if (0 != WSACleanup()) { - git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); - return -1; - } - - return 0; -#else - return close(s); -#endif - -} - -static int socket_connect(git_stream *stream) -{ - struct addrinfo *info = NULL, *p; - struct addrinfo hints; - git_socket_stream *st = (git_socket_stream *) stream; - GIT_SOCKET s = INVALID_SOCKET; - int ret; - -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - WSADATA wsd; - - if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { - git_error_set(GIT_ERROR_OS, "winsock init failed"); - return -1; - } - - if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { - WSACleanup(); - git_error_set(GIT_ERROR_OS, "winsock init failed"); - return -1; - } -#endif - - memset(&hints, 0x0, sizeof(struct addrinfo)); - hints.ai_socktype = SOCK_STREAM; - hints.ai_family = AF_UNSPEC; - - if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { - git_error_set(GIT_ERROR_NET, - "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); - return -1; - } - - for (p = info; p != NULL; p = p->ai_next) { - s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); - - if (s == INVALID_SOCKET) - continue; - - if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) - break; - - /* If we can't connect, try the next one */ - close_socket(s); - s = INVALID_SOCKET; - } - - /* Oops, we couldn't connect to any address */ - if (s == INVALID_SOCKET && p == NULL) { - git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); - p_freeaddrinfo(info); - return -1; - } - - st->s = s; - p_freeaddrinfo(info); - return 0; -} - -static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) -{ - git_socket_stream *st = (git_socket_stream *) stream; - ssize_t written; - - errno = 0; - - if ((written = p_send(st->s, data, len, flags)) < 0) { - net_set_error("error sending data"); - return -1; - } - - return written; -} - -static ssize_t socket_read(git_stream *stream, void *data, size_t len) -{ - ssize_t ret; - 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"); - - return ret; -} - -static int socket_close(git_stream *stream) -{ - git_socket_stream *st = (git_socket_stream *) stream; - int error; - - error = close_socket(st->s); - st->s = INVALID_SOCKET; - - return error; -} - -static void socket_free(git_stream *stream) -{ - git_socket_stream *st = (git_socket_stream *) stream; - - git__free(st->host); - git__free(st->port); - git__free(st); -} - -static int default_socket_stream_new( - git_stream **out, - const char *host, - const char *port) -{ - git_socket_stream *st; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - st = git__calloc(1, sizeof(git_socket_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->host = git__strdup(host); - GIT_ERROR_CHECK_ALLOC(st->host); - - if (port) { - st->port = git__strdup(port); - GIT_ERROR_CHECK_ALLOC(st->port); - } - - st->parent.version = GIT_STREAM_VERSION; - st->parent.connect = socket_connect; - st->parent.write = socket_write; - st->parent.read = socket_read; - st->parent.close = socket_close; - st->parent.free = socket_free; - st->s = INVALID_SOCKET; - - *out = (git_stream *) st; - return 0; -} - -int git_socket_stream_new( - git_stream **out, - const char *host, - const char *port) -{ - int (*init)(git_stream **, const char *, const char *) = NULL; - git_stream_registration custom = {0}; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0) - init = custom.init; - else if (error == GIT_ENOTFOUND) - init = default_socket_stream_new; - else - return error; - - if (!init) { - git_error_set(GIT_ERROR_NET, "there is no socket stream available"); - return -1; - } - - return init(out, host, port); -} diff --git a/src/streams/socket.h b/src/streams/socket.h deleted file mode 100644 index 3235f3167..000000000 --- a/src/streams/socket.h +++ /dev/null @@ -1,23 +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_streams_socket_h__ -#define INCLUDE_streams_socket_h__ - -#include "common.h" - -#include "netops.h" - -typedef struct { - git_stream parent; - char *host; - char *port; - GIT_SOCKET s; -} git_socket_stream; - -extern int git_socket_stream_new(git_stream **out, const char *host, const char *port); - -#endif diff --git a/src/streams/stransport.c b/src/streams/stransport.c deleted file mode 100644 index 3f31d2541..000000000 --- a/src/streams/stransport.c +++ /dev/null @@ -1,326 +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 "streams/stransport.h" - -#ifdef GIT_SECURE_TRANSPORT - -#include -#include -#include - -#include "git2/transport.h" - -#include "streams/socket.h" - -static int stransport_error(OSStatus ret) -{ - CFStringRef message; - - if (ret == noErr || ret == errSSLClosedGraceful) { - git_error_clear(); - return 0; - } - -#if !TARGET_OS_IPHONE - message = SecCopyErrorMessageString(ret, NULL); - GIT_ERROR_CHECK_ALLOC(message); - - git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8)); - CFRelease(message); -#else - git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret); - GIT_UNUSED(message); -#endif - - return -1; -} - -typedef struct { - git_stream parent; - git_stream *io; - int owned; - SSLContextRef ctx; - CFDataRef der_data; - git_cert_x509 cert_info; -} stransport_stream; - -static int stransport_connect(git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - int error; - SecTrustRef trust = NULL; - SecTrustResultType sec_res; - OSStatus ret; - - if (st->owned && (error = git_stream_connect(st->io)) < 0) - return error; - - ret = SSLHandshake(st->ctx); - if (ret != errSSLServerAuthCompleted) { - git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret); - return -1; - } - - if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) - goto on_error; - - if (!trust) - return GIT_ECERTIFICATE; - - if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr) - goto on_error; - - CFRelease(trust); - - if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) { - git_error_set(GIT_ERROR_SSL, "internal security trust error"); - return -1; - } - - if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure || - sec_res == kSecTrustResultFatalTrustFailure) { - git_error_set(GIT_ERROR_SSL, "untrusted connection error"); - return GIT_ECERTIFICATE; - } - - return 0; - -on_error: - if (trust) - CFRelease(trust); - - return stransport_error(ret); -} - -static int stransport_certificate(git_cert **out, git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - SecTrustRef trust = NULL; - SecCertificateRef sec_cert; - OSStatus ret; - - if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) - return stransport_error(ret); - - sec_cert = SecTrustGetCertificateAtIndex(trust, 0); - st->der_data = SecCertificateCopyData(sec_cert); - CFRelease(trust); - - if (st->der_data == NULL) { - git_error_set(GIT_ERROR_SSL, "retrieved invalid certificate data"); - return -1; - } - - st->cert_info.parent.cert_type = GIT_CERT_X509; - st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data); - st->cert_info.len = CFDataGetLength(st->der_data); - - *out = (git_cert *)&st->cert_info; - return 0; -} - -static int stransport_set_proxy( - git_stream *stream, - const git_proxy_options *proxy_opts) -{ - stransport_stream *st = (stransport_stream *) stream; - - return git_stream_set_proxy(st->io, proxy_opts); -} - -/* - * Contrary to typical network IO callbacks, Secure Transport write callback is - * expected to write *all* passed data, not just as much as it can, and any - * other case would be considered a failure. - * - * This behavior is actually not specified in the Apple documentation, but is - * required for things to work correctly (and incidentally, that's also how - * Apple implements it in its projects at opensource.apple.com). - * - * Libgit2 streams happen to already have this very behavior so this is just - * passthrough. - */ -static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) -{ - git_stream *io = (git_stream *) conn; - - if (git_stream__write_full(io, data, *len, 0) < 0) - return -36; /* "ioErr" from MacErrors.h which is not available on iOS */ - - return noErr; -} - -static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags) -{ - stransport_stream *st = (stransport_stream *) stream; - size_t data_len, processed; - OSStatus ret; - - GIT_UNUSED(flags); - - data_len = min(len, SSIZE_MAX); - if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) - return stransport_error(ret); - - GIT_ASSERT(processed < SSIZE_MAX); - return (ssize_t)processed; -} - -/* - * Contrary to typical network IO callbacks, Secure Transport read callback is - * expected to read *exactly* the requested number of bytes, not just as much - * as it can, and any other case would be considered a failure. - * - * This behavior is actually not specified in the Apple documentation, but is - * required for things to work correctly (and incidentally, that's also how - * Apple implements it in its projects at opensource.apple.com). - */ -static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) -{ - git_stream *io = (git_stream *) conn; - OSStatus error = noErr; - size_t off = 0; - ssize_t ret; - - do { - ret = git_stream_read(io, data + off, *len - off); - if (ret < 0) { - error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */ - break; - } - if (ret == 0) { - error = errSSLClosedGraceful; - break; - } - - off += ret; - } while (off < *len); - - *len = off; - return error; -} - -static ssize_t stransport_read(git_stream *stream, void *data, size_t len) -{ - stransport_stream *st = (stransport_stream *) stream; - size_t processed; - OSStatus ret; - - if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) - return stransport_error(ret); - - return processed; -} - -static int stransport_close(git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - OSStatus ret; - - ret = SSLClose(st->ctx); - if (ret != noErr && ret != errSSLClosedGraceful) - return stransport_error(ret); - - return st->owned ? git_stream_close(st->io) : 0; -} - -static void stransport_free(git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - - if (st->owned) - git_stream_free(st->io); - - CFRelease(st->ctx); - if (st->der_data) - CFRelease(st->der_data); - git__free(st); -} - -static int stransport_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) -{ - stransport_stream *st; - OSStatus ret; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(host); - - st = git__calloc(1, sizeof(stransport_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->io = in; - st->owned = owned; - - st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); - if (!st->ctx) { - git_error_set(GIT_ERROR_NET, "failed to create SSL context"); - git__free(st); - return -1; - } - - if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || - (ret = SSLSetConnection(st->ctx, st->io)) != noErr || - (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr || - (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr || - (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr || - (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) { - CFRelease(st->ctx); - git__free(st); - return stransport_error(ret); - } - - st->parent.version = GIT_STREAM_VERSION; - st->parent.encrypted = 1; - st->parent.proxy_support = git_stream_supports_proxy(st->io); - st->parent.connect = stransport_connect; - st->parent.certificate = stransport_certificate; - st->parent.set_proxy = stransport_set_proxy; - st->parent.read = stransport_read; - st->parent.write = stransport_write; - st->parent.close = stransport_close; - st->parent.free = stransport_free; - - *out = (git_stream *) st; - return 0; -} - -int git_stransport_stream_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return stransport_wrap(out, in, host, 0); -} - -int git_stransport_stream_new(git_stream **out, const char *host, const char *port) -{ - git_stream *stream = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - - error = git_socket_stream_new(&stream, host, port); - - if (!error) - error = stransport_wrap(out, stream, host, 1); - - if (error < 0 && stream) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -#endif diff --git a/src/streams/stransport.h b/src/streams/stransport.h deleted file mode 100644 index 1026e204b..000000000 --- a/src/streams/stransport.h +++ /dev/null @@ -1,21 +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_streams_stransport_h__ -#define INCLUDE_streams_stransport_h__ - -#include "common.h" - -#include "git2/sys/stream.h" - -#ifdef GIT_SECURE_TRANSPORT - -extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port); -extern int git_stransport_stream_wrap(git_stream **out, git_stream *in, const char *host); - -#endif - -#endif diff --git a/src/streams/tls.c b/src/streams/tls.c deleted file mode 100644 index e063a33f9..000000000 --- a/src/streams/tls.c +++ /dev/null @@ -1,75 +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 "git2/errors.h" - -#include "common.h" -#include "streams/registry.h" -#include "streams/tls.h" -#include "streams/mbedtls.h" -#include "streams/openssl.h" -#include "streams/stransport.h" - -int git_tls_stream_new(git_stream **out, const char *host, const char *port) -{ - int (*init)(git_stream **, const char *, const char *) = NULL; - git_stream_registration custom = {0}; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) { - init = custom.init; - } else if (error == GIT_ENOTFOUND) { -#ifdef GIT_SECURE_TRANSPORT - init = git_stransport_stream_new; -#elif defined(GIT_OPENSSL) - init = git_openssl_stream_new; -#elif defined(GIT_MBEDTLS) - init = git_mbedtls_stream_new; -#endif - } else { - return error; - } - - if (!init) { - git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); - return -1; - } - - return init(out, host, port); -} - -int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host) -{ - int (*wrap)(git_stream **, git_stream *, const char *) = NULL; - git_stream_registration custom = {0}; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - - if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) { - wrap = custom.wrap; - } else { -#ifdef GIT_SECURE_TRANSPORT - wrap = git_stransport_stream_wrap; -#elif defined(GIT_OPENSSL) - wrap = git_openssl_stream_wrap; -#elif defined(GIT_MBEDTLS) - wrap = git_mbedtls_stream_wrap; -#endif - } - - if (!wrap) { - git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); - return -1; - } - - return wrap(out, in, host); -} diff --git a/src/streams/tls.h b/src/streams/tls.h deleted file mode 100644 index 465a6ea89..000000000 --- a/src/streams/tls.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_streams_tls_h__ -#define INCLUDE_streams_tls_h__ - -#include "common.h" - -#include "git2/sys/stream.h" - -/** - * Create a TLS stream with the most appropriate backend available for - * the current platform, whether that's SecureTransport on macOS, - * OpenSSL or mbedTLS on other Unixes, or something else entirely. - */ -extern int git_tls_stream_new(git_stream **out, const char *host, const char *port); - -/** - * Create a TLS stream on top of an existing insecure stream, using - * the most appropriate backend available for the current platform. - * - * This allows us to create a CONNECT stream on top of a proxy; - * using SecureTransport on macOS, OpenSSL or mbedTLS on other - * Unixes, or something else entirely. - */ -extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host); - -#endif diff --git a/src/strmap.c b/src/strmap.c deleted file mode 100644 index c6e5b6dc7..000000000 --- a/src/strmap.c +++ /dev/null @@ -1,100 +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 "strmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__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_new(git_strmap **out) -{ - *out = kh_init(str); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_strmap_free(git_strmap *map) -{ - kh_destroy(str, map); -} - -void git_strmap_clear(git_strmap *map) -{ - kh_clear(str, map); -} - -size_t git_strmap_size(git_strmap *map) -{ - return kh_size(map); -} - -void *git_strmap_get(git_strmap *map, const char *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_set(git_strmap *map, const char *key, void *value) -{ - 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) -{ - return kh_get(str, map, key) != kh_end(map); -} - -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_val(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/strmap.h b/src/strmap.h deleted file mode 100644 index 9f5e4cc8b..000000000 --- a/src/strmap.h +++ /dev/null @@ -1,131 +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_strmap_h__ -#define INCLUDE_strmap_h__ - -#include "common.h" - -/** A map with C strings as key. */ -typedef struct kh_str_s git_strmap; - -/** - * 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); - -/** - * 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); - -/** - * 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); - -/** - * 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); - -#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 = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/strnlen.h b/src/strnlen.h deleted file mode 100644 index eecfe3c02..000000000 --- a/src/strnlen.h +++ /dev/null @@ -1,24 +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_strlen_h__ -#define INCLUDE_strlen_h__ - -#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ - (defined(_MSC_VER) && _MSC_VER < 1500) -# define NO_STRNLEN -#endif - -#ifdef NO_STRNLEN -GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { - const char *end = memchr(s, 0, maxlen); - return end ? (size_t)(end - s) : maxlen; -} -#else -# define p_strnlen strnlen -#endif - -#endif diff --git a/src/submodule.c b/src/submodule.c deleted file mode 100644 index 0f4f0726c..000000000 --- a/src/submodule.c +++ /dev/null @@ -1,2380 +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 "submodule.h" - -#include "buf.h" -#include "branch.h" -#include "vector.h" -#include "posix.h" -#include "config_backend.h" -#include "config.h" -#include "repository.h" -#include "tree.h" -#include "iterator.h" -#include "fs_path.h" -#include "str.h" -#include "index.h" -#include "worktree.h" -#include "clone.h" -#include "path.h" - -#include "git2/config.h" -#include "git2/sys/config.h" -#include "git2/types.h" -#include "git2/index.h" - -#define GIT_MODULES_FILE ".gitmodules" - -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_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_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 { - CACHE_OK = 0, - CACHE_REFRESH = 1, - CACHE_FLUSH = 2 -}; -enum { - GITMODULES_EXISTING = 0, - GITMODULES_CREATE = 1 -}; - -static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); -static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); -static int gitmodules_snapshot(git_config **snap, git_repository *repo); -static int get_url_base(git_str *url, git_repository *repo); -static int lookup_head_remote_key(git_str *remote_key, git_repository *repo); -static int lookup_default_remote(git_remote **remote, git_repository *repo); -static int submodule_load_each(const git_config_entry *entry, void *payload); -static int submodule_read_config(git_submodule *sm, git_config *cfg); -static int submodule_load_from_wd_lite(git_submodule *); -static void submodule_get_index_status(unsigned int *, git_submodule *); -static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); -static void submodule_update_from_index_entry(git_submodule *sm, const git_index_entry *ie); -static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, const git_oid *id); - -static int submodule_cmp(const void *a, const void *b) -{ - return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); -} - -static int submodule_config_key_trunc_puts(git_str *key, const char *suffix) -{ - ssize_t idx = git_str_rfind(key, '.'); - git_str_truncate(key, (size_t)(idx + 1)); - return git_str_puts(key, suffix); -} - -/* - * PUBLIC APIS - */ - -static void submodule_set_lookup_error(int error, const char *name) -{ - if (!error) - return; - - git_error_set(GIT_ERROR_SUBMODULE, (error == GIT_ENOTFOUND) ? - "no submodule named '%s'" : - "submodule '%s' has not been added yet", name); -} - -typedef struct { - const char *path; - char *name; -} fbp_data; - -static int find_by_path(const git_config_entry *entry, void *payload) -{ - fbp_data *data = payload; - - if (!strcmp(entry->value, data->path)) { - const char *fdot, *ldot; - fdot = strchr(entry->name, '.'); - ldot = strrchr(entry->name, '.'); - data->name = git__strndup(fdot + 1, ldot - fdot - 1); - GIT_ERROR_CHECK_ALLOC(data->name); - } - - return 0; -} - -/* - * Checks to see if the submodule shares its name with a file or directory that - * already exists on the index. If so, the submodule cannot be added. - */ -static int is_path_occupied(bool *occupied, git_repository *repo, const char *path) -{ - int error = 0; - git_index *index; - git_str dir = GIT_STR_INIT; - *occupied = false; - - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - goto out; - - if ((error = git_index_find(NULL, index, path)) != GIT_ENOTFOUND) { - if (!error) { - git_error_set(GIT_ERROR_SUBMODULE, - "File '%s' already exists in the index", path); - *occupied = true; - } - goto out; - } - - if ((error = git_str_sets(&dir, path)) < 0) - goto out; - - if ((error = git_fs_path_to_dir(&dir)) < 0) - goto out; - - if ((error = git_index_find_prefix(NULL, index, dir.ptr)) != GIT_ENOTFOUND) { - if (!error) { - git_error_set(GIT_ERROR_SUBMODULE, - "Directory '%s' already exists in the index", path); - *occupied = true; - } - goto out; - } - - error = 0; - -out: - git_str_dispose(&dir); - return error; -} - -/** - * Release the name map returned by 'load_submodule_names'. - */ -static void free_submodule_names(git_strmap *names) -{ - const char *key; - char *value; - - if (names == NULL) - return; - - git_strmap_foreach(names, key, value, { - git__free((char *) key); - git__free(value); - }); - git_strmap_free(names); - - return; -} - -/** - * Map submodule paths to names. - * TODO: for some use-cases, this might need case-folding on a - * case-insensitive filesystem - */ -static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) -{ - const char *key = "submodule\\..*\\.path"; - git_config_iterator *iter = NULL; - git_config_entry *entry; - git_str buf = GIT_STR_INIT; - git_strmap *names; - int isvalid, error; - - *out = NULL; - - if ((error = git_strmap_new(&names)) < 0) - goto out; - - if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) - goto out; - - while ((error = git_config_next(&entry, iter)) == 0) { - const char *fdot, *ldot; - fdot = strchr(entry->name, '.'); - ldot = strrchr(entry->name, '.'); - - if (git_strmap_exists(names, entry->value)) { - git_error_set(GIT_ERROR_SUBMODULE, - "duplicated submodule path '%s'", entry->value); - error = -1; - goto out; - } - - git_str_clear(&buf); - git_str_put(&buf, fdot + 1, ldot - fdot - 1); - isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0); - if (isvalid < 0) { - error = isvalid; - goto out; - } - if (!isvalid) - continue; - - if ((error = git_strmap_set(names, git__strdup(entry->value), git_str_detach(&buf))) < 0) { - git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table"); - error = -1; - goto out; - } - } - if (error == GIT_ITEROVER) - error = 0; - - *out = names; - names = NULL; - -out: - free_submodule_names(names); - git_str_dispose(&buf); - git_config_iterator_free(iter); - return error; -} - -int git_submodule_cache_init(git_strmap **out, git_repository *repo) -{ - int error = 0; - git_strmap *cache = NULL; - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - if ((error = git_strmap_new(&cache)) < 0) - return error; - if ((error = git_submodule__map(repo, cache)) < 0) { - git_submodule_cache_free(cache); - return error; - } - *out = cache; - return error; -} - -int git_submodule_cache_free(git_strmap *cache) -{ - git_submodule *sm = NULL; - if (cache == NULL) - return 0; - git_strmap_foreach_value(cache, sm, { - git_submodule_free(sm); - }); - git_strmap_free(cache); - return 0; -} - -int git_submodule_lookup( - git_submodule **out, /* NULL if user only wants to test existence */ - git_repository *repo, - const char *name) /* trailing slash is allowed */ -{ - return git_submodule__lookup_with_cache(out, repo, name, repo->submodule_cache); -} - -int git_submodule__lookup_with_cache( - git_submodule **out, /* NULL if user only wants to test existence */ - git_repository *repo, - const char *name, /* trailing slash is allowed */ - git_strmap *cache) -{ - int error; - unsigned int location; - git_submodule *sm; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if (repo->is_bare) { - git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); - return -1; - } - - if (cache != NULL) { - if ((sm = git_strmap_get(cache, name)) != NULL) { - if (out) { - *out = sm; - GIT_REFCOUNT_INC(*out); - } - return 0; - } - } - - if ((error = submodule_alloc(&sm, repo, name)) < 0) - return error; - - if ((error = git_submodule_reload(sm, false)) < 0) { - git_submodule_free(sm); - return error; - } - - if ((error = git_submodule_location(&location, sm)) < 0) { - git_submodule_free(sm); - return error; - } - - /* If it's not configured or we're looking by path */ - if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { - git_config_backend *mods; - const char *pattern = "submodule\\..*\\.path"; - git_str path = GIT_STR_INIT; - fbp_data data = { NULL, NULL }; - - git_str_puts(&path, name); - while (path.ptr[path.size-1] == '/') { - path.ptr[--path.size] = '\0'; - } - data.path = path.ptr; - - mods = open_gitmodules(repo, GITMODULES_EXISTING); - - if (mods) - error = git_config_backend_foreach_match(mods, pattern, find_by_path, &data); - - git_config_backend_free(mods); - - if (error < 0) { - git_submodule_free(sm); - git_str_dispose(&path); - return error; - } - - if (data.name) { - git__free(sm->name); - sm->name = data.name; - sm->path = git_str_detach(&path); - - /* Try to load again with the right name */ - if ((error = git_submodule_reload(sm, false)) < 0) { - git_submodule_free(sm); - return error; - } - } - - git_str_dispose(&path); - } - - if ((error = git_submodule_location(&location, sm)) < 0) { - git_submodule_free(sm); - return error; - } - - /* If we still haven't found it, do the WD check */ - if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { - git_submodule_free(sm); - error = GIT_ENOTFOUND; - - /* If it's not configured, we still check if there's a repo at the path */ - if (git_repository_workdir(repo)) { - git_str path = GIT_STR_INIT; - if (git_str_join3(&path, '/', - git_repository_workdir(repo), - name, DOT_GIT) < 0 || - git_path_validate_str_length(NULL, &path) < 0) - return -1; - - if (git_fs_path_exists(path.ptr)) - error = GIT_EEXISTS; - - git_str_dispose(&path); - } - - submodule_set_lookup_error(error, name); - return error; - } - - if (out) - *out = sm; - else - git_submodule_free(sm); - - return 0; -} - -int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags) -{ - git_str buf = GIT_STR_INIT; - int error, isvalid; - - if (flags == 0) - flags = GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS; - - /* Avoid allocating a new string if we can avoid it */ - if (strchr(name, '\\') != NULL) { - if ((error = git_fs_path_normalize_slashes(&buf, name)) < 0) - return error; - } else { - git_str_attach_notowned(&buf, name, strlen(name)); - } - - isvalid = git_path_is_valid(repo, buf.ptr, 0, flags); - git_str_dispose(&buf); - - return isvalid; -} - -static void submodule_free_dup(void *sm) -{ - git_submodule_free(sm); -} - -static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name) -{ - git_submodule *sm = NULL; - int error; - - 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; - - if ((error = git_strmap_set(map, sm->name, sm)) < 0) { - git_submodule_free(sm); - return error; - } - -done: - GIT_REFCOUNT_INC(sm); - *out = sm; - return 0; -} - -static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) -{ - int error; - git_iterator *i = NULL; - const git_index_entry *entry; - git_strmap *names; - - if ((error = load_submodule_names(&names, git_index_owner(idx), cfg))) - goto done; - - if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0) - goto done; - - while (!(error = git_iterator_advance(&entry, i))) { - git_submodule *sm; - - 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)) { - const char *name; - - 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); - git_submodule_free(sm); - } - } - } - - if (error == GIT_ITEROVER) - error = 0; - -done: - git_iterator_free(i); - free_submodule_names(names); - - return error; -} - -static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) -{ - int error; - git_iterator *i = NULL; - const git_index_entry *entry; - git_strmap *names; - - if ((error = load_submodule_names(&names, git_tree_owner(head), cfg))) - goto done; - - if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) - goto done; - - while (!(error = git_iterator_advance(&entry, i))) { - git_submodule *sm; - - 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)) { - const char *name; - - 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( - sm, entry->mode, &entry->id); - git_submodule_free(sm); - } - } - } - - if (error == GIT_ITEROVER) - error = 0; - -done: - git_iterator_free(i); - free_submodule_names(names); - - return error; -} - -/* If have_sm is true, sm is populated, otherwise map an repo are. */ -typedef struct { - git_config *mods; - git_strmap *map; - git_repository *repo; -} lfc_data; - -int git_submodule__map(git_repository *repo, git_strmap *map) -{ - int error = 0; - git_index *idx = NULL; - git_tree *head = NULL; - git_str path = GIT_STR_INIT; - git_submodule *sm; - git_config *mods = NULL; - bool has_workdir; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(map); - - /* get sources that we will need to check */ - if (git_repository_index(&idx, repo) < 0) - git_error_clear(); - if (git_repository_head_tree(&head, repo) < 0) - git_error_clear(); - - has_workdir = git_repository_workdir(repo) != NULL; - - if (has_workdir && - (error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) - goto cleanup; - - /* add submodule information from .gitmodules */ - if (has_workdir) { - lfc_data data = { 0 }; - data.map = map; - data.repo = repo; - - if ((error = gitmodules_snapshot(&mods, repo)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - goto cleanup; - } - - data.mods = mods; - if ((error = git_config_foreach( - mods, submodule_load_each, &data)) < 0) - goto cleanup; - } - /* add back submodule information from index */ - if (mods && idx) { - if ((error = submodules_from_index(map, idx, mods)) < 0) - goto cleanup; - } - /* add submodule information from HEAD */ - if (mods && head) { - if ((error = submodules_from_head(map, head, mods)) < 0) - goto cleanup; - } - /* shallow scan submodules in work tree as needed */ - if (has_workdir) { - git_strmap_foreach_value(map, sm, { - submodule_load_from_wd_lite(sm); - }); - } - -cleanup: - git_config_free(mods); - /* TODO: if we got an error, mark submodule config as invalid? */ - git_index_free(idx); - git_tree_free(head); - git_str_dispose(&path); - return error; -} - -int git_submodule_foreach( - git_repository *repo, - git_submodule_cb callback, - void *payload) -{ - git_vector snapshot = GIT_VECTOR_INIT; - git_strmap *submodules; - git_submodule *sm; - int error; - size_t i; - - if (repo->is_bare) { - git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); - return -1; - } - - 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_size(submodules), submodule_cmp))) { - - git_strmap_foreach_value(submodules, sm, { - if ((error = git_vector_insert(&snapshot, sm)) < 0) - break; - GIT_REFCOUNT_INC(sm); - }); - } - - if (error < 0) - goto done; - - git_vector_uniq(&snapshot, submodule_free_dup); - - git_vector_foreach(&snapshot, i, sm) { - if ((error = callback(sm, sm->name, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - -done: - git_vector_foreach(&snapshot, i, sm) - git_submodule_free(sm); - git_vector_free(&snapshot); - - git_strmap_foreach_value(submodules, sm, { - git_submodule_free(sm); - }); - git_strmap_free(submodules); - - return error; -} - -static int submodule_repo_init( - git_repository **out, - git_repository *parent_repo, - const char *path, - const char *url, - bool use_gitlink) -{ - int error = 0; - git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; - git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_repository *subrepo = NULL; - - error = git_repository_workdir_path(&workdir, parent_repo, path); - if (error < 0) - goto cleanup; - - initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT; - initopt.origin_url = url; - - /* init submodule repository and add origin remote as needed */ - - /* New style: sub-repo goes in /modules// with a - * gitlink in the sub-repo workdir directory to that repository - * - * Old style: sub-repo goes directly into repo//.git/ - */ - if (use_gitlink) { - error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); - if (error < 0) - goto cleanup; - error = git_str_joinpath(&repodir, repodir.ptr, path); - if (error < 0) - goto cleanup; - - initopt.workdir_path = workdir.ptr; - initopt.flags |= - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | - GIT_REPOSITORY_INIT_RELATIVE_GITLINK; - - error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); - } else - error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt); - -cleanup: - git_str_dispose(&workdir); - git_str_dispose(&repodir); - - *out = subrepo; - - return error; -} - -static int git_submodule__resolve_url( - git_str *out, - git_repository *repo, - const char *url) -{ - int error = 0; - git_str normalized = GIT_STR_INIT; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(url); - - /* We do this in all platforms in case someone on Windows created the .gitmodules */ - if (strchr(url, '\\')) { - if ((error = git_fs_path_normalize_slashes(&normalized, url)) < 0) - return error; - - url = normalized.ptr; - } - - - if (git_fs_path_is_relative(url)) { - if (!(error = get_url_base(out, repo))) - error = git_fs_path_apply_relative(out, url); - } else if (strchr(url, ':') != NULL || url[0] == '/') { - error = git_str_sets(out, url); - } else { - git_error_set(GIT_ERROR_SUBMODULE, "invalid format for submodule URL"); - error = -1; - } - - git_str_dispose(&normalized); - return error; -} - -int git_submodule_resolve_url( - git_buf *out, - git_repository *repo, - const char *url) -{ - GIT_BUF_WRAP_PRIVATE(out, git_submodule__resolve_url, repo, url); -} - -int git_submodule_add_setup( - git_submodule **out, - git_repository *repo, - const char *url, - const char *path, - int use_gitlink) -{ - int error = 0; - git_config_backend *mods = NULL; - git_submodule *sm = NULL; - git_str name = GIT_STR_INIT, real_url = GIT_STR_INIT; - git_repository *subrepo = NULL; - bool path_occupied; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(path); - - /* see if there is already an entry for this submodule */ - - if (git_submodule_lookup(NULL, repo, path) < 0) - git_error_clear(); - else { - git_error_set(GIT_ERROR_SUBMODULE, - "attempt to add submodule '%s' that already exists", path); - return GIT_EEXISTS; - } - - /* validate and normalize path */ - - if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) - path += strlen(git_repository_workdir(repo)); - - if (git_fs_path_root(path) >= 0) { - git_error_set(GIT_ERROR_SUBMODULE, "submodule path must be a relative path"); - error = -1; - goto cleanup; - } - - if ((error = is_path_occupied(&path_occupied, repo, path)) < 0) - goto cleanup; - - if (path_occupied) { - error = GIT_EEXISTS; - goto cleanup; - } - - /* update .gitmodules */ - - if (!(mods = open_gitmodules(repo, GITMODULES_CREATE))) { - git_error_set(GIT_ERROR_SUBMODULE, - "adding submodules to a bare repository is not supported"); - return -1; - } - - if ((error = git_str_printf(&name, "submodule.%s.path", path)) < 0 || - (error = git_config_backend_set_string(mods, name.ptr, path)) < 0) - goto cleanup; - - if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || - (error = git_config_backend_set_string(mods, name.ptr, url)) < 0) - goto cleanup; - - git_str_clear(&name); - - /* init submodule repository and add origin remote as needed */ - - error = git_repository_workdir_path(&name, repo, path); - if (error < 0) - goto cleanup; - - /* if the repo does not already exist, then init a new repo and add it. - * Otherwise, just add the existing repo. - */ - if (!(git_fs_path_exists(name.ptr) && - git_fs_path_contains(&name, DOT_GIT))) { - - /* resolve the actual URL to use */ - if ((error = git_submodule__resolve_url(&real_url, repo, url)) < 0) - goto cleanup; - - if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0) - goto cleanup; - } - - if ((error = git_submodule_lookup(&sm, repo, path)) < 0) - goto cleanup; - - error = git_submodule_init(sm, false); - -cleanup: - if (error && sm) { - git_submodule_free(sm); - sm = NULL; - } - if (out != NULL) - *out = sm; - - git_config_backend_free(mods); - git_repository_free(subrepo); - git_str_dispose(&real_url); - git_str_dispose(&name); - - return error; -} - -int git_submodule_repo_init( - git_repository **out, - const git_submodule *sm, - int use_gitlink) -{ - int error; - git_repository *sub_repo = NULL; - const char *configured_url; - git_config *cfg = NULL; - git_str buf = GIT_STR_INIT; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(sm); - - /* get the configured remote url of the submodule */ - if ((error = git_str_printf(&buf, "submodule.%s.url", sm->name)) < 0 || - (error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 || - (error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 || - (error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0) - goto done; - - *out = sub_repo; - -done: - git_config_free(cfg); - git_str_dispose(&buf); - 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_str rel_path = GIT_STR_INIT; - git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - GIT_ASSERT_ARG(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; - - error = git_repository_workdir_path(&rel_path, git_submodule_owner(submodule), git_submodule_path(submodule)); - if (error < 0) - goto cleanup; - - error = git_clone__submodule(&clone, git_submodule_url(submodule), git_str_cstr(&rel_path), &opts); - if (error < 0) - goto cleanup; - - if (!out) - git_repository_free(clone); - else - *out = clone; - -cleanup: - git_str_dispose(&rel_path); - - return error; -} - -int git_submodule_add_finalize(git_submodule *sm) -{ - int error; - git_index *index; - - GIT_ASSERT_ARG(sm); - - if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || - (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0) - return error; - - return git_submodule_add_to_index(sm, true); -} - -int git_submodule_add_to_index(git_submodule *sm, int write_index) -{ - int error; - git_repository *sm_repo = NULL; - git_index *index; - git_str path = GIT_STR_INIT; - git_commit *head; - git_index_entry entry; - struct stat st; - - GIT_ASSERT_ARG(sm); - - /* force reload of wd OID by git_submodule_open */ - sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; - - if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || - (error = git_repository_workdir_path(&path, sm->repo, sm->path)) < 0 || - (error = git_submodule_open(&sm_repo, sm)) < 0) - goto cleanup; - - /* read stat information for submodule working directory */ - if (p_stat(path.ptr, &st) < 0) { - git_error_set(GIT_ERROR_SUBMODULE, - "cannot add submodule without working directory"); - error = -1; - goto cleanup; - } - - memset(&entry, 0, sizeof(entry)); - entry.path = sm->path; - git_index_entry__init_from_stat( - &entry, &st, !(git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE)); - - /* calling git_submodule_open will have set sm->wd_oid if possible */ - if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { - git_error_set(GIT_ERROR_SUBMODULE, - "cannot add submodule without HEAD to index"); - error = -1; - goto cleanup; - } - git_oid_cpy(&entry.id, &sm->wd_oid); - - if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) - goto cleanup; - - entry.ctime.seconds = (int32_t)git_commit_time(head); - entry.ctime.nanoseconds = 0; - entry.mtime.seconds = (int32_t)git_commit_time(head); - entry.mtime.nanoseconds = 0; - - git_commit_free(head); - - /* add it */ - error = git_index_add(index, &entry); - - /* write it, if requested */ - if (!error && write_index) { - error = git_index_write(index); - - if (!error) - git_oid_cpy(&sm->index_oid, &sm->wd_oid); - } - -cleanup: - git_repository_free(sm_repo); - git_str_dispose(&path); - return error; -} - -static const char *submodule_update_to_str(git_submodule_update_t update) -{ - int i; - for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i) - if (_sm_update_map[i].map_value == (int)update) - return _sm_update_map[i].str_match; - return NULL; -} - -git_repository *git_submodule_owner(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->repo; -} - -const char *git_submodule_name(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->name; -} - -const char *git_submodule_path(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->path; -} - -const char *git_submodule_url(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->url; -} - -static int write_var(git_repository *repo, const char *name, const char *var, const char *val) -{ - git_str key = GIT_STR_INIT; - git_config_backend *mods; - int error; - - mods = open_gitmodules(repo, GITMODULES_CREATE); - if (!mods) - return -1; - - if ((error = git_str_printf(&key, "submodule.%s.%s", name, var)) < 0) - goto cleanup; - - if (val) - error = git_config_backend_set_string(mods, key.ptr, val); - else - error = git_config_backend_delete(mods, key.ptr); - - git_str_dispose(&key); - -cleanup: - git_config_backend_free(mods); - return error; -} - -static int write_mapped_var(git_repository *repo, const char *name, git_configmap *maps, size_t nmaps, const char *var, int ival) -{ - git_configmap_t type; - const char *val; - - if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) { - git_error_set(GIT_ERROR_SUBMODULE, "invalid value for %s", var); - return -1; - } - - if (type == GIT_CONFIGMAP_TRUE) - val = "true"; - - return write_var(repo, name, var, val); -} - -const char *git_submodule_branch(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->branch; -} - -int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_var(repo, name, "branch", branch); -} - -int git_submodule_set_url(git_repository *repo, const char *name, const char *url) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(url); - - return write_var(repo, name, "url", url); -} - -const git_oid *git_submodule_index_id(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - - if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) - return &submodule->index_oid; - else - return NULL; -} - -const git_oid *git_submodule_head_id(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - - if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) - return &submodule->head_oid; - else - return NULL; -} - -const git_oid *git_submodule_wd_id(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - - /* load unless we think we have a valid oid */ - if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { - git_repository *subrepo; - - /* calling submodule open grabs the HEAD OID if possible */ - if (!git_submodule_open_bare(&subrepo, submodule)) - git_repository_free(subrepo); - else - git_error_clear(); - } - - if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) - return &submodule->wd_oid; - else - return NULL; -} - -git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_IGNORE_UNSPECIFIED); - - return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ? - GIT_SUBMODULE_IGNORE_NONE : submodule->ignore; -} - -int git_submodule_set_ignore(git_repository *repo, const char *name, git_submodule_ignore_t ignore) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_mapped_var(repo, name, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), "ignore", ignore); -} - -git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_UPDATE_NONE); - - return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ? - GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update; -} - -int git_submodule_set_update(git_repository *repo, const char *name, git_submodule_update_t update) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_mapped_var(repo, name, _sm_update_map, ARRAY_SIZE(_sm_update_map), "update", update); -} - -git_submodule_recurse_t git_submodule_fetch_recurse_submodules( - git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_RECURSE_NO); - return submodule->fetch_recurse; -} - -int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_mapped_var(repo, name, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), "fetchRecurseSubmodules", recurse); -} - -static int submodule_repo_create( - git_repository **out, - git_repository *parent_repo, - const char *path) -{ - int error = 0; - git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; - git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_repository *subrepo = NULL; - - initopt.flags = - GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_NO_REINIT | - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | - GIT_REPOSITORY_INIT_RELATIVE_GITLINK; - - /* Workdir: path to sub-repo working directory */ - error = git_repository_workdir_path(&workdir, parent_repo, path); - if (error < 0) - goto cleanup; - - initopt.workdir_path = workdir.ptr; - - /** - * Repodir: path to the sub-repo. sub-repo goes in: - * /modules// with a gitlink in the - * sub-repo workdir directory to that repository. - */ - error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); - if (error < 0) - goto cleanup; - error = git_str_joinpath(&repodir, repodir.ptr, path); - if (error < 0) - goto cleanup; - - error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); - -cleanup: - git_str_dispose(&workdir); - git_str_dispose(&repodir); - - *out = subrepo; - - return error; -} - -/** - * Callback to override sub-repository creation when - * cloning a sub-repository. - */ -static int git_submodule_update_repo_init_cb( - git_repository **out, - const char *path, - int bare, - void *payload) -{ - git_submodule *sm; - - GIT_UNUSED(bare); - - sm = payload; - - return submodule_repo_create(out, sm->repo, path); -} - -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; -} - -#ifndef GIT_DEPRECATE_HARD -int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) -{ - return git_submodule_update_options_init(opts, version); -} -#endif - -int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options) -{ - int error; - unsigned int submodule_status; - git_config *config = NULL; - const char *submodule_url; - git_repository *sub_repo = NULL; - git_remote *remote = NULL; - git_object *target_commit = NULL; - git_str buf = GIT_STR_INIT; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT; - - GIT_ASSERT_ARG(sm); - - if (_update_options) - memcpy(&update_options, _update_options, sizeof(git_submodule_update_options)); - - GIT_ERROR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); - - /* Copy over the remote callbacks */ - memcpy(&clone_options.fetch_opts, &update_options.fetch_opts, sizeof(git_fetch_options)); - - /* Get the status of the submodule to determine if it is already initialized */ - if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) - goto done; - - /* - * If submodule work dir is not already initialized, check to see - * what we need to do (initialize, clone, return error...) - */ - if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) { - /* - * Work dir is not initialized, check to see if the submodule - * info has been copied into .git/config - */ - if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || - (error = git_str_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0) - goto done; - - if ((error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) { - /* - * If the error is not "not found" or if it is "not found" and we are not - * initializing the submodule, then return error. - */ - if (error != GIT_ENOTFOUND) - goto done; - - if (!init) { - git_error_set(GIT_ERROR_SUBMODULE, "submodule is not initialized"); - error = GIT_ERROR; - goto done; - } - - /* The submodule has not been initialized yet - initialize it now.*/ - if ((error = git_submodule_init(sm, 0)) < 0) - goto done; - - git_config_free(config); - config = NULL; - - if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || - (error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) - goto done; - } - - /** submodule is initialized - now clone it **/ - /* override repo creation */ - clone_options.repository_cb = git_submodule_update_repo_init_cb; - clone_options.repository_cb_payload = sm; - - /* - * Do not perform checkout as part of clone, instead we - * will checkout the specific commit manually. - */ - clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; - - if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 || - (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0 || - (error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0) - goto done; - } else { - const git_oid *oid; - - /** - * Work dir is initialized - look up the commit in the parent repository's index, - * update the workdir contents of the subrepository, and set the subrepository's - * head to the new commit. - */ - if ((error = git_submodule_open(&sub_repo, sm)) < 0) - goto done; - - if ((oid = git_submodule_index_id(sm)) == NULL) { - git_error_set(GIT_ERROR_SUBMODULE, "could not get ID of submodule in index"); - error = -1; - goto done; - } - - /* Look up the target commit in the submodule. */ - if ((error = git_object_lookup(&target_commit, sub_repo, oid, GIT_OBJECT_COMMIT)) < 0) { - /* If it isn't found then fetch and try again. */ - if (error != GIT_ENOTFOUND || !update_options.allow_fetch || - (error = lookup_default_remote(&remote, sub_repo)) < 0 || - (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 || - (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJECT_COMMIT)) < 0) - goto done; - } - - if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 || - (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0) - goto done; - - /* Invalidate the wd flags as the workdir has been updated. */ - sm->flags = sm->flags & - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_SCANNED); - } - -done: - git_str_dispose(&buf); - git_config_free(config); - git_object_free(target_commit); - git_remote_free(remote); - git_repository_free(sub_repo); - - return error; -} - -int git_submodule_init(git_submodule *sm, int overwrite) -{ - int error; - const char *val; - git_str key = GIT_STR_INIT, effective_submodule_url = GIT_STR_INIT; - git_config *cfg = NULL; - - if (!sm->url) { - git_error_set(GIT_ERROR_SUBMODULE, - "no URL configured for submodule '%s'", sm->name); - return -1; - } - - if ((error = git_repository_config(&cfg, sm->repo)) < 0) - return error; - - /* write "submodule.NAME.url" */ - - if ((error = git_submodule__resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 || - (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 || - (error = git_config__update_entry( - cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0) - goto cleanup; - - /* write "submodule.NAME.update" if not default */ - - val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? - NULL : submodule_update_to_str(sm->update); - - if ((error = git_str_printf(&key, "submodule.%s.update", sm->name)) < 0 || - (error = git_config__update_entry( - cfg, key.ptr, val, overwrite != 0, false)) < 0) - goto cleanup; - - /* success */ - -cleanup: - git_config_free(cfg); - git_str_dispose(&key); - git_str_dispose(&effective_submodule_url); - - return error; -} - -int git_submodule_sync(git_submodule *sm) -{ - git_str key = GIT_STR_INIT, url = GIT_STR_INIT, remote_name = GIT_STR_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); - return -1; - } - - /* copy URL over to config only if it already exists */ - if ((error = git_repository_config__weakptr(&cfg, sm->repo)) < 0 || - (error = git_str_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 (!(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 (lookup_head_remote_key(&remote_name, smrepo) == 0) { - if ((error = git_str_join3(&key, '.', "remote", remote_name.ptr, "url")) < 0) - goto out; - } else if ((error = git_str_sets(&key, "remote.origin.url")) < 0) { - goto out; - } - - if ((error = git_config__update_entry(cfg, key.ptr, url.ptr, true, false)) < 0) - goto out; - -out: - git_repository_free(smrepo); - git_str_dispose(&remote_name); - git_str_dispose(&key); - git_str_dispose(&url); - return error; -} - -static int git_submodule__open( - git_repository **subrepo, git_submodule *sm, bool bare) -{ - int error; - git_str path = GIT_STR_INIT; - unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; - const char *wd; - - GIT_ASSERT_ARG(sm); - GIT_ASSERT_ARG(subrepo); - - if (git_repository__ensure_not_bare( - sm->repo, "open submodule repository") < 0) - return GIT_EBAREREPO; - - wd = git_repository_workdir(sm->repo); - - if (git_str_join3(&path, '/', wd, sm->path, DOT_GIT) < 0) - return -1; - - sm->flags = sm->flags & - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_SCANNED); - - if (bare) - flags |= GIT_REPOSITORY_OPEN_BARE; - - error = git_repository_open_ext(subrepo, path.ptr, flags, wd); - - /* if we opened the submodule successfully, grab HEAD OID, etc. */ - if (!error) { - sm->flags |= GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_SCANNED; - - if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE)) - sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; - else - git_error_clear(); - } else if (git_fs_path_exists(path.ptr)) { - sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | - GIT_SUBMODULE_STATUS_IN_WD; - } else { - git_str_rtruncate_at_char(&path, '/'); /* remove "/.git" */ - - if (git_fs_path_isdir(path.ptr)) - sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; - } - - git_str_dispose(&path); - - return error; -} - -int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm) -{ - return git_submodule__open(subrepo, sm, true); -} - -int git_submodule_open(git_repository **subrepo, git_submodule *sm) -{ - return git_submodule__open(subrepo, sm, false); -} - -static void submodule_update_from_index_entry( - git_submodule *sm, const git_index_entry *ie) -{ - bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0; - - if (!S_ISGITLINK(ie->mode)) { - if (!already_found) - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; - } else { - if (already_found) - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; - else - git_oid_cpy(&sm->index_oid, &ie->id); - - sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID; - } -} - -static int submodule_update_index(git_submodule *sm) -{ - git_index *index; - const git_index_entry *ie; - - if (git_repository_index__weakptr(&index, sm->repo) < 0) - return -1; - - sm->flags = sm->flags & - ~(GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID); - - if (!(ie = git_index_get_bypath(index, sm->path, 0))) - return 0; - - submodule_update_from_index_entry(sm, ie); - - return 0; -} - -static void submodule_update_from_head_data( - git_submodule *sm, mode_t mode, const git_oid *id) -{ - if (!S_ISGITLINK(mode)) - sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; - else { - git_oid_cpy(&sm->head_oid, id); - - sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS__HEAD_OID_VALID; - } -} - -static int submodule_update_head(git_submodule *submodule) -{ - git_tree *head = NULL; - git_tree_entry *te = NULL; - - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS__HEAD_OID_VALID); - - /* if we can't look up file in current head, then done */ - if (git_repository_head_tree(&head, submodule->repo) < 0 || - git_tree_entry_bypath(&te, head, submodule->path) < 0) - git_error_clear(); - else - submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te)); - - git_tree_entry_free(te); - git_tree_free(head); - return 0; -} - -int git_submodule_reload(git_submodule *sm, int force) -{ - git_config *mods = NULL; - int error; - - GIT_UNUSED(force); - - GIT_ASSERT_ARG(sm); - - 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 */ - goto out; - - if (git_repository_is_bare(sm->repo)) - goto out; - - /* refresh config data */ - if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) - goto out; - - if (mods != NULL && (error = submodule_read_config(sm, mods)) < 0) - goto out; - - /* refresh wd data */ - sm->flags &= - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_FLAGS); - - 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; -} - -static void submodule_copy_oid_maybe( - git_oid *tgt, const git_oid *src, bool valid) -{ - if (tgt) { - if (valid) - memcpy(tgt, src, sizeof(*tgt)); - else - memset(tgt, 0, sizeof(*tgt)); - } -} - -int git_submodule__status( - unsigned int *out_status, - git_oid *out_head_id, - git_oid *out_index_id, - git_oid *out_wd_id, - git_submodule *sm, - git_submodule_ignore_t ign) -{ - unsigned int status; - git_repository *smrepo = NULL; - - if (ign == GIT_SUBMODULE_IGNORE_UNSPECIFIED) - ign = sm->ignore; - - /* only return location info if ignore == all */ - if (ign == GIT_SUBMODULE_IGNORE_ALL) { - *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS); - return 0; - } - - /* If the user has requested caching submodule state, performing these - * expensive operations (especially `submodule_update_head`, which is - * bottlenecked on `git_repository_head_tree`) eliminates much of the - * advantage. We will, therefore, interpret the request for caching to - * apply here to and skip them. - */ - - if (sm->repo->submodule_cache == NULL) { - /* refresh the index OID */ - if (submodule_update_index(sm) < 0) - return -1; - - /* refresh the HEAD OID */ - if (submodule_update_head(sm) < 0) - return -1; - } - - /* for ignore == dirty, don't scan the working directory */ - if (ign == GIT_SUBMODULE_IGNORE_DIRTY) { - /* git_submodule_open_bare will load WD OID data */ - if (git_submodule_open_bare(&smrepo, sm) < 0) - git_error_clear(); - else - git_repository_free(smrepo); - smrepo = NULL; - } else if (git_submodule_open(&smrepo, sm) < 0) { - git_error_clear(); - smrepo = NULL; - } - - status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags); - - submodule_get_index_status(&status, sm); - submodule_get_wd_status(&status, sm, smrepo, ign); - - git_repository_free(smrepo); - - *out_status = status; - - submodule_copy_oid_maybe(out_head_id, &sm->head_oid, - (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0); - submodule_copy_oid_maybe(out_index_id, &sm->index_oid, - (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0); - submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid, - (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0); - - return 0; -} - -int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore) -{ - git_submodule *sm; - int error; - - GIT_ASSERT_ARG(status); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_submodule_lookup(&sm, repo, name)) < 0) - return error; - - error = git_submodule__status(status, NULL, NULL, NULL, sm, ignore); - git_submodule_free(sm); - - return error; -} - -int git_submodule_location(unsigned int *location, git_submodule *sm) -{ - GIT_ASSERT_ARG(location); - GIT_ASSERT_ARG(sm); - - return git_submodule__status( - location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL); -} - -/* - * INTERNAL FUNCTIONS - */ - -static int submodule_alloc( - git_submodule **out, git_repository *repo, const char *name) -{ - size_t namelen; - git_submodule *sm; - - if (!name || !(namelen = strlen(name))) { - git_error_set(GIT_ERROR_SUBMODULE, "invalid submodule name"); - return -1; - } - - sm = git__calloc(1, sizeof(git_submodule)); - GIT_ERROR_CHECK_ALLOC(sm); - - sm->name = sm->path = git__strdup(name); - if (!sm->name) { - git__free(sm); - return -1; - } - - GIT_REFCOUNT_INC(sm); - sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE; - sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT; - sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO; - sm->repo = repo; - sm->branch = NULL; - - *out = sm; - return 0; -} - -static void submodule_release(git_submodule *sm) -{ - if (!sm) - return; - - if (sm->repo) { - sm->repo = NULL; - } - - if (sm->path != sm->name) - git__free(sm->path); - git__free(sm->name); - git__free(sm->url); - git__free(sm->branch); - git__memzero(sm, sizeof(*sm)); - git__free(sm); -} - -int git_submodule_dup(git_submodule **out, git_submodule *source) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(source); - - GIT_REFCOUNT_INC(source); - - *out = source; - return 0; -} - -void git_submodule_free(git_submodule *sm) -{ - if (!sm) - return; - GIT_REFCOUNT_DEC(sm, submodule_release); -} - -static int submodule_config_error(const char *property, const char *value) -{ - git_error_set(GIT_ERROR_INVALID, - "invalid value for submodule '%s' property: '%s'", property, value); - return -1; -} - -int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value) -{ - int val; - - if (git_config_lookup_map_value( - &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) { - *out = GIT_SUBMODULE_IGNORE_NONE; - return submodule_config_error("ignore", value); - } - - *out = (git_submodule_ignore_t)val; - return 0; -} - -int git_submodule_parse_update(git_submodule_update_t *out, const char *value) -{ - int val; - - if (git_config_lookup_map_value( - &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) { - *out = GIT_SUBMODULE_UPDATE_CHECKOUT; - return submodule_config_error("update", value); - } - - *out = (git_submodule_update_t)val; - return 0; -} - -static int submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) -{ - int val; - - if (git_config_lookup_map_value( - &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) { - *out = GIT_SUBMODULE_RECURSE_YES; - return submodule_config_error("recurse", value); - } - - *out = (git_submodule_recurse_t)val; - return 0; -} - -static int get_value(const char **out, git_config *cfg, git_str *buf, const char *name, const char *field) -{ - int error; - - git_str_clear(buf); - - if ((error = git_str_printf(buf, "submodule.%s.%s", name, field)) < 0 || - (error = git_config_get_string(out, cfg, buf->ptr)) < 0) - return error; - - return error; -} - -static bool looks_like_command_line_option(const char *s) -{ - if (s && s[0] == '-') - return true; - - return false; -} - -static int submodule_read_config(git_submodule *sm, git_config *cfg) -{ - git_str key = GIT_STR_INIT; - const char *value; - int error, in_config = 0; - - /* - * TODO: Look up path in index and if it is present but not a GITLINK - * then this should be deleted (at least to match git's behavior) - */ - - if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { - in_config = 1; - /* We would warn here if we had that API */ - if (!looks_like_command_line_option(value)) { - /* - * TODO: if case insensitive filesystem, then the following strcmp - * should be strcasecmp - */ - if (strcmp(sm->name, value) != 0) { - if (sm->path != sm->name) - git__free(sm->path); - sm->path = git__strdup(value); - GIT_ERROR_CHECK_ALLOC(sm->path); - } - - } - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { - /* We would warn here if we had that API */ - if (!looks_like_command_line_option(value)) { - in_config = 1; - sm->url = git__strdup(value); - GIT_ERROR_CHECK_ALLOC(sm->url); - } - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { - in_config = 1; - sm->branch = git__strdup(value); - GIT_ERROR_CHECK_ALLOC(sm->branch); - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { - in_config = 1; - if ((error = git_submodule_parse_update(&sm->update, value)) < 0) - goto cleanup; - sm->update_default = sm->update; - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { - in_config = 1; - if ((error = submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) - goto cleanup; - sm->fetch_recurse_default = sm->fetch_recurse; - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { - in_config = 1; - if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) - goto cleanup; - sm->ignore_default = sm->ignore; - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if (in_config) - sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; - - error = 0; - -cleanup: - git_str_dispose(&key); - return error; -} - -static int submodule_load_each(const git_config_entry *entry, void *payload) -{ - lfc_data *data = payload; - const char *namestart, *property; - git_strmap *map = data->map; - git_str name = GIT_STR_INIT; - git_submodule *sm; - int error, isvalid; - - if (git__prefixcmp(entry->name, "submodule.") != 0) - return 0; - - namestart = entry->name + strlen("submodule."); - property = strrchr(namestart, '.'); - - if (!property || (property == namestart)) - return 0; - - property++; - - if ((error = git_str_set(&name, namestart, property - namestart -1)) < 0) - return error; - - isvalid = git_submodule_name_is_valid(data->repo, name.ptr, 0); - if (isvalid <= 0) { - error = isvalid; - goto done; - } - - /* - * Now that we have the submodule's name, we can use that to - * figure out whether it's in the map. If it's not, we create - * a new submodule, load the config and insert it. If it's - * already inserted, we've already loaded it, so we skip. - */ - if (git_strmap_exists(map, name.ptr)) { - error = 0; - goto done; - } - - if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) - goto done; - - if ((error = submodule_read_config(sm, data->mods)) < 0) { - git_submodule_free(sm); - goto done; - } - - if ((error = git_strmap_set(map, sm->name, sm)) < 0) - goto done; - - error = 0; - -done: - git_str_dispose(&name); - return error; -} - -static int submodule_load_from_wd_lite(git_submodule *sm) -{ - git_str path = GIT_STR_INIT; - - if (git_repository_workdir_path(&path, sm->repo, sm->path) < 0) - return -1; - - if (git_fs_path_isdir(path.ptr)) - sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; - - if (git_fs_path_contains(&path, DOT_GIT)) - sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; - - git_str_dispose(&path); - return 0; -} - -/** - * Requests a snapshot of $WORK_TREE/.gitmodules. - * - * Returns GIT_ENOTFOUND in case no .gitmodules file exist - */ -static int gitmodules_snapshot(git_config **snap, git_repository *repo) -{ - git_config *mods = NULL; - git_str path = GIT_STR_INIT; - int error; - - if (git_repository_workdir(repo) == NULL) - return GIT_ENOTFOUND; - - if ((error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) - return error; - - if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0) - goto cleanup; - git_str_dispose(&path); - - if ((error = git_config_snapshot(snap, mods)) < 0) - goto cleanup; - - error = 0; - -cleanup: - if (mods) - git_config_free(mods); - git_str_dispose(&path); - - return error; -} - -static git_config_backend *open_gitmodules( - git_repository *repo, - int okay_to_create) -{ - git_str path = GIT_STR_INIT; - git_config_backend *mods = NULL; - - if (git_repository_workdir(repo) != NULL) { - if (git_repository_workdir_path(&path, repo, GIT_MODULES_FILE) != 0) - return NULL; - - if (okay_to_create || git_fs_path_isfile(path.ptr)) { - /* git_config_backend_from_file should only fail if OOM */ - if (git_config_backend_from_file(&mods, path.ptr) < 0) - mods = NULL; - /* open should only fail here if the file is malformed */ - else if (git_config_backend_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) { - git_config_backend_free(mods); - mods = NULL; - } - } - } - - git_str_dispose(&path); - - return mods; -} - -/* Lookup name of remote of the local tracking branch HEAD points to */ -static int lookup_head_remote_key(git_str *remote_name, git_repository *repo) -{ - int error; - git_reference *head = NULL; - git_str upstream_name = GIT_STR_INIT; - - /* lookup and dereference HEAD */ - if ((error = git_repository_head(&head, repo)) < 0) - return error; - - /** - * If head does not refer to a branch, then return - * GIT_ENOTFOUND to indicate that we could not find - * a remote key for the local tracking branch HEAD points to. - **/ - if (!git_reference_is_branch(head)) { - git_error_set(GIT_ERROR_INVALID, - "HEAD does not refer to a branch."); - error = GIT_ENOTFOUND; - goto done; - } - - /* lookup remote tracking branch of HEAD */ - if ((error = git_branch__upstream_name( - &upstream_name, - repo, - git_reference_name(head))) < 0) - goto done; - - /* lookup remote of remote tracking branch */ - if ((error = git_branch__remote_name(remote_name, repo, upstream_name.ptr)) < 0) - goto done; - -done: - git_str_dispose(&upstream_name); - git_reference_free(head); - - return error; -} - -/* Lookup the remote of the local tracking branch HEAD points to */ -static int lookup_head_remote(git_remote **remote, git_repository *repo) -{ - int error; - git_str remote_name = GIT_STR_INIT; - - /* lookup remote of remote tracking branch name */ - if (!(error = lookup_head_remote_key(&remote_name, repo))) - error = git_remote_lookup(remote, repo, remote_name.ptr); - - git_str_dispose(&remote_name); - - return error; -} - -/* Lookup remote, either from HEAD or fall back on origin */ -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 || error == GIT_EUNBORNBRANCH) - error = git_remote_lookup(remote, repo, "origin"); - - if (error == GIT_ENOTFOUND) - git_error_set( - GIT_ERROR_SUBMODULE, - "cannot get default remote for submodule - no local tracking " - "branch for HEAD and origin does not exist"); - - return error; -} - -static int get_url_base(git_str *url, git_repository *repo) -{ - int error; - git_worktree *wt = NULL; - git_remote *remote = NULL; - - if ((error = lookup_default_remote(&remote, repo)) == 0) { - error = git_str_sets(url, git_remote_url(remote)); - goto out; - } else if (error != GIT_ENOTFOUND) - goto out; - else - git_error_clear(); - - /* if repository does not have a default remote, use workdir instead */ - if (git_repository_is_worktree(repo)) { - if ((error = git_worktree_open_from_repository(&wt, repo)) < 0) - goto out; - error = git_str_sets(url, wt->parent_path); - } else { - error = git_str_sets(url, git_repository_workdir(repo)); - } - -out: - git_remote_free(remote); - git_worktree_free(wt); - - return error; -} - -static void submodule_get_index_status(unsigned int *status, git_submodule *sm) -{ - const git_oid *head_oid = git_submodule_head_id(sm); - const git_oid *index_oid = git_submodule_index_id(sm); - - *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS; - - if (!head_oid) { - if (index_oid) - *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; - } - else if (!index_oid) - *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; - else if (!git_oid_equal(head_oid, index_oid)) - *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; -} - - -static void submodule_get_wd_status( - unsigned int *status, - git_submodule *sm, - git_repository *sm_repo, - git_submodule_ignore_t ign) -{ - const git_oid *index_oid = git_submodule_index_id(sm); - const git_oid *wd_oid = - (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; - git_tree *sm_head = NULL; - git_index *index = NULL; - git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff *diff; - - *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; - - if (!index_oid) { - if (wd_oid) - *status |= GIT_SUBMODULE_STATUS_WD_ADDED; - } - else if (!wd_oid) { - if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 && - (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) - *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED; - else - *status |= GIT_SUBMODULE_STATUS_WD_DELETED; - } - else if (!git_oid_equal(index_oid, wd_oid)) - *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; - - /* if we have no repo, then we're done */ - if (!sm_repo) - return; - - /* the diffs below could be optimized with an early termination - * option to the git_diff functions, but for now this is sufficient - * (and certainly no worse that what core git does). - */ - - if (ign == GIT_SUBMODULE_IGNORE_NONE) - opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - (void)git_repository_index__weakptr(&index, sm_repo); - - /* if we don't have an unborn head, check diff with index */ - if (git_repository_head_tree(&sm_head, sm_repo) < 0) - git_error_clear(); - else { - /* perform head to index diff on submodule */ - if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0) - git_error_clear(); - else { - if (git_diff_num_deltas(diff) > 0) - *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; - git_diff_free(diff); - diff = NULL; - } - - git_tree_free(sm_head); - } - - /* perform index-to-workdir diff on submodule */ - if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0) - git_error_clear(); - else { - size_t untracked = - git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); - - if (untracked > 0) - *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; - - if (git_diff_num_deltas(diff) != untracked) - *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; - - git_diff_free(diff); - diff = NULL; - } -} diff --git a/src/submodule.h b/src/submodule.h deleted file mode 100644 index 7fa982486..000000000 --- a/src/submodule.h +++ /dev/null @@ -1,164 +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_submodule_h__ -#define INCLUDE_submodule_h__ - -#include "common.h" - -#include "git2/submodule.h" -#include "git2/repository.h" -#include "futils.h" - -/* Notes: - * - * Submodule information can be in four places: the index, the config files - * (both .git/config and .gitmodules), the HEAD tree, and the working - * directory. - * - * In the index: - * - submodule is found by path - * - may be missing, present, or of the wrong type - * - will have an oid if present - * - * In the HEAD tree: - * - submodule is found by path - * - may be missing, present, or of the wrong type - * - will have an oid if present - * - * In the config files: - * - submodule is found by submodule "name" which is usually the path - * - may be missing or present - * - will have a name, path, url, and other properties - * - * In the working directory: - * - submodule is found by path - * - may be missing, an empty directory, a checked out directory, - * or of the wrong type - * - if checked out, will have a HEAD oid - * - if checked out, will have git history that can be used to compare oids - * - if checked out, may have modified files and/or untracked files - */ - -/** - * Description of submodule - * - * This record describes a submodule found in a repository. There should be - * an entry for every submodule found in the HEAD and index, and for every - * submodule described in .gitmodules. The fields are as follows: - * - * - `rc` tracks the refcount of how many hash table entries in the - * git_submodule_cache there are for this submodule. It only comes into - * play if the name and path of the submodule differ. - * - * - `name` is the name of the submodule from .gitmodules. - * - `path` is the path to the submodule from the repo root. It is almost - * always the same as `name`. - * - `url` is the url for the submodule. - * - `update` is a git_submodule_update_t value - see gitmodules(5) update. - * - `update_default` is the update value from the config - * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. - * - `ignore_default` is the ignore value from the config - * - `fetch_recurse` is a git_submodule_recurse_t value - see gitmodules(5) - * fetchRecurseSubmodules. - * - `fetch_recurse_default` is the recurse value from the config - * - * - `repo` is the parent repository that contains this submodule. - * - `flags` after for internal use, tracking where this submodule has been - * found (head, index, config, workdir) and known status info, etc. - * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. - * - `index_oid` is the SHA1 for the submodule recorded in the index. - * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. - * - * If the submodule has been added to .gitmodules but not yet git added, - * then the `index_oid` will be zero but still marked valid. If the - * submodule has been deleted, but the delete has not been committed yet, - * then the `index_oid` will be set, but the `url` will be NULL. - */ -struct git_submodule { - git_refcount rc; - - /* information from config */ - char *name; - char *path; /* important: may just point to "name" string */ - char *url; - char *branch; - git_submodule_update_t update; - git_submodule_update_t update_default; - git_submodule_ignore_t ignore; - git_submodule_ignore_t ignore_default; - git_submodule_recurse_t fetch_recurse; - git_submodule_recurse_t fetch_recurse_default; - - /* internal information */ - git_repository *repo; - uint32_t flags; - git_oid head_oid; - git_oid index_oid; - git_oid wd_oid; -}; - -/* Additional flags on top of public GIT_SUBMODULE_STATUS values */ -enum { - GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20), - GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21), - GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22), - GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23), - GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24), - GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25), - GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26), - GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27) -}; - -#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ - ((S) & ~(0xFFFFFFFFu << 20)) - -/* Initialize an external submodule cache for the provided repo. */ -extern int git_submodule_cache_init(git_strmap **out, git_repository *repo); - -/* Release the resources of the submodule cache. */ -extern int git_submodule_cache_free(git_strmap *cache); - -/* Submodule lookup with an explicit cache */ -extern int git_submodule__lookup_with_cache( - git_submodule **out, git_repository *repo, const char *path, git_strmap *cache); - -/* Internal status fn returns status and optionally the various OIDs */ -extern int git_submodule__status( - unsigned int *out_status, - git_oid *out_head_id, - git_oid *out_index_id, - git_oid *out_wd_id, - git_submodule *sm, - git_submodule_ignore_t ign); - -/* Open submodule repository as bare repo for quick HEAD check, etc. */ -extern int git_submodule_open_bare( - git_repository **repo, - git_submodule *submodule); - -extern int git_submodule_parse_ignore( - git_submodule_ignore_t *out, const char *value); -extern int git_submodule_parse_update( - git_submodule_update_t *out, const char *value); - -extern int git_submodule__map( - git_repository *repo, - git_strmap *map); - -/** - * Check whether a submodule's name is valid. - * - * Check the path against the path validity rules, either the filesystem - * defaults (like checkout does) or whichever you want to compare against. - * - * @param repo the repository which contains the submodule - * @param name the name to check - * @param flags the `GIT_PATH` flags to use for the check (0 to use filesystem defaults) - */ -extern int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags); - -#endif diff --git a/src/sysdir.c b/src/sysdir.c deleted file mode 100644 index 450cb509b..000000000 --- a/src/sysdir.c +++ /dev/null @@ -1,363 +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 "sysdir.h" - -#include "runtime.h" -#include "str.h" -#include "fs_path.h" -#include -#if GIT_WIN32 -#include "win32/findfile.h" -#else -#include -#include -#endif - -static int git_sysdir_guess_programdata_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_programdata_dirs(out); -#else - git_str_clear(out); - return 0; -#endif -} - -static int git_sysdir_guess_system_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_system_dirs(out, "etc"); -#else - return git_str_sets(out, "/etc"); -#endif -} - -#ifndef GIT_WIN32 -static int get_passwd_home(git_str *out, uid_t uid) -{ - struct passwd pwd, *pwdptr; - char *buf = NULL; - long buflen; - int error; - - GIT_ASSERT_ARG(out); - - if ((buflen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) - buflen = 1024; - - do { - buf = git__realloc(buf, buflen); - error = getpwuid_r(uid, &pwd, buf, buflen, &pwdptr); - buflen *= 2; - } while (error == ERANGE && buflen <= 8192); - - if (error) { - git_error_set(GIT_ERROR_OS, "failed to get passwd entry"); - goto out; - } - - if (!pwdptr) { - git_error_set(GIT_ERROR_OS, "no passwd entry found for user"); - goto out; - } - - if ((error = git_str_puts(out, pwdptr->pw_dir)) < 0) - goto out; - -out: - git__free(buf); - return error; -} -#endif - -static int git_sysdir_guess_global_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_global_dirs(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 (!sandbox_id && uid == euid) - error = git__getenv(out, "HOME"); - else - error = get_passwd_home(out, euid); - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - return error; -#endif -} - -static int git_sysdir_guess_xdg_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_xdg_dirs(out); -#else - git_str env = GIT_STR_INIT; - int error; - uid_t uid, euid; - - uid = getuid(); - euid = geteuid(); - - /* - * In case we are running setuid, only look up passwd - * directory of the effective user. - */ - if (uid == euid) { - if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) - error = git_str_joinpath(out, env.ptr, "git"); - - if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) - error = git_str_joinpath(out, env.ptr, ".config/git"); - } else { - if ((error = get_passwd_home(&env, euid)) == 0) - error = git_str_joinpath(out, env.ptr, ".config/git"); - } - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - git_str_dispose(&env); - return error; -#endif -} - -static int git_sysdir_guess_template_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_system_dirs(out, "share/git-core/templates"); -#else - return git_str_sets(out, "/usr/share/git-core/templates"); -#endif -} - -struct git_sysdir__dir { - git_str buf; - int (*guess)(git_str *out); -}; - -static struct git_sysdir__dir git_sysdir__dirs[] = { - { GIT_STR_INIT, git_sysdir_guess_system_dirs }, - { GIT_STR_INIT, git_sysdir_guess_global_dirs }, - { GIT_STR_INIT, git_sysdir_guess_xdg_dirs }, - { GIT_STR_INIT, git_sysdir_guess_programdata_dirs }, - { GIT_STR_INIT, git_sysdir_guess_template_dirs }, -}; - -static void git_sysdir_global_shutdown(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(git_sysdir__dirs); ++i) - git_str_dispose(&git_sysdir__dirs[i].buf); -} - -int git_sysdir_global_init(void) -{ - size_t i; - int error = 0; - - for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); i++) - error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); - - if (error) - return error; - - return git_runtime_shutdown_register(git_sysdir_global_shutdown); -} - -int git_sysdir_reset(void) -{ - size_t i; - int error = 0; - - for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); ++i) { - git_str_dispose(&git_sysdir__dirs[i].buf); - error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); - } - - return error; -} - -static int git_sysdir_check_selector(git_sysdir_t which) -{ - if (which < ARRAY_SIZE(git_sysdir__dirs)) - return 0; - - git_error_set(GIT_ERROR_INVALID, "config directory selector out of range"); - return -1; -} - - -int git_sysdir_get(const git_str **out, git_sysdir_t which) -{ - GIT_ASSERT_ARG(out); - - *out = NULL; - - GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); - - *out = &git_sysdir__dirs[which].buf; - return 0; -} - -#define PATH_MAGIC "$PATH" - -int git_sysdir_set(git_sysdir_t which, const char *search_path) -{ - const char *expand_path = NULL; - git_str merge = GIT_STR_INIT; - - GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); - - if (search_path != NULL) - expand_path = strstr(search_path, PATH_MAGIC); - - /* reset the default if this path has been cleared */ - if (!search_path) - git_sysdir__dirs[which].guess(&git_sysdir__dirs[which].buf); - - /* if $PATH is not referenced, then just set the path */ - if (!expand_path) { - if (search_path) - git_str_sets(&git_sysdir__dirs[which].buf, search_path); - - goto done; - } - - /* otherwise set to join(before $PATH, old value, after $PATH) */ - if (expand_path > search_path) - git_str_set(&merge, search_path, expand_path - search_path); - - if (git_str_len(&git_sysdir__dirs[which].buf)) - git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, - merge.ptr, git_sysdir__dirs[which].buf.ptr); - - expand_path += strlen(PATH_MAGIC); - if (*expand_path) - git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path); - - git_str_swap(&git_sysdir__dirs[which].buf, &merge); - git_str_dispose(&merge); - -done: - if (git_str_oom(&git_sysdir__dirs[which].buf)) - return -1; - - return 0; -} - -static int git_sysdir_find_in_dirlist( - git_str *path, - const char *name, - git_sysdir_t which, - const char *label) -{ - size_t len; - const char *scan, *next = NULL; - const git_str *syspath; - - GIT_ERROR_CHECK_ERROR(git_sysdir_get(&syspath, which)); - if (!syspath || !git_str_len(syspath)) - goto done; - - for (scan = git_str_cstr(syspath); scan; scan = next) { - /* find unescaped separator or end of string */ - for (next = scan; *next; ++next) { - if (*next == GIT_PATH_LIST_SEPARATOR && - (next <= scan || next[-1] != '\\')) - break; - } - - len = (size_t)(next - scan); - next = (*next ? next + 1 : NULL); - if (!len) - continue; - - GIT_ERROR_CHECK_ERROR(git_str_set(path, scan, len)); - if (name) - GIT_ERROR_CHECK_ERROR(git_str_joinpath(path, path->ptr, name)); - - if (git_fs_path_exists(path->ptr)) - return 0; - } - -done: - if (name) - git_error_set(GIT_ERROR_OS, "the %s file '%s' doesn't exist", label, name); - else - git_error_set(GIT_ERROR_OS, "the %s directory doesn't exist", label); - git_str_dispose(path); - return GIT_ENOTFOUND; -} - -int git_sysdir_find_system_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_SYSTEM, "system"); -} - -int git_sysdir_find_global_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_GLOBAL, "global"); -} - -int git_sysdir_find_xdg_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_XDG, "global/xdg"); -} - -int git_sysdir_find_programdata_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_PROGRAMDATA, "ProgramData"); -} - -int git_sysdir_find_template_dir(git_str *path) -{ - return git_sysdir_find_in_dirlist( - path, NULL, GIT_SYSDIR_TEMPLATE, "template"); -} - -int git_sysdir_expand_global_file(git_str *path, const char *filename) -{ - int error; - - if ((error = git_sysdir_find_global_file(path, NULL)) == 0) { - if (filename) - error = git_str_joinpath(path, path->ptr, filename); - } - - return error; -} diff --git a/src/sysdir.h b/src/sysdir.h deleted file mode 100644 index 568f27940..000000000 --- a/src/sysdir.h +++ /dev/null @@ -1,113 +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_sysdir_h__ -#define INCLUDE_sysdir_h__ - -#include "common.h" - -#include "posix.h" -#include "str.h" - -/** - * Find a "global" file (i.e. one in a user's home directory). - * - * @param path buffer to write the full path into - * @param filename name of file to find in the home directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_global_file(git_str *path, const char *filename); - -/** - * Find an "XDG" file (i.e. one in user's XDG config path). - * - * @param path buffer to write the full path into - * @param filename name of file to find in the home directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_xdg_file(git_str *path, const char *filename); - -/** - * Find a "system" file (i.e. one shared for all users of the system). - * - * @param path buffer to write the full path into - * @param filename name of file to find in the home directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_system_file(git_str *path, const char *filename); - -/** - * Find a "ProgramData" file (i.e. one in %PROGRAMDATA%) - * - * @param path buffer to write the full path into - * @param filename name of file to find in the ProgramData directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_programdata_file(git_str *path, const char *filename); - -/** - * Find template directory. - * - * @param path buffer to write the full path into - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_template_dir(git_str *path); - -/** - * Expand the name of a "global" file (i.e. one in a user's home - * directory). Unlike `find_global_file` (above), this makes no - * attempt to check for the existence of the file, and is useful if - * you want the full path regardless of existence. - * - * @param path buffer to write the full path into - * @param filename name of file in the home directory - * @return 0 on success or -1 on error - */ -extern int git_sysdir_expand_global_file(git_str *path, const char *filename); - -typedef enum { - GIT_SYSDIR_SYSTEM = 0, - GIT_SYSDIR_GLOBAL = 1, - GIT_SYSDIR_XDG = 2, - GIT_SYSDIR_PROGRAMDATA = 3, - GIT_SYSDIR_TEMPLATE = 4, - GIT_SYSDIR__MAX = 5 -} git_sysdir_t; - -/** - * Configures global data for configuration file search paths. - * - * @return 0 on success, <0 on failure - */ -extern int git_sysdir_global_init(void); - -/** - * Get the search path for global/system/xdg files - * - * @param out pointer to git_str containing search path - * @param which which list of paths to return - * @return 0 on success, <0 on failure - */ -extern int git_sysdir_get(const git_str **out, git_sysdir_t which); - -/** - * Set search paths for global/system/xdg files - * - * The first occurrence of the magic string "$PATH" in the new value will - * be replaced with the old value of the search path. - * - * @param which Which search path to modify - * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR) - * @return 0 on success, <0 on failure (allocation error) - */ -extern int git_sysdir_set(git_sysdir_t which, const char *paths); - -/** - * Reset search paths for global/system/xdg files. - */ -extern int git_sysdir_reset(void); - -#endif diff --git a/src/tag.c b/src/tag.c deleted file mode 100644 index 5734106fa..000000000 --- a/src/tag.c +++ /dev/null @@ -1,570 +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 "tag.h" - -#include "commit.h" -#include "signature.h" -#include "wildmatch.h" -#include "git2/object.h" -#include "git2/repository.h" -#include "git2/signature.h" -#include "git2/odb_backend.h" - -void git_tag__free(void *_tag) -{ - git_tag *tag = _tag; - git_signature_free(tag->tagger); - git__free(tag->message); - git__free(tag->tag_name); - git__free(tag); -} - -int git_tag_target(git_object **target, const git_tag *t) -{ - GIT_ASSERT_ARG(t); - return git_object_lookup(target, t->object.repo, &t->target, t->type); -} - -const git_oid *git_tag_target_id(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); - return &t->target; -} - -git_object_t git_tag_target_type(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, GIT_OBJECT_INVALID); - return t->type; -} - -const char *git_tag_name(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); - return t->tag_name; -} - -const git_signature *git_tag_tagger(const git_tag *t) -{ - return t->tagger; -} - -const char *git_tag_message(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); - return t->message; -} - -static int tag_error(const char *str) -{ - git_error_set(GIT_ERROR_TAG, "failed to parse tag: %s", str); - return GIT_EINVALID; -} - -static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) -{ - static const char *tag_types[] = { - NULL, "commit\n", "tree\n", "blob\n", "tag\n" - }; - size_t text_len, alloc_len; - const char *search; - unsigned int i; - int error; - - if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) - return tag_error("object field invalid"); - - if (buffer + 5 >= buffer_end) - return tag_error("object too short"); - - if (memcmp(buffer, "type ", 5) != 0) - return tag_error("type field not found"); - buffer += 5; - - tag->type = GIT_OBJECT_INVALID; - - for (i = 1; i < ARRAY_SIZE(tag_types); ++i) { - size_t type_length = strlen(tag_types[i]); - - if (buffer + type_length >= buffer_end) - return tag_error("object too short"); - - if (memcmp(buffer, tag_types[i], type_length) == 0) { - tag->type = i; - buffer += type_length; - break; - } - } - - if (tag->type == GIT_OBJECT_INVALID) - return tag_error("invalid object type"); - - if (buffer + 4 >= buffer_end) - return tag_error("object too short"); - - if (memcmp(buffer, "tag ", 4) != 0) - return tag_error("tag field not found"); - - buffer += 4; - - search = memchr(buffer, '\n', buffer_end - buffer); - if (search == NULL) - return tag_error("object too short"); - - text_len = search - buffer; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); - tag->tag_name = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(tag->tag_name); - - memcpy(tag->tag_name, buffer, text_len); - tag->tag_name[text_len] = '\0'; - - buffer = search + 1; - - tag->tagger = NULL; - if (buffer < buffer_end && *buffer != '\n') { - tag->tagger = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(tag->tagger); - - if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n')) < 0) - return error; - } - - tag->message = NULL; - if (buffer < buffer_end) { - /* If we're not at the end of the header, search for it */ - if(*buffer != '\n') { - search = git__memmem(buffer, buffer_end - buffer, - "\n\n", 2); - if (search) - buffer = search + 1; - else - return tag_error("tag contains no message"); - } - - text_len = buffer_end - ++buffer; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); - tag->message = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(tag->message); - - memcpy(tag->message, buffer, text_len); - tag->message[text_len] = '\0'; - } - - return 0; -} - -int git_tag__parse_raw(void *_tag, const char *data, size_t size) -{ - return tag_parse(_tag, data, data + size); -} - -int git_tag__parse(void *_tag, git_odb_object *odb_obj) -{ - git_tag *tag = _tag; - const char *buffer = git_odb_object_data(odb_obj); - const char *buffer_end = buffer + git_odb_object_size(odb_obj); - - return tag_parse(tag, buffer, buffer_end); -} - -static int retrieve_tag_reference( - git_reference **tag_reference_out, - git_str *ref_name_out, - git_repository *repo, - const char *tag_name) -{ - git_reference *tag_ref; - int error; - - *tag_reference_out = NULL; - - if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) - return -1; - - error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); - if (error < 0) - return error; /* Be it not foundo or corrupted */ - - *tag_reference_out = tag_ref; - - return 0; -} - -static int retrieve_tag_reference_oid( - git_oid *oid, - git_str *ref_name_out, - git_repository *repo, - const char *tag_name) -{ - if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) - return -1; - - return git_reference_name_to_id(oid, repo, ref_name_out->ptr); -} - -static int write_tag_annotation( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message) -{ - git_str tag = GIT_STR_INIT; - git_odb *odb; - - git_oid__writebuf(&tag, "object ", git_object_id(target)); - git_str_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); - git_str_printf(&tag, "tag %s\n", tag_name); - git_signature__writebuf(&tag, "tagger ", tagger); - git_str_putc(&tag, '\n'); - - if (git_str_puts(&tag, message) < 0) - goto on_error; - - if (git_repository_odb__weakptr(&odb, repo) < 0) - goto on_error; - - if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJECT_TAG) < 0) - goto on_error; - - git_str_dispose(&tag); - return 0; - -on_error: - git_str_dispose(&tag); - git_error_set(GIT_ERROR_OBJECT, "failed to create tag annotation"); - return -1; -} - -static int git_tag_create__internal( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message, - int allow_ref_overwrite, - int create_tag_annotation) -{ - git_reference *new_ref = NULL; - git_str ref_name = GIT_STR_INIT; - - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(tag_name); - GIT_ASSERT_ARG(target); - GIT_ASSERT_ARG(!create_tag_annotation || (tagger && message)); - - if (git_object_owner(target) != repo) { - git_error_set(GIT_ERROR_INVALID, "the given target does not belong to this repository"); - return -1; - } - - error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - /** Ensure the tag name doesn't conflict with an already existing - * reference unless overwriting has explicitly been requested **/ - if (error == 0 && !allow_ref_overwrite) { - git_str_dispose(&ref_name); - git_error_set(GIT_ERROR_TAG, "tag already exists"); - return GIT_EEXISTS; - } - - if (create_tag_annotation) { - if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) - return -1; - } else - git_oid_cpy(oid, git_object_id(target)); - - error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); - -cleanup: - git_reference_free(new_ref); - git_str_dispose(&ref_name); - return error; -} - -int git_tag_create( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message, - int allow_ref_overwrite) -{ - return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); -} - -int git_tag_annotation_create( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message) -{ - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(tag_name); - GIT_ASSERT_ARG(target); - GIT_ASSERT_ARG(tagger); - GIT_ASSERT_ARG(message); - - return write_tag_annotation(oid, repo, tag_name, target, tagger, message); -} - -int git_tag_create_lightweight( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - int allow_ref_overwrite) -{ - return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); -} - -int git_tag_create_from_buffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) -{ - git_tag tag; - int error; - git_odb *odb; - git_odb_stream *stream; - git_odb_object *target_obj; - - git_reference *new_ref = NULL; - git_str ref_name = GIT_STR_INIT; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(buffer); - - memset(&tag, 0, sizeof(tag)); - - if (git_repository_odb__weakptr(&odb, repo) < 0) - return -1; - - /* validate the buffer */ - if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0) - return -1; - - /* validate the target */ - if (git_odb_read(&target_obj, odb, &tag.target) < 0) - goto on_error; - - if (tag.type != target_obj->cached.type) { - git_error_set(GIT_ERROR_TAG, "the type for the given target is invalid"); - goto on_error; - } - - error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); - if (error < 0 && error != GIT_ENOTFOUND) - goto on_error; - - /* We don't need these objects after this */ - git_signature_free(tag.tagger); - git__free(tag.tag_name); - git__free(tag.message); - git_odb_object_free(target_obj); - - /** Ensure the tag name doesn't conflict with an already existing - * reference unless overwriting has explicitly been requested **/ - if (error == 0 && !allow_ref_overwrite) { - git_error_set(GIT_ERROR_TAG, "tag already exists"); - return GIT_EEXISTS; - } - - /* write the buffer */ - if ((error = git_odb_open_wstream( - &stream, odb, strlen(buffer), GIT_OBJECT_TAG)) < 0) - return error; - - if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) - error = git_odb_stream_finalize_write(oid, stream); - - git_odb_stream_free(stream); - - if (error < 0) { - git_str_dispose(&ref_name); - return error; - } - - error = git_reference_create( - &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); - - git_reference_free(new_ref); - git_str_dispose(&ref_name); - - return error; - -on_error: - git_signature_free(tag.tagger); - git__free(tag.tag_name); - git__free(tag.message); - git_odb_object_free(target_obj); - return -1; -} - -int git_tag_delete(git_repository *repo, const char *tag_name) -{ - git_reference *tag_ref; - git_str ref_name = GIT_STR_INIT; - int error; - - error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); - - git_str_dispose(&ref_name); - - if (error < 0) - return error; - - error = git_reference_delete(tag_ref); - - git_reference_free(tag_ref); - - return error; -} - -typedef struct { - git_repository *repo; - git_tag_foreach_cb cb; - void *cb_data; -} tag_cb_data; - -static int tags_cb(const char *ref, void *data) -{ - int error; - git_oid oid; - tag_cb_data *d = (tag_cb_data *)data; - - if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0) - return 0; /* no tag */ - - if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) { - if ((error = d->cb(ref, &oid, d->cb_data)) != 0) - git_error_set_after_callback_function(error, "git_tag_foreach"); - } - - return error; -} - -int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) -{ - tag_cb_data data; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cb); - - data.cb = cb; - data.cb_data = cb_data; - data.repo = repo; - - return git_reference_foreach_name(repo, &tags_cb, &data); -} - -typedef struct { - git_vector *taglist; - const char *pattern; -} tag_filter_data; - -#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) - -static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) -{ - tag_filter_data *filter = (tag_filter_data *)data; - GIT_UNUSED(oid); - - if (!*filter->pattern || - 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); - - return git_vector_insert(filter->taglist, matched); - } - - return 0; -} - -int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) -{ - int error; - tag_filter_data filter; - git_vector taglist; - - GIT_ASSERT_ARG(tag_names); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(pattern); - - if ((error = git_vector_init(&taglist, 8, NULL)) < 0) - return error; - - filter.taglist = &taglist; - filter.pattern = pattern; - - error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); - - if (error < 0) - git_vector_free(&taglist); - - tag_names->strings = - (char **)git_vector_detach(&tag_names->count, NULL, &taglist); - - return 0; -} - -int git_tag_list(git_strarray *tag_names, git_repository *repo) -{ - return git_tag_list_match(tag_names, "", repo); -} - -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); -} - -int git_tag_name_is_valid(int *valid, const char *name) -{ - git_str ref_name = GIT_STR_INIT; - int error = 0; - - GIT_ASSERT(valid); - - *valid = 0; - - /* - * Discourage tag name starting with dash, - * https://github.com/git/git/commit/4f0accd638b8d2 - */ - if (!name || name[0] == '-') - goto done; - - if ((error = git_str_puts(&ref_name, GIT_REFS_TAGS_DIR)) < 0 || - (error = git_str_puts(&ref_name, name)) < 0) - goto done; - - error = git_reference_name_is_valid(valid, ref_name.ptr); - -done: - git_str_dispose(&ref_name); - return error; -} - -/* Deprecated Functions */ - -#ifndef GIT_DEPRECATE_HARD -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); -} -#endif diff --git a/src/tag.h b/src/tag.h deleted file mode 100644 index 76ae1508e..000000000 --- a/src/tag.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_tag_h__ -#define INCLUDE_tag_h__ - -#include "common.h" - -#include "git2/tag.h" -#include "repository.h" -#include "odb.h" - -struct git_tag { - git_object object; - - git_oid target; - git_object_t type; - - char *tag_name; - git_signature *tagger; - char *message; -}; - -void git_tag__free(void *tag); -int git_tag__parse(void *tag, git_odb_object *obj); -int git_tag__parse_raw(void *tag, const char *data, size_t size); - -#endif diff --git a/src/thread.c b/src/thread.c deleted file mode 100644 index 3171771d7..000000000 --- a/src/thread.c +++ /dev/null @@ -1,140 +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 "common.h" - -#if !defined(GIT_THREADS) - -#define TLSDATA_MAX 16 - -typedef struct { - void *value; - void (GIT_SYSTEM_CALL *destroy_fn)(void *); -} tlsdata_value; - -static tlsdata_value tlsdata_values[TLSDATA_MAX]; -static int tlsdata_cnt = 0; - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - if (tlsdata_cnt >= TLSDATA_MAX) - return -1; - - tlsdata_values[tlsdata_cnt].value = NULL; - tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn; - - *key = tlsdata_cnt; - tlsdata_cnt++; - - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (key < 0 || key > tlsdata_cnt) - return -1; - - tlsdata_values[key].value = value; - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - if (key < 0 || key > tlsdata_cnt) - return NULL; - - return tlsdata_values[key].value; -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - void *value; - void (*destroy_fn)(void *) = NULL; - - if (key < 0 || key > tlsdata_cnt) - return -1; - - value = tlsdata_values[key].value; - destroy_fn = tlsdata_values[key].destroy_fn; - - tlsdata_values[key].value = NULL; - tlsdata_values[key].destroy_fn = NULL; - - if (value && destroy_fn) - destroy_fn(value); - - return 0; -} - -#elif defined(GIT_WIN32) - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - DWORD fls_index = FlsAlloc(destroy_fn); - - if (fls_index == FLS_OUT_OF_INDEXES) - return -1; - - *key = fls_index; - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (!FlsSetValue(key, value)) - return -1; - - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - return FlsGetValue(key); -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - if (!FlsFree(key)) - return -1; - - return 0; -} - -#elif defined(_POSIX_THREADS) - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - if (pthread_key_create(key, destroy_fn) != 0) - return -1; - - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (pthread_setspecific(key, value) != 0) - return -1; - - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - return pthread_getspecific(key); -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - if (pthread_key_delete(key) != 0) - return -1; - - return 0; -} - -#else -# error unknown threading model -#endif diff --git a/src/thread.h b/src/thread.h deleted file mode 100644 index 4bbac9fd8..000000000 --- a/src/thread.h +++ /dev/null @@ -1,479 +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_thread_h__ -#define INCLUDE_thread_h__ - -#if defined(GIT_THREADS) - -#if defined(__clang__) - -# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1)) -# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF -# else -# define GIT_BUILTIN_ATOMIC -# endif - -#elif defined(__GNUC__) - -# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) -# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF -# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) -# define GIT_BUILTIN_ATOMIC -# else -# define GIT_BUILTIN_SYNC -# endif - -#endif - -#endif /* GIT_THREADS */ - -/* Common operations even if threading has been disabled */ -typedef struct { -#if defined(GIT_WIN32) - volatile long val; -#else - volatile int val; -#endif -} git_atomic32; - -#ifdef GIT_ARCH_64 - -typedef struct { -#if defined(GIT_WIN32) - volatile __int64 val; -#else - volatile int64_t val; -#endif -} git_atomic64; - -typedef git_atomic64 git_atomic_ssize; - -#define git_atomic_ssize_set git_atomic64_set -#define git_atomic_ssize_add git_atomic64_add -#define git_atomic_ssize_get git_atomic64_get - -#else - -typedef git_atomic32 git_atomic_ssize; - -#define git_atomic_ssize_set git_atomic32_set -#define git_atomic_ssize_add git_atomic32_add -#define git_atomic_ssize_get git_atomic32_get - -#endif - -#ifdef GIT_THREADS - -#ifdef GIT_WIN32 -# include "win32/thread.h" -#else -# include "unix/pthread.h" -#endif - -/* - * Atomically sets the contents of *a to be val. - */ -GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) -{ -#if defined(GIT_WIN32) - InterlockedExchange(&a->val, (LONG)val); -#elif defined(GIT_BUILTIN_ATOMIC) - __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - __sync_lock_test_and_set(&a->val, val); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically increments the contents of *a by 1, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return InterlockedIncrement(&a->val); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, 1); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically adds the contents of *a and addend, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) -{ -#if defined(GIT_WIN32) - return InterlockedAdd(&a->val, addend); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, addend); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically decrements the contents of *a by 1, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return InterlockedDecrement(&a->val); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_sub_and_fetch(&a->val, 1); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically gets the contents of *a. - * @return the contents of *a. - */ -GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return (int)InterlockedCompareExchange(&a->val, 0, 0); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(&a->val, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(void *) git_atomic__compare_and_swap( - void * volatile *ptr, void *oldval, void *newval) -{ -#if defined(GIT_WIN32) - return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); -#elif defined(GIT_BUILTIN_ATOMIC) - void *foundval = oldval; - __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); - return foundval; -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(ptr, oldval, newval); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(volatile void *) git_atomic__swap( - void * volatile *ptr, void *newval) -{ -#if defined(GIT_WIN32) - return InterlockedExchangePointer(ptr, newval); -#elif defined(GIT_BUILTIN_ATOMIC) - void * foundval = NULL; - __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); - return foundval; -#elif defined(GIT_BUILTIN_SYNC) - return (volatile void *)__sync_lock_test_and_set(ptr, newval); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) -{ -#if defined(GIT_WIN32) - void *newval = NULL, *oldval = NULL; - return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); -#elif defined(GIT_BUILTIN_ATOMIC) - return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -#ifdef GIT_ARCH_64 - -/* - * Atomically adds the contents of *a and addend, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) -{ -#if defined(GIT_WIN32) - return InterlockedAdd64(&a->val, addend); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, addend); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically sets the contents of *a to be val. - */ -GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) -{ -#if defined(GIT_WIN32) - InterlockedExchange64(&a->val, val); -#elif defined(GIT_BUILTIN_ATOMIC) - __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - __sync_lock_test_and_set(&a->val, val); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically gets the contents of *a. - * @return the contents of *a. - */ -GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) -{ -#if defined(GIT_WIN32) - return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(&a->val, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -#endif - -#else - -#define git_threads_global_init git__noop - -#define git_thread unsigned int -#define git_thread_create(thread, start_routine, arg) git__noop() -#define git_thread_join(id, status) git__noop() - -/* Pthreads Mutex */ -#define git_mutex unsigned int -#define git_mutex_init(a) git__noop() -#define git_mutex_init(a) git__noop() -#define git_mutex_lock(a) git__noop() -#define git_mutex_unlock(a) git__noop() -#define git_mutex_free(a) git__noop() - -/* Pthreads condition vars */ -#define git_cond unsigned int -#define git_cond_init(c) git__noop() -#define git_cond_free(c) git__noop() -#define git_cond_wait(c, l) git__noop() -#define git_cond_signal(c) git__noop() -#define git_cond_broadcast(c) git__noop() - -/* Pthreads rwlock */ -#define git_rwlock unsigned int -#define git_rwlock_init(a) git__noop() -#define git_rwlock_rdlock(a) git__noop() -#define git_rwlock_rdunlock(a) git__noop() -#define git_rwlock_wrlock(a) git__noop() -#define git_rwlock_wrunlock(a) git__noop() -#define git_rwlock_free(a) git__noop() -#define GIT_RWLOCK_STATIC_INIT 0 - - -GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) -{ - a->val = val; -} - -GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) -{ - return ++a->val; -} - -GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) -{ - a->val += addend; - return a->val; -} - -GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) -{ - return --a->val; -} - -GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) -{ - return (int)a->val; -} - -GIT_INLINE(void *) git_atomic__compare_and_swap( - void * volatile *ptr, void *oldval, void *newval) -{ - void *foundval = *ptr; - if (foundval == oldval) - *ptr = newval; - return foundval; -} - -GIT_INLINE(volatile void *) git_atomic__swap( - void * volatile *ptr, void *newval) -{ - volatile void *old = *ptr; - *ptr = newval; - return old; -} - -GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) -{ - return *ptr; -} - -#ifdef GIT_ARCH_64 - -GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) -{ - a->val += addend; - return a->val; -} - -GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) -{ - a->val = val; -} - -GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) -{ - return (int64_t)a->val; -} - -#endif - -#endif - -/* - * Atomically replace the contents of *ptr (if they are equal to oldval) with - * newval. ptr must point to a pointer or a value that is the same size as a - * pointer. This is semantically compatible with: - * - * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ - * ({ \ - * void *foundval = *ptr; \ - * if (foundval == oldval) \ - * *ptr = newval; \ - * foundval; \ - * }) - * - * @return the original contents of *ptr. - */ -#define git_atomic_compare_and_swap(ptr, oldval, newval) \ - git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) - -/* - * Atomically replace the contents of v with newval. v must be the same size as - * a pointer. This is semantically compatible with: - * - * #define git_atomic_swap(v, newval) \ - * ({ \ - * volatile void *old = v; \ - * v = newval; \ - * old; \ - * }) - * - * @return the original contents of v. - */ -#define git_atomic_swap(v, newval) \ - (void *)git_atomic__swap((void * volatile *)&(v), newval) - -/* - * Atomically reads the contents of v. v must be the same size as a pointer. - * This is semantically compatible with: - * - * #define git_atomic_load(v) v - * - * @return the contents of v. - */ -#define git_atomic_load(v) \ - (void *)git_atomic__load((void * volatile *)&(v)) - -#if defined(GIT_THREADS) - -# if defined(GIT_WIN32) -# define GIT_MEMORY_BARRIER MemoryBarrier() -# elif defined(GIT_BUILTIN_ATOMIC) -# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST) -# elif defined(GIT_BUILTIN_SYNC) -# define GIT_MEMORY_BARRIER __sync_synchronize() -# endif - -#else - -# define GIT_MEMORY_BARRIER /* noop */ - -#endif - -/* Thread-local data */ - -#if !defined(GIT_THREADS) -# define git_tlsdata_key int -#elif defined(GIT_WIN32) -# define git_tlsdata_key DWORD -#elif defined(_POSIX_THREADS) -# define git_tlsdata_key pthread_key_t -#else -# error unknown threading model -#endif - -/** - * Create a thread-local data key. The destroy function will be - * called upon thread exit. On some platforms, it may be called - * when all threads have deleted their keys. - * - * Note that the tlsdata functions do not set an error message on - * failure; this is because the error handling in libgit2 is itself - * handled by thread-local data storage. - * - * @param key the tlsdata key - * @param destroy_fn function pointer called upon thread exit - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)); - -/** - * Set a the thread-local value for the given key. - * - * @param key the tlsdata key to store data on - * @param value the pointer to store - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_set(git_tlsdata_key key, void *value); - -/** - * Get the thread-local value for the given key. - * - * @param key the tlsdata key to retrieve the value of - * @return the pointer stored with git_tlsdata_set - */ -void *git_tlsdata_get(git_tlsdata_key key); - -/** - * Delete the given thread-local key. - * - * @param key the tlsdata key to dispose - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_dispose(git_tlsdata_key key); - -#endif diff --git a/src/threadstate.c b/src/threadstate.c deleted file mode 100644 index 9e3ef5818..000000000 --- a/src/threadstate.c +++ /dev/null @@ -1,84 +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 "threadstate.h" -#include "runtime.h" - -/** - * Handle the thread-local state - * - * `git_threadstate_global_init` will be called as part - * of `git_libgit2_init` (which itself must be called - * before calling any other function in the library). - * - * This function allocates a TLS index to store the per- - * thread state. - * - * Any internal method that requires thread-local state - * will then call `git_threadstate_get()` which returns a - * pointer to the thread-local state structure; this - * structure is lazily allocated on each thread. - * - * This mechanism will register a shutdown handler - * (`git_threadstate_global_shutdown`) which will free the - * TLS index. This shutdown handler will be called by - * `git_libgit2_shutdown`. - */ - -static git_tlsdata_key tls_key; - -static void threadstate_dispose(git_threadstate *threadstate) -{ - if (!threadstate) - return; - - if (threadstate->error_t.message != git_str__initstr) - git__free(threadstate->error_t.message); - threadstate->error_t.message = NULL; -} - -static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) -{ - threadstate_dispose(threadstate); - git__free(threadstate); -} - -static void git_threadstate_global_shutdown(void) -{ - git_threadstate *threadstate; - - threadstate = git_tlsdata_get(tls_key); - git_tlsdata_set(tls_key, NULL); - - threadstate_dispose(threadstate); - git__free(threadstate); - - git_tlsdata_dispose(tls_key); -} - -int git_threadstate_global_init(void) -{ - if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) - return -1; - - return git_runtime_shutdown_register(git_threadstate_global_shutdown); -} - -git_threadstate *git_threadstate_get(void) -{ - git_threadstate *threadstate; - - if ((threadstate = git_tlsdata_get(tls_key)) != NULL) - return threadstate; - - if ((threadstate = git__calloc(1, sizeof(git_threadstate))) == NULL || - git_str_init(&threadstate->error_buf, 0) < 0) - return NULL; - - git_tlsdata_set(tls_key, threadstate); - return threadstate; -} diff --git a/src/threadstate.h b/src/threadstate.h deleted file mode 100644 index c10f26b59..000000000 --- a/src/threadstate.h +++ /dev/null @@ -1,24 +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_threadstate_h__ -#define INCLUDE_threadstate_h__ - -#include "common.h" - -typedef struct { - git_error *last_error; - git_error error_t; - git_str error_buf; - char oid_fmt[GIT_OID_HEXSZ+1]; -} git_threadstate; - -extern int git_threadstate_global_init(void); -extern git_threadstate *git_threadstate_get(void); - -#define GIT_THREADSTATE (git_threadstate_get()) - -#endif diff --git a/src/trace.c b/src/trace.c deleted file mode 100644 index b0c56c4dc..000000000 --- a/src/trace.c +++ /dev/null @@ -1,25 +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 "trace.h" - -#include "str.h" -#include "runtime.h" -#include "git2/trace.h" - -struct git_trace_data git_trace__data = {0}; - -int git_trace_set(git_trace_level_t level, git_trace_cb callback) -{ - GIT_ASSERT_ARG(level == 0 || callback != NULL); - - git_trace__data.level = level; - git_trace__data.callback = callback; - GIT_MEMORY_BARRIER; - - return 0; -} diff --git a/src/trace.h b/src/trace.h deleted file mode 100644 index 239928dcb..000000000 --- a/src/trace.h +++ /dev/null @@ -1,51 +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_trace_h__ -#define INCLUDE_trace_h__ - -#include "common.h" - -#include -#include "str.h" - -struct git_trace_data { - git_trace_level_t level; - git_trace_cb callback; -}; - -extern struct git_trace_data git_trace__data; - -GIT_INLINE(void) git_trace__write_fmt( - git_trace_level_t level, - const char *fmt, - va_list ap) -{ - git_trace_cb callback = git_trace__data.callback; - git_str message = GIT_STR_INIT; - - git_str_vprintf(&message, fmt, ap); - - callback(level, git_str_cstr(&message)); - - git_str_dispose(&message); -} - -#define git_trace_level() (git_trace__data.level) - -GIT_INLINE(void) git_trace(git_trace_level_t level, const char *fmt, ...) -{ - if (git_trace__data.level >= level && - git_trace__data.callback != NULL) { - va_list ap; - - va_start(ap, fmt); - git_trace__write_fmt(level, fmt, ap); - va_end(ap); - } -} - -#endif diff --git a/src/trailer.c b/src/trailer.c deleted file mode 100644 index 4761c9922..000000000 --- a/src/trailer.c +++ /dev/null @@ -1,430 +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 "array.h" -#include "common.h" -#include "git2/message.h" - -#include -#include -#include - -#define COMMENT_LINE_CHAR '#' -#define TRAILER_SEPARATORS ":" - -static const char *const git_generated_prefixes[] = { - "Signed-off-by: ", - "(cherry picked from commit ", - NULL -}; - -static int is_blank_line(const char *str) -{ - const char *s = str; - while (*s && *s != '\n' && isspace(*s)) - s++; - return !*s || *s == '\n'; -} - -static const char *next_line(const char *str) -{ - const char *nl = strchr(str, '\n'); - - if (nl) { - return nl + 1; - } else { - /* return pointer to the NUL terminator: */ - return str + strlen(str); - } -} - -/* - * Return the position of the start of the last line. If len is 0, return 0. - */ -static bool last_line(size_t *out, const char *buf, size_t len) -{ - size_t i; - - *out = 0; - - if (len == 0) - return false; - if (len == 1) - 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 - * of the last line anyway. - */ - i = len - 2; - - for (; i > 0; i--) { - if (buf[i] == '\n') { - *out = i + 1; - return true; - } - } - return true; -} - -/* - * If the given line is of the form - * "..." 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 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 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)) { - *out = c - line; - return true; - } - - if (!whitespace_found && (isalnum(*c) || *c == '-')) - continue; - if (c != line && (*c == ' ' || *c == '\t')) { - whitespace_found = 1; - continue; - } - break; - } - return false; -} - -/* - * Inspect the given string and determine the true "end" of the log message, in - * order to find where to put a new Signed-off-by: line. Ignored are - * trailing comment lines and blank lines. To support "git commit -s - * --amend" on an existing commit, we also ignore "Conflicts:". To - * support "git commit -v", we truncate at cut lines. - * - * Returns the number of bytes from the tail to ignore, to be fed as - * the second parameter to append_signoff(). - */ -static size_t ignore_non_trailer(const char *buf, size_t len) -{ - size_t boc = 0, bol = 0; - int in_old_conflicts_block = 0; - size_t cutoff = len; - - while (bol < cutoff) { - const char *next_line = memchr(buf + bol, '\n', len - bol); - - if (!next_line) - next_line = buf + len; - else - next_line++; - - if (buf[bol] == COMMENT_LINE_CHAR || buf[bol] == '\n') { - /* is this the first of the run of comments? */ - if (!boc) - boc = bol; - /* otherwise, it is just continuing */ - } else if (git__prefixcmp(buf + bol, "Conflicts:\n") == 0) { - in_old_conflicts_block = 1; - if (!boc) - boc = bol; - } else if (in_old_conflicts_block && buf[bol] == '\t') { - ; /* a pathname in the conflicts block */ - } else if (boc) { - /* the previous was not trailing comment */ - boc = 0; - in_old_conflicts_block = 0; - } - bol = next_line - buf; - } - return boc ? len - boc : len - cutoff; -} - -/* - * Return the position of the start of the patch or the length of str if there - * is no patch in the message. - */ -static size_t find_patch_start(const char *str) -{ - const char *s; - - for (s = str; *s; s = next_line(s)) { - if (git__prefixcmp(s, "---") == 0) - return s - str; - } - - return s - str; -} - -/* - * Return the position of the first trailer line or len if there are no - * trailers. - */ -static size_t find_trailer_start(const char *buf, size_t len) -{ - const char *s; - 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 - * reset to 0 if we encounter a trailer (since those lines are to be - * considered continuations of that trailer), and added to - * non_trailer_lines if we encounter a non-trailer (since those lines - * are to be considered non-trailers). - */ - int possible_continuation_lines = 0; - - /* The first paragraph is the title and cannot be trailers */ - for (s = buf; s < buf + len; s = next_line(s)) { - if (s[0] == COMMENT_LINE_CHAR) - continue; - if (is_blank_line(s)) - break; - } - end_of_title = s - buf; - - /* - * Get the start of the trailers by looking starting from the end for a - * blank line before a set of non-blank lines that (i) are all - * trailers, or (ii) contains at least one Git-generated trailer and - * consists of at least 25% trailers. - */ - l = len; - while (last_line(&l, buf, l) && l >= end_of_title) { - const char *bol = buf + l; - const char *const *p; - size_t separator_pos = 0; - - if (bol[0] == COMMENT_LINE_CHAR) { - non_trailer_lines += possible_continuation_lines; - possible_continuation_lines = 0; - continue; - } - if (is_blank_line(bol)) { - if (only_spaces) - continue; - non_trailer_lines += possible_continuation_lines; - if (recognized_prefix && - trailer_lines * 3 >= non_trailer_lines) - return next_line(bol) - buf; - else if (trailer_lines && !non_trailer_lines) - return next_line(bol) - buf; - return len; - } - only_spaces = 0; - - for (p = git_generated_prefixes; *p; p++) { - if (git__prefixcmp(bol, *p) == 0) { - trailer_lines++; - possible_continuation_lines = 0; - recognized_prefix = 1; - goto continue_outer_loop; - } - } - - find_separator(&separator_pos, bol, TRAILER_SEPARATORS); - if (separator_pos >= 1 && !isspace(bol[0])) { - trailer_lines++; - possible_continuation_lines = 0; - if (recognized_prefix) - continue; - } else if (isspace(bol[0])) - possible_continuation_lines++; - else { - non_trailer_lines++; - non_trailer_lines += possible_continuation_lines; - possible_continuation_lines = 0; - } -continue_outer_loop: - ; - } - - return len; -} - -/* Return the position of the end of the trailers. */ -static size_t find_trailer_end(const char *buf, size_t len) -{ - return len - ignore_non_trailer(buf, len); -} - -static char *extract_trailer_block(const char *message, size_t *len) -{ - size_t patch_start = find_patch_start(message); - size_t trailer_end = find_trailer_end(message, patch_start); - size_t trailer_start = find_trailer_start(message, trailer_end); - - 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; - - *len = trailer_len; - - return buffer; -} - -enum trailer_state { - S_START = 0, - S_KEY = 1, - S_KEY_WS = 2, - S_SEP_WS = 3, - S_VALUE = 4, - S_VALUE_NL = 5, - S_VALUE_END = 6, - S_IGNORE = 7 -}; - -#define NEXT(st) { state = (st); ptr++; continue; } -#define GOTO(st) { state = (st); continue; } - -typedef git_array_t(git_message_trailer) git_array_trailer_t; - -int git_message_trailers(git_message_trailer_array *trailer_arr, const char *message) -{ - enum trailer_state state = S_START; - int rc = 0; - char *ptr; - char *key = NULL; - char *value = NULL; - git_array_trailer_t arr = GIT_ARRAY_INIT; - - size_t trailer_len; - char *trailer = extract_trailer_block(message, &trailer_len); - if (trailer == NULL) - return -1; - - for (ptr = trailer;;) { - switch (state) { - case S_START: { - if (*ptr == 0) { - goto ret; - } - - key = ptr; - GOTO(S_KEY); - } - case S_KEY: { - if (*ptr == 0) { - goto ret; - } - - if (isalnum(*ptr) || *ptr == '-') { - /* legal key character */ - NEXT(S_KEY); - } - - if (*ptr == ' ' || *ptr == '\t') { - /* optional whitespace before separator */ - *ptr = 0; - NEXT(S_KEY_WS); - } - - if (strchr(TRAILER_SEPARATORS, *ptr)) { - *ptr = 0; - NEXT(S_SEP_WS); - } - - /* illegal character */ - GOTO(S_IGNORE); - } - case S_KEY_WS: { - if (*ptr == 0) { - goto ret; - } - - if (*ptr == ' ' || *ptr == '\t') { - NEXT(S_KEY_WS); - } - - if (strchr(TRAILER_SEPARATORS, *ptr)) { - NEXT(S_SEP_WS); - } - - /* illegal character */ - GOTO(S_IGNORE); - } - case S_SEP_WS: { - if (*ptr == 0) { - goto ret; - } - - if (*ptr == ' ' || *ptr == '\t') { - NEXT(S_SEP_WS); - } - - value = ptr; - NEXT(S_VALUE); - } - case S_VALUE: { - if (*ptr == 0) { - GOTO(S_VALUE_END); - } - - if (*ptr == '\n') { - NEXT(S_VALUE_NL); - } - - NEXT(S_VALUE); - } - case S_VALUE_NL: { - if (*ptr == ' ') { - /* continuation; */ - NEXT(S_VALUE); - } - - ptr[-1] = 0; - GOTO(S_VALUE_END); - } - case S_VALUE_END: { - git_message_trailer *t = git_array_alloc(arr); - - t->key = key; - t->value = value; - - key = NULL; - value = NULL; - - GOTO(S_START); - } - case S_IGNORE: { - if (*ptr == 0) { - goto ret; - } - - if (*ptr == '\n') { - NEXT(S_START); - } - - NEXT(S_IGNORE); - } - } - } - -ret: - trailer_arr->_trailer_block = trailer; - trailer_arr->trailers = arr.ptr; - trailer_arr->count = arr.size; - - return rc; -} - -void git_message_trailer_array_free(git_message_trailer_array *arr) -{ - git__free(arr->_trailer_block); - git__free(arr->trailers); -} diff --git a/src/transaction.c b/src/transaction.c deleted file mode 100644 index ccffa9984..000000000 --- a/src/transaction.c +++ /dev/null @@ -1,395 +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 "transaction.h" - -#include "repository.h" -#include "strmap.h" -#include "refdb.h" -#include "pool.h" -#include "reflog.h" -#include "signature.h" -#include "config.h" - -#include "git2/transaction.h" -#include "git2/signature.h" -#include "git2/sys/refs.h" -#include "git2/sys/refdb_backend.h" - -typedef enum { - TRANSACTION_NONE, - TRANSACTION_REFS, - TRANSACTION_CONFIG -} transaction_t; - -typedef struct { - const char *name; - void *payload; - - git_reference_t ref_type; - union { - git_oid id; - char *symbolic; - } target; - git_reflog *reflog; - - const char *message; - git_signature *sig; - - unsigned int committed :1, - remove :1; -} transaction_node; - -struct git_transaction { - transaction_t type; - git_repository *repo; - git_refdb *db; - git_config *cfg; - - git_strmap *locks; - git_pool pool; -}; - -int git_transaction_config_new(git_transaction **out, git_config *cfg) -{ - git_transaction *tx; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); - - tx = git__calloc(1, sizeof(git_transaction)); - GIT_ERROR_CHECK_ALLOC(tx); - - tx->type = TRANSACTION_CONFIG; - tx->cfg = cfg; - *out = tx; - return 0; -} - -int git_transaction_new(git_transaction **out, git_repository *repo) -{ - int error; - git_pool pool; - git_transaction *tx = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if ((error = git_pool_init(&pool, 1)) < 0) - goto on_error; - - tx = git_pool_mallocz(&pool, sizeof(git_transaction)); - if (!tx) { - error = -1; - goto on_error; - } - - if ((error = git_strmap_new(&tx->locks)) < 0) { - error = -1; - goto on_error; - } - - if ((error = git_repository_refdb(&tx->db, repo)) < 0) - goto on_error; - - tx->type = TRANSACTION_REFS; - memcpy(&tx->pool, &pool, sizeof(git_pool)); - tx->repo = repo; - *out = tx; - return 0; - -on_error: - git_pool_clear(&pool); - return error; -} - -int git_transaction_lock_ref(git_transaction *tx, const char *refname) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - - node = git_pool_mallocz(&tx->pool, sizeof(transaction_node)); - GIT_ERROR_CHECK_ALLOC(node); - - node->name = git_pool_strdup(&tx->pool, refname); - GIT_ERROR_CHECK_ALLOC(node->name); - - if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) - return error; - - if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) - goto cleanup; - - return 0; - -cleanup: - git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); - - return error; -} - -static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) -{ - transaction_node *node; - - if ((node = git_strmap_get(tx->locks, refname)) == NULL) { - git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); - return GIT_ENOTFOUND; - } - - *out = node; - return 0; -} - -static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg) -{ - if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0) - return -1; - - if (!node->sig) { - git_signature *tmp; - int error; - - if (git_reference__log_signature(&tmp, tx->repo) < 0) - return -1; - - /* make sure the sig we use is in our pool */ - error = git_signature__pdup(&node->sig, tmp, &tx->pool); - git_signature_free(tmp); - if (error < 0) - return error; - } - - if (msg) { - node->message = git_pool_strdup(&tx->pool, msg); - GIT_ERROR_CHECK_ALLOC(node->message); - } - - return 0; -} - -int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - GIT_ASSERT_ARG(target); - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - if ((error = copy_common(node, tx, sig, msg)) < 0) - return error; - - git_oid_cpy(&node->target.id, target); - node->ref_type = GIT_REFERENCE_DIRECT; - - return 0; -} - -int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - GIT_ASSERT_ARG(target); - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - if ((error = copy_common(node, tx, sig, msg)) < 0) - return error; - - node->target.symbolic = git_pool_strdup(&tx->pool, target); - GIT_ERROR_CHECK_ALLOC(node->target.symbolic); - node->ref_type = GIT_REFERENCE_SYMBOLIC; - - return 0; -} - -int git_transaction_remove(git_transaction *tx, const char *refname) -{ - int error; - transaction_node *node; - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - node->remove = true; - node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */ - - return 0; -} - -static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool) -{ - git_reflog *reflog; - git_reflog_entry *entries; - size_t len, i; - - reflog = git_pool_mallocz(pool, sizeof(git_reflog)); - GIT_ERROR_CHECK_ALLOC(reflog); - - reflog->ref_name = git_pool_strdup(pool, in->ref_name); - GIT_ERROR_CHECK_ALLOC(reflog->ref_name); - - len = in->entries.length; - reflog->entries.length = len; - reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(reflog->entries.contents); - - entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry)); - GIT_ERROR_CHECK_ALLOC(entries); - - for (i = 0; i < len; i++) { - const git_reflog_entry *src; - git_reflog_entry *tgt; - - tgt = &entries[i]; - reflog->entries.contents[i] = tgt; - - src = git_vector_get(&in->entries, i); - git_oid_cpy(&tgt->oid_old, &src->oid_old); - git_oid_cpy(&tgt->oid_cur, &src->oid_cur); - - tgt->msg = git_pool_strdup(pool, src->msg); - GIT_ERROR_CHECK_ALLOC(tgt->msg); - - if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0) - return -1; - } - - - *out = reflog; - return 0; -} - -int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - GIT_ASSERT_ARG(reflog); - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0) - return error; - - return 0; -} - -static int update_target(git_refdb *db, transaction_node *node) -{ - git_reference *ref; - int error, update_reflog; - - if (node->ref_type == GIT_REFERENCE_DIRECT) { - ref = git_reference__alloc(node->name, &node->target.id, NULL); - } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { - ref = git_reference__alloc_symbolic(node->name, node->target.symbolic); - } else { - abort(); - } - - GIT_ERROR_CHECK_ALLOC(ref); - update_reflog = node->reflog == NULL; - - if (node->remove) { - error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL); - } else if (node->ref_type == GIT_REFERENCE_DIRECT) { - error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); - } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { - error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); - } else { - abort(); - } - - git_reference_free(ref); - node->committed = true; - - return error; -} - -int git_transaction_commit(git_transaction *tx) -{ - transaction_node *node; - int error = 0; - - GIT_ASSERT_ARG(tx); - - if (tx->type == TRANSACTION_CONFIG) { - error = git_config_unlock(tx->cfg, true); - tx->cfg = NULL; - - return error; - } - - git_strmap_foreach_value(tx->locks, node, { - if (node->reflog) { - if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) - return error; - } - - 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; - } - }); - - return 0; -} - -void git_transaction_free(git_transaction *tx) -{ - transaction_node *node; - git_pool pool; - - if (!tx) - return; - - if (tx->type == TRANSACTION_CONFIG) { - if (tx->cfg) { - git_config_unlock(tx->cfg, false); - git_config_free(tx->cfg); - } - - git__free(tx); - return; - } - - /* start by unlocking the ones we've left hanging, if any */ - git_strmap_foreach_value(tx->locks, node, { - if (node->committed) - continue; - - git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); - }); - - git_refdb_free(tx->db); - git_strmap_free(tx->locks); - - /* tx is inside the pool, so we need to extract the data */ - memcpy(&pool, &tx->pool, sizeof(git_pool)); - git_pool_clear(&pool); -} diff --git a/src/transaction.h b/src/transaction.h deleted file mode 100644 index 780c06830..000000000 --- a/src/transaction.h +++ /dev/null @@ -1,14 +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_transaction_h__ -#define INCLUDE_transaction_h__ - -#include "common.h" - -int git_transaction_config_new(git_transaction **out, git_config *cfg); - -#endif diff --git a/src/transport.c b/src/transport.c deleted file mode 100644 index 640ccacae..000000000 --- a/src/transport.c +++ /dev/null @@ -1,222 +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 "common.h" - -#include "git2/types.h" -#include "git2/remote.h" -#include "git2/net.h" -#include "git2/transport.h" -#include "git2/sys/transport.h" -#include "fs_path.h" - -typedef struct transport_definition { - char *prefix; - git_transport_cb fn; - void *param; -} transport_definition; - -static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL }; -static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL }; -#ifdef GIT_SSH -static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL }; -#endif - -static transport_definition local_transport_definition = { "file://", git_transport_local, NULL }; - -static transport_definition transports[] = { - { "git://", git_transport_smart, &git_subtransport_definition }, - { "http://", git_transport_smart, &http_subtransport_definition }, - { "https://", git_transport_smart, &http_subtransport_definition }, - { "file://", git_transport_local, NULL }, -#ifdef GIT_SSH - { "ssh://", git_transport_smart, &ssh_subtransport_definition }, - { "ssh+git://", git_transport_smart, &ssh_subtransport_definition }, - { "git+ssh://", git_transport_smart, &ssh_subtransport_definition }, -#endif - { NULL, 0, 0 } -}; - -static git_vector custom_transports = GIT_VECTOR_INIT; - -#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 - -static transport_definition * transport_find_by_url(const char *url) -{ - size_t i = 0; - transport_definition *d; - - /* Find a user transport who wants to deal with this URI */ - git_vector_foreach(&custom_transports, i, d) { - if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { - return d; - } - } - - /* Find a system transport for this URI */ - for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { - d = &transports[i]; - - if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { - return d; - } - } - - return NULL; -} - -static int transport_find_fn( - git_transport_cb *out, - const char *url, - void **param) -{ - transport_definition *definition = transport_find_by_url(url); - -#ifdef GIT_WIN32 - /* On Windows, it might not be possible to discern between absolute local - * and ssh paths - first check if this is a valid local path that points - * to a directory and if so assume local path, else assume SSH */ - - /* Check to see if the path points to a file on the local file system */ - if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) - definition = &local_transport_definition; -#endif - - /* For other systems, perform the SSH check first, to avoid going to the - * filesystem if it is not necessary */ - - /* It could be a SSH remote path. Check to see if there's a : */ - if (!definition && strrchr(url, ':')) { - /* re-search transports again with ssh:// as url - * so that we can find a third party ssh transport */ - definition = transport_find_by_url("ssh://"); - } - -#ifndef GIT_WIN32 - /* Check to see if the path points to a file on the local file system */ - if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) - definition = &local_transport_definition; -#endif - - if (!definition) - return GIT_ENOTFOUND; - - *out = definition->fn; - *param = definition->param; - - return 0; -} - -/************** - * Public API * - **************/ - -int git_transport_new(git_transport **out, git_remote *owner, const char *url) -{ - git_transport_cb fn; - git_transport *transport; - void *param; - int error; - - if ((error = transport_find_fn(&fn, url, ¶m)) == GIT_ENOTFOUND) { - git_error_set(GIT_ERROR_NET, "unsupported URL protocol"); - return -1; - } else if (error < 0) - return error; - - if ((error = fn(&transport, owner, param)) < 0) - return error; - - GIT_ERROR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); - - *out = transport; - - return 0; -} - -int git_transport_register( - const char *scheme, - git_transport_cb cb, - void *param) -{ - git_str prefix = GIT_STR_INIT; - transport_definition *d, *definition = NULL; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(scheme); - GIT_ASSERT_ARG(cb); - - if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) - goto on_error; - - git_vector_foreach(&custom_transports, i, d) { - if (strcasecmp(d->prefix, prefix.ptr) == 0) { - error = GIT_EEXISTS; - goto on_error; - } - } - - definition = git__calloc(1, sizeof(transport_definition)); - GIT_ERROR_CHECK_ALLOC(definition); - - definition->prefix = git_str_detach(&prefix); - definition->fn = cb; - definition->param = param; - - if (git_vector_insert(&custom_transports, definition) < 0) - goto on_error; - - return 0; - -on_error: - git_str_dispose(&prefix); - git__free(definition); - return error; -} - -int git_transport_unregister(const char *scheme) -{ - git_str prefix = GIT_STR_INIT; - transport_definition *d; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(scheme); - - if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) - goto done; - - git_vector_foreach(&custom_transports, i, d) { - if (strcasecmp(d->prefix, prefix.ptr) == 0) { - if ((error = git_vector_remove(&custom_transports, i)) < 0) - goto done; - - git__free(d->prefix); - git__free(d); - - if (!custom_transports.length) - git_vector_free(&custom_transports); - - error = 0; - goto done; - } - } - - error = GIT_ENOTFOUND; - -done: - git_str_dispose(&prefix); - return error; -} - -int git_transport_init(git_transport *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_transport, GIT_TRANSPORT_INIT); - return 0; -} diff --git a/src/transports/auth.c b/src/transports/auth.c deleted file mode 100644 index 90b6b124f..000000000 --- a/src/transports/auth.c +++ /dev/null @@ -1,74 +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 "auth.h" - -#include "git2/sys/credential.h" - -static int basic_next_token( - git_str *out, - git_http_auth_context *ctx, - git_credential *c) -{ - git_credential_userpass_plaintext *cred; - git_str raw = GIT_STR_INIT; - int error = GIT_EAUTH; - - GIT_UNUSED(ctx); - - if (c->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { - git_error_set(GIT_ERROR_INVALID, "invalid credential type for basic auth"); - goto on_error; - } - - cred = (git_credential_userpass_plaintext *)c; - - git_str_printf(&raw, "%s:%s", cred->username, cred->password); - - if (git_str_oom(&raw) || - git_str_puts(out, "Basic ") < 0 || - git_str_encode_base64(out, git_str_cstr(&raw), raw.size) < 0) - goto on_error; - - error = 0; - -on_error: - if (raw.size) - git__memzero(raw.ptr, raw.size); - - git_str_dispose(&raw); - return error; -} - -static git_http_auth_context basic_context = { - 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 git_net_url *url) -{ - GIT_UNUSED(url); - - *out = &basic_context; - return 0; -} - -int git_http_auth_dummy( - git_http_auth_context **out, const git_net_url *url) -{ - GIT_UNUSED(url); - - *out = NULL; - return GIT_PASSTHROUGH; -} - diff --git a/src/transports/auth.h b/src/transports/auth.h deleted file mode 100644 index 64680cc53..000000000 --- a/src/transports/auth.h +++ /dev/null @@ -1,70 +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_transports_auth_h__ -#define INCLUDE_transports_auth_h__ - -#include "common.h" - -#include "netops.h" - -typedef enum { - 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_auth_t type; - - /** Supported credentials */ - 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_str *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); -}; - -typedef struct { - /** Type of scheme */ - git_http_auth_t type; - - /** Name of the scheme (as used in the Authorization header) */ - const char *name; - - /** Credential types this scheme supports */ - git_credential_t credtypes; - - /** Function to initialize an authentication context */ - int (*init_context)( - git_http_auth_context **out, - const git_net_url *url); -} git_http_auth_scheme; - -int git_http_auth_dummy( - git_http_auth_context **out, - const git_net_url *url); - -int git_http_auth_basic( - git_http_auth_context **out, - const git_net_url *url); - -#endif diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c deleted file mode 100644 index 6380504be..000000000 --- a/src/transports/auth_negotiate.c +++ /dev/null @@ -1,314 +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 "auth_negotiate.h" - -#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) - -#include "git2.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" }; -static gss_OID_desc negotiate_oid_krb5 = - { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; - -static gss_OID negotiate_oids[] = - { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL }; - -typedef struct { - git_http_auth_context parent; - unsigned configured : 1, - complete : 1; - git_str target; - char *challenge; - gss_ctx_id_t gss_context; - gss_OID oid; -} http_auth_negotiate_context; - -static void negotiate_err_set( - OM_uint32 status_major, - OM_uint32 status_minor, - const char *message) -{ - gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; - OM_uint32 status_display, context = 0; - - if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE, - GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) { - git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)", - message, (int)buffer.length, (const char *)buffer.value, - status_major, status_minor); - gss_release_buffer(&status_minor, &buffer); - } else { - git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)", - message, status_major, status_minor); - } -} - -static int negotiate_set_challenge( - git_http_auth_context *c, - const char *challenge) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(challenge); - GIT_ASSERT(ctx->configured); - - git__free(ctx->challenge); - - ctx->challenge = git__strdup(challenge); - GIT_ERROR_CHECK_ALLOC(ctx->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_str_dispose(&ctx->target); - - git__free(ctx->challenge); - ctx->challenge = NULL; -} - -static int negotiate_next_token( - git_str *buf, - git_http_auth_context *c, - git_credential *cred) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - OM_uint32 status_major, status_minor; - gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER, - input_token = GSS_C_EMPTY_BUFFER, - output_token = GSS_C_EMPTY_BUFFER; - gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER; - git_str input_buf = GIT_STR_INIT; - gss_name_t server = NULL; - gss_OID mech; - size_t challenge_len; - int error = 0; - - GIT_ASSERT_ARG(buf); - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(cred); - - GIT_ASSERT(ctx->configured); - GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT); - - if (ctx->complete) - return 0; - - target_buffer.value = (void *)ctx->target.ptr; - target_buffer.length = ctx->target.size; - - status_major = gss_import_name(&status_minor, &target_buffer, - GSS_C_NT_HOSTBASED_SERVICE, &server); - - if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, - "could not parse principal"); - error = -1; - goto done; - } - - challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; - - 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; - } - - if (challenge_len > 9) { - if (git_str_decode_base64(&input_buf, - ctx->challenge + 10, challenge_len - 10) < 0) { - git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server"); - error = -1; - goto done; - } - - input_token.value = input_buf.ptr; - input_token.length = input_buf.size; - input_token_ptr = &input_token; - } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { - negotiate_context_dispose(ctx); - } - - mech = &negotiate_oid_spnego; - - status_major = gss_init_sec_context( - &status_minor, - GSS_C_NO_CREDENTIAL, - &ctx->gss_context, - server, - mech, - GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG, - GSS_C_INDEFINITE, - GSS_C_NO_CHANNEL_BINDINGS, - input_token_ptr, - NULL, - &output_token, - NULL, - 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; - } - - if (output_token.length == 0) { - git_error_set(GIT_ERROR_NET, "GSSAPI did not return token"); - error = -1; - goto done; - } - - git_str_puts(buf, "Negotiate "); - git_str_encode_base64(buf, output_token.value, output_token.length); - - if (git_str_oom(buf)) - error = -1; - -done: - gss_release_name(&status_minor, &server); - gss_release_buffer(&status_minor, (gss_buffer_t) &output_token); - git_str_dispose(&input_buf); - return error; -} - -static int negotiate_is_complete(git_http_auth_context *c) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - - GIT_ASSERT_ARG(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; - - negotiate_context_dispose(ctx); - - ctx->configured = 0; - ctx->complete = 0; - ctx->oid = NULL; - - git__free(ctx); -} - -static int negotiate_init_context( - http_auth_negotiate_context *ctx, - const git_net_url *url) -{ - OM_uint32 status_major, status_minor; - gss_OID item, *oid; - gss_OID_set mechanism_list; - size_t i; - - /* Query supported mechanisms looking for SPNEGO) */ - 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; - } - - if (mechanism_list) { - for (oid = negotiate_oids; *oid; oid++) { - for (i = 0; i < mechanism_list->count; i++) { - item = &mechanism_list->elements[i]; - - if (item->length == (*oid)->length && - memcmp(item->elements, (*oid)->elements, item->length) == 0) { - ctx->oid = *oid; - break; - } - - } - - if (ctx->oid) - break; - } - } - - gss_release_oid_set(&status_minor, &mechanism_list); - - if (!ctx->oid) { - git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported"); - return GIT_EAUTH; - } - - git_str_puts(&ctx->target, "HTTP@"); - git_str_puts(&ctx->target, url->host); - - if (git_str_oom(&ctx->target)) - return -1; - - ctx->gss_context = GSS_C_NO_CONTEXT; - ctx->configured = 1; - - return 0; -} - -int git_http_auth_negotiate( - git_http_auth_context **out, - const git_net_url *url) -{ - http_auth_negotiate_context *ctx; - - *out = NULL; - - ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); - GIT_ERROR_CHECK_ALLOC(ctx); - - if (negotiate_init_context(ctx, url) < 0) { - git__free(ctx); - return -1; - } - - 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; - - return 0; -} - -#endif /* GIT_GSSAPI */ - diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h deleted file mode 100644 index 34aff295b..000000000 --- a/src/transports/auth_negotiate.h +++ /dev/null @@ -1,27 +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_transports_auth_negotiate_h__ -#define INCLUDE_transports_auth_negotiate_h__ - -#include "common.h" -#include "git2.h" -#include "auth.h" - -#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) - -extern int git_http_auth_negotiate( - git_http_auth_context **out, - const git_net_url *url); - -#else - -#define git_http_auth_negotiate git_http_auth_dummy - -#endif /* GIT_GSSAPI */ - -#endif diff --git a/src/transports/auth_ntlm.c b/src/transports/auth_ntlm.c deleted file mode 100644 index f49ce101a..000000000 --- a/src/transports/auth_ntlm.c +++ /dev/null @@ -1,227 +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 "auth_ntlm.h" - -#include "common.h" -#include "str.h" -#include "auth.h" -#include "git2/sys/credential.h" - -#ifdef GIT_NTLM - -#include "ntlmclient.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; - - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(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; - - GIT_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_str *buf, - git_http_auth_context *c, - git_credential *cred) -{ - http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; - git_str input_buf = GIT_STR_INIT; - const unsigned char *msg; - size_t challenge_len, msg_len; - int error = GIT_EAUTH; - - GIT_ASSERT_ARG(buf); - GIT_ASSERT_ARG(ctx); - - GIT_ASSERT(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_str_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_str_puts(buf, "NTLM "); - git_str_encode_base64(buf, (const char *)msg, msg_len); - - if (git_str_oom(buf)) - goto done; - - error = 0; - -done: - git_str_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; - - GIT_ASSERT_ARG(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 deleted file mode 100644 index 40689498c..000000000 --- a/src/transports/auth_ntlm.h +++ /dev/null @@ -1,37 +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_transports_auth_ntlm_h__ -#define INCLUDE_transports_auth_ntlm_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/credential.c b/src/transports/credential.c deleted file mode 100644 index 6e00b0282..000000000 --- a/src/transports/credential.c +++ /dev/null @@ -1,486 +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 "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; - - GIT_ASSERT_ARG(cred); - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(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; - - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(cred); - GIT_ASSERT_ARG(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; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(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; - - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(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; - - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(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; - - GIT_ASSERT_ARG(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; - - GIT_ASSERT_ARG(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 */ - -#ifndef GIT_DEPRECATE_HARD -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); -} -#endif diff --git a/src/transports/credential_helpers.c b/src/transports/credential_helpers.c deleted file mode 100644 index 6d34a4e97..000000000 --- a/src/transports/credential_helpers.c +++ /dev/null @@ -1,68 +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 "common.h" - -#include "git2/credential_helpers.h" - -int git_credential_userpass( - git_credential **cred, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload) -{ - git_credential_userpass_payload *userpass = (git_credential_userpass_payload*)payload; - const char *effective_username = NULL; - - GIT_UNUSED(url); - - if (!userpass || !userpass->password) return -1; - - /* Username resolution: a username can be passed with the URL, the - * credentials payload, or both. Here's what we do. Note that if we get - * this far, we know that any password the url may contain has already - * failed at least once, so we ignore it. - * - * | Payload | URL | Used | - * +-------------+----------+-----------+ - * | yes | no | payload | - * | yes | yes | payload | - * | no | yes | url | - * | no | no | FAIL | - */ - if (userpass->username) - effective_username = userpass->username; - else if (user_from_url) - effective_username = user_from_url; - else - return -1; - - if (GIT_CREDENTIAL_USERNAME & allowed_types) - return git_credential_username_new(cred, effective_username); - - 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 */ - -#ifndef GIT_DEPRECATE_HARD -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); -} -#endif diff --git a/src/transports/git.c b/src/transports/git.c deleted file mode 100644 index 591e2ab03..000000000 --- a/src/transports/git.c +++ /dev/null @@ -1,361 +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 "common.h" - -#include "netops.h" -#include "stream.h" -#include "streams/socket.h" -#include "git2/sys/transport.h" - -#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) - -static const char prefix_git[] = "git://"; -static const char cmd_uploadpack[] = "git-upload-pack"; -static const char cmd_receivepack[] = "git-receive-pack"; - -typedef struct { - git_smart_subtransport_stream parent; - git_stream *io; - const char *cmd; - char *url; - unsigned sent_command : 1; -} git_proto_stream; - -typedef struct { - git_smart_subtransport parent; - git_transport *owner; - git_proto_stream *current_stream; -} git_subtransport; - -/* - * Create a git protocol request. - * - * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 - */ -static int gen_proto(git_str *request, const char *cmd, const char *url) -{ - char *delim, *repo; - char host[] = "host="; - size_t len; - - delim = strchr(url, '/'); - if (delim == NULL) { - git_error_set(GIT_ERROR_NET, "malformed URL"); - return -1; - } - - repo = delim; - if (repo[1] == '~') - ++repo; - - delim = strchr(url, ':'); - if (delim == NULL) - delim = strchr(url, '/'); - - len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; - - git_str_grow(request, len); - git_str_printf(request, "%04x%s %s%c%s", - (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); - git_str_put(request, url, delim - url); - git_str_putc(request, '\0'); - - if (git_str_oom(request)) - return -1; - - return 0; -} - -static int send_command(git_proto_stream *s) -{ - git_str request = GIT_STR_INIT; - int error; - - if ((error = gen_proto(&request, s->cmd, s->url)) < 0) - goto cleanup; - - if ((error = git_stream__write_full(s->io, request.ptr, request.size, 0)) < 0) - goto cleanup; - - s->sent_command = 1; - -cleanup: - git_str_dispose(&request); - return error; -} - -static int git_proto_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) -{ - int error; - git_proto_stream *s = (git_proto_stream *)stream; - gitno_buffer buf; - - *bytes_read = 0; - - if (!s->sent_command && (error = send_command(s)) < 0) - return error; - - gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size); - - if ((error = gitno_recv(&buf)) < 0) - return error; - - *bytes_read = buf.offset; - - return 0; -} - -static int git_proto_stream_write( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - git_proto_stream *s = (git_proto_stream *)stream; - int error; - - if (!s->sent_command && (error = send_command(s)) < 0) - return error; - - return git_stream__write_full(s->io, buffer, len, 0); -} - -static void git_proto_stream_free(git_smart_subtransport_stream *stream) -{ - git_proto_stream *s; - git_subtransport *t; - - if (!stream) - return; - - s = (git_proto_stream *)stream; - t = OWNING_SUBTRANSPORT(s); - - t->current_stream = NULL; - - git_stream_close(s->io); - git_stream_free(s->io); - git__free(s->url); - git__free(s); -} - -static int git_proto_stream_alloc( - git_subtransport *t, - const char *url, - const char *cmd, - const char *host, - const char *port, - git_smart_subtransport_stream **stream) -{ - git_proto_stream *s; - - if (!stream) - return -1; - - s = git__calloc(1, sizeof(git_proto_stream)); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = git_proto_stream_read; - s->parent.write = git_proto_stream_write; - s->parent.free = git_proto_stream_free; - - s->cmd = cmd; - s->url = git__strdup(url); - - if (!s->url) { - git__free(s); - return -1; - } - - if ((git_socket_stream_new(&s->io, host, port)) < 0) - return -1; - - GIT_ERROR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); - - *stream = &s->parent; - return 0; -} - -static int _git_uploadpack_ls( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - git_net_url urldata = GIT_NET_URL_INIT; - const char *stream_url = url; - const char *host, *port; - git_proto_stream *s; - int error; - - *stream = NULL; - - if (!git__prefixcmp(url, prefix_git)) - stream_url += strlen(prefix_git); - - 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_net_url_dispose(&urldata); - - if (error < 0) { - git_proto_stream_free(*stream); - return error; - } - - s = (git_proto_stream *) *stream; - if ((error = git_stream_connect(s->io)) < 0) { - git_proto_stream_free(*stream); - return error; - } - - t->current_stream = s; - - return 0; -} - -static int _git_uploadpack( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); - return -1; -} - -static int _git_receivepack_ls( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - git_net_url urldata = GIT_NET_URL_INIT; - const char *stream_url = url; - git_proto_stream *s; - int error; - - *stream = NULL; - if (!git__prefixcmp(url, prefix_git)) - stream_url += strlen(prefix_git); - - if ((error = git_net_url_parse(&urldata, url)) < 0) - return error; - - error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); - - git_net_url_dispose(&urldata); - - if (error < 0) { - git_proto_stream_free(*stream); - return error; - } - - s = (git_proto_stream *) *stream; - - if ((error = git_stream_connect(s->io)) < 0) - return error; - - t->current_stream = s; - - return 0; -} - -static int _git_receivepack( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); - return -1; -} - -static int _git_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - git_subtransport *t = (git_subtransport *) subtransport; - - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return _git_uploadpack_ls(t, url, stream); - - case GIT_SERVICE_UPLOADPACK: - return _git_uploadpack(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK_LS: - return _git_receivepack_ls(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK: - return _git_receivepack(t, url, stream); - } - - *stream = NULL; - return -1; -} - -static int _git_close(git_smart_subtransport *subtransport) -{ - git_subtransport *t = (git_subtransport *) subtransport; - - GIT_ASSERT(!t->current_stream); - - GIT_UNUSED(t); - - return 0; -} - -static void _git_free(git_smart_subtransport *subtransport) -{ - git_subtransport *t = (git_subtransport *) subtransport; - - git__free(t); -} - -int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param) -{ - git_subtransport *t; - - GIT_UNUSED(param); - - if (!out) - return -1; - - t = git__calloc(1, sizeof(git_subtransport)); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = owner; - t->parent.action = _git_action; - t->parent.close = _git_close; - t->parent.free = _git_free; - - *out = (git_smart_subtransport *) t; - return 0; -} diff --git a/src/transports/http.c b/src/transports/http.c deleted file mode 100644 index 7db5582ca..000000000 --- a/src/transports/http.c +++ /dev/null @@ -1,760 +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 "common.h" - -#ifndef GIT_WINHTTP - -#include "http_parser.h" -#include "net.h" -#include "netops.h" -#include "remote.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" -#include "git2/sys/credential.h" - -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 int initial : 1, - 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", - 1, - 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, - 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", - 1, - 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", - 0, - 1 -}; - -#define SERVER_TYPE_REMOTE "remote" -#define SERVER_TYPE_PROXY "proxy" - -#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) - -static int apply_url_credentials( - git_credential **cred, - unsigned int allowed_types, - const char *username, - const char *password) -{ - GIT_ASSERT_ARG(username); - - if (!password) - 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 GIT_PASSTHROUGH; -} - -GIT_INLINE(void) free_cred(git_credential **cred) -{ - if (*cred) { - git_credential_free(*cred); - (*cred) = NULL; - } -} - -static int handle_auth( - http_server *server, - const char *server_type, - const char *url, - unsigned int allowed_schemetypes, - unsigned int allowed_credtypes, - git_credential_acquire_cb callback, - void *callback_payload) -{ - int error = 1; - - 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) { - 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 (error > 0 && callback) { - error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload); - - /* treat GIT_PASSTHROUGH as if callback isn't set */ - if (error == GIT_PASSTHROUGH) - error = 1; - } - - if (error > 0) { - git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); - error = GIT_EAUTH; - } - - if (!error) - server->auth_schemetypes = allowed_schemetypes; - - return error; -} - -GIT_INLINE(int) handle_remote_auth( - http_stream *stream, - git_http_response *response) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - - if (response->server_auth_credtypes == 0) { - git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); - return GIT_EAUTH; - } - - /* Otherwise, prompt for credentials. */ - return handle_auth( - &transport->server, - SERVER_TYPE_REMOTE, - transport->owner->url, - response->server_auth_schemetypes, - response->server_auth_credtypes, - connect_opts->callbacks.credentials, - connect_opts->callbacks.payload); -} - -GIT_INLINE(int) handle_proxy_auth( - http_stream *stream, - git_http_response *response) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - - if (response->proxy_auth_credtypes == 0) { - git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); - return GIT_EAUTH; - } - - /* Otherwise, prompt for credentials. */ - return handle_auth( - &transport->proxy, - SERVER_TYPE_PROXY, - connect_opts->proxy_opts.url, - response->server_auth_schemetypes, - response->proxy_auth_credtypes, - connect_opts->proxy_opts.credentials, - connect_opts->proxy_opts.payload); -} - -static bool allow_redirect(http_stream *stream) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - - switch (transport->owner->connect_opts.follow_redirects) { - case GIT_REMOTE_REDIRECT_INITIAL: - return (stream->service->initial == 1); - case GIT_REMOTE_REDIRECT_ALL: - return true; - default: - return false; - } -} - -static int handle_response( - bool *complete, - http_stream *stream, - git_http_response *response, - bool allow_replay) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - int error; - - *complete = false; - - 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, allow_redirect(stream), stream->service->url) < 0) { - return -1; - } - - return 0; - } else if (git_http_response_is_redirect(response)) { - git_error_set(GIT_ERROR_HTTP, "unexpected redirect"); - return -1; - } - - /* 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 GIT_EAUTH; - } - - 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 (!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 (strcmp(response->content_type, stream->service->response_type) != 0) { - git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type); - return -1; - } - - *complete = true; - stream->state = HTTP_STATE_RECEIVING_RESPONSE; - return 0; -} - -static int lookup_proxy( - bool *out_use, - http_subtransport *transport) -{ - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - const char *proxy; - git_remote *remote; - char *config = NULL; - int error = 0; - - *out_use = false; - git_net_url_dispose(&transport->proxy.url); - - switch (connect_opts->proxy_opts.type) { - case GIT_PROXY_SPECIFIED: - proxy = connect_opts->proxy_opts.url; - break; - - case GIT_PROXY_AUTO: - remote = transport->owner->owner; - - error = git_remote__http_proxy(&config, remote, &transport->server.url); - - if (error || !config) - goto done; - - proxy = config; - break; - - default: - return 0; - } - - if (!proxy || - (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) - goto done; - - *out_use = true; - -done: - git__free(config); - return error; -} - -static int generate_request( - git_net_url *url, - git_http_request *request, - http_stream *stream, - size_t len) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - bool use_proxy = false; - int error; - - if ((error = git_net_url_joinpath(url, - &transport->server.url, stream->service->url)) < 0 || - (error = lookup_proxy(&use_proxy, transport)) < 0) - return error; - - 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->connect_opts.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; - } - - 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; - } - - /* - * Formulate the URL, send the request and read the response - * headers. Some of the request body may also be read. - */ - 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 = 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++; - } - - if (stream->state == HTTP_STATE_SENDING_REQUEST) { - git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - error = GIT_ERROR; /* not GIT_EAUTH, because the exact cause is unclear */ - goto done; - } - - GIT_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 bool needs_probe(http_stream *stream) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - - return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM || - transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE); -} - -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; - - /* NTLM requires a full challenge/response */ - if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM) - steps = GIT_AUTH_STEPS_NTLM; - - /* - * 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 ((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; - } - -done: - git_http_response_dispose(&response); - git_net_url_dispose(&url); - return error; -} - -/* -* 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; - - while (stream->state == HTTP_STATE_NONE && - stream->replay_count < GIT_HTTP_REPLAY_MAX) { - - git_net_url_dispose(&url); - git_http_response_dispose(&response); - - /* - * 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 (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 { - stream->state = HTTP_STATE_SENDING_REQUEST; - } - - stream->replay_count++; - } - - if (stream->state == HTTP_STATE_NONE) { - git_error_set(GIT_ERROR_HTTP, - "too many redirects or authentication replays"); - error = GIT_ERROR; /* not GIT_EAUTH because the exact cause is unclear */ - goto done; - } - - GIT_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; -} - -/* -* 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 *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; - - *out_len = 0; - - 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; - - GIT_ASSERT(complete); - stream->state = HTTP_STATE_RECEIVING_RESPONSE; - } - - error = git_http_client_read_body(client, buffer, buffer_size); - - if (error > 0) { - *out_len = error; - error = 0; - } - -done: - git_http_response_dispose(&response); - return error; -} - -static void http_stream_free(git_smart_subtransport_stream *stream) -{ - http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent); - git__free(s); -} - -static const http_service *select_service(git_smart_service_t action) -{ - 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; - } - - return NULL; -} - -static int http_action( - git_smart_subtransport_stream **out, - git_smart_subtransport *t, - const char *url, - git_smart_service_t action) -{ - http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - http_stream *stream; - const http_service *service; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(t); - - *out = NULL; - - /* - * If we've seen a redirect then preserve the location that we've - * been given. This is important to continue authorization against - * the redirect target, not the user-given source; the endpoint may - * have redirected us from HTTP->HTTPS and is using an auth mechanism - * that would be insecure in plaintext (eg, HTTP Basic). - */ - if (!git_net_url_valid(&transport->server.url) && - (error = git_net_url_parse(&transport->server.url, url)) < 0) - return error; - - if ((service = select_service(action)) == NULL) { - git_error_set(GIT_ERROR_HTTP, "invalid action"); - 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 = connect_opts->callbacks.certificate_check; - opts.server_certificate_check_payload = connect_opts->callbacks.payload; - opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check; - opts.proxy_certificate_check_payload = connect_opts->proxy_opts.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 int http_close(git_smart_subtransport *t) -{ - http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - - free_cred(&transport->server.cred); - free_cred(&transport->proxy.cred); - - transport->server.url_cred_presented = false; - transport->proxy.url_cred_presented = false; - - git_net_url_dispose(&transport->server.url); - git_net_url_dispose(&transport->proxy.url); - - return 0; -} - -static void http_free(git_smart_subtransport *t) -{ - http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - - git_http_client_free(transport->http_client); - - http_close(t); - git__free(transport); -} - -int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) -{ - http_subtransport *transport; - - GIT_UNUSED(param); - - GIT_ASSERT_ARG(out); - - transport = git__calloc(sizeof(http_subtransport), 1); - GIT_ERROR_CHECK_ALLOC(transport); - - transport->owner = (transport_smart *)owner; - transport->parent.action = http_action; - transport->parent.close = http_close; - transport->parent.free = http_free; - - *out = (git_smart_subtransport *) transport; - return 0; -} - -#endif /* !GIT_WINHTTP */ diff --git a/src/transports/http.h b/src/transports/http.h deleted file mode 100644 index 8e8e7226e..000000000 --- a/src/transports/http.h +++ /dev/null @@ -1,28 +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_transports_http_h__ -#define INCLUDE_transports_http_h__ - -#include "settings.h" -#include "httpclient.h" - -#define GIT_HTTP_REPLAY_MAX 15 - -extern bool git_http__expect_continue; - -GIT_INLINE(int) git_http__user_agent(git_str *buf) -{ - const char *ua = git_libgit2__user_agent(); - - if (!ua) - ua = "libgit2 " LIBGIT2_VERSION; - - return git_str_printf(buf, "git/2.0 (%s)", ua); -} - -#endif diff --git a/src/transports/httpclient.c b/src/transports/httpclient.c deleted file mode 100644 index 75782da82..000000000 --- a/src/transports/httpclient.c +++ /dev/null @@ -1,1579 +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 "common.h" -#include "git2.h" -#include "http_parser.h" -#include "vector.h" -#include "trace.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_str parse_header_name; - git_str 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_str request_msg; - git_str 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) -{ - if (!response) - return; - - 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_str *name = &ctx->parse_header_name; - git_str *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_str_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_str_clear(&ctx->parse_header_name); - git_str_clear(&ctx->parse_header_value); - /* Fall through */ - - case PARSE_HEADER_NONE: - case PARSE_HEADER_NAME: - ctx->parse_header_state = PARSE_HEADER_NAME; - - if (git_str_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_str_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; - } - - GIT_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); -} - -static 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_str *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_str token = GIT_STR_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 = GIT_EAUTH; - goto done; - } - - if (token.size > 0) - error = git_str_printf(buf, "%s: %s\r\n", header_name, token.ptr); - -done: - git_str_dispose(&token); - return error; -} - -GIT_INLINE(int) apply_server_credentials( - git_str *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_str *buf, - git_http_client *client, - git_http_request *request) -{ - return apply_credentials(buf, - &client->proxy, - "Proxy-Authorization", - request->proxy_credentials); -} - -static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) -{ - bool ipv6 = git_net_url_is_ipv6(url); - - if (ipv6) - git_str_putc(buf, '['); - - git_str_puts(buf, url->host); - - if (ipv6) - git_str_putc(buf, ']'); - - if (force_port || !git_net_url_is_default_port(url)) { - git_str_putc(buf, ':'); - git_str_puts(buf, url->port); - } - - return git_str_oom(buf) ? -1 : 0; -} - -static int generate_connect_request( - git_http_client *client, - git_http_request *request) -{ - git_str *buf; - int error; - - git_str_clear(&client->request_msg); - buf = &client->request_msg; - - git_str_puts(buf, "CONNECT "); - puts_host_and_port(buf, &client->server.url, true); - git_str_puts(buf, " HTTP/1.1\r\n"); - - git_str_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_str_puts(buf, "\r\n"); - - git_str_puts(buf, "Host: "); - puts_host_and_port(buf, &client->server.url, true); - git_str_puts(buf, "\r\n"); - - if ((error = apply_proxy_credentials(buf, client, request) < 0)) - return -1; - - git_str_puts(buf, "\r\n"); - - return git_str_oom(buf) ? -1 : 0; -} - -static bool use_connect_proxy(git_http_client *client) -{ - return client->proxy.url.host && !strcmp(client->server.url.scheme, "https"); -} - -static int generate_request( - git_http_client *client, - git_http_request *request) -{ - git_str *buf; - size_t i; - int error; - - GIT_ASSERT_ARG(client); - GIT_ASSERT_ARG(request); - - git_str_clear(&client->request_msg); - buf = &client->request_msg; - - /* GET|POST path HTTP/1.1 */ - git_str_puts(buf, name_for_method(request->method)); - git_str_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_str_puts(buf, " HTTP/1.1\r\n"); - - git_str_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_str_puts(buf, "\r\n"); - - git_str_puts(buf, "Host: "); - puts_host_and_port(buf, request->url, false); - git_str_puts(buf, "\r\n"); - - if (request->accept) - git_str_printf(buf, "Accept: %s\r\n", request->accept); - else - git_str_puts(buf, "Accept: */*\r\n"); - - if (request->content_type) - git_str_printf(buf, "Content-Type: %s\r\n", - request->content_type); - - if (request->chunked) - git_str_puts(buf, "Transfer-Encoding: chunked\r\n"); - - if (request->content_length > 0) - git_str_printf(buf, "Content-Length: %"PRIuZ "\r\n", - request->content_length); - - if (request->expect_continue) - git_str_printf(buf, "Expect: 100-continue\r\n"); - - if ((error = apply_server_credentials(buf, client, request)) < 0 || - (!use_connect_proxy(client) && - (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_str_printf(buf, "%s\r\n", hdr); - } - } - - git_str_puts(buf, "\r\n"); - - if (git_str_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; - - GIT_ASSERT_ARG(client); - GIT_ASSERT_ARG(request); - - GIT_ASSERT(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 port %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; - - GIT_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 = use_connect_proxy(client); - - 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 port %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_str 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 (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 - */ - GIT_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_str_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_str_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; - - GIT_ASSERT_ARG(client); - GIT_ASSERT_ARG(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_str url = GIT_STR_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_str_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_str hdr = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(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) { - GIT_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_str_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_str_dispose(&hdr); - return error; -} - -static int complete_request(git_http_client *client) -{ - int error = 0; - - GIT_ASSERT_ARG(client); - GIT_ASSERT(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; - - GIT_ASSERT_ARG(response); - GIT_ASSERT_ARG(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); - - if (client->current_server == PROXY) { - git_vector_free_deep(&client->proxy.auth_challenges); - } else if(client->current_server == SERVER) { - git_vector_free_deep(&client->server.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; - } - - GIT_ASSERT(client->state == READING_BODY || client->state == DONE); - -done: - git_str_dispose(&parser_context.parse_header_name); - git_str_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; - } - - GIT_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 >= 0 && client->state != DONE); - - 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; - - GIT_ASSERT_ARG(out); - - client = git__calloc(1, sizeof(git_http_client)); - GIT_ERROR_CHECK_ALLOC(client); - - git_str_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_str_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_str_dispose(&client->read_buf); - git__free(client); -} diff --git a/src/transports/httpclient.h b/src/transports/httpclient.h deleted file mode 100644 index 6d0ef9edb..000000000 --- a/src/transports/httpclient.h +++ /dev/null @@ -1,190 +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_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 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 deleted file mode 100644 index 6c754a034..000000000 --- a/src/transports/local.c +++ /dev/null @@ -1,754 +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 "common.h" - -#include "pack-objects.h" -#include "refs.h" -#include "posix.h" -#include "fs_path.h" -#include "repository.h" -#include "odb.h" -#include "push.h" -#include "remote.h" -#include "proxy.h" - -#include "git2/types.h" -#include "git2/net.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/tag.h" -#include "git2/transport.h" -#include "git2/revwalk.h" -#include "git2/odb_backend.h" -#include "git2/pack.h" -#include "git2/commit.h" -#include "git2/revparse.h" -#include "git2/sys/remote.h" - -typedef struct { - git_transport parent; - git_remote *owner; - char *url; - int direction; - git_atomic32 cancelled; - git_repository *repo; - git_remote_connect_options connect_opts; - git_vector refs; - unsigned connected : 1, - have_refs : 1; -} transport_local; - -static void free_head(git_remote_head *head) -{ - git__free(head->name); - git__free(head->symref_target); - git__free(head); -} - -static void free_heads(git_vector *heads) -{ - git_remote_head *head; - size_t i; - - git_vector_foreach(heads, i, head) - free_head(head); - - git_vector_free(heads); -} - -static int add_ref(transport_local *t, const char *name) -{ - const char peeled[] = "^{}"; - git_reference *ref, *resolved; - git_remote_head *head; - git_oid obj_id; - git_object *obj = NULL, *target = NULL; - git_str buf = GIT_STR_INIT; - int error; - - if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) - return error; - - error = git_reference_resolve(&resolved, ref); - if (error < 0) { - git_reference_free(ref); - if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { - /* This is actually okay. Empty repos often have a HEAD that - * points to a nonexistent "refs/heads/master". */ - git_error_clear(); - return 0; - } - return error; - } - - git_oid_cpy(&obj_id, git_reference_target(resolved)); - git_reference_free(resolved); - - head = git__calloc(1, sizeof(git_remote_head)); - GIT_ERROR_CHECK_ALLOC(head); - - head->name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(head->name); - - git_oid_cpy(&head->oid, &obj_id); - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - head->symref_target = git__strdup(git_reference_symbolic_target(ref)); - GIT_ERROR_CHECK_ALLOC(head->symref_target); - } - git_reference_free(ref); - - if ((error = git_vector_insert(&t->refs, head)) < 0) { - free_head(head); - return error; - } - - /* If it's not a tag, we don't need to try to peel it */ - if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) - return 0; - - if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0) - return error; - - head = NULL; - - /* If it's not an annotated tag, or if we're mocking - * git-receive-pack, just get out */ - if (git_object_type(obj) != GIT_OBJECT_TAG || - t->direction != GIT_DIRECTION_FETCH) { - git_object_free(obj); - return 0; - } - - /* And if it's a tag, peel it, and add it to the list */ - head = git__calloc(1, sizeof(git_remote_head)); - GIT_ERROR_CHECK_ALLOC(head); - - if (git_str_join(&buf, 0, name, peeled) < 0) { - free_head(head); - return -1; - } - head->name = git_str_detach(&buf); - - if (!(error = git_tag_peel(&target, (git_tag *)obj))) { - git_oid_cpy(&head->oid, git_object_id(target)); - - if ((error = git_vector_insert(&t->refs, head)) < 0) { - free_head(head); - } - } - - git_object_free(obj); - git_object_free(target); - - return error; -} - -static int store_refs(transport_local *t) -{ - size_t i; - git_remote_head *head; - git_strarray ref_names = {0}; - - GIT_ASSERT_ARG(t); - - if (git_reference_list(&ref_names, t->repo) < 0) - goto on_error; - - /* Clear all heads we might have fetched in a previous connect */ - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } - - /* Clear the vector so we can reuse it */ - git_vector_clear(&t->refs); - - /* Sort the references first */ - git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); - - /* Add HEAD iff direction is fetch */ - if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) - goto on_error; - - for (i = 0; i < ref_names.count; ++i) { - if (add_ref(t, ref_names.strings[i]) < 0) - goto on_error; - } - - t->have_refs = 1; - git_strarray_dispose(&ref_names); - return 0; - -on_error: - git_vector_free(&t->refs); - git_strarray_dispose(&ref_names); - return -1; -} - -/* - * Try to open the url as a git directory. The direction doesn't - * matter in this case because we're calculating the heads ourselves. - */ -static int local_connect( - git_transport *transport, - const char *url, - int direction, - const git_remote_connect_options *connect_opts) -{ - git_repository *repo; - int error; - transport_local *t = (transport_local *)transport; - const char *path; - git_str buf = GIT_STR_INIT; - - if (t->connected) - return 0; - - if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) - return -1; - - free_heads(&t->refs); - - t->url = git__strdup(url); - GIT_ERROR_CHECK_ALLOC(t->url); - t->direction = direction; - - /* 'url' may be a url or path; convert to a path */ - if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) { - git_str_dispose(&buf); - return error; - } - path = git_str_cstr(&buf); - - error = git_repository_open(&repo, path); - - git_str_dispose(&buf); - - if (error < 0) - return -1; - - t->repo = repo; - - if (store_refs(t) < 0) - return -1; - - t->connected = 1; - - return 0; -} - -static int local_set_connect_opts( - git_transport *transport, - const git_remote_connect_options *connect_opts) -{ - transport_local *t = (transport_local *)transport; - - if (!t->connected) { - git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); - return -1; - } - - return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts); -} - -static int local_capabilities(unsigned int *capabilities, git_transport *transport) -{ - GIT_UNUSED(transport); - - *capabilities = GIT_REMOTE_CAPABILITY_TIP_OID | - GIT_REMOTE_CAPABILITY_REACHABLE_OID; - return 0; -} - -static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - if (!t->have_refs) { - git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); - return -1; - } - - *out = (const git_remote_head **)t->refs.contents; - *size = t->refs.length; - - return 0; -} - -static int local_negotiate_fetch( - git_transport *transport, - git_repository *repo, - const git_remote_head * const *refs, - size_t count) -{ - transport_local *t = (transport_local*)transport; - git_remote_head *rhead; - unsigned int i; - - GIT_UNUSED(refs); - GIT_UNUSED(count); - - /* Fill in the loids */ - git_vector_foreach(&t->refs, i, rhead) { - git_object *obj; - - int error = git_revparse_single(&obj, repo, rhead->name); - if (!error) - git_oid_cpy(&rhead->loid, git_object_id(obj)); - else if (error != GIT_ENOTFOUND) - return error; - else - git_error_clear(); - git_object_free(obj); - } - - return 0; -} - -static int local_push_update_remote_ref( - git_repository *remote_repo, - const char *lref, - const char *rref, - git_oid *loid, - git_oid *roid) -{ - int error; - git_reference *remote_ref = NULL; - - /* check for lhs, if it's empty it means to delete */ - if (lref[0] != '\0') { - /* Create or update a ref */ - error = git_reference_create(NULL, remote_repo, rref, loid, - !git_oid_is_zero(roid), NULL); - } else { - /* Delete a ref */ - if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - return error; - } - - error = git_reference_delete(remote_ref); - git_reference_free(remote_ref); - } - - return error; -} - -static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload) -{ - const git_remote_callbacks *cbs = payload; - - if (!cbs || !cbs->push_transfer_progress) - return 0; - - return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes, - cbs->payload); -} - -static int local_push( - git_transport *transport, - git_push *push) -{ - transport_local *t = (transport_local *)transport; - git_remote_callbacks *cbs = &t->connect_opts.callbacks; - git_repository *remote_repo = NULL; - push_spec *spec; - char *url = NULL; - const char *path; - git_str buf = GIT_STR_INIT, odb_path = GIT_STR_INIT; - int error; - size_t j; - - /* 'push->remote->url' may be a url or path; convert to a path */ - if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) { - git_str_dispose(&buf); - return error; - } - path = git_str_cstr(&buf); - - error = git_repository_open(&remote_repo, path); - - git_str_dispose(&buf); - - if (error < 0) - return error; - - /* We don't currently support pushing locally to non-bare repos. Proper - non-bare repo push support would require checking configs to see if - we should override the default 'don't let this happen' behavior. - - Note that this is only an issue when pushing to the current branch, - but we forbid all pushes just in case */ - if (!remote_repo->is_bare) { - error = GIT_EBAREREPO; - git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos."); - goto on_error; - } - - if ((error = git_repository__item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 - || (error = git_str_joinpath(&odb_path, odb_path.ptr, "pack")) < 0) - goto on_error; - - error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); - git_str_dispose(&odb_path); - - if (error < 0) - goto on_error; - - push->unpack_ok = 1; - - git_vector_foreach(&push->specs, j, spec) { - push_status *status; - const git_error *last; - char *ref = spec->refspec.dst; - - status = git__calloc(1, sizeof(push_status)); - if (!status) - goto on_error; - - status->ref = git__strdup(ref); - if (!status->ref) { - git_push_status_free(status); - goto on_error; - } - - error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst, - &spec->loid, &spec->roid); - - switch (error) { - case GIT_OK: - break; - case GIT_EINVALIDSPEC: - status->msg = git__strdup("funny refname"); - break; - case GIT_ENOTFOUND: - status->msg = git__strdup("Remote branch not found to delete"); - break; - default: - last = git_error_last(); - - if (last && last->message) - status->msg = git__strdup(last->message); - else - status->msg = git__strdup("Unspecified error encountered"); - break; - } - - /* failed to allocate memory for a status message */ - if (error < 0 && !status->msg) { - git_push_status_free(status); - goto on_error; - } - - /* failed to insert the ref update status */ - if ((error = git_vector_insert(&push->status, status)) < 0) { - git_push_status_free(status); - goto on_error; - } - } - - if (push->specs.length) { - url = git__strdup(t->url); - - if (!url || t->parent.close(&t->parent) < 0 || - t->parent.connect(&t->parent, url, - GIT_DIRECTION_PUSH, NULL)) - goto on_error; - } - - error = 0; - -on_error: - git_repository_free(remote_repo); - git__free(url); - - return error; -} - -typedef struct foreach_data { - git_indexer_progress *stats; - git_indexer_progress_cb progress_cb; - void *progress_payload; - git_odb_writepack *writepack; -} foreach_data; - -static int foreach_cb(void *buf, size_t len, void *payload) -{ - foreach_data *data = (foreach_data*)payload; - - data->stats->received_bytes += len; - return data->writepack->append(data->writepack, buf, len, data->stats); -} - -static const char *counting_objects_fmt = "Counting objects %d\r"; -static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)"; - -static int local_counting(int stage, unsigned int current, unsigned int total, void *payload) -{ - git_str progress_info = GIT_STR_INIT; - transport_local *t = payload; - int error; - - if (!t->connect_opts.callbacks.sideband_progress) - return 0; - - if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) { - git_str_printf(&progress_info, counting_objects_fmt, current); - } else if (stage == GIT_PACKBUILDER_DELTAFICATION) { - float perc = (((float) current) / total) * 100; - git_str_printf(&progress_info, compressing_objects_fmt, perc, current, total); - if (current == total) - git_str_printf(&progress_info, ", done\n"); - else - git_str_putc(&progress_info, '\r'); - - } - - if (git_str_oom(&progress_info)) - return -1; - - if (progress_info.size > INT_MAX) { - git_error_set(GIT_ERROR_NET, "remote sent overly large progress data"); - git_str_dispose(&progress_info); - return -1; - } - - - error = t->connect_opts.callbacks.sideband_progress( - progress_info.ptr, - (int)progress_info.size, - t->connect_opts.callbacks.payload); - - git_str_dispose(&progress_info); - return error; -} - -static int foreach_reference_cb(git_reference *reference, void *payload) -{ - git_revwalk *walk = (git_revwalk *)payload; - int error; - - if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) { - git_reference_free(reference); - return 0; - } - - error = git_revwalk_hide(walk, git_reference_target(reference)); - /* The reference is in the local repository, so the target may not - * exist on the remote. It also may not be a commit. */ - if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) { - git_error_clear(); - error = 0; - } - - git_reference_free(reference); - - return error; -} - -static int local_download_pack( - git_transport *transport, - git_repository *repo, - git_indexer_progress *stats) -{ - transport_local *t = (transport_local*)transport; - git_revwalk *walk = NULL; - git_remote_head *rhead; - unsigned int i; - int error = -1; - git_packbuilder *pack = NULL; - git_odb_writepack *writepack = NULL; - git_odb *odb = NULL; - git_str progress_info = GIT_STR_INIT; - foreach_data data = {0}; - - if ((error = git_revwalk_new(&walk, t->repo)) < 0) - goto cleanup; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - if ((error = git_packbuilder_new(&pack, t->repo)) < 0) - goto cleanup; - - git_packbuilder_set_callbacks(pack, local_counting, t); - - stats->total_objects = 0; - stats->indexed_objects = 0; - stats->received_objects = 0; - stats->received_bytes = 0; - - git_vector_foreach(&t->refs, i, rhead) { - git_object *obj; - if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0) - goto cleanup; - - if (git_object_type(obj) == GIT_OBJECT_COMMIT) { - /* Revwalker includes only wanted commits */ - error = git_revwalk_push(walk, &rhead->oid); - } else { - /* Tag or some other wanted object. Add it on its own */ - error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name); - } - git_object_free(obj); - if (error < 0) - goto cleanup; - } - - if ((error = git_reference_foreach(repo, foreach_reference_cb, walk))) - goto cleanup; - - if ((error = git_packbuilder_insert_walk(pack, walk))) - goto cleanup; - - if (t->connect_opts.callbacks.sideband_progress) { - if ((error = git_str_printf( - &progress_info, - counting_objects_fmt, - git_packbuilder_object_count(pack))) < 0 || - (error = t->connect_opts.callbacks.sideband_progress( - progress_info.ptr, - (int)progress_info.size, - t->connect_opts.callbacks.payload)) < 0) - goto cleanup; - } - - /* Walk the objects, building a packfile */ - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - goto cleanup; - - /* One last one with the newline */ - if (t->connect_opts.callbacks.sideband_progress) { - git_str_clear(&progress_info); - - if ((error = git_str_printf( - &progress_info, - counting_objects_fmt, - git_packbuilder_object_count(pack))) < 0 || - (error = git_str_putc(&progress_info, '\n')) < 0 || - (error = t->connect_opts.callbacks.sideband_progress( - progress_info.ptr, - (int)progress_info.size, - t->connect_opts.callbacks.payload)) < 0) - goto cleanup; - } - - if ((error = git_odb_write_pack( - &writepack, - odb, - t->connect_opts.callbacks.transfer_progress, - t->connect_opts.callbacks.payload)) < 0) - goto cleanup; - - /* Write the data to the ODB */ - data.stats = stats; - data.progress_cb = t->connect_opts.callbacks.transfer_progress; - data.progress_payload = t->connect_opts.callbacks.payload; - data.writepack = writepack; - - /* autodetect */ - git_packbuilder_set_threads(pack, 0); - - if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) - goto cleanup; - - error = writepack->commit(writepack, stats); - -cleanup: - if (writepack) writepack->free(writepack); - git_str_dispose(&progress_info); - git_packbuilder_free(pack); - git_revwalk_free(walk); - return error; -} - -static int local_is_connected(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - return t->connected; -} - -static void local_cancel(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - git_atomic32_set(&t->cancelled, 1); -} - -static int local_close(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - t->connected = 0; - - if (t->repo) { - git_repository_free(t->repo); - t->repo = NULL; - } - - if (t->url) { - git__free(t->url); - t->url = NULL; - } - - return 0; -} - -static void local_free(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - free_heads(&t->refs); - - /* Close the transport, if it's still open. */ - local_close(transport); - - /* Free the transport */ - git__free(t); -} - -/************** - * Public API * - **************/ - -int git_transport_local(git_transport **out, git_remote *owner, void *param) -{ - int error; - transport_local *t; - - GIT_UNUSED(param); - - t = git__calloc(1, sizeof(transport_local)); - GIT_ERROR_CHECK_ALLOC(t); - - t->parent.version = GIT_TRANSPORT_VERSION; - t->parent.connect = local_connect; - t->parent.set_connect_opts = local_set_connect_opts; - t->parent.capabilities = local_capabilities; - t->parent.negotiate_fetch = local_negotiate_fetch; - t->parent.download_pack = local_download_pack; - t->parent.push = local_push; - t->parent.close = local_close; - t->parent.free = local_free; - t->parent.ls = local_ls; - t->parent.is_connected = local_is_connected; - t->parent.cancel = local_cancel; - - if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) { - git__free(t); - return error; - } - - t->owner = owner; - - *out = (git_transport *) t; - - return 0; -} diff --git a/src/transports/smart.c b/src/transports/smart.c deleted file mode 100644 index 801fcbe53..000000000 --- a/src/transports/smart.c +++ /dev/null @@ -1,472 +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 "smart.h" - -#include "git2.h" -#include "git2/sys/remote.h" -#include "refs.h" -#include "refspec.h" -#include "proxy.h" - -static int git_smart__recv_cb(gitno_buffer *buf) -{ - transport_smart *t = (transport_smart *) buf->cb_data; - size_t old_len, bytes_read; - int error; - - GIT_ASSERT(t->current_stream); - - old_len = buf->offset; - - if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0) - return error; - - buf->offset += bytes_read; - - if (t->packetsize_cb && !t->cancelled.val) { - error = t->packetsize_cb(bytes_read, t->packetsize_payload); - if (error) { - git_atomic32_set(&t->cancelled, 1); - return GIT_EUSER; - } - } - - return (int)(buf->offset - old_len); -} - -GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport) -{ - if (t->current_stream) { - t->current_stream->free(t->current_stream); - t->current_stream = NULL; - } - - if (close_subtransport) { - git__free(t->url); - t->url = NULL; - - if (t->wrapped->close(t->wrapped) < 0) - return -1; - } - - return 0; -} - -int git_smart__update_heads(transport_smart *t, git_vector *symrefs) -{ - size_t i; - git_pkt *pkt; - - git_vector_clear(&t->heads); - git_vector_foreach(&t->refs, i, pkt) { - git_pkt_ref *ref = (git_pkt_ref *) pkt; - if (pkt->type != GIT_PKT_REF) - continue; - - if (symrefs) { - git_refspec *spec; - git_str buf = GIT_STR_INIT; - size_t j; - int error = 0; - - git_vector_foreach(symrefs, j, spec) { - git_str_clear(&buf); - if (git_refspec_src_matches(spec, ref->head.name) && - !(error = git_refspec__transform(&buf, spec, ref->head.name))) { - git__free(ref->head.symref_target); - ref->head.symref_target = git_str_detach(&buf); - } - } - - git_str_dispose(&buf); - - if (error < 0) - return error; - } - - if (git_vector_insert(&t->heads, &ref->head) < 0) - return -1; - } - - return 0; -} - -static void free_symrefs(git_vector *symrefs) -{ - git_refspec *spec; - size_t i; - - git_vector_foreach(symrefs, i, spec) { - git_refspec__dispose(spec); - git__free(spec); - } - - git_vector_free(symrefs); -} - -static int git_smart__connect( - git_transport *transport, - const char *url, - int direction, - const git_remote_connect_options *connect_opts) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_smart_subtransport_stream *stream; - int error; - git_pkt *pkt; - git_pkt_ref *first; - git_vector symrefs; - git_smart_service_t service; - - if (git_smart__reset_stream(t, true) < 0) - return -1; - - if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) - return -1; - - t->url = git__strdup(url); - GIT_ERROR_CHECK_ALLOC(t->url); - - t->direction = direction; - - if (GIT_DIRECTION_FETCH == t->direction) { - service = GIT_SERVICE_UPLOADPACK_LS; - } else if (GIT_DIRECTION_PUSH == t->direction) { - service = GIT_SERVICE_RECEIVEPACK_LS; - } else { - git_error_set(GIT_ERROR_NET, "invalid direction"); - return -1; - } - - if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0) - return error; - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = stream; - - gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - /* 2 flushes for RPC; 1 for stateful */ - if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) - return error; - - /* Strip the comment packet for RPC */ - if (t->rpc) { - pkt = (git_pkt *)git_vector_get(&t->refs, 0); - - if (!pkt || GIT_PKT_COMMENT != pkt->type) { - git_error_set(GIT_ERROR_NET, "invalid response"); - return -1; - } else { - /* Remove the comment pkt from the list */ - git_vector_remove(&t->refs, 0); - git__free(pkt); - } - } - - /* We now have loaded the refs. */ - t->have_refs = 1; - - pkt = (git_pkt *)git_vector_get(&t->refs, 0); - if (pkt && GIT_PKT_REF != pkt->type) { - git_error_set(GIT_ERROR_NET, "invalid response"); - return -1; - } - first = (git_pkt_ref *)pkt; - - if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) - return error; - - /* Detect capabilities */ - 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_is_zero(&first->head.oid)) { - git_vector_clear(&t->refs); - git_pkt_free((git_pkt *)first); - } - - /* Keep a list of heads for _ls */ - git_smart__update_heads(t, &symrefs); - } else if (error == GIT_ENOTFOUND) { - /* There was no ref packet received, or the cap list was empty */ - error = 0; - } else { - git_error_set(GIT_ERROR_NET, "invalid response"); - goto cleanup; - } - - if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0) - goto cleanup; - - /* We're now logically connected. */ - t->connected = 1; - -cleanup: - free_symrefs(&symrefs); - - return error; -} - -static int git_smart__set_connect_opts( - git_transport *transport, - const git_remote_connect_options *opts) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - if (!t->connected) { - git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); - return -1; - } - - return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts); -} - -static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - *capabilities = 0; - - if (t->caps.want_tip_sha1) - *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID; - - if (t->caps.want_reachable_sha1) - *capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID; - - return 0; -} - -static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *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"); - return -1; - } - - *out = (const git_remote_head **) t->heads.contents; - *size = t->heads.length; - - return 0; -} - -int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_smart_subtransport_stream *stream; - int error; - - if (t->rpc && git_smart__reset_stream(t, false) < 0) - return -1; - - if (GIT_DIRECTION_FETCH != t->direction) { - git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch"); - return -1; - } - - if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) - return error; - - /* If this is a stateful implementation, the stream we get back should be the same */ - GIT_ASSERT(t->rpc || t->current_stream == stream); - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = stream; - - if ((error = stream->write(stream, (const char *)data, len)) < 0) - return error; - - gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - return 0; -} - -int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream) -{ - int error; - - if (t->rpc && git_smart__reset_stream(t, false) < 0) - return -1; - - if (GIT_DIRECTION_PUSH != t->direction) { - git_error_set(GIT_ERROR_NET, "this operation is only valid for push"); - return -1; - } - - if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0) - return error; - - /* If this is a stateful implementation, the stream we get back should be the same */ - GIT_ASSERT(t->rpc || t->current_stream == *stream); - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = *stream; - - gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - return 0; -} - -static void git_smart__cancel(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - git_atomic32_set(&t->cancelled, 1); -} - -static int git_smart__is_connected(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - return t->connected; -} - -static int git_smart__close(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_vector *common = &t->common; - unsigned int i; - git_pkt *p; - int ret; - git_smart_subtransport_stream *stream; - const char flush[] = "0000"; - - /* - * If we're still connected at this point and not using RPC, - * we should say goodbye by sending a flush, or git-daemon - * will complain that we disconnected unexpectedly. - */ - if (t->connected && !t->rpc && - !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { - t->current_stream->write(t->current_stream, flush, 4); - } - - ret = git_smart__reset_stream(t, true); - - git_vector_foreach(common, i, p) - git_pkt_free(p); - - git_vector_free(common); - - if (t->url) { - git__free(t->url); - t->url = NULL; - } - - t->connected = 0; - - return ret; -} - -static void git_smart__free(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_vector *refs = &t->refs; - unsigned int i; - git_pkt *p; - - /* Make sure that the current stream is closed, if we have one. */ - git_smart__close(transport); - - /* Free the subtransport */ - t->wrapped->free(t->wrapped); - - git_vector_free(&t->heads); - git_vector_foreach(refs, i, p) - git_pkt_free(p); - - git_vector_free(refs); - - git_remote_connect_options_dispose(&t->connect_opts); - - git__free(t); -} - -static int ref_name_cmp(const void *a, const void *b) -{ - const git_pkt_ref *ref_a = a, *ref_b = b; - - return strcmp(ref_a->head.name, ref_b->head.name); -} - -int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_remote_connect_options *connect_opts = &t->connect_opts; - - GIT_ASSERT_ARG(transport); - GIT_ASSERT_ARG(cert); - GIT_ASSERT_ARG(hostname); - - if (!connect_opts->callbacks.certificate_check) - return GIT_PASSTHROUGH; - - return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload); -} - -int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_remote_connect_options *connect_opts = &t->connect_opts; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(transport); - - if (!connect_opts->callbacks.credentials) - return GIT_PASSTHROUGH; - - return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload); -} - -int git_transport_smart(git_transport **out, git_remote *owner, void *param) -{ - transport_smart *t; - git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; - - if (!param) - return -1; - - t = git__calloc(1, sizeof(transport_smart)); - GIT_ERROR_CHECK_ALLOC(t); - - t->parent.version = GIT_TRANSPORT_VERSION; - t->parent.connect = git_smart__connect; - t->parent.set_connect_opts = git_smart__set_connect_opts; - t->parent.capabilities = git_smart__capabilities; - t->parent.close = git_smart__close; - t->parent.free = git_smart__free; - t->parent.negotiate_fetch = git_smart__negotiate_fetch; - t->parent.download_pack = git_smart__download_pack; - t->parent.push = git_smart__push; - t->parent.ls = git_smart__ls; - t->parent.is_connected = git_smart__is_connected; - t->parent.cancel = git_smart__cancel; - - t->owner = owner; - t->rpc = definition->rpc; - - if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) { - git__free(t); - return -1; - } - - if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) { - git__free(t); - return -1; - } - - if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) { - git__free(t); - return -1; - } - - *out = (git_transport *) t; - return 0; -} diff --git a/src/transports/smart.h b/src/transports/smart.h deleted file mode 100644 index 9323d6c44..000000000 --- a/src/transports/smart.h +++ /dev/null @@ -1,193 +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_transports_smart_h__ -#define INCLUDE_transports_smart_h__ - -#include "common.h" - -#include "git2.h" -#include "vector.h" -#include "netops.h" -#include "push.h" -#include "str.h" -#include "git2/sys/transport.h" - -#define GIT_SIDE_BAND_DATA 1 -#define GIT_SIDE_BAND_PROGRESS 2 -#define GIT_SIDE_BAND_ERROR 3 - -#define GIT_CAP_OFS_DELTA "ofs-delta" -#define GIT_CAP_MULTI_ACK "multi_ack" -#define GIT_CAP_MULTI_ACK_DETAILED "multi_ack_detailed" -#define GIT_CAP_SIDE_BAND "side-band" -#define GIT_CAP_SIDE_BAND_64K "side-band-64k" -#define GIT_CAP_INCLUDE_TAG "include-tag" -#define GIT_CAP_DELETE_REFS "delete-refs" -#define GIT_CAP_REPORT_STATUS "report-status" -#define GIT_CAP_THIN_PACK "thin-pack" -#define GIT_CAP_SYMREF "symref" -#define GIT_CAP_WANT_TIP_SHA1 "allow-tip-sha1-in-want" -#define GIT_CAP_WANT_REACHABLE_SHA1 "allow-reachable-sha1-in-want" - -extern bool git_smart__ofs_delta_enabled; - -typedef enum { - GIT_PKT_CMD, - GIT_PKT_FLUSH, - GIT_PKT_REF, - GIT_PKT_HAVE, - GIT_PKT_ACK, - GIT_PKT_NAK, - GIT_PKT_COMMENT, - GIT_PKT_ERR, - GIT_PKT_DATA, - GIT_PKT_PROGRESS, - GIT_PKT_OK, - GIT_PKT_NG, - GIT_PKT_UNPACK -} git_pkt_type; - -/* Used for multi_ack and multi_ack_detailed */ -enum git_ack_status { - GIT_ACK_NONE, - GIT_ACK_CONTINUE, - GIT_ACK_COMMON, - GIT_ACK_READY -}; - -/* This would be a flush pkt */ -typedef struct { - git_pkt_type type; -} git_pkt; - -struct git_pkt_cmd { - git_pkt_type type; - char *cmd; - char *path; - char *host; -}; - -/* This is a pkt-line with some info in it */ -typedef struct { - git_pkt_type type; - git_remote_head head; - char *capabilities; -} git_pkt_ref; - -/* Useful later */ -typedef struct { - git_pkt_type type; - git_oid oid; - enum git_ack_status status; -} git_pkt_ack; - -typedef struct { - git_pkt_type type; - char comment[GIT_FLEX_ARRAY]; -} git_pkt_comment; - -typedef struct { - git_pkt_type type; - size_t len; - char data[GIT_FLEX_ARRAY]; -} git_pkt_data; - -typedef git_pkt_data git_pkt_progress; - -typedef struct { - git_pkt_type type; - size_t len; - char error[GIT_FLEX_ARRAY]; -} git_pkt_err; - -typedef struct { - git_pkt_type type; - char *ref; -} git_pkt_ok; - -typedef struct { - git_pkt_type type; - char *ref; - char *msg; -} git_pkt_ng; - -typedef struct { - git_pkt_type type; - int unpack_ok; -} git_pkt_unpack; - -typedef struct transport_smart_caps { - unsigned int common:1, - ofs_delta:1, - multi_ack:1, - multi_ack_detailed:1, - side_band:1, - side_band_64k:1, - include_tag:1, - delete_refs:1, - report_status:1, - thin_pack:1, - want_tip_sha1:1, - want_reachable_sha1:1; -} transport_smart_caps; - -typedef int (*packetsize_cb)(size_t received, void *payload); - -typedef struct { - git_transport parent; - git_remote *owner; - char *url; - git_remote_connect_options connect_opts; - int direction; - git_smart_subtransport *wrapped; - git_smart_subtransport_stream *current_stream; - transport_smart_caps caps; - git_vector refs; - git_vector heads; - git_vector common; - git_atomic32 cancelled; - packetsize_cb packetsize_cb; - void *packetsize_payload; - unsigned rpc : 1, - have_refs : 1, - connected : 1; - gitno_buffer buffer; - char buffer_data[65536]; -} transport_smart; - -/* smart_protocol.c */ -int git_smart__store_refs(transport_smart *t, int flushes); -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); -int git_smart__push(git_transport *transport, git_push *push); - -int git_smart__negotiate_fetch( - git_transport *transport, - git_repository *repo, - const git_remote_head * const *refs, - size_t count); - -int git_smart__download_pack( - git_transport *transport, - git_repository *repo, - git_indexer_progress *stats); - -/* smart.c */ -int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); -int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); - -int git_smart__update_heads(transport_smart *t, git_vector *symrefs); - -/* smart_pkt.c */ -int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen); -int git_pkt_buffer_flush(git_str *buf); -int git_pkt_send_flush(GIT_SOCKET s); -int git_pkt_buffer_done(git_str *buf); -int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_str *buf); -int git_pkt_buffer_have(git_oid *oid, git_str *buf); -void git_pkt_free(git_pkt *pkt); - -#endif diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c deleted file mode 100644 index b42edd0d6..000000000 --- a/src/transports/smart_pkt.c +++ /dev/null @@ -1,637 +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 "common.h" - -#include "smart.h" -#include "util.h" -#include "netops.h" -#include "posix.h" -#include "str.h" - -#include "git2/types.h" -#include "git2/errors.h" -#include "git2/refs.h" -#include "git2/revwalk.h" - -#include - -#define PKT_LEN_SIZE 4 -static const char pkt_done_str[] = "0009done\n"; -static const char pkt_flush_str[] = "0000"; -static const char pkt_have_prefix[] = "0032have "; -static const char pkt_want_prefix[] = "0032want "; - -static int flush_pkt(git_pkt **out) -{ - git_pkt *pkt; - - pkt = git__malloc(sizeof(git_pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_FLUSH; - *out = pkt; - - return 0; -} - -/* the rest of the line will be useful for multi_ack and multi_ack_detailed */ -static int ack_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ack *pkt; - - pkt = git__calloc(1, sizeof(git_pkt_ack)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_ACK; - - if (git__prefixncmp(line, len, "ACK ")) - goto out_err; - line += 4; - len -= 4; - - if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->oid, line) < 0) - goto out_err; - line += GIT_OID_HEXSZ; - len -= GIT_OID_HEXSZ; - - if (len && line[0] == ' ') { - line++; - len--; - - if (!git__prefixncmp(line, len, "continue")) - pkt->status = GIT_ACK_CONTINUE; - else if (!git__prefixncmp(line, len, "common")) - pkt->status = GIT_ACK_COMMON; - else if (!git__prefixncmp(line, len, "ready")) - pkt->status = GIT_ACK_READY; - else - goto out_err; - } - - *out = (git_pkt *) pkt; - - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing ACK pkt-line"); - git__free(pkt); - return -1; -} - -static int nak_pkt(git_pkt **out) -{ - git_pkt *pkt; - - pkt = git__malloc(sizeof(git_pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_NAK; - *out = pkt; - - return 0; -} - -static int comment_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_comment *pkt; - size_t alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_comment), len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_COMMENT; - memcpy(pkt->comment, line, len); - pkt->comment[len] = '\0'; - - *out = (git_pkt *) pkt; - - return 0; -} - -static int err_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_err *pkt = NULL; - size_t alloclen; - - /* Remove "ERR " from the line */ - if (git__prefixncmp(line, len, "ERR ")) - goto out_err; - line += 4; - len -= 4; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_ERR; - pkt->len = len; - - memcpy(pkt->error, line, len); - pkt->error[len] = '\0'; - - *out = (git_pkt *) pkt; - - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing ERR pkt-line"); - git__free(pkt); - return -1; -} - -static int data_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_data *pkt; - size_t alloclen; - - line++; - len--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_DATA; - pkt->len = len; - memcpy(pkt->data, line, len); - - *out = (git_pkt *) pkt; - - return 0; -} - -static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_progress *pkt; - size_t alloclen; - - line++; - len--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_PROGRESS; - pkt->len = len; - memcpy(pkt->data, line, len); - - *out = (git_pkt *) pkt; - - return 0; -} - -static int sideband_error_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_err *pkt; - size_t alloc_len; - - line++; - len--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(git_pkt_err), len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - pkt = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_ERR; - pkt->len = (int)len; - memcpy(pkt->error, line, len); - pkt->error[len] = '\0'; - - *out = (git_pkt *)pkt; - - return 0; -} - -/* - * Parse an other-ref line. - */ -static int ref_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ref *pkt; - size_t alloclen; - - pkt = git__calloc(1, sizeof(git_pkt_ref)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_REF; - - if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->head.oid, line) < 0) - goto out_err; - line += GIT_OID_HEXSZ; - len -= GIT_OID_HEXSZ; - - if (git__prefixncmp(line, len, " ")) - goto out_err; - line++; - len--; - - if (!len) - goto out_err; - - if (line[len - 1] == '\n') - --len; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - pkt->head.name = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt->head.name); - - memcpy(pkt->head.name, line, len); - pkt->head.name[len] = '\0'; - - if (strlen(pkt->head.name) < len) - pkt->capabilities = strchr(pkt->head.name, '\0') + 1; - - *out = (git_pkt *)pkt; - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing REF pkt-line"); - if (pkt) - git__free(pkt->head.name); - git__free(pkt); - return -1; -} - -static int ok_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ok *pkt; - size_t alloc_len; - - pkt = git__malloc(sizeof(*pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_OK; - - if (git__prefixncmp(line, len, "ok ")) - goto out_err; - line += 3; - len -= 3; - - if (len && line[len - 1] == '\n') - --len; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); - pkt->ref = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(pkt->ref); - - memcpy(pkt->ref, line, len); - pkt->ref[len] = '\0'; - - *out = (git_pkt *)pkt; - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing OK pkt-line"); - git__free(pkt); - return -1; -} - -static int ng_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ng *pkt; - const char *ptr, *eol; - size_t alloclen; - - pkt = git__malloc(sizeof(*pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->ref = NULL; - pkt->type = GIT_PKT_NG; - - eol = line + len; - - if (git__prefixncmp(line, len, "ng ")) - goto out_err; - line += 3; - - if (!(ptr = memchr(line, ' ', eol - line))) - goto out_err; - len = ptr - line; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - pkt->ref = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt->ref); - - memcpy(pkt->ref, line, len); - pkt->ref[len] = '\0'; - - line = ptr + 1; - if (line >= eol) - goto out_err; - - if (!(ptr = memchr(line, '\n', eol - line))) - goto out_err; - len = ptr - line; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - pkt->msg = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt->msg); - - memcpy(pkt->msg, line, len); - pkt->msg[len] = '\0'; - - *out = (git_pkt *)pkt; - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "invalid packet line"); - git__free(pkt->ref); - git__free(pkt); - return -1; -} - -static int unpack_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_unpack *pkt; - - pkt = git__malloc(sizeof(*pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_UNPACK; - - if (!git__prefixncmp(line, len, "unpack ok")) - pkt->unpack_ok = 1; - else - pkt->unpack_ok = 0; - - *out = (git_pkt *)pkt; - return 0; -} - -static int parse_len(size_t *out, const char *line, size_t linelen) -{ - char num[PKT_LEN_SIZE + 1]; - int i, k, error; - int32_t len; - const char *num_end; - - /* Not even enough for the length */ - if (linelen < PKT_LEN_SIZE) - return GIT_EBUFS; - - memcpy(num, line, PKT_LEN_SIZE); - num[PKT_LEN_SIZE] = '\0'; - - for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit(num[i])) { - /* Make sure there are no special characters before passing to error message */ - for (k = 0; k < PKT_LEN_SIZE; ++k) { - if(!isprint(num[k])) { - num[k] = '.'; - } - } - - git_error_set(GIT_ERROR_NET, "invalid hex digit in length: '%s'", num); - return -1; - } - } - - if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0) - return error; - - if (len < 0) - return -1; - - *out = (size_t) len; - return 0; -} - -/* - * As per the documentation, the syntax is: - * - * pkt-line = data-pkt / flush-pkt - * data-pkt = pkt-len pkt-payload - * pkt-len = 4*(HEXDIG) - * pkt-payload = (pkt-len -4)*(OCTET) - * flush-pkt = "0000" - * - * Which means that the first four bytes are the length of the line, - * in ASCII hexadecimal (including itself) - */ - -int git_pkt_parse_line( - git_pkt **pkt, const char **endptr, const char *line, size_t linelen) -{ - int error; - size_t len; - - if ((error = parse_len(&len, line, linelen)) < 0) { - /* - * If we fail to parse the length, it might be - * because the server is trying to send us the - * packfile already or because we do not yet have - * enough data. - */ - if (error == GIT_EBUFS) - ; - else if (!git__prefixncmp(line, linelen, "PACK")) - git_error_set(GIT_ERROR_NET, "unexpected pack file"); - else - git_error_set(GIT_ERROR_NET, "bad packet length"); - return error; - } - - /* - * Make sure there is enough in the buffer to satisfy - * this line. - */ - if (linelen < len) - return GIT_EBUFS; - - /* - * The length has to be exactly 0 in case of a flush - * packet or greater than PKT_LEN_SIZE, as the decoded - * length includes its own encoded length of four bytes. - */ - if (len != 0 && len < PKT_LEN_SIZE) - return GIT_ERROR; - - line += PKT_LEN_SIZE; - /* - * The Git protocol does not specify empty lines as part - * of the protocol. Not knowing what to do with an empty - * line, we should return an error upon hitting one. - */ - if (len == PKT_LEN_SIZE) { - git_error_set_str(GIT_ERROR_NET, "Invalid empty packet"); - return GIT_ERROR; - } - - if (len == 0) { /* Flush pkt */ - *endptr = line; - return flush_pkt(pkt); - } - - len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ - - if (*line == GIT_SIDE_BAND_DATA) - error = data_pkt(pkt, line, len); - else if (*line == GIT_SIDE_BAND_PROGRESS) - error = sideband_progress_pkt(pkt, line, len); - else if (*line == GIT_SIDE_BAND_ERROR) - error = sideband_error_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "ACK")) - error = ack_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "NAK")) - error = nak_pkt(pkt); - else if (!git__prefixncmp(line, len, "ERR")) - error = err_pkt(pkt, line, len); - else if (*line == '#') - error = comment_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "ok")) - error = ok_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "ng")) - error = ng_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "unpack")) - error = unpack_pkt(pkt, line, len); - else - error = ref_pkt(pkt, line, len); - - *endptr = line + len; - - return error; -} - -void git_pkt_free(git_pkt *pkt) -{ - if (pkt == NULL) { - return; - } - if (pkt->type == GIT_PKT_REF) { - git_pkt_ref *p = (git_pkt_ref *) pkt; - git__free(p->head.name); - git__free(p->head.symref_target); - } - - if (pkt->type == GIT_PKT_OK) { - git_pkt_ok *p = (git_pkt_ok *) pkt; - git__free(p->ref); - } - - if (pkt->type == GIT_PKT_NG) { - git_pkt_ng *p = (git_pkt_ng *) pkt; - git__free(p->ref); - git__free(p->msg); - } - - git__free(pkt); -} - -int git_pkt_buffer_flush(git_str *buf) -{ - return git_str_put(buf, pkt_flush_str, strlen(pkt_flush_str)); -} - -static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_str *buf) -{ - git_str str = GIT_STR_INIT; - char oid[GIT_OID_HEXSZ +1] = {0}; - size_t len; - - /* Prefer multi_ack_detailed */ - if (caps->multi_ack_detailed) - git_str_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " "); - else if (caps->multi_ack) - git_str_puts(&str, GIT_CAP_MULTI_ACK " "); - - /* Prefer side-band-64k if the server supports both */ - if (caps->side_band_64k) - git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); - else if (caps->side_band) - git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND); - - if (caps->include_tag) - git_str_puts(&str, GIT_CAP_INCLUDE_TAG " "); - - if (caps->thin_pack) - git_str_puts(&str, GIT_CAP_THIN_PACK " "); - - if (caps->ofs_delta) - git_str_puts(&str, GIT_CAP_OFS_DELTA " "); - - if (git_str_oom(&str)) - return -1; - - len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + - git_str_len(&str) + 1 /* LF */; - - if (len > 0xffff) { - git_error_set(GIT_ERROR_NET, - "tried to produce packet with invalid length %" PRIuZ, len); - return -1; - } - - git_str_grow_by(buf, len); - git_oid_fmt(oid, &head->oid); - git_str_printf(buf, - "%04xwant %s %s\n", (unsigned int)len, oid, git_str_cstr(&str)); - git_str_dispose(&str); - - GIT_ERROR_CHECK_ALLOC_STR(buf); - - return 0; -} - -/* - * All "want" packets have the same length and format, so what we do - * is overwrite the OID each time. - */ - -int git_pkt_buffer_wants( - const git_remote_head * const *refs, - size_t count, - transport_smart_caps *caps, - git_str *buf) -{ - size_t i = 0; - const git_remote_head *head; - - if (caps->common) { - for (; i < count; ++i) { - head = refs[i]; - if (!head->local) - break; - } - - if (buffer_want_with_caps(refs[i], caps, buf) < 0) - return -1; - - i++; - } - - for (; i < count; ++i) { - char oid[GIT_OID_HEXSZ]; - - head = refs[i]; - if (head->local) - continue; - - git_oid_fmt(oid, &head->oid); - git_str_put(buf, pkt_want_prefix, strlen(pkt_want_prefix)); - git_str_put(buf, oid, GIT_OID_HEXSZ); - git_str_putc(buf, '\n'); - if (git_str_oom(buf)) - return -1; - } - - return git_pkt_buffer_flush(buf); -} - -int git_pkt_buffer_have(git_oid *oid, git_str *buf) -{ - char oidhex[GIT_OID_HEXSZ + 1]; - - memset(oidhex, 0x0, sizeof(oidhex)); - git_oid_fmt(oidhex, oid); - return git_str_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); -} - -int git_pkt_buffer_done(git_str *buf) -{ - return git_str_puts(buf, pkt_done_str); -} diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c deleted file mode 100644 index 8cf027133..000000000 --- a/src/transports/smart_protocol.c +++ /dev/null @@ -1,1094 +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 "common.h" - -#include "git2.h" -#include "git2/odb_backend.h" - -#include "smart.h" -#include "refs.h" -#include "repository.h" -#include "push.h" -#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). */ -#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 - -bool git_smart__ofs_delta_enabled = true; - -int git_smart__store_refs(transport_smart *t, int flushes) -{ - gitno_buffer *buf = &t->buffer; - git_vector *refs = &t->refs; - int error, flush = 0, recvd; - const char *line_end = NULL; - git_pkt *pkt = NULL; - size_t i; - - /* Clear existing refs in case git_remote_connect() is called again - * after git_remote_disconnect(). - */ - git_vector_foreach(refs, i, pkt) { - git_pkt_free(pkt); - } - git_vector_clear(refs); - pkt = NULL; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, &line_end, buf->data, buf->offset); - else - error = GIT_EBUFS; - - if (error < 0 && error != GIT_EBUFS) - return error; - - if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) - return recvd; - - if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); - return GIT_EEOF; - } - - continue; - } - - if (gitno_consume(buf, line_end) < 0) - return -1; - - if (pkt->type == GIT_PKT_ERR) { - git_error_set(GIT_ERROR_NET, "remote error: %s", ((git_pkt_err *)pkt)->error); - git__free(pkt); - return -1; - } - - if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) - return -1; - - if (pkt->type == GIT_PKT_FLUSH) { - flush++; - git_pkt_free(pkt); - } - } while (flush < flushes); - - return flush; -} - -static int append_symref(const char **out, git_vector *symrefs, const char *ptr) -{ - int error; - const char *end; - git_str buf = GIT_STR_INIT; - git_refspec *mapping = NULL; - - ptr += strlen(GIT_CAP_SYMREF); - if (*ptr != '=') - goto on_invalid; - - ptr++; - if (!(end = strchr(ptr, ' ')) && - !(end = strchr(ptr, '\0'))) - goto on_invalid; - - if ((error = git_str_put(&buf, ptr, end - ptr)) < 0) - return error; - - /* symref mapping has refspec format */ - mapping = git__calloc(1, sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(mapping); - - error = git_refspec__parse(mapping, git_str_cstr(&buf), true); - git_str_dispose(&buf); - - /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ - if (error < 0) { - if (git_error_last()->klass != GIT_ERROR_NOMEMORY) - goto on_invalid; - - git__free(mapping); - return error; - } - - if ((error = git_vector_insert(symrefs, mapping)) < 0) - return error; - - *out = end; - return 0; - -on_invalid: - git_error_set(GIT_ERROR_NET, "remote sent invalid symref"); - git_refspec__dispose(mapping); - git__free(mapping); - return -1; -} - -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) -{ - const char *ptr; - - /* No refs or capabilities, odd but not a problem */ - if (pkt == NULL || pkt->capabilities == NULL) - return GIT_ENOTFOUND; - - ptr = pkt->capabilities; - while (ptr != NULL && *ptr != '\0') { - if (*ptr == ' ') - ptr++; - - if (git_smart__ofs_delta_enabled && !git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { - caps->common = caps->ofs_delta = 1; - ptr += strlen(GIT_CAP_OFS_DELTA); - continue; - } - - /* Keep multi_ack_detailed before multi_ack */ - if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) { - caps->common = caps->multi_ack_detailed = 1; - ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { - caps->common = caps->multi_ack = 1; - ptr += strlen(GIT_CAP_MULTI_ACK); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { - caps->common = caps->include_tag = 1; - ptr += strlen(GIT_CAP_INCLUDE_TAG); - continue; - } - - /* Keep side-band check after side-band-64k */ - if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { - caps->common = caps->side_band_64k = 1; - ptr += strlen(GIT_CAP_SIDE_BAND_64K); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { - caps->common = caps->side_band = 1; - ptr += strlen(GIT_CAP_SIDE_BAND); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { - caps->common = caps->delete_refs = 1; - ptr += strlen(GIT_CAP_DELETE_REFS); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { - caps->common = caps->thin_pack = 1; - ptr += strlen(GIT_CAP_THIN_PACK); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { - int error; - - if ((error = append_symref(&ptr, symrefs, ptr)) < 0) - return error; - - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_WANT_TIP_SHA1)) { - caps->common = caps->want_tip_sha1 = 1; - ptr += strlen(GIT_CAP_DELETE_REFS); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_WANT_REACHABLE_SHA1)) { - caps->common = caps->want_reachable_sha1 = 1; - ptr += strlen(GIT_CAP_DELETE_REFS); - continue; - } - - /* We don't know this capability, so skip it */ - ptr = strchr(ptr, ' '); - } - - return 0; -} - -static int recv_pkt(git_pkt **out_pkt, git_pkt_type *out_type, gitno_buffer *buf) -{ - const char *ptr = buf->data, *line_end = ptr; - git_pkt *pkt = NULL; - int error = 0, ret; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, &line_end, ptr, buf->offset); - else - error = GIT_EBUFS; - - if (error == 0) - break; /* return the pkt */ - - if (error < 0 && error != GIT_EBUFS) - return error; - - if ((ret = gitno_recv(buf)) < 0) { - return ret; - } else if (ret == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); - return GIT_EEOF; - } - } while (error); - - if (gitno_consume(buf, line_end) < 0) - return -1; - - if (out_type != NULL) - *out_type = pkt->type; - if (out_pkt != NULL) - *out_pkt = pkt; - else - git__free(pkt); - - return error; -} - -static int store_common(transport_smart *t) -{ - git_pkt *pkt = NULL; - gitno_buffer *buf = &t->buffer; - int error; - - do { - if ((error = recv_pkt(&pkt, NULL, buf)) < 0) - return error; - - if (pkt->type != GIT_PKT_ACK) { - git__free(pkt); - return 0; - } - - if (git_vector_insert(&t->common, pkt) < 0) { - git__free(pkt); - return -1; - } - } while (1); - - return 0; -} - -static int wait_while_ack(gitno_buffer *buf) -{ - int error; - git_pkt *pkt = NULL; - git_pkt_ack *ack = NULL; - - while (1) { - git_pkt_free(pkt); - - if ((error = recv_pkt(&pkt, NULL, buf)) < 0) - return error; - - if (pkt->type == GIT_PKT_NAK) - break; - if (pkt->type != GIT_PKT_ACK) - continue; - - ack = (git_pkt_ack*)pkt; - - if (ack->status != GIT_ACK_CONTINUE && - ack->status != GIT_ACK_COMMON && - ack->status != GIT_ACK_READY) { - break; - } - } - - git_pkt_free(pkt); - return 0; -} - -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_str data = GIT_STR_INIT; - git_revwalk *walk = NULL; - int error = -1; - git_pkt_type pkt_type; - unsigned int i; - git_oid oid; - - if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) - return error; - - 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; - - /* - * Our support for ACK extensions is simply to parse them. On - * the first ACK we will accept that as enough common - * objects. We give up if we haven't found an answer in the - * first 256 we send. - */ - i = 0; - while (i < 256) { - error = git_revwalk_next(&oid, walk); - - if (error < 0) { - if (GIT_ITEROVER == error) - break; - - goto on_error; - } - - git_pkt_buffer_have(&oid, &data); - i++; - if (i % 20 == 0) { - if (t->cancelled.val) { - git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - - git_pkt_buffer_flush(&data); - if (git_str_oom(&data)) { - error = -1; - goto on_error; - } - - if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) - goto on_error; - - git_str_clear(&data); - if (t->caps.multi_ack || t->caps.multi_ack_detailed) { - if ((error = store_common(t)) < 0) - goto on_error; - } else { - if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) - goto on_error; - - if (pkt_type == GIT_PKT_ACK) { - break; - } else if (pkt_type == GIT_PKT_NAK) { - continue; - } else { - git_error_set(GIT_ERROR_NET, "unexpected pkt type"); - error = -1; - goto on_error; - } - } - } - - if (t->common.length > 0) - break; - - if (i % 20 == 0 && t->rpc) { - git_pkt_ack *pkt; - unsigned int j; - - if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) - goto on_error; - - git_vector_foreach(&t->common, j, pkt) { - if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) - goto on_error; - } - - if (git_str_oom(&data)) { - error = -1; - goto on_error; - } - } - } - - /* Tell the other end that we're done negotiating */ - if (t->rpc && t->common.length > 0) { - git_pkt_ack *pkt; - unsigned int j; - - if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) - goto on_error; - - git_vector_foreach(&t->common, j, pkt) { - if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) - goto on_error; - } - - if (git_str_oom(&data)) { - error = -1; - goto on_error; - } - } - - if ((error = git_pkt_buffer_done(&data)) < 0) - goto on_error; - - if (t->cancelled.val) { - git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) - goto on_error; - - git_str_dispose(&data); - git_revwalk_free(walk); - - /* Now let's eat up whatever the server gives us */ - if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) { - if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) - return error; - - if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { - git_error_set(GIT_ERROR_NET, "unexpected pkt type"); - return -1; - } - } else { - error = wait_while_ack(buf); - } - - return error; - -on_error: - git_revwalk_free(walk); - git_str_dispose(&data); - return error; -} - -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"); - return GIT_EUSER; - } - - if (writepack->append(writepack, buf->data, buf->offset, stats) < 0) - return -1; - - gitno_consume_n(buf, buf->offset); - - if ((recvd = gitno_recv(buf)) < 0) - return recvd; - } while(recvd > 0); - - if (writepack->commit(writepack, stats) < 0) - return -1; - - return 0; -} - -struct network_packetsize_payload -{ - git_indexer_progress_cb callback; - void *payload; - git_indexer_progress *stats; - size_t last_fired_bytes; -}; - -static int network_packetsize(size_t received, void *payload) -{ - struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; - - /* Accumulate bytes */ - npp->stats->received_bytes += received; - - /* Fire notification if the threshold is reached */ - if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { - npp->last_fired_bytes = npp->stats->received_bytes; - - if (npp->callback(npp->stats, npp->payload)) - return GIT_EUSER; - } - - return 0; -} - -int git_smart__download_pack( - git_transport *transport, - git_repository *repo, - git_indexer_progress *stats) -{ - transport_smart *t = (transport_smart *)transport; - gitno_buffer *buf = &t->buffer; - git_odb *odb; - struct git_odb_writepack *writepack = NULL; - int error = 0; - struct network_packetsize_payload npp = {0}; - - git_indexer_progress_cb progress_cb = t->connect_opts.callbacks.transfer_progress; - void *progress_payload = t->connect_opts.callbacks.payload; - - memset(stats, 0, sizeof(git_indexer_progress)); - - if (progress_cb) { - npp.callback = progress_cb; - npp.payload = progress_payload; - npp.stats = stats; - t->packetsize_cb = &network_packetsize; - t->packetsize_payload = &npp; - - /* We might have something in the buffer already from negotiate_fetch */ - if (t->buffer.offset > 0 && !t->cancelled.val) - if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload)) - git_atomic32_set(&t->cancelled, 1); - } - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)) - goto done; - - /* - * If the remote doesn't support the side-band, we can feed - * the data directly to the pack writer. Otherwise, we need to - * check which one belongs there. - */ - if (!t->caps.side_band && !t->caps.side_band_64k) { - error = no_sideband(t, writepack, buf, stats); - goto done; - } - - do { - git_pkt *pkt = NULL; - - /* Check cancellation before network call */ - if (t->cancelled.val) { - git_error_clear(); - error = GIT_EUSER; - goto done; - } - - if ((error = recv_pkt(&pkt, NULL, buf)) >= 0) { - /* Check cancellation after network call */ - if (t->cancelled.val) { - git_error_clear(); - error = GIT_EUSER; - } else if (pkt->type == GIT_PKT_PROGRESS) { - if (t->connect_opts.callbacks.sideband_progress) { - git_pkt_progress *p = (git_pkt_progress *) pkt; - - if (p->len > INT_MAX) { - git_error_set(GIT_ERROR_NET, "oversized progress message"); - error = GIT_ERROR; - goto done; - } - - error = t->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, t->connect_opts.callbacks.payload); - } - } else if (pkt->type == GIT_PKT_DATA) { - git_pkt_data *p = (git_pkt_data *) pkt; - - if (p->len) - error = writepack->append(writepack, p->data, p->len, stats); - } else if (pkt->type == GIT_PKT_FLUSH) { - /* A flush indicates the end of the packfile */ - git__free(pkt); - break; - } - } - - git_pkt_free(pkt); - - if (error < 0) - goto done; - - } while (1); - - /* - * 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 - * "received_bytes" value, but until (if?) this is unified - * then we will report progress again to be sure that the - * correct last received_bytes value is reported. - */ - if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) { - error = npp.callback(npp.stats, npp.payload); - if (error != 0) - goto done; - } - - error = writepack->commit(writepack, stats); - -done: - if (writepack) - writepack->free(writepack); - if (progress_cb) { - t->packetsize_cb = NULL; - t->packetsize_payload = NULL; - } - - return error; -} - -static int gen_pktline(git_str *buf, git_push *push) -{ - push_spec *spec; - size_t i, len; - char old_id[GIT_OID_HEXSZ+1], new_id[GIT_OID_HEXSZ+1]; - - old_id[GIT_OID_HEXSZ] = '\0'; new_id[GIT_OID_HEXSZ] = '\0'; - - git_vector_foreach(&push->specs, i, spec) { - len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->refspec.dst); - - if (i == 0) { - ++len; /* '\0' */ - if (push->report_status) - len += strlen(GIT_CAP_REPORT_STATUS) + 1; - len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; - } - - git_oid_fmt(old_id, &spec->roid); - git_oid_fmt(new_id, &spec->loid); - - git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst); - - if (i == 0) { - git_str_putc(buf, '\0'); - /* Core git always starts their capabilities string with a space */ - if (push->report_status) { - git_str_putc(buf, ' '); - git_str_printf(buf, GIT_CAP_REPORT_STATUS); - } - git_str_putc(buf, ' '); - git_str_printf(buf, GIT_CAP_SIDE_BAND_64K); - } - - git_str_putc(buf, '\n'); - } - - git_str_puts(buf, "0000"); - return git_str_oom(buf) ? -1 : 0; -} - -static int add_push_report_pkt(git_push *push, git_pkt *pkt) -{ - push_status *status; - - switch (pkt->type) { - case GIT_PKT_OK: - status = git__calloc(1, sizeof(push_status)); - GIT_ERROR_CHECK_ALLOC(status); - status->msg = NULL; - status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); - if (!status->ref || - git_vector_insert(&push->status, status) < 0) { - git_push_status_free(status); - return -1; - } - break; - case GIT_PKT_NG: - status = git__calloc(1, sizeof(push_status)); - GIT_ERROR_CHECK_ALLOC(status); - status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); - status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); - if (!status->ref || !status->msg || - git_vector_insert(&push->status, status) < 0) { - git_push_status_free(status); - return -1; - } - break; - case GIT_PKT_UNPACK: - push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; - break; - case GIT_PKT_FLUSH: - return GIT_ITEROVER; - default: - git_error_set(GIT_ERROR_NET, "report-status: protocol error"); - return -1; - } - - return 0; -} - -static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_str *data_pkt_buf) -{ - git_pkt *pkt; - const char *line, *line_end = NULL; - size_t line_len; - int error; - int reading_from_buf = data_pkt_buf->size > 0; - - if (reading_from_buf) { - /* We had an existing partial packet, so add the new - * packet to the buffer and parse the whole thing */ - git_str_put(data_pkt_buf, data_pkt->data, data_pkt->len); - line = data_pkt_buf->ptr; - line_len = data_pkt_buf->size; - } - else { - line = data_pkt->data; - line_len = data_pkt->len; - } - - while (line_len > 0) { - error = git_pkt_parse_line(&pkt, &line_end, line, line_len); - - if (error == GIT_EBUFS) { - /* Buffer the data when the inner packet is split - * across multiple sideband packets */ - if (!reading_from_buf) - git_str_put(data_pkt_buf, line, line_len); - error = 0; - goto done; - } - else if (error < 0) - goto done; - - /* Advance in the buffer */ - line_len -= (line_end - line); - line = line_end; - - error = add_push_report_pkt(push, pkt); - - git_pkt_free(pkt); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - - error = 0; - -done: - if (reading_from_buf) - git_str_consume(data_pkt_buf, line_end); - return error; -} - -static int parse_report(transport_smart *transport, git_push *push) -{ - git_pkt *pkt = NULL; - const char *line_end = NULL; - gitno_buffer *buf = &transport->buffer; - int error, recvd; - git_str data_pkt_buf = GIT_STR_INIT; - - for (;;) { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, &line_end, - buf->data, buf->offset); - else - error = GIT_EBUFS; - - if (error < 0 && error != GIT_EBUFS) { - error = -1; - goto done; - } - - if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) { - error = recvd; - goto done; - } - - if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); - error = GIT_EEOF; - goto done; - } - continue; - } - - if (gitno_consume(buf, line_end) < 0) - return -1; - - error = 0; - - switch (pkt->type) { - case GIT_PKT_DATA: - /* This is a sideband packet which contains other packets */ - error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt, &data_pkt_buf); - break; - case GIT_PKT_ERR: - git_error_set(GIT_ERROR_NET, "report-status: Error reported: %s", - ((git_pkt_err *)pkt)->error); - error = -1; - break; - case GIT_PKT_PROGRESS: - if (transport->connect_opts.callbacks.sideband_progress) { - git_pkt_progress *p = (git_pkt_progress *) pkt; - - if (p->len > INT_MAX) { - git_error_set(GIT_ERROR_NET, "oversized progress message"); - error = GIT_ERROR; - goto done; - } - - error = transport->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, transport->connect_opts.callbacks.payload); - } - break; - default: - error = add_push_report_pkt(push, pkt); - break; - } - - git_pkt_free(pkt); - - /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ - if (error == GIT_ITEROVER) { - error = 0; - 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"); - error = GIT_ERROR; - } - goto done; - } - - if (error < 0) { - goto done; - } - } -done: - git_str_dispose(&data_pkt_buf); - return error; -} - -static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec) -{ - git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref)); - GIT_ERROR_CHECK_ALLOC(added); - - added->type = GIT_PKT_REF; - git_oid_cpy(&added->head.oid, &push_spec->loid); - added->head.name = git__strdup(push_spec->refspec.dst); - - if (!added->head.name || - git_vector_insert(refs, added) < 0) { - git_pkt_free((git_pkt *)added); - return -1; - } - - return 0; -} - -static int update_refs_from_report( - git_vector *refs, - git_vector *push_specs, - git_vector *push_report) -{ - git_pkt_ref *ref; - push_spec *push_spec; - push_status *push_status; - size_t i, j, refs_len; - int cmp; - - /* For each push spec we sent to the server, we should have - * gotten back a status packet in the push report */ - if (push_specs->length != push_report->length) { - git_error_set(GIT_ERROR_NET, "report-status: protocol error"); - return -1; - } - - /* We require that push_specs be sorted with push_spec_rref_cmp, - * and that push_report be sorted with push_status_ref_cmp */ - git_vector_sort(push_specs); - git_vector_sort(push_report); - - git_vector_foreach(push_specs, i, push_spec) { - push_status = git_vector_get(push_report, i); - - /* For each push spec we sent to the server, we should have - * gotten back a status packet in the push report which matches */ - if (strcmp(push_spec->refspec.dst, push_status->ref)) { - git_error_set(GIT_ERROR_NET, "report-status: protocol error"); - return -1; - } - } - - /* We require that refs be sorted with ref_name_cmp */ - git_vector_sort(refs); - i = j = 0; - refs_len = refs->length; - - /* Merge join push_specs with refs */ - while (i < push_specs->length && j < refs_len) { - push_spec = git_vector_get(push_specs, i); - push_status = git_vector_get(push_report, i); - ref = git_vector_get(refs, j); - - cmp = strcmp(push_spec->refspec.dst, ref->head.name); - - /* Iterate appropriately */ - if (cmp <= 0) i++; - if (cmp >= 0) j++; - - /* Add case */ - if (cmp < 0 && - !push_status->msg && - add_ref_from_push_spec(refs, push_spec) < 0) - return -1; - - /* Update case, delete case */ - if (cmp == 0 && - !push_status->msg) - git_oid_cpy(&ref->head.oid, &push_spec->loid); - } - - for (; i < push_specs->length; i++) { - push_spec = git_vector_get(push_specs, i); - push_status = git_vector_get(push_report, i); - - /* Add case */ - if (!push_status->msg && - add_ref_from_push_spec(refs, push_spec) < 0) - return -1; - } - - /* Remove any refs which we updated to have a zero OID. */ - git_vector_rforeach(refs, i, ref) { - if (git_oid_is_zero(&ref->head.oid)) { - git_vector_remove(refs, i); - git_pkt_free((git_pkt *)ref); - } - } - - git_vector_sort(refs); - - return 0; -} - -struct push_packbuilder_payload -{ - git_smart_subtransport_stream *stream; - git_packbuilder *pb; - git_push_transfer_progress_cb cb; - void *cb_payload; - size_t last_bytes; - double last_progress_report_time; -}; - -static int stream_thunk(void *buf, size_t size, void *data) -{ - int error = 0; - struct push_packbuilder_payload *payload = data; - - if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0) - return error; - - if (payload->cb) { - double current_time = git__timer(); - double elapsed = current_time - payload->last_progress_report_time; - payload->last_bytes += size; - - if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { - payload->last_progress_report_time = current_time; - error = payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload); - } - } - - return error; -} - -int git_smart__push(git_transport *transport, git_push *push) -{ - transport_smart *t = (transport_smart *)transport; - git_remote_callbacks *cbs = &t->connect_opts.callbacks; - struct push_packbuilder_payload packbuilder_payload = {0}; - git_str pktline = GIT_STR_INIT; - int error = 0, need_pack = 0; - push_spec *spec; - unsigned int i; - - packbuilder_payload.pb = push->pb; - - if (cbs && cbs->push_transfer_progress) { - packbuilder_payload.cb = cbs->push_transfer_progress; - packbuilder_payload.cb_payload = cbs->payload; - } - -#ifdef PUSH_DEBUG -{ - git_remote_head *head; - char hex[GIT_OID_HEXSZ+1]; hex[GIT_OID_HEXSZ] = '\0'; - - git_vector_foreach(&push->remote->refs, i, head) { - git_oid_fmt(hex, &head->oid); - fprintf(stderr, "%s (%s)\n", hex, head->name); - } - - git_vector_foreach(&push->specs, i, spec) { - git_oid_fmt(hex, &spec->roid); - fprintf(stderr, "%s (%s) -> ", hex, spec->lref); - git_oid_fmt(hex, &spec->loid); - fprintf(stderr, "%s (%s)\n", hex, spec->rref ? - spec->rref : spec->lref); - } -} -#endif - - /* - * Figure out if we need to send a packfile; which is in all - * cases except when we only send delete commands - */ - git_vector_foreach(&push->specs, i, spec) { - if (spec->refspec.src && spec->refspec.src[0] != '\0') { - need_pack = 1; - break; - } - } - - /* prepare pack before sending pack header to avoid timeouts */ - if (need_pack && ((error = git_packbuilder__prepare(push->pb))) < 0) - goto done; - - if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 || - (error = gen_pktline(&pktline, push)) < 0 || - (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_str_cstr(&pktline), git_str_len(&pktline))) < 0) - goto done; - - if (need_pack && - (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0) - goto done; - - /* If we sent nothing or the server doesn't support report-status, then - * we consider the pack to have been unpacked successfully */ - if (!push->specs.length || !push->report_status) - push->unpack_ok = 1; - else if ((error = parse_report(t, push)) < 0) - goto done; - - /* If progress is being reported write the final report */ - if (cbs && cbs->push_transfer_progress) { - error = cbs->push_transfer_progress( - push->pb->nr_written, - push->pb->nr_objects, - packbuilder_payload.last_bytes, - cbs->payload); - - if (error < 0) - goto done; - } - - if (push->status.length) { - error = update_refs_from_report(&t->refs, &push->specs, &push->status); - if (error < 0) - goto done; - - error = git_smart__update_heads(t, NULL); - } - -done: - git_str_dispose(&pktline); - return error; -} diff --git a/src/transports/ssh.c b/src/transports/ssh.c deleted file mode 100644 index 89f085230..000000000 --- a/src/transports/ssh.c +++ /dev/null @@ -1,915 +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 "ssh.h" - -#ifdef GIT_SSH -#include -#endif - -#include "runtime.h" -#include "net.h" -#include "netops.h" -#include "smart.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) - -static const char cmd_uploadpack[] = "git-upload-pack"; -static const char cmd_receivepack[] = "git-receive-pack"; - -typedef struct { - git_smart_subtransport_stream parent; - git_stream *io; - LIBSSH2_SESSION *session; - LIBSSH2_CHANNEL *channel; - const char *cmd; - git_net_url url; - unsigned sent_command : 1; -} ssh_stream; - -typedef struct { - git_smart_subtransport parent; - transport_smart *owner; - ssh_stream *current_stream; - git_credential *cred; - char *cmd_uploadpack; - char *cmd_receivepack; -} ssh_subtransport; - -static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); - -static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) -{ - char *ssherr; - libssh2_session_last_error(session, &ssherr, NULL, 0); - - git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); -} - -/* - * Create a git protocol request. - * - * For example: git-upload-pack '/libgit2/libgit2' - */ -static int gen_proto(git_str *request, const char *cmd, git_net_url *url) -{ - const char *repo; - - repo = url->path; - - if (repo && repo[0] == '/' && repo[1] == '~') - repo++; - - if (!repo || !repo[0]) { - git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); - return -1; - } - - git_str_puts(request, cmd); - git_str_puts(request, " '"); - git_str_puts(request, repo); - git_str_puts(request, "'"); - - if (git_str_oom(request)) - return -1; - - return 0; -} - -static int send_command(ssh_stream *s) -{ - int error; - git_str request = GIT_STR_INIT; - - error = gen_proto(&request, s->cmd, &s->url); - if (error < 0) - goto cleanup; - - error = libssh2_channel_exec(s->channel, request.ptr); - if (error < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not execute request"); - goto cleanup; - } - - s->sent_command = 1; - -cleanup: - git_str_dispose(&request); - return error; -} - -static int ssh_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) -{ - int rc; - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - - *bytes_read = 0; - - if (!s->sent_command && send_command(s) < 0) - return -1; - - if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not read data"); - return -1; - } - - /* - * If we can't get anything out of stdout, it's typically a - * not-found error, so read from stderr and signal EOF on - * stderr. - */ - if (rc == 0) { - if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { - git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer); - return GIT_EEOF; - } else if (rc < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not read stderr"); - return -1; - } - } - - - *bytes_read = rc; - - return 0; -} - -static int ssh_stream_write( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - size_t off = 0; - ssize_t ret = 0; - - if (!s->sent_command && send_command(s) < 0) - return -1; - - do { - ret = libssh2_channel_write(s->channel, buffer + off, len - off); - if (ret < 0) - break; - - off += ret; - - } while (off < len); - - if (ret < 0) { - ssh_error(s->session, "SSH could not write data"); - return -1; - } - - return 0; -} - -static void ssh_stream_free(git_smart_subtransport_stream *stream) -{ - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - ssh_subtransport *t; - - if (!stream) - return; - - t = OWNING_SUBTRANSPORT(s); - t->current_stream = NULL; - - if (s->channel) { - libssh2_channel_close(s->channel); - libssh2_channel_free(s->channel); - s->channel = NULL; - } - - if (s->session) { - libssh2_session_disconnect(s->session, "closing transport"); - libssh2_session_free(s->session); - s->session = NULL; - } - - if (s->io) { - git_stream_close(s->io); - git_stream_free(s->io); - s->io = NULL; - } - - git_net_url_dispose(&s->url); - git__free(s); -} - -static int ssh_stream_alloc( - ssh_subtransport *t, - const char *cmd, - git_smart_subtransport_stream **stream) -{ - ssh_stream *s; - - GIT_ASSERT_ARG(stream); - - s = git__calloc(sizeof(ssh_stream), 1); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = ssh_stream_read; - s->parent.write = ssh_stream_write; - s->parent.free = ssh_stream_free; - - s->cmd = cmd; - - *stream = &s->parent; - return 0; -} - -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; - - LIBSSH2_AGENT *agent = libssh2_agent_init(session); - - if (agent == NULL) - return -1; - - rc = libssh2_agent_connect(agent); - - if (rc != LIBSSH2_ERROR_NONE) - goto shutdown; - - rc = libssh2_agent_list_identities(agent); - - if (rc != LIBSSH2_ERROR_NONE) - goto shutdown; - - while (1) { - rc = libssh2_agent_get_identity(agent, &curr, prev); - - if (rc < 0) - goto shutdown; - - /* rc is set to 1 whenever the ssh agent ran out of keys to check. - * Set the error code to authentication failure rather than erroring - * out with an untranslatable error code. - */ - if (rc == 1) { - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - goto shutdown; - } - - rc = libssh2_agent_userauth(agent, c->username, curr); - - if (rc == 0) - break; - - prev = curr; - } - -shutdown: - - if (rc != LIBSSH2_ERROR_NONE) - ssh_error(session, "error authenticating"); - - libssh2_agent_disconnect(agent); - libssh2_agent_free(agent); - - return rc; -} - -static int _git_ssh_authenticate_session( - LIBSSH2_SESSION *session, - git_credential *cred) -{ - int rc; - - do { - git_error_clear(); - switch (cred->credtype) { - 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_CREDENTIAL_SSH_KEY: { - git_credential_ssh_key *c = (git_credential_ssh_key *)cred; - - if (c->privatekey) - rc = libssh2_userauth_publickey_fromfile( - session, c->username, c->publickey, - c->privatekey, c->passphrase); - else - rc = ssh_agent_auth(session, c); - - break; - } - 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_CREDENTIAL_SSH_INTERACTIVE: { - void **abstract = libssh2_session_abstract(session); - 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(). - * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() - * allows you to pass the `abstract` as part of the call, whereas - * libssh2_userauth_keyboard_interactive() does not! - * - * The only way to set the `abstract` pointer is by calling - * libssh2_session_abstract(), which will replace the existing - * pointer as is done below. This is safe for now (at time of writing), - * but may not be valid in future. - */ - *abstract = c->payload; - - rc = libssh2_userauth_keyboard_interactive( - session, c->username, c->prompt_callback); - break; - } -#ifdef GIT_SSH_MEMORY_CREDENTIALS - case GIT_CREDENTIAL_SSH_MEMORY: { - git_credential_ssh_key *c = (git_credential_ssh_key *)cred; - - GIT_ASSERT(c->username); - GIT_ASSERT(c->privatekey); - - rc = libssh2_userauth_publickey_frommemory( - session, - c->username, - strlen(c->username), - c->publickey, - c->publickey ? strlen(c->publickey) : 0, - c->privatekey, - strlen(c->privatekey), - c->passphrase); - break; - } -#endif - default: - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - } - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || - rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || - rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) - return GIT_EAUTH; - - if (rc != LIBSSH2_ERROR_NONE) { - if (!git_error_last()) - ssh_error(session, "Failed to authenticate SSH session"); - return -1; - } - - return 0; -} - -static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) -{ - int error, no_callback = 0; - git_credential *cred = NULL; - - if (!t->owner->connect_opts.callbacks.credentials) { - no_callback = 1; - } else { - error = t->owner->connect_opts.callbacks.credentials( - &cred, - t->owner->url, - user, - auth_methods, - t->owner->connect_opts.callbacks.payload); - - if (error == GIT_PASSTHROUGH) { - no_callback = 1; - } else if (error < 0) { - return error; - } else if (!cred) { - git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); - return -1; - } - } - - if (no_callback) { - git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); - return GIT_EAUTH; - } - - if (!(cred->credtype & auth_methods)) { - cred->free(cred); - git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); - return GIT_EAUTH; - } - - *out = cred; - - return 0; -} - -static int _git_ssh_session_create( - LIBSSH2_SESSION **session, - git_stream *io) -{ - int rc = 0; - LIBSSH2_SESSION *s; - git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); - - GIT_ASSERT_ARG(session); - - s = libssh2_session_init(); - if (!s) { - git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); - return -1; - } - - do { - rc = libssh2_session_handshake(s, socket->s); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (rc != LIBSSH2_ERROR_NONE) { - ssh_error(s, "failed to start SSH session"); - libssh2_session_free(s); - return -1; - } - - libssh2_session_set_blocking(s, 1); - - *session = s; - - 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) -{ - int auth_methods, error = 0; - ssh_stream *s; - git_credential *cred = NULL; - LIBSSH2_SESSION *session=NULL; - LIBSSH2_CHANNEL *channel=NULL; - - t->current_stream = NULL; - - *stream = NULL; - if (ssh_stream_alloc(t, cmd, stream) < 0) - return -1; - - s = (ssh_stream *)*stream; - s->session = NULL; - s->channel = NULL; - - if (git_net_str_is_url(url)) - error = git_net_url_parse(&s->url, url); - else - error = git_net_url_parse_scp(&s->url, url); - - if (error < 0) - goto done; - - if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || - (error = git_stream_connect(s->io)) < 0) - goto done; - - if ((error = _git_ssh_session_create(&session, s->io)) < 0) - goto done; - - if (t->owner->connect_opts.callbacks.certificate_check != NULL) { - git_cert_hostkey cert = {{ 0 }}, *cert_ptr; - const char *key; - size_t cert_len; - int cert_type; - - cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; - - key = libssh2_session_hostkey(session, &cert_len, &cert_type); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_RAW; - cert.hostkey = key; - cert.hostkey_len = cert_len; - switch (cert_type) { - case LIBSSH2_HOSTKEY_TYPE_RSA: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA; - break; - case LIBSSH2_HOSTKEY_TYPE_DSS: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS; - break; - -#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 - case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384; - break; - case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521; - break; -#endif - -#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 - case LIBSSH2_HOSTKEY_TYPE_ED25519: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519; - break; -#endif - default: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN; - } - } - -#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; - memcpy(&cert.hash_sha1, key, 20); - } - - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_MD5; - memcpy(&cert.hash_md5, key, 16); - } - - if (cert.type == 0) { - git_error_set(GIT_ERROR_SSH, "unable to get the host key"); - error = -1; - goto done; - } - - /* We don't currently trust any hostkeys */ - git_error_clear(); - - cert_ptr = &cert; - - error = t->owner->connect_opts.callbacks.certificate_check( - (git_cert *)cert_ptr, - 0, - s->url.host, - t->owner->connect_opts.callbacks.payload); - - if (error < 0 && error != GIT_PASSTHROUGH) { - if (!git_error_last()) - git_error_set(GIT_ERROR_NET, "user cancelled hostkey check"); - - goto done; - } - } - - /* we need the username to ask for auth methods */ - if (!s->url.username) { - if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) - goto done; - - s->url.username = git__strdup(((git_credential_username *) cred)->username); - cred->free(cred); - cred = NULL; - if (!s->url.username) - goto done; - } else if (s->url.username && s->url.password) { - if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0) - goto done; - } - - if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) - goto done; - - error = GIT_EAUTH; - /* if we already have something to try */ - if (cred && auth_methods & cred->credtype) - error = _git_ssh_authenticate_session(session, cred); - - while (error == GIT_EAUTH) { - if (cred) { - cred->free(cred); - cred = NULL; - } - - if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0) - goto done; - - if (strcmp(s->url.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, s->url.username)) < 0) - goto done; - else - error = GIT_EAUTH; - } - } - - if (error < 0) - goto done; - - channel = libssh2_channel_open_session(session); - if (!channel) { - error = -1; - ssh_error(session, "Failed to open SSH channel"); - goto done; - } - - libssh2_channel_set_blocking(channel, 1); - - s->session = session; - s->channel = channel; - - t->current_stream = s; - -done: - if (error < 0) { - ssh_stream_free(*stream); - - if (session) - libssh2_session_free(session); - } - - if (cred) - cred->free(cred); - - return error; -} - -static int ssh_uploadpack_ls( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; - - return _git_ssh_setup_conn(t, url, cmd, stream); -} - -static int ssh_uploadpack( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); - return -1; -} - -static int ssh_receivepack_ls( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; - - - return _git_ssh_setup_conn(t, url, cmd, stream); -} - -static int ssh_receivepack( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); - return -1; -} - -static int _ssh_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return ssh_uploadpack_ls(t, url, stream); - - case GIT_SERVICE_UPLOADPACK: - return ssh_uploadpack(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK_LS: - return ssh_receivepack_ls(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK: - return ssh_receivepack(t, url, stream); - } - - *stream = NULL; - return -1; -} - -static int _ssh_close(git_smart_subtransport *subtransport) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - GIT_ASSERT(!t->current_stream); - - GIT_UNUSED(t); - - return 0; -} - -static void _ssh_free(git_smart_subtransport *subtransport) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - git__free(t->cmd_uploadpack); - git__free(t->cmd_receivepack); - git__free(t); -} - -#define SSH_AUTH_PUBLICKEY "publickey" -#define SSH_AUTH_PASSWORD "password" -#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" - -static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) -{ - const char *list, *ptr; - - *out = 0; - - list = libssh2_userauth_list(session, username, strlen(username)); - - /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ - if (list == NULL && !libssh2_userauth_authenticated(session)) { - ssh_error(session, "Failed to retrieve list of SSH authentication methods"); - return GIT_EAUTH; - } - - ptr = list; - while (ptr) { - if (*ptr == ',') - ptr++; - - if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { - *out |= GIT_CREDENTIAL_SSH_KEY; - *out |= GIT_CREDENTIAL_SSH_CUSTOM; -#ifdef GIT_SSH_MEMORY_CREDENTIALS - *out |= GIT_CREDENTIAL_SSH_MEMORY; -#endif - ptr += strlen(SSH_AUTH_PUBLICKEY); - continue; - } - - if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { - *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - ptr += strlen(SSH_AUTH_PASSWORD); - continue; - } - - if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { - *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; - ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); - continue; - } - - /* Skip it if we don't know it */ - ptr = strchr(ptr, ','); - } - - return 0; -} -#endif - -int git_smart_subtransport_ssh( - git_smart_subtransport **out, git_transport *owner, void *param) -{ -#ifdef GIT_SSH - ssh_subtransport *t; - - GIT_ASSERT_ARG(out); - - GIT_UNUSED(param); - - t = git__calloc(sizeof(ssh_subtransport), 1); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = (transport_smart *)owner; - t->parent.action = _ssh_action; - t->parent.close = _ssh_close; - t->parent.free = _ssh_free; - - *out = (git_smart_subtransport *) t; - return 0; -#else - GIT_UNUSED(owner); - GIT_UNUSED(param); - - GIT_ASSERT_ARG(out); - *out = NULL; - - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); - return -1; -#endif -} - -int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) -{ -#ifdef GIT_SSH - git_strarray *paths = (git_strarray *) payload; - git_transport *transport; - transport_smart *smart; - ssh_subtransport *t; - int error; - git_smart_subtransport_definition ssh_definition = { - git_smart_subtransport_ssh, - 0, /* no RPC */ - NULL, - }; - - if (paths->count != 2) { - git_error_set(GIT_ERROR_SSH, "invalid ssh paths, must be two strings"); - return GIT_EINVALIDSPEC; - } - - if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) - return error; - - smart = (transport_smart *) transport; - t = (ssh_subtransport *) smart->wrapped; - - t->cmd_uploadpack = git__strdup(paths->strings[0]); - GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); - t->cmd_receivepack = git__strdup(paths->strings[1]); - GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); - - *out = transport; - return 0; -#else - GIT_UNUSED(owner); - GIT_UNUSED(payload); - - GIT_ASSERT_ARG(out); - *out = NULL; - - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); - return -1; -#endif -} - -#ifdef GIT_SSH -static void shutdown_ssh(void) -{ - libssh2_exit(); -} -#endif - -int git_transport_ssh_global_init(void) -{ -#ifdef GIT_SSH - if (libssh2_init(0) < 0) { - git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); - return -1; - } - - return git_runtime_shutdown_register(shutdown_ssh); - -#else - - /* Nothing to initialize */ - return 0; - -#endif -} diff --git a/src/transports/ssh.h b/src/transports/ssh.h deleted file mode 100644 index d3e741f1d..000000000 --- a/src/transports/ssh.h +++ /dev/null @@ -1,14 +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_transports_ssh_h__ -#define INCLUDE_transports_ssh_h__ - -#include "common.h" - -int git_transport_ssh_global_init(void); - -#endif diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c deleted file mode 100644 index 8ec5b37c5..000000000 --- a/src/transports/winhttp.c +++ /dev/null @@ -1,1686 +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 "common.h" - -#ifdef GIT_WINHTTP - -#include "git2.h" -#include "git2/transport.h" -#include "posix.h" -#include "str.h" -#include "netops.h" -#include "smart.h" -#include "remote.h" -#include "repository.h" -#include "http.h" -#include "git2/sys/credential.h" - -#include -#include - -/* For IInternetSecurityManager zone check */ -#include -#include - -#define WIDEN2(s) L ## s -#define WIDEN(s) WIDEN2(s) - -#define MAX_CONTENT_TYPE_LEN 100 -#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 -#define CACHED_POST_BODY_BUF_SIZE 4096 -#define UUID_LENGTH_CCH 32 -#define TIMEOUT_INFINITE -1 -#define DEFAULT_CONNECT_TIMEOUT 60000 -#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH -#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0 -#endif - -#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 -# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200 -#endif - -#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 -# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 -#endif - -#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 -# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 0x00002000 -#endif - -#ifndef WINHTTP_NO_CLIENT_CERT_CONTEXT -# define WINHTTP_NO_CLIENT_CERT_CONTEXT NULL -#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"; -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 wchar_t *get_verb = L"GET"; -static const wchar_t *post_verb = L"POST"; -static const wchar_t *pragma_nocache = L"Pragma: no-cache"; -static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked"; -static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | - SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | - SECURITY_FLAG_IGNORE_UNKNOWN_CA; - -#if defined(__MINGW32__) -static const CLSID CLSID_InternetSecurityManager_mingw = - { 0x7B8A2D94, 0x0AC9, 0x11D1, - { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } }; -static const IID IID_IInternetSecurityManager_mingw = - { 0x79EAC9EE, 0xBAF9, 0x11CE, - { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } }; - -# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw -# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw -#endif - -#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) - -typedef enum { - GIT_WINHTTP_AUTH_BASIC = 1, - GIT_WINHTTP_AUTH_NTLM = 2, - GIT_WINHTTP_AUTH_NEGOTIATE = 4, - GIT_WINHTTP_AUTH_DIGEST = 8 -} winhttp_authmechanism_t; - -typedef struct { - git_smart_subtransport_stream parent; - const char *service; - const char *service_url; - const wchar_t *verb; - HINTERNET request; - wchar_t *request_uri; - char *chunk_buffer; - unsigned chunk_buffer_len; - HANDLE post_body; - DWORD post_body_len; - unsigned sent_request : 1, - received_response : 1, - chunked : 1, - status_sending_request_reached: 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; - - winhttp_server server; - winhttp_server proxy; - - HINTERNET session; - HINTERNET connection; -} winhttp_subtransport; - -static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_credential *cred) -{ - 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 = GIT_EAUTH; - goto done; - } - - if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0) - goto done; - - if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0) - goto done; - - if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) { - git_error_set(GIT_ERROR_OS, "failed to set credentials"); - error = -1; - } - -done: - if (user_len > 0) - git__memzero(user, user_len * sizeof(wchar_t)); - - if (pass_len > 0) - git__memzero(pass, pass_len * sizeof(wchar_t)); - - git__free(user); - git__free(pass); - - return error; -} - -static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms) -{ - DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; - DWORD native_scheme = 0; - - if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) { - native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; - } 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 GIT_EAUTH; - } - - /* - * 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, target, native_scheme, NULL, NULL, NULL)) { - git_error_set(GIT_ERROR_OS, "could not configure credentials"); - return -1; - } - - return 0; -} - -static int acquire_url_cred( - git_credential **cred, - unsigned int allowed_types, - 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; - - /* If the target URI supports integrated Windows authentication - * as an authentication mechanism */ - if (GIT_CREDENTIAL_DEFAULT & allowed_types) { - wchar_t *wide_url; - HRESULT hCoInitResult; - - /* Convert URL to wide characters */ - if (git__utf8_to_16_alloc(&wide_url, url) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); - return -1; - } - - hCoInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); - - if (SUCCEEDED(hCoInitResult) || hCoInitResult == RPC_E_CHANGED_MODE) { - IInternetSecurityManager *pISM; - - /* And if the target URI is in the My Computer, Intranet, or Trusted zones */ - if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL, - CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) { - DWORD dwZone; - - if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) && - (URLZONE_LOCAL_MACHINE == dwZone || - URLZONE_INTRANET == dwZone || - URLZONE_TRUSTED == dwZone)) { - git_credential *existing = *cred; - - if (existing) - existing->free(existing); - - /* Then use default Windows credentials to authenticate this request */ - error = git_credential_default_new(cred); - } - - pISM->lpVtbl->Release(pISM); - } - - /* Only uninitialize if the call to CoInitializeEx was successful. */ - if (SUCCEEDED(hCoInitResult)) - CoUninitialize(); - } - - git__free(wide_url); - } - - return error; -} - -static int certificate_check(winhttp_stream *s, int valid) -{ - int error; - winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); - PCERT_CONTEXT cert_ctx; - DWORD cert_ctx_size = sizeof(cert_ctx); - git_cert_x509 cert; - - /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ - if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { - if (!git_error_last()) - git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); - - return GIT_ECERTIFICATE; - } - - if (t->owner->connect_opts.callbacks.certificate_check == 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)) { - git_error_set(GIT_ERROR_OS, "failed to get server certificate"); - return -1; - } - - git_error_clear(); - cert.parent.cert_type = GIT_CERT_X509; - cert.data = cert_ctx->pbCertEncoded; - cert.len = cert_ctx->cbCertEncoded; - error = t->owner->connect_opts.callbacks.certificate_check((git_cert *) &cert, valid, t->server.url.host, t->owner->connect_opts.callbacks.payload); - CertFreeCertificateContext(cert_ctx); - - if (error == GIT_PASSTHROUGH) - error = valid ? 0 : GIT_ECERTIFICATE; - - if (error < 0 && !git_error_last()) - git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); - - return error; -} - -static void winhttp_stream_close(winhttp_stream *s) -{ - if (s->chunk_buffer) { - git__free(s->chunk_buffer); - s->chunk_buffer = NULL; - } - - if (s->post_body) { - CloseHandle(s->post_body); - s->post_body = NULL; - } - - if (s->request_uri) { - git__free(s->request_uri); - s->request_uri = NULL; - } - - if (s->request) { - WinHttpCloseHandle(s->request); - s->request = NULL; - } - - s->sent_request = 0; -} - -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) -{ - winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); - git_str buf = GIT_STR_INIT; - char *proxy_url = NULL; - wchar_t ct[MAX_CONTENT_TYPE_LEN]; - LPCWSTR types[] = { L"*/*", NULL }; - BOOL peerdist = FALSE; - int error = -1; - 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_str_printf(&buf, "%s%s", t->server.url.path, service_url); - - if (git_str_oom(&buf)) - return -1; - - /* Convert URL to wide characters */ - if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); - goto on_error; - } - - /* Establish request */ - s->request = WinHttpOpenRequest( - t->connection, - s->verb, - s->request_uri, - NULL, - WINHTTP_NO_REFERER, - types, - 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; - } - - proxy_opts = &t->owner->connect_opts.proxy_opts; - if (proxy_opts->type == GIT_PROXY_AUTO) { - /* Set proxy if necessary */ - if (git_remote__http_proxy(&proxy_url, t->owner->owner, &t->server.url) < 0) - goto on_error; - } - else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { - proxy_url = git__strdup(proxy_opts->url); - GIT_ERROR_CHECK_ALLOC(proxy_url); - } - - if (proxy_url) { - git_str processed_url = GIT_STR_INIT; - WINHTTP_PROXY_INFO proxy_info; - wchar_t *proxy_wide; - - git_net_url_dispose(&t->proxy.url); - - if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 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; - } - - git_str_puts(&processed_url, t->proxy.url.scheme); - git_str_PUTS(&processed_url, "://"); - - if (git_net_url_is_ipv6(&t->proxy.url)) - git_str_putc(&processed_url, '['); - - git_str_puts(&processed_url, t->proxy.url.host); - - if (git_net_url_is_ipv6(&t->proxy.url)) - git_str_putc(&processed_url, ']'); - - if (!git_net_url_is_default_port(&t->proxy.url)) - git_str_printf(&processed_url, ":%s", t->proxy.url.port); - - if (git_str_oom(&processed_url)) { - error = -1; - goto on_error; - } - - /* Convert URL to wide characters */ - error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr); - git_str_dispose(&processed_url); - if (error < 0) - goto on_error; - - proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; - proxy_info.lpszProxy = proxy_wide; - proxy_info.lpszProxyBypass = NULL; - - if (!WinHttpSetOption(s->request, - WINHTTP_OPTION_PROXY, - &proxy_info, - sizeof(WINHTTP_PROXY_INFO))) { - git_error_set(GIT_ERROR_OS, "failed to set proxy"); - git__free(proxy_wide); - goto on_error; - } - - git__free(proxy_wide); - - 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? - * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae - */ - if (!WinHttpSetOption(s->request, - WINHTTP_OPTION_DISABLE_FEATURE, - &disable_redirects, - sizeof(disable_redirects))) { - git_error_set(GIT_ERROR_OS, "failed to disable redirects"); - error = -1; - goto on_error; - } - - /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP - * adds itself. This option may not be supported by the underlying - * platform, so we do not error-check it */ - WinHttpSetOption(s->request, - WINHTTP_OPTION_PEERDIST_EXTENSION_STATE, - &peerdist, - sizeof(peerdist)); - - /* Send Pragma: no-cache header */ - if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - - if (post_verb == s->verb) { - /* Send Content-Type and Accept headers -- only necessary on a POST */ - git_str_clear(&buf); - if (git_str_printf(&buf, - "Content-Type: application/x-git-%s-request", - s->service) < 0) - goto on_error; - - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters"); - goto on_error; - } - - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - - git_str_clear(&buf); - if (git_str_printf(&buf, - "Accept: application/x-git-%s-result", - s->service) < 0) - goto on_error; - - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters"); - goto on_error; - } - - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - } - - for (i = 0; i < t->owner->connect_opts.custom_headers.count; i++) { - if (t->owner->connect_opts.custom_headers.strings[i]) { - git_str_clear(&buf); - git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]); - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters"); - goto on_error; - } - - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - } - } - - if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0) - goto on_error; - - /* We've done everything up to calling WinHttpSendRequest. */ - - error = 0; - -on_error: - if (error < 0) - winhttp_stream_close(s); - - git__free(proxy_url); - git_str_dispose(&buf); - return error; -} - -static int parse_unauthorized_response( - int *allowed_types, - int *allowed_mechanisms, - HINTERNET request) -{ - DWORD supported, first, target; - - *allowed_types = 0; - *allowed_mechanisms = 0; - - /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes(). - * We can assume this was already done, since we know we are unauthorized. - */ - if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) { - git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes"); - return GIT_EAUTH; - } - - if (WINHTTP_AUTH_SCHEME_NTLM & supported) { - *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_CREDENTIAL_DEFAULT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE; - } - - if (WINHTTP_AUTH_SCHEME_BASIC & supported) { - *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_BASIC; - } - - if (WINHTTP_AUTH_SCHEME_DIGEST & supported) { - *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST; - } - - return 0; -} - -static int write_chunk(HINTERNET request, const char *buffer, size_t len) -{ - DWORD bytes_written; - git_str buf = GIT_STR_INIT; - - /* Chunk header */ - git_str_printf(&buf, "%"PRIXZ"\r\n", len); - - if (git_str_oom(&buf)) - return -1; - - if (!WinHttpWriteData(request, - git_str_cstr(&buf), (DWORD)git_str_len(&buf), - &bytes_written)) { - git_str_dispose(&buf); - git_error_set(GIT_ERROR_OS, "failed to write chunk header"); - return -1; - } - - git_str_dispose(&buf); - - /* Chunk body */ - if (!WinHttpWriteData(request, - buffer, (DWORD)len, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write chunk"); - return -1; - } - - /* Chunk footer */ - if (!WinHttpWriteData(request, - "\r\n", 2, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write chunk footer"); - return -1; - } - - return 0; -} - -static int winhttp_close_connection(winhttp_subtransport *t) -{ - int ret = 0; - - if (t->connection) { - if (!WinHttpCloseHandle(t->connection)) { - git_error_set(GIT_ERROR_OS, "unable to close connection"); - ret = -1; - } - - t->connection = NULL; - } - - if (t->session) { - if (!WinHttpCloseHandle(t->session)) { - git_error_set(GIT_ERROR_OS, "unable to close session"); - ret = -1; - } - - t->session = NULL; - } - - return ret; -} - -static void CALLBACK winhttp_status( - HINTERNET connection, - DWORD_PTR ctx, - DWORD code, - LPVOID info, - DWORD info_len) -{ - DWORD status; - - GIT_UNUSED(connection); - GIT_UNUSED(info_len); - - switch (code) { - case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: - status = *((DWORD *)info); - - if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)) - 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_HTTP, "SSL certificate has expired"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_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_HTTP, "SSL certificate is invalid"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_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_HTTP, "SSL certificate was revoked"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)) - git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded"); - else - git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status); - - break; - - case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: - ((winhttp_stream *) ctx)->status_sending_request_reached = 1; - - break; - } -} - -static int winhttp_connect( - winhttp_subtransport *t) -{ - wchar_t *wide_host = NULL; - int32_t port; - wchar_t *wide_ua = NULL; - git_str ipv6 = GIT_STR_INIT, ua = GIT_STR_INIT; - const char *host; - int error = -1; - int default_timeout = TIMEOUT_INFINITE; - int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; - DWORD protocols = - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; - - t->session = NULL; - t->connection = NULL; - - /* Prepare port */ - if (git__strntol32(&port, t->server.url.port, - strlen(t->server.url.port), NULL, 10) < 0) - goto on_error; - - /* IPv6? Add braces around the host. */ - if (git_net_url_is_ipv6(&t->server.url)) { - if (git_str_printf(&ipv6, "[%s]", t->server.url.host) < 0) - goto on_error; - - host = ipv6.ptr; - } else { - host = t->server.url.host; - } - - /* Prepare host */ - if (git__utf8_to_16_alloc(&wide_host, host) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); - goto on_error; - } - - - if (git_http__user_agent(&ua) < 0) - goto on_error; - - if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); - goto on_error; - } - - /* Establish session */ - t->session = WinHttpOpen( - wide_ua, - WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - 0); - - if (!t->session) { - git_error_set(GIT_ERROR_OS, "failed to init WinHTTP"); - goto on_error; - } - - /* - * Do a best-effort attempt to enable TLS 1.3 and 1.2 but allow this to - * fail; if TLS 1.2 or 1.3 support is not available for some reason, - * ignore the failure (it will keep the default protocols). - */ - if (WinHttpSetOption(t->session, - WINHTTP_OPTION_SECURE_PROTOCOLS, - &protocols, - sizeof(protocols)) == FALSE) { - protocols &= ~WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; - WinHttpSetOption(t->session, - WINHTTP_OPTION_SECURE_PROTOCOLS, - &protocols, - sizeof(protocols)); - } - - if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { - git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); - goto on_error; - } - - - /* Establish connection */ - t->connection = WinHttpConnect( - t->session, - wide_host, - (INTERNET_PORT) port, - 0); - - if (!t->connection) { - git_error_set(GIT_ERROR_OS, "failed to connect to host"); - goto on_error; - } - - if (WinHttpSetStatusCallback( - t->connection, - winhttp_status, - WINHTTP_CALLBACK_FLAG_SECURE_FAILURE | WINHTTP_CALLBACK_FLAG_SEND_REQUEST, - 0 - ) == WINHTTP_INVALID_STATUS_CALLBACK) { - git_error_set(GIT_ERROR_OS, "failed to set status callback"); - goto on_error; - } - - error = 0; - -on_error: - if (error < 0) - winhttp_close_connection(t); - - git_str_dispose(&ua); - git_str_dispose(&ipv6); - git__free(wide_host); - git__free(wide_ua); - - return error; -} - -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 (chunked) { - success = WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, (DWORD_PTR)s); - } else { - success = WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - (DWORD)len, (DWORD_PTR)s); - } - - if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL) - break; - } - - return success ? 0 : -1; -} - -static int send_request(winhttp_stream *s, size_t len, bool chunked) -{ - int request_failed = 1, error, attempts = 0; - DWORD ignore_flags, send_request_error; - - git_error_clear(); - - while (request_failed && attempts++ < 3) { - int cert_valid = 1; - int client_cert_requested = 0; - request_failed = 0; - if ((error = do_send_request(s, len, chunked)) < 0) { - send_request_error = GetLastError(); - request_failed = 1; - switch (send_request_error) { - case ERROR_WINHTTP_SECURE_FAILURE: - cert_valid = 0; - break; - case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED: - client_cert_requested = 1; - break; - default: - git_error_set(GIT_ERROR_OS, "failed to send request"); - return -1; - } - } - - /* - * Only check the certificate if we were able to reach the sending request phase, or - * received a secure failure error. Otherwise, the server certificate won't be available - * since the request wasn't able to complete (e.g. proxy auth required) - */ - if (!cert_valid || - (!request_failed && s->status_sending_request_reached)) { - git_error_clear(); - if ((error = certificate_check(s, cert_valid)) < 0) { - if (!git_error_last()) - git_error_set(GIT_ERROR_OS, "user cancelled certificate check"); - - return error; - } - } - - /* if neither the request nor the certificate check returned errors, we're done */ - if (!request_failed) - return 0; - - if (!cert_valid) { - ignore_flags = no_check_cert_flags; - if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) { - git_error_set(GIT_ERROR_OS, "failed to set security options"); - return -1; - } - } - - if (client_cert_requested) { - /* - * Client certificates are not supported, explicitly tell the server that - * (it's possible a client certificate was requested but is not required) - */ - if (!WinHttpSetOption(s->request, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) { - git_error_set(GIT_ERROR_OS, "failed to set client cert context"); - return -1; - } - } - } - - 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, - size_t buf_size, - size_t *bytes_read) -{ - winhttp_stream *s = (winhttp_stream *)stream; - winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); - DWORD dw_bytes_read; - char replay_count = 0; - int error; - -replay: - /* Enforce a reasonable cap on the number of replays */ - if (replay_count++ >= GIT_HTTP_REPLAY_MAX) { - git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - return GIT_ERROR; /* not GIT_EAUTH because the exact cause is not clear */ - } - - /* Connect if necessary */ - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - if (!s->received_response) { - DWORD status_code, status_code_length, content_type_length, bytes_written; - char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; - wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; - - if (!s->sent_request) { - - if ((error = send_request(s, s->post_body_len, false)) < 0) - return error; - - s->sent_request = 1; - } - - if (s->chunked) { - GIT_ASSERT(s->verb == post_verb); - - /* Flush, if necessary */ - if (s->chunk_buffer_len > 0 && - write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - - /* Write the final chunk. */ - if (!WinHttpWriteData(s->request, - "0\r\n\r\n", 5, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write final chunk"); - return -1; - } - } - else if (s->post_body) { - char *buffer; - DWORD len = s->post_body_len, bytes_read; - - if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body, - 0, 0, FILE_BEGIN) && - NO_ERROR != GetLastError()) { - git_error_set(GIT_ERROR_OS, "failed to reset file pointer"); - return -1; - } - - buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); - GIT_ERROR_CHECK_ALLOC(buffer); - - while (len > 0) { - DWORD bytes_written; - - if (!ReadFile(s->post_body, buffer, - min(CACHED_POST_BODY_BUF_SIZE, len), - &bytes_read, NULL) || - !bytes_read) { - git__free(buffer); - git_error_set(GIT_ERROR_OS, "failed to read from temp file"); - return -1; - } - - if (!WinHttpWriteData(s->request, buffer, - bytes_read, &bytes_written)) { - git__free(buffer); - git_error_set(GIT_ERROR_OS, "failed to write data"); - return -1; - } - - len -= bytes_read; - GIT_ASSERT(bytes_read == bytes_written); - } - - git__free(buffer); - - /* Eagerly close the temp file */ - CloseHandle(s->post_body); - s->post_body = NULL; - } - - if (!WinHttpReceiveResponse(s->request, 0)) { - git_error_set(GIT_ERROR_OS, "failed to receive response"); - return -1; - } - - /* Verify that we got a 200 back */ - status_code_length = sizeof(status_code); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &status_code, &status_code_length, - WINHTTP_NO_HEADER_INDEX)) { - git_error_set(GIT_ERROR_OS, "failed to retrieve status code"); - return -1; - } - - /* The implementation of WinHTTP prior to Windows 7 will not - * redirect to an identical URI. Some Git hosters use self-redirects - * as part of their DoS mitigation strategy. Check first to see if we - * have a redirect status code, and that we haven't already streamed - * a post body. (We can't replay a streamed POST.) */ - if (!s->chunked && - (HTTP_STATUS_MOVED == status_code || - HTTP_STATUS_REDIRECT == status_code || - (HTTP_STATUS_REDIRECT_METHOD == status_code && - get_verb == s->verb) || - 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. */ - wchar_t *location; - DWORD location_length; - char *location8; - - /* OK, fetch the Location header from the redirect. */ - if (WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_LOCATION, - WINHTTP_HEADER_NAME_BY_INDEX, - WINHTTP_NO_OUTPUT_BUFFER, - &location_length, - WINHTTP_NO_HEADER_INDEX) || - GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - git_error_set(GIT_ERROR_OS, "failed to read Location header"); - return -1; - } - - location = git__malloc(location_length); - GIT_ERROR_CHECK_ALLOC(location); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_LOCATION, - WINHTTP_HEADER_NAME_BY_INDEX, - location, - &location_length, - WINHTTP_NO_HEADER_INDEX)) { - git_error_set(GIT_ERROR_OS, "failed to read Location header"); - git__free(location); - return -1; - } - - /* Convert the Location header to UTF-8 */ - if (git__utf16_to_8_alloc(&location8, location) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8"); - git__free(location); - return -1; - } - - git__free(location); - - /* Replay the request */ - winhttp_stream_close(s); - - if (!git__prefixcmp_icase(location8, prefix_https)) { - bool follow = (t->owner->connect_opts.follow_redirects != GIT_REMOTE_REDIRECT_NONE); - - /* Upgrade to secure connection; disconnect and start over */ - if (git_net_url_apply_redirect(&t->server.url, location8, follow, s->service_url) < 0) { - git__free(location8); - return -1; - } - - winhttp_close_connection(t); - - if (winhttp_connect(t) < 0) - return -1; - } - - git__free(location8); - goto replay; - } - - /* Handle authentication failures */ - if (status_code == HTTP_STATUS_DENIED) { - int error = acquire_credentials(s->request, - &t->server, - t->owner->url, - t->owner->connect_opts.callbacks.credentials, - t->owner->connect_opts.callbacks.payload); - - if (error < 0) { - return error; - } else if (!error) { - GIT_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->connect_opts.proxy_opts.url, - t->owner->connect_opts.proxy_opts.credentials, - t->owner->connect_opts.proxy_opts.payload); - - if (error < 0) { - return error; - } else if (!error) { - GIT_ASSERT(t->proxy.cred); - winhttp_stream_close(s); - goto replay; - } - } - - if (HTTP_STATUS_OK != status_code) { - git_error_set(GIT_ERROR_HTTP, "request failed with status code: %lu", status_code); - return -1; - } - - /* Verify that we got the correct content-type back */ - if (post_verb == s->verb) - p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); - else - p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); - - if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters"); - return -1; - } - - content_type_length = sizeof(content_type); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_CONTENT_TYPE, - WINHTTP_HEADER_NAME_BY_INDEX, - &content_type, &content_type_length, - WINHTTP_NO_HEADER_INDEX)) { - git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type"); - return -1; - } - - if (wcscmp(expected_content_type, content_type)) { - git_error_set(GIT_ERROR_HTTP, "received unexpected content-type"); - return -1; - } - - s->received_response = 1; - } - - if (!WinHttpReadData(s->request, - (LPVOID)buffer, - (DWORD)buf_size, - &dw_bytes_read)) - { - git_error_set(GIT_ERROR_OS, "failed to read data"); - return -1; - } - - *bytes_read = dw_bytes_read; - - return 0; -} - -static int winhttp_stream_write_single( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - winhttp_stream *s = (winhttp_stream *)stream; - DWORD bytes_written; - int error; - - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - /* This implementation of write permits only a single call. */ - if (s->sent_request) { - git_error_set(GIT_ERROR_HTTP, "subtransport configured for only one write"); - return -1; - } - - if ((error = send_request(s, len, false)) < 0) - return error; - - s->sent_request = 1; - - if (!WinHttpWriteData(s->request, - (LPCVOID)buffer, - (DWORD)len, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write data"); - return -1; - } - - GIT_ASSERT((DWORD)len == bytes_written); - - return 0; -} - -static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch) -{ - UUID uuid; - RPC_STATUS status = UuidCreate(&uuid); - int result; - - if (RPC_S_OK != status && - RPC_S_UUID_LOCAL_ONLY != status && - RPC_S_UUID_NO_ADDRESS != status) { - 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_HTTP, "buffer too small for name of temp file"); - return -1; - } - -#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) - result = swprintf_s(buffer, buffer_len_cch, -#else - result = wsprintfW(buffer, -#endif - L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", - uuid.Data1, uuid.Data2, uuid.Data3, - uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], - uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); - - if (result < UUID_LENGTH_CCH) { - git_error_set(GIT_ERROR_OS, "unable to generate name for temp file"); - return -1; - } - - return 0; -} - -static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch) -{ - size_t len; - - if (!GetTempPathW(buffer_len_cch, buffer)) { - git_error_set(GIT_ERROR_OS, "failed to get temp path"); - return -1; - } - - len = wcslen(buffer); - - if (buffer[len - 1] != '\\' && len < buffer_len_cch) - buffer[len++] = '\\'; - - if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0) - return -1; - - return 0; -} - -static int winhttp_stream_write_buffered( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - winhttp_stream *s = (winhttp_stream *)stream; - DWORD bytes_written; - - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - /* Buffer the payload, using a temporary file so we delegate - * memory management of the data to the operating system. */ - if (!s->post_body) { - wchar_t temp_path[MAX_PATH + 1]; - - if (get_temp_file(temp_path, MAX_PATH + 1) < 0) - return -1; - - s->post_body = CreateFileW(temp_path, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_DELETE, NULL, - CREATE_NEW, - FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN, - NULL); - - if (INVALID_HANDLE_VALUE == s->post_body) { - s->post_body = NULL; - git_error_set(GIT_ERROR_OS, "failed to create temporary file"); - return -1; - } - } - - if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) { - git_error_set(GIT_ERROR_OS, "failed to write to temporary file"); - return -1; - } - - GIT_ASSERT((DWORD)len == bytes_written); - - s->post_body_len += bytes_written; - - return 0; -} - -static int winhttp_stream_write_chunked( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - winhttp_stream *s = (winhttp_stream *)stream; - int error; - - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - if (!s->sent_request) { - /* Send Transfer-Encoding: chunked header */ - if (!WinHttpAddRequestHeaders(s->request, - transfer_encoding, (ULONG) -1L, - WINHTTP_ADDREQ_FLAG_ADD)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - return -1; - } - - if ((error = send_request(s, 0, true)) < 0) - return error; - - s->sent_request = 1; - } - - if (len > CACHED_POST_BODY_BUF_SIZE) { - /* Flush, if necessary */ - if (s->chunk_buffer_len > 0) { - if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - } - - /* Write chunk directly */ - if (write_chunk(s->request, buffer, len) < 0) - return -1; - } - else { - /* 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) { - 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; - buffer += count; - len -= count; - - /* Is the buffer full? If so, then flush */ - if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) { - if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - - /* Is there any remaining data from the source? */ - if (len > 0) { - memcpy(s->chunk_buffer, buffer, len); - s->chunk_buffer_len = (unsigned int)len; - } - } - } - - return 0; -} - -static void winhttp_stream_free(git_smart_subtransport_stream *stream) -{ - winhttp_stream *s = (winhttp_stream *)stream; - - winhttp_stream_close(s); - git__free(s); -} - -static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream) -{ - winhttp_stream *s; - - if (!stream) - return -1; - - s = git__calloc(1, sizeof(winhttp_stream)); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = winhttp_stream_read; - s->parent.write = winhttp_stream_write_single; - s->parent.free = winhttp_stream_free; - - *stream = s; - - return 0; -} - -static int winhttp_uploadpack_ls( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - s->service = upload_pack_service; - s->service_url = upload_pack_ls_service_url; - s->verb = get_verb; - - return 0; -} - -static int winhttp_uploadpack( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - s->service = upload_pack_service; - s->service_url = upload_pack_service_url; - s->verb = post_verb; - - return 0; -} - -static int winhttp_receivepack_ls( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - s->service = receive_pack_service; - s->service_url = receive_pack_ls_service_url; - s->verb = get_verb; - - return 0; -} - -static int winhttp_receivepack( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - /* WinHTTP only supports Transfer-Encoding: chunked - * on Windows Vista (NT 6.0) and higher. */ - s->chunked = git_has_win32_version(6, 0, 0); - - if (s->chunked) - s->parent.write = winhttp_stream_write_chunked; - else - s->parent.write = winhttp_stream_write_buffered; - - s->service = receive_pack_service; - s->service_url = receive_pack_service_url; - s->verb = post_verb; - - return 0; -} - -static int winhttp_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - winhttp_stream *s; - int ret = -1; - - if (!t->connection) - if ((ret = git_net_url_parse(&t->server.url, url)) < 0 || - (ret = winhttp_connect(t)) < 0) - return ret; - - if (winhttp_stream_alloc(t, &s) < 0) - return -1; - - if (!stream) - return -1; - - switch (action) - { - case GIT_SERVICE_UPLOADPACK_LS: - ret = winhttp_uploadpack_ls(t, s); - break; - - case GIT_SERVICE_UPLOADPACK: - ret = winhttp_uploadpack(t, s); - break; - - case GIT_SERVICE_RECEIVEPACK_LS: - ret = winhttp_receivepack_ls(t, s); - break; - - case GIT_SERVICE_RECEIVEPACK: - ret = winhttp_receivepack(t, s); - break; - - default: - GIT_ASSERT(0); - } - - if (!ret) - *stream = &s->parent; - - return ret; -} - -static int winhttp_close(git_smart_subtransport *subtransport) -{ - winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - - git_net_url_dispose(&t->server.url); - git_net_url_dispose(&t->proxy.url); - - 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; - } - - return winhttp_close_connection(t); -} - -static void winhttp_free(git_smart_subtransport *subtransport) -{ - winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - - winhttp_close(subtransport); - - git__free(t); -} - -int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) -{ - winhttp_subtransport *t; - - GIT_UNUSED(param); - - if (!out) - return -1; - - t = git__calloc(1, sizeof(winhttp_subtransport)); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = (transport_smart *)owner; - t->parent.action = winhttp_action; - t->parent.close = winhttp_close; - t->parent.free = winhttp_free; - - *out = (git_smart_subtransport *) t; - return 0; -} - -#endif /* GIT_WINHTTP */ diff --git a/src/tree-cache.c b/src/tree-cache.c deleted file mode 100644 index 0977c92f3..000000000 --- a/src/tree-cache.c +++ /dev/null @@ -1,277 +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 "tree-cache.h" - -#include "pool.h" -#include "tree.h" - -static git_tree_cache *find_child( - const git_tree_cache *tree, const char *path, const char *end) -{ - size_t i, dirlen = end ? (size_t)(end - path) : strlen(path); - - for (i = 0; i < tree->children_count; ++i) { - git_tree_cache *child = tree->children[i]; - - if (child->namelen == dirlen && !memcmp(path, child->name, dirlen)) - return child; - } - - return NULL; -} - -void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path) -{ - const char *ptr = path, *end; - - if (tree == NULL) - return; - - tree->entry_count = -1; - - while (ptr != NULL) { - end = strchr(ptr, '/'); - - if (end == NULL) /* End of path */ - break; - - tree = find_child(tree, ptr, end); - if (tree == NULL) /* We don't have that tree */ - return; - - tree->entry_count = -1; - ptr = end + 1; - } -} - -const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path) -{ - const char *ptr = path, *end; - - if (tree == NULL) { - return NULL; - } - - while (1) { - end = strchr(ptr, '/'); - - tree = find_child(tree, ptr, end); - if (tree == NULL) /* Can't find it */ - return NULL; - - if (end == NULL || *end + 1 == '\0') - return tree; - - ptr = end + 1; - } -} - -static int read_tree_internal(git_tree_cache **out, - const char **buffer_in, const char *buffer_end, - git_pool *pool) -{ - git_tree_cache *tree = NULL; - const char *name_start, *buffer; - int count; - - buffer = name_start = *buffer_in; - - if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) - goto corrupted; - - if (++buffer >= buffer_end) - goto corrupted; - - if (git_tree_cache_new(&tree, name_start, pool) < 0) - return -1; - - /* Blank-terminated ASCII decimal number of entries in this tree */ - if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0) - goto corrupted; - - tree->entry_count = count; - - if (*buffer != ' ' || ++buffer >= buffer_end) - goto corrupted; - - /* Number of children of the tree, newline-terminated */ - if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0 || count < 0) - goto corrupted; - - tree->children_count = count; - - if (*buffer != '\n' || ++buffer > buffer_end) - goto corrupted; - - /* The SHA1 is only there if it's not invalidated */ - if (tree->entry_count >= 0) { - /* 160-bit SHA-1 for this tree and it's children */ - if (buffer + GIT_OID_RAWSZ > buffer_end) - goto corrupted; - - git_oid_fromraw(&tree->oid, (const unsigned char *)buffer); - buffer += GIT_OID_RAWSZ; - } - - /* Parse children: */ - if (tree->children_count > 0) { - size_t i, bufsize; - - 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, bufsize); - - for (i = 0; i < tree->children_count; ++i) { - if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0) - goto corrupted; - } - } - - *buffer_in = buffer; - *out = tree; - return 0; - - corrupted: - git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index"); - return -1; -} - -int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool) -{ - const char *buffer_end = buffer + buffer_size; - - if (read_tree_internal(tree, &buffer, buffer_end, pool) < 0) - return -1; - - if (buffer < buffer_end) { - git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index (unexpected trailing data)"); - return -1; - } - - return 0; -} - -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, alloc_size; - int error; - - repo = git_tree_owner(tree); - - git_oid_cpy(&cache->oid, git_tree_id(tree)); - nentries = git_tree_entrycount(tree); - - /* - * We make sure we know how many trees we need to allocate for - * so we don't have to realloc and change the pointers for the - * parents. - */ - ntrees = 0; - for (i = 0; i < nentries; i++) { - const git_tree_entry *entry; - - entry = git_tree_entry_byindex(tree, i); - if (git_tree_entry_filemode(entry) == GIT_FILEMODE_TREE) - ntrees++; - } - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_size, ntrees, sizeof(git_tree_cache *)); - - cache->children_count = ntrees; - cache->children = git_pool_mallocz(pool, alloc_size); - GIT_ERROR_CHECK_ALLOC(cache->children); - - j = 0; - for (i = 0; i < nentries; i++) { - const git_tree_entry *entry; - git_tree *subtree; - - entry = git_tree_entry_byindex(tree, i); - if (git_tree_entry_filemode(entry) != GIT_FILEMODE_TREE) { - cache->entry_count++; - continue; - } - - if ((error = git_tree_cache_new(&cache->children[j], git_tree_entry_name(entry), pool)) < 0) - return error; - - if ((error = git_tree_lookup(&subtree, repo, git_tree_entry_id(entry))) < 0) - return error; - - error = read_tree_recursive(cache->children[j], subtree, pool); - git_tree_free(subtree); - cache->entry_count += cache->children[j]->entry_count; - j++; - - if (error < 0) - return error; - } - - return 0; -} - -int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool) -{ - int error; - git_tree_cache *cache; - - if ((error = git_tree_cache_new(&cache, "", pool)) < 0) - return error; - - if ((error = read_tree_recursive(cache, tree, pool)) < 0) - return error; - - *out = cache; - return 0; -} - -int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool) -{ - size_t name_len, alloc_size; - git_tree_cache *tree; - - name_len = strlen(name); - - 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)); - /* NUL-terminated tree name */ - tree->namelen = name_len; - memcpy(tree->name, name, name_len); - tree->name[name_len] = '\0'; - - *out = tree; - return 0; -} - -static void write_tree(git_str *out, git_tree_cache *tree) -{ - size_t i; - - git_str_printf(out, "%s%c%"PRIdZ" %"PRIuZ"\n", tree->name, 0, tree->entry_count, tree->children_count); - - if (tree->entry_count != -1) - git_str_put(out, (const char *) &tree->oid, GIT_OID_RAWSZ); - - for (i = 0; i < tree->children_count; i++) - write_tree(out, tree->children[i]); -} - -int git_tree_cache_write(git_str *out, git_tree_cache *tree) -{ - write_tree(out, tree); - - return git_str_oom(out) ? -1 : 0; -} diff --git a/src/tree-cache.h b/src/tree-cache.h deleted file mode 100644 index a27e30466..000000000 --- a/src/tree-cache.h +++ /dev/null @@ -1,38 +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_tree_cache_h__ -#define INCLUDE_tree_cache_h__ - -#include "common.h" - -#include "pool.h" -#include "str.h" -#include "git2/oid.h" - -typedef struct git_tree_cache { - struct git_tree_cache **children; - size_t children_count; - - ssize_t entry_count; - git_oid oid; - size_t namelen; - char name[GIT_FLEX_ARRAY]; -} git_tree_cache; - -int git_tree_cache_write(git_str *out, git_tree_cache *tree); -int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool); -void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path); -const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path); -int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool); -/** - * Read a tree as the root of the tree cache (like for `git read-tree`) - */ -int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool); -void git_tree_cache_free(git_tree_cache *tree); - -#endif diff --git a/src/tree.c b/src/tree.c deleted file mode 100644 index a1545dc2d..000000000 --- a/src/tree.c +++ /dev/null @@ -1,1331 +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 "tree.h" - -#include "commit.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "futils.h" -#include "tree-cache.h" -#include "index.h" -#include "path.h" - -#define DEFAULT_TREE_SIZE 16 -#define MAX_FILEMODE_BYTES 6 - -#define TREE_ENTRY_CHECK_NAMELEN(n) \ - if (n > UINT16_MAX) { git_error_set(GIT_ERROR_INVALID, "tree entry path too long"); } - -static bool valid_filemode(const int filemode) -{ - return (filemode == GIT_FILEMODE_TREE - || filemode == GIT_FILEMODE_BLOB - || filemode == GIT_FILEMODE_BLOB_EXECUTABLE - || filemode == GIT_FILEMODE_LINK - || filemode == GIT_FILEMODE_COMMIT); -} - -GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) -{ - /* Tree bits set, but it's not a commit */ - if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE) - return GIT_FILEMODE_TREE; - - /* If any of the x bits are set */ - if (GIT_PERMS_IS_EXEC(filemode)) - return GIT_FILEMODE_BLOB_EXECUTABLE; - - /* 16XXXX means commit */ - if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT) - return GIT_FILEMODE_COMMIT; - - /* 12XXXX means symlink */ - if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK) - return GIT_FILEMODE_LINK; - - /* Otherwise, return a blob */ - return GIT_FILEMODE_BLOB; -} - -static int valid_entry_name(git_repository *repo, const char *filename) -{ - return *filename != '\0' && - git_path_is_valid(repo, filename, 0, - GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_FS_PATH_REJECT_SLASH); -} - -static int entry_sort_cmp(const void *a, const void *b) -{ - const git_tree_entry *e1 = (const git_tree_entry *)a; - const git_tree_entry *e2 = (const git_tree_entry *)b; - - return git_fs_path_cmp( - e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), - e2->filename, e2->filename_len, git_tree_entry__is_tree(e2), - git__strncmp); -} - -int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) -{ - return entry_sort_cmp(e1, e2); -} - -/** - * Allocate a new self-contained entry, with enough space after it to - * store the filename and the id. - */ -static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id) -{ - git_tree_entry *entry = NULL; - size_t tree_len; - - TREE_ENTRY_CHECK_NAMELEN(filename_len); - - if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) || - GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) || - GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ)) - return NULL; - - entry = git__calloc(1, tree_len); - if (!entry) - return NULL; - - { - char *filename_ptr; - void *id_ptr; - - filename_ptr = ((char *) entry) + sizeof(git_tree_entry); - memcpy(filename_ptr, filename, filename_len); - entry->filename = filename_ptr; - - id_ptr = filename_ptr + filename_len + 1; - git_oid_cpy(id_ptr, id); - entry->oid = id_ptr; - } - - entry->filename_len = (uint16_t)filename_len; - - return entry; -} - -struct tree_key_search { - const char *filename; - uint16_t filename_len; -}; - -static int homing_search_cmp(const void *key, const void *array_member) -{ - const struct tree_key_search *ksearch = key; - const git_tree_entry *entry = array_member; - - const uint16_t len1 = ksearch->filename_len; - const uint16_t len2 = entry->filename_len; - - return memcmp( - ksearch->filename, - entry->filename, - len1 < len2 ? len1 : len2 - ); -} - -/* - * Search for an entry in a given tree. - * - * Note that this search is performed in two steps because - * of the way tree entries are sorted internally in git: - * - * Entries in a tree are not sorted alphabetically; two entries - * with the same root prefix will have different positions - * depending on whether they are folders (subtrees) or normal files. - * - * Consequently, it is not possible to find an entry on the tree - * with a binary search if you don't know whether the filename - * you're looking for is a folder or a normal file. - * - * To work around this, we first perform a homing binary search - * on the tree, using the minimal length root prefix of our filename. - * Once the comparisons for this homing search start becoming - * ambiguous because of folder vs file sorting, we look linearly - * around the area for our target file. - */ -static int tree_key_search( - size_t *at_pos, - const git_tree *tree, - const char *filename, - size_t filename_len) -{ - struct tree_key_search ksearch; - const git_tree_entry *entry; - size_t homing, i; - - TREE_ENTRY_CHECK_NAMELEN(filename_len); - - ksearch.filename = filename; - ksearch.filename_len = (uint16_t)filename_len; - - /* Initial homing search; find an entry on the tree with - * the same prefix as the filename we're looking for */ - - if (git_array_search(&homing, - tree->entries, &homing_search_cmp, &ksearch) < 0) - return GIT_ENOTFOUND; /* just a signal error; not passed back to user */ - - /* We found a common prefix. Look forward as long as - * there are entries that share the common prefix */ - for (i = homing; i < tree->entries.size; ++i) { - entry = git_array_get(tree->entries, i); - - if (homing_search_cmp(&ksearch, entry) < 0) - break; - - if (entry->filename_len == filename_len && - memcmp(filename, entry->filename, filename_len) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } - - /* If we haven't found our filename yet, look backwards - * too as long as we have entries with the same prefix */ - if (homing > 0) { - i = homing - 1; - - do { - entry = git_array_get(tree->entries, i); - - if (homing_search_cmp(&ksearch, entry) > 0) - break; - - if (entry->filename_len == filename_len && - memcmp(filename, entry->filename, filename_len) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } while (i-- > 0); - } - - /* The filename doesn't exist at all */ - return GIT_ENOTFOUND; -} - -void git_tree_entry_free(git_tree_entry *entry) -{ - if (entry == NULL) - return; - - git__free(entry); -} - -int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source) -{ - git_tree_entry *cpy; - - GIT_ASSERT_ARG(source); - - cpy = alloc_entry(source->filename, source->filename_len, source->oid); - if (cpy == NULL) - return -1; - - cpy->attr = source->attr; - - *dest = cpy; - return 0; -} - -void git_tree__free(void *_tree) -{ - git_tree *tree = _tree; - - git_odb_object_free(tree->odb_obj); - git_array_clear(tree->entries); - git__free(tree); -} - -git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) -{ - return normalize_filemode(entry->attr); -} - -git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry) -{ - return entry->attr; -} - -const char *git_tree_entry_name(const git_tree_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->filename; -} - -const git_oid *git_tree_entry_id(const git_tree_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->oid; -} - -git_object_t git_tree_entry_type(const git_tree_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, GIT_OBJECT_INVALID); - - if (S_ISGITLINK(entry->attr)) - return GIT_OBJECT_COMMIT; - else if (S_ISDIR(entry->attr)) - return GIT_OBJECT_TREE; - else - return GIT_OBJECT_BLOB; -} - -int git_tree_entry_to_object( - git_object **object_out, - git_repository *repo, - const git_tree_entry *entry) -{ - GIT_ASSERT_ARG(entry); - GIT_ASSERT_ARG(object_out); - - return git_object_lookup(object_out, repo, entry->oid, GIT_OBJECT_ANY); -} - -static const git_tree_entry *entry_fromname( - const git_tree *tree, const char *name, size_t name_len) -{ - size_t idx; - - if (tree_key_search(&idx, tree, name, name_len) < 0) - return NULL; - - return git_array_get(tree->entries, idx); -} - -const git_tree_entry *git_tree_entry_byname( - const git_tree *tree, const char *filename) -{ - GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); - - return entry_fromname(tree, filename, strlen(filename)); -} - -const git_tree_entry *git_tree_entry_byindex( - const git_tree *tree, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); - return git_array_get(tree->entries, idx); -} - -const git_tree_entry *git_tree_entry_byid( - const git_tree *tree, const git_oid *id) -{ - size_t i; - const git_tree_entry *e; - - GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); - - git_array_foreach(tree->entries, i, e) { - if (memcmp(&e->oid->id, &id->id, sizeof(id->id)) == 0) - return e; - } - - return NULL; -} - -size_t git_tree_entrycount(const git_tree *tree) -{ - GIT_ASSERT_ARG_WITH_RETVAL(tree, 0); - return tree->entries.size; -} - -size_t git_treebuilder_entrycount(git_treebuilder *bld) -{ - GIT_ASSERT_ARG_WITH_RETVAL(bld, 0); - - return git_strmap_size(bld->map); -} - -GIT_INLINE(void) set_error(const char *str, const char *path) -{ - if (path) - git_error_set(GIT_ERROR_TREE, "%s - %s", str, path); - else - git_error_set(GIT_ERROR_TREE, "%s", str); -} - -static int tree_error(const char *str, const char *path) -{ - set_error(str, path); - return -1; -} - -static int tree_parse_error(const char *str, const char *path) -{ - set_error(str, path); - return GIT_EINVALID; -} - -static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out) -{ - int32_t mode; - int error; - - if (!buffer_len || git__isspace(*buffer)) - return -1; - - if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0) - return error; - - if (mode < 0 || mode > UINT16_MAX) - return -1; - - *mode_out = mode; - - return 0; -} - -int git_tree__parse_raw(void *_tree, const char *data, size_t size) -{ - git_tree *tree = _tree; - const char *buffer; - const char *buffer_end; - - buffer = data; - buffer_end = buffer + size; - - tree->odb_obj = NULL; - git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE); - GIT_ERROR_CHECK_ARRAY(tree->entries); - - while (buffer < buffer_end) { - git_tree_entry *entry; - size_t filename_len; - const char *nul; - uint16_t attr; - - if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer) - return tree_parse_error("failed to parse tree: can't parse filemode", NULL); - - if (buffer >= buffer_end || (*buffer++) != ' ') - return tree_parse_error("failed to parse tree: missing space after filemode", NULL); - - if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL) - return tree_parse_error("failed to parse tree: object is corrupted", NULL); - - if ((filename_len = nul - buffer) == 0 || filename_len > UINT16_MAX) - return tree_parse_error("failed to parse tree: can't parse filename", NULL); - - if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ) - return tree_parse_error("failed to parse tree: can't parse OID", NULL); - - /* Allocate the entry */ - { - entry = git_array_alloc(tree->entries); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->attr = attr; - entry->filename_len = (uint16_t)filename_len; - entry->filename = buffer; - entry->oid = (git_oid *) ((char *) buffer + filename_len + 1); - } - - buffer += filename_len + 1; - buffer += GIT_OID_RAWSZ; - } - - return 0; -} - -int git_tree__parse(void *_tree, git_odb_object *odb_obj) -{ - git_tree *tree = _tree; - const char *data = git_odb_object_data(odb_obj); - size_t size = git_odb_object_size(odb_obj); - int error; - - if ((error = git_tree__parse_raw(tree, data, size)) < 0 || - (error = git_odb_object_dup(&tree->odb_obj, odb_obj)) < 0) - return error; - - return error; -} - -static size_t find_next_dir(const char *dirname, git_index *index, size_t start) -{ - size_t dirlen, i, entries = git_index_entrycount(index); - - dirlen = strlen(dirname); - for (i = start; i < entries; ++i) { - const git_index_entry *entry = git_index_get_byindex(index, i); - if (strlen(entry->path) < dirlen || - memcmp(entry->path, dirname, dirlen) || - (dirlen > 0 && entry->path[dirlen] != '/')) { - break; - } - } - - return i; -} - -static git_object_t otype_from_mode(git_filemode_t filemode) -{ - switch (filemode) { - case GIT_FILEMODE_TREE: - return GIT_OBJECT_TREE; - case GIT_FILEMODE_COMMIT: - return GIT_OBJECT_COMMIT; - default: - return GIT_OBJECT_BLOB; - } -} - -static int check_entry(git_repository *repo, const char *filename, const git_oid *id, git_filemode_t filemode) -{ - if (!valid_filemode(filemode)) - return tree_error("failed to insert entry: invalid filemode for file", filename); - - if (!valid_entry_name(repo, filename)) - return tree_error("failed to insert entry: invalid name for a tree entry", filename); - - if (git_oid_is_zero(id)) - return tree_error("failed to insert entry: invalid null OID", filename); - - if (filemode != GIT_FILEMODE_COMMIT && - !git_object__is_valid(repo, id, otype_from_mode(filemode))) - return tree_error("failed to insert entry: invalid object specified", filename); - - return 0; -} - -static int git_treebuilder__write_with_buffer( - git_oid *oid, - git_treebuilder *bld, - git_str *buf) -{ - int error = 0; - size_t i, entrycount; - git_odb *odb; - git_tree_entry *entry; - git_vector entries = GIT_VECTOR_INIT; - - git_str_clear(buf); - - entrycount = git_strmap_size(bld->map); - if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0) - goto out; - - if (buf->asize == 0 && - (error = git_str_grow(buf, entrycount * 72)) < 0) - goto out; - - git_strmap_foreach_value(bld->map, entry, { - if ((error = git_vector_insert(&entries, entry)) < 0) - goto out; - }); - - git_vector_sort(&entries); - - for (i = 0; i < entries.length && !error; ++i) { - entry = git_vector_get(&entries, i); - - git_str_printf(buf, "%o ", entry->attr); - git_str_put(buf, entry->filename, entry->filename_len + 1); - git_str_put(buf, (char *)entry->oid->id, GIT_OID_RAWSZ); - - if (git_str_oom(buf)) { - error = -1; - goto out; - } - } - - if ((error = git_repository_odb__weakptr(&odb, bld->repo)) == 0) - error = git_odb_write(oid, odb, buf->ptr, buf->size, GIT_OBJECT_TREE); - -out: - git_vector_free(&entries); - - return error; -} - -static int append_entry( - git_treebuilder *bld, - const char *filename, - const git_oid *id, - git_filemode_t filemode, - bool validate) -{ - git_tree_entry *entry; - int error = 0; - - if (validate && ((error = check_entry(bld->repo, filename, id, filemode)) < 0)) - return error; - - entry = alloc_entry(filename, strlen(filename), id); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->attr = (uint16_t)filemode; - - 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; - } - - return 0; -} - -static int write_tree( - git_oid *oid, - git_repository *repo, - git_index *index, - const char *dirname, - size_t start, - git_str *shared_buf) -{ - git_treebuilder *bld = NULL; - size_t i, entries = git_index_entrycount(index); - int error; - size_t dirname_len = strlen(dirname); - const git_tree_cache *cache; - - cache = git_tree_cache_get(index->tree, dirname); - if (cache != NULL && cache->entry_count >= 0){ - git_oid_cpy(oid, &cache->oid); - return (int)find_next_dir(dirname, index, start); - } - - if ((error = git_treebuilder_new(&bld, repo, NULL)) < 0 || bld == NULL) - return -1; - - /* - * This loop is unfortunate, but necessary. The index doesn't have - * any directories, so we need to handle that manually, and we - * need to keep track of the current position. - */ - for (i = start; i < entries; ++i) { - const git_index_entry *entry = git_index_get_byindex(index, i); - const char *filename, *next_slash; - - /* - * If we've left our (sub)tree, exit the loop and return. The - * first check is an early out (and security for the - * third). The second check is a simple prefix comparison. The - * third check catches situations where there is a directory - * win32/sys and a file win32mmap.c. Without it, the following - * code believes there is a file win32/mmap.c - */ - if (strlen(entry->path) < dirname_len || - memcmp(entry->path, dirname, dirname_len) || - (dirname_len > 0 && entry->path[dirname_len] != '/')) { - break; - } - - filename = entry->path + dirname_len; - if (*filename == '/') - filename++; - next_slash = strchr(filename, '/'); - if (next_slash) { - git_oid sub_oid; - int written; - char *subdir, *last_comp; - - subdir = git__strndup(entry->path, next_slash - entry->path); - GIT_ERROR_CHECK_ALLOC(subdir); - - /* Write out the subtree */ - written = write_tree(&sub_oid, repo, index, subdir, i, shared_buf); - if (written < 0) { - git__free(subdir); - goto on_error; - } else { - i = written - 1; /* -1 because of the loop increment */ - } - - /* - * We need to figure out what we want toinsert - * into this tree. If we're traversing - * deps/zlib/, then we only want to write - * 'zlib' into the tree. - */ - last_comp = strrchr(subdir, '/'); - if (last_comp) { - last_comp++; /* Get rid of the '/' */ - } else { - last_comp = subdir; - } - - error = append_entry(bld, last_comp, &sub_oid, S_IFDIR, true); - git__free(subdir); - if (error < 0) - goto on_error; - } else { - error = append_entry(bld, filename, &entry->id, entry->mode, true); - if (error < 0) - goto on_error; - } - } - - if (git_treebuilder__write_with_buffer(oid, bld, shared_buf) < 0) - goto on_error; - - git_treebuilder_free(bld); - return (int)i; - -on_error: - git_treebuilder_free(bld); - return -1; -} - -int git_tree__write_index( - git_oid *oid, git_index *index, git_repository *repo) -{ - int ret; - git_tree *tree; - git_str shared_buf = GIT_STR_INIT; - bool old_ignore_case = false; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(repo); - - if (git_index_has_conflicts(index)) { - git_error_set(GIT_ERROR_INDEX, - "cannot create a tree from a not fully merged index."); - return GIT_EUNMERGED; - } - - if (index->tree != NULL && index->tree->entry_count >= 0) { - git_oid_cpy(oid, &index->tree->oid); - return 0; - } - - /* The tree cache didn't help us; we'll have to write - * out a tree. If the index is ignore_case, we must - * make it case-sensitive for the duration of the tree-write - * operation. */ - - if (index->ignore_case) { - old_ignore_case = true; - git_index__set_ignore_case(index, false); - } - - ret = write_tree(oid, repo, index, "", 0, &shared_buf); - git_str_dispose(&shared_buf); - - if (old_ignore_case) - git_index__set_ignore_case(index, true); - - index->tree = NULL; - - if (ret < 0) - return ret; - - git_pool_clear(&index->tree_pool); - - if ((ret = git_tree_lookup(&tree, repo, oid)) < 0) - return ret; - - /* Read the tree cache into the index */ - ret = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); - git_tree_free(tree); - - return ret; -} - -int git_treebuilder_new( - git_treebuilder **builder_p, - git_repository *repo, - const git_tree *source) -{ - git_treebuilder *bld; - size_t i; - - GIT_ASSERT_ARG(builder_p); - GIT_ASSERT_ARG(repo); - - bld = git__calloc(1, sizeof(git_treebuilder)); - GIT_ERROR_CHECK_ALLOC(bld); - - bld->repo = repo; - - if (git_strmap_new(&bld->map) < 0) { - git__free(bld); - return -1; - } - - if (source != NULL) { - git_tree_entry *entry_src; - - git_array_foreach(source->entries, i, entry_src) { - if (append_entry( - bld, entry_src->filename, - entry_src->oid, - entry_src->attr, - false) < 0) - goto on_error; - } - } - - *builder_p = bld; - return 0; - -on_error: - git_treebuilder_free(bld); - return -1; -} - -int git_treebuilder_insert( - const git_tree_entry **entry_out, - git_treebuilder *bld, - const char *filename, - const git_oid *id, - git_filemode_t filemode) -{ - git_tree_entry *entry; - int error; - - GIT_ASSERT_ARG(bld); - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(filename); - - if ((error = check_entry(bld->repo, filename, id, filemode)) < 0) - return error; - - 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); - - 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; - } - } - - entry->attr = filemode; - - if (entry_out) - *entry_out = entry; - - return 0; -} - -static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) -{ - GIT_ASSERT_ARG_WITH_RETVAL(bld, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); - - return git_strmap_get(bld->map, filename); -} - -const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) -{ - return treebuilder_get(bld, filename); -} - -int git_treebuilder_remove(git_treebuilder *bld, const char *filename) -{ - git_tree_entry *entry = treebuilder_get(bld, filename); - - if (entry == NULL) - return tree_error("failed to remove entry: file isn't in the tree", filename); - - git_strmap_delete(bld->map, filename); - git_tree_entry_free(entry); - - return 0; -} - -int git_treebuilder_write(git_oid *oid, git_treebuilder *bld) -{ - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(bld); - - return git_treebuilder__write_with_buffer(oid, bld, &bld->write_cache); -} - -int git_treebuilder_filter( - git_treebuilder *bld, - git_treebuilder_filter_cb filter, - void *payload) -{ - const char *filename; - git_tree_entry *entry; - - GIT_ASSERT_ARG(bld); - GIT_ASSERT_ARG(filter); - - git_strmap_foreach(bld->map, filename, entry, { - if (filter(entry, payload)) { - git_strmap_delete(bld->map, filename); - git_tree_entry_free(entry); - } - }); - - return 0; -} - -int git_treebuilder_clear(git_treebuilder *bld) -{ - git_tree_entry *e; - - GIT_ASSERT_ARG(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) -{ - if (bld == NULL) - return; - - git_str_dispose(&bld->write_cache); - git_treebuilder_clear(bld); - git_strmap_free(bld->map); - git__free(bld); -} - -static size_t subpath_len(const char *path) -{ - const char *slash_pos = strchr(path, '/'); - if (slash_pos == NULL) - return strlen(path); - - return slash_pos - path; -} - -int git_tree_entry_bypath( - git_tree_entry **entry_out, - const git_tree *root, - const char *path) -{ - int error = 0; - git_tree *subtree; - const git_tree_entry *entry; - size_t filename_len; - - /* Find how long is the current path component (i.e. - * the filename between two slashes */ - filename_len = subpath_len(path); - - if (filename_len == 0) { - git_error_set(GIT_ERROR_TREE, "invalid tree path given"); - return GIT_ENOTFOUND; - } - - entry = entry_fromname(root, path, filename_len); - - if (entry == NULL) { - git_error_set(GIT_ERROR_TREE, - "the path '%.*s' does not exist in the given tree", (int) filename_len, path); - return GIT_ENOTFOUND; - } - - switch (path[filename_len]) { - case '/': - /* If there are more components in the path... - * then this entry *must* be a tree */ - if (!git_tree_entry__is_tree(entry)) { - git_error_set(GIT_ERROR_TREE, - "the path '%.*s' exists but is not a tree", (int) filename_len, path); - return GIT_ENOTFOUND; - } - - /* If there's only a slash left in the path, we - * return the current entry; otherwise, we keep - * walking down the path */ - if (path[filename_len + 1] != '\0') - break; - /* fall through */ - case '\0': - /* If there are no more components in the path, return - * this entry */ - return git_tree_entry_dup(entry_out, entry); - } - - if (git_tree_lookup(&subtree, root->object.repo, entry->oid) < 0) - return -1; - - error = git_tree_entry_bypath( - entry_out, - subtree, - path + filename_len + 1 - ); - - git_tree_free(subtree); - return error; -} - -static int tree_walk( - const git_tree *tree, - git_treewalk_cb callback, - git_str *path, - void *payload, - bool preorder) -{ - int error = 0; - size_t i; - const git_tree_entry *entry; - - git_array_foreach(tree->entries, i, entry) { - if (preorder) { - error = callback(path->ptr, entry, payload); - if (error < 0) { /* negative value stops iteration */ - git_error_set_after_callback_function(error, "git_tree_walk"); - break; - } - if (error > 0) { /* positive value skips this entry */ - error = 0; - continue; - } - } - - if (git_tree_entry__is_tree(entry)) { - git_tree *subtree; - size_t path_len = git_str_len(path); - - error = git_tree_lookup(&subtree, tree->object.repo, entry->oid); - if (error < 0) - break; - - /* append the next entry to the path */ - git_str_puts(path, entry->filename); - git_str_putc(path, '/'); - - if (git_str_oom(path)) - error = -1; - else - error = tree_walk(subtree, callback, path, payload, preorder); - - git_tree_free(subtree); - if (error != 0) - break; - - git_str_truncate(path, path_len); - } - - if (!preorder) { - error = callback(path->ptr, entry, payload); - if (error < 0) { /* negative value stops iteration */ - git_error_set_after_callback_function(error, "git_tree_walk"); - break; - } - error = 0; - } - } - - return error; -} - -int git_tree_walk( - const git_tree *tree, - git_treewalk_mode mode, - git_treewalk_cb callback, - void *payload) -{ - int error = 0; - git_str root_path = GIT_STR_INIT; - - if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) { - git_error_set(GIT_ERROR_INVALID, "invalid walking mode for tree walk"); - return -1; - } - - error = tree_walk( - tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE)); - - git_str_dispose(&root_path); - - return error; -} - -static int compare_entries(const void *_a, const void *_b) -{ - const git_tree_update *a = (git_tree_update *) _a; - const git_tree_update *b = (git_tree_update *) _b; - - return strcmp(a->path, b->path); -} - -static int on_dup_entry(void **old, void *new) -{ - GIT_UNUSED(old); GIT_UNUSED(new); - - git_error_set(GIT_ERROR_TREE, "duplicate entries given for update"); - return -1; -} - -/* - * We keep the previous tree and the new one at each level of the - * stack. When we leave a level we're done with that tree and we can - * write it out to the odb. - */ -typedef struct { - git_treebuilder *bld; - git_tree *tree; - char *name; -} tree_stack_entry; - -/** Count how many slashes (i.e. path components) there are in this string */ -GIT_INLINE(size_t) count_slashes(const char *path) -{ - size_t count = 0; - const char *slash; - - while ((slash = strchr(path, '/')) != NULL) { - count++; - path = slash + 1; - } - - return count; -} - -static bool next_component(git_str *out, const char *in) -{ - const char *slash = strchr(in, '/'); - - git_str_clear(out); - - if (slash) - git_str_put(out, in, slash - in); - - return !!slash; -} - -static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_str *component) -{ - int error; - git_oid new_tree; - - git_tree_free(popped->tree); - - /* If the tree would be empty, remove it from the one higher up */ - if (git_treebuilder_entrycount(popped->bld) == 0) { - git_treebuilder_free(popped->bld); - error = git_treebuilder_remove(current->bld, popped->name); - git__free(popped->name); - return error; - } - - error = git_treebuilder_write(&new_tree, popped->bld); - git_treebuilder_free(popped->bld); - - if (error < 0) { - git__free(popped->name); - return error; - } - - /* We've written out the tree, now we have to put the new value into its parent */ - git_str_clear(component); - git_str_puts(component, popped->name); - git__free(popped->name); - - GIT_ERROR_CHECK_ALLOC(component->ptr); - - /* Error out if this would create a D/F conflict in this update */ - if (current->tree) { - const git_tree_entry *to_replace; - to_replace = git_tree_entry_byname(current->tree, component->ptr); - if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJECT_TREE) { - git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); - return -1; - } - } - - return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE); -} - -int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates) -{ - git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT; - tree_stack_entry *root_elem; - git_vector entries; - int error; - size_t i; - git_str component = GIT_STR_INIT; - - if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0) - return error; - - /* Sort the entries for treversal */ - for (i = 0 ; i < nupdates; i++) { - if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0) - goto cleanup; - } - - root_elem = git_array_alloc(stack); - GIT_ERROR_CHECK_ALLOC(root_elem); - memset(root_elem, 0, sizeof(*root_elem)); - - if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0) - goto cleanup; - - if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0) - goto cleanup; - - for (i = 0; i < nupdates; i++) { - const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1); - const git_tree_update *update = git_vector_get(&entries, i); - size_t common_prefix = 0, steps_up, j; - const char *path; - - /* Figure out how much we need to change from the previous tree */ - if (last_update) - common_prefix = git_fs_path_common_dirlen(last_update->path, update->path); - - /* - * The entries are sorted, so when we find we're no - * longer in the same directory, we need to abandon - * the old tree (steps up) and dive down to the next - * one. - */ - steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]); - - for (j = 0; j < steps_up; j++) { - tree_stack_entry *current, *popped = git_array_pop(stack); - GIT_ASSERT(popped); - - current = git_array_last(stack); - GIT_ASSERT(current); - - if ((error = create_popped_tree(current, popped, &component)) < 0) - goto cleanup; - } - - /* Now that we've created the trees we popped from the stack, let's go back down */ - path = &update->path[common_prefix]; - while (next_component(&component, path)) { - tree_stack_entry *last, *new_entry; - const git_tree_entry *entry; - - last = git_array_last(stack); - entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL; - if (!entry) - entry = treebuilder_get(last->bld, component.ptr); - - if (entry && git_tree_entry_type(entry) != GIT_OBJECT_TREE) { - git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); - error = -1; - goto cleanup; - } - - new_entry = git_array_alloc(stack); - GIT_ERROR_CHECK_ALLOC(new_entry); - memset(new_entry, 0, sizeof(*new_entry)); - - new_entry->tree = NULL; - if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0) - goto cleanup; - - if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0) - goto cleanup; - - new_entry->name = git__strdup(component.ptr); - GIT_ERROR_CHECK_ALLOC(new_entry->name); - - /* Get to the start of the next component */ - path += component.size + 1; - } - - /* After all that, we're finally at the place where we want to perform the update */ - switch (update->action) { - case GIT_TREE_UPDATE_UPSERT: - { - /* Make sure we're replacing something of the same type */ - tree_stack_entry *last = git_array_last(stack); - char *basename = git_fs_path_basename(update->path); - const git_tree_entry *e = git_treebuilder_get(last->bld, basename); - if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) { - git__free(basename); - git_error_set(GIT_ERROR_TREE, "cannot replace '%s' with '%s' at '%s'", - git_object_type2string(git_tree_entry_type(e)), - git_object_type2string(git_object__type_from_filemode(update->filemode)), - update->path); - error = -1; - goto cleanup; - } - - error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode); - git__free(basename); - break; - } - case GIT_TREE_UPDATE_REMOVE: - { - tree_stack_entry *last = git_array_last(stack); - char *basename = git_fs_path_basename(update->path); - error = git_treebuilder_remove(last->bld, basename); - git__free(basename); - break; - } - default: - git_error_set(GIT_ERROR_TREE, "unknown action for update"); - error = -1; - goto cleanup; - } - - if (error < 0) - goto cleanup; - } - - /* We're done, go up the stack again and write out the tree */ - { - tree_stack_entry *current = NULL, *popped = NULL; - while ((popped = git_array_pop(stack)) != NULL) { - current = git_array_last(stack); - /* We've reached the top, current is the root tree */ - if (!current) - break; - - if ((error = create_popped_tree(current, popped, &component)) < 0) - goto cleanup; - } - - /* Write out the root tree */ - git__free(popped->name); - git_tree_free(popped->tree); - - error = git_treebuilder_write(out, popped->bld); - git_treebuilder_free(popped->bld); - if (error < 0) - goto cleanup; - } - -cleanup: - { - tree_stack_entry *e; - while ((e = git_array_pop(stack)) != NULL) { - git_treebuilder_free(e->bld); - git_tree_free(e->tree); - git__free(e->name); - } - } - - git_str_dispose(&component); - git_array_clear(stack); - git_vector_free(&entries); - return error; -} - -/* Deprecated Functions */ - -#ifndef GIT_DEPRECATE_HARD - -int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_buf *buf) -{ - GIT_UNUSED(buf); - - return git_treebuilder_write(oid, bld); -} - -#endif diff --git a/src/tree.h b/src/tree.h deleted file mode 100644 index 6bd9ed652..000000000 --- a/src/tree.h +++ /dev/null @@ -1,58 +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_tree_h__ -#define INCLUDE_tree_h__ - -#include "common.h" - -#include "git2/tree.h" -#include "repository.h" -#include "odb.h" -#include "vector.h" -#include "strmap.h" -#include "pool.h" - -struct git_tree_entry { - uint16_t attr; - uint16_t filename_len; - const git_oid *oid; - const char *filename; -}; - -struct git_tree { - git_object object; - git_odb_object *odb_obj; - git_array_t(git_tree_entry) entries; -}; - -struct git_treebuilder { - git_repository *repo; - git_strmap *map; - git_str write_cache; -}; - -GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) -{ - return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); -} - -void git_tree__free(void *tree); -int git_tree__parse(void *tree, git_odb_object *obj); -int git_tree__parse_raw(void *_tree, const char *data, size_t size); - -/** - * Write a tree to the given repository - */ -int git_tree__write_index( - git_oid *oid, git_index *index, git_repository *repo); - -/** - * Obsolete mode kept for compatibility reasons - */ -#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664 - -#endif diff --git a/src/tsort.c b/src/tsort.c deleted file mode 100644 index 045efad23..000000000 --- a/src/tsort.c +++ /dev/null @@ -1,382 +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 "common.h" - -/** - * An array-of-pointers implementation of Python's Timsort - * Based on code by Christopher Swenson under the MIT license - * - * Copyright (c) 2010 Christopher Swenson - * Copyright (c) 2011 Vicent Marti - */ - -#ifndef MAX -# define MAX(x,y) (((x) > (y) ? (x) : (y))) -#endif - -#ifndef MIN -# define MIN(x,y) (((x) < (y) ? (x) : (y))) -#endif - -static int binsearch( - void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) -{ - int l, c, r; - void *lx, *cx; - - l = 0; - r = (int)size - 1; - c = r >> 1; - lx = dst[l]; - - /* check for beginning conditions */ - if (cmp(x, lx, payload) < 0) - return 0; - - else if (cmp(x, lx, payload) == 0) { - int i = 1; - while (cmp(x, dst[i], payload) == 0) - i++; - return i; - } - - /* guaranteed not to be >= rx */ - cx = dst[c]; - while (1) { - const int val = cmp(x, cx, payload); - if (val < 0) { - if (c - l <= 1) return c; - r = c; - } else if (val > 0) { - if (r - c <= 1) return c + 1; - l = c; - lx = cx; - } else { - do { - cx = dst[++c]; - } while (cmp(x, cx, payload) == 0); - return c; - } - c = l + ((r - l) >> 1); - cx = dst[c]; - } -} - -/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ -static void bisort( - void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) -{ - size_t i; - void *x; - int location; - - for (i = start; i < size; i++) { - int j; - /* If this entry is already correct, just move along */ - if (cmp(dst[i - 1], dst[i], payload) <= 0) - continue; - - /* Else we need to find the right place, shift everything over, and squeeze in */ - x = dst[i]; - location = binsearch(dst, x, i, cmp, payload); - for (j = (int)i - 1; j >= location; j--) { - dst[j + 1] = dst[j]; - } - dst[location] = x; - } -} - - -/* timsort implementation, based on timsort.txt */ -struct tsort_run { - ssize_t start; - ssize_t length; -}; - -struct tsort_store { - size_t alloc; - git__sort_r_cmp cmp; - void *payload; - void **storage; -}; - -static void reverse_elements(void **dst, ssize_t start, ssize_t end) -{ - while (start < end) { - void *tmp = dst[start]; - dst[start] = dst[end]; - dst[end] = tmp; - - start++; - end--; - } -} - -static ssize_t count_run( - void **dst, ssize_t start, ssize_t size, struct tsort_store *store) -{ - ssize_t curr = start + 2; - - if (size - start == 1) - return 1; - - if (start >= size - 2) { - if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { - void *tmp = dst[size - 1]; - dst[size - 1] = dst[size - 2]; - dst[size - 2] = tmp; - } - - return 2; - } - - if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { - while (curr < size - 1 && - store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) - curr++; - - return curr - start; - } else { - while (curr < size - 1 && - store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) - curr++; - - /* reverse in-place */ - reverse_elements(dst, start, curr - 1); - return curr - start; - } -} - -static size_t compute_minrun(size_t n) -{ - int r = 0; - while (n >= 64) { - r |= n & 1; - n >>= 1; - } - return n + r; -} - -static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) -{ - if (stack_curr < 2) - return 1; - - else if (stack_curr == 2) { - const ssize_t A = stack[stack_curr - 2].length; - const ssize_t B = stack[stack_curr - 1].length; - return (A > B); - } else { - const ssize_t A = stack[stack_curr - 3].length; - const ssize_t B = stack[stack_curr - 2].length; - const ssize_t C = stack[stack_curr - 1].length; - return !((A <= B + C) || (B <= C)); - } -} - -static int resize(struct tsort_store *store, size_t new_size) -{ - if (store->alloc < new_size) { - void **tempstore; - - tempstore = git__reallocarray(store->storage, new_size, sizeof(void *)); - - /** - * Do not propagate on OOM; this will abort the sort and - * leave the array unsorted, but no error code will be - * raised - */ - if (tempstore == NULL) - return -1; - - store->storage = tempstore; - store->alloc = new_size; - } - - return 0; -} - -static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) -{ - const ssize_t A = stack[stack_curr - 2].length; - const ssize_t B = stack[stack_curr - 1].length; - const ssize_t curr = stack[stack_curr - 2].start; - - void **storage; - ssize_t i, j, k; - - if (resize(store, MIN(A, B)) < 0) - return; - - storage = store->storage; - - /* left merge */ - if (A < B) { - memcpy(storage, &dst[curr], A * sizeof(void *)); - i = 0; - j = curr + A; - - for (k = curr; k < curr + A + B; k++) { - if ((i < A) && (j < curr + A + B)) { - if (store->cmp(storage[i], dst[j], store->payload) <= 0) - dst[k] = storage[i++]; - else - dst[k] = dst[j++]; - } else if (i < A) { - dst[k] = storage[i++]; - } else - dst[k] = dst[j++]; - } - } else { - memcpy(storage, &dst[curr + A], B * sizeof(void *)); - i = B - 1; - j = curr + A - 1; - - for (k = curr + A + B - 1; k >= curr; k--) { - if ((i >= 0) && (j >= curr)) { - if (store->cmp(dst[j], storage[i], store->payload) > 0) - dst[k] = dst[j--]; - else - dst[k] = storage[i--]; - } else if (i >= 0) - dst[k] = storage[i--]; - else - dst[k] = dst[j--]; - } - } -} - -static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) -{ - ssize_t A, B, C; - - while (1) { - /* if the stack only has one thing on it, we are done with the collapse */ - if (stack_curr <= 1) - break; - - /* if this is the last merge, just do it */ - if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { - merge(dst, stack, stack_curr, store); - stack[0].length += stack[1].length; - stack_curr--; - break; - } - - /* check if the invariant is off for a stack of 2 elements */ - else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { - merge(dst, stack, stack_curr, store); - stack[0].length += stack[1].length; - stack_curr--; - break; - } - else if (stack_curr == 2) - break; - - A = stack[stack_curr - 3].length; - B = stack[stack_curr - 2].length; - C = stack[stack_curr - 1].length; - - /* check first invariant */ - if (A <= B + C) { - if (A < C) { - merge(dst, stack, stack_curr - 1, store); - stack[stack_curr - 3].length += stack[stack_curr - 2].length; - stack[stack_curr - 2] = stack[stack_curr - 1]; - stack_curr--; - } else { - merge(dst, stack, stack_curr, store); - stack[stack_curr - 2].length += stack[stack_curr - 1].length; - stack_curr--; - } - } else if (B <= C) { - merge(dst, stack, stack_curr, store); - stack[stack_curr - 2].length += stack[stack_curr - 1].length; - stack_curr--; - } else - break; - } - - return stack_curr; -} - -#define PUSH_NEXT() do {\ - len = count_run(dst, curr, size, store);\ - run = minrun;\ - if (run > (ssize_t)size - curr) run = size - curr;\ - if (run > len) {\ - bisort(&dst[curr], len, run, cmp, payload);\ - len = run;\ - }\ - run_stack[stack_curr].start = curr;\ - run_stack[stack_curr++].length = len;\ - curr += len;\ - if (curr == (ssize_t)size) {\ - /* finish up */ \ - while (stack_curr > 1) { \ - merge(dst, run_stack, stack_curr, store); \ - run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ - stack_curr--; \ - } \ - if (store->storage != NULL) {\ - git__free(store->storage);\ - store->storage = NULL;\ - }\ - return;\ - }\ -}\ -while (0) - -void git__tsort_r( - void **dst, size_t size, git__sort_r_cmp cmp, void *payload) -{ - struct tsort_store _store, *store = &_store; - struct tsort_run run_stack[128]; - - ssize_t stack_curr = 0; - ssize_t len, run; - ssize_t curr = 0; - ssize_t minrun; - - if (size < 64) { - bisort(dst, 1, size, cmp, payload); - return; - } - - /* compute the minimum run length */ - minrun = (ssize_t)compute_minrun(size); - - /* temporary storage for merges */ - store->alloc = 0; - store->storage = NULL; - store->cmp = cmp; - store->payload = payload; - - PUSH_NEXT(); - PUSH_NEXT(); - PUSH_NEXT(); - - while (1) { - if (!check_invariant(run_stack, stack_curr)) { - stack_curr = collapse(dst, run_stack, stack_curr, store, size); - continue; - } - - PUSH_NEXT(); - } -} - -static int tsort_r_cmp(const void *a, const void *b, void *payload) -{ - return ((git__tsort_cmp)payload)(a, b); -} - -void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) -{ - git__tsort_r(dst, size, tsort_r_cmp, cmp); -} diff --git a/src/unix/map.c b/src/unix/map.c deleted file mode 100644 index 23fcb786e..000000000 --- a/src/unix/map.c +++ /dev/null @@ -1,76 +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 "common.h" - -#if !defined(GIT_WIN32) && !defined(NO_MMAP) - -#include "map.h" -#include -#include -#include - -int git__page_size(size_t *page_size) -{ - long sc_page_size = sysconf(_SC_PAGE_SIZE); - if (sc_page_size < 0) { - git_error_set(GIT_ERROR_OS, "can't determine system page size"); - return -1; - } - *page_size = (size_t) sc_page_size; - return 0; -} - -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, off64_t offset) -{ - int mprot = PROT_READ; - int mflag = 0; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - out->data = NULL; - out->len = 0; - - if (prot & GIT_PROT_WRITE) - mprot |= PROT_WRITE; - - if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) - mflag = MAP_SHARED; - else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) - mflag = MAP_PRIVATE; - else - mflag = MAP_SHARED; - - out->data = mmap(NULL, len, mprot, mflag, fd, offset); - - if (!out->data || out->data == MAP_FAILED) { - git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data"); - return -1; - } - - out->len = len; - - return 0; -} - -int p_munmap(git_map *map) -{ - GIT_ASSERT_ARG(map); - munmap(map->data, map->len); - map->data = NULL; - map->len = 0; - - return 0; -} - -#endif - diff --git a/src/unix/posix.h b/src/unix/posix.h deleted file mode 100644 index 49065e533..000000000 --- a/src/unix/posix.h +++ /dev/null @@ -1,104 +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_unix_posix_h__ -#define INCLUDE_unix_posix_h__ - -#include "common.h" - -#include -#include -#include -#include -#include - -typedef int GIT_SOCKET; -#define INVALID_SOCKET -1 - -#define p_lseek(f,n,w) lseek(f, n, w) -#define p_fstat(f,b) fstat(f, b) -#define p_lstat(p,b) lstat(p,b) -#define p_stat(p,b) stat(p, b) - -#if defined(GIT_USE_STAT_MTIMESPEC) -# define st_atime_nsec st_atimespec.tv_nsec -# define st_mtime_nsec st_mtimespec.tv_nsec -# define st_ctime_nsec st_ctimespec.tv_nsec -#elif defined(GIT_USE_STAT_MTIM) -# 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_NSEC) -# error GIT_USE_NSEC defined but unknown struct stat nanosecond type -#endif - -#define p_utimes(f, t) utimes(f, t) - -#define p_readlink(a, b, c) readlink(a, b, c) -#define p_symlink(o,n) symlink(o, n) -#define p_link(o,n) link(o, n) -#define p_unlink(p) unlink(p) -#define p_mkdir(p,m) mkdir(p, m) -extern char *p_realpath(const char *, char *); - -GIT_INLINE(int) p_fsync(int fd) -{ - p_fsync__cnt++; - return fsync(fd); -} - -#define p_recv(s,b,l,f) recv(s,b,l,f) -#define p_send(s,b,l,f) send(s,b,l,f) -#define p_inet_pton(a, b, c) inet_pton(a, b, c) - -#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 snprintf -#define p_chdir(p) chdir(p) -#define p_rmdir(p) rmdir(p) -#define p_access(p,m) access(p,m) -#define p_ftruncate(fd, sz) ftruncate(fd, sz) - -/* - * Pre-Android 5 did not implement a virtual filesystem atop FAT - * partitions for Unix permissions, which causes chmod to fail. However, - * Unix permissions have no effect on Android anyway as file permissions - * are not actually managed this way, so treating it as a no-op across - * all Android is safe. - */ -#ifdef __ANDROID__ -# define p_chmod(p,m) 0 -#else -# define p_chmod(p,m) chmod(p, m) -#endif - -/* see win32/posix.h for explanation about why this exists */ -#define p_lstat_posixly(p,b) lstat(p,b) - -#define p_localtime_r(c, r) localtime_r(c, r) -#define p_gmtime_r(c, r) gmtime_r(c, r) - -#define p_timeval timeval - -#ifdef GIT_USE_FUTIMENS -GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) -{ - struct timespec s[2]; - s[0].tv_sec = t[0].tv_sec; - s[0].tv_nsec = t[0].tv_usec * 1000; - s[1].tv_sec = t[1].tv_sec; - s[1].tv_nsec = t[1].tv_usec * 1000; - return futimens(f, s); -} -#else -# define p_futimes futimes -#endif - -#define p_pread(f, d, s, o) pread(f, d, s, o) -#define p_pwrite(f, d, s, o) pwrite(f, d, s, o) - -#endif diff --git a/src/unix/pthread.h b/src/unix/pthread.h deleted file mode 100644 index 55f4ae227..000000000 --- a/src/unix/pthread.h +++ /dev/null @@ -1,57 +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_unix_pthread_h__ -#define INCLUDE_unix_pthread_h__ - -typedef struct { - pthread_t thread; -} git_thread; - -GIT_INLINE(int) git_threads_global_init(void) { return 0; } - -#define git_thread_create(git_thread_ptr, start_routine, arg) \ - pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) -#define git_thread_join(git_thread_ptr, status) \ - pthread_join((git_thread_ptr)->thread, status) -#define git_thread_currentid() ((size_t)(pthread_self())) -#define git_thread_exit(retval) pthread_exit(retval) - -/* Git Mutex */ -#define git_mutex pthread_mutex_t -#define git_mutex_init(a) pthread_mutex_init(a, NULL) -#define git_mutex_lock(a) pthread_mutex_lock(a) -#define git_mutex_unlock(a) pthread_mutex_unlock(a) -#define git_mutex_free(a) pthread_mutex_destroy(a) - -/* Git condition vars */ -#define git_cond pthread_cond_t -#define git_cond_init(c) pthread_cond_init(c, NULL) -#define git_cond_free(c) pthread_cond_destroy(c) -#define git_cond_wait(c, l) pthread_cond_wait(c, l) -#define git_cond_signal(c) pthread_cond_signal(c) -#define git_cond_broadcast(c) pthread_cond_broadcast(c) - -/* Pthread (-ish) rwlock - * - * This differs from normal pthreads rwlocks in two ways: - * 1. Separate APIs for releasing read locks and write locks (as - * opposed to the pure POSIX API which only has one unlock fn) - * 2. You should not use recursive read locks (i.e. grabbing a read - * lock in a thread that already holds a read lock) because the - * Windows implementation doesn't support it - */ -#define git_rwlock pthread_rwlock_t -#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) -#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) -#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) -#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) -#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) -#define git_rwlock_free(a) pthread_rwlock_destroy(a) -#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER - -#endif diff --git a/src/unix/realpath.c b/src/unix/realpath.c deleted file mode 100644 index f1ca669f7..000000000 --- a/src/unix/realpath.c +++ /dev/null @@ -1,32 +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 "common.h" - -#ifndef GIT_WIN32 - -#include -#include -#include -#include - -char *p_realpath(const char *pathname, char *resolved) -{ - char *ret; - if ((ret = realpath(pathname, resolved)) == NULL) - return NULL; - -#ifdef __OpenBSD__ - /* The OpenBSD realpath function behaves differently, - * figure out if the file exists */ - if (access(ret, F_OK) < 0) - ret = NULL; -#endif - return ret; -} - -#endif diff --git a/src/userdiff.h b/src/userdiff.h deleted file mode 100644 index c9a80d712..000000000 --- a/src/userdiff.h +++ /dev/null @@ -1,210 +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_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 - * original authors for relicensing to libgit2). - */ - -typedef struct { - const char *name; - const char *fns; - const char *words; - int flags; -} git_diff_driver_definition; - -#define WORD_DEFAULT "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" - -/* - * These builtin driver definition macros have same signature as in core - * git userdiff.c so that the data can be extracted verbatim - */ -#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, GIT_REGEXP_ICASE } - -/* - * The table of diff driver patterns - * - * Function name patterns are a list of newline separated patterns that - * match a function declaration (i.e. the line you want in the hunk header), - * or a negative pattern prefixed with a '!' to reject a pattern (such as - * rejecting goto labels in C code). - * - * Word boundary patterns are just a simple pattern that will be OR'ed with - * the default value above (i.e. whitespace or non-ASCII characters). - */ -static git_diff_driver_definition builtin_defs[] = { - -IPATTERN("ada", - "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n" - "!^[ \t]*with[ \t].*$\n" - "^[ \t]*((procedure|function)[ \t]+.*)$\n" - "^[ \t]*((package|protected|task)[ \t]+.*)$", - /* -- */ - "[a-zA-Z][a-zA-Z0-9_]*" - "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?" - "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"), - -IPATTERN("fortran", - "!^([C*]|[ \t]*!)\n" - "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n" - "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA" - "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$", - /* -- */ - "[a-zA-Z][a-zA-Z0-9_]*" - "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\." - /* numbers and format statements like 2E14.4, or ES12.6, 9X. - * Don't worry about format statements without leading digits since - * they would have been matched above as a variable anyway. */ - "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" - "|//|\\*\\*|::|[/<>=]="), - -PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", - "[^<>= \t]+"), - -PATTERNS("java", - "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" - "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=" - "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), - -PATTERNS("matlab", - "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$", - "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"), - -PATTERNS("objc", - /* Negate C statements that can look like functions */ - "!^[ \t]*(do|for|if|else|return|switch|while)\n" - /* Objective-C methods */ - "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n" - /* C functions */ - "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n" - /* Objective-C class/protocol definitions */ - "^(@(implementation|interface|protocol)[ \t].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), - -PATTERNS("pascal", - "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|" - "implementation|initialization|finalization)[ \t]*.*)$" - "\n" - "^(.*=[ \t]*(class|record).*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" - "|<>|<=|>=|:=|\\.\\."), - -PATTERNS("perl", - "^package .*\n" - "^sub [[:alnum:]_':]+[ \t]*" - "(\\([^)]*\\)[ \t]*)?" /* prototype */ - /* - * Attributes. A regex can't count nested parentheses, - * so just slurp up whatever we see, taking care not - * to accept lines like "sub foo; # defined elsewhere". - * - * An attribute could contain a semicolon, but at that - * point it seems reasonable enough to give up. - */ - "(:[^;#]*)?" - "(\\{[ \t]*)?" /* brace can come here or on the next line */ - "(#.*)?$\n" /* comment */ - "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*" - "(\\{[ \t]*)?" /* brace can come here or on the next line */ - "(#.*)?$\n" - "^=head[0-9] .*", /* POD */ - /* -- */ - "[[:alpha:]_'][[:alnum:]_']*" - "|0[xb]?[0-9a-fA-F_]*" - /* taking care not to interpret 3..5 as (3.)(.5) */ - "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?" - "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::" - "|&&=|\\|\\|=|//=|\\*\\*=" - "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?" - "|[-+*/%.^&<>=!|]=" - "|=~|!~" - "|<<|<>|<=>|>>"), - -PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"), - -PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", - /* -- */ - "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." - "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"), - -PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", - "[={}\"]|[^={}\" \t]+"), - -PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", - "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), - -PATTERNS("cpp", - /* Jump targets or access declarations */ - "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n" - /* functions/methods, variables, and compounds at top level */ - "^((::[[:space:]]*)?[A-Za-z_].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"), - -PATTERNS("csharp", - /* Keywords */ - "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" - /* Methods and constructors */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" - /* Properties */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" - /* Type definitions */ - "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" - /* Namespace */ - "^[ \t]*(namespace[ \t]+.*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), - -PATTERNS("php", - "^[ \t]*(((public|private|protected|static|final)[ \t]+)*((class|function)[ \t].*))$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), - -PATTERNS("javascript", - "([a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z0-9_$]+)*[ \t]*=[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" - "([a-zA-Z_$][a-zA-Z0-9_$]*[ \t]*:[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" - "[^a-zA-Z0-9_\\$](function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), -}; - -#undef IPATTERN -#undef PATTERNS -#undef WORD_DEFAULT - -#endif - diff --git a/src/utf8.c b/src/utf8.c deleted file mode 100644 index 77065cb71..000000000 --- a/src/utf8.c +++ /dev/null @@ -1,150 +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 "utf8.h" - -#include "common.h" - -/* - * git_utf8_iterate is taken from the utf8proc project, - * http://www.public-software-group.org/utf8proc - * - * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the ""Software""), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * 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. - */ - -static const uint8_t utf8proc_utf8class[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -static int utf8_charlen(const uint8_t *str, size_t str_len) -{ - uint8_t length; - size_t i; - - length = utf8proc_utf8class[str[0]]; - if (!length) - return -1; - - if (str_len > 0 && length > str_len) - return -1; - - for (i = 1; i < length; i++) { - if ((str[i] & 0xC0) != 0x80) - return -1; - } - - return (int)length; -} - -int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - uint32_t uc = 0; - int length; - - *out = 0; - - if ((length = utf8_charlen(str, str_len)) < 0) - return -1; - - switch (length) { - case 1: - uc = str[0]; - break; - case 2: - uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); - if (uc < 0x80) uc = -1; - break; - case 3: - uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) - + (str[2] & 0x3F); - if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || - (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; - break; - case 4: - uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) - + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); - if (uc < 0x10000 || uc >= 0x110000) uc = -1; - break; - default: - return -1; - } - - if ((uc & 0xFFFF) >= 0xFFFE) - return -1; - - *out = uc; - return length; -} - -size_t git_utf8_char_length(const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - size_t offset = 0, count = 0; - - while (offset < str_len) { - int length = utf8_charlen(str + offset, str_len - offset); - - if (length < 0) - length = 1; - - offset += length; - count++; - } - - return count; -} - -size_t git_utf8_valid_buf_length(const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - size_t offset = 0; - - while (offset < str_len) { - int length = utf8_charlen(str + offset, str_len - offset); - - if (length < 0) - break; - - offset += length; - } - - return offset; -} diff --git a/src/utf8.h b/src/utf8.h deleted file mode 100644 index dff91b294..000000000 --- a/src/utf8.h +++ /dev/null @@ -1,52 +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_utf8_h__ -#define INCLUDE_utf8_h__ - -#include "common.h" - -/* - * Iterate through an UTF-8 string, yielding one codepoint at a time. - * - * @param out pointer where to store the current codepoint - * @param str current position in the string - * @param str_len size left in the string - * @return length in bytes of the read codepoint; -1 if the codepoint was invalid - */ -extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len); - -/** - * Returns the number of characters in the given string. - * - * This function will count invalid codepoints; if any given byte is - * not part of a valid UTF-8 codepoint, then it will be counted toward - * the length in characters. - * - * In other words: - * 0x24 (U+0024 "$") has length 1 - * 0xc2 0xa2 (U+00A2 "¢") has length 1 - * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2 - * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1 - * 0x24 0xc0 0xc1 0x34 (U+0024 "4) has length 4 - * - * @param str string to scan - * @param str_len size of the string - * @return length in characters of the string - */ -extern size_t git_utf8_char_length(const char *str, size_t str_len); - -/** - * Iterate through an UTF-8 string and stops after finding any invalid UTF-8 - * codepoints. - * - * @param str string to scan - * @param str_len size of the string - * @return length in bytes of the string that contains valid data - */ -extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len); - -#endif diff --git a/src/util.c b/src/util.c deleted file mode 100644 index e06d4ca09..000000000 --- a/src/util.c +++ /dev/null @@ -1,819 +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 "util.h" - -#include "common.h" - -#ifdef GIT_WIN32 -# include "win32/utf-conv.h" -# include "win32/w32_buffer.h" - -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include - -# ifdef GIT_QSORT_S -# include -# endif -#endif - -#ifdef _MSC_VER -# include -#endif - -#if defined(hpux) || defined(__hpux) || defined(_hpux) -# include -#endif - -int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) -{ - const char *p; - int64_t n, nn, v; - int c, ovfl, neg, ndig; - - p = nptr; - neg = 0; - n = 0; - ndig = 0; - ovfl = 0; - - /* - * White space - */ - while (nptr_len && git__isspace(*p)) - p++, nptr_len--; - - if (!nptr_len) - goto Return; - - /* - * Sign - */ - if (*p == '-' || *p == '+') { - if (*p == '-') - neg = 1; - p++; - nptr_len--; - } - - if (!nptr_len) - goto Return; - - /* - * Automatically detect the base if none was given to us. - * Right now, we assume that a number starting with '0x' - * is hexadecimal and a number starting with '0' is - * octal. - */ - if (base == 0) { - if (*p != '0') - base = 10; - else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X')) - base = 16; - else - base = 8; - } - - if (base < 0 || 36 < base) - goto Return; - - /* - * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no - * need to do the same for '0'-prefixed octal numbers as a - * leading '0' does not have any impact. Also, if we skip a - * leading '0' in such a string, then we may end up with no - * digits left and produce an error later on which isn't one. - */ - if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - p += 2; - nptr_len -= 2; - } - - /* - * Non-empty sequence of digits - */ - for (; nptr_len > 0; p++,ndig++,nptr_len--) { - c = *p; - v = base; - if ('0'<=c && c<='9') - v = c - '0'; - else if ('a'<=c && c<='z') - v = c - 'a' + 10; - else if ('A'<=c && c<='Z') - v = c - 'A' + 10; - if (v >= base) - break; - v = neg ? -v : v; - if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) { - ovfl = 1; - /* Keep on iterating until the end of this number */ - continue; - } - } - -Return: - if (ndig == 0) { - git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number"); - return -1; - } - - if (endptr) - *endptr = p; - - if (ovfl) { - git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error"); - return -1; - } - - *result = n; - return 0; -} - -int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) -{ - const char *tmp_endptr; - int32_t tmp_int; - int64_t tmp_long; - int error; - - if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) - return error; - - tmp_int = tmp_long & 0xFFFFFFFF; - if (tmp_int != tmp_long) { - int len = (int)(tmp_endptr - nptr); - git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); - return -1; - } - - *result = tmp_int; - if (endptr) - *endptr = tmp_endptr; - - return error; -} - -int git__strcasecmp(const char *a, const char *b) -{ - while (*a && *b && git__tolower(*a) == git__tolower(*b)) - ++a, ++b; - return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b)); -} - -int git__strcasesort_cmp(const char *a, const char *b) -{ - int cmp = 0; - - while (*a && *b) { - if (*a != *b) { - if (git__tolower(*a) != git__tolower(*b)) - break; - /* use case in sort order even if not in equivalence */ - if (!cmp) - cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); - } - - ++a, ++b; - } - - if (*a || *b) - return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b); - - return cmp; -} - -int git__strncasecmp(const char *a, const char *b, size_t sz) -{ - int al, bl; - - do { - al = (unsigned char)git__tolower(*a); - bl = (unsigned char)git__tolower(*b); - ++a, ++b; - } while (--sz && al && al == bl); - - return al - bl; -} - -void git__strntolower(char *str, size_t len) -{ - size_t i; - - for (i = 0; i < len; ++i) { - str[i] = (char)git__tolower(str[i]); - } -} - -void git__strtolower(char *str) -{ - git__strntolower(str, strlen(str)); -} - -GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) -{ - int s, p; - - while (str_n--) { - s = (unsigned char)*str++; - p = (unsigned char)*prefix++; - - if (icase) { - s = git__tolower(s); - p = git__tolower(p); - } - - if (!p) - return 0; - - if (s != p) - return s - p; - } - - return (0 - *prefix); -} - -int git__prefixcmp(const char *str, const char *prefix) -{ - 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) -{ - return prefixcmp(str, str_n, prefix, false); -} - -int git__prefixcmp_icase(const char *str, const char *prefix) -{ - return prefixcmp(str, SIZE_MAX, prefix, true); -} - -int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) -{ - return prefixcmp(str, str_n, prefix, true); -} - -int git__suffixcmp(const char *str, const char *suffix) -{ - size_t a = strlen(str); - size_t b = strlen(suffix); - if (a < b) - return -1; - return strcmp(str + (a - b), suffix); -} - -char *git__strtok(char **end, const char *sep) -{ - char *ptr = *end; - - while (*ptr && strchr(sep, *ptr)) - ++ptr; - - if (*ptr) { - char *start = ptr; - *end = start + 1; - - while (**end && !strchr(sep, **end)) - ++*end; - - if (**end) { - **end = '\0'; - ++*end; - } - - return start; - } - - return NULL; -} - -/* Similar to strtok, but does not collapse repeated tokens. */ -char *git__strsep(char **end, const char *sep) -{ - char *start = *end, *ptr = *end; - - while (*ptr && !strchr(sep, *ptr)) - ++ptr; - - if (*ptr) { - *end = ptr + 1; - *ptr = '\0'; - - return start; - } - - return NULL; -} - -size_t git__linenlen(const char *buffer, size_t buffer_len) -{ - char *nl = memchr(buffer, '\n', buffer_len); - return nl ? (size_t)(nl - buffer) + 1 : buffer_len; -} - -/* - * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ - */ -const void * git__memmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen) -{ - const char *h, *n; - size_t j, k, l; - - if (needlelen > haystacklen || !haystacklen || !needlelen) - return NULL; - - h = (const char *) haystack, - n = (const char *) needle; - - if (needlelen == 1) - return memchr(haystack, *n, haystacklen); - - if (n[0] == n[1]) { - k = 2; - l = 1; - } else { - k = 1; - l = 2; - } - - j = 0; - while (j <= haystacklen - needlelen) { - if (n[1] != h[j + 1]) { - j += k; - } else { - if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && - n[0] == h[j]) - return h + j; - j += l; - } - } - - return NULL; -} - -void git__hexdump(const char *buffer, size_t len) -{ - static const size_t LINE_WIDTH = 16; - - size_t line_count, last_line, i, j; - const char *line; - - line_count = (len / LINE_WIDTH); - last_line = (len % LINE_WIDTH); - - for (i = 0; i < line_count; ++i) { - printf("%08" PRIxZ " ", (i * LINE_WIDTH)); - - line = buffer + (i * LINE_WIDTH); - for (j = 0; j < LINE_WIDTH; ++j, ++line) { - printf("%02x ", (unsigned char)*line & 0xFF); - - if (j == (LINE_WIDTH / 2)) - printf(" "); - } - - printf(" |"); - - line = buffer + (i * LINE_WIDTH); - for (j = 0; j < LINE_WIDTH; ++j, ++line) - printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); - - printf("|\n"); - } - - if (last_line > 0) { - printf("%08" PRIxZ " ", (line_count * LINE_WIDTH)); - - line = buffer + (line_count * LINE_WIDTH); - for (j = 0; j < last_line; ++j, ++line) { - printf("%02x ", (unsigned char)*line & 0xFF); - - if (j == (LINE_WIDTH / 2)) - printf(" "); - } - - if (j < (LINE_WIDTH / 2)) - printf(" "); - for (j = 0; j < (LINE_WIDTH - last_line); ++j) - printf(" "); - - printf(" |"); - - line = buffer + (line_count * LINE_WIDTH); - for (j = 0; j < last_line; ++j, ++line) - printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); - - printf("|\n"); - } - - printf("\n"); -} - -#ifdef GIT_LEGACY_HASH -uint32_t git__hash(const void *key, int len, unsigned int seed) -{ - const uint32_t m = 0x5bd1e995; - const int r = 24; - uint32_t h = seed ^ len; - - const unsigned char *data = (const unsigned char *)key; - - while(len >= 4) { - uint32_t k = *(uint32_t *)data; - - k *= m; - k ^= k >> r; - k *= m; - - h *= m; - h ^= k; - - data += 4; - len -= 4; - } - - switch(len) { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; - h *= m; - }; - - h ^= h >> 13; - h *= m; - h ^= h >> 15; - - return h; -} -#else -/* - Cross-platform version of Murmurhash3 - http://code.google.com/p/smhasher/wiki/MurmurHash3 - by Austin Appleby (aappleby@gmail.com) - - This code is on the public domain. -*/ -uint32_t git__hash(const void *key, int len, uint32_t seed) -{ - -#define MURMUR_BLOCK() {\ - k1 *= c1; \ - k1 = git__rotl(k1,11);\ - k1 *= c2;\ - h1 ^= k1;\ - h1 = h1*3 + 0x52dce729;\ - c1 = c1*5 + 0x7b7d159c;\ - c2 = c2*5 + 0x6bce6396;\ -} - - const uint8_t *data = (const uint8_t*)key; - const int nblocks = len / 4; - - const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); - const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); - - uint32_t h1 = 0x971e137b ^ seed; - uint32_t k1; - - uint32_t c1 = 0x95543787; - uint32_t c2 = 0x2ad7eb25; - - int i; - - for (i = -nblocks; i; i++) { - k1 = blocks[i]; - MURMUR_BLOCK(); - } - - k1 = 0; - - switch(len & 3) { - case 3: k1 ^= tail[2] << 16; - /* fall through */ - case 2: k1 ^= tail[1] << 8; - /* fall through */ - case 1: k1 ^= tail[0]; - MURMUR_BLOCK(); - } - - h1 ^= len; - h1 ^= h1 >> 16; - h1 *= 0x85ebca6b; - h1 ^= h1 >> 13; - h1 *= 0xc2b2ae35; - h1 ^= h1 >> 16; - - return h1; -} -#endif - -/** - * A modified `bsearch` from the BSD glibc. - * - * Copyright (c) 1990 Regents of the University of California. - * All rights reserved. - * 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. [rescinded 22 July 1999] - * 4. 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. - */ -int git__bsearch( - void **array, - size_t array_len, - const void *key, - int (*compare)(const void *, const void *), - size_t *position) -{ - size_t lim; - int cmp = -1; - void **part, **base = array; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1); - cmp = (*compare)(key, *part); - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1; - lim--; - } /* else take left partition */ - } - - if (position) - *position = (base - array); - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -int git__bsearch_r( - void **array, - size_t array_len, - const void *key, - int (*compare_r)(const void *, const void *, void *), - void *payload, - size_t *position) -{ - size_t lim; - int cmp = -1; - void **part, **base = array; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1); - cmp = (*compare_r)(key, *part, payload); - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1; - lim--; - } /* else take left partition */ - } - - if (position) - *position = (base - array); - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -/** - * A strcmp wrapper - * - * We don't want direct pointers to the CRT on Windows, we may - * get stdcall conflicts. - */ -int git__strcmp_cb(const void *a, const void *b) -{ - return strcmp((const char *)a, (const char *)b); -} - -int git__strcasecmp_cb(const void *a, const void *b) -{ - return strcasecmp((const char *)a, (const char *)b); -} - -int git__parse_bool(int *out, const char *value) -{ - /* A missing value means true */ - if (value == NULL || - !strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on")) { - *out = 1; - return 0; - } - if (!strcasecmp(value, "false") || - !strcasecmp(value, "no") || - !strcasecmp(value, "off") || - value[0] == '\0') { - *out = 0; - return 0; - } - - return -1; -} - -size_t git__unescape(char *str) -{ - char *scan, *pos = str; - - if (!str) - return 0; - - for (scan = str; *scan; pos++, scan++) { - if (*scan == '\\' && *(scan + 1) != '\0') - scan++; /* skip '\' but include next char */ - if (pos != scan) - *pos = *scan; - } - - if (pos != scan) { - *pos = '\0'; - } - - return (pos - str); -} - -#if defined(GIT_QSORT_S) || defined(GIT_QSORT_R_BSD) -typedef struct { - git__sort_r_cmp cmp; - void *payload; -} git__qsort_r_glue; - -static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp( - void *payload, const void *a, const void *b) -{ - git__qsort_r_glue *glue = payload; - return glue->cmp(a, b, glue->payload); -} -#endif - - -#if !defined(GIT_QSORT_R_BSD) && \ - !defined(GIT_QSORT_R_GNU) && \ - !defined(GIT_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) -{ -#if defined(GIT_QSORT_R_BSD) - git__qsort_r_glue glue = { cmp, payload }; - qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); -#elif defined(GIT_QSORT_R_GNU) - qsort_r(els, nel, elsize, cmp, payload); -#elif defined(GIT_QSORT_S) - git__qsort_r_glue glue = { cmp, payload }; - qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); -#else - insertsort(els, nel, elsize, cmp, payload); -#endif -} - -#ifdef GIT_WIN32 -int git__getenv(git_str *out, const char *name) -{ - wchar_t *wide_name = NULL, *wide_value = NULL; - DWORD value_len; - int error = -1; - - git_str_clear(out); - - if (git__utf8_to_16_alloc(&wide_name, name) < 0) - return -1; - - if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { - wide_value = git__malloc(value_len * sizeof(wchar_t)); - GIT_ERROR_CHECK_ALLOC(wide_value); - - value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); - } - - if (value_len) - error = git_str_put_w(out, wide_value, value_len); - else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND) - error = GIT_ENOTFOUND; - else - git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name); - - git__free(wide_name); - git__free(wide_value); - return error; -} -#else -int git__getenv(git_str *out, const char *name) -{ - const char *val = getenv(name); - - git_str_clear(out); - - if (!val) - return GIT_ENOTFOUND; - - return git_str_puts(out, val); -} -#endif - -/* - * By doing this in two steps we can at least get - * the function to be somewhat coherent, even - * with this disgusting nest of #ifdefs. - */ -#ifndef _SC_NPROCESSORS_ONLN -# ifdef _SC_NPROC_ONLN -# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN -# elif defined _SC_CRAY_NCPU -# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU -# endif -#endif - -int git__online_cpus(void) -{ -#ifdef _SC_NPROCESSORS_ONLN - long ncpus; -#endif - -#ifdef _WIN32 - SYSTEM_INFO info; - GetSystemInfo(&info); - - if ((int)info.dwNumberOfProcessors > 0) - return (int)info.dwNumberOfProcessors; -#elif defined(hpux) || defined(__hpux) || defined(_hpux) - struct pst_dynamic psd; - - if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) - return (int)psd.psd_proc_cnt; -#endif - -#ifdef _SC_NPROCESSORS_ONLN - if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) - return (int)ncpus; -#endif - - return 1; -} diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 141779ade..000000000 --- a/src/util.h +++ /dev/null @@ -1,387 +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_util_h__ -#define INCLUDE_util_h__ - -#ifndef GIT_WIN32 -# include -#endif - -#include "str.h" -#include "common.h" -#include "strnlen.h" -#include "thread.h" - -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define bitsizeof(x) (CHAR_BIT * sizeof(x)) -#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) -#ifndef min -# define min(a,b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef max -# 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 - -/** - * Return the length of a constant string. - * We are aware that `strlen` performs the same task and is usually - * optimized away by the compiler, whilst being safer because it returns - * valid values when passed a pointer instead of a constant string; however - * this macro will transparently work with wide-char and single-char strings. - */ -#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) - -#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ - ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) - -#define CASESELECT(IGNORE_CASE, ICASE, CASE) \ - ((IGNORE_CASE) ? (ICASE) : (CASE)) - -extern int git__prefixcmp(const char *str, const char *prefix); -extern int git__prefixcmp_icase(const char *str, const char *prefix); -extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); -extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); -extern int git__suffixcmp(const char *str, const char *suffix); - -GIT_INLINE(int) git__signum(int val) -{ - return ((val > 0) - (val < 0)); -} - -extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); -extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); - - -extern void git__hexdump(const char *buffer, size_t n); -extern uint32_t git__hash(const void *key, int len, uint32_t seed); - -/* 32-bit cross-platform rotl */ -#ifdef _MSC_VER /* use built-in method in MSVC */ -# define git__rotl(v, s) (uint32_t)_rotl(v, s) -#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */ -# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) -#endif - -extern char *git__strtok(char **end, const char *sep); -extern char *git__strsep(char **end, const char *sep); - -extern void git__strntolower(char *str, size_t len); -extern void git__strtolower(char *str); - -#ifdef GIT_WIN32 -GIT_INLINE(int) git__tolower(int c) -{ - return (c >= 'A' && c <= 'Z') ? (c + 32) : c; -} -#else -# define git__tolower(a) tolower(a) -#endif - -extern size_t git__linenlen(const char *buffer, size_t buffer_len); - -GIT_INLINE(const char *) git__next_line(const char *s) -{ - while (*s && *s != '\n') s++; - while (*s == '\n' || *s == '\r') s++; - return s; -} - -GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) -{ - const unsigned char *cp; - - if (n != 0) { - cp = (unsigned char *)s + n; - do { - if (*(--cp) == (unsigned char)c) - return cp; - } while (--n != 0); - } - - return NULL; -} - -extern const void * git__memmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen); - -typedef int (*git__tsort_cmp)(const void *a, const void *b); - -extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); - -typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); - -extern void git__tsort_r( - void **dst, size_t size, git__sort_r_cmp cmp, void *payload); - -extern void git__qsort_r( - void *els, size_t nel, size_t elsize, 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. - * @return 0 if found; GIT_ENOTFOUND if not found - */ -extern int git__bsearch( - void **array, - size_t array_len, - const void *key, - int (*compare)(const void *key, const void *element), - size_t *position); - -extern int git__bsearch_r( - void **array, - size_t array_len, - const void *key, - int (*compare_r)(const void *key, const void *element, void *payload), - 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__strcasecmp(const char *a, const char *b); -extern int git__strncasecmp(const char *a, const char *b, size_t sz); - -extern int git__strcasesort_cmp(const char *a, const char *b); - -/* - * Compare some NUL-terminated `a` to a possibly non-NUL terminated - * `b` of length `b_len`; like `strncmp` but ensuring that - * `strlen(a) == b_len` as well. - */ -GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len) -{ - int cmp = strncmp(a, b, b_len); - return cmp ? cmp : (int)a[b_len]; -} - -typedef struct { - git_atomic32 refcount; - void *owner; -} git_refcount; - -typedef void (*git_refcount_freeptr)(void *r); - -#define GIT_REFCOUNT_INC(r) { \ - git_atomic32_inc(&(r)->rc.refcount); \ -} - -#define GIT_REFCOUNT_DEC(_r, do_free) { \ - git_refcount *r = &(_r)->rc; \ - int val = git_atomic32_dec(&r->refcount); \ - if (val <= 0 && r->owner == NULL) { do_free(_r); } \ -} - -#define GIT_REFCOUNT_OWN(r, o) { \ - (void)git_atomic_swap((r)->rc.owner, o); \ -} - -#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner) - -#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount) - - -static signed char from_hex[] = { --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ -}; - -GIT_INLINE(int) git__fromhex(char h) -{ - return from_hex[(unsigned char) h]; -} - -GIT_INLINE(int) git__ishex(const char *str) -{ - unsigned i; - for (i=0; str[i] != '\0'; i++) - if (git__fromhex(str[i]) < 0) - return 0; - return 1; -} - -GIT_INLINE(size_t) git__size_t_bitmask(size_t v) -{ - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - - return v; -} - -GIT_INLINE(size_t) git__size_t_powerof2(size_t v) -{ - return git__size_t_bitmask(v) + 1; -} - -GIT_INLINE(bool) git__isupper(int c) -{ - return (c >= 'A' && c <= 'Z'); -} - -GIT_INLINE(bool) git__isalpha(int c) -{ - return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); -} - -GIT_INLINE(bool) git__isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -GIT_INLINE(bool) git__isspace(int c) -{ - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); -} - -GIT_INLINE(bool) git__isspace_nonlf(int c) -{ - return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); -} - -GIT_INLINE(bool) git__iswildcard(int c) -{ - return (c == '*' || c == '?' || c == '['); -} - -GIT_INLINE(bool) git__isxdigit(int c) -{ - return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -/* - * Parse a string value as a boolean, just like Core Git does. - * - * Valid values for true are: 'true', 'yes', 'on' - * Valid values for false are: 'false', 'no', 'off' - */ -extern int git__parse_bool(int *out, const char *value); - -/* - * Unescapes a string in-place. - * - * Edge cases behavior: - * - "jackie\" -> "jacky\" - * - "chan\\" -> "chan\" - */ -extern size_t git__unescape(char *str); - -/* - * Safely zero-out memory, making sure that the compiler - * doesn't optimize away the operation. - */ -GIT_INLINE(void) git__memzero(void *data, size_t size) -{ -#ifdef _MSC_VER - SecureZeroMemory((PVOID)data, size); -#else - volatile uint8_t *scan = (volatile uint8_t *)data; - - while (size--) - *scan++ = 0x0; -#endif -} - -#ifdef GIT_WIN32 - -GIT_INLINE(double) git__timer(void) -{ - /* GetTickCount64 returns the number of milliseconds that have - * elapsed since the system was started. */ - return (double) GetTickCount64() / (double) 1000; -} - -#elif __APPLE__ - -#include - -GIT_INLINE(double) git__timer(void) -{ - uint64_t time = mach_absolute_time(); - static double scaling_factor = 0; - - if (scaling_factor == 0) { - mach_timebase_info_data_t info; - (void)mach_timebase_info(&info); - scaling_factor = (double)info.numer / (double)info.denom; - } - - return (double)time * scaling_factor / 1.0E9; -} - -#elif defined(__amigaos4__) - -#include - -GIT_INLINE(double) git__timer(void) -{ - struct TimeVal tv; - ITimer->GetUpTime(&tv); - return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6; -} - -#else - -#include - -GIT_INLINE(double) git__timer(void) -{ - struct timeval tv; - -#ifdef CLOCK_MONOTONIC - struct timespec tp; - if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) - return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; -#endif - - /* Fall back to using gettimeofday */ - gettimeofday(&tv, NULL); - return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; -} - -#endif - -extern int git__getenv(git_str *out, const char *name); - -extern int git__online_cpus(void); - -GIT_INLINE(int) git__noop(void) { return 0; } - -#include "alloc.h" - -#endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt new file mode 100644 index 000000000..b2833954d --- /dev/null +++ b/src/util/CMakeLists.txt @@ -0,0 +1,74 @@ +# util: a shared library for common utility functions for libgit2 projects + +add_library(util OBJECT) +set_target_properties(util PROPERTIES C_STANDARD 90) +set_target_properties(util PROPERTIES C_EXTENSIONS OFF) + +set(UTIL_INCLUDES + "${PROJECT_BINARY_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/util" + "${PROJECT_SOURCE_DIR}/include") + +file(GLOB UTIL_SRC *.c *.h allocators/*.c allocators/*.h hash.h) +list(SORT UTIL_SRC) + +# +# Platform specific sources +# + +if(WIN32 AND NOT CYGWIN) + file(GLOB UTIL_SRC_OS win32/*.c win32/*.h) + list(SORT UTIL_SRC_OS) +elseif(NOT AMIGA) + file(GLOB UTIL_SRC_OS unix/*.c unix/*.h) + list(SORT UTIL_SRC_OS) +endif() + +# +# Hash backend selection +# + +if(USE_SHA1 STREQUAL "CollisionDetection") + file(GLOB UTIL_SRC_SHA1 hash/collisiondetect.* hash/sha1dc/*) + target_compile_definitions(util PRIVATE SHA1DC_NO_STANDARD_INCLUDES=1) + target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_SHA1_C=\"git2_util.h\") + target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"git2_util.h\") +elseif(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA1 STREQUAL "OpenSSL-Dynamic") + file(GLOB UTIL_SRC_SHA1 hash/openssl.*) +elseif(USE_SHA1 STREQUAL "CommonCrypto") + file(GLOB UTIL_SRC_SHA1 hash/common_crypto.*) +elseif(USE_SHA1 STREQUAL "mbedTLS") + file(GLOB UTIL_SRC_SHA1 hash/mbedtls.*) +elseif(USE_SHA1 STREQUAL "Win32") + file(GLOB UTIL_SRC_SHA1 hash/win32.*) +else() + message(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}") +endif() + +list(SORT UTIL_SRC_SHA1) + +if(USE_SHA256 STREQUAL "Builtin") + file(GLOB UTIL_SRC_SHA256 hash/builtin.* hash/rfc6234/*) +elseif(USE_SHA256 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL-Dynamic") + file(GLOB UTIL_SRC_SHA256 hash/openssl.*) +elseif(USE_SHA256 STREQUAL "CommonCrypto") + file(GLOB UTIL_SRC_SHA256 hash/common_crypto.*) +elseif(USE_SHA256 STREQUAL "mbedTLS") + file(GLOB UTIL_SRC_SHA256 hash/mbedtls.*) +elseif(USE_SHA256 STREQUAL "Win32") + file(GLOB UTIL_SRC_SHA256 hash/win32.*) +else() + message(FATAL_ERROR "Asked for unknown SHA256 backend: ${USE_SHA256}") +endif() + +list(SORT UTIL_SRC_SHA256) + +# +# Build the library +# + +target_sources(util PRIVATE ${UTIL_SRC} ${UTIL_SRC_OS} ${UTIL_SRC_SHA1} ${UTIL_SRC_SHA256}) +ide_split_sources(util) + +target_include_directories(util PRIVATE ${UTIL_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${libgit2_SOURCE_DIR}/include) +target_include_directories(util SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) diff --git a/src/util/alloc.c b/src/util/alloc.c new file mode 100644 index 000000000..2820d84a2 --- /dev/null +++ b/src/util/alloc.c @@ -0,0 +1,56 @@ +/* + * 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 "alloc.h" +#include "runtime.h" + +#include "allocators/failalloc.h" +#include "allocators/stdalloc.h" +#include "allocators/win32_leakcheck.h" + +/* Fail any allocation until git_libgit2_init is called. */ +git_allocator git__allocator = { + git_failalloc_malloc, + git_failalloc_calloc, + git_failalloc_strdup, + git_failalloc_strndup, + git_failalloc_substrdup, + git_failalloc_realloc, + git_failalloc_reallocarray, + git_failalloc_mallocarray, + git_failalloc_free +}; + +static int setup_default_allocator(void) +{ +#if defined(GIT_WIN32_LEAKCHECK) + return git_win32_leakcheck_init_allocator(&git__allocator); +#else + return git_stdalloc_init_allocator(&git__allocator); +#endif +} + +int git_allocator_global_init(void) +{ + /* + * We don't want to overwrite any allocator which has been set + * before the init function is called. + */ + if (git__allocator.gmalloc != git_failalloc_malloc) + return 0; + + return setup_default_allocator(); +} + +int git_allocator_setup(git_allocator *allocator) +{ + if (!allocator) + return setup_default_allocator(); + + memcpy(&git__allocator, allocator, sizeof(*allocator)); + return 0; +} diff --git a/src/util/alloc.h b/src/util/alloc.h new file mode 100644 index 000000000..04fb7e101 --- /dev/null +++ b/src/util/alloc.h @@ -0,0 +1,40 @@ +/* + * 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_alloc_h__ +#define INCLUDE_alloc_h__ + +#include "git2/sys/alloc.h" + +extern git_allocator git__allocator; + +#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__) +#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__) +#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__) +#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__) +#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__) +#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__) +#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__) +#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__) +#define git__free git__allocator.gfree + +/** + * This function is being called by our global setup routines to + * initialize the standard allocator. + */ +int git_allocator_global_init(void); + +/** + * Switch out libgit2's global memory allocator + * + * @param allocator The new allocator that should be used. All function pointers + * of it need to be set correctly. + * @return An error code or 0. + */ +int git_allocator_setup(git_allocator *allocator); + +#endif diff --git a/src/util/allocators/failalloc.c b/src/util/allocators/failalloc.c new file mode 100644 index 000000000..5257d1dec --- /dev/null +++ b/src/util/allocators/failalloc.c @@ -0,0 +1,92 @@ +/* + * 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 "failalloc.h" + +void *git_failalloc_malloc(size_t len, const char *file, int line) +{ + GIT_UNUSED(len); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_strdup(const char *str, const char *file, int line) +{ + GIT_UNUSED(str); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line) +{ + GIT_UNUSED(str); + GIT_UNUSED(n); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line) +{ + GIT_UNUSED(start); + GIT_UNUSED(n); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line) +{ + GIT_UNUSED(ptr); + GIT_UNUSED(size); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(ptr); + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void git_failalloc_free(void *ptr) +{ + GIT_UNUSED(ptr); +} diff --git a/src/util/allocators/failalloc.h b/src/util/allocators/failalloc.h new file mode 100644 index 000000000..91264a0bb --- /dev/null +++ b/src/util/allocators/failalloc.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_allocators_failalloc_h__ +#define INCLUDE_allocators_failalloc_h__ + +#include "git2_util.h" + +extern void *git_failalloc_malloc(size_t len, const char *file, int line); +extern void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line); +extern char *git_failalloc_strdup(const char *str, const char *file, int line); +extern char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line); +extern char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line); +extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line); +extern void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line); +extern void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line); +extern void git_failalloc_free(void *ptr); + +#endif diff --git a/src/util/allocators/stdalloc.c b/src/util/allocators/stdalloc.c new file mode 100644 index 000000000..2b36d9f3d --- /dev/null +++ b/src/util/allocators/stdalloc.c @@ -0,0 +1,150 @@ +/* + * 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 "stdalloc.h" + +static void *stdalloc__malloc(size_t len, const char *file, int line) +{ + void *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!len) + return NULL; +#endif + + ptr = malloc(len); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!elsize || !nelem) + return NULL; +#endif + + ptr = calloc(nelem, elsize); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static char *stdalloc__strdup(const char *str, const char *file, int line) +{ + char *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + ptr = strdup(str); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static char *stdalloc__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 = stdalloc__malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *stdalloc__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 = stdalloc__malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!size) + return NULL; +#endif + + new_ptr = realloc(ptr, size); + + if (!new_ptr) + git_error_set_oom(); + + return new_ptr; +} + +static void *stdalloc__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 stdalloc__realloc(ptr, newsize, file, line); +} + +static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return stdalloc__reallocarray(NULL, nelem, elsize, file, line); +} + +static void stdalloc__free(void *ptr) +{ + free(ptr); +} + +int git_stdalloc_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = stdalloc__malloc; + allocator->gcalloc = stdalloc__calloc; + allocator->gstrdup = stdalloc__strdup; + allocator->gstrndup = stdalloc__strndup; + allocator->gsubstrdup = stdalloc__substrdup; + allocator->grealloc = stdalloc__realloc; + allocator->greallocarray = stdalloc__reallocarray; + allocator->gmallocarray = stdalloc__mallocarray; + allocator->gfree = stdalloc__free; + return 0; +} diff --git a/src/util/allocators/stdalloc.h b/src/util/allocators/stdalloc.h new file mode 100644 index 000000000..955038cb0 --- /dev/null +++ b/src/util/allocators/stdalloc.h @@ -0,0 +1,17 @@ +/* + * 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_allocators_stdalloc_h__ +#define INCLUDE_allocators_stdalloc_h__ + +#include "git2_util.h" + +#include "alloc.h" + +int git_stdalloc_init_allocator(git_allocator *allocator); + +#endif diff --git a/src/util/allocators/win32_leakcheck.c b/src/util/allocators/win32_leakcheck.c new file mode 100644 index 000000000..fe06a14af --- /dev/null +++ b/src/util/allocators/win32_leakcheck.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_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) + +#include "win32/w32_leakcheck.h" + +static void *leakcheck_malloc(size_t len, const char *file, int line) +{ + void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static void *leakcheck_calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *leakcheck_strdup(const char *str, const char *file, int line) +{ + char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *leakcheck_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 = leakcheck_malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *leakcheck_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 = leakcheck_malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!new_ptr) git_error_set_oom(); + return new_ptr; +} + +static void *leakcheck_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 leakcheck_realloc(ptr, newsize, file, line); +} + +static void *leakcheck_mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return leakcheck_reallocarray(NULL, nelem, elsize, file, line); +} + +static void leakcheck_free(void *ptr) +{ + free(ptr); +} + +int git_win32_leakcheck_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = leakcheck_malloc; + allocator->gcalloc = leakcheck_calloc; + allocator->gstrdup = leakcheck_strdup; + allocator->gstrndup = leakcheck_strndup; + allocator->gsubstrdup = leakcheck_substrdup; + allocator->grealloc = leakcheck_realloc; + allocator->greallocarray = leakcheck_reallocarray; + allocator->gmallocarray = leakcheck_mallocarray; + allocator->gfree = leakcheck_free; + return 0; +} + +#else + +int git_win32_leakcheck_init_allocator(git_allocator *allocator) +{ + GIT_UNUSED(allocator); + git_error_set(GIT_EINVALID, "leakcheck memory allocator not available"); + return -1; +} + +#endif diff --git a/src/util/allocators/win32_leakcheck.h b/src/util/allocators/win32_leakcheck.h new file mode 100644 index 000000000..edcd9307f --- /dev/null +++ b/src/util/allocators/win32_leakcheck.h @@ -0,0 +1,17 @@ +/* + * 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_allocators_win32_leakcheck_h +#define INCLUDE_allocators_win32_leakcheck_h + +#include "git2_util.h" + +#include "alloc.h" + +int git_win32_leakcheck_init_allocator(git_allocator *allocator); + +#endif diff --git a/src/util/array.h b/src/util/array.h new file mode 100644 index 000000000..cbab52ad1 --- /dev/null +++ b/src/util/array.h @@ -0,0 +1,124 @@ +/* + * 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_array_h__ +#define INCLUDE_array_h__ + +#include "git2_util.h" + +/* + * Use this to declare a typesafe resizable array of items, a la: + * + * git_array_t(int) my_ints = GIT_ARRAY_INIT; + * ... + * int *i = git_array_alloc(my_ints); + * GIT_ERROR_CHECK_ALLOC(i); + * ... + * git_array_clear(my_ints); + * + * You may also want to do things like: + * + * typedef git_array_t(my_struct) my_struct_array_t; + */ +#define git_array_t(type) struct { type *ptr; size_t size, asize; } + +#define GIT_ARRAY_INIT { NULL, 0, 0 } + +#define git_array_init(a) \ + do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) + +#define git_array_init_to_size(a, desired) \ + do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) + +#define git_array_clear(a) \ + do { git__free((a).ptr); git_array_init(a); } while (0) + +#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) + + +typedef git_array_t(char) git_array_generic_t; + +/* use a generic array for growth, return 0 on success */ +GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) +{ + volatile git_array_generic_t *a = _a; + size_t new_size; + char *new_array; + + if (a->size < 8) { + new_size = 8; + } else { + if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) + goto on_oom; + new_size /= 2; + } + + if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) + goto on_oom; + + a->ptr = new_array; + a->asize = new_size; + return 0; + +on_oom: + git_array_clear(*a); + return -1; +} + +#define git_array_alloc(a) \ + (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ + &(a).ptr[(a).size++] : (void *)NULL) + +#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) + +#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL) + +#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL) + +#define git_array_size(a) (a).size + +#define git_array_valid_index(a, i) ((i) < (a).size) + +#define git_array_foreach(a, i, element) \ + for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) + +GIT_INLINE(int) git_array__search( + size_t *out, + void *array_ptr, + size_t item_size, + size_t array_len, + int (*compare)(const void *, const void *), + const void *key) +{ + size_t lim; + unsigned char *part, *array = array_ptr, *base = array_ptr; + int cmp = -1; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1) * item_size; + cmp = (*compare)(key, part); + + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1 * item_size; + lim--; + } /* else take left partition */ + } + + if (out) + *out = (base - array) / item_size; + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +#define git_array_search(out, a, cmp, key) \ + git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ + (cmp), (key)) + +#endif diff --git a/src/util/assert_safe.h b/src/util/assert_safe.h new file mode 100644 index 000000000..cc0bac551 --- /dev/null +++ b/src/util/assert_safe.h @@ -0,0 +1,74 @@ +/* + * 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_assert_safe_h__ +#define INCLUDE_assert_safe_h__ + +/* + * In a debug build, we'll assert(3) for aide in debugging. In release + * builds, we will provide macros that will set an error message that + * indicate a failure and return. Note that memory leaks can occur in + * a release-mode assertion failure -- it is impractical to provide + * safe clean up routines in these very extreme failures, but care + * should be taken to not leak very large objects. + */ + +#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0 +# include + +# define GIT_ASSERT(expr) assert(expr) +# define GIT_ASSERT_ARG(expr) assert(expr) + +# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr) +# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr) + +# define GIT_ASSERT_WITH_CLEANUP(expr, cleanup) assert(expr) +#else + +/** Internal consistency check to stop the function. */ +# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1) + +/** + * Assert that a consumer-provided argument is valid, setting an + * actionable error message and returning -1 if it is not. + */ +# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1) + +/** Internal consistency check to return the `fail` param on failure. */ +# define GIT_ASSERT_WITH_RETVAL(expr, fail) \ + GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail) + +/** + * Assert that a consumer-provided argument is valid, setting an + * actionable error message and returning the `fail` param if not. + */ +# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \ + GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail) + +# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \ + if (!(expr)) { \ + git_error_set(code, "%s: '%s'", msg, #expr); \ + return fail; \ + } \ + } while(0) + +/** + * Go to to the given label on assertion failures; useful when you have + * taken a lock or otherwise need to release a resource. + */ +# define GIT_ASSERT_WITH_CLEANUP(expr, cleanup) \ + GIT_ASSERT__WITH_CLEANUP(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", cleanup) + +# define GIT_ASSERT__WITH_CLEANUP(expr, code, msg, cleanup) do { \ + if (!(expr)) { \ + git_error_set(code, "%s: '%s'", msg, #expr); \ + cleanup; \ + } \ + } while(0) + +#endif /* GIT_ASSERT_HARD */ + +#endif diff --git a/src/util/bitvec.h b/src/util/bitvec.h new file mode 100644 index 000000000..544832d95 --- /dev/null +++ b/src/util/bitvec.h @@ -0,0 +1,75 @@ +/* + * 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_bitvec_h__ +#define INCLUDE_bitvec_h__ + +#include "common.h" + +/* + * This is a silly little fixed length bit vector type that will store + * vectors of 64 bits or less directly in the structure and allocate + * memory for vectors longer than 64 bits. You can use the two versions + * transparently through the API and avoid heap allocation completely when + * using a short bit vector as a result. + */ +typedef struct { + size_t length; + union { + uint64_t *words; + uint64_t bits; + } u; +} git_bitvec; + +GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) +{ + memset(bv, 0x0, sizeof(*bv)); + + if (capacity >= 64) { + bv->length = (capacity / 64) + 1; + bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); + if (!bv->u.words) + return -1; + } + + return 0; +} + +#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) +#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) + +GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + uint64_t mask = GIT_BITVEC_MASK(bit); + + if (on) + *word |= mask; + else + *word &= ~mask; +} + +GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + return (*word & GIT_BITVEC_MASK(bit)) != 0; +} + +GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) +{ + if (!bv->length) + bv->u.bits = 0; + else + memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); +} + +GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) +{ + if (bv->length) + git__free(bv->u.words); +} + +#endif diff --git a/src/util/cc-compat.h b/src/util/cc-compat.h new file mode 100644 index 000000000..a0971e86c --- /dev/null +++ b/src/util/cc-compat.h @@ -0,0 +1,106 @@ +/* + * 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_cc_compat_h__ +#define INCLUDE_cc_compat_h__ + +#include + +/* + * See if our compiler is known to support flexible array members. + */ +#ifndef GIT_FLEX_ARRAY +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define GIT_FLEX_ARRAY /* empty */ +# elif defined(__GNUC__) +# if (__GNUC__ >= 3) +# define GIT_FLEX_ARRAY /* empty */ +# else +# define GIT_FLEX_ARRAY 0 /* older GNU extension */ +# endif +# endif + +/* Default to safer but a bit wasteful traditional style */ +# ifndef GIT_FLEX_ARRAY +# define GIT_FLEX_ARRAY 1 +# endif +#endif + +#if defined(__GNUC__) +# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size))) +#elif defined(_MSC_VER) +# define GIT_ALIGN(x,size) __declspec(align(size)) x +#else +# define GIT_ALIGN(x,size) x +#endif + +#if defined(__GNUC__) +# define GIT_UNUSED(x) \ + do { \ + __typeof__(x) _unused __attribute__((unused)); \ + _unused = (x); \ + } while (0) +#else +# define GIT_UNUSED(x) ((void)(x)) +#endif + +/* Define the printf format specifier 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" +# define PRIxZ "I64x" +# define PRIXZ "I64X" +# define PRIdZ "I64d" +# else +# define PRIuZ "Iu" +# define PRIxZ "Ix" +# define PRIXZ "IX" +# define PRIdZ "Id" +# endif + +#else +# define PRIuZ "zu" +# define PRIxZ "zx" +# define PRIXZ "zX" +# define PRIdZ "zd" +#endif + +/* Microsoft Visual C/C++ */ +#if defined(_MSC_VER) +/* disable "deprecated function" warnings */ +# pragma warning ( disable : 4996 ) +/* disable "conditional expression is constant" level 4 warnings */ +# pragma warning ( disable : 4127 ) +#endif + +#if defined (_MSC_VER) + typedef unsigned char bool; +# ifndef true +# define true 1 +# endif +# ifndef false +# define false 0 +# endif +#else +# include +#endif + +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst, src) __va_copy(dst, src) +# else +# define va_copy(dst, src) ((dst) = (src)) +# endif +#endif + +#endif diff --git a/src/util/date.c b/src/util/date.c new file mode 100644 index 000000000..4d757e21a --- /dev/null +++ b/src/util/date.c @@ -0,0 +1,899 @@ +/* + * 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_util.h" + +#ifndef GIT_WIN32 +#include +#endif + +#include "util.h" +#include "posix.h" +#include "date.h" + +#include +#include + +typedef enum { + DATE_NORMAL = 0, + DATE_RELATIVE, + DATE_SHORT, + DATE_LOCAL, + DATE_ISO8601, + DATE_RFC2822, + DATE_RAW +} date_mode; + +/* + * This is like mktime, but without normalization of tm_wday and tm_yday. + */ +static git_time_t tm_to_time_t(const struct tm *tm) +{ + static const int mdays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + int year = tm->tm_year - 70; + int month = tm->tm_mon; + int day = tm->tm_mday; + + if (year < 0 || year > 129) /* algo only works for 1970-2099 */ + return -1; + if (month < 0 || month > 11) /* array bounds */ + return -1; + if (month < 2 || (year + 2) % 4) + day--; + if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) + return -1; + return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + + tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; +} + +static const char *month_names[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + +static const char *weekday_names[] = { + "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" +}; + + + +/* + * Check these. And note how it doesn't do the summer-time conversion. + * + * In my world, it's always summer, and things are probably a bit off + * in other ways too. + */ +static const struct { + const char *name; + int offset; + int dst; +} timezone_names[] = { + { "IDLW", -12, 0, }, /* International Date Line West */ + { "NT", -11, 0, }, /* Nome */ + { "CAT", -10, 0, }, /* Central Alaska */ + { "HST", -10, 0, }, /* Hawaii Standard */ + { "HDT", -10, 1, }, /* Hawaii Daylight */ + { "YST", -9, 0, }, /* Yukon Standard */ + { "YDT", -9, 1, }, /* Yukon Daylight */ + { "PST", -8, 0, }, /* Pacific Standard */ + { "PDT", -8, 1, }, /* Pacific Daylight */ + { "MST", -7, 0, }, /* Mountain Standard */ + { "MDT", -7, 1, }, /* Mountain Daylight */ + { "CST", -6, 0, }, /* Central Standard */ + { "CDT", -6, 1, }, /* Central Daylight */ + { "EST", -5, 0, }, /* Eastern Standard */ + { "EDT", -5, 1, }, /* Eastern Daylight */ + { "AST", -3, 0, }, /* Atlantic Standard */ + { "ADT", -3, 1, }, /* Atlantic Daylight */ + { "WAT", -1, 0, }, /* West Africa */ + + { "GMT", 0, 0, }, /* Greenwich Mean */ + { "UTC", 0, 0, }, /* Universal (Coordinated) */ + { "Z", 0, 0, }, /* Zulu, alias for UTC */ + + { "WET", 0, 0, }, /* Western European */ + { "BST", 0, 1, }, /* British Summer */ + { "CET", +1, 0, }, /* Central European */ + { "MET", +1, 0, }, /* Middle European */ + { "MEWT", +1, 0, }, /* Middle European Winter */ + { "MEST", +1, 1, }, /* Middle European Summer */ + { "CEST", +1, 1, }, /* Central European Summer */ + { "MESZ", +1, 1, }, /* Middle European Summer */ + { "FWT", +1, 0, }, /* French Winter */ + { "FST", +1, 1, }, /* French Summer */ + { "EET", +2, 0, }, /* Eastern Europe */ + { "EEST", +2, 1, }, /* Eastern European Daylight */ + { "WAST", +7, 0, }, /* West Australian Standard */ + { "WADT", +7, 1, }, /* West Australian Daylight */ + { "CCT", +8, 0, }, /* China Coast */ + { "JST", +9, 0, }, /* Japan Standard */ + { "EAST", +10, 0, }, /* Eastern Australian Standard */ + { "EADT", +10, 1, }, /* Eastern Australian Daylight */ + { "GST", +10, 0, }, /* Guam Standard */ + { "NZT", +12, 0, }, /* New Zealand */ + { "NZST", +12, 0, }, /* New Zealand Standard */ + { "NZDT", +12, 1, }, /* New Zealand Daylight */ + { "IDLE", +12, 0, }, /* International Date Line East */ +}; + +static size_t match_string(const char *date, const char *str) +{ + size_t i = 0; + + for (i = 0; *date; date++, str++, i++) { + if (*date == *str) + continue; + if (toupper(*date) == toupper(*str)) + continue; + if (!isalnum(*date)) + break; + return 0; + } + return i; +} + +static int skip_alpha(const char *date) +{ + int i = 0; + do { + i++; + } while (isalpha(date[i])); + return i; +} + +/* +* Parse month, weekday, or timezone name +*/ +static size_t match_alpha(const char *date, struct tm *tm, int *offset) +{ + unsigned int i; + + for (i = 0; i < 12; i++) { + size_t match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + return match; + } + } + + for (i = 0; i < 7; i++) { + size_t match = match_string(date, weekday_names[i]); + if (match >= 3) { + tm->tm_wday = i; + return match; + } + } + + for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { + size_t match = match_string(date, timezone_names[i].name); + if (match >= 3 || match == strlen(timezone_names[i].name)) { + int off = timezone_names[i].offset; + + /* This is bogus, but we like summer */ + off += timezone_names[i].dst; + + /* Only use the tz name offset if we don't have anything better */ + if (*offset == -1) + *offset = 60*off; + + return match; + } + } + + if (match_string(date, "PM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 12; + return 2; + } + + if (match_string(date, "AM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 0; + return 2; + } + + /* BAD */ + return skip_alpha(date); +} + +static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) +{ + if (month > 0 && month < 13 && day > 0 && day < 32) { + struct tm check = *tm; + struct tm *r = (now_tm ? &check : tm); + git_time_t specified; + + r->tm_mon = month - 1; + r->tm_mday = day; + if (year == -1) { + if (!now_tm) + return 1; + r->tm_year = now_tm->tm_year; + } + else if (year >= 1970 && year < 2100) + r->tm_year = year - 1900; + else if (year > 70 && year < 100) + r->tm_year = year; + else if (year < 38) + r->tm_year = year + 100; + else + return 0; + if (!now_tm) + return 1; + + specified = tm_to_time_t(r); + + /* Be it commit time or author time, it does not make + * sense to specify timestamp way into the future. Make + * sure it is not later than ten days from now... + */ + if (now + 10*24*3600 < specified) + return 0; + tm->tm_mon = r->tm_mon; + tm->tm_mday = r->tm_mday; + if (year != -1) + tm->tm_year = r->tm_year; + return 1; + } + return 0; +} + +static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) +{ + time_t now; + struct tm now_tm; + struct tm *refuse_future; + long num2, num3; + + num2 = strtol(end+1, &end, 10); + num3 = -1; + if (*end == c && isdigit(end[1])) + num3 = strtol(end+1, &end, 10); + + /* Time? Date? */ + switch (c) { + case ':': + if (num3 < 0) + num3 = 0; + if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { + tm->tm_hour = num; + tm->tm_min = num2; + tm->tm_sec = num3; + break; + } + return 0; + + case '-': + case '/': + case '.': + now = time(NULL); + refuse_future = NULL; + if (p_gmtime_r(&now, &now_tm)) + refuse_future = &now_tm; + + if (num > 70) { + /* yyyy-mm-dd? */ + if (is_date(num, num2, num3, refuse_future, now, tm)) + break; + /* yyyy-dd-mm? */ + if (is_date(num, num3, num2, refuse_future, now, tm)) + break; + } + /* Our eastern European friends say dd.mm.yy[yy] + * is the norm there, so giving precedence to + * mm/dd/yy[yy] form only when separator is not '.' + */ + if (c != '.' && + is_date(num3, num, num2, refuse_future, now, tm)) + break; + /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ + if (is_date(num3, num2, num, refuse_future, now, tm)) + break; + /* Funny European mm.dd.yy */ + if (c == '.' && + is_date(num3, num, num2, refuse_future, now, tm)) + break; + return 0; + } + return end - date; +} + +/* + * Have we filled in any part of the time/date yet? + * We just do a binary 'and' to see if the sign bit + * is set in all the values. + */ +static int nodate(struct tm *tm) +{ + return (tm->tm_year & + tm->tm_mon & + tm->tm_mday & + tm->tm_hour & + tm->tm_min & + tm->tm_sec) < 0; +} + +/* + * We've seen a digit. Time? Year? Date? + */ +static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) +{ + size_t n; + char *end; + unsigned long num; + + num = strtoul(date, &end, 10); + + /* + * Seconds since 1970? We trigger on that for any numbers with + * more than 8 digits. This is because we don't want to rule out + * numbers like 20070606 as a YYYYMMDD date. + */ + if (num >= 100000000 && nodate(tm)) { + time_t time = num; + if (p_gmtime_r(&time, tm)) { + *tm_gmt = 1; + return end - date; + } + } + + /* + * Check for special formats: num[-.:/]num[same]num + */ + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + size_t match = match_multi_number(num, *end, date, end, tm); + if (match) + return match; + } + } + + /* + * None of the special formats? Try to guess what + * the number meant. We use the number of digits + * to make a more educated guess.. + */ + n = 0; + do { + n++; + } while (isdigit(date[n])); + + /* Four-digit year or a timezone? */ + if (n == 4) { + if (num <= 1400 && *offset == -1) { + unsigned int minutes = num % 100; + unsigned int hours = num / 100; + *offset = hours*60 + minutes; + } else if (num > 1900 && num < 2100) + tm->tm_year = num - 1900; + return n; + } + + /* + * Ignore lots of numerals. We took care of 4-digit years above. + * Days or months must be one or two digits. + */ + if (n > 2) + return n; + + /* + * NOTE! We will give precedence to day-of-month over month or + * year numbers in the 1-12 range. So 05 is always "mday 5", + * unless we already have a mday.. + * + * IOW, 01 Apr 05 parses as "April 1st, 2005". + */ + if (num > 0 && num < 32 && tm->tm_mday < 0) { + tm->tm_mday = num; + return n; + } + + /* Two-digit year? */ + if (n == 2 && tm->tm_year < 0) { + if (num < 10 && tm->tm_mday >= 0) { + tm->tm_year = num + 100; + return n; + } + if (num >= 70) { + tm->tm_year = num; + return n; + } + } + + if (num > 0 && num < 13 && tm->tm_mon < 0) + tm->tm_mon = num-1; + + return n; +} + +static size_t match_tz(const char *date, int *offp) +{ + char *end; + int hour = strtoul(date + 1, &end, 10); + size_t n = end - (date + 1); + int min = 0; + + if (n == 4) { + /* hhmm */ + min = hour % 100; + hour = hour / 100; + } else if (n != 2) { + min = 99; /* random stuff */ + } else if (*end == ':') { + /* hh:mm? */ + min = strtoul(end + 1, &end, 10); + if (end - (date + 1) != 5) + min = 99; /* random stuff */ + } /* otherwise we parsed "hh" */ + + /* + * Don't accept any random stuff. Even though some places have + * offset larger than 12 hours (e.g. Pacific/Kiritimati is at + * UTC+14), there is something wrong if hour part is much + * larger than that. We might also want to check that the + * minutes are divisible by 15 or something too. (Offset of + * Kathmandu, Nepal is UTC+5:45) + */ + if (min < 60 && hour < 24) { + int offset = hour * 60 + min; + if (*date == '-') + offset = -offset; + *offp = offset; + } + return end - date; +} + +/* + * Parse a string like "0 +0000" as ancient timestamp near epoch, but + * only when it appears not as part of any other string. + */ +static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset) +{ + char *end; + unsigned long stamp; + int ofs; + + if (*date < '0' || '9' <= *date) + return -1; + stamp = strtoul(date, &end, 10); + if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) + return -1; + date = end + 2; + ofs = strtol(date, &end, 10); + if ((*end != '\0' && (*end != '\n')) || end != date + 4) + return -1; + ofs = (ofs / 100) * 60 + (ofs % 100); + if (date[-1] == '-') + ofs = -ofs; + *timestamp = stamp; + *offset = ofs; + return 0; +} + +/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 + (i.e. English) day/month names, and it doesn't work correctly with %z. */ +static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset) +{ + struct tm tm; + int tm_gmt; + git_time_t dummy_timestamp; + int dummy_offset; + + if (!timestamp) + timestamp = &dummy_timestamp; + if (!offset) + offset = &dummy_offset; + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + tm.tm_isdst = -1; + tm.tm_hour = -1; + tm.tm_min = -1; + tm.tm_sec = -1; + *offset = -1; + tm_gmt = 0; + + if (*date == '@' && + !match_object_header_date(date + 1, timestamp, offset)) + return 0; /* success */ + for (;;) { + size_t match = 0; + unsigned char c = *date; + + /* Stop at end of string or newline */ + if (!c || c == '\n') + break; + + if (isalpha(c)) + match = match_alpha(date, &tm, offset); + else if (isdigit(c)) + match = match_digit(date, &tm, offset, &tm_gmt); + else if ((c == '-' || c == '+') && isdigit(date[1])) + match = match_tz(date, offset); + + if (!match) { + /* BAD */ + match = 1; + } + + date += match; + } + + /* mktime uses local timezone */ + *timestamp = tm_to_time_t(&tm); + if (*offset == -1) + *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60; + + if (*timestamp == (git_time_t)-1) + return -1; + + if (!tm_gmt) + *timestamp -= *offset * 60; + return 0; /* success */ +} + + +/* + * Relative time update (eg "2 days ago"). If we haven't set the time + * yet, we need to set it from current time. + */ +static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec) +{ + time_t n; + + if (tm->tm_mday < 0) + tm->tm_mday = now->tm_mday; + if (tm->tm_mon < 0) + tm->tm_mon = now->tm_mon; + if (tm->tm_year < 0) { + tm->tm_year = now->tm_year; + if (tm->tm_mon > now->tm_mon) + tm->tm_year--; + } + + n = mktime(tm) - sec; + p_localtime_r(&n, tm); + return n; +} + +static void date_now(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + update_tm(tm, now, 0); +} + +static void date_yesterday(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + update_tm(tm, now, 24*60*60); +} + +static void date_time(struct tm *tm, struct tm *now, int hour) +{ + if (tm->tm_hour < hour) + date_yesterday(tm, now, NULL); + tm->tm_hour = hour; + tm->tm_min = 0; + tm->tm_sec = 0; +} + +static void date_midnight(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 0); +} + +static void date_noon(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 12); +} + +static void date_tea(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 17); +} + +static void date_pm(struct tm *tm, struct tm *now, int *num) +{ + int hour, n = *num; + *num = 0; + GIT_UNUSED(now); + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12) + 12; +} + +static void date_am(struct tm *tm, struct tm *now, int *num) +{ + int hour, n = *num; + *num = 0; + GIT_UNUSED(now); + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12); +} + +static void date_never(struct tm *tm, struct tm *now, int *num) +{ + time_t n = 0; + GIT_UNUSED(now); + GIT_UNUSED(num); + p_localtime_r(&n, tm); +} + +static const struct special { + const char *name; + void (*fn)(struct tm *, struct tm *, int *); +} special[] = { + { "yesterday", date_yesterday }, + { "noon", date_noon }, + { "midnight", date_midnight }, + { "tea", date_tea }, + { "PM", date_pm }, + { "AM", date_am }, + { "never", date_never }, + { "now", date_now }, + { NULL } +}; + +static const char *number_name[] = { + "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "ten", +}; + +static const struct typelen { + const char *type; + int length; +} typelen[] = { + { "seconds", 1 }, + { "minutes", 60 }, + { "hours", 60*60 }, + { "days", 24*60*60 }, + { "weeks", 7*24*60*60 }, + { NULL } +}; + +static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) +{ + const struct typelen *tl; + const struct special *s; + const char *end = date; + int i; + + while (isalpha(*++end)) + /* scan to non-alpha */; + + for (i = 0; i < 12; i++) { + size_t match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + *touched = 1; + return end; + } + } + + for (s = special; s->name; s++) { + size_t len = strlen(s->name); + if (match_string(date, s->name) == len) { + s->fn(tm, now, num); + *touched = 1; + return end; + } + } + + if (!*num) { + for (i = 1; i < 11; i++) { + size_t len = strlen(number_name[i]); + if (match_string(date, number_name[i]) == len) { + *num = i; + *touched = 1; + return end; + } + } + if (match_string(date, "last") == 4) { + *num = 1; + *touched = 1; + } + return end; + } + + tl = typelen; + while (tl->type) { + size_t len = strlen(tl->type); + if (match_string(date, tl->type) >= len-1) { + update_tm(tm, now, tl->length * (unsigned long)*num); + *num = 0; + *touched = 1; + return end; + } + tl++; + } + + for (i = 0; i < 7; i++) { + size_t match = match_string(date, weekday_names[i]); + if (match >= 3) { + int diff, n = *num -1; + *num = 0; + + diff = tm->tm_wday - i; + if (diff <= 0) + n++; + diff += 7*n; + + update_tm(tm, now, diff * 24 * 60 * 60); + *touched = 1; + return end; + } + } + + if (match_string(date, "months") >= 5) { + int n; + update_tm(tm, now, 0); /* fill in date fields if needed */ + n = tm->tm_mon - *num; + *num = 0; + while (n < 0) { + n += 12; + tm->tm_year--; + } + tm->tm_mon = n; + *touched = 1; + return end; + } + + if (match_string(date, "years") >= 4) { + update_tm(tm, now, 0); /* fill in date fields if needed */ + tm->tm_year -= *num; + *num = 0; + *touched = 1; + return end; + } + + return end; +} + +static const char *approxidate_digit(const char *date, struct tm *tm, int *num) +{ + char *end; + unsigned long number = strtoul(date, &end, 10); + + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + size_t match = match_multi_number(number, *end, date, end, tm); + if (match) + return date + match; + } + } + + /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ + if (date[0] != '0' || end - date <= 2) + *num = number; + return end; +} + +/* + * Do we have a pending number at the end, or when + * we see a new one? Let's assume it's a month day, + * as in "Dec 6, 1992" + */ +static void pending_number(struct tm *tm, int *num) +{ + int number = *num; + + if (number) { + *num = 0; + if (tm->tm_mday < 0 && number < 32) + tm->tm_mday = number; + else if (tm->tm_mon < 0 && number < 13) + tm->tm_mon = number-1; + else if (tm->tm_year < 0) { + if (number > 1969 && number < 2100) + tm->tm_year = number - 1900; + else if (number > 69 && number < 100) + tm->tm_year = number; + else if (number < 38) + tm->tm_year = 100 + number; + /* We mess up for number = 00 ? */ + } + } +} + +static git_time_t approxidate_str(const char *date, + time_t time_sec, + int *error_ret) +{ + int number = 0; + int touched = 0; + struct tm tm = {0}, now; + + p_localtime_r(&time_sec, &tm); + now = tm; + + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + + for (;;) { + unsigned char c = *date; + if (!c) + break; + date++; + if (isdigit(c)) { + pending_number(&tm, &number); + date = approxidate_digit(date-1, &tm, &number); + touched = 1; + continue; + } + if (isalpha(c)) + date = approxidate_alpha(date-1, &tm, &now, &number, &touched); + } + pending_number(&tm, &number); + if (!touched) + *error_ret = -1; + return update_tm(&tm, &now, 0); +} + +int git_date_parse(git_time_t *out, const char *date) +{ + time_t time_sec; + git_time_t timestamp; + int offset, error_ret=0; + + if (!parse_date_basic(date, ×tamp, &offset)) { + *out = timestamp; + return 0; + } + + if (time(&time_sec) == -1) + return -1; + + *out = approxidate_str(date, time_sec, &error_ret); + return error_ret; +} + +int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) +{ + time_t t; + struct tm gmt; + + GIT_ASSERT_ARG(out); + + t = (time_t) (time + offset * 60); + + if (p_gmtime_r(&t, &gmt) == NULL) + return -1; + + return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d", + weekday_names[gmt.tm_wday], + gmt.tm_mday, + month_names[gmt.tm_mon], + gmt.tm_year + 1900, + gmt.tm_hour, gmt.tm_min, gmt.tm_sec, + offset / 60, offset % 60); +} + diff --git a/src/util/date.h b/src/util/date.h new file mode 100644 index 000000000..7ebd3c30e --- /dev/null +++ b/src/util/date.h @@ -0,0 +1,33 @@ +/* + * 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_date_h__ +#define INCLUDE_date_h__ + +#include "util.h" +#include "str.h" + +/* + * Parse a string into a value as a git_time_t. + * + * Sample valid input: + * - "yesterday" + * - "July 17, 2003" + * - "2003-7-17 08:23" + */ +extern int git_date_parse(git_time_t *out, const char *date); + +/* + * Format a git_time as a RFC2822 string + * + * @param out buffer to store formatted date + * @param time the time to be formatted + * @param offset the timezone offset + * @return 0 if successful; -1 on error + */ +extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset); + +#endif diff --git a/src/util/filebuf.c b/src/util/filebuf.c new file mode 100644 index 000000000..e014d43b2 --- /dev/null +++ b/src/util/filebuf.c @@ -0,0 +1,595 @@ +/* + * 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 "filebuf.h" + +#include "futils.h" + +static const size_t WRITE_BUFFER_SIZE = (4096 * 2); + +enum buferr_t { + BUFERR_OK = 0, + BUFERR_WRITE, + BUFERR_ZLIB, + BUFERR_MEM +}; + +#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } + +static int verify_last_error(git_filebuf *file) +{ + switch (file->last_error) { + case BUFERR_WRITE: + git_error_set(GIT_ERROR_OS, "failed to write out file"); + return -1; + + case BUFERR_MEM: + git_error_set_oom(); + return -1; + + case BUFERR_ZLIB: + git_error_set(GIT_ERROR_ZLIB, + "Buffer error when writing out ZLib data"); + return -1; + + default: + return 0; + } +} + +static int lock_file(git_filebuf *file, int flags, mode_t mode) +{ + if (git_fs_path_exists(file->path_lock) == true) { + 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_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 { + file->fd = git_futils_creat_locked(file->path_lock, mode); + } + + if (file->fd < 0) + return file->fd; + + file->fd_is_open = true; + + if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { + git_file source; + char buffer[GIT_BUFSIZE_FILEIO]; + ssize_t read_bytes; + int error = 0; + + source = p_open(file->path_original, O_RDONLY); + if (source < 0) { + git_error_set(GIT_ERROR_OS, + "failed to open file '%s' for reading", + file->path_original); + return -1; + } + + while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { + if ((error = p_write(file->fd, buffer, read_bytes)) < 0) + break; + if (file->compute_digest) + git_hash_update(&file->digest, buffer, read_bytes); + } + + p_close(source); + + if (read_bytes < 0) { + git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); + return -1; + } else if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); + return -1; + } + } + + return 0; +} + +void git_filebuf_cleanup(git_filebuf *file) +{ + if (file->fd_is_open && file->fd >= 0) + p_close(file->fd); + + if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) + p_unlink(file->path_lock); + + if (file->compute_digest) { + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + } + + if (file->buffer) + git__free(file->buffer); + + /* use the presence of z_buf to decide if we need to deflateEnd */ + if (file->z_buf) { + git__free(file->z_buf); + deflateEnd(&file->zs); + } + + if (file->path_original) + git__free(file->path_original); + if (file->path_lock) + git__free(file->path_lock); + + memset(file, 0x0, sizeof(git_filebuf)); + file->fd = -1; +} + +GIT_INLINE(int) flush_buffer(git_filebuf *file) +{ + int result = file->write(file, file->buffer, file->buf_pos); + file->buf_pos = 0; + return result; +} + +int git_filebuf_flush(git_filebuf *file) +{ + return flush_buffer(file); +} + +static int write_normal(git_filebuf *file, void *source, size_t len) +{ + if (len > 0) { + if (p_write(file->fd, (void *)source, len) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + + if (file->compute_digest) + git_hash_update(&file->digest, source, len); + } + + return 0; +} + +static int write_deflate(git_filebuf *file, void *source, size_t len) +{ + z_stream *zs = &file->zs; + + if (len > 0 || file->flush_mode == Z_FINISH) { + zs->next_in = source; + zs->avail_in = (uInt)len; + + do { + size_t have; + + zs->next_out = file->z_buf; + zs->avail_out = (uInt)file->buf_size; + + if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { + file->last_error = BUFERR_ZLIB; + return -1; + } + + have = file->buf_size - (size_t)zs->avail_out; + + if (p_write(file->fd, file->z_buf, have) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + + } while (zs->avail_out == 0); + + GIT_ASSERT(zs->avail_in == 0); + + if (file->compute_digest) + git_hash_update(&file->digest, source, len); + } + + return 0; +} + +#define MAX_SYMLINK_DEPTH 5 + +static int resolve_symlink(git_str *out, const char *path) +{ + int i, error, root; + ssize_t ret; + struct stat st; + git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; + + if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || + (error = git_str_puts(&curpath, path)) < 0) + return error; + + for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { + error = p_lstat(curpath.ptr, &st); + if (error < 0 && errno == ENOENT) { + error = git_str_puts(out, curpath.ptr); + goto cleanup; + } + + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (!S_ISLNK(st.st_mode)) { + error = git_str_puts(out, curpath.ptr); + goto cleanup; + } + + ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); + if (ret < 0) { + git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (ret == GIT_PATH_MAX) { + git_error_set(GIT_ERROR_INVALID, "symlink target too long"); + error = -1; + goto cleanup; + } + + /* readlink(2) won't NUL-terminate for us */ + target.ptr[ret] = '\0'; + target.size = ret; + + root = git_fs_path_root(target.ptr); + if (root >= 0) { + if ((error = git_str_sets(&curpath, target.ptr)) < 0) + goto cleanup; + } else { + git_str dir = GIT_STR_INIT; + + if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) + goto cleanup; + + git_str_swap(&curpath, &dir); + git_str_dispose(&dir); + + if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) + goto cleanup; + } + } + + git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); + error = -1; + +cleanup: + git_str_dispose(&curpath); + git_str_dispose(&target); + return error; +} + +int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) +{ + return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); +} + +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) +{ + int compression, error = -1; + size_t path_len, alloc_len; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(path); + GIT_ASSERT(file->buffer == NULL); + + memset(file, 0x0, sizeof(git_filebuf)); + + if (flags & GIT_FILEBUF_DO_NOT_BUFFER) + file->do_not_buffer = true; + + if (flags & GIT_FILEBUF_FSYNC) + file->do_fsync = true; + + file->buf_size = size; + file->buf_pos = 0; + file->fd = -1; + file->last_error = BUFERR_OK; + + /* Allocate the main cache buffer */ + if (!file->do_not_buffer) { + file->buffer = git__malloc(file->buf_size); + GIT_ERROR_CHECK_ALLOC(file->buffer); + } + + /* If we are hashing on-write, allocate a new hash context */ + if (flags & GIT_FILEBUF_HASH_CONTENTS) { + file->compute_digest = 1; + + if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) + goto cleanup; + } + + compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; + + /* If we are deflating on-write, */ + if (compression != 0) { + /* Initialize the ZLib stream */ + if (deflateInit(&file->zs, compression) != Z_OK) { + git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); + goto cleanup; + } + + /* Allocate the Zlib cache buffer */ + file->z_buf = git__malloc(file->buf_size); + GIT_ERROR_CHECK_ALLOC(file->z_buf); + + /* Never flush */ + file->flush_mode = Z_NO_FLUSH; + file->write = &write_deflate; + } else { + file->write = &write_normal; + } + + /* If we are writing to a temp file */ + if (flags & GIT_FILEBUF_TEMPORARY) { + git_str tmp_path = GIT_STR_INIT; + + /* Open the file as temporary for locking */ + file->fd = git_futils_mktmp(&tmp_path, path, mode); + + if (file->fd < 0) { + git_str_dispose(&tmp_path); + goto cleanup; + } + file->fd_is_open = true; + file->created_lock = true; + + /* No original path */ + file->path_original = NULL; + file->path_lock = git_str_detach(&tmp_path); + GIT_ERROR_CHECK_ALLOC(file->path_lock); + } else { + git_str resolved_path = GIT_STR_INIT; + + if ((error = resolve_symlink(&resolved_path, path)) < 0) + goto cleanup; + + /* Save the original path of the file */ + path_len = resolved_path.size; + file->path_original = git_str_detach(&resolved_path); + + /* create the locking path by appending ".lock" to the original */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); + file->path_lock = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(file->path_lock); + + memcpy(file->path_lock, file->path_original, path_len); + memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); + + if (git_fs_path_isdir(file->path_original)) { + git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); + error = GIT_EDIRECTORY; + goto cleanup; + } + + /* open the file for locking */ + if ((error = lock_file(file, flags, mode)) < 0) + goto cleanup; + + file->created_lock = true; + } + + return 0; + +cleanup: + git_filebuf_cleanup(file); + return error; +} + +int git_filebuf_hash(unsigned char *out, git_filebuf *file) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(file->compute_digest); + + flush_buffer(file); + + if (verify_last_error(file) < 0) + return -1; + + git_hash_final(out, &file->digest); + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + + return 0; +} + +int git_filebuf_commit_at(git_filebuf *file, const char *path) +{ + git__free(file->path_original); + file->path_original = git__strdup(path); + GIT_ERROR_CHECK_ALLOC(file->path_original); + + return git_filebuf_commit(file); +} + +int git_filebuf_commit(git_filebuf *file) +{ + /* temporary files cannot be committed */ + GIT_ASSERT_ARG(file); + GIT_ASSERT(file->path_original); + + file->flush_mode = Z_FINISH; + flush_buffer(file); + + if (verify_last_error(file) < 0) + goto on_error; + + file->fd_is_open = false; + + if (file->do_fsync && p_fsync(file->fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); + goto on_error; + } + + if (p_close(file->fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); + goto on_error; + } + + file->fd = -1; + + if (p_rename(file->path_lock, file->path_original) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); + goto on_error; + } + + if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) + goto on_error; + + file->did_rename = true; + + git_filebuf_cleanup(file); + return 0; + +on_error: + git_filebuf_cleanup(file); + return -1; +} + +GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) +{ + memcpy(file->buffer + file->buf_pos, buf, len); + file->buf_pos += len; +} + +int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) +{ + const unsigned char *buf = buff; + + ENSURE_BUF_OK(file); + + if (file->do_not_buffer) + return file->write(file, (void *)buff, len); + + for (;;) { + size_t space_left = file->buf_size - file->buf_pos; + + /* cache if it's small */ + if (space_left > len) { + add_to_cache(file, buf, len); + return 0; + } + + add_to_cache(file, buf, space_left); + if (flush_buffer(file) < 0) + return -1; + + len -= space_left; + buf += space_left; + } +} + +int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) +{ + size_t space_left = file->buf_size - file->buf_pos; + + *buffer = NULL; + + ENSURE_BUF_OK(file); + + if (len > file->buf_size) { + file->last_error = BUFERR_MEM; + return -1; + } + + if (space_left <= len) { + if (flush_buffer(file) < 0) + return -1; + } + + *buffer = (file->buffer + file->buf_pos); + file->buf_pos += len; + + return 0; +} + +int git_filebuf_printf(git_filebuf *file, const char *format, ...) +{ + va_list arglist; + size_t space_left, len, alloclen; + int written, res; + char *tmp_buffer; + + ENSURE_BUF_OK(file); + + space_left = file->buf_size - file->buf_pos; + + do { + va_start(arglist, format); + written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + va_end(arglist); + + if (written < 0) { + file->last_error = BUFERR_MEM; + return -1; + } + + len = written; + if (len + 1 <= space_left) { + file->buf_pos += len; + return 0; + } + + if (flush_buffer(file) < 0) + return -1; + + space_left = file->buf_size - file->buf_pos; + + } while (len + 1 <= space_left); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || + !(tmp_buffer = git__malloc(alloclen))) { + file->last_error = BUFERR_MEM; + return -1; + } + + va_start(arglist, format); + written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); + va_end(arglist); + + if (written < 0) { + git__free(tmp_buffer); + file->last_error = BUFERR_MEM; + return -1; + } + + res = git_filebuf_write(file, tmp_buffer, len); + git__free(tmp_buffer); + + return res; +} + +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) +{ + int res; + struct stat st; + + if (file->fd_is_open) + res = p_fstat(file->fd, &st); + else + res = p_stat(file->path_original, &st); + + if (res < 0) { + git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", + file->path_original); + return res; + } + + if (mtime) + *mtime = st.st_mtime; + if (size) + *size = (size_t)st.st_size; + + return 0; +} diff --git a/src/util/filebuf.h b/src/util/filebuf.h new file mode 100644 index 000000000..4a61ae4e3 --- /dev/null +++ b/src/util/filebuf.h @@ -0,0 +1,94 @@ +/* + * 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_filebuf_h__ +#define INCLUDE_filebuf_h__ + +#include "git2_util.h" + +#include "futils.h" +#include "hash.h" +#include + +#ifdef GIT_THREADS +# define GIT_FILEBUF_THREADS +#endif + +#define GIT_FILEBUF_HASH_CONTENTS (1 << 0) +#define GIT_FILEBUF_APPEND (1 << 2) +#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) +#define GIT_FILEBUF_DEFLATE_SHIFT (7) + +#define GIT_FILELOCK_EXTENSION ".lock\0" +#define GIT_FILELOCK_EXTLENGTH 6 + +typedef struct git_filebuf git_filebuf; +struct git_filebuf { + char *path_original; + char *path_lock; + + int (*write)(git_filebuf *file, void *source, size_t len); + + bool compute_digest; + git_hash_ctx digest; + + unsigned char *buffer; + unsigned char *z_buf; + + z_stream zs; + int flush_mode; + + size_t buf_size, buf_pos; + git_file fd; + bool fd_is_open; + bool created_lock; + bool did_rename; + bool do_not_buffer; + bool do_fsync; + int last_error; +}; + +#define GIT_FILEBUF_INIT {0} + +/* + * The git_filebuf object lifecycle is: + * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. + * + * - Call git_filebuf_open() to initialize the filebuf for use. + * + * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), + * git_filebuf_reserve() as you like. The error codes for these + * functions don't need to be checked. They are stored internally + * by the file buffer. + * + * - While you are writing, you may call git_filebuf_hash() to get + * the hash of all you have written so far. This function will + * fail if any of the previous writes to the buffer failed. + * + * - To close the git_filebuf, you may call git_filebuf_commit() or + * git_filebuf_commit_at() to save the file, or + * git_filebuf_cleanup() to abandon the file. All of these will + * free the git_filebuf object. Likewise, all of these will fail + * if any of the previous writes to the buffer failed, and set + * an error code accordingly. + */ +int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); +int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); +int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); + +int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); +int git_filebuf_commit(git_filebuf *lock); +int git_filebuf_commit_at(git_filebuf *lock, const char *path); +void git_filebuf_cleanup(git_filebuf *lock); +int git_filebuf_hash(unsigned char *out, git_filebuf *file); +int git_filebuf_flush(git_filebuf *file); +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); + +#endif diff --git a/src/util/fs_path.c b/src/util/fs_path.c new file mode 100644 index 000000000..6c87bfd01 --- /dev/null +++ b/src/util/fs_path.c @@ -0,0 +1,2066 @@ +/* + * 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 "fs_path.h" + +#include "git2_util.h" +#include "futils.h" +#include "posix.h" +#ifdef GIT_WIN32 +#include "win32/posix.h" +#include "win32/w32_buffer.h" +#include "win32/w32_util.h" +#include "win32/version.h" +#include +#else +#include +#endif +#include +#include + +#define ensure_error_set(code) do { \ + const git_error *e = git_error_last(); \ + if (!e || !e->message) \ + git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, \ + "filesystem callback returned %d", code); \ + } while(0) + +static int dos_drive_prefix_length(const char *path) +{ + int i; + + /* + * Does it start with an ASCII letter (i.e. highest bit not set), + * followed by a colon? + */ + if (!(0x80 & (unsigned char)*path)) + return *path && path[1] == ':' ? 2 : 0; + + /* + * While drive letters must be letters of the English alphabet, it is + * possible to assign virtually _any_ Unicode character via `subst` as + * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff + * like this: + * + * subst ֍: %USERPROFILE%\Desktop + */ + for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) + ; /* skip first UTF-8 character */ + return path[i] == ':' ? i + 1 : 0; +} + +#ifdef GIT_WIN32 +static bool looks_like_network_computer_name(const char *path, int pos) +{ + if (pos < 3) + return false; + + if (path[0] != '/' || path[1] != '/') + return false; + + while (pos-- > 2) { + if (path[pos] == '/') + return false; + } + + return true; +} +#endif + +/* + * Based on the Android implementation, BSD licensed. + * http://android.git.kernel.org/ + * + * 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. + */ +int git_fs_path_basename_r(git_str *buffer, const char *path) +{ + const char *endp, *startp; + int len, result; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + startp = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); + +Exit: + result = len; + + if (buffer != NULL && git_str_set(buffer, startp, len) < 0) + return -1; + + return result; +} + +/* + * Determine if the path is a Windows prefix and, if so, returns + * its actual length. If it is not a prefix, returns -1. + */ +static int win32_prefix_length(const char *path, int len) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(path); + GIT_UNUSED(len); +#else + /* + * Mimic unix behavior where '/.git' returns '/': 'C:/.git' + * will return 'C:/' here + */ + if (dos_drive_prefix_length(path) == len) + return len; + + /* + * Similarly checks if we're dealing with a network computer name + * '//computername/.git' will return '//computername/' + */ + if (looks_like_network_computer_name(path, len)) + return len; +#endif + + return -1; +} + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_fs_path_dirname_r(git_str *buffer, const char *path) +{ + const char *endp; + int is_prefix = 0, len; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + path = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + return -1; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { + is_prefix = 1; + goto Exit; + } + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + return -1; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { + is_prefix = 1; + goto Exit; + } + + /* Cast is safe because max path < max int */ + len = (int)(endp - path + 1); + +Exit: + if (buffer) { + if (git_str_set(buffer, path, len) < 0) + return -1; + if (is_prefix && git_str_putc(buffer, '/') < 0) + return -1; + } + + return len; +} + + +char *git_fs_path_dirname(const char *path) +{ + git_str buf = GIT_STR_INIT; + char *dirname; + + git_fs_path_dirname_r(&buf, path); + dirname = git_str_detach(&buf); + git_str_dispose(&buf); /* avoid memleak if error occurs */ + + return dirname; +} + +char *git_fs_path_basename(const char *path) +{ + git_str buf = GIT_STR_INIT; + char *basename; + + git_fs_path_basename_r(&buf, path); + basename = git_str_detach(&buf); + git_str_dispose(&buf); /* avoid memleak if error occurs */ + + return basename; +} + +size_t git_fs_path_basename_offset(git_str *buffer) +{ + ssize_t slash; + + if (!buffer || buffer->size <= 0) + return 0; + + slash = git_str_rfind_next(buffer, '/'); + + if (slash >= 0 && buffer->ptr[slash] == '/') + return (size_t)(slash + 1); + + return 0; +} + +int git_fs_path_root(const char *path) +{ + int offset = 0, prefix_len; + + /* Does the root of the path look like a windows drive ? */ + if ((prefix_len = dos_drive_prefix_length(path))) + offset += prefix_len; + +#ifdef GIT_WIN32 + /* Are we dealing with a windows network path? */ + else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || + (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) + { + offset += 2; + + /* Skip the computer name segment */ + while (path[offset] && path[offset] != '/' && path[offset] != '\\') + offset++; + } + + if (path[offset] == '\\') + return offset; +#endif + + if (path[offset] == '/') + return offset; + + return -1; /* Not a real error - signals that path is not rooted */ +} + +static void path_trim_slashes(git_str *path) +{ + int ceiling = git_fs_path_root(path->ptr) + 1; + + if (ceiling < 0) + return; + + while (path->size > (size_t)ceiling) { + if (path->ptr[path->size-1] != '/') + break; + + path->ptr[path->size-1] = '\0'; + path->size--; + } +} + +int git_fs_path_join_unrooted( + git_str *path_out, const char *path, const char *base, ssize_t *root_at) +{ + ssize_t root; + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(path); + + root = (ssize_t)git_fs_path_root(path); + + if (base != NULL && root < 0) { + if (git_str_joinpath(path_out, base, path) < 0) + return -1; + + root = (ssize_t)strlen(base); + } else { + if (git_str_sets(path_out, path) < 0) + return -1; + + if (root < 0) + root = 0; + else if (base) + git_fs_path_equal_or_prefixed(base, path, &root); + } + + if (root_at) + *root_at = root; + + return 0; +} + +void git_fs_path_squash_slashes(git_str *path) +{ + char *p, *q; + + if (path->size == 0) + return; + + for (p = path->ptr, q = path->ptr; *q; p++, q++) { + *p = *q; + + while (*q == '/' && *(q+1) == '/') { + path->size--; + q++; + } + } + + *p = '\0'; +} + +int git_fs_path_prettify(git_str *path_out, const char *path, const char *base) +{ + char buf[GIT_PATH_MAX]; + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(path); + + /* construct path if needed */ + if (base != NULL && git_fs_path_root(path) < 0) { + if (git_str_joinpath(path_out, base, path) < 0) + return -1; + path = path_out->ptr; + } + + if (p_realpath(path, buf) == NULL) { + /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */ + int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; + git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path); + + git_str_clear(path_out); + + return error; + } + + return git_str_sets(path_out, buf); +} + +int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base) +{ + int error = git_fs_path_prettify(path_out, path, base); + return (error < 0) ? error : git_fs_path_to_dir(path_out); +} + +int git_fs_path_to_dir(git_str *path) +{ + if (path->asize > 0 && + git_str_len(path) > 0 && + path->ptr[git_str_len(path) - 1] != '/') + git_str_putc(path, '/'); + + return git_str_oom(path) ? -1 : 0; +} + +void git_fs_path_string_to_dir(char *path, size_t size) +{ + size_t end = strlen(path); + + if (end && path[end - 1] != '/' && end < size) { + path[end] = '/'; + path[end + 1] = '\0'; + } +} + +int git__percent_decode(git_str *decoded_out, const char *input) +{ + int len, hi, lo, i; + + GIT_ASSERT_ARG(decoded_out); + GIT_ASSERT_ARG(input); + + len = (int)strlen(input); + git_str_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + if (git_str_putc(decoded_out, c) < 0) + return -1; + } + + return 0; +} + +static int error_invalid_local_file_uri(const char *uri) +{ + git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri); + return -1; +} + +static int local_file_url_prefixlen(const char *file_url) +{ + int len = -1; + + if (git__prefixcmp(file_url, "file://") == 0) { + if (file_url[7] == '/') + len = 8; + else if (git__prefixcmp(file_url + 7, "localhost/") == 0) + len = 17; + } + + return len; +} + +bool git_fs_path_is_local_file_url(const char *file_url) +{ + return (local_file_url_prefixlen(file_url) > 0); +} + +int git_fs_path_fromurl(git_str *local_path_out, const char *file_url) +{ + int offset; + + GIT_ASSERT_ARG(local_path_out); + GIT_ASSERT_ARG(file_url); + + if ((offset = local_file_url_prefixlen(file_url)) < 0 || + file_url[offset] == '\0' || file_url[offset] == '/') + return error_invalid_local_file_uri(file_url); + +#ifndef GIT_WIN32 + offset--; /* A *nix absolute path starts with a forward slash */ +#endif + + git_str_clear(local_path_out); + return git__percent_decode(local_path_out, file_url + offset); +} + +int git_fs_path_walk_up( + git_str *path, + const char *ceiling, + int (*cb)(void *data, const char *), + void *data) +{ + int error = 0; + git_str iter; + ssize_t stop = 0, scan; + char oldc = '\0'; + + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == 0) + stop = (ssize_t)strlen(ceiling); + else + stop = git_str_len(path); + } + scan = git_str_len(path); + + /* empty path: yield only once */ + if (!scan) { + error = cb(data, ""); + if (error) + ensure_error_set(error); + return error; + } + + iter.ptr = path->ptr; + iter.size = git_str_len(path); + iter.asize = path->asize; + + while (scan >= stop) { + error = cb(data, iter.ptr); + iter.ptr[scan] = oldc; + + if (error) { + ensure_error_set(error); + break; + } + + scan = git_str_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + /* relative path: yield for the last component */ + if (!error && stop == 0 && iter.ptr[0] != '/') { + error = cb(data, ""); + if (error) + ensure_error_set(error); + } + + return error; +} + +bool git_fs_path_exists(const char *path) +{ + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + return p_access(path, F_OK) == 0; +} + +bool git_fs_path_isdir(const char *path) +{ + struct stat st; + if (p_stat(path, &st) < 0) + return false; + + return S_ISDIR(st.st_mode) != 0; +} + +bool git_fs_path_isfile(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0; +} + +bool git_fs_path_islink(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_lstat(path, &st) < 0) + return false; + + return S_ISLNK(st.st_mode) != 0; +} + +#ifdef GIT_WIN32 + +bool git_fs_path_is_empty_dir(const char *path) +{ + git_win32_path filter_w; + bool empty = false; + + if (git_win32__findfirstfile_filter(filter_w, path)) { + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(filter_w, &findData); + + /* FindFirstFile will fail if there are no children to the given + * path, which can happen if the given path is a file (and obviously + * has no children) or if the given path is an empty mount point. + * (Most directories have at least directory entries '.' and '..', + * but ridiculously another volume mounted in another drive letter's + * path space do not, and thus have nothing to enumerate.) If + * FindFirstFile fails, check if this is a directory-like thing + * (a mount point). + */ + if (hFind == INVALID_HANDLE_VALUE) + return git_fs_path_isdir(path); + + /* If the find handle was created successfully, then it's a directory */ + empty = true; + + do { + /* Allow the enumeration to return . and .. and still be considered + * empty. In the special case of drive roots (i.e. C:\) where . and + * .. do not occur, we can still consider the path to be an empty + * directory if there's nothing there. */ + if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) { + empty = false; + break; + } + } while (FindNextFileW(hFind, &findData)); + + FindClose(hFind); + } + + return empty; +} + +#else + +static int path_found_entry(void *payload, git_str *path) +{ + GIT_UNUSED(payload); + return !git_fs_path_is_dot_or_dotdot(path->ptr); +} + +bool git_fs_path_is_empty_dir(const char *path) +{ + int error; + git_str dir = GIT_STR_INIT; + + if (!git_fs_path_isdir(path)) + return false; + + if ((error = git_str_sets(&dir, path)) != 0) + git_error_clear(); + else + error = git_fs_path_direach(&dir, 0, path_found_entry, NULL); + + git_str_dispose(&dir); + + return !error; +} + +#endif + +int git_fs_path_set_error(int errno_value, const char *path, const char *action) +{ + switch (errno_value) { + case ENOENT: + case ENOTDIR: + git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action); + return GIT_ENOTFOUND; + + case EINVAL: + case ENAMETOOLONG: + git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path); + return GIT_EINVALIDSPEC; + + case EEXIST: + git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path); + return GIT_EEXISTS; + + case EACCES: + git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path); + return GIT_ELOCKED; + + default: + git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path); + return -1; + } +} + +int git_fs_path_lstat(const char *path, struct stat *st) +{ + if (p_lstat(path, st) == 0) + return 0; + + return git_fs_path_set_error(errno, path, "stat"); +} + +static bool _check_dir_contents( + git_str *dir, + const char *sub, + bool (*predicate)(const char *)) +{ + bool result; + size_t dir_size = git_str_len(dir); + size_t sub_size = strlen(sub); + size_t alloc_size; + + /* leave base valid even if we could not make space for subdir */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) || + GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) || + git_str_try_grow(dir, alloc_size, false) < 0) + return false; + + /* save excursion */ + if (git_str_joinpath(dir, dir->ptr, sub) < 0) + return false; + + result = predicate(dir->ptr); + + /* restore path */ + git_str_truncate(dir, dir_size); + return result; +} + +bool git_fs_path_contains(git_str *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_fs_path_exists); +} + +bool git_fs_path_contains_dir(git_str *base, const char *subdir) +{ + return _check_dir_contents(base, subdir, &git_fs_path_isdir); +} + +bool git_fs_path_contains_file(git_str *base, const char *file) +{ + return _check_dir_contents(base, file, &git_fs_path_isfile); +} + +int git_fs_path_find_dir(git_str *dir) +{ + int error = 0; + char buf[GIT_PATH_MAX]; + + if (p_realpath(dir->ptr, buf) != NULL) + error = git_str_sets(dir, buf); + + /* call dirname if this is not a directory */ + if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */ + error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0; + + if (!error) + error = git_fs_path_to_dir(dir); + + return error; +} + +int git_fs_path_resolve_relative(git_str *path, size_t ceiling) +{ + char *base, *to, *from, *next; + size_t len; + + GIT_ERROR_CHECK_ALLOC_STR(path); + + if (ceiling > path->size) + ceiling = path->size; + + /* recognize drive prefixes, etc. that should not be backed over */ + if (ceiling == 0) + ceiling = git_fs_path_root(path->ptr) + 1; + + /* recognize URL prefixes that should not be backed over */ + if (ceiling == 0) { + for (next = path->ptr; *next && git__isalpha(*next); ++next); + if (next[0] == ':' && next[1] == '/' && next[2] == '/') + ceiling = (next + 3) - path->ptr; + } + + base = to = from = path->ptr + ceiling; + + while (*from) { + for (next = from; *next && *next != '/'; ++next); + + len = next - from; + + if (len == 1 && from[0] == '.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == '.' && from[1] == '.') { + /* error out if trying to up one from a hard base */ + if (to == base && ceiling != 0) { + git_error_set(GIT_ERROR_INVALID, + "cannot strip root component off url"); + return -1; + } + + /* no more path segments to strip, + * use '../' as a new base path */ + if (to == base) { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + /* this is now the base, can't back up from a + * relative prefix */ + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + } else { + if (*next == '/' && *from != '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + } + + from += len; + + while (*from == '/') from++; + } + + *to = '\0'; + + path->size = to - path->ptr; + + return 0; +} + +int git_fs_path_apply_relative(git_str *target, const char *relpath) +{ + return git_str_joinpath(target, git_str_cstr(target), relpath) || + git_fs_path_resolve_relative(target, 0); +} + +int git_fs_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)) +{ + unsigned char c1, c2; + size_t len = len1 < len2 ? len1 : len2; + int cmp; + + cmp = compare(name1, name2, len); + if (cmp) + return cmp; + + c1 = name1[len]; + c2 = name2[len]; + + if (c1 == '\0' && isdir1) + c1 = '/'; + + if (c2 == '\0' && isdir2) + c2 = '/'; + + return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; +} + +size_t git_fs_path_common_dirlen(const char *one, const char *two) +{ + const char *p, *q, *dirsep = NULL; + + for (p = one, q = two; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') + dirsep = p; + else if (*p != *q) + break; + } + + return dirsep ? (dirsep - one) + 1 : 0; +} + +int git_fs_path_make_relative(git_str *path, const char *parent) +{ + const char *p, *q, *p_dirsep, *q_dirsep; + size_t plen = path->size, newlen, alloclen, depth = 1, i, offset; + + for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') { + p_dirsep = p; + q_dirsep = q; + } + else if (*p != *q) + break; + } + + /* need at least 1 common path segment */ + if ((p_dirsep == path->ptr || q_dirsep == parent) && + (*p_dirsep != '/' || *q_dirsep != '/')) { + git_error_set(GIT_ERROR_INVALID, + "%s is not a parent of %s", parent, path->ptr); + return GIT_ENOTFOUND; + } + + if (*p == '/' && !*q) + p++; + else if (!*p && *q == '/') + q++; + else if (!*p && !*q) + return git_str_clear(path), 0; + else { + p = p_dirsep + 1; + q = q_dirsep + 1; + } + + plen -= (p - path->ptr); + + if (!*q) + return git_str_set(path, p, plen); + + for (; (q = strchr(q, '/')) && *(q + 1); q++) + depth++; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3); + GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1); + + /* save the offset as we might realllocate the pointer */ + offset = p - path->ptr; + if (git_str_try_grow(path, alloclen, 1) < 0) + return -1; + p = path->ptr + offset; + + memmove(path->ptr + (depth * 3), p, plen + 1); + + for (i = 0; i < depth; i++) + memcpy(path->ptr + (i * 3), "../", 3); + + path->size = newlen; + return 0; +} + +bool git_fs_path_has_non_ascii(const char *path, size_t pathlen) +{ + const uint8_t *scan = (const uint8_t *)path, *end; + + for (end = scan + pathlen; scan < end; ++scan) + if (*scan & 0x80) + return true; + + return false; +} + +#ifdef GIT_USE_ICONV + +int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic) +{ + git_str_init(&ic->buf, 0); + ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); + return 0; +} + +void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic) +{ + if (ic) { + if (ic->map != (iconv_t)-1) + iconv_close(ic->map); + git_str_dispose(&ic->buf); + } +} + +int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen) +{ + char *nfd = (char*)*in, *nfc; + size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv; + int retry = 1; + + if (!ic || ic->map == (iconv_t)-1 || + !git_fs_path_has_non_ascii(*in, *inlen)) + return 0; + + git_str_clear(&ic->buf); + + while (1) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1); + if (git_str_grow(&ic->buf, alloclen) < 0) + return -1; + + nfc = ic->buf.ptr + ic->buf.size; + nfclen = ic->buf.asize - ic->buf.size; + + rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); + + ic->buf.size = (nfc - ic->buf.ptr); + + if (rv != (size_t)-1) + break; + + /* if we cannot convert the data (probably because iconv thinks + * it is not valid UTF-8 source data), then use original data + */ + if (errno != E2BIG) + return 0; + + /* make space for 2x the remaining data to be converted + * (with per retry overhead to avoid infinite loops) + */ + wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); + + if (retry++ > 4) + goto fail; + } + + ic->buf.ptr[ic->buf.size] = '\0'; + + *in = ic->buf.ptr; + *inlen = ic->buf.size; + + return 0; + +fail: + git_error_set(GIT_ERROR_OS, "unable to convert unicode path data"); + return -1; +} + +static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + +/* Check if the platform is decomposing unicode data for us. We will + * emulate core Git and prefer to use precomposed unicode data internally + * on these platforms, composing the decomposed unicode on the fly. + * + * This mainly happens on the Mac where HDFS stores filenames as + * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will + * return decomposed unicode from readdir() even when the actual + * filesystem is storing precomposed unicode. + */ +bool git_fs_path_does_decompose_unicode(const char *root) +{ + git_str nfc_path = GIT_STR_INIT; + git_str nfd_path = GIT_STR_INIT; + int fd; + bool found_decomposed = false; + size_t orig_len; + const char *trailer; + + /* Create a file using a precomposed path and then try to find it + * using the decomposed name. If the lookup fails, then we will mark + * that we should precompose unicode for this repository. + */ + if (git_str_joinpath(&nfc_path, root, nfc_file) < 0) + goto done; + + /* record original path length before trailer */ + orig_len = nfc_path.size; + + if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0) + goto done; + p_close(fd); + + trailer = nfc_path.ptr + orig_len; + + /* try to look up as NFD path */ + if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 || + git_str_puts(&nfd_path, trailer) < 0) + goto done; + + found_decomposed = git_fs_path_exists(nfd_path.ptr); + + /* remove temporary file (using original precomposed path) */ + (void)p_unlink(nfc_path.ptr); + +done: + git_str_dispose(&nfc_path); + git_str_dispose(&nfd_path); + return found_decomposed; +} + +#else + +bool git_fs_path_does_decompose_unicode(const char *root) +{ + GIT_UNUSED(root); + return false; +} + +#endif + +#if defined(__sun) || defined(__GNU__) +typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; +#else +typedef struct dirent path_dirent_data; +#endif + +int git_fs_path_direach( + git_str *path, + uint32_t flags, + int (*fn)(void *, git_str *), + void *arg) +{ + int error = 0; + ssize_t wd_len; + DIR *dir; + struct dirent *de; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + + GIT_UNUSED(flags); + + if (git_fs_path_to_dir(path) < 0) + return -1; + + wd_len = git_str_len(path); + + if ((dir = opendir(path->ptr)) == NULL) { + git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr); + if (errno == ENOENT) + return GIT_ENOTFOUND; + + return -1; + } + +#ifdef GIT_USE_ICONV + if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_fs_path_iconv_init_precompose(&ic); +#endif + + while ((de = readdir(dir)) != NULL) { + const char *de_path = de->d_name; + size_t de_len = strlen(de_path); + + if (git_fs_path_is_dot_or_dotdot(de_path)) + continue; + +#ifdef GIT_USE_ICONV + if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0) + break; +#endif + + if ((error = git_str_put(path, de_path, de_len)) < 0) + break; + + git_error_clear(); + error = fn(arg, path); + + git_str_truncate(path, wd_len); /* restore path */ + + /* Only set our own error if the callback did not set one already */ + if (error != 0) { + if (!git_error_last()) + ensure_error_set(error); + + break; + } + } + + closedir(dir); + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif + + return error; +} + +#if defined(GIT_WIN32) && !defined(__MINGW32__) + +/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7 + * and better. + */ +#ifndef FIND_FIRST_EX_LARGE_FETCH +# define FIND_FIRST_EX_LARGE_FETCH 2 +#endif + +int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + git_win32_path path_filter; + + static int is_win7_or_later = -1; + if (is_win7_or_later < 0) + is_win7_or_later = git_has_win32_version(6, 1, 0); + + GIT_ASSERT_ARG(diriter); + GIT_ASSERT_ARG(path); + + memset(diriter, 0, sizeof(git_fs_path_diriter)); + diriter->handle = INVALID_HANDLE_VALUE; + + if (git_str_puts(&diriter->path_utf8, path) < 0) + return -1; + + path_trim_slashes(&diriter->path_utf8); + + if (diriter->path_utf8.size == 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); + return -1; + } + + if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 || + !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) { + git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path); + return -1; + } + + diriter->handle = FindFirstFileExW( + path_filter, + is_win7_or_later ? FindExInfoBasic : FindExInfoStandard, + &diriter->current, + FindExSearchNameMatch, + NULL, + is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0); + + if (diriter->handle == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path); + return -1; + } + + diriter->parent_utf8_len = diriter->path_utf8.size; + diriter->flags = flags; + return 0; +} + +static int diriter_update_paths(git_fs_path_diriter *diriter) +{ + size_t filename_len, path_len; + + filename_len = wcslen(diriter->current.cFileName); + + if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) || + GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2)) + return -1; + + if (path_len > GIT_WIN_PATH_UTF16) { + git_error_set(GIT_ERROR_FILESYSTEM, + "invalid path '%.*ls\\%ls' (path too long)", + diriter->parent_len, diriter->path, diriter->current.cFileName); + return -1; + } + + diriter->path[diriter->parent_len] = L'\\'; + memcpy(&diriter->path[diriter->parent_len+1], + diriter->current.cFileName, filename_len * sizeof(wchar_t)); + diriter->path[path_len-1] = L'\0'; + + git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len); + + if (diriter->parent_utf8_len > 0 && + diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') + git_str_putc(&diriter->path_utf8, '/'); + + git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); + + if (git_str_oom(&diriter->path_utf8)) + return -1; + + return 0; +} + +int git_fs_path_diriter_next(git_fs_path_diriter *diriter) +{ + bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); + + do { + /* Our first time through, we already have the data from + * FindFirstFileW. Use it, otherwise get the next file. + */ + if (!diriter->needs_next) + diriter->needs_next = 1; + else if (!FindNextFileW(diriter->handle, &diriter->current)) + return GIT_ITEROVER; + } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName)); + + if (diriter_update_paths(diriter) < 0) + return -1; + + return 0; +} + +int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len); + + *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1]; + *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1; + return 0; +} + +int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + + *out = diriter->path_utf8.ptr; + *out_len = diriter->path_utf8.size; + return 0; +} + +int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diriter); + + return git_win32__file_attribute_to_stat(out, + (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current, + diriter->path); +} + +void git_fs_path_diriter_free(git_fs_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + git_str_dispose(&diriter->path_utf8); + + if (diriter->handle != INVALID_HANDLE_VALUE) { + FindClose(diriter->handle); + diriter->handle = INVALID_HANDLE_VALUE; + } +} + +#else + +int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + GIT_ASSERT_ARG(diriter); + GIT_ASSERT_ARG(path); + + memset(diriter, 0, sizeof(git_fs_path_diriter)); + + if (git_str_puts(&diriter->path, path) < 0) + return -1; + + path_trim_slashes(&diriter->path); + + if (diriter->path.size == 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); + return -1; + } + + if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) { + git_str_dispose(&diriter->path); + + git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path); + return -1; + } + +#ifdef GIT_USE_ICONV + if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_fs_path_iconv_init_precompose(&diriter->ic); +#endif + + diriter->parent_len = diriter->path.size; + diriter->flags = flags; + + return 0; +} + +int git_fs_path_diriter_next(git_fs_path_diriter *diriter) +{ + struct dirent *de; + const char *filename; + size_t filename_len; + bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); + int error = 0; + + GIT_ASSERT_ARG(diriter); + + errno = 0; + + do { + if ((de = readdir(diriter->dir)) == NULL) { + if (!errno) + return GIT_ITEROVER; + + git_error_set(GIT_ERROR_OS, + "could not read directory '%s'", diriter->path.ptr); + return -1; + } + } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name)); + + filename = de->d_name; + filename_len = strlen(filename); + +#ifdef GIT_USE_ICONV + if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 && + (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) + return error; +#endif + + git_str_truncate(&diriter->path, diriter->parent_len); + + if (diriter->parent_len > 0 && + diriter->path.ptr[diriter->parent_len-1] != '/') + git_str_putc(&diriter->path, '/'); + + git_str_put(&diriter->path, filename, filename_len); + + if (git_str_oom(&diriter->path)) + return -1; + + return error; +} + +int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + GIT_ASSERT(diriter->path.size > diriter->parent_len); + + *out = &diriter->path.ptr[diriter->parent_len+1]; + *out_len = diriter->path.size - diriter->parent_len - 1; + return 0; +} + +int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + + *out = diriter->path.ptr; + *out_len = diriter->path.size; + return 0; +} + +int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diriter); + + return git_fs_path_lstat(diriter->path.ptr, out); +} + +void git_fs_path_diriter_free(git_fs_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + if (diriter->dir) { + closedir(diriter->dir); + diriter->dir = NULL; + } + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&diriter->ic); +#endif + + git_str_dispose(&diriter->path); +} + +#endif + +int git_fs_path_dirload( + git_vector *contents, + const char *path, + size_t prefix_len, + uint32_t flags) +{ + git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT; + const char *name; + size_t name_len; + char *dup; + int error; + + GIT_ASSERT_ARG(contents); + GIT_ASSERT_ARG(path); + + if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0) + return error; + + while ((error = git_fs_path_diriter_next(&iter)) == 0) { + if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0) + break; + + GIT_ASSERT(name_len > prefix_len); + + dup = git__strndup(name + prefix_len, name_len - prefix_len); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(contents, dup)) < 0) + break; + } + + if (error == GIT_ITEROVER) + error = 0; + + git_fs_path_diriter_free(&iter); + return error; +} + +int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path) +{ + if (git_fs_path_is_local_file_url(url_or_path)) + return git_fs_path_fromurl(local_path_out, url_or_path); + else + return git_str_sets(local_path_out, url_or_path); +} + +/* Reject paths like AUX or COM1, or those versions that end in a dot or + * colon. ("AUX." or "AUX:") + */ +GIT_INLINE(bool) validate_dospath( + const char *component, + size_t len, + const char dospath[3], + bool trailing_num) +{ + size_t last = trailing_num ? 4 : 3; + + if (len < last || git__strncasecmp(component, dospath, 3) != 0) + return true; + + if (trailing_num && (component[3] < '1' || component[3] > '9')) + return true; + + return (len > last && + component[last] != '.' && + component[last] != ':'); +} + +GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags) +{ + if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\') + return false; + + if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/') + return false; + + if (flags & GIT_FS_PATH_REJECT_NT_CHARS) { + if (c < 32) + return false; + + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '|': + case '?': + case '*': + return false; + } + } + + return true; +} + +/* + * We fundamentally don't like some paths when dealing with user-inputted + * strings (to avoid escaping a sandbox): we don't want dot or dot-dot + * anywhere, we want to avoid writing weird paths on Windows that can't + * be handled by tools that use the non-\\?\ APIs, we don't want slashes + * or double slashes at the end of paths that can make them ambiguous. + * + * For checkout, we don't want to recurse into ".git" either. + */ +static bool validate_component( + const char *component, + size_t len, + unsigned int flags) +{ + if (len == 0) + return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT); + + if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && + len == 1 && component[0] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && + len == 2 && component[0] == '.' && component[1] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) && + component[len - 1] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) && + component[len - 1] == ' ') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) && + component[len - 1] == ':') + return false; + + if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) { + if (!validate_dospath(component, len, "CON", false) || + !validate_dospath(component, len, "PRN", false) || + !validate_dospath(component, len, "AUX", false) || + !validate_dospath(component, len, "NUL", false) || + !validate_dospath(component, len, "COM", true) || + !validate_dospath(component, len, "LPT", true)) + return false; + } + + return true; +} + +#ifdef GIT_WIN32 +GIT_INLINE(bool) validate_length( + const char *path, + size_t len, + size_t utf8_char_len) +{ + GIT_UNUSED(path); + GIT_UNUSED(len); + + return (utf8_char_len <= MAX_PATH); +} +#endif + +bool git_fs_path_str_is_valid_ext( + const git_str *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len), + void *payload) +{ + const char *start, *c; + size_t len = 0; + + if (!flags) + return true; + + for (start = c = path->ptr; *c && len < path->size; c++, len++) { + if (!validate_char(*c, flags)) + return false; + + if (validate_char_cb && !validate_char_cb(*c, payload)) + return false; + + if (*c != '/') + continue; + + if (!validate_component(start, (c - start), flags)) + return false; + + if (validate_component_cb && + !validate_component_cb(start, (c - start), payload)) + return false; + + start = c + 1; + } + + /* + * We want to support paths specified as either `const char *` + * or `git_str *`; we pass size as `SIZE_MAX` when we use a + * `const char *` to avoid a `strlen`. Ensure that we didn't + * have a NUL in the buffer if there was a non-SIZE_MAX length. + */ + if (path->size != SIZE_MAX && len != path->size) + return false; + + if (!validate_component(start, (c - start), flags)) + return false; + + if (validate_component_cb && + !validate_component_cb(start, (c - start), payload)) + return false; + +#ifdef GIT_WIN32 + if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) { + size_t utf8_len = git_utf8_char_length(path->ptr, len); + + if (!validate_length(path->ptr, len, utf8_len)) + return false; + + if (validate_length_cb && + !validate_length_cb(path->ptr, len, utf8_len)) + return false; + } +#else + GIT_UNUSED(validate_length_cb); +#endif + + return true; +} + +int git_fs_path_validate_str_length_with_suffix( + git_str *path, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t utf8_len = git_utf8_char_length(path->ptr, path->size); + size_t total_len; + + if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) || + total_len > MAX_PATH) { + + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", + (int)path->size, path->ptr); + return -1; + } +#else + GIT_UNUSED(path); + GIT_UNUSED(suffix_len); +#endif + + return 0; +} + +int git_fs_path_normalize_slashes(git_str *out, const char *path) +{ + int error; + char *p; + + if ((error = git_str_puts(out, path)) < 0) + return error; + + for (p = out->ptr; *p; p++) { + if (*p == '\\') + *p = '/'; + } + + return 0; +} + +bool git_fs_path_supports_symlinks(const char *dir) +{ + git_str path = GIT_STR_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_str_dispose(&path); + return supported; +} + +static git_fs_path_owner_t mock_owner = GIT_FS_PATH_OWNER_NONE; + +void git_fs_path__set_owner(git_fs_path_owner_t owner) +{ + mock_owner = owner; +} + +#ifdef GIT_WIN32 +static PSID *sid_dup(PSID sid) +{ + DWORD len; + PSID dup; + + len = GetLengthSid(sid); + + if ((dup = git__malloc(len)) == NULL) + return NULL; + + if (!CopySid(len, dup, sid)) { + git_error_set(GIT_ERROR_OS, "could not duplicate sid"); + git__free(dup); + return NULL; + } + + return dup; +} + +static int current_user_sid(PSID *out) +{ + TOKEN_USER *info = NULL; + HANDLE token = NULL; + DWORD len = 0; + int error = -1; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + git_error_set(GIT_ERROR_OS, "could not lookup process information"); + goto done; + } + + if (GetTokenInformation(token, TokenUser, NULL, 0, &len) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + git_error_set(GIT_ERROR_OS, "could not lookup token metadata"); + goto done; + } + + info = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(info); + + if (!GetTokenInformation(token, TokenUser, info, len, &len)) { + git_error_set(GIT_ERROR_OS, "could not lookup current user"); + goto done; + } + + if ((*out = sid_dup(info->User.Sid))) + error = 0; + +done: + if (token) + CloseHandle(token); + + git__free(info); + return error; +} + +static int file_owner_sid(PSID *out, const char *path) +{ + git_win32_path path_w32; + PSECURITY_DESCRIPTOR descriptor = NULL; + PSID owner_sid; + DWORD ret; + int error = -1; + + if (git_win32_path_from_utf8(path_w32, path) < 0) + return -1; + + ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + &owner_sid, NULL, NULL, NULL, &descriptor); + + if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND) + error = GIT_ENOTFOUND; + else if (ret != ERROR_SUCCESS) + git_error_set(GIT_ERROR_OS, "failed to get security information"); + else if (!IsValidSid(owner_sid)) + git_error_set(GIT_ERROR_OS, "file owner is not valid"); + else if ((*out = sid_dup(owner_sid))) + error = 0; + + if (descriptor) + LocalFree(descriptor); + + return error; +} + +int git_fs_path_owner_is( + bool *out, + const char *path, + git_fs_path_owner_t owner_type) +{ + PSID owner_sid = NULL, user_sid = NULL; + BOOL is_admin, admin_owned; + int error; + + if (mock_owner) { + *out = ((mock_owner & owner_type) != 0); + return 0; + } + + if ((error = file_owner_sid(&owner_sid, path)) < 0) + goto done; + + if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0) { + if ((error = current_user_sid(&user_sid)) < 0) + goto done; + + if (EqualSid(owner_sid, user_sid)) { + *out = true; + goto done; + } + } + + admin_owned = + IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || + IsWellKnownSid(owner_sid, WinLocalSystemSid); + + if (admin_owned && + (owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0) { + *out = true; + goto done; + } + + if (admin_owned && + (owner_type & GIT_FS_PATH_USER_IS_ADMINISTRATOR) != 0 && + CheckTokenMembership(NULL, owner_sid, &is_admin) && + is_admin) { + *out = true; + goto done; + } + + *out = false; + +done: + git__free(owner_sid); + git__free(user_sid); + return error; +} + +#else + +static int sudo_uid_lookup(uid_t *out) +{ + git_str uid_str = GIT_STR_INIT; + int64_t uid; + int error; + + if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 && + (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 && + uid == (int64_t)((uid_t)uid)) { + *out = (uid_t)uid; + } + + git_str_dispose(&uid_str); + return error; +} + +int git_fs_path_owner_is( + bool *out, + const char *path, + git_fs_path_owner_t owner_type) +{ + struct stat st; + uid_t euid, sudo_uid; + + if (mock_owner) { + *out = ((mock_owner & owner_type) != 0); + return 0; + } + + euid = geteuid(); + + if (p_lstat(path, &st) != 0) { + if (errno == ENOENT) + return GIT_ENOTFOUND; + + git_error_set(GIT_ERROR_OS, "could not stat '%s'", path); + return -1; + } + + if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0 && + st.st_uid == euid) { + *out = true; + return 0; + } + + if ((owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0 && + st.st_uid == 0) { + *out = true; + return 0; + } + + if ((owner_type & GIT_FS_PATH_OWNER_RUNNING_SUDO) != 0 && + euid == 0 && + sudo_uid_lookup(&sudo_uid) == 0 && + st.st_uid == sudo_uid) { + *out = true; + return 0; + } + + *out = false; + return 0; +} + +#endif + +int git_fs_path_owner_is_current_user(bool *out, const char *path) +{ + return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_CURRENT_USER); +} + +int git_fs_path_owner_is_system(bool *out, const char *path) +{ + return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR); +} + +int git_fs_path_find_executable(git_str *fullpath, const char *executable) +{ +#ifdef GIT_WIN32 + git_win32_path fullpath_w, executable_w; + int error; + + if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) + return -1; + + error = git_win32_path_find_executable(fullpath_w, executable_w); + + if (error == 0) + error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); + + return error; +#else + git_str path = GIT_STR_INIT; + const char *current_dir, *term; + bool found = false; + + if (git__getenv(&path, "PATH") < 0) + return -1; + + current_dir = path.ptr; + + while (*current_dir) { + if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) + term = strchr(current_dir, '\0'); + + git_str_clear(fullpath); + if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || + git_str_putc(fullpath, '/') < 0 || + git_str_puts(fullpath, executable) < 0) + return -1; + + if (git_fs_path_isfile(fullpath->ptr)) { + found = true; + break; + } + + current_dir = term; + + while (*current_dir == GIT_PATH_LIST_SEPARATOR) + current_dir++; + } + + git_str_dispose(&path); + + if (found) + return 0; + + git_str_clear(fullpath); + return GIT_ENOTFOUND; +#endif +} diff --git a/src/util/fs_path.h b/src/util/fs_path.h new file mode 100644 index 000000000..e5ca67378 --- /dev/null +++ b/src/util/fs_path.h @@ -0,0 +1,790 @@ +/* + * 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_fs_path_h__ +#define INCLUDE_fs_path_h__ + +#include "git2_util.h" + +#include "posix.h" +#include "str.h" +#include "vector.h" +#include "utf8.h" + +/** + * Path manipulation utils + * + * These are path utilities that munge paths without actually + * looking at the real filesystem. + */ + +/* + * The dirname() function shall take a pointer to a character string + * that contains a pathname, and return a pointer to a string that is a + * pathname of the parent directory of that file. Trailing '/' characters + * in the path are not counted as part of the path. + * + * If path does not contain a '/', then dirname() shall return a pointer to + * the string ".". If path is a null pointer or points to an empty string, + * dirname() shall return a pointer to the string "." . + * + * The `git_fs_path_dirname` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str` + * if the buffer pointer is not NULL. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the dirname (which will be > 0). + */ +extern char *git_fs_path_dirname(const char *path); +extern int git_fs_path_dirname_r(git_str *buffer, const char *path); + +/* + * This function returns the basename of the file, which is the last + * part of its full name given by fname, with the drive letter and + * leading directories stripped off. For example, the basename of + * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. + * + * Trailing slashes and backslashes are significant: the basename of + * c:/foo/bar/ is an empty string after the rightmost slash. + * + * The `git_fs_path_basename` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the basename (which will be >= 0). + */ +extern char *git_fs_path_basename(const char *path); +extern int git_fs_path_basename_r(git_str *buffer, const char *path); + +/* Return the offset of the start of the basename. Unlike the other + * basename functions, this returns 0 if the path is empty. + */ +extern size_t git_fs_path_basename_offset(git_str *buffer); + +/** + * Find offset to root of path if path has one. + * + * This will return a number >= 0 which is the offset to the start of the + * path, if the path is rooted (i.e. "/rooted/path" returns 0 and + * "c:/windows/rooted/path" returns 2). If the path is not rooted, this + * returns -1. + */ +extern int git_fs_path_root(const char *path); + +/** + * Ensure path has a trailing '/'. + */ +extern int git_fs_path_to_dir(git_str *path); + +/** + * Ensure string has a trailing '/' if there is space for it. + */ +extern void git_fs_path_string_to_dir(char *path, size_t size); + +/** + * Taken from git.git; returns nonzero if the given path is "." or "..". + */ +GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +#ifdef GIT_WIN32 +GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name) +{ + return (name[0] == L'.' && + (name[1] == L'\0' || + (name[1] == L'.' && name[2] == L'\0'))); +} + +#define git_fs_path_is_absolute(p) \ + (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) + +#define git_fs_path_is_dirsep(p) \ + ((p) == '/' || (p) == '\\') + +/** + * Convert backslashes in path to forward slashes. + */ +GIT_INLINE(void) git_fs_path_mkposix(char *path) +{ + while (*path) { + if (*path == '\\') + *path = '/'; + + path++; + } +} +#else +# define git_fs_path_mkposix(p) /* blank */ + +#define git_fs_path_is_absolute(p) \ + ((p)[0] == '/') + +#define git_fs_path_is_dirsep(p) \ + ((p) == '/') + +#endif + +/** + * Check if string is a relative path (i.e. starts with "./" or "../") + */ +GIT_INLINE(int) git_fs_path_is_relative(const char *p) +{ + return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); +} + +/** + * Check if string is at end of path segment (i.e. looking at '/' or '\0') + */ +GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p) +{ + return !*p || *p == '/'; +} + +extern int git__percent_decode(git_str *decoded_out, const char *input); + +/** + * Extract path from file:// URL. + */ +extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url); + + +/** + * Path filesystem utils + * + * These are path utilities that actually access the filesystem. + */ + +/** + * Check if a file exists and can be accessed. + * @return true or false + */ +extern bool git_fs_path_exists(const char *path); + +/** + * Check if the given path points to a directory. + * @return true or false + */ +extern bool git_fs_path_isdir(const char *path); + +/** + * Check if the given path points to a regular file. + * @return true or false + */ +extern bool git_fs_path_isfile(const char *path); + +/** + * Check if the given path points to a symbolic link. + * @return true or false + */ +extern bool git_fs_path_islink(const char *path); + +/** + * Check if the given path is a directory, and is empty. + */ +extern bool git_fs_path_is_empty_dir(const char *path); + +/** + * Stat a file and/or link and set error if needed. + */ +extern int git_fs_path_lstat(const char *path, struct stat *st); + +/** + * Check if the parent directory contains the item. + * + * @param dir Directory to check. + * @param item Item that might be in the directory. + * @return 0 if item exists in directory, <0 otherwise. + */ +extern bool git_fs_path_contains(git_str *dir, const char *item); + +/** + * Check if the given path contains the given subdirectory. + * + * @param parent Directory path that might contain subdir + * @param subdir Subdirectory name to look for in parent + * @return true if subdirectory exists, false otherwise. + */ +extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir); + +/** + * Determine the common directory length between two paths, including + * the final path separator. For example, given paths 'a/b/c/1.txt + * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this + * will return the length of the string 'a/b/c/', which is 6. + * + * @param one The first path + * @param two The second path + * @return The length of the common directory + */ +extern size_t git_fs_path_common_dirlen(const char *one, const char *two); + +/** + * Make the path relative to the given parent path. + * + * @param path The path to make relative + * @param parent The parent path to make path relative to + * @return 0 if path was made relative, GIT_ENOTFOUND + * if there was not common root between the paths, + * or <0. + */ +extern int git_fs_path_make_relative(git_str *path, const char *parent); + +/** + * Check if the given path contains the given file. + * + * @param dir Directory path that might contain file + * @param file File name to look for in parent + * @return true if file exists, false otherwise. + */ +extern bool git_fs_path_contains_file(git_str *dir, const char *file); + +/** + * Prepend base to unrooted path or just copy path over. + * + * This will optionally return the index into the path where the "root" + * is, either the end of the base directory prefix or the path root. + */ +extern int git_fs_path_join_unrooted( + git_str *path_out, const char *path, const char *base, ssize_t *root_at); + +/** + * Removes multiple occurrences of '/' in a row, squashing them into a + * single '/'. + */ +extern void git_fs_path_squash_slashes(git_str *path); + +/** + * Clean up path, prepending base if it is not already rooted. + */ +extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base); + +/** + * Clean up path, prepending base if it is not already rooted and + * appending a slash. + */ +extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base); + +/** + * Get a directory from a path. + * + * If path is a directory, this acts like `git_fs_path_prettify_dir` + * (cleaning up path and appending a '/'). If path is a normal file, + * this prettifies it, then removed the filename a la dirname and + * appends the trailing '/'. If the path does not exist, it is + * treated like a regular filename. + */ +extern int git_fs_path_find_dir(git_str *dir); + +/** + * Resolve relative references within a path. + * + * This eliminates "./" and "../" relative references inside a path, + * as well as condensing multiple slashes into single ones. It will + * not touch the path before the "ceiling" length. + * + * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL + * prefix and not touch that part of the path. + */ +extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling); + +/** + * Apply a relative path to base path. + * + * Note that the base path could be a filename or a URL and this + * should still work. The relative path is walked segment by segment + * with three rules: series of slashes will be condensed to a single + * slash, "." will be eaten with no change, and ".." will remove a + * segment from the base path. + */ +extern int git_fs_path_apply_relative(git_str *target, const char *relpath); + +enum { + GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0), + GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), + GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), +}; + +/** + * Walk each directory entry, except '.' and '..', calling fn(state). + * + * @param pathbuf Buffer the function reads the initial directory + * path from, and updates with each successive entry's name. + * @param flags Combination of GIT_FS_PATH_DIR flags. + * @param callback Callback for each entry. Passed the `payload` and each + * successive path inside the directory as a full path. This may + * safely append text to the pathbuf if needed. Return non-zero to + * cancel iteration (and return value will be propagated back). + * @param payload Passed to callback as first argument. + * @return 0 on success or error code from OS error or from callback + */ +extern int git_fs_path_direach( + git_str *pathbuf, + uint32_t flags, + int (*callback)(void *payload, git_str *path), + void *payload); + +/** + * Sort function to order two paths + */ +extern int git_fs_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)); + +/** + * Invoke callback up path directory by directory until the ceiling is + * reached (inclusive of a final call at the root_path). + * + * Returning anything other than 0 from the callback function + * will stop the iteration and propagate the error to the caller. + * + * @param pathbuf Buffer the function reads the directory from and + * and updates with each successive name. + * @param ceiling Prefix of path at which to stop walking up. If NULL, + * this will walk all the way up to the root. If not a prefix of + * pathbuf, the callback will be invoked a single time on the + * original input path. + * @param callback Function to invoke on each path. Passed the `payload` + * and the buffer containing the current path. The path should not + * be modified in any way. Return non-zero to stop iteration. + * @param payload Passed to fn as the first ath. + */ +extern int git_fs_path_walk_up( + git_str *pathbuf, + const char *ceiling, + int (*callback)(void *payload, const char *path), + void *payload); + + +enum { + GIT_FS_PATH_NOTEQUAL = 0, + GIT_FS_PATH_EQUAL = 1, + GIT_FS_PATH_PREFIX = 2 +}; + +/* + * Determines if a path is equal to or potentially a child of another. + * @param parent The possible parent + * @param child The possible child + */ +GIT_INLINE(int) git_fs_path_equal_or_prefixed( + const char *parent, + const char *child, + ssize_t *prefixlen) +{ + const char *p = parent, *c = child; + int lastslash = 0; + + while (*p && *c) { + lastslash = (*p == '/'); + + if (*p++ != *c++) + return GIT_FS_PATH_NOTEQUAL; + } + + if (*p != '\0') + return GIT_FS_PATH_NOTEQUAL; + + if (*c == '\0') { + if (prefixlen) + *prefixlen = p - parent; + + return GIT_FS_PATH_EQUAL; + } + + if (*c == '/' || lastslash) { + if (prefixlen) + *prefixlen = (p - parent) - lastslash; + + return GIT_FS_PATH_PREFIX; + } + + return GIT_FS_PATH_NOTEQUAL; +} + +/* translate errno to libgit2 error code and set error message */ +extern int git_fs_path_set_error( + int errno_value, const char *path, const char *action); + +/* check if non-ascii characters are present in filename */ +extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen); + +#define GIT_PATH_REPO_ENCODING "UTF-8" + +#ifdef __APPLE__ +#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" +#else +#define GIT_PATH_NATIVE_ENCODING "UTF-8" +#endif + +#ifdef GIT_USE_ICONV + +#include + +typedef struct { + iconv_t map; + git_str buf; +} git_fs_path_iconv_t; + +#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT } + +/* Init iconv data for converting decomposed UTF-8 to precomposed */ +extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic); + +/* Clear allocated iconv data */ +extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic); + +/* + * Rewrite `in` buffer using iconv map if necessary, replacing `in` + * pointer internal iconv buffer if rewrite happened. The `in` pointer + * will be left unchanged if no rewrite was needed. + */ +extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen); + +#endif /* GIT_USE_ICONV */ + +extern bool git_fs_path_does_decompose_unicode(const char *root); + + +typedef struct git_fs_path_diriter git_fs_path_diriter; + +#if defined(GIT_WIN32) && !defined(__MINGW32__) + +struct git_fs_path_diriter +{ + git_win32_path path; + size_t parent_len; + + git_str path_utf8; + size_t parent_utf8_len; + + HANDLE handle; + + unsigned int flags; + + WIN32_FIND_DATAW current; + unsigned int needs_next; +}; + +#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE } + +#else + +struct git_fs_path_diriter +{ + git_str path; + size_t parent_len; + + unsigned int flags; + + DIR *dir; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic; +#endif +}; + +#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT } + +#endif + +/** + * Initialize a directory iterator. + * + * @param diriter Pointer to a diriter structure that will be setup. + * @param path The path that will be iterated over + * @param flags Directory reader flags + * @return 0 or an error code + */ +extern int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags); + +/** + * Advance the directory iterator. Will return GIT_ITEROVER when + * the iteration has completed successfully. + * + * @param diriter The directory iterator + * @return 0, GIT_ITEROVER, or an error code + */ +extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter); + +/** + * Returns the file name of the current item in the iterator. + * + * @param out Pointer to store the path in + * @param out_len Pointer to store the length of the path in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter); + +/** + * Returns the full path of the current item in the iterator; that + * is the current filename plus the path of the directory that the + * iterator was constructed with. + * + * @param out Pointer to store the path in + * @param out_len Pointer to store the length of the path in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter); + +/** + * Performs an `lstat` on the current item in the iterator. + * + * @param out Pointer to store the stat data in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter); + +/** + * Closes the directory iterator. + * + * @param diriter The directory iterator + */ +extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter); + +/** + * Load all directory entries (except '.' and '..') into a vector. + * + * For cases where `git_fs_path_direach()` is not appropriate, this + * allows you to load the filenames in a directory into a vector + * of strings. That vector can then be sorted, iterated, or whatever. + * Remember to free alloc of the allocated strings when you are done. + * + * @param contents Vector to fill with directory entry names. + * @param path The directory to read from. + * @param prefix_len When inserting entries, the trailing part of path + * will be prefixed after this length. I.e. given path "/a/b" and + * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. + * @param flags Combination of GIT_FS_PATH_DIR flags. + */ +extern int git_fs_path_dirload( + git_vector *contents, + const char *path, + size_t prefix_len, + uint32_t flags); + + +/* Used for paths to repositories on the filesystem */ +extern bool git_fs_path_is_local_file_url(const char *file_url); +extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path); + +/* Flags to determine path validity in `git_fs_path_isvalid` */ +#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0) +#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1) +#define GIT_FS_PATH_REJECT_SLASH (1 << 2) +#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3) +#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4) +#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5) +#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6) +#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7) +#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8) +#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9) + +#define GIT_FS_PATH_REJECT_MAX (1 << 9) + +/* Default path safety for writing files to disk: since we use the + * Win32 "File Namespace" APIs ("\\?\") we need to protect from + * paths that the normal Win32 APIs would not write. + */ +#ifdef GIT_WIN32 +# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ + GIT_FS_PATH_REJECT_TRAVERSAL | \ + GIT_FS_PATH_REJECT_BACKSLASH | \ + GIT_FS_PATH_REJECT_TRAILING_DOT | \ + GIT_FS_PATH_REJECT_TRAILING_SPACE | \ + GIT_FS_PATH_REJECT_TRAILING_COLON | \ + GIT_FS_PATH_REJECT_DOS_PATHS | \ + GIT_FS_PATH_REJECT_NT_CHARS +#else +# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ + GIT_FS_PATH_REJECT_TRAVERSAL +#endif + +/** + * Validate a filesystem path; with custom callbacks per-character and + * per-path component. + */ +extern bool git_fs_path_str_is_valid_ext( + const git_str *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), + void *payload); + +GIT_INLINE(bool) git_fs_path_is_valid_ext( + const char *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), + void *payload) +{ + const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_fs_path_str_is_valid_ext( + &str, + flags, + validate_char_cb, + validate_component_cb, + validate_length_cb, + payload); +} + +/** + * Validate a filesystem path. This ensures that the given path is legal + * and does not contain any "unsafe" components like path traversal ('.' + * or '..'), characters that are inappropriate for lesser filesystems + * (trailing ' ' or ':' characters), or filenames ("component names") + * that are not supported ('AUX', 'COM1"). + */ +GIT_INLINE(bool) git_fs_path_is_valid( + const char *path, + unsigned int flags) +{ + const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL); +} + +/** Validate a filesystem path in a `git_str`. */ +GIT_INLINE(bool) git_fs_path_str_is_valid( + const git_str *path, + unsigned int flags) +{ + return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL); +} + +extern int git_fs_path_validate_str_length_with_suffix( + git_str *path, + size_t suffix_len); + +/** + * Validate an on-disk path, taking into account that it will have a + * suffix appended (eg, `.lock`). + */ +GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix( + const char *path, + size_t path_len, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t path_chars, total_chars; + + path_chars = git_utf8_char_length(path, path_len); + + if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || + total_chars > MAX_PATH) { + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); + return -1; + } + return 0; +#else + GIT_UNUSED(path); + GIT_UNUSED(path_len); + GIT_UNUSED(suffix_len); + return 0; +#endif +} + +/** + * Validate an path on the filesystem. This ensures that the given + * path is valid for the operating system/platform; for example, this + * will ensure that the given absolute path is smaller than MAX_PATH on + * Windows. + * + * For paths within the working directory, you should use ensure that + * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`. + */ +GIT_INLINE(int) git_fs_path_validate_filesystem( + const char *path, + size_t path_len) +{ + return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0); +} + +/** + * Convert any backslashes into slashes + */ +int git_fs_path_normalize_slashes(git_str *out, const char *path); + +bool git_fs_path_supports_symlinks(const char *dir); + +typedef enum { + GIT_FS_PATH_OWNER_NONE = 0, + + /** The file must be owned by the current user. */ + GIT_FS_PATH_OWNER_CURRENT_USER = (1 << 0), + + /** The file must be owned by the system account. */ + GIT_FS_PATH_OWNER_ADMINISTRATOR = (1 << 1), + + /** + * The file may be owned by a system account if the current + * user is in an administrator group. Windows only; this is + * a noop on non-Windows systems. + */ + GIT_FS_PATH_USER_IS_ADMINISTRATOR = (1 << 2), + + /** + * The file is owned by the current user, who is running `sudo`. + */ + GIT_FS_PATH_OWNER_RUNNING_SUDO = (1 << 3), + + /** The file may be owned by another user. */ + GIT_FS_PATH_OWNER_OTHER = (1 << 4) +} git_fs_path_owner_t; + +/** + * Sets the mock ownership for files; subsequent calls to + * `git_fs_path_owner_is_*` functions will return this data until + * cleared with `GIT_FS_PATH_OWNER_NONE`. + */ +void git_fs_path__set_owner(git_fs_path_owner_t owner); + +/** Verify that the file in question is owned by the given owner. */ +int git_fs_path_owner_is( + bool *out, + const char *path, + git_fs_path_owner_t owner_type); + +/** + * Verify that the file in question is owned by an administrator or system + * account. + */ +int git_fs_path_owner_is_system(bool *out, const char *path); + +/** + * Verify that the file in question is owned by the current user; + */ + +int git_fs_path_owner_is_current_user(bool *out, const char *path); + +/** + * Search the current PATH for the given executable, returning the full + * path if it is found. + */ +int git_fs_path_find_executable(git_str *fullpath, const char *executable); + +#endif diff --git a/src/util/futils.c b/src/util/futils.c new file mode 100644 index 000000000..cb872de09 --- /dev/null +++ b/src/util/futils.c @@ -0,0 +1,1236 @@ +/* + * 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 "futils.h" + +#include "runtime.h" +#include "strmap.h" +#include "hash.h" +#include "rand.h" + +#include +#if GIT_WIN32 +#include "win32/findfile.h" +#endif + +#define GIT_FILEMODE_DEFAULT 0100666 + +int git_futils_mkpath2file(const char *file_path, const mode_t mode) +{ + return git_futils_mkdir( + file_path, mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); +} + +int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode) +{ + const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; + unsigned int tries = 32; + int fd; + + while (tries--) { + uint64_t rand = git_rand_next(); + + git_str_sets(path_out, filename); + git_str_puts(path_out, "_git2_"); + git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t)); + + if (git_str_oom(path_out)) + return -1; + + /* Note that we open with O_CREAT | O_EXCL */ + if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0) + return fd; + } + + git_error_set(GIT_ERROR_OS, + "failed to create temporary file '%s'", path_out->ptr); + git_str_dispose(path_out); + return -1; +} + +int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + int fd; + + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + fd = p_creat(path, mode); + if (fd < 0) { + git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path); + return -1; + } + + return fd; +} + +int git_futils_creat_locked(const char *path, const mode_t mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, + mode); + + if (fd < 0) { + int error = errno; + git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path); + switch (error) { + case EEXIST: + return GIT_ELOCKED; + case ENOENT: + return GIT_ENOTFOUND; + default: + return -1; + } + } + + return fd; +} + +int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + return git_futils_creat_locked(path, mode); +} + +int git_futils_open_ro(const char *path) +{ + int fd = p_open(path, O_RDONLY); + if (fd < 0) + return git_fs_path_set_error(errno, path, "open"); + return fd; +} + +int git_futils_truncate(const char *path, int mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); + if (fd < 0) + return git_fs_path_set_error(errno, path, "open"); + + close(fd); + return 0; +} + +int git_futils_filesize(uint64_t *out, git_file fd) +{ + struct stat sb; + + if (p_fstat(fd, &sb)) { + git_error_set(GIT_ERROR_OS, "failed to stat file descriptor"); + return -1; + } + + 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) +{ + if (S_ISREG(raw_mode)) + return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); + else if (S_ISLNK(raw_mode)) + return S_IFLNK; + else if (S_ISGITLINK(raw_mode)) + return S_IFGITLINK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; + else + return 0; +} + +int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) +{ + ssize_t read_size = 0; + size_t alloc_len; + + git_str_clear(buf); + + if (!git__is_ssizet(len)) { + git_error_set(GIT_ERROR_INVALID, "read too large"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); + if (git_str_grow(buf, alloc_len) < 0) + return -1; + + /* p_read loops internally to read len bytes */ + read_size = p_read(fd, buf->ptr, len); + + if (read_size < 0) { + git_error_set(GIT_ERROR_OS, "failed to read descriptor"); + git_str_dispose(buf); + return -1; + } + + if ((size_t)read_size != len) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not read (expected %" PRIuZ " bytes, read %" PRIuZ ")", len, (size_t)read_size); + git_str_dispose(buf); + return -1; + } + + buf->ptr[read_size] = '\0'; + buf->size = read_size; + + return 0; +} + +int git_futils_readbuffer_fd_full(git_str *buf, git_file fd) +{ + static size_t blocksize = 10240; + size_t alloc_len = 0, total_size = 0; + ssize_t read_size = 0; + + git_str_clear(buf); + + while (true) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, blocksize); + + if (git_str_grow(buf, alloc_len) < 0) + return -1; + + /* p_read loops internally to read blocksize bytes */ + read_size = p_read(fd, buf->ptr, blocksize); + + if (read_size < 0) { + git_error_set(GIT_ERROR_OS, "failed to read descriptor"); + git_str_dispose(buf); + return -1; + } + + total_size += read_size; + + if ((size_t)read_size < blocksize) { + break; + } + } + + buf->ptr[total_size] = '\0'; + buf->size = total_size; + + return 0; +} + +int git_futils_readbuffer_updated( + git_str *out, + const char *path, + unsigned char checksum[GIT_HASH_SHA1_SIZE], + int *updated) +{ + int error; + git_file fd; + struct stat st; + git_str buf = GIT_STR_INIT; + unsigned char checksum_new[GIT_HASH_SHA1_SIZE]; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(path && *path); + + if (updated != NULL) + *updated = 0; + + if (p_stat(path, &st) < 0) + return git_fs_path_set_error(errno, path, "stat"); + + + if (S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_INVALID, "requested file is a directory"); + return GIT_ENOTFOUND; + } + + if (!git__is_sizet(st.st_size+1)) { + git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path); + return -1; + } + + if ((fd = git_futils_open_ro(path)) < 0) + return fd; + + if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { + p_close(fd); + return -1; + } + + p_close(fd); + + if (checksum) { + if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) { + git_str_dispose(&buf); + return error; + } + + /* + * If we were given a checksum, we only want to use it if it's different + */ + if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) { + git_str_dispose(&buf); + if (updated) + *updated = 0; + + return 0; + } + + memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE); + } + + /* + * If we're here, the file did change, or the user didn't have an old version + */ + if (updated != NULL) + *updated = 1; + + git_str_swap(out, &buf); + git_str_dispose(&buf); + + return 0; +} + +int git_futils_readbuffer(git_str *buf, const char *path) +{ + return git_futils_readbuffer_updated(buf, path, NULL, NULL); +} + +int git_futils_writebuffer( + const git_str *buf, const char *path, int flags, mode_t mode) +{ + int fd, do_fsync = 0, error = 0; + + if (!flags) + flags = O_CREAT | O_TRUNC | O_WRONLY; + + if ((flags & O_FSYNC) != 0) + do_fsync = 1; + + flags &= ~O_FSYNC; + + if (!mode) + mode = GIT_FILEMODE_DEFAULT; + + if ((fd = p_open(path, flags, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); + return fd; + } + + if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) { + git_error_set(GIT_ERROR_OS, "could not write to '%s'", path); + (void)p_close(fd); + return error; + } + + if (do_fsync && (error = p_fsync(fd)) < 0) { + git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path); + p_close(fd); + return error; + } + + if ((error = p_close(fd)) < 0) { + git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); + return error; + } + + if (do_fsync && (flags & O_CREAT)) + error = git_futils_fsync_parent(path); + + return error; +} + +int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) +{ + if (git_futils_mkpath2file(to, dirmode) < 0) + return -1; + + if (p_rename(from, to) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to); + return -1; + } + + return 0; +} + +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); +} + +int git_futils_mmap_ro_file(git_map *out, const char *path) +{ + git_file fd = git_futils_open_ro(path); + uint64_t len; + int result; + + if (fd < 0) + return fd; + + 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); + result = -1; + goto out; + } + + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); +out: + p_close(fd); + return result; +} + +void git_futils_mmap_free(git_map *out) +{ + p_munmap(out); +} + +GIT_INLINE(int) mkdir_validate_dir( + const char *path, + struct stat *st, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + /* with exclusive create, existing dir is an error */ + if ((flags & GIT_MKDIR_EXCL) != 0) { + git_error_set(GIT_ERROR_FILESYSTEM, + "failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + + if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || + (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { + if (p_unlink(path) < 0) { + git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'", + S_ISLNK(st->st_mode) ? "symlink" : "file", path); + return GIT_EEXISTS; + } + + opts->perfdata.mkdir_calls++; + + if (p_mkdir(path, mode) < 0) { + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); + return GIT_EEXISTS; + } + } + + else if (S_ISLNK(st->st_mode)) { + /* Re-stat the target, make sure it's a directory */ + opts->perfdata.stat_calls++; + + if (p_stat(path, st) < 0) { + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); + return GIT_EEXISTS; + } + } + + else if (!S_ISDIR(st->st_mode)) { + git_error_set(GIT_ERROR_FILESYSTEM, + "failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + + return 0; +} + +GIT_INLINE(int) mkdir_validate_mode( + const char *path, + struct stat *st, + bool terminal_path, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || + (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { + + opts->perfdata.chmod_calls++; + + if (p_chmod(path, mode) < 0) { + git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path); + return -1; + } + } + + return 0; +} + +GIT_INLINE(int) mkdir_canonicalize( + git_str *path, + uint32_t flags) +{ + ssize_t root_len; + + if (path->size == 0) { + git_error_set(GIT_ERROR_OS, "attempt to create empty path"); + return -1; + } + + /* Trim trailing slashes (except the root) */ + if ((root_len = git_fs_path_root(path->ptr)) < 0) + root_len = 0; + else + root_len++; + + while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') + path->ptr[--path->size] = '\0'; + + /* if we are not supposed to made the last element, truncate it */ + if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { + git_fs_path_dirname_r(path, path->ptr); + flags |= GIT_MKDIR_SKIP_LAST; + } + if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { + git_fs_path_dirname_r(path, path->ptr); + } + + /* We were either given the root path (or trimmed it to + * the root), we don't have anything to do. + */ + if (path->size <= (size_t)root_len) + git_str_clear(path); + + return 0; +} + +int git_futils_mkdir( + const char *path, + mode_t mode, + uint32_t flags) +{ + git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT; + const char *relative; + struct git_futils_mkdir_options opts = { 0 }; + struct stat st; + size_t depth = 0; + int len = 0, root_len, error; + + if ((error = git_str_puts(&make_path, path)) < 0 || + (error = mkdir_canonicalize(&make_path, flags)) < 0 || + (error = git_str_puts(&parent_path, make_path.ptr)) < 0 || + make_path.size == 0) + goto done; + + root_len = git_fs_path_root(make_path.ptr); + + /* find the first parent directory that exists. this will be used + * as the base to dirname_relative. + */ + for (relative = make_path.ptr; parent_path.size; ) { + error = p_lstat(parent_path.ptr, &st); + + if (error == 0) { + break; + } else if (errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); + error = -1; + goto done; + } + + depth++; + + /* examine the parent of the current path */ + if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { + error = len; + goto done; + } + + GIT_ASSERT(len); + + /* + * We've walked all the given path's parents and it's either relative + * (the parent is simply '.') or rooted (the length is less than or + * 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 == 1 && parent_path.ptr[0] == '/') || + len <= root_len) { + relative = make_path.ptr; + break; + } + + relative = make_path.ptr + len + 1; + + /* not recursive? just make this directory relative to its parent. */ + if ((flags & GIT_MKDIR_PATH) == 0) + break; + } + + /* we found an item at the location we're trying to create, + * validate it. + */ + if (depth == 0) { + error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); + + if (!error) + error = mkdir_validate_mode( + make_path.ptr, &st, true, mode, flags, &opts); + + goto done; + } + + /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when + * canonicalizing `make_path`. + */ + flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); + + error = git_futils_mkdir_relative(relative, + parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); + +done: + git_str_dispose(&make_path); + git_str_dispose(&parent_path); + return error; +} + +int git_futils_mkdir_r(const char *path, const mode_t mode) +{ + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); +} + +int git_futils_mkdir_relative( + const char *relative_path, + const char *base, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + git_str make_path = GIT_STR_INIT; + ssize_t root = 0, min_root_len; + char lastch = '/', *tail; + struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + int error; + + if (!opts) + opts = &empty_opts; + + /* build path and find "root" where we should start calling mkdir */ + if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + return -1; + + if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || + make_path.size == 0) + goto done; + + /* if we are not supposed to make the whole path, reset root */ + if ((flags & GIT_MKDIR_PATH) == 0) + root = git_str_rfind(&make_path, '/'); + + /* advance root past drive name or network mount prefix */ + min_root_len = git_fs_path_root(make_path.ptr); + if (root < min_root_len) + root = min_root_len; + while (root >= 0 && make_path.ptr[root] == '/') + ++root; + + /* clip root to make_path length */ + if (root > (ssize_t)make_path.size) + root = (ssize_t)make_path.size; /* i.e. NUL byte of string */ + if (root < 0) + root = 0; + + /* walk down tail of path making each directory */ + for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { + bool mkdir_attempted = false; + + /* advance tail to include next path component */ + while (*tail == '/') + tail++; + while (*tail && *tail != '/') + tail++; + + /* truncate path at next component */ + lastch = *tail; + *tail = '\0'; + st.st_mode = 0; + + if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) + continue; + + /* See what's going on with this path component */ + opts->perfdata.stat_calls++; + +retry_lstat: + if (p_lstat(make_path.ptr, &st) < 0) { + if (mkdir_attempted || errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr); + error = -1; + goto done; + } + + git_error_clear(); + opts->perfdata.mkdir_calls++; + mkdir_attempted = true; + if (p_mkdir(make_path.ptr, mode) < 0) { + if (errno == EEXIST) + goto retry_lstat; + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr); + error = -1; + goto done; + } + } else { + if ((error = mkdir_validate_dir( + make_path.ptr, &st, mode, flags, opts)) < 0) + goto done; + } + + /* chmod if requested and necessary */ + if ((error = mkdir_validate_mode( + make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) + goto done; + + if (opts->dir_map && opts->pool) { + char *cache_path; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); + 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); + + if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) + goto done; + } + } + + error = 0; + + /* check that full path really is a directory if requested & needed */ + if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && + lastch != '\0') { + opts->perfdata.stat_calls++; + + if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_OS, "path is not a directory '%s'", + make_path.ptr); + error = GIT_ENOTFOUND; + } + } + +done: + git_str_dispose(&make_path); + return error; +} + +typedef struct { + const char *base; + size_t baselen; + uint32_t flags; + int depth; +} futils__rmdir_data; + +#define FUTILS_MAX_DEPTH 100 + +static int futils__error_cannot_rmdir(const char *path, const char *filemsg) +{ + if (filemsg) + git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s", + path, filemsg); + else + git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path); + + return -1; +} + +static int futils__rm_first_parent(git_str *path, const char *ceiling) +{ + int error = GIT_ENOTFOUND; + struct stat st; + + while (error == GIT_ENOTFOUND) { + git_str_rtruncate_at_char(path, '/'); + + if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) + error = 0; + else if (p_lstat_posixly(path->ptr, &st) == 0) { + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + error = p_unlink(path->ptr); + else if (!S_ISDIR(st.st_mode)) + error = -1; /* fail to remove non-regular file */ + } else if (errno != ENOTDIR) + error = -1; + } + + if (error) + futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); + + return error; +} + +static int futils__rmdir_recurs_foreach(void *opaque, git_str *path) +{ + int error = 0; + futils__rmdir_data *data = opaque; + struct stat st; + + if (data->depth > FUTILS_MAX_DEPTH) + error = futils__error_cannot_rmdir( + path->ptr, "directory nesting too deep"); + + else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { + if (errno == ENOENT) + error = 0; + else if (errno == ENOTDIR) { + /* asked to remove a/b/c/d/e and a/b is a normal file */ + if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) + error = futils__rm_first_parent(path, data->base); + else + futils__error_cannot_rmdir( + path->ptr, "parent is not directory"); + } + else + error = git_fs_path_set_error(errno, path->ptr, "rmdir"); + } + + else if (S_ISDIR(st.st_mode)) { + data->depth++; + + error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data); + + data->depth--; + + if (error < 0) + return error; + + if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) + return error; + + if ((error = p_rmdir(path->ptr)) < 0) { + if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && + (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) + error = 0; + else + error = git_fs_path_set_error(errno, path->ptr, "rmdir"); + } + } + + else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { + if (p_unlink(path->ptr) < 0) + error = git_fs_path_set_error(errno, path->ptr, "remove"); + } + + else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) + error = futils__error_cannot_rmdir(path->ptr, "still present"); + + return error; +} + +static int futils__rmdir_empty_parent(void *opaque, const char *path) +{ + futils__rmdir_data *data = opaque; + int error = 0; + + if (strlen(path) <= data->baselen) + error = GIT_ITEROVER; + + else if (p_rmdir(path) < 0) { + int en = errno; + + if (en == ENOENT || en == ENOTDIR) { + /* do nothing */ + } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && + en == EBUSY) { + error = git_fs_path_set_error(errno, path, "rmdir"); + } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { + error = GIT_ITEROVER; + } else { + error = git_fs_path_set_error(errno, path, "rmdir"); + } + } + + return error; +} + +int git_futils_rmdir_r( + const char *path, const char *base, uint32_t flags) +{ + int error; + git_str fullpath = GIT_STR_INIT; + futils__rmdir_data data; + + /* build path and find "root" where we should start calling mkdir */ + if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0) + return -1; + + memset(&data, 0, sizeof(data)); + data.base = base ? base : ""; + data.baselen = base ? strlen(base) : 0; + data.flags = flags; + + error = futils__rmdir_recurs_foreach(&data, &fullpath); + + /* remove now-empty parents if requested */ + if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) + error = git_fs_path_walk_up( + &fullpath, base, futils__rmdir_empty_parent, &data); + + if (error == GIT_ITEROVER) { + git_error_clear(); + error = 0; + } + + git_str_dispose(&fullpath); + + return error; +} + +int git_futils_fake_symlink(const char *target, const char *path) +{ + int retcode = GIT_ERROR; + int fd = git_futils_creat_withpath(path, 0755, 0644); + if (fd >= 0) { + retcode = p_write(fd, target, strlen(target)); + p_close(fd); + } + return retcode; +} + +static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) +{ + int error = 0; + char buffer[GIT_BUFSIZE_FILEIO]; + ssize_t len = 0; + + while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) + /* p_write() does not have the same semantics as write(). It loops + * internally and will return 0 when it has completed writing. + */ + error = p_write(ofd, buffer, len); + + if (len < 0) { + git_error_set(GIT_ERROR_OS, "read error while copying file"); + error = (int)len; + } + + if (error < 0) + git_error_set(GIT_ERROR_OS, "write error while copying file"); + + if (close_fd_when_done) { + p_close(ifd); + p_close(ofd); + } + + return error; +} + +int git_futils_cp(const char *from, const char *to, mode_t filemode) +{ + int ifd, ofd; + + if ((ifd = git_futils_open_ro(from)) < 0) + return ifd; + + if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { + p_close(ifd); + return git_fs_path_set_error(errno, to, "open for writing"); + } + + return cp_by_fd(ifd, ofd, true); +} + +int git_futils_touch(const char *path, time_t *when) +{ + struct p_timeval times[2]; + int ret; + + times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); + times[0].tv_usec = times[1].tv_usec = 0; + + ret = p_utimes(path, times); + + return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0; +} + +static int cp_link(const char *from, const char *to, size_t link_size) +{ + int error = 0; + ssize_t read_len; + char *link_data; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1); + link_data = git__malloc(alloc_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(from, link_data, link_size); + if (read_len != (ssize_t)link_size) { + git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from); + error = -1; + } + else { + link_data[read_len] = '\0'; + + if (p_symlink(link_data, to) < 0) { + git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'", + link_data, to); + error = -1; + } + } + + git__free(link_data); + return error; +} + +typedef struct { + const char *to_root; + git_str to; + ssize_t from_prefix; + uint32_t flags; + uint32_t mkdir_flags; + mode_t dirmode; +} cp_r_info; + +#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) + +static int _cp_r_mkdir(cp_r_info *info, git_str *from) +{ + int error = 0; + + /* create root directory the first time we need to create a directory */ + if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { + error = git_futils_mkdir( + info->to_root, info->dirmode, + (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); + + info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; + } + + /* create directory with root as base to prevent excess chmods */ + if (!error) + error = git_futils_mkdir_relative( + from->ptr + info->from_prefix, info->to_root, + info->dirmode, info->mkdir_flags, NULL); + + return error; +} + +static int _cp_r_callback(void *ref, git_str *from) +{ + int error = 0; + cp_r_info *info = ref; + struct stat from_st, to_st; + bool exists = false; + + if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && + from->ptr[git_fs_path_basename_offset(from)] == '.') + return 0; + + if ((error = git_str_joinpath( + &info->to, info->to_root, from->ptr + info->from_prefix)) < 0) + return error; + + if (!(error = git_fs_path_lstat(info->to.ptr, &to_st))) + exists = true; + else if (error != GIT_ENOTFOUND) + return error; + else { + git_error_clear(); + error = 0; + } + + if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0) + return error; + + if (S_ISDIR(from_st.st_mode)) { + mode_t oldmode = info->dirmode; + + /* if we are not chmod'ing, then overwrite dirmode */ + if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) + info->dirmode = from_st.st_mode; + + /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ + if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) + error = _cp_r_mkdir(info, from); + + /* recurse onto target directory */ + if (!error && (!exists || S_ISDIR(to_st.st_mode))) + error = git_fs_path_direach(from, 0, _cp_r_callback, info); + + if (oldmode != 0) + info->dirmode = oldmode; + + return error; + } + + if (exists) { + if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) + return 0; + + if (p_unlink(info->to.ptr) < 0) { + git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'", + info->to.ptr); + return GIT_EEXISTS; + } + } + + /* Done if this isn't a regular file or a symlink */ + if (!S_ISREG(from_st.st_mode) && + (!S_ISLNK(from_st.st_mode) || + (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) + return 0; + + /* Make container directory on demand if needed */ + if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && + (error = _cp_r_mkdir(info, from)) < 0) + return error; + + /* make symlink or regular file */ + if (info->flags & GIT_CPDIR_LINK_FILES) { + if ((error = p_link(from->ptr, info->to.ptr)) < 0) + git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr); + } else if (S_ISLNK(from_st.st_mode)) { + error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); + } else { + mode_t usemode = from_st.st_mode; + + if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) + usemode = GIT_PERMS_FOR_WRITE(usemode); + + error = git_futils_cp(from->ptr, info->to.ptr, usemode); + } + + return error; +} + +int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode) +{ + int error; + git_str path = GIT_STR_INIT; + cp_r_info info; + + if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */ + return -1; + + memset(&info, 0, sizeof(info)); + info.to_root = to; + info.flags = flags; + info.dirmode = dirmode; + info.from_prefix = path.size; + git_str_init(&info.to, 0); + + /* precalculate mkdir flags */ + if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { + /* if not creating empty dirs, then use mkdir to create the path on + * demand right before files are copied. + */ + info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; + if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) + info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; + } else { + /* otherwise, we will do simple mkdir as directories are encountered */ + info.mkdir_flags = + ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; + } + + error = _cp_r_callback(&info, &path); + + git_str_dispose(&path); + git_str_dispose(&info.to); + + return error; +} + +int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path) +{ + struct stat st; + + /* if the stamp is NULL, then always reload */ + if (stamp == NULL) + return 1; + + if (p_stat(path, &st) < 0) + return GIT_ENOTFOUND; + + if (stamp->mtime.tv_sec == st.st_mtime && +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec == st.st_mtime_nsec && +#endif + stamp->size == (uint64_t)st.st_size && + stamp->ino == (unsigned int)st.st_ino) + return 0; + + stamp->mtime.tv_sec = st.st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st.st_mtime_nsec; +#endif + stamp->size = (uint64_t)st.st_size; + stamp->ino = (unsigned int)st.st_ino; + + return 1; +} + +void git_futils_filestamp_set( + git_futils_filestamp *target, const git_futils_filestamp *source) +{ + if (source) + memcpy(target, source, sizeof(*target)); + else + memset(target, 0, sizeof(*target)); +} + + +void git_futils_filestamp_set_from_stat( + git_futils_filestamp *stamp, struct stat *st) +{ + if (st) { + stamp->mtime.tv_sec = st->st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st->st_mtime_nsec; +#else + stamp->mtime.tv_nsec = 0; +#endif + stamp->size = (uint64_t)st->st_size; + stamp->ino = (unsigned int)st->st_ino; + } else { + memset(stamp, 0, sizeof(*stamp)); + } +} + +int git_futils_fsync_dir(const char *path) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(path); + return 0; +#else + int fd, error = -1; + + if ((fd = p_open(path, O_RDONLY)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path); + return -1; + } + + if ((error = p_fsync(fd)) < 0) + git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path); + + p_close(fd); + return error; +#endif +} + +int git_futils_fsync_parent(const char *path) +{ + char *parent; + int error; + + if ((parent = git_fs_path_dirname(path)) == NULL) + return -1; + + error = git_futils_fsync_dir(parent); + git__free(parent); + return error; +} diff --git a/src/util/futils.h b/src/util/futils.h new file mode 100644 index 000000000..3f207afb2 --- /dev/null +++ b/src/util/futils.h @@ -0,0 +1,403 @@ +/* + * 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_futils_h__ +#define INCLUDE_futils_h__ + +#include "git2_util.h" + +#include "map.h" +#include "posix.h" +#include "fs_path.h" +#include "pool.h" +#include "strmap.h" +#include "hash.h" + +/** + * Filebuffer methods + * + * Read whole files into an in-memory buffer for processing + */ +extern int git_futils_readbuffer(git_str *obj, const char *path); +extern int git_futils_readbuffer_updated( + git_str *obj, + const char *path, + unsigned char checksum[GIT_HASH_SHA1_SIZE], + int *updated); +extern int git_futils_readbuffer_fd_full(git_str *obj, git_file fd); +extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); + +/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We + * support these internally and they will be removed before the `open` call. + */ +#ifndef O_FSYNC +# define O_FSYNC (1 << 31) +#endif + +extern int git_futils_writebuffer( + const git_str *buf, const char *path, int open_flags, mode_t mode); + +/** + * File utils + * + * These are custom filesystem-related helper methods. They are + * rather high level, and wrap the underlying POSIX methods + * + * All these methods return 0 on success, + * or an error code on failure and an error message is set. + */ + +/** + * Create and open a file, while also + * creating all the folders in its path + */ +extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create and open a process-locked file + */ +extern int git_futils_creat_locked(const char *path, const mode_t mode); + +/** + * Create and open a process-locked file, while + * also creating all the folders in its path + */ +extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create a path recursively. + */ +extern int git_futils_mkdir_r(const char *path, const mode_t mode); + +/** + * Flags to pass to `git_futils_mkdir`. + * + * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. + * * GIT_MKDIR_PATH says to make all components in the path. + * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation + * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path + * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path + * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path + * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST + * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs + * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs + * + * Note that the chmod options will be executed even if the directory already + * exists, unless GIT_MKDIR_EXCL is given. + */ +typedef enum { + GIT_MKDIR_EXCL = 1, + GIT_MKDIR_PATH = 2, + GIT_MKDIR_CHMOD = 4, + GIT_MKDIR_CHMOD_PATH = 8, + GIT_MKDIR_SKIP_LAST = 16, + GIT_MKDIR_SKIP_LAST2 = 32, + GIT_MKDIR_VERIFY_DIR = 64, + GIT_MKDIR_REMOVE_FILES = 128, + GIT_MKDIR_REMOVE_SYMLINKS = 256 +} git_futils_mkdir_flags; + +struct git_futils_mkdir_perfdata +{ + size_t stat_calls; + size_t mkdir_calls; + size_t chmod_calls; +}; + +struct git_futils_mkdir_options +{ + git_strmap *dir_map; + git_pool *pool; + struct git_futils_mkdir_perfdata perfdata; +}; + +/** + * Create a directory or entire path. + * + * This makes a directory (and the entire path leading up to it if requested), + * and optionally chmods the directory immediately after (or each part of the + * path if requested). + * + * @param path The path to create, relative to base. + * @param base Root for relative path. These directories will never be made. + * @param mode The mode to use for created directories. + * @param flags Combination of the mkdir flags above. + * @param opts Extended options, or null. + * @return 0 on success, else error code + */ +extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); + +/** + * Create a directory or entire path. Similar to `git_futils_mkdir_relative` + * without performance data. + */ +extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); + +/** + * Create all the folders required to contain + * the full path of a file + */ +extern int git_futils_mkpath2file(const char *path, const mode_t mode); + +/** + * Flags to pass to `git_futils_rmdir_r`. + * + * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty + * dirs and generate error if any files are found. + * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy. + * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. + * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base + * if removing this item leaves them empty + * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR + * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself + */ +typedef enum { + GIT_RMDIR_EMPTY_HIERARCHY = 0, + GIT_RMDIR_REMOVE_FILES = (1 << 0), + GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), + GIT_RMDIR_EMPTY_PARENTS = (1 << 2), + GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), + GIT_RMDIR_SKIP_ROOT = (1 << 4) +} git_futils_rmdir_flags; + +/** + * Remove path and any files and directories beneath it. + * + * @param path Path to the top level directory to process. + * @param base Root for relative path. + * @param flags Combination of git_futils_rmdir_flags values + * @return 0 on success; -1 on error. + */ +extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); + +/** + * Create and open a temporary file with a `_git2_` suffix in a + * protected directory; the file created will created will honor + * the current `umask`. Writes the filename into path_out. + * + * This function uses a high-quality PRNG seeded by the system's + * entropy pool _where available_ and falls back to a simple seed + * (time plus system information) when not. This is suitable for + * writing within a protected directory, but the system's safe + * temporary file creation functions should be preferred where + * available when writing into world-writable (temp) directories. + * + * @return On success, an open file descriptor, else an error code < 0. + */ +extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode); + +/** + * Move a file on the filesystem, create the + * destination path if it doesn't exist + */ +extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); + +/** + * Copy a file + * + * The filemode will be used for the newly created file. + */ +extern int git_futils_cp( + const char *from, + const char *to, + mode_t filemode); + +/** + * Set the files atime and mtime to the given time, or the current time + * if `ts` is NULL. + */ +extern int git_futils_touch(const char *path, time_t *when); + +/** + * Flags that can be passed to `git_futils_cp_r`. + * + * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no + * files under them (otherwise directories will only be created lazily + * when a file inside them is copied). + * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. + * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. + * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, + * otherwise they are silently skipped. + * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` + * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the + * source file to the target; with this flag, always use 0666 (or 0777 if + * source has exec bits set) for target. + * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files + */ +typedef enum { + GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), + GIT_CPDIR_COPY_SYMLINKS = (1u << 1), + GIT_CPDIR_COPY_DOTFILES = (1u << 2), + GIT_CPDIR_OVERWRITE = (1u << 3), + GIT_CPDIR_CHMOD_DIRS = (1u << 4), + GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), + GIT_CPDIR_LINK_FILES = (1u << 6) +} git_futils_cpdir_flags; + +/** + * Copy a directory tree. + * + * This copies directories and files from one root to another. You can + * pass a combination of GIT_CPDIR flags as defined above. + * + * If you pass the CHMOD flag, then the dirmode will be applied to all + * directories that are created during the copy, overriding the natural + * permissions. If you do not pass the CHMOD flag, then the dirmode + * will actually be copied from the source files and the `dirmode` arg + * will be ignored. + */ +extern int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode); + +/** + * Open a file readonly and set error if needed. + */ +extern int git_futils_open_ro(const char *path); + +/** + * Truncate a file, creating it if it doesn't exist. + */ +extern int git_futils_truncate(const char *path, int mode); + +/** + * Get the filesize in bytes of a file + */ +extern int git_futils_filesize(uint64_t *out, git_file fd); + +#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0) +#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) +#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) + +#define GIT_MODE_PERMS_MASK 0777 +#define GIT_MODE_TYPE_MASK 0170000 +#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) +#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) + +/** + * Convert a mode_t from the OS to a legal git mode_t value. + */ +extern mode_t git_futils_canonical_mode(mode_t raw_mode); + + +/** + * Read-only map all or part of a file into memory. + * When possible this function should favor a virtual memory + * style mapping over some form of malloc()+read(), as the + * data access will be random and is not likely to touch the + * majority of the region requested. + * + * @param out buffer to populate with the mapping information. + * @param fd open descriptor to configure the mapping from. + * @param begin first byte to map, this should be page aligned. + * @param len number of bytes to map. + * @return + * - 0 on success; + * - -1 on error. + */ +extern int git_futils_mmap_ro( + git_map *out, + git_file fd, + off64_t begin, + size_t len); + +/** + * Read-only map an entire file. + * + * @param out buffer to populate with the mapping information. + * @param path path to file to be opened. + * @return + * - 0 on success; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. + */ +extern int git_futils_mmap_ro_file( + git_map *out, + const char *path); + +/** + * Release the memory associated with a previous memory mapping. + * @param map the mapping description previously configured. + */ +extern void git_futils_mmap_free(git_map *map); + +/** + * Create a "fake" symlink (text file containing the target path). + * + * @param target original symlink target + * @param path symlink file to be created + * @return 0 on success, -1 on error + */ +extern int git_futils_fake_symlink(const char *target, const char *path); + +/** + * A file stamp represents a snapshot of information about a file that can + * be used to test if the file changes. This portable implementation is + * based on stat data about that file, but it is possible that OS specific + * versions could be implemented in the future. + */ +typedef struct { + struct timespec mtime; + uint64_t size; + unsigned int ino; +} git_futils_filestamp; + +/** + * Compare stat information for file with reference info. + * + * This function updates the file stamp to current data for the given path + * and returns 0 if the file is up-to-date relative to the prior setting, + * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't + * exist. This will not call git_error_set, so you must set the error if you + * plan to return an error. + * + * @param stamp File stamp to be checked + * @param path Path to stat and check if changed + * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat + */ +extern int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path); + +/** + * Set or reset file stamp data + * + * This writes the target file stamp. If the source is NULL, this will set + * the target stamp to values that will definitely be out of date. If the + * source is not NULL, this copies the source values to the target. + * + * @param tgt File stamp to write to + * @param src File stamp to copy from or NULL to clear the target + */ +extern void git_futils_filestamp_set( + git_futils_filestamp *tgt, const git_futils_filestamp *src); + +/** + * Set file stamp data from stat structure + */ +extern void git_futils_filestamp_set_from_stat( + git_futils_filestamp *stamp, struct stat *st); + +/** + * `fsync` the parent directory of the given path, if `fsync` is + * supported for directories on this platform. + * + * @param path Path of the directory to sync. + * @return 0 on success, -1 on error + */ +extern int git_futils_fsync_dir(const char *path); + +/** + * `fsync` the parent directory of the given path, if `fsync` is + * supported for directories on this platform. + * + * @param path Path of the file whose parent directory should be synced. + * @return 0 on success, -1 on error + */ +extern int git_futils_fsync_parent(const char *path); + +#endif diff --git a/src/util/git2_util.h b/src/util/git2_util.h new file mode 100644 index 000000000..ad3f1c71f --- /dev/null +++ b/src/util/git2_util.h @@ -0,0 +1,168 @@ +/* + * 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_git2_util_h__ +#define INCLUDE_git2_util_h__ + +#ifndef LIBGIT2_NO_FEATURES_H +# include "git2/sys/features.h" +#endif + +#include "git2/common.h" +#include "cc-compat.h" + +typedef struct git_str git_str; + +/** Declare a function as always inlined. */ +#if defined(_MSC_VER) +# define GIT_INLINE(type) static __inline type +#elif defined(__GNUC__) +# define GIT_INLINE(type) static __inline__ type +#else +# define GIT_INLINE(type) static type +#endif + +/** Support for gcc/clang __has_builtin intrinsic */ +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +/** + * Declare that a function's return value must be used. + * + * Used mostly to guard against potential silent bugs at runtime. This is + * recommended to be added to functions that: + * + * - Allocate / reallocate memory. This prevents memory leaks or errors where + * buffers are expected to have grown to a certain size, but could not be + * resized. + * - Acquire locks. When a lock cannot be acquired, that will almost certainly + * cause a data race / undefined behavior. + */ +#if defined(__GNUC__) +# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +# define GIT_WARN_UNUSED_RESULT +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef GIT_WIN32 + +# include +# include +# include +# include +# include +# include "win32/msvc-compat.h" +# include "win32/mingw-compat.h" +# include "win32/win32-compat.h" +# include "win32/w32_common.h" +# include "win32/version.h" +# include "win32/error.h" +# ifdef GIT_THREADS +# include "win32/thread.h" +# endif + +#else + +# include +# include +# ifdef GIT_THREADS +# include +# include +# endif + +#define GIT_LIBGIT2_CALL +#define GIT_SYSTEM_CALL + +#ifdef GIT_USE_STAT_ATIMESPEC +# define st_atim st_atimespec +# define st_ctim st_ctimespec +# define st_mtim st_mtimespec +#endif + +# include + +#endif + +#include "git2/types.h" +#include "git2/errors.h" +#include "thread.h" +#include "integer.h" +#include "assert_safe.h" + +#include "posix.h" + +#define GIT_BUFSIZE_DEFAULT 65536 +#define GIT_BUFSIZE_FILEIO GIT_BUFSIZE_DEFAULT +#define GIT_BUFSIZE_FILTERIO GIT_BUFSIZE_DEFAULT +#define GIT_BUFSIZE_NETIO GIT_BUFSIZE_DEFAULT + + +/** + * Check a pointer allocation result, returning -1 if it failed. + */ +#define GIT_ERROR_CHECK_ALLOC(ptr) do { \ + if ((ptr) == NULL) { return -1; } \ + } while(0) + +/** + * Check a buffer allocation result, returning -1 if it failed. + */ +#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \ + if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \ + } while(0) + +/** + * Check a return value and propagate result if non-zero. + */ +#define GIT_ERROR_CHECK_ERROR(code) \ + do { int _err = (code); if (_err) return _err; } while (0) + + +/** Check for additive overflow, setting an error if would occur. */ +#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \ + (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0) + +/** Check for additive overflow, setting an error if would occur. */ +#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \ + (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0) + +/** Check for additive overflow, failing if it would occur. */ +#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } + +/** Check for multiplicative overflow, failing if it would occur. */ +#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ + if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } + +#include "util.h" + +#endif diff --git a/src/util/hash.c b/src/util/hash.c new file mode 100644 index 000000000..ff900cea7 --- /dev/null +++ b/src/util/hash.c @@ -0,0 +1,158 @@ +/* + * 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 "hash.h" + +int git_hash_global_init(void) +{ + if (git_hash_sha1_global_init() < 0 || + git_hash_sha256_global_init() < 0) + return -1; + + return 0; +} + +int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm) +{ + int error; + + switch (algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + error = git_hash_sha1_ctx_init(&ctx->ctx.sha1); + break; + case GIT_HASH_ALGORITHM_SHA256: + error = git_hash_sha256_ctx_init(&ctx->ctx.sha256); + break; + default: + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + error = -1; + } + + ctx->algorithm = algorithm; + return error; +} + +void git_hash_ctx_cleanup(git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1); + return; + case GIT_HASH_ALGORITHM_SHA256: + git_hash_sha256_ctx_cleanup(&ctx->ctx.sha256); + return; + default: + /* unreachable */ ; + } +} + +int git_hash_init(git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_init(&ctx->ctx.sha1); + case GIT_HASH_ALGORITHM_SHA256: + return git_hash_sha256_init(&ctx->ctx.sha256); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_update(&ctx->ctx.sha1, data, len); + case GIT_HASH_ALGORITHM_SHA256: + return git_hash_sha256_update(&ctx->ctx.sha256, data, len); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_final(unsigned char *out, git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_final(out, &ctx->ctx.sha1); + case GIT_HASH_ALGORITHM_SHA256: + return git_hash_sha256_final(out, &ctx->ctx.sha256); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_buf( + unsigned char *out, + const void *data, + size_t len, + git_hash_algorithm_t algorithm) +{ + git_hash_ctx ctx; + int error = 0; + + if (git_hash_ctx_init(&ctx, algorithm) < 0) + return -1; + + if ((error = git_hash_update(&ctx, data, len)) >= 0) + error = git_hash_final(out, &ctx); + + git_hash_ctx_cleanup(&ctx); + + return error; +} + +int git_hash_vec( + unsigned char *out, + git_str_vec *vec, + size_t n, + git_hash_algorithm_t algorithm) +{ + git_hash_ctx ctx; + size_t i; + int error = 0; + + if (git_hash_ctx_init(&ctx, algorithm) < 0) + return -1; + + for (i = 0; i < n; i++) { + if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) + goto done; + } + + error = git_hash_final(out, &ctx); + +done: + git_hash_ctx_cleanup(&ctx); + + return error; +} + +int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len) +{ + static char hex[] = "0123456789abcdef"; + char *str = out; + size_t i; + + for (i = 0; i < hash_len; i++) { + *str++ = hex[hash[i] >> 4]; + *str++ = hex[hash[i] & 0x0f]; + } + + *str++ = '\0'; + + return 0; +} diff --git a/src/util/hash.h b/src/util/hash.h new file mode 100644 index 000000000..387c5a66f --- /dev/null +++ b/src/util/hash.h @@ -0,0 +1,48 @@ +/* + * 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_h__ +#define INCLUDE_hash_h__ + +#include "git2_util.h" + +#include "hash/sha.h" + +typedef struct { + void *data; + size_t len; +} git_str_vec; + +typedef enum { + GIT_HASH_ALGORITHM_NONE = 0, + GIT_HASH_ALGORITHM_SHA1, + GIT_HASH_ALGORITHM_SHA256 +} git_hash_algorithm_t; + +typedef struct git_hash_ctx { + union { + git_hash_sha1_ctx sha1; + git_hash_sha256_ctx sha256; + } ctx; + git_hash_algorithm_t algorithm; +} git_hash_ctx; + +int git_hash_global_init(void); + +int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm); +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(unsigned char *out, git_hash_ctx *c); + +int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm); +int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm); + +int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len); + +#endif diff --git a/src/util/hash/builtin.c b/src/util/hash/builtin.c new file mode 100644 index 000000000..cc4aa58fe --- /dev/null +++ b/src/util/hash/builtin.c @@ -0,0 +1,53 @@ +/* + * 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 "builtin.h" + +int git_hash_sha256_global_init(void) +{ + return 0; +} + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx) +{ + return git_hash_sha256_init(ctx); +} + +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha256_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + if (SHA256Reset(&ctx->c)) { + git_error_set(GIT_ERROR_SHA, "SHA256 error"); + return -1; + } + return 0; +} + +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + if (SHA256Input(&ctx->c, data, len)) { + git_error_set(GIT_ERROR_SHA, "SHA256 error"); + return -1; + } + return 0; +} + +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + if (SHA256Result(&ctx->c, out)) { + git_error_set(GIT_ERROR_SHA, "SHA256 error"); + return -1; + } + return 0; +} diff --git a/src/util/hash/builtin.h b/src/util/hash/builtin.h new file mode 100644 index 000000000..769df1ac2 --- /dev/null +++ b/src/util/hash/builtin.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_builtin_h__ +#define INCLUDE_hash_builtin_h__ + +#include "hash/sha.h" + +#include "rfc6234/sha.h" + +struct git_hash_sha256_ctx { + SHA256Context c; +}; + +#endif diff --git a/src/util/hash/collisiondetect.c b/src/util/hash/collisiondetect.c new file mode 100644 index 000000000..c51a402d7 --- /dev/null +++ b/src/util/hash/collisiondetect.c @@ -0,0 +1,48 @@ +/* + * 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 "collisiondetect.h" + +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) +{ + GIT_ASSERT_ARG(ctx); + SHA1DCInit(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + SHA1DCUpdate(&ctx->c, data, len); + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + if (SHA1DCFinal(out, &ctx->c)) { + git_error_set(GIT_ERROR_SHA, "SHA1 collision attack detected"); + return -1; + } + + return 0; +} diff --git a/src/util/hash/collisiondetect.h b/src/util/hash/collisiondetect.h new file mode 100644 index 000000000..8de550230 --- /dev/null +++ b/src/util/hash/collisiondetect.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_collisiondetect_h__ +#define INCLUDE_hash_collisiondetect_h__ + +#include "hash/sha.h" + +#include "sha1dc/sha1.h" + +struct git_hash_sha1_ctx { + SHA1_CTX c; +}; + +#endif diff --git a/src/util/hash/common_crypto.c b/src/util/hash/common_crypto.c new file mode 100644 index 000000000..b327ba9e3 --- /dev/null +++ b/src/util/hash/common_crypto.c @@ -0,0 +1,112 @@ +/* + * 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_crypto.h" + +#define CC_LONG_MAX ((CC_LONG)-1) + +#ifdef GIT_SHA1_COMMON_CRYPTO + +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) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA1_Init(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + const unsigned char *data = _data; + + GIT_ASSERT_ARG(ctx); + + while (len > 0) { + CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; + + CC_SHA1_Update(&ctx->c, data, chunk); + + data += chunk; + len -= chunk; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA1_Final(out, &ctx->c); + return 0; +} + +#endif + +#ifdef GIT_SHA256_COMMON_CRYPTO + +int git_hash_sha256_global_init(void) +{ + return 0; +} + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx) +{ + return git_hash_sha256_init(ctx); +} + +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha256_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA256_Init(&ctx->c); + return 0; +} + +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *_data, size_t len) +{ + const unsigned char *data = _data; + + GIT_ASSERT_ARG(ctx); + + while (len > 0) { + CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; + + CC_SHA256_Update(&ctx->c, data, chunk); + + data += chunk; + len -= chunk; + } + + return 0; +} + +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA256_Final(out, &ctx->c); + return 0; +} + +#endif diff --git a/src/util/hash/common_crypto.h b/src/util/hash/common_crypto.h new file mode 100644 index 000000000..157712b5f --- /dev/null +++ b/src/util/hash/common_crypto.h @@ -0,0 +1,27 @@ +/* + * 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_common_crypto_h__ +#define INCLUDE_hash_common_crypto_h__ + +#include "hash/sha.h" + +#include + +#ifdef GIT_SHA1_COMMON_CRYPTO +struct git_hash_sha1_ctx { + CC_SHA1_CTX c; +}; +#endif + +#ifdef GIT_SHA256_COMMON_CRYPTO +struct git_hash_sha256_ctx { + CC_SHA256_CTX c; +}; +#endif + +#endif diff --git a/src/util/hash/mbedtls.c b/src/util/hash/mbedtls.c new file mode 100644 index 000000000..ecdfb7879 --- /dev/null +++ b/src/util/hash/mbedtls.c @@ -0,0 +1,92 @@ +/* + * 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 "mbedtls.h" + +#ifdef GIT_SHA1_MBEDTLS + +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) +{ + if (ctx) + mbedtls_sha1_free(&ctx->c); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_init(&ctx->c); + mbedtls_sha1_starts(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_update(&ctx->c, data, len); + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_finish(&ctx->c, out); + return 0; +} + +#endif + +#ifdef GIT_SHA256_MBEDTLS + +int git_hash_sha256_global_init(void) +{ + return 0; +} + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx) +{ + return git_hash_sha256_init(ctx); +} + +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx) +{ + if (ctx) + mbedtls_sha256_free(&ctx->c); +} + +int git_hash_sha256_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha256_init(&ctx->c); + mbedtls_sha256_starts(&ctx->c, 0); + return 0; +} + +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha256_update(&ctx->c, data, len); + return 0; +} + +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha256_finish(&ctx->c, out); + return 0; +} + +#endif diff --git a/src/util/hash/mbedtls.h b/src/util/hash/mbedtls.h new file mode 100644 index 000000000..05fb38b0e --- /dev/null +++ b/src/util/hash/mbedtls.h @@ -0,0 +1,29 @@ +/* + * 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_mbedtls_h__ +#define INCLUDE_hash_mbedtls_h__ + +#include "hash/sha.h" + +#ifdef GIT_SHA1_MBEDTLS +# include + +struct git_hash_sha1_ctx { + mbedtls_sha1_context c; +}; +#endif + +#ifdef GIT_SHA256_MBEDTLS +# include + +struct git_hash_sha256_ctx { + mbedtls_sha256_context c; +}; +#endif + +#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/util/hash/openssl.c b/src/util/hash/openssl.c new file mode 100644 index 000000000..649358ca2 --- /dev/null +++ b/src/util/hash/openssl.c @@ -0,0 +1,194 @@ +/* + * 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 "openssl.h" + +#ifdef GIT_OPENSSL_DYNAMIC +# include + +int handle_count; +void *openssl_handle; + +static int git_hash_openssl_global_shutdown(void) +{ + if (--handle_count == 0) { + dlclose(openssl_handle); + openssl_handle = NULL; + } + + return 0; +} + +static int git_hash_openssl_global_init(void) +{ + if (!handle_count) { + if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL) { + git_error_set(GIT_ERROR_SSL, "could not load ssl libraries"); + return -1; + } + } + + if (git_hash_openssl_global_shutdown() < 0) + return -1; + + handle_count++; + return 0; +} + +#endif + +#ifdef GIT_SHA1_OPENSSL + +# ifdef GIT_OPENSSL_DYNAMIC +static int (*SHA1_Init)(SHA_CTX *c); +static int (*SHA1_Update)(SHA_CTX *c, const void *data, size_t len); +static int (*SHA1_Final)(unsigned char *md, SHA_CTX *c); +# endif + +int git_hash_sha1_global_init(void) +{ +#ifdef GIT_OPENSSL_DYNAMIC + if (git_hash_openssl_global_init() < 0) + return -1; + + if ((SHA1_Init = dlsym(openssl_handle, "SHA1_Init")) == NULL || + (SHA1_Update = dlsym(openssl_handle, "SHA1_Update")) == NULL || + (SHA1_Final = dlsym(openssl_handle, "SHA1_Final")) == NULL) { + const char *msg = dlerror(); + git_error_set(GIT_ERROR_SSL, "could not load hash function: %s", msg ? msg : "unknown error"); + return -1; + } +#endif + + 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) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Init(&ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to initialize sha1 context"); + return -1; + } + + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Update(&ctx->c, data, len) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to update sha1"); + return -1; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Final(out, &ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to finalize sha1"); + return -1; + } + + return 0; +} + +#endif + +#ifdef GIT_SHA256_OPENSSL + +# ifdef GIT_OPENSSL_DYNAMIC +static int (*SHA256_Init)(SHA256_CTX *c); +static int (*SHA256_Update)(SHA256_CTX *c, const void *data, size_t len); +static int (*SHA256_Final)(unsigned char *md, SHA256_CTX *c); +#endif + +int git_hash_sha256_global_init(void) +{ +#ifdef GIT_OPENSSL_DYNAMIC + if (git_hash_openssl_global_init() < 0) + return -1; + + if ((SHA256_Init = dlsym(openssl_handle, "SHA256_Init")) == NULL || + (SHA256_Update = dlsym(openssl_handle, "SHA256_Update")) == NULL || + (SHA256_Final = dlsym(openssl_handle, "SHA256_Final")) == NULL) { + const char *msg = dlerror(); + git_error_set(GIT_ERROR_SSL, "could not load hash function: %s", msg ? msg : "unknown error"); + return -1; + } +#endif + + return 0; +} + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx) +{ + return git_hash_sha256_init(ctx); +} + +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha256_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA256_Init(&ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to initialize sha256 context"); + return -1; + } + + return 0; +} + +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA256_Update(&ctx->c, data, len) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to update sha256"); + return -1; + } + + return 0; +} + +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA256_Final(out, &ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to finalize sha256"); + return -1; + } + + return 0; +} + +#endif diff --git a/src/util/hash/openssl.h b/src/util/hash/openssl.h new file mode 100644 index 000000000..7cb089abc --- /dev/null +++ b/src/util/hash/openssl.h @@ -0,0 +1,45 @@ +/* + * 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_openssl_h__ +#define INCLUDE_hash_openssl_h__ + +#include "hash/sha.h" + +#ifndef GIT_OPENSSL_DYNAMIC +# include +#else + +typedef struct { + unsigned int h0, h1, h2, h3, h4; + unsigned int Nl, Nh; + unsigned int data[16]; + unsigned int num; +} SHA_CTX; + +typedef struct { + unsigned int h[8]; + unsigned int Nl, Nh; + unsigned int data[16]; + unsigned int num, md_len; +} SHA256_CTX; + +#endif + +#ifdef GIT_SHA1_OPENSSL +struct git_hash_sha1_ctx { + SHA_CTX c; +}; +#endif + +#ifdef GIT_SHA256_OPENSSL +struct git_hash_sha256_ctx { + SHA256_CTX c; +}; +#endif + +#endif diff --git a/src/util/hash/rfc6234/sha.h b/src/util/hash/rfc6234/sha.h new file mode 100644 index 000000000..e0c400ca1 --- /dev/null +++ b/src/util/hash/rfc6234/sha.h @@ -0,0 +1,355 @@ +/**************************** sha.h ****************************/ +/***************** See RFC 6234 for details. *******************/ +/* + Copyright (c) 2011 IETF Trust and the persons identified as + authors of the code. 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. + + - Neither the name of Internet Society, IETF or IETF Trust, nor + the names of specific contributors, may be used to endorse or + promote products derived from this software without specific + prior written permission. + + 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 _SHA_H_ +#define _SHA_H_ + +/* + * Description: + * This file implements the Secure Hash Algorithms + * as defined in the U.S. National Institute of Standards + * and Technology Federal Information Processing Standards + * Publication (FIPS PUB) 180-3 published in October 2008 + * and formerly defined in its predecessors, FIPS PUB 180-1 + * and FIP PUB 180-2. + * + * A combined document showing all algorithms is available at + * http://csrc.nist.gov/publications/fips/ + * fips180-3/fips180-3_final.pdf + * + * The five hashes are defined in these sizes: + * SHA-1 20 byte / 160 bit + * SHA-224 28 byte / 224 bit + * SHA-256 32 byte / 256 bit + * SHA-384 48 byte / 384 bit + * SHA-512 64 byte / 512 bit + * + * Compilation Note: + * These files may be compiled with two options: + * USE_32BIT_ONLY - use 32-bit arithmetic only, for systems + * without 64-bit integers + * + * USE_MODIFIED_MACROS - use alternate form of the SHA_Ch() + * and SHA_Maj() macros that are equivalent + * and potentially faster on many systems + * + */ + +#include +/* + * If you do not have the ISO standard stdint.h header file, then you + * must typedef the following: + * name meaning + * uint64_t unsigned 64-bit integer + * uint32_t unsigned 32-bit integer + * uint8_t unsigned 8-bit integer (i.e., unsigned char) + * int_least16_t integer of >= 16 bits + * + * See stdint-example.h + */ + +#ifndef _SHA_enum_ +#define _SHA_enum_ +/* + * All SHA functions return one of these values. + */ +enum { + shaSuccess = 0, + shaNull, /* Null pointer parameter */ + shaInputTooLong, /* input data too long */ + shaStateError, /* called Input after FinalBits or Result */ + shaBadParam /* passed a bad parameter */ +}; +#endif /* _SHA_enum_ */ + +/* + * These constants hold size information for each of the SHA + * hashing operations + */ +enum { + SHA1_Message_Block_Size = 64, SHA224_Message_Block_Size = 64, + SHA256_Message_Block_Size = 64, SHA384_Message_Block_Size = 128, + SHA512_Message_Block_Size = 128, + USHA_Max_Message_Block_Size = SHA512_Message_Block_Size, + SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32, + SHA384HashSize = 48, SHA512HashSize = 64, + USHAMaxHashSize = SHA512HashSize, + + SHA1HashSizeBits = 160, SHA224HashSizeBits = 224, + SHA256HashSizeBits = 256, SHA384HashSizeBits = 384, + SHA512HashSizeBits = 512, USHAMaxHashSizeBits = SHA512HashSizeBits +}; + +/* + * These constants are used in the USHA (Unified SHA) functions. + */ +typedef enum SHAversion { + SHA1, SHA224, SHA256, SHA384, SHA512 +} SHAversion; + +/* + * This structure will hold context information for the SHA-1 + * hashing operation. + */ +typedef struct SHA1Context { + uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ + + uint32_t Length_High; /* Message length in bits */ + uint32_t Length_Low; /* Message length in bits */ + + int_least16_t Message_Block_Index; /* Message_Block array index */ + /* 512-bit message blocks */ + uint8_t Message_Block[SHA1_Message_Block_Size]; + + int Computed; /* Is the hash computed? */ + int Corrupted; /* Cumulative corruption code */ +} SHA1Context; + +/* + * This structure will hold context information for the SHA-256 + * hashing operation. + */ +typedef struct SHA256Context { + uint32_t Intermediate_Hash[SHA256HashSize/4]; /* Message Digest */ + + uint32_t Length_High; /* Message length in bits */ + uint32_t Length_Low; /* Message length in bits */ + + int_least16_t Message_Block_Index; /* Message_Block array index */ + /* 512-bit message blocks */ + uint8_t Message_Block[SHA256_Message_Block_Size]; + + int Computed; /* Is the hash computed? */ + int Corrupted; /* Cumulative corruption code */ +} SHA256Context; + +/* + * This structure will hold context information for the SHA-512 + * hashing operation. + */ +typedef struct SHA512Context { +#ifdef USE_32BIT_ONLY + uint32_t Intermediate_Hash[SHA512HashSize/4]; /* Message Digest */ + uint32_t Length[4]; /* Message length in bits */ +#else /* !USE_32BIT_ONLY */ + uint64_t Intermediate_Hash[SHA512HashSize/8]; /* Message Digest */ + uint64_t Length_High, Length_Low; /* Message length in bits */ +#endif /* USE_32BIT_ONLY */ + + int_least16_t Message_Block_Index; /* Message_Block array index */ + /* 1024-bit message blocks */ + uint8_t Message_Block[SHA512_Message_Block_Size]; + + int Computed; /* Is the hash computed?*/ + int Corrupted; /* Cumulative corruption code */ +} SHA512Context; + +/* + * This structure will hold context information for the SHA-224 + * hashing operation. It uses the SHA-256 structure for computation. + */ +typedef struct SHA256Context SHA224Context; + +/* + * This structure will hold context information for the SHA-384 + * hashing operation. It uses the SHA-512 structure for computation. + */ +typedef struct SHA512Context SHA384Context; + +/* + * This structure holds context information for all SHA + * hashing operations. + */ +typedef struct USHAContext { + int whichSha; /* which SHA is being used */ + union { + SHA1Context sha1Context; + SHA224Context sha224Context; SHA256Context sha256Context; + SHA384Context sha384Context; SHA512Context sha512Context; + } ctx; +} USHAContext; + +/* + * This structure will hold context information for the HMAC + * keyed-hashing operation. + */ +typedef struct HMACContext { + int whichSha; /* which SHA is being used */ + int hashSize; /* hash size of SHA being used */ + int blockSize; /* block size of SHA being used */ + USHAContext shaContext; /* SHA context */ + unsigned char k_opad[USHA_Max_Message_Block_Size]; + /* outer padding - key XORd with opad */ + int Computed; /* Is the MAC computed? */ + int Corrupted; /* Cumulative corruption code */ + +} HMACContext; + +/* + * This structure will hold context information for the HKDF + * extract-and-expand Key Derivation Functions. + */ +typedef struct HKDFContext { + int whichSha; /* which SHA is being used */ + HMACContext hmacContext; + int hashSize; /* hash size of SHA being used */ + unsigned char prk[USHAMaxHashSize]; + /* pseudo-random key - output of hkdfInput */ + int Computed; /* Is the key material computed? */ + int Corrupted; /* Cumulative corruption code */ +} HKDFContext; + +/* + * Function Prototypes + */ + +/* SHA-1 */ +extern int SHA1Reset(SHA1Context *); +extern int SHA1Input(SHA1Context *, const uint8_t *bytes, + unsigned int bytecount); +extern int SHA1FinalBits(SHA1Context *, uint8_t bits, + unsigned int bit_count); +extern int SHA1Result(SHA1Context *, + uint8_t Message_Digest[SHA1HashSize]); + +/* SHA-224 */ +extern int SHA224Reset(SHA224Context *); +extern int SHA224Input(SHA224Context *, const uint8_t *bytes, + unsigned int bytecount); +extern int SHA224FinalBits(SHA224Context *, uint8_t bits, + unsigned int bit_count); +extern int SHA224Result(SHA224Context *, + uint8_t Message_Digest[SHA224HashSize]); + +/* SHA-256 */ +extern int SHA256Reset(SHA256Context *); +extern int SHA256Input(SHA256Context *, const uint8_t *bytes, + unsigned int bytecount); +extern int SHA256FinalBits(SHA256Context *, uint8_t bits, + unsigned int bit_count); +extern int SHA256Result(SHA256Context *, + uint8_t Message_Digest[SHA256HashSize]); + +/* SHA-384 */ +extern int SHA384Reset(SHA384Context *); +extern int SHA384Input(SHA384Context *, const uint8_t *bytes, + unsigned int bytecount); +extern int SHA384FinalBits(SHA384Context *, uint8_t bits, + unsigned int bit_count); +extern int SHA384Result(SHA384Context *, + uint8_t Message_Digest[SHA384HashSize]); + +/* SHA-512 */ +extern int SHA512Reset(SHA512Context *); +extern int SHA512Input(SHA512Context *, const uint8_t *bytes, + unsigned int bytecount); +extern int SHA512FinalBits(SHA512Context *, uint8_t bits, + unsigned int bit_count); +extern int SHA512Result(SHA512Context *, + uint8_t Message_Digest[SHA512HashSize]); + +/* Unified SHA functions, chosen by whichSha */ +extern int USHAReset(USHAContext *context, SHAversion whichSha); +extern int USHAInput(USHAContext *context, + const uint8_t *bytes, unsigned int bytecount); +extern int USHAFinalBits(USHAContext *context, + uint8_t bits, unsigned int bit_count); +extern int USHAResult(USHAContext *context, + uint8_t Message_Digest[USHAMaxHashSize]); +extern int USHABlockSize(enum SHAversion whichSha); +extern int USHAHashSize(enum SHAversion whichSha); +extern int USHAHashSizeBits(enum SHAversion whichSha); +extern const char *USHAHashName(enum SHAversion whichSha); + +/* + * HMAC Keyed-Hashing for Message Authentication, RFC 2104, + * for all SHAs. + * This interface allows a fixed-length text input to be used. + */ +extern int hmac(SHAversion whichSha, /* which SHA algorithm to use */ + const unsigned char *text, /* pointer to data stream */ + int text_len, /* length of data stream */ + const unsigned char *key, /* pointer to authentication key */ + int key_len, /* length of authentication key */ + uint8_t digest[USHAMaxHashSize]); /* caller digest to fill in */ + +/* + * HMAC Keyed-Hashing for Message Authentication, RFC 2104, + * for all SHAs. + * This interface allows any length of text input to be used. + */ +extern int hmacReset(HMACContext *context, enum SHAversion whichSha, + const unsigned char *key, int key_len); +extern int hmacInput(HMACContext *context, const unsigned char *text, + int text_len); +extern int hmacFinalBits(HMACContext *context, uint8_t bits, + unsigned int bit_count); +extern int hmacResult(HMACContext *context, + uint8_t digest[USHAMaxHashSize]); + +/* + * HKDF HMAC-based Extract-and-Expand Key Derivation Function, + * RFC 5869, for all SHAs. + */ +extern int hkdf(SHAversion whichSha, const unsigned char *salt, + int salt_len, const unsigned char *ikm, int ikm_len, + const unsigned char *info, int info_len, + uint8_t okm[ ], int okm_len); +extern int hkdfExtract(SHAversion whichSha, const unsigned char *salt, + int salt_len, const unsigned char *ikm, + int ikm_len, uint8_t prk[USHAMaxHashSize]); +extern int hkdfExpand(SHAversion whichSha, const uint8_t prk[ ], + int prk_len, const unsigned char *info, + int info_len, uint8_t okm[ ], int okm_len); + +/* + * HKDF HMAC-based Extract-and-Expand Key Derivation Function, + * RFC 5869, for all SHAs. + * This interface allows any length of text input to be used. + */ +extern int hkdfReset(HKDFContext *context, enum SHAversion whichSha, + const unsigned char *salt, int salt_len); +extern int hkdfInput(HKDFContext *context, const unsigned char *ikm, + int ikm_len); +extern int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits, + unsigned int ikm_bit_count); +extern int hkdfResult(HKDFContext *context, + uint8_t prk[USHAMaxHashSize], + const unsigned char *info, int info_len, + uint8_t okm[USHAMaxHashSize], int okm_len); +#endif /* _SHA_H_ */ diff --git a/src/util/hash/rfc6234/sha224-256.c b/src/util/hash/rfc6234/sha224-256.c new file mode 100644 index 000000000..c8e0cf854 --- /dev/null +++ b/src/util/hash/rfc6234/sha224-256.c @@ -0,0 +1,601 @@ +/************************* sha224-256.c ************************/ +/***************** See RFC 6234 for details. *******************/ +/* Copyright (c) 2011 IETF Trust and the persons identified as */ +/* authors of the code. All rights reserved. */ +/* See sha.h for terms of use and redistribution. */ + +/* + * Description: + * This file implements the Secure Hash Algorithms SHA-224 and + * SHA-256 as defined in the U.S. National Institute of Standards + * and Technology Federal Information Processing Standards + * Publication (FIPS PUB) 180-3 published in October 2008 + * and formerly defined in its predecessors, FIPS PUB 180-1 + * and FIP PUB 180-2. + * + * A combined document showing all algorithms is available at + * http://csrc.nist.gov/publications/fips/ + * fips180-3/fips180-3_final.pdf + * + * The SHA-224 and SHA-256 algorithms produce 224-bit and 256-bit + * message digests for a given data stream. It should take about + * 2**n steps to find a message with the same digest as a given + * message and 2**(n/2) to find any two messages with the same + * digest, when n is the digest size in bits. Therefore, this + * algorithm can serve as a means of providing a + * "fingerprint" for a message. + * + * Portability Issues: + * SHA-224 and SHA-256 are defined in terms of 32-bit "words". + * This code uses (included via "sha.h") to define 32- + * and 8-bit unsigned integer types. If your C compiler does not + * support 32-bit unsigned integers, this code is not + * appropriate. + * + * Caveats: + * SHA-224 and SHA-256 are designed to work with messages less + * than 2^64 bits long. This implementation uses SHA224/256Input() + * to hash the bits that are a multiple of the size of an 8-bit + * octet, and then optionally uses SHA224/256FinalBits() + * to hash the final few bits of the input. + */ + +#include "sha.h" + +/* + * These definitions are defined in FIPS 180-3, section 4.1. + * Ch() and Maj() are defined identically in sections 4.1.1, + * 4.1.2, and 4.1.3. + * + * The definitions used in FIPS 180-3 are as follows: + */ +#ifndef USE_MODIFIED_MACROS +#define SHA_Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +#define SHA_Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#else /* USE_MODIFIED_MACROS */ +/* + * The following definitions are equivalent and potentially faster. + */ +#define SHA_Ch(x, y, z) (((x) & ((y) ^ (z))) ^ (z)) +#define SHA_Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) +#endif /* USE_MODIFIED_MACROS */ + +#define SHA_Parity(x, y, z) ((x) ^ (y) ^ (z)) + +/* Define the SHA shift, rotate left, and rotate right macros */ +#define SHA256_SHR(bits,word) ((word) >> (bits)) +#define SHA256_ROTL(bits,word) \ + (((word) << (bits)) | ((word) >> (32-(bits)))) +#define SHA256_ROTR(bits,word) \ + (((word) >> (bits)) | ((word) << (32-(bits)))) + +/* Define the SHA SIGMA and sigma macros */ +#define SHA256_SIGMA0(word) \ + (SHA256_ROTR( 2,word) ^ SHA256_ROTR(13,word) ^ SHA256_ROTR(22,word)) +#define SHA256_SIGMA1(word) \ + (SHA256_ROTR( 6,word) ^ SHA256_ROTR(11,word) ^ SHA256_ROTR(25,word)) +#define SHA256_sigma0(word) \ + (SHA256_ROTR( 7,word) ^ SHA256_ROTR(18,word) ^ SHA256_SHR( 3,word)) +#define SHA256_sigma1(word) \ + (SHA256_ROTR(17,word) ^ SHA256_ROTR(19,word) ^ SHA256_SHR(10,word)) + +/* + * Add "length" to the length. + * Set Corrupted when overflow has occurred. + */ +static uint32_t addTemp; +#define SHA224_256AddLength(context, length) \ + (addTemp = (context)->Length_Low, (context)->Corrupted = \ + (((context)->Length_Low += (length)) < addTemp) && \ + (++(context)->Length_High == 0) ? shaInputTooLong : \ + (context)->Corrupted ) + +/* Local Function Prototypes */ +static int SHA224_256Reset(SHA256Context *context, uint32_t *H0); +static void SHA224_256ProcessMessageBlock(SHA256Context *context); +static void SHA224_256Finalize(SHA256Context *context, + uint8_t Pad_Byte); +static void SHA224_256PadMessage(SHA256Context *context, + uint8_t Pad_Byte); +static int SHA224_256ResultN(SHA256Context *context, + uint8_t Message_Digest[ ], int HashSize); + +/* Initial Hash Values: FIPS 180-3 section 5.3.2 */ +static uint32_t SHA224_H0[SHA256HashSize/4] = { + 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, + 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4 +}; + +/* Initial Hash Values: FIPS 180-3 section 5.3.3 */ +static uint32_t SHA256_H0[SHA256HashSize/4] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + +/* + * SHA224Reset + * + * Description: + * This function will initialize the SHA224Context in preparation + * for computing a new SHA224 message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * sha Error Code. + */ +int SHA224Reset(SHA224Context *context) +{ + return SHA224_256Reset(context, SHA224_H0); +} + +/* + * SHA224Input + * + * Description: + * This function accepts an array of octets as the next portion + * of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update. + * message_array[ ]: [in] + * An array of octets representing the next portion of + * the message. + * length: [in] + * The length of the message in message_array. + * + * Returns: + * sha Error Code. + * + */ +int SHA224Input(SHA224Context *context, const uint8_t *message_array, + unsigned int length) +{ + return SHA256Input(context, message_array, length); +} + +/* + * SHA224FinalBits + * + * Description: + * This function will add in any final bits of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update. + * message_bits: [in] + * The final bits of the message, in the upper portion of the + * byte. (Use 0b###00000 instead of 0b00000### to input the + * three bits ###.) + * length: [in] + * The number of bits in message_bits, between 1 and 7. + * + * Returns: + * sha Error Code. + */ +int SHA224FinalBits(SHA224Context *context, + uint8_t message_bits, unsigned int length) +{ + return SHA256FinalBits(context, message_bits, length); +} + +/* + * SHA224Result + * + * Description: + * This function will return the 224-bit message digest + * into the Message_Digest array provided by the caller. + * NOTE: + * The first octet of hash is stored in the element with index 0, + * the last octet of hash in the element with index 27. + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA hash. + * Message_Digest[ ]: [out] + * Where the digest is returned. + * + * Returns: + * sha Error Code. + */ +int SHA224Result(SHA224Context *context, + uint8_t Message_Digest[SHA224HashSize]) +{ + return SHA224_256ResultN(context, Message_Digest, SHA224HashSize); +} + +/* + * SHA256Reset + * + * Description: + * This function will initialize the SHA256Context in preparation + * for computing a new SHA256 message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * sha Error Code. + */ +int SHA256Reset(SHA256Context *context) +{ + return SHA224_256Reset(context, SHA256_H0); +} + +/* + * SHA256Input + * + * Description: + * This function accepts an array of octets as the next portion + * of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update. + * message_array[ ]: [in] + * An array of octets representing the next portion of + * the message. + * length: [in] + * The length of the message in message_array. + * + * Returns: + * sha Error Code. + */ +int SHA256Input(SHA256Context *context, const uint8_t *message_array, + unsigned int length) +{ + if (!context) return shaNull; + if (!length) return shaSuccess; + if (!message_array) return shaNull; + if (context->Computed) return context->Corrupted = shaStateError; + if (context->Corrupted) return context->Corrupted; + + while (length--) { + context->Message_Block[context->Message_Block_Index++] = + *message_array; + + if ((SHA224_256AddLength(context, 8) == shaSuccess) && + (context->Message_Block_Index == SHA256_Message_Block_Size)) + SHA224_256ProcessMessageBlock(context); + + message_array++; + } + + return context->Corrupted; + +} + +/* + * SHA256FinalBits + * + * Description: + * This function will add in any final bits of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update. + * message_bits: [in] + * The final bits of the message, in the upper portion of the + * byte. (Use 0b###00000 instead of 0b00000### to input the + * three bits ###.) + * length: [in] + * The number of bits in message_bits, between 1 and 7. + * + * Returns: + * sha Error Code. + */ +int SHA256FinalBits(SHA256Context *context, + uint8_t message_bits, unsigned int length) +{ + static uint8_t masks[8] = { + /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80, + /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0, + /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8, + /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE + }; + static uint8_t markbit[8] = { + /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40, + /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10, + /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04, + /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01 + }; + + if (!context) return shaNull; + if (!length) return shaSuccess; + if (context->Corrupted) return context->Corrupted; + if (context->Computed) return context->Corrupted = shaStateError; + if (length >= 8) return context->Corrupted = shaBadParam; + + SHA224_256AddLength(context, length); + SHA224_256Finalize(context, (uint8_t) + ((message_bits & masks[length]) | markbit[length])); + + return context->Corrupted; +} + +/* + * SHA256Result + * + * Description: + * This function will return the 256-bit message digest + * into the Message_Digest array provided by the caller. + * NOTE: + * The first octet of hash is stored in the element with index 0, + * the last octet of hash in the element with index 31. + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA hash. + * Message_Digest[ ]: [out] + * Where the digest is returned. + * + * Returns: + * sha Error Code. + */ +int SHA256Result(SHA256Context *context, + uint8_t Message_Digest[SHA256HashSize]) +{ + return SHA224_256ResultN(context, Message_Digest, SHA256HashSize); +} + +/* + * SHA224_256Reset + * + * Description: + * This helper function will initialize the SHA256Context in + * preparation for computing a new SHA-224 or SHA-256 message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * H0[ ]: [in] + * The initial hash value array to use. + * + * Returns: + * sha Error Code. + */ +static int SHA224_256Reset(SHA256Context *context, uint32_t *H0) +{ + if (!context) return shaNull; + + context->Length_High = context->Length_Low = 0; + context->Message_Block_Index = 0; + + context->Intermediate_Hash[0] = H0[0]; + context->Intermediate_Hash[1] = H0[1]; + context->Intermediate_Hash[2] = H0[2]; + context->Intermediate_Hash[3] = H0[3]; + context->Intermediate_Hash[4] = H0[4]; + context->Intermediate_Hash[5] = H0[5]; + context->Intermediate_Hash[6] = H0[6]; + context->Intermediate_Hash[7] = H0[7]; + + context->Computed = 0; + context->Corrupted = shaSuccess; + + return shaSuccess; +} + +/* + * SHA224_256ProcessMessageBlock + * + * Description: + * This helper function will process the next 512 bits of the + * message stored in the Message_Block array. + * + * Parameters: + * context: [in/out] + * The SHA context to update. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in this code, especially the + * single character names, were used because those were the + * names used in the Secure Hash Standard. + */ +static void SHA224_256ProcessMessageBlock(SHA256Context *context) +{ + /* Constants defined in FIPS 180-3, section 4.2.2 */ + static const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, + 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, + 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, + 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, + 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, + 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, + 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, + 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + int t, t4; /* Loop counter */ + uint32_t temp1, temp2; /* Temporary word value */ + uint32_t W[64]; /* Word sequence */ + uint32_t A, B, C, D, E, F, G, H; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for (t = t4 = 0; t < 16; t++, t4 += 4) + W[t] = (((uint32_t)context->Message_Block[t4]) << 24) | + (((uint32_t)context->Message_Block[t4 + 1]) << 16) | + (((uint32_t)context->Message_Block[t4 + 2]) << 8) | + (((uint32_t)context->Message_Block[t4 + 3])); + + for (t = 16; t < 64; t++) + W[t] = SHA256_sigma1(W[t-2]) + W[t-7] + + SHA256_sigma0(W[t-15]) + W[t-16]; + + A = context->Intermediate_Hash[0]; + B = context->Intermediate_Hash[1]; + C = context->Intermediate_Hash[2]; + D = context->Intermediate_Hash[3]; + E = context->Intermediate_Hash[4]; + F = context->Intermediate_Hash[5]; + G = context->Intermediate_Hash[6]; + H = context->Intermediate_Hash[7]; + + for (t = 0; t < 64; t++) { + temp1 = H + SHA256_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t]; + temp2 = SHA256_SIGMA0(A) + SHA_Maj(A,B,C); + H = G; + G = F; + F = E; + E = D + temp1; + D = C; + C = B; + B = A; + A = temp1 + temp2; + } + + context->Intermediate_Hash[0] += A; + context->Intermediate_Hash[1] += B; + context->Intermediate_Hash[2] += C; + context->Intermediate_Hash[3] += D; + context->Intermediate_Hash[4] += E; + context->Intermediate_Hash[5] += F; + context->Intermediate_Hash[6] += G; + context->Intermediate_Hash[7] += H; + + context->Message_Block_Index = 0; +} + +/* + * SHA224_256Finalize + * + * Description: + * This helper function finishes off the digest calculations. + * + * Parameters: + * context: [in/out] + * The SHA context to update. + * Pad_Byte: [in] + * The last byte to add to the message block before the 0-padding + * and length. This will contain the last bits of the message + * followed by another single bit. If the message was an + * exact multiple of 8-bits long, Pad_Byte will be 0x80. + * + * Returns: + * sha Error Code. + */ +static void SHA224_256Finalize(SHA256Context *context, + uint8_t Pad_Byte) +{ + int i; + SHA224_256PadMessage(context, Pad_Byte); + /* message may be sensitive, so clear it out */ + for (i = 0; i < SHA256_Message_Block_Size; ++i) + context->Message_Block[i] = 0; + context->Length_High = 0; /* and clear length */ + context->Length_Low = 0; + context->Computed = 1; +} + +/* + * SHA224_256PadMessage + * + * Description: + * According to the standard, the message must be padded to the next + * even multiple of 512 bits. The first padding bit must be a '1'. + * The last 64 bits represent the length of the original message. + * All bits in between should be 0. This helper function will pad + * the message according to those rules by filling the + * Message_Block array accordingly. When it returns, it can be + * assumed that the message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad. + * Pad_Byte: [in] + * The last byte to add to the message block before the 0-padding + * and length. This will contain the last bits of the message + * followed by another single bit. If the message was an + * exact multiple of 8-bits long, Pad_Byte will be 0x80. + * + * Returns: + * Nothing. + */ +static void SHA224_256PadMessage(SHA256Context *context, + uint8_t Pad_Byte) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index >= (SHA256_Message_Block_Size-8)) { + context->Message_Block[context->Message_Block_Index++] = Pad_Byte; + while (context->Message_Block_Index < SHA256_Message_Block_Size) + context->Message_Block[context->Message_Block_Index++] = 0; + SHA224_256ProcessMessageBlock(context); + } else + context->Message_Block[context->Message_Block_Index++] = Pad_Byte; + + while (context->Message_Block_Index < (SHA256_Message_Block_Size-8)) + context->Message_Block[context->Message_Block_Index++] = 0; + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = (uint8_t)(context->Length_High >> 24); + context->Message_Block[57] = (uint8_t)(context->Length_High >> 16); + context->Message_Block[58] = (uint8_t)(context->Length_High >> 8); + context->Message_Block[59] = (uint8_t)(context->Length_High); + context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24); + context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16); + context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8); + context->Message_Block[63] = (uint8_t)(context->Length_Low); + + SHA224_256ProcessMessageBlock(context); +} + +/* + * SHA224_256ResultN + * + * Description: + * This helper function will return the 224-bit or 256-bit message + * digest into the Message_Digest array provided by the caller. + * NOTE: + * The first octet of hash is stored in the element with index 0, + * the last octet of hash in the element with index 27/31. + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA hash. + * Message_Digest[ ]: [out] + * Where the digest is returned. + * HashSize: [in] + * The size of the hash, either 28 or 32. + * + * Returns: + * sha Error Code. + */ +static int SHA224_256ResultN(SHA256Context *context, + uint8_t Message_Digest[ ], int HashSize) +{ + int i; + + if (!context) return shaNull; + if (!Message_Digest) return shaNull; + if (context->Corrupted) return context->Corrupted; + + if (!context->Computed) + SHA224_256Finalize(context, 0x80); + + for (i = 0; i < HashSize; ++i) + Message_Digest[i] = (uint8_t) + (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) )); + + return shaSuccess; +} + diff --git a/src/util/hash/sha.h b/src/util/hash/sha.h new file mode 100644 index 000000000..4f596234c --- /dev/null +++ b/src/util/hash/sha.h @@ -0,0 +1,70 @@ +/* + * 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_sha_h__ +#define INCLUDE_hash_sha_h__ + +#include "git2_util.h" + +typedef struct git_hash_sha1_ctx git_hash_sha1_ctx; +typedef struct git_hash_sha256_ctx git_hash_sha256_ctx; + +#if defined(GIT_SHA1_COMMON_CRYPTO) || defined(GIT_SHA256_COMMON_CRYPTO) +# include "common_crypto.h" +#endif + +#if defined(GIT_SHA1_OPENSSL) || defined(GIT_SHA256_OPENSSL) +# include "openssl.h" +#endif + +#if defined(GIT_SHA1_WIN32) || defined(GIT_SHA256_WIN32) +# include "win32.h" +#endif + +#if defined(GIT_SHA1_MBEDTLS) || defined(GIT_SHA256_MBEDTLS) +# include "mbedtls.h" +#endif + +#if defined(GIT_SHA1_COLLISIONDETECT) +# include "collisiondetect.h" +#endif + +#if defined(GIT_SHA256_BUILTIN) +# include "builtin.h" +#endif + +/* + * SHA1 + */ + +#define GIT_HASH_SHA1_SIZE 20 + +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(unsigned char *out, git_hash_sha1_ctx *c); + +/* + * SHA256 + */ + +#define GIT_HASH_SHA256_SIZE 32 + +int git_hash_sha256_global_init(void); + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx); +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx); + +int git_hash_sha256_init(git_hash_sha256_ctx *c); +int git_hash_sha256_update(git_hash_sha256_ctx *c, const void *data, size_t len); +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *c); + +#endif diff --git a/src/util/hash/sha1dc/sha1.c b/src/util/hash/sha1dc/sha1.c new file mode 100644 index 000000000..929822728 --- /dev/null +++ b/src/util/hash/sha1dc/sha1.c @@ -0,0 +1,1909 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow (danshu@microsoft.com) +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#include +#include +#include +#include /* make sure macros like _BIG_ENDIAN visible */ +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif + +#include "sha1.h" +#include "ubc_check.h" + +#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) +#define SHA1DC_ON_INTEL_LIKE_PROCESSOR +#endif + +/* + Because Little-Endian architectures are most common, + we only set SHA1DC_BIGENDIAN if one of these conditions is met. + Note that all MSFT platforms are little endian, + so none of these will be defined under the MSC compiler. + If you are compiling on a big endian platform and your compiler does not define one of these, + you will have to add whatever macros your tool chain defines to indicate Big-Endianness. + */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +/* + * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn + * rev #165881). See + * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + * + * This also works under clang since 3.2, it copied the GCC-ism. See + * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ + * predefined macro", 2012-07-27) + */ +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike */ +#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) +/* + * Should detect Big Endian under glibc.git since 14245eb70e ("entered + * into RCS", 1992-11-25). Defined in which will have been + * brought in by standard headers. See glibc.git and + * https://sourceforge.net/p/predef/wiki/Endianness/ + */ +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc */ +#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) +/* + * *BSD and newlib (embedded linux, cygwin, etc). + * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents + * this condition from matching with Solaris/sparc. + * (Solaris defines only one endian macro) + */ +#if _BYTE_ORDER == _BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc or *BSD or newlib */ +#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ + defined(__sparc)) +/* + * Should define Big Endian for a whitelist of known processors. See + * https://sourceforge.net/p/predef/wiki/Endianness/ and + * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html + */ +#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 or */ + +/* We do nothing more here for now */ +/*#error "Uncomment this to see if you fall through all the detection"*/ + +#endif /* Big Endian detection */ + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) +#undef SHA1DC_BIGENDIAN +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#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 /*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)))) + +#define sha1_bswap32(x) \ + {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} + +#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) + +#ifdef SHA1DC_BIGENDIAN + #define sha1_load(m, t, temp) { temp = m[t]; } +#else + #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } +#endif + +#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x + +#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) +#define sha1_f2(b,c,d) ((b)^(c)^(d)) +#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) +#define sha1_f4(b,c,d) ((b)^(c)^(d)) + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ + {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} + +#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } + + +#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; + +#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION +void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) +{ + uint32_t W[80]; + uint32_t a,b,c,d,e; + unsigned i; + + memcpy(W, m, 16 * 4); + for (i = 16; i < 80; ++i) + W[i] = sha1_mix(W, i); + + a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} +#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ + + +static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + +void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + uint32_t temp; + +#ifdef DOSTORESTATE00 + SHA1_STORE_STATE(0) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); + +#ifdef DOSTORESTATE01 + SHA1_STORE_STATE(1) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); + +#ifdef DOSTORESTATE02 + SHA1_STORE_STATE(2) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); + +#ifdef DOSTORESTATE03 + SHA1_STORE_STATE(3) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); + +#ifdef DOSTORESTATE04 + SHA1_STORE_STATE(4) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); + +#ifdef DOSTORESTATE05 + SHA1_STORE_STATE(5) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); + +#ifdef DOSTORESTATE06 + SHA1_STORE_STATE(6) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); + +#ifdef DOSTORESTATE07 + SHA1_STORE_STATE(7) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); + +#ifdef DOSTORESTATE08 + SHA1_STORE_STATE(8) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); + +#ifdef DOSTORESTATE09 + SHA1_STORE_STATE(9) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); + +#ifdef DOSTORESTATE10 + SHA1_STORE_STATE(10) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); + +#ifdef DOSTORESTATE11 + SHA1_STORE_STATE(11) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); + +#ifdef DOSTORESTATE12 + SHA1_STORE_STATE(12) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); + +#ifdef DOSTORESTATE13 + SHA1_STORE_STATE(13) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); + +#ifdef DOSTORESTATE14 + SHA1_STORE_STATE(14) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); + +#ifdef DOSTORESTATE15 + SHA1_STORE_STATE(15) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); + +#ifdef DOSTORESTATE16 + SHA1_STORE_STATE(16) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); + +#ifdef DOSTORESTATE17 + SHA1_STORE_STATE(17) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); + +#ifdef DOSTORESTATE18 + SHA1_STORE_STATE(18) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); + +#ifdef DOSTORESTATE19 + SHA1_STORE_STATE(19) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); + + + +#ifdef DOSTORESTATE20 + SHA1_STORE_STATE(20) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); + +#ifdef DOSTORESTATE21 + SHA1_STORE_STATE(21) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); + +#ifdef DOSTORESTATE22 + SHA1_STORE_STATE(22) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); + +#ifdef DOSTORESTATE23 + SHA1_STORE_STATE(23) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); + +#ifdef DOSTORESTATE24 + SHA1_STORE_STATE(24) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); + +#ifdef DOSTORESTATE25 + SHA1_STORE_STATE(25) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); + +#ifdef DOSTORESTATE26 + SHA1_STORE_STATE(26) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); + +#ifdef DOSTORESTATE27 + SHA1_STORE_STATE(27) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); + +#ifdef DOSTORESTATE28 + SHA1_STORE_STATE(28) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); + +#ifdef DOSTORESTATE29 + SHA1_STORE_STATE(29) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); + +#ifdef DOSTORESTATE30 + SHA1_STORE_STATE(30) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); + +#ifdef DOSTORESTATE31 + SHA1_STORE_STATE(31) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); + +#ifdef DOSTORESTATE32 + SHA1_STORE_STATE(32) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); + +#ifdef DOSTORESTATE33 + SHA1_STORE_STATE(33) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); + +#ifdef DOSTORESTATE34 + SHA1_STORE_STATE(34) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); + +#ifdef DOSTORESTATE35 + SHA1_STORE_STATE(35) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); + +#ifdef DOSTORESTATE36 + SHA1_STORE_STATE(36) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); + +#ifdef DOSTORESTATE37 + SHA1_STORE_STATE(37) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); + +#ifdef DOSTORESTATE38 + SHA1_STORE_STATE(38) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); + +#ifdef DOSTORESTATE39 + SHA1_STORE_STATE(39) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); + + + +#ifdef DOSTORESTATE40 + SHA1_STORE_STATE(40) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); + +#ifdef DOSTORESTATE41 + SHA1_STORE_STATE(41) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); + +#ifdef DOSTORESTATE42 + SHA1_STORE_STATE(42) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); + +#ifdef DOSTORESTATE43 + SHA1_STORE_STATE(43) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); + +#ifdef DOSTORESTATE44 + SHA1_STORE_STATE(44) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); + +#ifdef DOSTORESTATE45 + SHA1_STORE_STATE(45) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); + +#ifdef DOSTORESTATE46 + SHA1_STORE_STATE(46) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); + +#ifdef DOSTORESTATE47 + SHA1_STORE_STATE(47) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); + +#ifdef DOSTORESTATE48 + SHA1_STORE_STATE(48) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); + +#ifdef DOSTORESTATE49 + SHA1_STORE_STATE(49) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); + +#ifdef DOSTORESTATE50 + SHA1_STORE_STATE(50) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); + +#ifdef DOSTORESTATE51 + SHA1_STORE_STATE(51) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); + +#ifdef DOSTORESTATE52 + SHA1_STORE_STATE(52) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); + +#ifdef DOSTORESTATE53 + SHA1_STORE_STATE(53) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); + +#ifdef DOSTORESTATE54 + SHA1_STORE_STATE(54) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); + +#ifdef DOSTORESTATE55 + SHA1_STORE_STATE(55) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); + +#ifdef DOSTORESTATE56 + SHA1_STORE_STATE(56) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); + +#ifdef DOSTORESTATE57 + SHA1_STORE_STATE(57) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); + +#ifdef DOSTORESTATE58 + SHA1_STORE_STATE(58) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); + +#ifdef DOSTORESTATE59 + SHA1_STORE_STATE(59) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); + + + + +#ifdef DOSTORESTATE60 + SHA1_STORE_STATE(60) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); + +#ifdef DOSTORESTATE61 + SHA1_STORE_STATE(61) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); + +#ifdef DOSTORESTATE62 + SHA1_STORE_STATE(62) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); + +#ifdef DOSTORESTATE63 + SHA1_STORE_STATE(63) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); + +#ifdef DOSTORESTATE64 + SHA1_STORE_STATE(64) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); + +#ifdef DOSTORESTATE65 + SHA1_STORE_STATE(65) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); + +#ifdef DOSTORESTATE66 + SHA1_STORE_STATE(66) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); + +#ifdef DOSTORESTATE67 + SHA1_STORE_STATE(67) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); + +#ifdef DOSTORESTATE68 + SHA1_STORE_STATE(68) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); + +#ifdef DOSTORESTATE69 + SHA1_STORE_STATE(69) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); + +#ifdef DOSTORESTATE70 + SHA1_STORE_STATE(70) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); + +#ifdef DOSTORESTATE71 + SHA1_STORE_STATE(71) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); + +#ifdef DOSTORESTATE72 + SHA1_STORE_STATE(72) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); + +#ifdef DOSTORESTATE73 + SHA1_STORE_STATE(73) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); + +#ifdef DOSTORESTATE74 + SHA1_STORE_STATE(74) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); + +#ifdef DOSTORESTATE75 + SHA1_STORE_STATE(75) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); + +#ifdef DOSTORESTATE76 + SHA1_STORE_STATE(76) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); + +#ifdef DOSTORESTATE77 + SHA1_STORE_STATE(77) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); + +#ifdef DOSTORESTATE78 + SHA1_STORE_STATE(78) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); + +#ifdef DOSTORESTATE79 + SHA1_STORE_STATE(79) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); + + + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + + +#define SHA1_RECOMPRESS(t) \ +static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ +{ \ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ + if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ + if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ + if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ + if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ + if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ + if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ + if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ + if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ + if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ + if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ + if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ + if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ + if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ + if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ + if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ + if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ + if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ + if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ + if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ + if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ + if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ + if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ + if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ + if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ + if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ + if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ + if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ + if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ + if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ + if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ + if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ + if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ + if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ + if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ + if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ + if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ + if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ + if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ + if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ + if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ + if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ + if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ + if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ + if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ + if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ + if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ + if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ + if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ + if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ + if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ + if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ + if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ + if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ + if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ + if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ + if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ + if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ + if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ + if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ + if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ + if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ + if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ + if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ + if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ + if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ + if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ + if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ + if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ + if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ + if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ + if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ + if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ + if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ + if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ + if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ + if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ + if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ + if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ + if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ + if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ + ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ + a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ + if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ + if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ + if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ + if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ + if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ + if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ + if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ + if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ + if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ + if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ + if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ + if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ + if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ + if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ + if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ + if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ + if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ + if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ + if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ + if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ + if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ + if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ + if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ + if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ + if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ + if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ + if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ + if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ + if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ + if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ + if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ + if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ + if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ + if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ + if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ + if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ + if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ + if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ + if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ + if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ + if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ + if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ + if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ + if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ + if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ + if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ + if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ + if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ + if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ + if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ + if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ + if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ + if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ + if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ + if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ + if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ + if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ + if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ + if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ + if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ + if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ + if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ + if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ + if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ + if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ + if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ + if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ + if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ + if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ + if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ + if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ + if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ + if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ + if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ + if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ + if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ + if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ + if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ + if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ + if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ + ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */ +#endif + +#ifdef DOSTORESTATE0 +SHA1_RECOMPRESS(0) +#endif + +#ifdef DOSTORESTATE1 +SHA1_RECOMPRESS(1) +#endif + +#ifdef DOSTORESTATE2 +SHA1_RECOMPRESS(2) +#endif + +#ifdef DOSTORESTATE3 +SHA1_RECOMPRESS(3) +#endif + +#ifdef DOSTORESTATE4 +SHA1_RECOMPRESS(4) +#endif + +#ifdef DOSTORESTATE5 +SHA1_RECOMPRESS(5) +#endif + +#ifdef DOSTORESTATE6 +SHA1_RECOMPRESS(6) +#endif + +#ifdef DOSTORESTATE7 +SHA1_RECOMPRESS(7) +#endif + +#ifdef DOSTORESTATE8 +SHA1_RECOMPRESS(8) +#endif + +#ifdef DOSTORESTATE9 +SHA1_RECOMPRESS(9) +#endif + +#ifdef DOSTORESTATE10 +SHA1_RECOMPRESS(10) +#endif + +#ifdef DOSTORESTATE11 +SHA1_RECOMPRESS(11) +#endif + +#ifdef DOSTORESTATE12 +SHA1_RECOMPRESS(12) +#endif + +#ifdef DOSTORESTATE13 +SHA1_RECOMPRESS(13) +#endif + +#ifdef DOSTORESTATE14 +SHA1_RECOMPRESS(14) +#endif + +#ifdef DOSTORESTATE15 +SHA1_RECOMPRESS(15) +#endif + +#ifdef DOSTORESTATE16 +SHA1_RECOMPRESS(16) +#endif + +#ifdef DOSTORESTATE17 +SHA1_RECOMPRESS(17) +#endif + +#ifdef DOSTORESTATE18 +SHA1_RECOMPRESS(18) +#endif + +#ifdef DOSTORESTATE19 +SHA1_RECOMPRESS(19) +#endif + +#ifdef DOSTORESTATE20 +SHA1_RECOMPRESS(20) +#endif + +#ifdef DOSTORESTATE21 +SHA1_RECOMPRESS(21) +#endif + +#ifdef DOSTORESTATE22 +SHA1_RECOMPRESS(22) +#endif + +#ifdef DOSTORESTATE23 +SHA1_RECOMPRESS(23) +#endif + +#ifdef DOSTORESTATE24 +SHA1_RECOMPRESS(24) +#endif + +#ifdef DOSTORESTATE25 +SHA1_RECOMPRESS(25) +#endif + +#ifdef DOSTORESTATE26 +SHA1_RECOMPRESS(26) +#endif + +#ifdef DOSTORESTATE27 +SHA1_RECOMPRESS(27) +#endif + +#ifdef DOSTORESTATE28 +SHA1_RECOMPRESS(28) +#endif + +#ifdef DOSTORESTATE29 +SHA1_RECOMPRESS(29) +#endif + +#ifdef DOSTORESTATE30 +SHA1_RECOMPRESS(30) +#endif + +#ifdef DOSTORESTATE31 +SHA1_RECOMPRESS(31) +#endif + +#ifdef DOSTORESTATE32 +SHA1_RECOMPRESS(32) +#endif + +#ifdef DOSTORESTATE33 +SHA1_RECOMPRESS(33) +#endif + +#ifdef DOSTORESTATE34 +SHA1_RECOMPRESS(34) +#endif + +#ifdef DOSTORESTATE35 +SHA1_RECOMPRESS(35) +#endif + +#ifdef DOSTORESTATE36 +SHA1_RECOMPRESS(36) +#endif + +#ifdef DOSTORESTATE37 +SHA1_RECOMPRESS(37) +#endif + +#ifdef DOSTORESTATE38 +SHA1_RECOMPRESS(38) +#endif + +#ifdef DOSTORESTATE39 +SHA1_RECOMPRESS(39) +#endif + +#ifdef DOSTORESTATE40 +SHA1_RECOMPRESS(40) +#endif + +#ifdef DOSTORESTATE41 +SHA1_RECOMPRESS(41) +#endif + +#ifdef DOSTORESTATE42 +SHA1_RECOMPRESS(42) +#endif + +#ifdef DOSTORESTATE43 +SHA1_RECOMPRESS(43) +#endif + +#ifdef DOSTORESTATE44 +SHA1_RECOMPRESS(44) +#endif + +#ifdef DOSTORESTATE45 +SHA1_RECOMPRESS(45) +#endif + +#ifdef DOSTORESTATE46 +SHA1_RECOMPRESS(46) +#endif + +#ifdef DOSTORESTATE47 +SHA1_RECOMPRESS(47) +#endif + +#ifdef DOSTORESTATE48 +SHA1_RECOMPRESS(48) +#endif + +#ifdef DOSTORESTATE49 +SHA1_RECOMPRESS(49) +#endif + +#ifdef DOSTORESTATE50 +SHA1_RECOMPRESS(50) +#endif + +#ifdef DOSTORESTATE51 +SHA1_RECOMPRESS(51) +#endif + +#ifdef DOSTORESTATE52 +SHA1_RECOMPRESS(52) +#endif + +#ifdef DOSTORESTATE53 +SHA1_RECOMPRESS(53) +#endif + +#ifdef DOSTORESTATE54 +SHA1_RECOMPRESS(54) +#endif + +#ifdef DOSTORESTATE55 +SHA1_RECOMPRESS(55) +#endif + +#ifdef DOSTORESTATE56 +SHA1_RECOMPRESS(56) +#endif + +#ifdef DOSTORESTATE57 +SHA1_RECOMPRESS(57) +#endif + +#ifdef DOSTORESTATE58 +SHA1_RECOMPRESS(58) +#endif + +#ifdef DOSTORESTATE59 +SHA1_RECOMPRESS(59) +#endif + +#ifdef DOSTORESTATE60 +SHA1_RECOMPRESS(60) +#endif + +#ifdef DOSTORESTATE61 +SHA1_RECOMPRESS(61) +#endif + +#ifdef DOSTORESTATE62 +SHA1_RECOMPRESS(62) +#endif + +#ifdef DOSTORESTATE63 +SHA1_RECOMPRESS(63) +#endif + +#ifdef DOSTORESTATE64 +SHA1_RECOMPRESS(64) +#endif + +#ifdef DOSTORESTATE65 +SHA1_RECOMPRESS(65) +#endif + +#ifdef DOSTORESTATE66 +SHA1_RECOMPRESS(66) +#endif + +#ifdef DOSTORESTATE67 +SHA1_RECOMPRESS(67) +#endif + +#ifdef DOSTORESTATE68 +SHA1_RECOMPRESS(68) +#endif + +#ifdef DOSTORESTATE69 +SHA1_RECOMPRESS(69) +#endif + +#ifdef DOSTORESTATE70 +SHA1_RECOMPRESS(70) +#endif + +#ifdef DOSTORESTATE71 +SHA1_RECOMPRESS(71) +#endif + +#ifdef DOSTORESTATE72 +SHA1_RECOMPRESS(72) +#endif + +#ifdef DOSTORESTATE73 +SHA1_RECOMPRESS(73) +#endif + +#ifdef DOSTORESTATE74 +SHA1_RECOMPRESS(74) +#endif + +#ifdef DOSTORESTATE75 +SHA1_RECOMPRESS(75) +#endif + +#ifdef DOSTORESTATE76 +SHA1_RECOMPRESS(76) +#endif + +#ifdef DOSTORESTATE77 +SHA1_RECOMPRESS(77) +#endif + +#ifdef DOSTORESTATE78 +SHA1_RECOMPRESS(78) +#endif + +#ifdef DOSTORESTATE79 +SHA1_RECOMPRESS(79) +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) +{ + switch (step) + { +#ifdef DOSTORESTATE0 + case 0: + sha1recompress_fast_0(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE1 + case 1: + sha1recompress_fast_1(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE2 + case 2: + sha1recompress_fast_2(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE3 + case 3: + sha1recompress_fast_3(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE4 + case 4: + sha1recompress_fast_4(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE5 + case 5: + sha1recompress_fast_5(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE6 + case 6: + sha1recompress_fast_6(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE7 + case 7: + sha1recompress_fast_7(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE8 + case 8: + sha1recompress_fast_8(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE9 + case 9: + sha1recompress_fast_9(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE10 + case 10: + sha1recompress_fast_10(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE11 + case 11: + sha1recompress_fast_11(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE12 + case 12: + sha1recompress_fast_12(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE13 + case 13: + sha1recompress_fast_13(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE14 + case 14: + sha1recompress_fast_14(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE15 + case 15: + sha1recompress_fast_15(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE16 + case 16: + sha1recompress_fast_16(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE17 + case 17: + sha1recompress_fast_17(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE18 + case 18: + sha1recompress_fast_18(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE19 + case 19: + sha1recompress_fast_19(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE20 + case 20: + sha1recompress_fast_20(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE21 + case 21: + sha1recompress_fast_21(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE22 + case 22: + sha1recompress_fast_22(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE23 + case 23: + sha1recompress_fast_23(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE24 + case 24: + sha1recompress_fast_24(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE25 + case 25: + sha1recompress_fast_25(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE26 + case 26: + sha1recompress_fast_26(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE27 + case 27: + sha1recompress_fast_27(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE28 + case 28: + sha1recompress_fast_28(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE29 + case 29: + sha1recompress_fast_29(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE30 + case 30: + sha1recompress_fast_30(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE31 + case 31: + sha1recompress_fast_31(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE32 + case 32: + sha1recompress_fast_32(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE33 + case 33: + sha1recompress_fast_33(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE34 + case 34: + sha1recompress_fast_34(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE35 + case 35: + sha1recompress_fast_35(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE36 + case 36: + sha1recompress_fast_36(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE37 + case 37: + sha1recompress_fast_37(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE38 + case 38: + sha1recompress_fast_38(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE39 + case 39: + sha1recompress_fast_39(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE40 + case 40: + sha1recompress_fast_40(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE41 + case 41: + sha1recompress_fast_41(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE42 + case 42: + sha1recompress_fast_42(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE43 + case 43: + sha1recompress_fast_43(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE44 + case 44: + sha1recompress_fast_44(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE45 + case 45: + sha1recompress_fast_45(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE46 + case 46: + sha1recompress_fast_46(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE47 + case 47: + sha1recompress_fast_47(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE48 + case 48: + sha1recompress_fast_48(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE49 + case 49: + sha1recompress_fast_49(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE50 + case 50: + sha1recompress_fast_50(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE51 + case 51: + sha1recompress_fast_51(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE52 + case 52: + sha1recompress_fast_52(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE53 + case 53: + sha1recompress_fast_53(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE54 + case 54: + sha1recompress_fast_54(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE55 + case 55: + sha1recompress_fast_55(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE56 + case 56: + sha1recompress_fast_56(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE57 + case 57: + sha1recompress_fast_57(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE58 + case 58: + sha1recompress_fast_58(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE59 + case 59: + sha1recompress_fast_59(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE60 + case 60: + sha1recompress_fast_60(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE61 + case 61: + sha1recompress_fast_61(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE62 + case 62: + sha1recompress_fast_62(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE63 + case 63: + sha1recompress_fast_63(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE64 + case 64: + sha1recompress_fast_64(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE65 + case 65: + sha1recompress_fast_65(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE66 + case 66: + sha1recompress_fast_66(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE67 + case 67: + sha1recompress_fast_67(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE68 + case 68: + sha1recompress_fast_68(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE69 + case 69: + sha1recompress_fast_69(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE70 + case 70: + sha1recompress_fast_70(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE71 + case 71: + sha1recompress_fast_71(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE72 + case 72: + sha1recompress_fast_72(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE73 + case 73: + sha1recompress_fast_73(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE74 + case 74: + sha1recompress_fast_74(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE75 + case 75: + sha1recompress_fast_75(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE76 + case 76: + sha1recompress_fast_76(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE77 + case 77: + sha1recompress_fast_77(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE78 + case 78: + sha1recompress_fast_78(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE79 + case 79: + sha1recompress_fast_79(ihvin, ihvout, me2, state); + break; +#endif + default: + abort(); + } + +} + + + +static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) +{ + unsigned i, j; + uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; + uint32_t ihvtmp[5]; + + ctx->ihv1[0] = ctx->ihv[0]; + ctx->ihv1[1] = ctx->ihv[1]; + ctx->ihv1[2] = ctx->ihv[2]; + ctx->ihv1[3] = ctx->ihv[3]; + ctx->ihv1[4] = ctx->ihv[4]; + + sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); + + if (ctx->detect_coll) + { + if (ctx->ubc_check) + { + ubc_check(ctx->m1, ubc_dv_mask); + } + + if (ubc_dv_mask[0] != 0) + { + for (i = 0; sha1_dvs[i].dvType != 0; ++i) + { + if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) + { + for (j = 0; j < 80; ++j) + ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; + + sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); + + /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ + if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) + || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) + { + ctx->found_collision = 1; + + if (ctx->safe_hash) + { + sha1_compression_W(ctx->ihv, ctx->m1); + sha1_compression_W(ctx->ihv, ctx->m1); + } + + break; + } + } + } + } + } +} + +void SHA1DCInit(SHA1_CTX *ctx) +{ + ctx->total = 0; + ctx->ihv[0] = 0x67452301; + ctx->ihv[1] = 0xEFCDAB89; + ctx->ihv[2] = 0x98BADCFE; + ctx->ihv[3] = 0x10325476; + ctx->ihv[4] = 0xC3D2E1F0; + ctx->found_collision = 0; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; + ctx->ubc_check = 1; + ctx->detect_coll = 1; + ctx->reduced_round_coll = 0; + ctx->callback = NULL; +} + +void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + + +void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) +{ + unsigned left, fill; + + if (len == 0) + return; + + left = ctx->total & 63; + fill = 64 - left; + + if (left && len >= fill) + { + ctx->total += fill; + memcpy(ctx->buffer + left, buf, fill); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + buf += fill; + len -= fill; + left = 0; + } + while (len >= 64) + { + ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) + sha1_process(ctx, (uint32_t*)(buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ + buf += 64; + len -= 64; + } + if (len > 0) + { + ctx->total += len; + memcpy(ctx->buffer + left, buf, len); + } +} + +static const unsigned char sha1_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); + + total = ctx->total - padn; + total <<= 3; + ctx->buffer[56] = (unsigned char)(total >> 56); + ctx->buffer[57] = (unsigned char)(total >> 48); + ctx->buffer[58] = (unsigned char)(total >> 40); + ctx->buffer[59] = (unsigned char)(total >> 32); + ctx->buffer[60] = (unsigned char)(total >> 24); + ctx->buffer[61] = (unsigned char)(total >> 16); + ctx->buffer[62] = (unsigned char)(total >> 8); + ctx->buffer[63] = (unsigned char)(total); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + output[0] = (unsigned char)(ctx->ihv[0] >> 24); + output[1] = (unsigned char)(ctx->ihv[0] >> 16); + output[2] = (unsigned char)(ctx->ihv[0] >> 8); + output[3] = (unsigned char)(ctx->ihv[0]); + output[4] = (unsigned char)(ctx->ihv[1] >> 24); + output[5] = (unsigned char)(ctx->ihv[1] >> 16); + output[6] = (unsigned char)(ctx->ihv[1] >> 8); + output[7] = (unsigned char)(ctx->ihv[1]); + output[8] = (unsigned char)(ctx->ihv[2] >> 24); + output[9] = (unsigned char)(ctx->ihv[2] >> 16); + output[10] = (unsigned char)(ctx->ihv[2] >> 8); + output[11] = (unsigned char)(ctx->ihv[2]); + output[12] = (unsigned char)(ctx->ihv[3] >> 24); + output[13] = (unsigned char)(ctx->ihv[3] >> 16); + output[14] = (unsigned char)(ctx->ihv[3] >> 8); + output[15] = (unsigned char)(ctx->ihv[3]); + output[16] = (unsigned char)(ctx->ihv[4] >> 24); + output[17] = (unsigned char)(ctx->ihv[4] >> 16); + output[18] = (unsigned char)(ctx->ihv[4] >> 8); + output[19] = (unsigned char)(ctx->ihv[4]); + return ctx->found_collision; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/src/util/hash/sha1dc/sha1.h b/src/util/hash/sha1dc/sha1.h new file mode 100644 index 000000000..1e4e94be5 --- /dev/null +++ b/src/util/hash/sha1dc/sha1.h @@ -0,0 +1,110 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif + +/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ +/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ +void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); + +/* +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. +*/ +typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); + +/* A callback function type that can be set to be called when a collision block has been found: */ +/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ +typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); + +/* The SHA-1 context. */ +typedef struct { + uint64_t total; + uint32_t ihv[5]; + unsigned char buffer[64]; + int found_collision; + int safe_hash; + int detect_coll; + int ubc_check; + int reduced_round_coll; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/* Initialize SHA-1 context. */ +void SHA1DCInit(SHA1_CTX*); + +/* + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. + An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, + but it will result in a different SHA-1 hash for messages where a collision attack was detected. + This will automatically invalidate SHA-1 based digital signature forgeries. + Enabled by default. +*/ +void SHA1DCSetSafeHash(SHA1_CTX*, int); + +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). + Enabled by default + */ +void SHA1DCSetUseUBC(SHA1_CTX*, int); + +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ +void SHA1DCSetUseDetectColl(SHA1_CTX*, int); + +/* function to disable or enable the detection of reduced-round SHA-1 collisions */ +/* disabled by default */ +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); + +/* function to set a callback function, pass NULL to disable */ +/* by default no callback set */ +void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); + +/* update SHA-1 context with buffer contents */ +void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); + +/* obtain SHA-1 hash from SHA-1 context */ +/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ +int SHA1DCFinal(unsigned char[20], SHA1_CTX*); + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/src/util/hash/sha1dc/ubc_check.c b/src/util/hash/sha1dc/ubc_check.c new file mode 100644 index 000000000..b3beff2af --- /dev/null +++ b/src/util/hash/sha1dc/ubc_check.c @@ -0,0 +1,372 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section +*/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif +#include "ubc_check.h" + +static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; +static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; +static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; +static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; +static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; +static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; +static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; +static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; +static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; +static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; +static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; +static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; +static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; +static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; +static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; +static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; +static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; +static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; +static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; +static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; +static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; +static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; +static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; +static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; +static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; +static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; +static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; +static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; +static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; +static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; +static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; +static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; + +dv_info_t sha1_dvs[] = +{ + {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } +, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } +, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } +, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } +, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } +, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } +, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } +, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } +, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } +, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } +, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } +, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } +, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } +, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } +, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } +, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } +, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } +, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } +, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } +, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } +, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } +, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } +, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } +, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } +, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } +, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } +, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } +, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } +, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } +, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } +, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } +, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } +, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} +}; +void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) +{ + uint32_t mask = ~((uint32_t)(0)); + mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); + mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); + mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); + mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); + mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); + mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); + mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); + mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); + mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); + mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); + mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); + mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); + mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); + mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); + mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); + mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); + mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) + mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) + mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) + mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) + mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) + mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) + mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) + mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) + mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); + if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) + mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) + mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); + if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) + mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); + mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) + mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); + mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) + mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); + mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); + mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) + mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) + mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) + mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); + mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) + mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) + mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) + mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) + mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) + mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) + mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) + mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); + mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); + mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); + mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); + mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); + mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); + mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); + mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); + mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); + mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); + mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) + mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) + mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); + if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) + mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) + mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) + mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); +if (mask) { + + if (mask & DV_I_43_0_bit) + if ( + !((W[61]^(W[62]>>5)) & (1<<1)) + || !(!((W[59]^(W[63]>>25)) & (1<<5))) + || !((W[58]^(W[63]>>30)) & (1<<0)) + ) mask &= ~DV_I_43_0_bit; + if (mask & DV_I_44_0_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<1)) + || !(!((W[60]^(W[64]>>25)) & (1<<5))) + || !((W[59]^(W[64]>>30)) & (1<<0)) + ) mask &= ~DV_I_44_0_bit; + if (mask & DV_I_46_2_bit) + mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); + if (mask & DV_I_47_2_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<2)) + || !(!((W[41]^W[43]) & (1<<6))) + ) mask &= ~DV_I_47_2_bit; + if (mask & DV_I_48_2_bit) + if ( + !((W[63]^(W[64]>>5)) & (1<<2)) + || !(!((W[48]^(W[49]<<5)) & (1<<6))) + ) mask &= ~DV_I_48_2_bit; + if (mask & DV_I_49_2_bit) + if ( + !(!((W[49]^(W[50]<<5)) & (1<<6))) + || !((W[42]^W[50]) & (1<<1)) + || !(!((W[39]^(W[40]<<5)) & (1<<6))) + || !((W[38]^W[40]) & (1<<1)) + ) mask &= ~DV_I_49_2_bit; + if (mask & DV_I_50_0_bit) + mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); + if (mask & DV_I_50_2_bit) + mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); + if (mask & DV_I_51_0_bit) + mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); + if (mask & DV_I_51_2_bit) + if ( + !(!((W[51]^(W[52]<<5)) & (1<<6))) + || !(!((W[49]^W[51]) & (1<<6))) + || !(!((W[37]^(W[37]>>5)) & (1<<1))) + || !(!((W[35]^(W[39]>>25)) & (1<<5))) + ) mask &= ~DV_I_51_2_bit; + if (mask & DV_I_52_0_bit) + mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); + if (mask & DV_II_46_2_bit) + mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); + if (mask & DV_II_48_0_bit) + if ( + !(!((W[36]^(W[40]>>25)) & (1<<3))) + || !((W[35]^(W[40]<<2)) & (1<<30)) + ) mask &= ~DV_II_48_0_bit; + if (mask & DV_II_49_0_bit) + if ( + !(!((W[37]^(W[41]>>25)) & (1<<3))) + || !((W[36]^(W[41]<<2)) & (1<<30)) + ) mask &= ~DV_II_49_0_bit; + if (mask & DV_II_49_2_bit) + if ( + !(!((W[53]^(W[54]<<5)) & (1<<6))) + || !(!((W[51]^W[53]) & (1<<6))) + || !((W[50]^W[54]) & (1<<1)) + || !(!((W[45]^(W[46]<<5)) & (1<<6))) + || !(!((W[37]^(W[41]>>25)) & (1<<5))) + || !((W[36]^(W[41]>>30)) & (1<<0)) + ) mask &= ~DV_II_49_2_bit; + if (mask & DV_II_50_0_bit) + if ( + !((W[55]^W[58]) & (1<<29)) + || !(!((W[38]^(W[42]>>25)) & (1<<3))) + || !((W[37]^(W[42]<<2)) & (1<<30)) + ) mask &= ~DV_II_50_0_bit; + if (mask & DV_II_50_2_bit) + if ( + !(!((W[54]^(W[55]<<5)) & (1<<6))) + || !(!((W[52]^W[54]) & (1<<6))) + || !((W[51]^W[55]) & (1<<1)) + || !((W[45]^W[47]) & (1<<1)) + || !(!((W[38]^(W[42]>>25)) & (1<<5))) + || !((W[37]^(W[42]>>30)) & (1<<0)) + ) mask &= ~DV_II_50_2_bit; + if (mask & DV_II_51_0_bit) + if ( + !(!((W[39]^(W[43]>>25)) & (1<<3))) + || !((W[38]^(W[43]<<2)) & (1<<30)) + ) mask &= ~DV_II_51_0_bit; + if (mask & DV_II_51_2_bit) + if ( + !(!((W[55]^(W[56]<<5)) & (1<<6))) + || !(!((W[53]^W[55]) & (1<<6))) + || !((W[52]^W[56]) & (1<<1)) + || !((W[46]^W[48]) & (1<<1)) + || !(!((W[39]^(W[43]>>25)) & (1<<5))) + || !((W[38]^(W[43]>>30)) & (1<<0)) + ) mask &= ~DV_II_51_2_bit; + if (mask & DV_II_52_0_bit) + if ( + !(!((W[59]^W[60]) & (1<<29))) + || !(!((W[40]^(W[44]>>25)) & (1<<3))) + || !(!((W[40]^(W[44]>>25)) & (1<<4))) + || !((W[39]^(W[44]<<2)) & (1<<30)) + ) mask &= ~DV_II_52_0_bit; + if (mask & DV_II_53_0_bit) + if ( + !((W[58]^W[61]) & (1<<29)) + || !(!((W[57]^(W[61]>>25)) & (1<<4))) + || !(!((W[41]^(W[45]>>25)) & (1<<3))) + || !(!((W[41]^(W[45]>>25)) & (1<<4))) + ) mask &= ~DV_II_53_0_bit; + if (mask & DV_II_54_0_bit) + if ( + !(!((W[58]^(W[62]>>25)) & (1<<4))) + || !(!((W[42]^(W[46]>>25)) & (1<<3))) + || !(!((W[42]^(W[46]>>25)) & (1<<4))) + ) mask &= ~DV_II_54_0_bit; + if (mask & DV_II_55_0_bit) + if ( + !(!((W[59]^(W[63]>>25)) & (1<<4))) + || !(!((W[57]^(W[59]>>25)) & (1<<4))) + || !(!((W[43]^(W[47]>>25)) & (1<<3))) + || !(!((W[43]^(W[47]>>25)) & (1<<4))) + ) mask &= ~DV_II_55_0_bit; + if (mask & DV_II_56_0_bit) + if ( + !(!((W[60]^(W[64]>>25)) & (1<<4))) + || !(!((W[44]^(W[48]>>25)) & (1<<3))) + || !(!((W[44]^(W[48]>>25)) & (1<<4))) + ) mask &= ~DV_II_56_0_bit; +} + + dvmask[0]=mask; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/src/util/hash/sha1dc/ubc_check.h b/src/util/hash/sha1dc/ubc_check.h new file mode 100644 index 000000000..d7e17dc73 --- /dev/null +++ b/src/util/hash/sha1dc/ubc_check.h @@ -0,0 +1,52 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +*/ + +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif + +#define DVMASKSIZE 1 +typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; +extern dv_info_t sha1_dvs[]; +void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); + +#define DOSTORESTATE58 +#define DOSTORESTATE65 + +#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/src/util/hash/win32.c b/src/util/hash/win32.c new file mode 100644 index 000000000..f80c0d5ca --- /dev/null +++ b/src/util/hash/win32.c @@ -0,0 +1,549 @@ +/* + * 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.h" + +#include "runtime.h" + +#include +#include + +#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" + +/* BCRYPT_SHA1_ALGORITHM */ +#define GIT_HASH_CNG_SHA1_TYPE L"SHA1" +#define GIT_HASH_CNG_SHA256_TYPE L"SHA256" + +/* BCRYPT_OBJECT_LENGTH */ +#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" + +/* BCRYPT_HASH_REUSEABLE_FLAGS */ +#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 + +/* Definitions */ + +/* CryptoAPI is available for hashing on Windows XP and newer. */ +struct cryptoapi_provider { + HCRYPTPROV handle; +}; + +/* + * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is + * preferred, however it is only available on Windows 2008 and newer and + * must therefore be dynamically loaded, and we must inline constants that + * would not exist when building in pre-Windows 2008 environments. + */ + +/* Function declarations for CNG */ +typedef NTSTATUS (WINAPI *cng_open_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, + LPCWSTR pszAlgId, + LPCWSTR pszImplementation, + DWORD dwFlags); + +typedef NTSTATUS (WINAPI *cng_get_property_fn)( + HANDLE /* BCRYPT_HANDLE */ hObject, + LPCWSTR pszProperty, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG *pcbResult, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *cng_create_hash_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, + PUCHAR pbHashObject, ULONG cbHashObject, + PUCHAR pbSecret, + ULONG cbSecret, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *cng_finish_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *cng_hash_data_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbInput, + ULONG cbInput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *cng_destroy_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash); + +typedef NTSTATUS (WINAPI *cng_close_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + ULONG dwFlags); + +struct cng_provider { + /* DLL for CNG */ + HINSTANCE dll; + + /* Function pointers for CNG */ + cng_open_algorithm_provider_fn open_algorithm_provider; + cng_get_property_fn get_property; + cng_create_hash_fn create_hash; + cng_finish_hash_fn finish_hash; + cng_hash_data_fn hash_data; + cng_destroy_hash_fn destroy_hash; + cng_close_algorithm_provider_fn close_algorithm_provider; + + HANDLE /* BCRYPT_ALG_HANDLE */ sha1_handle; + DWORD sha1_object_size; + + HANDLE /* BCRYPT_ALG_HANDLE */ sha256_handle; + DWORD sha256_object_size; +}; + +typedef struct { + git_hash_win32_provider_t type; + + union { + struct cryptoapi_provider cryptoapi; + struct cng_provider cng; + } provider; +} hash_win32_provider; + +/* Hash provider definition */ + +static hash_win32_provider hash_provider = {0}; + +/* Hash initialization */ + +/* Initialize CNG, if available */ +GIT_INLINE(int) cng_provider_init(void) +{ + char dll_path[MAX_PATH]; + DWORD dll_path_len, size_len; + + /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ + if (!git_has_win32_version(6, 0, 1)) { + git_error_set(GIT_ERROR_SHA, "CryptoNG is not supported on this platform"); + return -1; + } + + /* Load bcrypt.dll explicitly from the system directory */ + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || + dll_path_len > MAX_PATH || + StringCchCat(dll_path, MAX_PATH, "\\") < 0 || + StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || + (hash_provider.provider.cng.dll = LoadLibrary(dll_path)) == NULL) { + git_error_set(GIT_ERROR_SHA, "CryptoNG library could not be loaded"); + return -1; + } + + /* Load the function addresses */ + if ((hash_provider.provider.cng.open_algorithm_provider = (cng_open_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptOpenAlgorithmProvider"))) == NULL || + (hash_provider.provider.cng.get_property = (cng_get_property_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptGetProperty"))) == NULL || + (hash_provider.provider.cng.create_hash = (cng_create_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCreateHash"))) == NULL || + (hash_provider.provider.cng.finish_hash = (cng_finish_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptFinishHash"))) == NULL || + (hash_provider.provider.cng.hash_data = (cng_hash_data_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptHashData"))) == NULL || + (hash_provider.provider.cng.destroy_hash = (cng_destroy_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptDestroyHash"))) == NULL || + (hash_provider.provider.cng.close_algorithm_provider = (cng_close_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCloseAlgorithmProvider"))) == NULL) { + FreeLibrary(hash_provider.provider.cng.dll); + + git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded"); + return -1; + } + + /* Load the SHA1 algorithm */ + if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_SHA1_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 || + hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha1_object_size, sizeof(DWORD), &size_len, 0) < 0) { + git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); + goto on_error; + } + + /* Load the SHA256 algorithm */ + if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_SHA256_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 || + hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha256_object_size, sizeof(DWORD), &size_len, 0) < 0) { + git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); + goto on_error; + } + + hash_provider.type = GIT_HASH_WIN32_CNG; + return 0; + +on_error: + if (hash_provider.provider.cng.sha1_handle) + hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0); + + if (hash_provider.provider.cng.sha256_handle) + hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0); + + if (hash_provider.provider.cng.dll) + FreeLibrary(hash_provider.provider.cng.dll); + + return -1; +} + +GIT_INLINE(void) cng_provider_shutdown(void) +{ + if (hash_provider.type == GIT_HASH_WIN32_INVALID) + return; + + hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0); + hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0); + FreeLibrary(hash_provider.provider.cng.dll); + + hash_provider.type = GIT_HASH_WIN32_INVALID; +} + +/* Initialize CryptoAPI */ +GIT_INLINE(int) cryptoapi_provider_init(void) +{ + if (!CryptAcquireContext(&hash_provider.provider.cryptoapi.handle, NULL, 0, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { + git_error_set(GIT_ERROR_OS, "legacy hash context could not be started"); + return -1; + } + + hash_provider.type = GIT_HASH_WIN32_CRYPTOAPI; + return 0; +} + +GIT_INLINE(void) cryptoapi_provider_shutdown(void) +{ + if (hash_provider.type == GIT_HASH_WIN32_INVALID) + return; + + CryptReleaseContext(hash_provider.provider.cryptoapi.handle, 0); + + hash_provider.type = GIT_HASH_WIN32_INVALID; +} + +static void hash_provider_shutdown(void) +{ + if (hash_provider.type == GIT_HASH_WIN32_CNG) + cng_provider_shutdown(); + else if (hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI) + cryptoapi_provider_shutdown(); +} + +static int hash_provider_init(void) +{ + int error = 0; + + if (hash_provider.type != GIT_HASH_WIN32_INVALID) + return 0; + + if ((error = cng_provider_init()) < 0) + error = cryptoapi_provider_init(); + + if (!error) + error = git_runtime_shutdown_register(hash_provider_shutdown); + + return error; +} + +git_hash_win32_provider_t git_hash_win32_provider(void) +{ + return hash_provider.type; +} + +int git_hash_win32_set_provider(git_hash_win32_provider_t provider) +{ + if (provider == hash_provider.type) + return 0; + + hash_provider_shutdown(); + + if (provider == GIT_HASH_WIN32_CNG) + return cng_provider_init(); + else if (provider == GIT_HASH_WIN32_CRYPTOAPI) + return cryptoapi_provider_init(); + + git_error_set(GIT_ERROR_SHA, "unsupported win32 provider"); + return -1; +} + +/* CryptoAPI: available in Windows XP and newer */ + +GIT_INLINE(int) hash_cryptoapi_init(git_hash_win32_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + + if (!CryptCreateHash(hash_provider.provider.cryptoapi.handle, ctx->algorithm, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { + ctx->ctx.cryptoapi.valid = 0; + git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); + return -1; + } + + ctx->ctx.cryptoapi.valid = 1; + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_update(git_hash_win32_ctx *ctx, const void *_data, size_t len) +{ + const BYTE *data = (BYTE *)_data; + + GIT_ASSERT(ctx->ctx.cryptoapi.valid); + + while (len > 0) { + DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; + + if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { + git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } + + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_win32_ctx *ctx) +{ + DWORD len = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE; + int error = 0; + + GIT_ASSERT(ctx->ctx.cryptoapi.valid); + + if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { + git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); + error = -1; + } + + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + ctx->ctx.cryptoapi.valid = 0; + + return error; +} + +GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_win32_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); +} + +GIT_INLINE(int) hash_sha1_cryptoapi_ctx_init_init(git_hash_win32_ctx *ctx) +{ + ctx->algorithm = CALG_SHA1; + return hash_cryptoapi_init(ctx); +} + +GIT_INLINE(int) hash_sha256_cryptoapi_ctx_init(git_hash_win32_ctx *ctx) +{ + ctx->algorithm = CALG_SHA_256; + return hash_cryptoapi_init(ctx); +} + +/* CNG: Available in Windows Server 2008 and newer */ + +GIT_INLINE(int) hash_sha1_cng_ctx_init(git_hash_win32_ctx *ctx) +{ + if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha1_object_size)) == NULL) + return -1; + + if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha1_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha1_object_size, NULL, 0, 0) < 0) { + git__free(ctx->ctx.cng.hash_object); + + git_error_set(GIT_ERROR_OS, "sha1 implementation could not be created"); + return -1; + } + + ctx->algorithm = CALG_SHA1; + return 0; +} + +GIT_INLINE(int) hash_sha256_cng_ctx_init(git_hash_win32_ctx *ctx) +{ + if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha256_object_size)) == NULL) + return -1; + + if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha256_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha256_object_size, NULL, 0, 0) < 0) { + git__free(ctx->ctx.cng.hash_object); + + git_error_set(GIT_ERROR_OS, "sha256 implementation could not be created"); + return -1; + } + + ctx->algorithm = CALG_SHA_256; + return 0; +} + +GIT_INLINE(int) hash_cng_init(git_hash_win32_ctx *ctx) +{ + BYTE hash[GIT_HASH_SHA256_SIZE]; + ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE; + + if (!ctx->ctx.cng.updated) + return 0; + + /* CNG needs to be finished to restart */ + if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, size, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); + return -1; + } + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(int) hash_cng_update(git_hash_win32_ctx *ctx, const void *_data, size_t len) +{ + PBYTE data = (PBYTE)_data; + + while (len > 0) { + ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; + + if (hash_provider.provider.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } + + return 0; +} + +GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_win32_ctx *ctx) +{ + ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE; + + if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, out, size, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash could not be finished"); + return -1; + } + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_win32_ctx *ctx) +{ + hash_provider.provider.cng.destroy_hash(ctx->ctx.cng.hash_handle); + git__free(ctx->ctx.cng.hash_object); +} + +/* Indirection between CryptoAPI and CNG */ + +GIT_INLINE(int) hash_sha1_win32_ctx_init(git_hash_win32_ctx *ctx) +{ + GIT_ASSERT_ARG(hash_provider.type); + + memset(ctx, 0x0, sizeof(git_hash_win32_ctx)); + return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha1_cng_ctx_init(ctx) : hash_sha1_cryptoapi_ctx_init_init(ctx); +} + +GIT_INLINE(int) hash_sha256_win32_ctx_init(git_hash_win32_ctx *ctx) +{ + GIT_ASSERT_ARG(hash_provider.type); + + memset(ctx, 0x0, sizeof(git_hash_win32_ctx)); + return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha256_cng_ctx_init(ctx) : hash_sha256_cryptoapi_ctx_init(ctx); +} + +GIT_INLINE(int) hash_win32_init(git_hash_win32_ctx *ctx) +{ + return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); +} + +GIT_INLINE(int) hash_win32_update(git_hash_win32_ctx *ctx, const void *data, size_t len) +{ + return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); +} + +GIT_INLINE(int) hash_win32_final(unsigned char *out, git_hash_win32_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); +} + +GIT_INLINE(void) hash_win32_cleanup(git_hash_win32_ctx *ctx) +{ + if (hash_provider.type == GIT_HASH_WIN32_CNG) + hash_ctx_cng_cleanup(ctx); + else if(hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI) + hash_ctx_cryptoapi_cleanup(ctx); +} + +#ifdef GIT_SHA1_WIN32 + +int git_hash_sha1_global_init(void) +{ + return hash_provider_init(); +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return hash_sha1_win32_ctx_init(&ctx->win32); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return hash_win32_init(&ctx->win32); +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + return hash_win32_update(&ctx->win32, data, len); +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return hash_win32_final(out, &ctx->win32); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + if (!ctx) + return; + hash_win32_cleanup(&ctx->win32); +} + +#endif + +#ifdef GIT_SHA256_WIN32 + +int git_hash_sha256_global_init(void) +{ + return hash_provider_init(); +} + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return hash_sha256_win32_ctx_init(&ctx->win32); +} + +int git_hash_sha256_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return hash_win32_init(&ctx->win32); +} + +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + return hash_win32_update(&ctx->win32, data, len); +} + +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + return hash_win32_final(out, &ctx->win32); +} + +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx) +{ + if (!ctx) + return; + hash_win32_cleanup(&ctx->win32); +} + +#endif diff --git a/src/util/hash/win32.h b/src/util/hash/win32.h new file mode 100644 index 000000000..a9fb87aee --- /dev/null +++ b/src/util/hash/win32.h @@ -0,0 +1,60 @@ +/* + * 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_win32_h__ +#define INCLUDE_hash_win32_h__ + +#include "hash/sha.h" + +#include + +typedef enum { + GIT_HASH_WIN32_INVALID = 0, + GIT_HASH_WIN32_CRYPTOAPI, + GIT_HASH_WIN32_CNG +} git_hash_win32_provider_t; + +struct git_hash_win32_cryptoapi_ctx { + bool valid; + HCRYPTHASH hash_handle; +}; + +struct git_hash_win32_cng_ctx { + bool updated; + HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; + PBYTE hash_object; +}; + +typedef struct { + ALG_ID algorithm; + + union { + struct git_hash_win32_cryptoapi_ctx cryptoapi; + struct git_hash_win32_cng_ctx cng; + } ctx; +} git_hash_win32_ctx; + +/* + * Gets/sets the current hash provider (cng or cryptoapi). This is only + * for testing purposes. + */ +git_hash_win32_provider_t git_hash_win32_provider(void); +int git_hash_win32_set_provider(git_hash_win32_provider_t provider); + +#ifdef GIT_SHA1_WIN32 +struct git_hash_sha1_ctx { + git_hash_win32_ctx win32; +}; +#endif + +#ifdef GIT_SHA256_WIN32 +struct git_hash_sha256_ctx { + git_hash_win32_ctx win32; +}; +#endif + +#endif diff --git a/src/util/integer.h b/src/util/integer.h new file mode 100644 index 000000000..63277177b --- /dev/null +++ b/src/util/integer.h @@ -0,0 +1,218 @@ +/* + * 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_integer_h__ +#define INCLUDE_integer_h__ + +/** @return true if p fits into the range of a size_t */ +GIT_INLINE(int) git__is_sizet(int64_t p) +{ + size_t r = (size_t)p; + return p == (int64_t)r; +} + +/** @return true if p fits into the range of an ssize_t */ +GIT_INLINE(int) git__is_ssizet(size_t p) +{ + ssize_t r = (ssize_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) +{ + uint32_t r = (uint32_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of an unsigned long */ +GIT_INLINE(int) git__is_ulong(int64_t p) +{ + unsigned long r = (unsigned long)p; + return p == (int64_t)r; +} + +/** @return true if p fits into the range of an int */ +GIT_INLINE(int) git__is_int(int64_t p) +{ + int r = (int)p; + return p == (int64_t)r; +} + +/* Use clang/gcc compiler intrinsics whenever possible */ +#if (__has_builtin(__builtin_add_overflow) || \ + (defined(__GNUC__) && (__GNUC__ >= 5))) + +# if (SIZE_MAX == UINT_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uadd_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umul_overflow(one, two, out) +# elif (SIZE_MAX == ULONG_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uaddl_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umull_overflow(one, two, out) +# elif (SIZE_MAX == ULLONG_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uaddll_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umulll_overflow(one, two, out) +# else +# 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) + +# define git__add_int64_overflow(out, one, two) \ + __builtin_add_overflow(one, two, out) + +/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */ +# if !defined(__clang__) || !defined(GIT_ARCH_32) +# define git__multiply_int64_overflow(out, one, two) \ + __builtin_mul_overflow(one, two, out) +# endif + +/* 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) + +#define git__add_int64_overflow(out, one, two) \ + (LongLongAdd(one, two, out) != S_OK) +#define git__multiply_int64_overflow(out, one, two) \ + (LongLongMult(one, two, out) != S_OK) + +#else + +/** + * Sets `one + two` into `out`, unless the arithmetic would overflow. + * @return false if the result fits in a `size_t`, true on overflow. + */ +GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two) +{ + if (SIZE_MAX - one < two) + return true; + *out = one + two; + return false; +} + +/** + * Sets `one * two` into `out`, unless the arithmetic would overflow. + * @return false if the result fits in a `size_t`, true on overflow. + */ +GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two) +{ + if (one && SIZE_MAX / one < two) + return true; + *out = one * two; + 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; +} + +GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two) +{ + if ((two > 0 && one > (INT64_MAX - two)) || + (two < 0 && one < (INT64_MIN - two))) + return true; + *out = one + two; + return false; +} + +#endif + +/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */ +#if !defined(git__multiply_int64_overflow) +GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two) +{ + /* + * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`, + * without incurring in undefined behavior. That is done by performing the + * comparison with a division instead of a multiplication, which translates + * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats: + * + * - The comparison sign is inverted when both sides of the inequality are + * multiplied/divided by a negative number, so if `one < 0` the comparison + * needs to be flipped. + * - `INT64_MAX / -1` itself overflows (or traps), so that case should be + * avoided. + * - Since the overflow flag is defined as the discrepance between the result + * of performing the multiplication in a signed integer at twice the width + * of the operands, and the truncated+sign-extended version of that same + * result, there are four cases where the result is the opposite of what + * would be expected: + * * `INT64_MIN * -1` / `-1 * INT64_MIN` + * * `INT64_MIN * 1 / `1 * INT64_MIN` + */ + if (one && two) { + if (one > 0 && two > 0) { + if (INT64_MAX / one < two) + return true; + } else if (one < 0 && two < 0) { + if ((one == -1 && two == INT64_MIN) || + (two == -1 && one == INT64_MIN)) { + *out = INT64_MIN; + return false; + } + if (INT64_MAX / one > two) + return true; + } else if (one > 0 && two < 0) { + if ((one == 1 && two == INT64_MIN) || + (INT64_MIN / one > two)) + return true; + } else if (one == -1) { + if (INT64_MIN / two > one) + return true; + } else { + if ((one == INT64_MIN && two == 1) || + (INT64_MIN / one < two)) + return true; + } + } + *out = one * two; + return false; +} +#endif + +#endif diff --git a/src/util/khash.h b/src/util/khash.h new file mode 100644 index 000000000..c9b7f131f --- /dev/null +++ b/src/util/khash.h @@ -0,0 +1,615 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE 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. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +typedef uint32_t khint32_t; +typedef uint64_t khint64_t; + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#elif defined(__GNUC__) +#define kh_inline __inline__ +#else +#define kh_inline +#endif +#endif /* kh_inline */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kreallocarray +#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More convenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/src/util/map.h b/src/util/map.h new file mode 100644 index 000000000..c101e46f6 --- /dev/null +++ b/src/util/map.h @@ -0,0 +1,46 @@ +/* + * 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_map_h__ +#define INCLUDE_map_h__ + +#include "git2_util.h" + + +/* p_mmap() prot values */ +#define GIT_PROT_NONE 0x0 +#define GIT_PROT_READ 0x1 +#define GIT_PROT_WRITE 0x2 +#define GIT_PROT_EXEC 0x4 + +/* git__mmmap() flags values */ +#define GIT_MAP_FILE 0 +#define GIT_MAP_SHARED 1 +#define GIT_MAP_PRIVATE 2 +#define GIT_MAP_TYPE 0xf +#define GIT_MAP_FIXED 0x10 + +#ifdef __amigaos4__ +#define MAP_FAILED 0 +#endif + +typedef struct { /* memory mapped buffer */ + void *data; /* data bytes */ + size_t len; /* data length */ +#ifdef GIT_WIN32 + HANDLE fmh; /* file mapping handle */ +#endif +} git_map; + +#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ + GIT_ASSERT(out != NULL && len > 0); \ + GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ + GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0) + +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/util/net.c b/src/util/net.c new file mode 100644 index 000000000..b2236daf8 --- /dev/null +++ b/src/util/net.c @@ -0,0 +1,749 @@ +/* + * 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 + +#include "posix.h" +#include "str.h" +#include "http_parser.h" +#include "runtime.h" + +#define DEFAULT_PORT_HTTP "80" +#define DEFAULT_PORT_HTTPS "443" +#define DEFAULT_PORT_GIT "9418" +#define DEFAULT_PORT_SSH "22" + +bool git_net_str_is_url(const char *str) +{ + const char *c; + + for (c = str; *c; c++) { + if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') + return true; + + if ((*c < 'a' || *c > 'z') && + (*c < 'A' || *c > 'Z') && + (*c < '0' || *c > '9') && + (*c != '+' && *c != '-' && *c != '.')) + break; + } + + return false; +} + +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 || + strcmp(scheme, "ssh+git") == 0 || + strcmp(scheme, "git+ssh") == 0) + return DEFAULT_PORT_SSH; + + return NULL; +} + +int git_net_url_dup(git_net_url *out, git_net_url *in) +{ + if (in->scheme) { + out->scheme = git__strdup(in->scheme); + GIT_ERROR_CHECK_ALLOC(out->scheme); + } + + if (in->host) { + out->host = git__strdup(in->host); + GIT_ERROR_CHECK_ALLOC(out->host); + } + + if (in->port) { + out->port = git__strdup(in->port); + GIT_ERROR_CHECK_ALLOC(out->port); + } + + if (in->path) { + out->path = git__strdup(in->path); + GIT_ERROR_CHECK_ALLOC(out->path); + } + + if (in->query) { + out->query = git__strdup(in->query); + GIT_ERROR_CHECK_ALLOC(out->query); + } + + if (in->username) { + out->username = git__strdup(in->username); + GIT_ERROR_CHECK_ALLOC(out->username); + } + + if (in->password) { + out->password = git__strdup(in->password); + GIT_ERROR_CHECK_ALLOC(out->password); + } + + return 0; +} + +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_str scheme = GIT_STR_INIT, + host = GIT_STR_INIT, + port = GIT_STR_INIT, + path = GIT_STR_INIT, + username = GIT_STR_INIT, + password = GIT_STR_INIT, + query = GIT_STR_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_str_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_str_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_str_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_str_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_str_put(&path, url_path, url_path_len); + } else { + git_str_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_str_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_str_decode_percent(&username, url_username, url_username_len); + git_str_decode_percent(&password, url_password, url_password_len); + } else { + git_str_decode_percent(&username, url_userinfo, url_userinfo_len); + } + } + + if (git_str_oom(&scheme) || + git_str_oom(&host) || + git_str_oom(&port) || + git_str_oom(&path) || + git_str_oom(&query) || + git_str_oom(&username) || + git_str_oom(&password)) + return -1; + + url->scheme = git_str_detach(&scheme); + url->host = git_str_detach(&host); + url->port = git_str_detach(&port); + url->path = git_str_detach(&path); + url->query = git_str_detach(&query); + url->username = git_str_detach(&username); + url->password = git_str_detach(&password); + + error = 0; + +done: + git_str_dispose(&scheme); + git_str_dispose(&host); + git_str_dispose(&port); + git_str_dispose(&path); + git_str_dispose(&query); + git_str_dispose(&username); + git_str_dispose(&password); + return error; +} + +static int scp_invalid(const char *message) +{ + git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); + return GIT_EINVALIDSPEC; +} + +static bool is_ipv6(const char *str) +{ + const char *c; + size_t colons = 0; + + if (*str++ != '[') + return false; + + for (c = str; *c; c++) { + if (*c == ':') + colons++; + + if (*c == ']') + return (colons > 1); + + if (*c != ':' && + (*c < '0' || *c > '9') && + (*c < 'a' || *c > 'f') && + (*c < 'A' || *c > 'F')) + return false; + } + + return false; +} + +static bool has_at(const char *str) +{ + const char *c; + + for (c = str; *c; c++) { + if (*c == '@') + return true; + + if (*c == ':') + break; + } + + return false; +} + +int git_net_url_parse_scp(git_net_url *url, const char *given) +{ + const char *default_port = default_port_for_scheme("ssh"); + const char *c, *user, *host, *port, *path = NULL; + size_t user_len = 0, host_len = 0, port_len = 0; + unsigned short bracket = 0; + + enum { + NONE, + USER, + HOST_START, HOST, HOST_END, + IPV6, IPV6_END, + PORT_START, PORT, PORT_END, + PATH_START + } state = NONE; + + memset(url, 0, sizeof(git_net_url)); + + for (c = given; *c && !path; c++) { + switch (state) { + case NONE: + switch (*c) { + case '@': + return scp_invalid("unexpected '@'"); + case ':': + return scp_invalid("unexpected ':'"); + case '[': + if (is_ipv6(c)) { + state = IPV6; + host = c; + } else if (bracket++ > 1) { + return scp_invalid("unexpected '['"); + } + break; + default: + if (has_at(c)) { + state = USER; + user = c; + } else { + state = HOST; + host = c; + } + break; + } + break; + + case USER: + if (*c == '@') { + user_len = (c - user); + state = HOST_START; + } + break; + + case HOST_START: + state = (*c == '[') ? IPV6 : HOST; + host = c; + break; + + case HOST: + if (*c == ':') { + host_len = (c - host); + state = bracket ? PORT_START : PATH_START; + } else if (*c == ']') { + if (bracket-- == 0) + return scp_invalid("unexpected ']'"); + + host_len = (c - host); + state = HOST_END; + } + break; + + case HOST_END: + if (*c != ':') + return scp_invalid("unexpected character after hostname"); + state = PATH_START; + break; + + case IPV6: + if (*c == ']') + state = IPV6_END; + break; + + case IPV6_END: + if (*c != ':') + return scp_invalid("unexpected character after ipv6 address"); + + host_len = (c - host); + state = bracket ? PORT_START : PATH_START; + break; + + case PORT_START: + port = c; + state = PORT; + break; + + case PORT: + if (*c == ']') { + if (bracket-- == 0) + return scp_invalid("unexpected ']'"); + + port_len = c - port; + state = PORT_END; + } + break; + + case PORT_END: + if (*c != ':') + return scp_invalid("unexpected character after ipv6 address"); + + state = PATH_START; + break; + + case PATH_START: + path = c; + break; + + default: + GIT_ASSERT("unhandled state"); + } + } + + if (!path) + return scp_invalid("path is required"); + + GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh")); + + if (user_len) + GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len)); + + GIT_ASSERT(host_len); + GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); + + if (port_len) + GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); + else + GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); + + GIT_ASSERT(path); + GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); + + return 0; +} + +int git_net_url_joinpath( + git_net_url *out, + git_net_url *one, + const char *two) +{ + git_str path = GIT_STR_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_str_put(&path, one->path, one_len); + git_str_putc(&path, '/'); + git_str_put(&path, two, two_len); + + if (git_str_oom(&path)) + return -1; + + out->path = git_str_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, + bool allow_offsite, + const char *service_suffix) +{ + git_net_url tmp = GIT_NET_URL_INIT; + int error = 0; + + GIT_ASSERT(url); + GIT_ASSERT(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 && + !allow_offsite && + 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); +} + +bool git_net_url_is_default_port(git_net_url *url) +{ + const char *default_port; + + if ((default_port = default_port_for_scheme(url->scheme)) != NULL) + return (strcmp(url->port, default_port) == 0); + else + return false; +} + +bool git_net_url_is_ipv6(git_net_url *url) +{ + return (strchr(url->host, ':') != NULL); +} + +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_str *buf, git_net_url *url) +{ + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(url->scheme); + GIT_ASSERT_ARG(url->host); + + git_str_puts(buf, url->scheme); + git_str_puts(buf, "://"); + + if (url->username) { + git_str_puts(buf, url->username); + + if (url->password) { + git_str_puts(buf, ":"); + git_str_puts(buf, url->password); + } + + git_str_putc(buf, '@'); + } + + git_str_puts(buf, url->host); + + if (url->port && !git_net_url_is_default_port(url)) { + git_str_putc(buf, ':'); + git_str_puts(buf, url->port); + } + + git_str_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_str_putc(buf, '?'); + git_str_puts(buf, url->query); + } + + return git_str_oom(buf) ? -1 : 0; +} + +int git_net_url_fmt_path(git_str *buf, git_net_url *url) +{ + git_str_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_str_putc(buf, '?'); + git_str_puts(buf, url->query); + } + + return git_str_oom(buf) ? -1 : 0; +} + +static bool matches_pattern( + git_net_url *url, + const char *pattern, + size_t pattern_len) +{ + const char *domain, *port = NULL, *colon; + size_t host_len, domain_len, port_len = 0, wildcard = 0; + + GIT_UNUSED(url); + GIT_UNUSED(pattern); + + if (!pattern_len) + return false; + else if (pattern_len == 1 && pattern[0] == '*') + return true; + else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') + wildcard = 2; + else if (pattern[0] == '.') + wildcard = 1; + + domain = pattern + wildcard; + domain_len = pattern_len - wildcard; + + if ((colon = memchr(domain, ':', domain_len)) != NULL) { + domain_len = colon - domain; + port = colon + 1; + port_len = pattern_len - wildcard - domain_len - 1; + } + + /* A pattern's port *must* match if it's specified */ + if (port_len && git__strlcmp(url->port, port, port_len) != 0) + return false; + + /* No wildcard? Host must match exactly. */ + if (!wildcard) + return !git__strlcmp(url->host, domain, domain_len); + + /* Wildcard: ensure there's (at least) a suffix match */ + if ((host_len = strlen(url->host)) < domain_len || + memcmp(url->host + (host_len - domain_len), domain, domain_len)) + return false; + + /* The pattern is *.domain and the host is simply domain */ + if (host_len == domain_len) + return true; + + /* The pattern is *.domain and the host is foo.domain */ + return (url->host[host_len - domain_len - 1] == '.'); +} + +bool git_net_url_matches_pattern(git_net_url *url, const char *pattern) +{ + return matches_pattern(url, pattern, strlen(pattern)); +} + +bool git_net_url_matches_pattern_list( + git_net_url *url, + const char *pattern_list) +{ + const char *pattern, *pattern_end, *sep; + + for (pattern = pattern_list; + pattern && *pattern; + pattern = sep ? sep + 1 : NULL) { + sep = strchr(pattern, ','); + pattern_end = sep ? sep : strchr(pattern, '\0'); + + if (matches_pattern(url, pattern, (pattern_end - pattern))) + return true; + } + + return false; +} + +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/util/net.h b/src/util/net.h new file mode 100644 index 000000000..88030a952 --- /dev/null +++ b/src/util/net.h @@ -0,0 +1,78 @@ +/* + * 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 "git2_util.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 } + +/** Is a given string a url? */ +extern bool git_net_str_is_url(const char *str); + +/** Duplicate a URL */ +extern int git_net_url_dup(git_net_url *out, git_net_url *in); + +/** Parses a string containing a URL into a structure. */ +extern int git_net_url_parse(git_net_url *url, const char *str); + +/** Parses a string containing an SCP style path into a URL structure. */ +extern int git_net_url_parse_scp(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 true if the URL is on the default port. */ +extern bool git_net_url_is_default_port(git_net_url *url); + +/** Returns true if the host portion of the URL is an ipv6 address. */ +extern bool git_net_url_is_ipv6(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, + bool allow_offsite, + 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_str *out, git_net_url *url); + +/** Place the path and query string into the given buffer. */ +extern int git_net_url_fmt_path(git_str *buf, git_net_url *url); + +/** Determines if the url matches given pattern or pattern list */ +extern bool git_net_url_matches_pattern( + git_net_url *url, + const char *pattern); +extern bool git_net_url_matches_pattern_list( + git_net_url *url, + const char *pattern_list); + +/** Disposes the contents of the structure. */ +extern void git_net_url_dispose(git_net_url *url); + +#endif diff --git a/src/util/pool.c b/src/util/pool.c new file mode 100644 index 000000000..16ffa398d --- /dev/null +++ b/src/util/pool.c @@ -0,0 +1,260 @@ +/* + * 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 "pool.h" + +#include "posix.h" +#ifndef GIT_WIN32 +#include +#endif + +struct git_pool_page { + git_pool_page *next; + size_t size; + size_t avail; + GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); +}; + +static void *pool_alloc_page(git_pool *pool, size_t size); + +#ifndef GIT_DEBUG_POOL + +static size_t system_page_size = 0; + +int git_pool_global_init(void) +{ + if (git__page_size(&system_page_size) < 0) + system_page_size = 4096; + /* allow space for malloc overhead */ + system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page); + return 0; +} + +int git_pool_init(git_pool *pool, size_t item_size) +{ + GIT_ASSERT_ARG(pool); + GIT_ASSERT_ARG(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = system_page_size; + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_pool_page *scan, *next; + + for (scan = pool->pages; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + + pool->pages = NULL; +} + +static void *pool_alloc_page(git_pool *pool, size_t size) +{ + git_pool_page *page; + 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)) || + !(page = git__malloc(alloc_size))) + return NULL; + + page->size = new_page_size; + page->avail = new_page_size - size; + page->next = pool->pages; + + pool->pages = page; + + return page->data; +} + +static void *pool_alloc(git_pool *pool, size_t size) +{ + git_pool_page *page = pool->pages; + void *ptr = NULL; + + if (!page || page->avail < size) + return pool_alloc_page(pool, size); + + ptr = &page->data[page->size - page->avail]; + page->avail -= size; + + return ptr; +} + +uint32_t git_pool__open_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; + return ct; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + return false; +} + +#else + +int git_pool_global_init(void) +{ + return 0; +} + +static int git_pool__ptr_cmp(const void * a, const void * b) +{ + if(a > b) { + return 1; + } + if(a < b) { + return -1; + } + else { + return 0; + } +} + +int git_pool_init(git_pool *pool, size_t item_size) +{ + GIT_ASSERT_ARG(pool); + GIT_ASSERT_ARG(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = git_pool__system_page_size(); + git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_vector_free_deep(&pool->allocations); +} + +static void *pool_alloc(git_pool *pool, size_t size) { + void *ptr = NULL; + if((ptr = git__malloc(size)) == NULL) { + return NULL; + } + git_vector_insert_sorted(&pool->allocations, ptr, NULL); + return ptr; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + size_t pos; + return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; +} +#endif + +void git_pool_swap(git_pool *a, git_pool *b) +{ + git_pool temp; + + if (a == b) + return; + + memcpy(&temp, a, sizeof(temp)); + memcpy(a, b, sizeof(temp)); + memcpy(b, &temp, sizeof(temp)); +} + +static size_t alloc_size(git_pool *pool, size_t count) +{ + const size_t align = sizeof(void *) - 1; + + if (pool->item_size > 1) { + 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, size_t items) +{ + return pool_alloc(pool, alloc_size(pool, items)); +} + +void *git_pool_mallocz(git_pool *pool, size_t items) +{ + const size_t size = alloc_size(pool, items); + void *ptr = pool_alloc(pool, size); + if (ptr) + memset(ptr, 0x0, size); + return ptr; +} + +char *git_pool_strndup(git_pool *pool, const char *str, size_t n) +{ + char *ptr = NULL; + + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + if (n == SIZE_MAX) + return NULL; + + if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { + memcpy(ptr, str, n); + ptr[n] = '\0'; + } + + return ptr; +} + +char *git_pool_strdup(git_pool *pool, const char *str) +{ + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + return git_pool_strndup(pool, str, strlen(str)); +} + +char *git_pool_strdup_safe(git_pool *pool, const char *str) +{ + return str ? git_pool_strdup(pool, str) : NULL; +} + +char *git_pool_strcat(git_pool *pool, const char *a, const char *b) +{ + void *ptr; + size_t len_a, len_b, total; + + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + len_a = a ? strlen(a) : 0; + len_b = b ? strlen(b) : 0; + + 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) + memcpy(((char *)ptr) + len_a, b, len_b); + *(((char *)ptr) + len_a + len_b) = '\0'; + } + return ptr; +} diff --git a/src/util/pool.h b/src/util/pool.h new file mode 100644 index 000000000..0238431b0 --- /dev/null +++ b/src/util/pool.h @@ -0,0 +1,146 @@ +/* + * 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_pool_h__ +#define INCLUDE_pool_h__ + +#include "git2_util.h" + +#include "vector.h" + +typedef struct git_pool_page git_pool_page; + +#ifndef GIT_DEBUG_POOL +/** + * Chunked allocator. + * + * A `git_pool` can be used when you want to cheaply allocate + * multiple items of the same type and are willing to free them + * all together with a single call. The two most common cases + * are a set of fixed size items (such as lots of OIDs) or a + * bunch of strings. + * + * Internally, a `git_pool` allocates pages of memory and then + * deals out blocks from the trailing unused portion of each page. + * The pages guarantee that the number of actual allocations done + * will be much smaller than the number of items needed. + * + * For examples of how to set up a `git_pool` see `git_pool_init`. + */ +typedef struct { + git_pool_page *pages; /* allocated pages */ + 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 } + +#else + +/** + * Debug chunked allocator. + * + * Acts just like `git_pool` but instead of actually pooling allocations it + * passes them through to `git__malloc`. This makes it possible to easily debug + * systems that use `git_pool` using valgrind. + * + * In order to track allocations during the lifetime of the pool we use a + * `git_vector`. When the pool is deallocated everything in the vector is + * freed. + * + * `API is exactly the same as the standard `git_pool` with one exception. + * Since we aren't allocating pages to hand out in chunks we can't easily + * implement `git_pool__open_pages`. + */ +typedef struct { + git_vector allocations; + size_t item_size; + size_t page_size; +} git_pool; + +#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } + +#endif + +/** + * Initialize a pool. + * + * To allocation strings, use like this: + * + * git_pool_init(&string_pool, 1); + * my_string = git_pool_strdup(&string_pool, your_string); + * + * To allocate items of fixed size, use like this: + * + * git_pool_init(&pool, sizeof(item)); + * my_item = git_pool_malloc(&pool, 1); + * + * Of course, you can use this in other ways, but those are the + * two most common patterns. + */ +extern int git_pool_init(git_pool *pool, size_t item_size); + +/** + * Free all items in pool + */ +extern void git_pool_clear(git_pool *pool); + +/** + * Swap two pools with one another + */ +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, size_t items); +extern void *git_pool_mallocz(git_pool *pool, size_t items); + +/** + * Allocate space and duplicate string data into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); + +/** + * Allocate space and duplicate a string into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup(git_pool *pool, const char *str); + +/** + * Allocate space and duplicate a string into it, NULL is no error. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup_safe(git_pool *pool, const char *str); + +/** + * Allocate space for the concatenation of two strings. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); + +/* + * Misc utilities + */ +#ifndef GIT_DEBUG_POOL +extern uint32_t git_pool__open_pages(git_pool *pool); +#endif +extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); + +/** + * This function is being called by our global setup routines to + * initialize the system pool size. + * + * @return 0 on success, <0 on failure + */ +extern int git_pool_global_init(void); + +#endif diff --git a/src/util/posix.c b/src/util/posix.c new file mode 100644 index 000000000..b1f85dc94 --- /dev/null +++ b/src/util/posix.c @@ -0,0 +1,303 @@ +/* + * 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 "posix.h" + +#include "fs_path.h" +#include +#include + +size_t p_fsync__cnt = 0; + +#ifndef GIT_WIN32 + +#ifdef NO_ADDRINFO + +int p_getaddrinfo( + const char *host, + const char *port, + struct addrinfo *hints, + struct addrinfo **info) +{ + struct addrinfo *ainfo, *ai; + int p = 0; + + GIT_UNUSED(hints); + + if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) + return -1; + + if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { + git__free(ainfo); + return -2; + } + + ainfo->ai_servent = getservbyname(port, 0); + + if (ainfo->ai_servent) + ainfo->ai_port = ainfo->ai_servent->s_port; + else + ainfo->ai_port = htons(atol(port)); + + memcpy(&ainfo->ai_addr_in.sin_addr, + ainfo->ai_hostent->h_addr_list[0], + ainfo->ai_hostent->h_length); + + ainfo->ai_protocol = 0; + ainfo->ai_socktype = hints->ai_socktype; + ainfo->ai_family = ainfo->ai_hostent->h_addrtype; + ainfo->ai_addr_in.sin_family = ainfo->ai_family; + ainfo->ai_addr_in.sin_port = ainfo->ai_port; + ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in; + ainfo->ai_addrlen = sizeof(struct sockaddr_in); + + *info = ainfo; + + if (ainfo->ai_hostent->h_addr_list[1] == NULL) { + ainfo->ai_next = NULL; + return 0; + } + + ai = ainfo; + + for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { + if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { + p_freeaddrinfo(ainfo); + return -1; + } + memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); + memcpy(&ai->ai_next->ai_addr_in.sin_addr, + ainfo->ai_hostent->h_addr_list[p], + ainfo->ai_hostent->h_length); + ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in; + ai = ai->ai_next; + } + + ai->ai_next = NULL; + return 0; +} + +void p_freeaddrinfo(struct addrinfo *info) +{ + struct addrinfo *p, *next; + + p = info; + + while(p != NULL) { + next = p->ai_next; + git__free(p); + p = next; + } +} + +const char *p_gai_strerror(int ret) +{ + switch(ret) { + case -1: return "Out of memory"; break; + case -2: return "Address lookup failed"; break; + default: return "Unknown error"; break; + } +} + +#endif /* NO_ADDRINFO */ + +int p_open(const char *path, volatile int flags, ...) +{ + mode_t mode = 0; + + #ifdef GIT_DEBUG_STRICT_OPEN + if (strstr(path, "//") != NULL) { + errno = EACCES; + return -1; + } + #endif + + if (flags & O_CREAT) { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + return open(path, flags | O_BINARY | O_CLOEXEC, mode); +} + +int p_creat(const char *path, mode_t mode) +{ + return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); +} + +int p_getcwd(char *buffer_out, size_t size) +{ + char *cwd_buffer; + + GIT_ASSERT_ARG(buffer_out); + GIT_ASSERT_ARG(size > 0); + + cwd_buffer = getcwd(buffer_out, size); + + if (cwd_buffer == NULL) + return -1; + + git_fs_path_mkposix(buffer_out); + git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */ + + return 0; +} + +int p_rename(const char *from, const char *to) +{ + if (!link(from, to)) { + p_unlink(from); + return 0; + } + + if (!rename(from, to)) + return 0; + + return -1; +} + +#endif /* GIT_WIN32 */ + +ssize_t p_read(git_file fd, void *buf, size_t cnt) +{ + char *b = buf; + + if (!git__is_ssizet(cnt)) { +#ifdef GIT_WIN32 + SetLastError(ERROR_INVALID_PARAMETER); +#endif + errno = EINVAL; + return -1; + } + + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt); +#else + r = read(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!r) + break; + cnt -= r; + b += r; + } + return (b - (char *)buf); +} + +int p_write(git_file fd, const void *buf, size_t cnt) +{ + const char *b = buf; + + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + GIT_ASSERT((size_t)((unsigned int)cnt) == cnt); + r = write(fd, b, (unsigned int)cnt); +#else + r = write(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || GIT_ISBLOCKED(errno)) + continue; + return -1; + } + if (!r) { + errno = EPIPE; + return -1; + } + cnt -= r; + b += r; + } + return 0; +} + +#ifdef NO_MMAP + +#include "map.h" + +int git__page_size(size_t *page_size) +{ + /* dummy; here we don't need any alignment anyway */ + *page_size = 4096; + return 0; +} + +int git__mmap_alignment(size_t *alignment) +{ + /* dummy; here we don't need any alignment anyway */ + *alignment = 4096; + return 0; +} + + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + const char *ptr; + size_t remaining_len; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + /* writes cannot be emulated without handling pagefaults since write happens by + * writing to mapped memory */ + if (prot & GIT_PROT_WRITE) { + git_error_set(GIT_ERROR_OS, "trying to map %s-writeable", + ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private"); + return -1; + } + + if (!git__is_ssizet(len)) { + errno = EINVAL; + return -1; + } + + out->len = 0; + out->data = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(out->data); + + remaining_len = len; + ptr = (const char *)out->data; + while (remaining_len > 0) { + ssize_t nb; + HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset)); + if (nb <= 0) { + git_error_set(GIT_ERROR_OS, "mmap emulation failed"); + git__free(out->data); + out->data = NULL; + return -1; + } + + ptr += nb; + offset += nb; + remaining_len -= nb; + } + + out->len = len; + return 0; +} + +int p_munmap(git_map *map) +{ + GIT_ASSERT_ARG(map); + git__free(map->data); + + /* Initializing will help debug use-after-free */ + map->len = 0; + map->data = NULL; + + return 0; +} + +#endif diff --git a/src/util/posix.h b/src/util/posix.h new file mode 100644 index 000000000..c8f8cd9d2 --- /dev/null +++ b/src/util/posix.h @@ -0,0 +1,196 @@ +/* + * 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_posix_h__ +#define INCLUDE_posix_h__ + +#include "git2_util.h" + +#include +#include +#include + +/* stat: file mode type testing macros */ +#ifndef S_IFGITLINK +#define S_IFGITLINK 0160000 +#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) +#endif + +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#undef _S_IFLNK +#define _S_IFLNK S_IFLNK +#endif + +#ifndef S_IWUSR +#define S_IWUSR 00200 +#endif + +#ifndef S_IXUSR +#define S_IXUSR 00100 +#endif + +#ifndef S_ISLNK +#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#ifndef S_ISFIFO +#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) +#endif + +/* if S_ISGID is not defined, then don't try to set it */ +#ifndef S_ISGID +#define S_ISGID 0 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif + +/* access() mode parameter #defines */ +#ifndef F_OK +#define F_OK 0 /* existence check */ +#endif +#ifndef W_OK +#define W_OK 2 /* write mode check */ +#endif +#ifndef R_OK +#define R_OK 4 /* read mode check */ +#endif + +/* Determine whether an errno value indicates that a read or write failed + * because the descriptor is blocked. + */ +#if defined(EWOULDBLOCK) +#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) +#else +#define GIT_ISBLOCKED(e) ((e) == EAGAIN) +#endif + +/* define some standard errnos that the runtime may be missing. for example, + * mingw lacks EAFNOSUPPORT. */ +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT (INT_MAX-1) +#endif + +/* Compiler independent macro to handle signal interrpted system calls */ +#define HANDLE_EINTR(result, x) do { \ + result = (x); \ + } while (result == -1 && errno == EINTR); + + +/* 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; + +/** + * Standard POSIX Methods + * + * All the methods starting with the `p_` prefix are + * direct ports of the standard POSIX methods. + * + * Some of the methods are slightly wrapped to provide + * saner defaults. Some of these methods are emulated + * in Windows platforms. + * + * Use your manpages to check the docs on these. + */ + +extern ssize_t p_read(git_file fd, void *buf, size_t cnt); +extern int p_write(git_file fd, const void *buf, size_t cnt); + +extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset); +extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset); + +#define p_close(fd) close(fd) +#define p_umask(m) umask(m) + +extern int p_open(const char *path, int flags, ...); +extern int p_creat(const char *path, mode_t mode); +extern int p_getcwd(char *buffer_out, size_t size); +extern int p_rename(const char *from, const char *to); + +extern int git__page_size(size_t *page_size); +extern int git__mmap_alignment(size_t *page_size); + +/* The number of times `p_fsync` has been called. Note that this is for + * test code only; it it not necessarily thread-safe and should not be + * relied upon in production. + */ +extern size_t p_fsync__cnt; + +/** + * Platform-dependent methods + */ +#ifdef GIT_WIN32 +# include "win32/posix.h" +#else +# include "unix/posix.h" +#endif + +#include "strnlen.h" + +#ifdef NO_READDIR_R +GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) +{ + GIT_UNUSED(entry); + *result = readdir(dirp); + return 0; +} +#else /* NO_READDIR_R */ +# define p_readdir_r(d,e,r) readdir_r(d,e,r) +#endif + +#ifdef NO_ADDRINFO +# include +struct addrinfo { + struct hostent *ai_hostent; + struct servent *ai_servent; + struct sockaddr_in ai_addr_in; + struct sockaddr *ai_addr; + size_t ai_addrlen; + int ai_family; + int ai_socktype; + int ai_protocol; + long ai_port; + struct addrinfo *ai_next; +}; + +extern int p_getaddrinfo(const char *host, const char *port, + struct addrinfo *hints, struct addrinfo **info); +extern void p_freeaddrinfo(struct addrinfo *info); +extern const char *p_gai_strerror(int ret); +#else +# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d) +# define p_freeaddrinfo(a) freeaddrinfo(a) +# define p_gai_strerror(c) gai_strerror(c) +#endif /* NO_ADDRINFO */ + +#endif diff --git a/src/util/pqueue.c b/src/util/pqueue.c new file mode 100644 index 000000000..3820e999c --- /dev/null +++ b/src/util/pqueue.c @@ -0,0 +1,125 @@ +/* + * 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 "pqueue.h" + +#include "util.h" + +#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) +#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2) +#define PQUEUE_PARENT_OF(I) (((I)-1)>>1) + +int git_pqueue_init( + git_pqueue *pq, + uint32_t flags, + size_t init_size, + git_vector_cmp cmp) +{ + int error = git_vector_init(pq, init_size, cmp); + + if (!error) { + /* mix in our flags */ + pq->flags |= flags; + + /* if fixed size heap, pretend vector is exactly init_size elements */ + if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0) + pq->_alloc_size = init_size; + } + + return error; +} + +static void pqueue_up(git_pqueue *pq, size_t el) +{ + size_t parent_el = PQUEUE_PARENT_OF(el); + void *kid = git_vector_get(pq, el); + + while (el > 0) { + void *parent = pq->contents[parent_el]; + + if (pq->_cmp(parent, kid) <= 0) + break; + + pq->contents[el] = parent; + + el = parent_el; + parent_el = PQUEUE_PARENT_OF(el); + } + + pq->contents[el] = kid; +} + +static void pqueue_down(git_pqueue *pq, size_t el) +{ + void *parent = git_vector_get(pq, el), *kid, *rkid; + + while (1) { + size_t kid_el = PQUEUE_LCHILD_OF(el); + + if ((kid = git_vector_get(pq, kid_el)) == NULL) + break; + + if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL && + pq->_cmp(kid, rkid) > 0) { + kid = rkid; + kid_el += 1; + } + + if (pq->_cmp(parent, kid) <= 0) + break; + + pq->contents[el] = kid; + el = kid_el; + } + + pq->contents[el] = parent; +} + +int git_pqueue_insert(git_pqueue *pq, void *item) +{ + int error = 0; + + /* if heap is full, pop the top element if new one should replace it */ + if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && + pq->length >= pq->_alloc_size) + { + /* skip this item if below min item in heap or if + * we do not have a comparison function */ + if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) + return 0; + /* otherwise remove the min item before inserting new */ + (void)git_pqueue_pop(pq); + } + + if (!(error = git_vector_insert(pq, item)) && pq->_cmp) + pqueue_up(pq, pq->length - 1); + + return error; +} + +void *git_pqueue_pop(git_pqueue *pq) +{ + void *rval; + + if (!pq->_cmp) { + rval = git_vector_last(pq); + } else { + rval = git_pqueue_get(pq, 0); + } + + if (git_pqueue_size(pq) > 1 && pq->_cmp) { + /* move last item to top of heap, shrink, and push item down */ + pq->contents[0] = git_vector_last(pq); + git_vector_pop(pq); + pqueue_down(pq, 0); + } else { + /* all we need to do is shrink the heap in this case */ + git_vector_pop(pq); + } + + return rval; +} diff --git a/src/util/pqueue.h b/src/util/pqueue.h new file mode 100644 index 000000000..97232b4a9 --- /dev/null +++ b/src/util/pqueue.h @@ -0,0 +1,59 @@ +/* + * 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_pqueue_h__ +#define INCLUDE_pqueue_h__ + +#include "git2_util.h" + +#include "vector.h" + +typedef git_vector git_pqueue; + +enum { + /* flag meaning: don't grow heap, keep highest values only */ + GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1) +}; + +/** + * Initialize priority queue + * + * @param pq The priority queue struct to initialize + * @param flags Flags (see above) to control queue behavior + * @param init_size The initial queue size + * @param cmp The entry priority comparison function + * @return 0 on success, <0 on error + */ +extern int git_pqueue_init( + git_pqueue *pq, + uint32_t flags, + size_t init_size, + git_vector_cmp cmp); + +#define git_pqueue_free git_vector_free +#define git_pqueue_clear git_vector_clear +#define git_pqueue_size git_vector_length +#define git_pqueue_get git_vector_get +#define git_pqueue_reverse git_vector_reverse + +/** + * Insert a new item into the queue + * + * @param pq The priority queue + * @param item Pointer to the item data + * @return 0 on success, <0 on failure + */ +extern int git_pqueue_insert(git_pqueue *pq, void *item); + +/** + * Remove the top item in the priority queue + * + * @param pq The priority queue + * @return item from heap on success, NULL if queue is empty + */ +extern void *git_pqueue_pop(git_pqueue *pq); + +#endif diff --git a/src/util/rand.c b/src/util/rand.c new file mode 100644 index 000000000..d28e4aa97 --- /dev/null +++ b/src/util/rand.c @@ -0,0 +1,230 @@ +/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + +To the extent possible under law, the author has dedicated all copyright +and related and neighboring rights to this software to the public domain +worldwide. This software is distributed without any warranty. + +See . */ + +#include "git2_util.h" +#include "rand.h" +#include "runtime.h" + +#if defined(GIT_RAND_GETENTROPY) +# include +#endif + +static uint64_t state[4]; +static git_mutex state_lock; + +typedef union { + double f; + uint64_t d; +} bits; + +#if defined(GIT_WIN32) +GIT_INLINE(int) getseed(uint64_t *seed) +{ + HCRYPTPROV provider; + SYSTEMTIME systemtime; + FILETIME filetime, idletime, kerneltime, usertime; + bits convert; + + if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { + BOOL success = CryptGenRandom(provider, sizeof(uint64_t), (void *)seed); + CryptReleaseContext(provider, 0); + + if (success) + return 0; + } + + GetSystemTime(&systemtime); + if (!SystemTimeToFileTime(&systemtime, &filetime)) { + git_error_set(GIT_ERROR_OS, "could not get time for random seed"); + return -1; + } + + /* Fall-through: generate a seed from the time and system state */ + *seed = 0; + *seed |= ((uint64_t)filetime.dwLowDateTime << 32); + *seed |= ((uint64_t)filetime.dwHighDateTime); + + GetSystemTimes(&idletime, &kerneltime, &usertime); + + *seed ^= ((uint64_t)idletime.dwLowDateTime << 32); + *seed ^= ((uint64_t)kerneltime.dwLowDateTime); + *seed ^= ((uint64_t)usertime.dwLowDateTime << 32); + + *seed ^= ((uint64_t)idletime.dwHighDateTime); + *seed ^= ((uint64_t)kerneltime.dwHighDateTime << 12); + *seed ^= ((uint64_t)usertime.dwHighDateTime << 24); + + *seed ^= ((uint64_t)GetCurrentProcessId() << 32); + *seed ^= ((uint64_t)GetCurrentThreadId() << 48); + + convert.f = git__timer(); *seed ^= (convert.d); + + /* Mix in the addresses of some functions and variables */ + *seed ^= (((uint64_t)((uintptr_t)seed) << 32)); + *seed ^= (((uint64_t)((uintptr_t)&errno))); + + return 0; +} + +#else + +GIT_INLINE(int) getseed(uint64_t *seed) +{ + struct timeval tv; + double loadavg[3]; + bits convert; + int fd; + +# if defined(GIT_RAND_GETENTROPY) + GIT_UNUSED((fd = 0)); + + if (getentropy(seed, sizeof(uint64_t)) == 0) + return 0; +# else + /* + * Try to read from /dev/urandom; most modern systems will have + * this, but we may be chrooted, etc, so it's not a fatal error + */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) { + ssize_t ret = read(fd, seed, sizeof(uint64_t)); + close(fd); + + if (ret == (ssize_t)sizeof(uint64_t)) + return 0; + } +# endif + + /* Fall-through: generate a seed from the time and system state */ + if (gettimeofday(&tv, NULL) < 0) { + git_error_set(GIT_ERROR_OS, "could get time for random seed"); + return -1; + } + + *seed = 0; + *seed |= ((uint64_t)tv.tv_usec << 40); + *seed |= ((uint64_t)tv.tv_sec); + + *seed ^= ((uint64_t)getpid() << 48); + *seed ^= ((uint64_t)getppid() << 32); + *seed ^= ((uint64_t)getpgid(0) << 28); + *seed ^= ((uint64_t)getsid(0) << 16); + *seed ^= ((uint64_t)getuid() << 8); + *seed ^= ((uint64_t)getgid()); + +# if defined(GIT_RAND_GETLOADAVG) + getloadavg(loadavg, 3); + + convert.f = loadavg[0]; *seed ^= (convert.d >> 36); + convert.f = loadavg[1]; *seed ^= (convert.d); + convert.f = loadavg[2]; *seed ^= (convert.d >> 16); +# else + GIT_UNUSED(loadavg[0]); +# endif + + convert.f = git__timer(); *seed ^= (convert.d); + + /* Mix in the addresses of some variables */ + *seed ^= ((uint64_t)((size_t)((void *)seed)) << 32); + *seed ^= ((uint64_t)((size_t)((void *)&errno))); + + return 0; +} +#endif + +static void git_rand_global_shutdown(void) +{ + git_mutex_free(&state_lock); +} + +int git_rand_global_init(void) +{ + uint64_t seed = 0; + + if (git_mutex_init(&state_lock) < 0 || getseed(&seed) < 0) + return -1; + + if (!seed) { + git_error_set(GIT_ERROR_INTERNAL, "failed to generate random seed"); + return -1; + } + + git_rand_seed(seed); + git_runtime_shutdown_register(git_rand_global_shutdown); + + return 0; +} + +/* + * This is splitmix64. xoroshiro256** uses 256 bit seed; this is used + * to generate 256 bits of seed from the given 64, per the author's + * recommendation. + */ +GIT_INLINE(uint64_t) splitmix64(uint64_t *in) +{ + uint64_t z; + + *in += 0x9e3779b97f4a7c15; + + z = *in; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); +} + +void git_rand_seed(uint64_t seed) +{ + uint64_t mixer; + + mixer = seed; + + git_mutex_lock(&state_lock); + state[0] = splitmix64(&mixer); + state[1] = splitmix64(&mixer); + state[2] = splitmix64(&mixer); + state[3] = splitmix64(&mixer); + git_mutex_unlock(&state_lock); +} + +/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid + generators. It has excellent (sub-ns) speed, a state (256 bits) that is + large enough for any parallel application, and it passes all tests we + are aware of. + + For generating just floating-point numbers, xoshiro256+ is even faster. + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. */ + +GIT_INLINE(uint64_t) rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t git_rand_next(void) { + uint64_t t, result; + + git_mutex_lock(&state_lock); + + result = rotl(state[1] * 5, 7) * 9; + + t = state[1] << 17; + + state[2] ^= state[0]; + state[3] ^= state[1]; + state[1] ^= state[2]; + state[0] ^= state[3]; + + state[2] ^= t; + + state[3] = rotl(state[3], 45); + + git_mutex_unlock(&state_lock); + + return result; +} diff --git a/src/util/rand.h b/src/util/rand.h new file mode 100644 index 000000000..fa0619aa2 --- /dev/null +++ b/src/util/rand.h @@ -0,0 +1,37 @@ +/* + * 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_rand_h__ +#define INCLUDE_rand_h__ + +#include "git2_util.h" + +/** + * Initialize the random number generation subsystem. This will + * seed the random number generator with the system's entropy pool, + * if available, and will fall back to the current time and + * system information if not. + */ +int git_rand_global_init(void); + +/** + * Seed the pseudo-random number generator. This is not needed to be + * called; the PRNG is seeded by `git_rand_global_init`, but it may + * be useful for testing. When the same seed is specified, the same + * sequence of random numbers from `git_rand_next` is emitted. + * + * @param seed the seed to use + */ +void git_rand_seed(uint64_t seed); + +/** + * Get the next pseudo-random number in the sequence. + * + * @return a 64-bit pseudo-random number + */ +uint64_t git_rand_next(void); + +#endif diff --git a/src/util/regexp.c b/src/util/regexp.c new file mode 100644 index 000000000..2569dea0a --- /dev/null +++ b/src/util/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 = NULL; + + 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] = {0}, *ovec; + int error; + size_t i; + + /* The ovec array always needs to be a multiple 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/util/regexp.h b/src/util/regexp.h new file mode 100644 index 000000000..d0862b107 --- /dev/null +++ b/src/util/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 "git2_util.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/util/runtime.c b/src/util/runtime.c new file mode 100644 index 000000000..a7711ffc4 --- /dev/null +++ b/src/util/runtime.c @@ -0,0 +1,162 @@ +/* + * 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_util.h" +#include "runtime.h" + +static git_runtime_shutdown_fn shutdown_callback[32]; +static git_atomic32 shutdown_callback_count; + +static git_atomic32 init_count; + +static int init_common(git_runtime_init_fn init_fns[], size_t cnt) +{ + size_t i; + int ret; + + /* Initialize subsystems that have global state */ + for (i = 0; i < cnt; i++) { + if ((ret = init_fns[i]()) != 0) + break; + } + + GIT_MEMORY_BARRIER; + + return ret; +} + +static void shutdown_common(void) +{ + git_runtime_shutdown_fn cb; + int pos; + + for (pos = git_atomic32_get(&shutdown_callback_count); + pos > 0; + pos = git_atomic32_dec(&shutdown_callback_count)) { + cb = git_atomic_swap(shutdown_callback[pos - 1], NULL); + + if (cb != NULL) + cb(); + } +} + +int git_runtime_shutdown_register(git_runtime_shutdown_fn callback) +{ + int count = git_atomic32_inc(&shutdown_callback_count); + + if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) { + git_error_set(GIT_ERROR_INVALID, + "too many shutdown callbacks registered"); + git_atomic32_dec(&shutdown_callback_count); + return -1; + } + + shutdown_callback[count - 1] = callback; + + return 0; +} + +#if defined(GIT_THREADS) && defined(GIT_WIN32) + +/* + * On Win32, we use a spinlock to provide locking semantics. This is + * lighter-weight than a proper critical section. + */ +static volatile LONG init_spinlock = 0; + +GIT_INLINE(int) init_lock(void) +{ + while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); } + return 0; +} + +GIT_INLINE(int) init_unlock(void) +{ + InterlockedExchange(&init_spinlock, 0); + return 0; +} + +#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) + +/* + * On POSIX, we need to use a proper mutex for locking. We might prefer + * a spinlock here, too, but there's no static initializer for a + * pthread_spinlock_t. + */ +static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; + +GIT_INLINE(int) init_lock(void) +{ + return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1; +} + +GIT_INLINE(int) init_unlock(void) +{ + return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1; +} + +#elif defined(GIT_THREADS) +# error unknown threading model +#else + +# define init_lock() git__noop() +# define init_unlock() git__noop() + +#endif + +int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt) +{ + int ret; + + if (init_lock() < 0) + return -1; + + /* Only do work on a 0 -> 1 transition of the refcount */ + if ((ret = git_atomic32_inc(&init_count)) == 1) { + if (init_common(init_fns, cnt) < 0) + ret = -1; + } + + if (init_unlock() < 0) + return -1; + + return ret; +} + +int git_runtime_init_count(void) +{ + int ret; + + if (init_lock() < 0) + return -1; + + ret = git_atomic32_get(&init_count); + + if (init_unlock() < 0) + return -1; + + return ret; +} + +int git_runtime_shutdown(void) +{ + int ret; + + /* Enter the lock */ + if (init_lock() < 0) + return -1; + + /* Only do work on a 1 -> 0 transition of the refcount */ + if ((ret = git_atomic32_dec(&init_count)) == 0) + shutdown_common(); + + /* Exit the lock */ + if (init_unlock() < 0) + return -1; + + return ret; +} diff --git a/src/util/runtime.h b/src/util/runtime.h new file mode 100644 index 000000000..6cbfd6043 --- /dev/null +++ b/src/util/runtime.h @@ -0,0 +1,62 @@ +/* + * 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_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "git2_util.h" + +typedef int (*git_runtime_init_fn)(void); +typedef void (*git_runtime_shutdown_fn)(void); + +/** + * Start up a new runtime. If this is the first time that this + * function is called within the context of the current library + * or executable, then the given `init_fns` will be invoked. If + * it is not the first time, they will be ignored. + * + * The given initialization functions _may_ register shutdown + * handlers using `git_runtime_shutdown_register` to be notified + * when the runtime is shutdown. + * + * @param init_fns The list of initialization functions to call + * @param cnt The number of init_fns + * @return The number of initializations performed (including this one) or an error + */ +int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt); + +/* + * Returns the number of initializations active (the number of calls to + * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`). + * If 0, the runtime is not currently initialized. + * + * @return The number of initializations performed or an error + */ +int git_runtime_init_count(void); + +/** + * Shut down the runtime. If this is the last shutdown call, + * such that there are no remaining `init` calls, then any + * shutdown hooks that have been registered will be invoked. + * + * The number of outstanding initializations will be returned. + * If this number is 0, then the runtime is shutdown. + * + * @return The number of outstanding initializations (after this one) or an error + */ +int git_runtime_shutdown(void); + +/** + * Register a shutdown handler for this runtime. This should be done + * by a function invoked by `git_runtime_init` to ensure that the + * appropriate locks are taken. + * + * @param callback The shutdown handler callback + * @return 0 or an error code + */ +int git_runtime_shutdown_register(git_runtime_shutdown_fn callback); + +#endif diff --git a/src/util/sortedcache.c b/src/util/sortedcache.c new file mode 100644 index 000000000..7ff900efe --- /dev/null +++ b/src/util/sortedcache.c @@ -0,0 +1,380 @@ +/* + * 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 "sortedcache.h" + +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path) +{ + git_sortedcache *sc; + size_t pathlen, alloclen; + + pathlen = path ? strlen(path) : 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + sc = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(sc); + + if (git_pool_init(&sc->pool, 1) < 0 || + git_vector_init(&sc->items, 4, item_cmp) < 0 || + git_strmap_new(&sc->map) < 0) + goto fail; + + if (git_rwlock_init(&sc->lock)) { + git_error_set(GIT_ERROR_OS, "failed to initialize lock"); + goto fail; + } + + sc->item_path_offset = item_path_offset; + sc->free_item = free_item; + sc->free_item_payload = free_item_payload; + GIT_REFCOUNT_INC(sc); + if (pathlen) + memcpy(sc->path, path, pathlen); + + *out = sc; + return 0; + +fail: + git_strmap_free(sc->map); + git_vector_free(&sc->items); + git_pool_clear(&sc->pool); + git__free(sc); + return -1; +} + +void git_sortedcache_incref(git_sortedcache *sc) +{ + GIT_REFCOUNT_INC(sc); +} + +const char *git_sortedcache_path(git_sortedcache *sc) +{ + return sc->path; +} + +static void sortedcache_clear(git_sortedcache *sc) +{ + git_strmap_clear(sc->map); + + if (sc->free_item) { + size_t i; + void *item; + + git_vector_foreach(&sc->items, i, item) { + sc->free_item(sc->free_item_payload, item); + } + } + + git_vector_clear(&sc->items); + + git_pool_clear(&sc->pool); +} + +static void sortedcache_free(git_sortedcache *sc) +{ + /* acquire write lock to make sure everyone else is done */ + if (git_sortedcache_wlock(sc) < 0) + return; + + sortedcache_clear(sc); + git_vector_free(&sc->items); + git_strmap_free(sc->map); + + git_sortedcache_wunlock(sc); + + git_rwlock_free(&sc->lock); + git__free(sc); +} + +void git_sortedcache_free(git_sortedcache *sc) +{ + if (!sc) + return; + GIT_REFCOUNT_DEC(sc, sortedcache_free); +} + +static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) +{ + git_sortedcache *sc = payload; + /* path will already have been copied by upsert */ + memcpy(tgt_item, src_item, sc->item_path_offset); + return 0; +} + +/* copy a sorted cache */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload) +{ + int error = 0; + git_sortedcache *tgt; + size_t i; + void *src_item, *tgt_item; + + /* just use memcpy if no special copy fn is passed in */ + if (!copy_item) { + copy_item = sortedcache_copy_item; + payload = src; + } + + if ((error = git_sortedcache_new( + &tgt, src->item_path_offset, + src->free_item, src->free_item_payload, + src->items._cmp, src->path)) < 0) + return error; + + if (lock && git_sortedcache_rlock(src) < 0) { + git_sortedcache_free(tgt); + return -1; + } + + git_vector_foreach(&src->items, i, src_item) { + char *path = ((char *)src_item) + src->item_path_offset; + + if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || + (error = copy_item(payload, tgt_item, src_item)) < 0) + break; + } + + if (lock) + git_sortedcache_runlock(src); + if (error) + git_sortedcache_free(tgt); + + *out = !error ? tgt : NULL; + + return error; +} + +/* lock sortedcache while making modifications */ +int git_sortedcache_wlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_wrlock(&sc->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done with modifications */ +void git_sortedcache_wunlock(git_sortedcache *sc) +{ + git_vector_sort(&sc->items); + git_rwlock_wrunlock(&sc->lock); +} + +/* lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_rdlock(&sc->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done reading */ +void git_sortedcache_runlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + git_rwlock_rdunlock(&sc->lock); +} + +/* if the file has changed, lock cache and load file contents into buf; + * returns <0 on error, >0 if file has not changed + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf) +{ + int error, fd; + struct stat st; + + if ((error = git_sortedcache_wlock(sc)) < 0) + return error; + + if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) + goto unlock; + + if ((fd = git_futils_open_ro(sc->path)) < 0) { + error = fd; + goto unlock; + } + + if (p_fstat(fd, &st) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat file"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (!git__is_sizet(st.st_size)) { + git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (buf) + error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); + + (void)p_close(fd); + + if (error < 0) + goto unlock; + + return 1; /* return 1 -> file needs reload and was successfully loaded */ + +unlock: + git_sortedcache_wunlock(sc); + return error; +} + +void git_sortedcache_updated(git_sortedcache *sc) +{ + /* update filestamp to latest value */ + git_futils_filestamp_check(&sc->stamp, sc->path); +} + +/* release all items in sorted cache */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock) +{ + if (wlock && git_sortedcache_wlock(sc) < 0) + return -1; + + sortedcache_clear(sc); + + if (wlock) + git_sortedcache_wunlock(sc); + + return 0; +} + +/* find and/or insert item, returning pointer to item data */ +int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) +{ + size_t keylen, itemlen; + int error = 0; + char *item_key; + void *item; + + 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, itemlen)) == NULL) { + /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ + error = -1; + goto done; + } + + /* one strange thing is that even if the vector or hash table insert + * fail, there is no way to free the pool item so we just abandon it + */ + + item_key = ((char *)item) + sc->item_path_offset; + memcpy(item_key, key, keylen); + + if ((error = git_strmap_set(sc->map, item_key, item)) < 0) + goto done; + + if ((error = git_vector_insert(&sc->items, item)) < 0) + git_strmap_delete(sc->map, item_key); + +done: + if (out) + *out = !error ? item : NULL; + return error; +} + +/* lookup item by key */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) +{ + return git_strmap_get(sc->map, key); +} + +/* find out how many items are in the cache */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc) +{ + return git_vector_length(&sc->items); +} + +/* lookup item by index */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) +{ + /* make sure the items are sorted so this gets the correct item */ + if (!git_vector_is_sorted(&sc->items)) + git_vector_sort(&sc->items); + + return git_vector_get(&sc->items, pos); +} + +/* helper struct so bsearch callback can know offset + key value for cmp */ +struct sortedcache_magic_key { + size_t offset; + const char *key; +}; + +static int sortedcache_magic_cmp(const void *key, const void *value) +{ + const struct sortedcache_magic_key *magic = key; + const char *value_key = ((const char *)value) + magic->offset; + return strcmp(magic->key, value_key); +} + +/* lookup index of item by key */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key) +{ + struct sortedcache_magic_key magic; + + magic.offset = sc->item_path_offset; + magic.key = key; + + return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); +} + +/* remove entry from cache */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos) +{ + char *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. + */ + + if ((item = git_vector_get(&sc->items, pos)) == NULL) { + git_error_set(GIT_ERROR_INVALID, "removing item out of range"); + return GIT_ENOTFOUND; + } + + (void)git_vector_remove(&sc->items, pos); + + git_strmap_delete(sc->map, item + sc->item_path_offset); + + if (sc->free_item) + sc->free_item(sc->free_item_payload, item); + + return 0; +} + diff --git a/src/util/sortedcache.h b/src/util/sortedcache.h new file mode 100644 index 000000000..3eee4659f --- /dev/null +++ b/src/util/sortedcache.h @@ -0,0 +1,182 @@ +/* + * 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_sorted_cache_h__ +#define INCLUDE_sorted_cache_h__ + +#include "git2_util.h" + +#include "util.h" +#include "futils.h" +#include "vector.h" +#include "thread.h" +#include "pool.h" +#include "strmap.h" + +#include + +/* + * The purpose of this data structure is to cache the parsed contents of a + * file (a.k.a. the backing file) where each item in the file can be + * identified by a key string and you want to both look them up by name + * and traverse them in sorted order. Each item is assumed to itself end + * in a GIT_FLEX_ARRAY. + */ + +typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); + +typedef struct { + git_refcount rc; + git_rwlock lock; + size_t item_path_offset; + git_sortedcache_free_item_fn free_item; + void *free_item_payload; + git_pool pool; + git_vector items; + git_strmap *map; + git_futils_filestamp stamp; + char path[GIT_FLEX_ARRAY]; +} git_sortedcache; + +/* Create a new sortedcache + * + * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at + * the end containing their key string, you have to provide the item_cmp + * sorting function because the sorting function doesn't get a payload + * and therefore can't know the offset to the item key string. :-( + * + * @param out The allocated git_sortedcache + * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the + * struct - use offsetof(struct mine, key-field) to get this + * @param free_item Optional callback to free each item + * @param free_item_payload Optional payload passed to free_item callback + * @param item_cmp Compare the keys of two items + * @param path The path to the backing store file for this cache; this + * may be NULL. The cache makes it easy to load this and check + * if it has been modified since the last load and/or write. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, /* use offsetof(struct, path-field) macro */ + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path); + +/* Copy a sorted cache + * + * - `copy_item` can be NULL to just use memcpy + * - if `lock`, grabs read lock on `src` during copy and releases after + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload); + +/* Free sorted cache (first calling `free_item` callbacks) + * + * Don't call on a locked collection - it may acquire a write lock + */ +void git_sortedcache_free(git_sortedcache *sc); + +/* Increment reference count - balance with call to free */ +void git_sortedcache_incref(git_sortedcache *sc); + +/* Get the pathname associated with this cache at creation time */ +const char *git_sortedcache_path(git_sortedcache *sc); + +/* + * CACHE WRITE FUNCTIONS + * + * The following functions require you to have a writer lock to make the + * modification. Some of the functions take a `wlock` parameter and + * will optionally lock and unlock for you if that is passed as true. + * + */ + +/* Lock sortedcache for write */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with write */ +void git_sortedcache_wunlock(git_sortedcache *sc); + +/* Lock cache and load backing file into a buffer. + * + * This grabs a write lock on the cache then looks at the modification + * time and size of the file on disk. + * + * If the file appears to have changed, this loads the file contents into + * the buffer and returns a positive value leaving the cache locked - the + * caller should parse the file content, update the cache as needed, then + * release the lock. NOTE: In this case, the caller MUST unlock the cache. + * + * If the file appears to be unchanged, then this automatically releases + * the lock on the cache, clears the buffer, and returns 0. + * + * @return 0 if up-to-date, 1 if out-of-date, <0 on error + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload( + git_sortedcache *sc, git_str *buf); + +/* Refresh file timestamp after write completes + * You should already be holding the write lock when you call this. + */ +void git_sortedcache_updated(git_sortedcache *sc); + +/* Release all items in sorted cache + * + * If `wlock` is true, grabs write lock and releases when done, otherwise + * you should already be holding a write lock when you call this. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_clear( + git_sortedcache *sc, bool wlock); + +/* Find and/or insert item, returning pointer to item data. + * You should already be holding the write lock when you call this. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert( + void **out, git_sortedcache *sc, const char *key); + +/* Removes entry at pos from cache + * You should already be holding the write lock when you call this. + */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos); + +/* + * CACHE READ FUNCTIONS + * + * The following functions access items in the cache. To prevent the + * results from being invalidated before they can be used, you should be + * holding either a read lock or a write lock when using these functions. + * + */ + +/* Lock sortedcache for read */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with read */ +void git_sortedcache_runlock(git_sortedcache *sc); + +/* Lookup item by key - returns NULL if not found */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); + +/* Get how many items are in the cache + * + * You can call this function without holding a lock, but be aware + * that it may change before you use it. + */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc); + +/* Lookup item by index - returns NULL if out of range */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); + +/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key); + +#endif diff --git a/src/util/str.c b/src/util/str.c new file mode 100644 index 000000000..0d405bfda --- /dev/null +++ b/src/util/str.c @@ -0,0 +1,1372 @@ +/* + * 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 "str.h" +#include "posix.h" +#include + +/* Used as default value for git_str->ptr so that people can always + * assume ptr is non-NULL and zero terminated even for new git_strs. + */ +char git_str__initstr[1]; + +char git_str__oom[1]; + +#define ENSURE_SIZE(b, d) \ + if ((b)->ptr == git_str__oom || \ + ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\ + return -1; + + +int git_str_init(git_str *buf, size_t initial_size) +{ + buf->asize = 0; + buf->size = 0; + buf->ptr = git_str__initstr; + + ENSURE_SIZE(buf, initial_size); + + return 0; +} + +int git_str_try_grow( + git_str *buf, size_t target_size, bool mark_oom) +{ + char *new_ptr; + size_t new_size; + + if (buf->ptr == git_str__oom) + return -1; + + if (buf->asize == 0 && buf->size != 0) { + git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer"); + return GIT_EINVALID; + } + + if (!target_size) + target_size = buf->size; + + if (target_size <= buf->asize) + return 0; + + if (buf->asize == 0) { + new_size = target_size; + 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; + } + + /* round allocation up to multiple of 8 */ + new_size = (new_size + 7) & ~7; + + if (new_size < buf->size) { + if (mark_oom) { + if (buf->ptr && buf->ptr != git_str__initstr) + git__free(buf->ptr); + buf->ptr = git_str__oom; + } + + git_error_set_oom(); + return -1; + } + + new_ptr = git__realloc(new_ptr, new_size); + + if (!new_ptr) { + if (mark_oom) { + if (buf->ptr && (buf->ptr != git_str__initstr)) + git__free(buf->ptr); + buf->ptr = git_str__oom; + } + return -1; + } + + buf->asize = new_size; + buf->ptr = new_ptr; + + /* truncate the existing buffer size if necessary */ + if (buf->size >= buf->asize) + buf->size = buf->asize - 1; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_grow(git_str *buffer, size_t target_size) +{ + return git_str_try_grow(buffer, target_size, true); +} + +int git_str_grow_by(git_str *buffer, size_t additional_size) +{ + size_t newsize; + + if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) { + buffer->ptr = git_str__oom; + return -1; + } + + return git_str_try_grow(buffer, newsize, true); +} + +void git_str_dispose(git_str *buf) +{ + if (!buf) return; + + if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom) + git__free(buf->ptr); + + git_str_init(buf, 0); +} + +void git_str_clear(git_str *buf) +{ + buf->size = 0; + + if (!buf->ptr) { + buf->ptr = git_str__initstr; + buf->asize = 0; + } + + if (buf->asize > 0) + buf->ptr[0] = '\0'; +} + +int git_str_set(git_str *buf, const void *data, size_t len) +{ + size_t alloclen; + + if (len == 0 || data == NULL) { + git_str_clear(buf); + } else { + if (data != buf->ptr) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + ENSURE_SIZE(buf, alloclen); + memmove(buf->ptr, data, len); + } + + buf->size = len; + if (buf->asize > buf->size) + buf->ptr[buf->size] = '\0'; + + } + return 0; +} + +int git_str_sets(git_str *buf, const char *string) +{ + return git_str_set(buf, string, string ? strlen(string) : 0); +} + +int git_str_putc(git_str *buf, char c) +{ + size_t new_size; + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2); + ENSURE_SIZE(buf, new_size); + buf->ptr[buf->size++] = c; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_putcn(git_str *buf, char c, size_t len) +{ + size_t new_size; + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + memset(buf->ptr + buf->size, c, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_put(git_str *buf, const char *data, size_t len) +{ + if (len) { + size_t new_size; + + GIT_ASSERT_ARG(data); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + memmove(buf->ptr + buf->size, data, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + } + return 0; +} + +int git_str_puts(git_str *buf, const char *string) +{ + GIT_ASSERT_ARG(string); + + return git_str_put(buf, string, strlen(string)); +} + +static char hex_encode[] = "0123456789abcdef"; + +int git_str_encode_hexstr(git_str *str, const char *data, size_t len) +{ + size_t new_size, i; + char *s; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + + if (git_str_grow_by(str, new_size) < 0) + return -1; + + s = str->ptr + str->size; + + for (i = 0; i < len; i++) { + *s++ = hex_encode[(data[i] & 0xf0) >> 4]; + *s++ = hex_encode[(data[i] & 0x0f)]; + } + + str->size += (len * 2); + str->ptr[str->size] = '\0'; + + return 0; +} + +static const char base64_encode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int git_str_encode_base64(git_str *buf, const char *data, size_t len) +{ + size_t extra = len % 3; + uint8_t *write, a, b, c; + const uint8_t *read = (const uint8_t *)data; + size_t blocks = (len / 3) + !!extra, alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); + + ENSURE_SIZE(buf, alloclen); + write = (uint8_t *)&buf->ptr[buf->size]; + + /* convert each run of 3 bytes into 4 output bytes */ + for (len -= extra; len > 0; len -= 3) { + a = *read++; + b = *read++; + c = *read++; + + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; + *write++ = base64_encode[c & 0x3f]; + } + + if (extra > 0) { + a = *read++; + b = (extra > 1) ? *read++ : 0; + + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; + *write++ = '='; + } + + buf->size = ((char *)write) - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +/* The inverse of base64_encode */ +static const int8_t base64_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_str_decode_base64(git_str *buf, const char *base64, size_t len) +{ + size_t i; + int8_t a, b, c, d; + size_t orig_size = buf->size, new_size; + + if (len % 4) { + git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); + return -1; + } + + GIT_ASSERT_ARG(len % 4 == 0); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (i = 0; i < len; i += 4) { + if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || + (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || + (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || + (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); + return -1; + } + + buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); + buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + +static const char base85_encode[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; + +int git_str_encode_base85(git_str *buf, const char *data, size_t len) +{ + size_t blocks = (len / 4) + !!(len % 4), alloclen; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + ENSURE_SIZE(buf, alloclen); + + while (len) { + uint32_t acc = 0; + char b85[5]; + int i; + + for (i = 24; i >= 0; i -= 8) { + uint8_t ch = *data++; + acc |= (uint32_t)ch << i; + + if (--len == 0) + break; + } + + for (i = 4; i >= 0; i--) { + int val = acc % 85; + acc /= 85; + + b85[i] = base85_encode[val]; + } + + for (i = 0; i < 5; i++) + buf->ptr[buf->size++] = b85[i]; + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + +/* The inverse of base85_encode */ +static const int8_t base85_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, + 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, + 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_str_decode_base85( + git_str *buf, + const char *base85, + size_t base85_len, + size_t output_len) +{ + size_t orig_size = buf->size, new_size; + + if (base85_len % 5 || + output_len > base85_len * 4 / 5) { + git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + while (output_len) { + unsigned acc = 0; + int de, cnt = 4; + unsigned char ch; + do { + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + acc = acc * 85 + de; + } while (--cnt); + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + /* Detect overflow. */ + if (0xffffffff / 85 < acc || + 0xffffffff - de < (acc *= 85)) + goto on_error; + + acc += de; + + cnt = (output_len < 4) ? (int)output_len : 4; + output_len -= cnt; + do { + acc = (acc << 8) | (acc >> 24); + buf->ptr[buf->size++] = acc; + } while (--cnt); + } + + buf->ptr[buf->size] = 0; + + return 0; + +on_error: + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); + return -1; +} + +#define HEX_DECODE(c) ((c | 32) % 39 - 9) + +int git_str_decode_percent( + git_str *buf, + const char *str, + size_t str_len) +{ + size_t str_pos, new_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { + if (str[str_pos] == '%' && + str_len > str_pos + 2 && + isxdigit(str[str_pos + 1]) && + isxdigit(str[str_pos + 2])) { + buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + + HEX_DECODE(str[str_pos + 2]); + str_pos += 2; + } else { + buf->ptr[buf->size] = str[str_pos]; + } + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_vprintf(git_str *buf, const char *format, va_list ap) +{ + size_t expected_size, new_size; + int len; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2); + GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size); + ENSURE_SIZE(buf, expected_size); + + while (1) { + va_list args; + va_copy(args, ap); + + len = p_vsnprintf( + buf->ptr + buf->size, + buf->asize - buf->size, + format, args + ); + + va_end(args); + + if (len < 0) { + git__free(buf->ptr); + buf->ptr = git_str__oom; + return -1; + } + + if ((size_t)len + 1 <= buf->asize - buf->size) { + buf->size += len; + break; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + } + + return 0; +} + +int git_str_printf(git_str *buf, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = git_str_vprintf(buf, format, ap); + va_end(ap); + + return r; +} + +int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf) +{ + size_t copylen; + + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(datasize); + GIT_ASSERT_ARG(buf); + + data[0] = '\0'; + + if (buf->size == 0 || buf->asize <= 0) + return 0; + + copylen = buf->size; + if (copylen > datasize - 1) + copylen = datasize - 1; + memmove(data, buf->ptr, copylen); + data[copylen] = '\0'; + + return 0; +} + +void git_str_consume_bytes(git_str *buf, size_t len) +{ + git_str_consume(buf, buf->ptr + len); +} + +void git_str_consume(git_str *buf, const char *end) +{ + if (end > buf->ptr && end <= buf->ptr + buf->size) { + size_t consumed = end - buf->ptr; + memmove(buf->ptr, end, buf->size - consumed); + buf->size -= consumed; + buf->ptr[buf->size] = '\0'; + } +} + +void git_str_truncate(git_str *buf, size_t len) +{ + if (len >= buf->size) + return; + + buf->size = len; + if (buf->size < buf->asize) + buf->ptr[buf->size] = '\0'; +} + +void git_str_shorten(git_str *buf, size_t amount) +{ + if (buf->size > amount) + git_str_truncate(buf, buf->size - amount); + else + git_str_clear(buf); +} + +void git_str_truncate_at_char(git_str *buf, char separator) +{ + ssize_t idx = git_str_find(buf, separator); + if (idx >= 0) + git_str_truncate(buf, (size_t)idx); +} + +void git_str_rtruncate_at_char(git_str *buf, char separator) +{ + ssize_t idx = git_str_rfind_next(buf, separator); + git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx); +} + +void git_str_swap(git_str *str_a, git_str *str_b) +{ + git_str t = *str_a; + *str_a = *str_b; + *str_b = t; +} + +char *git_str_detach(git_str *buf) +{ + char *data = buf->ptr; + + if (buf->asize == 0 || buf->ptr == git_str__oom) + return NULL; + + git_str_init(buf, 0); + + return data; +} + +int git_str_attach(git_str *buf, char *ptr, size_t asize) +{ + git_str_dispose(buf); + + if (ptr) { + buf->ptr = ptr; + buf->size = strlen(ptr); + if (asize) + buf->asize = (asize < buf->size) ? buf->size + 1 : asize; + else /* pass 0 to fall back on strlen + 1 */ + buf->asize = buf->size + 1; + } + + ENSURE_SIZE(buf, asize); + return 0; +} + +void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size) +{ + if (git_str_is_allocated(buf)) + git_str_dispose(buf); + + if (!size) { + git_str_init(buf, 0); + } else { + buf->ptr = (char *)ptr; + buf->asize = 0; + buf->size = size; + } +} + +int git_str_join_n(git_str *buf, char separator, int nbuf, ...) +{ + va_list ap; + int i; + size_t total_size = 0, original_size = buf->size; + char *out, *original = buf->ptr; + + if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) + ++total_size; /* space for initial separator */ + + /* Make two passes to avoid multiple reallocation */ + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char *segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + segment_len = strlen(segment); + + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len); + + if (segment_len == 0 || segment[segment_len - 1] != separator) + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); + } + va_end(ap); + + /* expand buffer if needed */ + if (total_size == 0) + return 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); + if (git_str_grow_by(buf, total_size) < 0) + return -1; + + out = buf->ptr + buf->size; + + /* append separator to existing buf if needed */ + if (buf->size > 0 && out[-1] != separator) + *out++ = separator; + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char *segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + /* deal with join that references buffer's original content */ + if (segment >= original && segment < original + original_size) { + size_t offset = (segment - original); + segment = buf->ptr + offset; + segment_len = original_size - offset; + } else { + segment_len = strlen(segment); + } + + /* skip leading separators */ + if (out > buf->ptr && out[-1] == separator) + while (segment_len > 0 && *segment == separator) { + segment++; + segment_len--; + } + + /* copy over next buffer */ + if (segment_len > 0) { + memmove(out, segment, segment_len); + out += segment_len; + } + + /* append trailing separator (except for last item) */ + if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) + *out++ = separator; + } + va_end(ap); + + /* set size based on num characters actually written */ + buf->size = out - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_join( + git_str *buf, + char separator, + const char *str_a, + const char *str_b) +{ + size_t strlen_a = str_a ? strlen(str_a) : 0; + size_t strlen_b = strlen(str_b); + size_t alloc_len; + int need_sep = 0; + ssize_t offset_a = -1; + + /* not safe to have str_b point internally to the buffer */ + if (buf->size) + GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size); + + /* figure out if we need to insert a separator */ + if (separator && strlen_a) { + while (*str_b == separator) { str_b++; strlen_b--; } + if (str_a[strlen_a - 1] != separator) + need_sep = 1; + } + + /* str_a could be part of the buffer */ + if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size) + offset_a = str_a - buf->ptr; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + ENSURE_SIZE(buf, alloc_len); + + /* fix up internal pointers */ + if (offset_a >= 0) + str_a = buf->ptr + offset_a; + + /* do the actual copying */ + if (offset_a != 0 && str_a) + memmove(buf->ptr, str_a, strlen_a); + if (need_sep) + buf->ptr[strlen_a] = separator; + memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); + + buf->size = strlen_a + strlen_b + need_sep; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_join3( + git_str *buf, + char separator, + const char *str_a, + const char *str_b, + const char *str_c) +{ + size_t len_a = strlen(str_a), + len_b = strlen(str_b), + len_c = strlen(str_c), + len_total; + int sep_a = 0, sep_b = 0; + char *tgt; + + /* for this function, disallow pointers into the existing buffer */ + GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size); + GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size); + GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size); + + if (separator) { + if (len_a > 0) { + while (*str_b == separator) { str_b++; len_b--; } + sep_a = (str_a[len_a - 1] != separator); + } + if (len_a > 0 || len_b > 0) + while (*str_c == separator) { str_c++; len_c--; } + if (len_b > 0) + sep_b = (str_b[len_b - 1] != separator); + } + + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1); + ENSURE_SIZE(buf, len_total); + + tgt = buf->ptr; + + if (len_a) { + memcpy(tgt, str_a, len_a); + tgt += len_a; + } + if (sep_a) + *tgt++ = separator; + if (len_b) { + memcpy(tgt, str_b, len_b); + tgt += len_b; + } + if (sep_b) + *tgt++ = separator; + if (len_c) + memcpy(tgt, str_c, len_c); + + buf->size = len_a + sep_a + len_b + sep_b + len_c; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_str_rtrim(git_str *buf) +{ + while (buf->size > 0) { + if (!git__isspace(buf->ptr[buf->size - 1])) + break; + + buf->size--; + } + + if (buf->asize > buf->size) + buf->ptr[buf->size] = '\0'; +} + +int git_str_cmp(const git_str *a, const git_str *b) +{ + int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); + return (result != 0) ? result : + (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; +} + +int git_str_splice( + git_str *buf, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert) +{ + char *splice_loc; + size_t new_size, alloc_size; + + GIT_ASSERT(buf); + GIT_ASSERT(where <= buf->size); + GIT_ASSERT(nb_to_remove <= buf->size - where); + + splice_loc = buf->ptr + where; + + /* Ported from git.git + * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 + */ + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1); + ENSURE_SIZE(buf, alloc_size); + + memmove(splice_loc + nb_to_insert, + splice_loc + nb_to_remove, + buf->size - where - nb_to_remove); + + memcpy(splice_loc, data, nb_to_insert); + + buf->size = new_size; + buf->ptr[buf->size] = '\0'; + return 0; +} + +/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_str_quote(git_str *buf) +{ + const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; + git_str quoted = GIT_STR_INIT; + size_t i = 0; + bool quote = false; + int error = 0; + + /* walk to the first char that needs quoting */ + if (buf->size && buf->ptr[0] == '!') + quote = true; + + for (i = 0; !quote && i < buf->size; i++) { + if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || + buf->ptr[i] < ' ' || buf->ptr[i] > '~') { + quote = true; + break; + } + } + + if (!quote) + goto done; + + git_str_putc("ed, '"'); + git_str_put("ed, buf->ptr, i); + + for (; i < buf->size; i++) { + /* whitespace - use the map above, which is ordered by ascii value */ + if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { + git_str_putc("ed, '\\'); + git_str_putc("ed, whitespace[buf->ptr[i] - '\a']); + } + + /* double quote and backslash must be escaped */ + else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { + git_str_putc("ed, '\\'); + git_str_putc("ed, buf->ptr[i]); + } + + /* escape anything unprintable as octal */ + else if (buf->ptr[i] != ' ' && + (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { + git_str_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); + } + + /* yay, printable! */ + else { + git_str_putc("ed, buf->ptr[i]); + } + } + + git_str_putc("ed, '"'); + + if (git_str_oom("ed)) { + error = -1; + goto done; + } + + git_str_swap("ed, buf); + +done: + git_str_dispose("ed); + return error; +} + +/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_str_unquote(git_str *buf) +{ + size_t i, j; + char ch; + + git_str_rtrim(buf); + + if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') + goto invalid; + + for (i = 0, j = 1; j < buf->size-1; i++, j++) { + ch = buf->ptr[j]; + + if (ch == '\\') { + if (j == buf->size-2) + goto invalid; + + ch = buf->ptr[++j]; + + switch (ch) { + /* \" or \\ simply copy the char in */ + case '"': case '\\': + break; + + /* add the appropriate escaped char */ + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + + /* \xyz digits convert to the char*/ + case '0': case '1': case '2': case '3': + if (j == buf->size-3) { + git_error_set(GIT_ERROR_INVALID, + "truncated quoted character \\%c", ch); + return -1; + } + + if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || + buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { + git_error_set(GIT_ERROR_INVALID, + "truncated quoted character \\%c%c%c", + buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); + return -1; + } + + ch = ((buf->ptr[j] - '0') << 6) | + ((buf->ptr[j+1] - '0') << 3) | + (buf->ptr[j+2] - '0'); + j += 2; + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch); + return -1; + } + } + + buf->ptr[i] = ch; + } + + buf->ptr[i] = '\0'; + buf->size = i; + + return 0; + +invalid: + git_error_set(GIT_ERROR_INVALID, "invalid quoted line"); + return -1; +} + +int git_str_puts_escaped( + git_str *buf, + const char *string, + const char *esc_chars, + const char *esc_with) +{ + const char *scan; + size_t total = 0, esc_len = strlen(esc_with), count, alloclen; + + if (!string) + return 0; + + for (scan = string; *scan; ) { + /* count run of non-escaped characters */ + count = strcspn(scan, esc_chars); + total += count; + scan += count; + /* count run of escaped characters */ + count = strspn(scan, esc_chars); + total += count * (esc_len + 1); + scan += count; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1); + if (git_str_grow_by(buf, alloclen) < 0) + return -1; + + for (scan = string; *scan; ) { + count = strcspn(scan, esc_chars); + + memmove(buf->ptr + buf->size, scan, count); + scan += count; + buf->size += count; + + for (count = strspn(scan, esc_chars); count > 0; --count) { + /* copy escape sequence */ + memmove(buf->ptr + buf->size, esc_with, esc_len); + buf->size += esc_len; + /* copy character to be escaped */ + buf->ptr[buf->size] = *scan; + buf->size++; + scan++; + } + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_str_unescape(git_str *buf) +{ + buf->size = git__unescape(buf->ptr); +} + +int git_str_crlf_to_lf(git_str *tgt, const git_str *src) +{ + const char *scan = src->ptr; + const char *scan_end = src->ptr + src->size; + const char *next = memchr(scan, '\r', src->size); + size_t new_size; + char *out; + + GIT_ASSERT(tgt != src); + + if (!next) + return git_str_set(tgt, src->ptr, src->size); + + /* reduce reallocs while in the loop */ + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1); + if (git_str_grow(tgt, new_size) < 0) + return -1; + + out = tgt->ptr; + tgt->size = 0; + + /* Find the next \r and copy whole chunk up to there to tgt */ + for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { + if (next > scan) { + size_t copylen = (size_t)(next - scan); + memcpy(out, scan, copylen); + out += copylen; + } + + /* Do not drop \r unless it is followed by \n */ + if (next + 1 == scan_end || next[1] != '\n') + *out++ = '\r'; + } + + /* Copy remaining input into dest */ + if (scan < scan_end) { + size_t remaining = (size_t)(scan_end - scan); + memcpy(out, scan, remaining); + out += remaining; + } + + tgt->size = (size_t)(out - tgt->ptr); + tgt->ptr[tgt->size] = '\0'; + + return 0; +} + +int git_str_lf_to_crlf(git_str *tgt, const git_str *src) +{ + const char *start = src->ptr; + const char *end = start + src->size; + const char *scan = start; + const char *next = memchr(scan, '\n', src->size); + size_t alloclen; + + GIT_ASSERT(tgt != src); + + if (!next) + return git_str_set(tgt, src->ptr, src->size); + + /* attempt to reduce reallocs while in the loop */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + if (git_str_grow(tgt, alloclen) < 0) + return -1; + tgt->size = 0; + + for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { + size_t copylen = next - scan; + + /* if we find mixed line endings, carry on */ + if (copylen && next[-1] == '\r') + copylen--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3); + if (git_str_grow_by(tgt, alloclen) < 0) + return -1; + + if (copylen) { + memcpy(tgt->ptr + tgt->size, scan, copylen); + tgt->size += copylen; + } + + tgt->ptr[tgt->size++] = '\r'; + tgt->ptr[tgt->size++] = '\n'; + } + + tgt->ptr[tgt->size] = '\0'; + return git_str_put(tgt, scan, end - scan); +} + +int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count) +{ + size_t i; + const char *str, *pfx; + + git_str_clear(buf); + + if (!strings || !count) + return 0; + + /* initialize common prefix to first string */ + if (git_str_sets(buf, strings[0]) < 0) + return -1; + + /* go through the rest of the strings, truncating to shared prefix */ + for (i = 1; i < count; ++i) { + + for (str = strings[i], pfx = buf->ptr; + *str && *str == *pfx; + str++, pfx++) + /* scanning */; + + git_str_truncate(buf, pfx - buf->ptr); + + if (!buf->size) + break; + } + + return 0; +} + +int git_str_is_binary(const git_str *buf) +{ + const char *scan = buf->ptr, *end = buf->ptr + buf->size; + git_str_bom_t bom; + int printable = 0, nonprintable = 0; + + scan += git_str_detect_bom(&bom, buf); + + if (bom > GIT_STR_BOM_UTF8) + return 1; + + while (scan < end) { + unsigned char c = *scan++; + + /* Printable characters are those above SPACE (0x1F) excluding DEL, + * and including BS, ESC and FF. + */ + if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014') + printable++; + else if (c == '\0') + return true; + else if (!git__isspace(c)) + nonprintable++; + } + + return ((printable >> 7) < nonprintable); +} + +int git_str_contains_nul(const git_str *buf) +{ + return (memchr(buf->ptr, '\0', buf->size) != NULL); +} + +int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf) +{ + const char *ptr; + size_t len; + + *bom = GIT_STR_BOM_NONE; + /* need at least 2 bytes to look for any BOM */ + if (buf->size < 2) + return 0; + + ptr = buf->ptr; + len = buf->size; + + switch (*ptr++) { + case 0: + if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') { + *bom = GIT_STR_BOM_UTF32_BE; + return 4; + } + break; + case '\xEF': + if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') { + *bom = GIT_STR_BOM_UTF8; + return 3; + } + break; + case '\xFE': + if (*ptr == '\xFF') { + *bom = GIT_STR_BOM_UTF16_BE; + return 2; + } + break; + case '\xFF': + if (*ptr != '\xFE') + break; + if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) { + *bom = GIT_STR_BOM_UTF32_LE; + return 4; + } else { + *bom = GIT_STR_BOM_UTF16_LE; + return 2; + } + break; + default: + break; + } + + return 0; +} + +bool git_str_gather_text_stats( + git_str_text_stats *stats, const git_str *buf, bool skip_bom) +{ + const char *scan = buf->ptr, *end = buf->ptr + buf->size; + int skip; + + memset(stats, 0, sizeof(*stats)); + + /* BOM detection */ + skip = git_str_detect_bom(&stats->bom, buf); + if (skip_bom) + scan += skip; + + /* Ignore EOF character */ + if (buf->size > 0 && end[-1] == '\032') + end--; + + /* Counting loop */ + while (scan < end) { + unsigned char c = *scan++; + + if (c > 0x1F && c != 0x7F) + stats->printable++; + else switch (c) { + case '\0': + stats->nul++; + stats->nonprintable++; + break; + case '\n': + stats->lf++; + break; + case '\r': + stats->cr++; + if (scan < end && *scan == '\n') + stats->crlf++; + break; + case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ + stats->printable++; + break; + default: + stats->nonprintable++; + break; + } + } + + /* Treat files with a bare CR as binary */ + return (stats->cr != stats->crlf || stats->nul > 0 || + ((stats->printable >> 7) < stats->nonprintable)); +} diff --git a/src/util/str.h b/src/util/str.h new file mode 100644 index 000000000..588e6fc22 --- /dev/null +++ b/src/util/str.h @@ -0,0 +1,357 @@ +/* + * 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_str_h__ +#define INCLUDE_str_h__ + +#include "git2_util.h" + +struct git_str { + char *ptr; + size_t asize; + size_t size; +}; + +typedef enum { + GIT_STR_BOM_NONE = 0, + GIT_STR_BOM_UTF8 = 1, + GIT_STR_BOM_UTF16_LE = 2, + GIT_STR_BOM_UTF16_BE = 3, + GIT_STR_BOM_UTF32_LE = 4, + GIT_STR_BOM_UTF32_BE = 5 +} git_str_bom_t; + +typedef struct { + git_str_bom_t bom; /* BOM found at head of text */ + unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */ + unsigned int printable, nonprintable; /* These are just approximations! */ +} git_str_text_stats; + +extern char git_str__initstr[]; +extern char git_str__oom[]; + +/* Use to initialize string buffer structure when git_str is on stack */ +#define GIT_STR_INIT { git_str__initstr, 0, 0 } + +/** + * Static initializer for git_str from static string buffer + */ +#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) } + +GIT_INLINE(bool) git_str_is_allocated(const git_str *str) +{ + return (str->ptr != NULL && str->asize > 0); +} + +/** + * Initialize a git_str structure. + * + * For the cases where GIT_STR_INIT cannot be used to do static + * initialization. + */ +extern int git_str_init(git_str *str, size_t initial_size); + +extern void git_str_dispose(git_str *str); + +/** + * Resize the string buffer allocation to make more space. + * + * This will attempt to grow the string buffer to accommodate the target + * size. The bstring buffer's `ptr` will be replaced with a newly + * allocated block of data. Be careful so that memory allocated by the + * caller is not lost. As a special variant, if you pass `target_size` as + * 0 and the memory is not allocated by libgit2, this will allocate a new + * buffer of size `size` and copy the external data into it. + * + * Currently, this will never shrink a buffer, only expand it. + * + * If the allocation fails, this will return an error and the buffer will be + * marked as invalid for future operations, invaliding the contents. + * + * @param str The buffer to be resized; may or may not be allocated yet + * @param target_size The desired available size + * @return 0 on success, -1 on allocation failure + */ +int git_str_grow(git_str *str, size_t target_size); + +/** + * Resize the buffer allocation to make more space. + * + * This will attempt to grow the string buffer to accommodate the + * additional size. It is similar to `git_str_grow`, but performs the + * new size calculation, checking for overflow. + * + * Like `git_str_grow`, if this is a user-supplied string buffer, + * this will allocate a new string uffer. + */ +extern int git_str_grow_by(git_str *str, size_t additional_size); + +/** + * Attempt to grow the buffer to hold at least `target_size` bytes. + * + * If the allocation fails, this will return an error. If `mark_oom` is + * true, this will mark the string buffer as invalid for future + * operations; if false, existing string buffer content will be preserved, + * but calling code must handle that string buffer was not expanded. If + * `preserve_external` is true, then any existing data pointed to be + * `ptr` even if `asize` is zero will be copied into the newly allocated + * string buffer. + */ +extern int git_str_try_grow( + git_str *str, size_t target_size, bool mark_oom); + +extern void git_str_swap(git_str *str_a, git_str *str_b); +extern char *git_str_detach(git_str *str); +extern int git_str_attach(git_str *str, char *ptr, size_t asize); + +/* Populates a `git_str` where the contents are not "owned" by the string + * buffer, and calls to `git_str_dispose` will not free the given str. + */ +extern void git_str_attach_notowned( + git_str *str, const char *ptr, size_t size); + +/** + * Test if there have been any reallocation failures with this git_str. + * + * Any function that writes to a git_str can fail due to memory allocation + * issues. If one fails, the git_str will be marked with an OOM error and + * further calls to modify the string buffer will fail. Check + * git_str_oom() at the end of your sequence and it will be true if you + * ran out of memory at any point with that string buffer. + * + * @return false if no error, true if allocation error + */ +GIT_INLINE(bool) git_str_oom(const git_str *str) +{ + return (str->ptr == git_str__oom); +} + +/* + * Functions below that return int value error codes will return 0 on + * success or -1 on failure (which generally means an allocation failed). + * Using a git_str where the allocation has failed with result in -1 from + * all further calls using that string buffer. As a result, you can + * ignore the return code of these functions and call them in a series + * then just call git_str_oom at the end. + */ + +int git_str_set(git_str *str, const void *data, size_t datalen); + +int git_str_sets(git_str *str, const char *string); +int git_str_putc(git_str *str, char c); +int git_str_putcn(git_str *str, char c, size_t len); +int git_str_put(git_str *str, const char *data, size_t len); +int git_str_puts(git_str *str, const char *string); +int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); +int git_str_vprintf(git_str *str, const char *format, va_list ap); +void git_str_clear(git_str *str); +void git_str_consume_bytes(git_str *str, size_t len); +void git_str_consume(git_str *str, const char *end); +void git_str_truncate(git_str *str, size_t len); +void git_str_shorten(git_str *str, size_t amount); +void git_str_truncate_at_char(git_str *path, char separator); +void git_str_rtruncate_at_char(git_str *path, char separator); + +/** General join with separator */ +int git_str_join_n(git_str *str, char separator, int len, ...); +/** Fast join of two strings - first may legally point into `str` data */ +int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b); +/** Fast join of three strings - cannot reference `str` data */ +int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c); + +/** + * Join two strings as paths, inserting a slash between as needed. + * @return 0 on success, -1 on failure + */ +GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b) +{ + return git_str_join(str, '/', a, b); +} + +GIT_INLINE(const char *) git_str_cstr(const git_str *str) +{ + return str->ptr; +} + +GIT_INLINE(size_t) git_str_len(const git_str *str) +{ + return str->size; +} + +int git_str_copy_cstr(char *data, size_t datasize, const git_str *str); + +#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1) + +GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch) +{ + ssize_t idx = (ssize_t)str->size - 1; + while (idx >= 0 && str->ptr[idx] == ch) idx--; + while (idx >= 0 && str->ptr[idx] != ch) idx--; + return idx; +} + +GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch) +{ + ssize_t idx = (ssize_t)str->size - 1; + while (idx >= 0 && str->ptr[idx] != ch) idx--; + return idx; +} + +GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch) +{ + void *found = memchr(str->ptr, ch, str->size); + return found ? (ssize_t)((const char *)found - str->ptr) : -1; +} + +/* Remove whitespace from the end of the string buffer */ +void git_str_rtrim(git_str *str); + +int git_str_cmp(const git_str *a, const git_str *b); + +/* Quote and unquote a string buffer as specified in + * http://marc.info/?l=git&m=112927316408690&w=2 + */ +int git_str_quote(git_str *str); +int git_str_unquote(git_str *str); + +/* Write data as a hex string */ +int git_str_encode_hexstr(git_str *str, const char *data, size_t len); + +/* Write data as base64 encoded in string buffer */ +int git_str_encode_base64(git_str *str, const char *data, size_t len); +/* Decode the given bas64 and write the result to the string buffer */ +int git_str_decode_base64(git_str *str, const char *base64, size_t len); + +/* Write data as "base85" encoded in string buffer */ +int git_str_encode_base85(git_str *str, const char *data, size_t len); +/* Decode the given "base85" and write the result to the string buffer */ +int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len); + +/* + * Decode the given percent-encoded string and write the result to the + * string buffer. + */ +int git_str_decode_percent(git_str *str, const char *encoded, size_t len); + +/* + * Insert, remove or replace a portion of the string buffer. + * + * @param str The string buffer to work with + * + * @param where The location in the string buffer where the transformation + * should be applied. + * + * @param nb_to_remove The number of chars to be removed. 0 to not + * remove any character in the string buffer. + * + * @param data A pointer to the data which should be inserted. + * + * @param nb_to_insert The number of chars to be inserted. 0 to not + * insert any character from the string buffer. + * + * @return 0 or an error code. + */ +int git_str_splice( + git_str *str, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert); + +/** + * Append string to string buffer, prefixing each character from + * `esc_chars` with `esc_with` string. + * + * @param str String buffer to append data to + * @param string String to escape and append + * @param esc_chars Characters to be escaped + * @param esc_with String to insert in from of each found character + * @return 0 on success, <0 on failure (probably allocation problem) + */ +extern int git_str_puts_escaped( + git_str *str, + const char *string, + const char *esc_chars, + const char *esc_with); + +/** + * Append string escaping characters that are regex special + */ +GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) +{ + return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); +} + +/** + * Unescape all characters in a string buffer in place + * + * I.e. remove backslashes + */ +extern void git_str_unescape(git_str *str); + +/** + * Replace all \r\n with \n. + * + * @return 0 on success, -1 on memory error + */ +extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src); + +/** + * Replace all \n with \r\n. Does not modify existing \r\n. + * + * @return 0 on success, -1 on memory error + */ +extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src); + +/** + * Fill string buffer with the common prefix of a array of strings + * + * String buffer will be set to empty if there is no common prefix + */ +extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count); + +/** + * Check if a string buffer begins with a UTF BOM + * + * @param bom Set to the type of BOM detected or GIT_BOM_NONE + * @param str String buffer in which to check the first bytes for a BOM + * @return Number of bytes of BOM data (or 0 if no BOM found) + */ +extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str); + +/** + * Gather stats for a piece of text + * + * Fill the `stats` structure with counts of unreadable characters, carriage + * returns, etc, so it can be used in heuristics. This automatically skips + * a trailing EOF (\032 character). Also it will look for a BOM at the + * start of the text and can be told to skip that as well. + * + * @param stats Structure to be filled in + * @param str Text to process + * @param skip_bom Exclude leading BOM from stats if true + * @return Does the string buffer heuristically look like binary data + */ +extern bool git_str_gather_text_stats( + git_str_text_stats *stats, const git_str *str, bool skip_bom); + +/** +* Check quickly if string buffer looks like it contains binary data +* +* @param str string buffer to check +* @return 1 if string buffer looks like non-text data +*/ +int git_str_is_binary(const git_str *str); + +/** +* Check quickly if buffer contains a NUL byte +* +* @param str string buffer to check +* @return 1 if string buffer contains a NUL byte +*/ +int git_str_contains_nul(const git_str *str); + +#endif diff --git a/src/util/strmap.c b/src/util/strmap.c new file mode 100644 index 000000000..c6e5b6dc7 --- /dev/null +++ b/src/util/strmap.c @@ -0,0 +1,100 @@ +/* + * 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 "strmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__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_new(git_strmap **out) +{ + *out = kh_init(str); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_strmap_free(git_strmap *map) +{ + kh_destroy(str, map); +} + +void git_strmap_clear(git_strmap *map) +{ + kh_clear(str, map); +} + +size_t git_strmap_size(git_strmap *map) +{ + return kh_size(map); +} + +void *git_strmap_get(git_strmap *map, const char *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_set(git_strmap *map, const char *key, void *value) +{ + 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) +{ + return kh_get(str, map, key) != kh_end(map); +} + +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_val(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/util/strmap.h b/src/util/strmap.h new file mode 100644 index 000000000..b64d3dcb5 --- /dev/null +++ b/src/util/strmap.h @@ -0,0 +1,131 @@ +/* + * 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_strmap_h__ +#define INCLUDE_strmap_h__ + +#include "git2_util.h" + +/** A map with C strings as key. */ +typedef struct kh_str_s git_strmap; + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +#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 = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/util/strnlen.h b/src/util/strnlen.h new file mode 100644 index 000000000..eecfe3c02 --- /dev/null +++ b/src/util/strnlen.h @@ -0,0 +1,24 @@ +/* + * 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_strlen_h__ +#define INCLUDE_strlen_h__ + +#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ + (defined(_MSC_VER) && _MSC_VER < 1500) +# define NO_STRNLEN +#endif + +#ifdef NO_STRNLEN +GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { + const char *end = memchr(s, 0, maxlen); + return end ? (size_t)(end - s) : maxlen; +} +#else +# define p_strnlen strnlen +#endif + +#endif diff --git a/src/util/thread.c b/src/util/thread.c new file mode 100644 index 000000000..bc7364f8c --- /dev/null +++ b/src/util/thread.c @@ -0,0 +1,140 @@ +/* + * 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_util.h" + +#if !defined(GIT_THREADS) + +#define TLSDATA_MAX 16 + +typedef struct { + void *value; + void (GIT_SYSTEM_CALL *destroy_fn)(void *); +} tlsdata_value; + +static tlsdata_value tlsdata_values[TLSDATA_MAX]; +static int tlsdata_cnt = 0; + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + if (tlsdata_cnt >= TLSDATA_MAX) + return -1; + + tlsdata_values[tlsdata_cnt].value = NULL; + tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn; + + *key = tlsdata_cnt; + tlsdata_cnt++; + + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (key < 0 || key > tlsdata_cnt) + return -1; + + tlsdata_values[key].value = value; + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + if (key < 0 || key > tlsdata_cnt) + return NULL; + + return tlsdata_values[key].value; +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + void *value; + void (*destroy_fn)(void *) = NULL; + + if (key < 0 || key > tlsdata_cnt) + return -1; + + value = tlsdata_values[key].value; + destroy_fn = tlsdata_values[key].destroy_fn; + + tlsdata_values[key].value = NULL; + tlsdata_values[key].destroy_fn = NULL; + + if (value && destroy_fn) + destroy_fn(value); + + return 0; +} + +#elif defined(GIT_WIN32) + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + DWORD fls_index = FlsAlloc(destroy_fn); + + if (fls_index == FLS_OUT_OF_INDEXES) + return -1; + + *key = fls_index; + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (!FlsSetValue(key, value)) + return -1; + + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + return FlsGetValue(key); +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + if (!FlsFree(key)) + return -1; + + return 0; +} + +#elif defined(_POSIX_THREADS) + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + if (pthread_key_create(key, destroy_fn) != 0) + return -1; + + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (pthread_setspecific(key, value) != 0) + return -1; + + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + return pthread_getspecific(key); +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + if (pthread_key_delete(key) != 0) + return -1; + + return 0; +} + +#else +# error unknown threading model +#endif diff --git a/src/util/thread.h b/src/util/thread.h new file mode 100644 index 000000000..4bbac9fd8 --- /dev/null +++ b/src/util/thread.h @@ -0,0 +1,479 @@ +/* + * 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_thread_h__ +#define INCLUDE_thread_h__ + +#if defined(GIT_THREADS) + +#if defined(__clang__) + +# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1)) +# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF +# else +# define GIT_BUILTIN_ATOMIC +# endif + +#elif defined(__GNUC__) + +# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) +# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF +# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) +# define GIT_BUILTIN_ATOMIC +# else +# define GIT_BUILTIN_SYNC +# endif + +#endif + +#endif /* GIT_THREADS */ + +/* Common operations even if threading has been disabled */ +typedef struct { +#if defined(GIT_WIN32) + volatile long val; +#else + volatile int val; +#endif +} git_atomic32; + +#ifdef GIT_ARCH_64 + +typedef struct { +#if defined(GIT_WIN32) + volatile __int64 val; +#else + volatile int64_t val; +#endif +} git_atomic64; + +typedef git_atomic64 git_atomic_ssize; + +#define git_atomic_ssize_set git_atomic64_set +#define git_atomic_ssize_add git_atomic64_add +#define git_atomic_ssize_get git_atomic64_get + +#else + +typedef git_atomic32 git_atomic_ssize; + +#define git_atomic_ssize_set git_atomic32_set +#define git_atomic_ssize_add git_atomic32_add +#define git_atomic_ssize_get git_atomic32_get + +#endif + +#ifdef GIT_THREADS + +#ifdef GIT_WIN32 +# include "win32/thread.h" +#else +# include "unix/pthread.h" +#endif + +/* + * Atomically sets the contents of *a to be val. + */ +GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) +{ +#if defined(GIT_WIN32) + InterlockedExchange(&a->val, (LONG)val); +#elif defined(GIT_BUILTIN_ATOMIC) + __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically increments the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return InterlockedIncrement(&a->val); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, 1); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) +{ +#if defined(GIT_WIN32) + return InterlockedAdd(&a->val, addend); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, addend); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically decrements the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return InterlockedDecrement(&a->val); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_sub_and_fetch(&a->val, 1); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ +GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return (int)InterlockedCompareExchange(&a->val, 0, 0); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(&a->val, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(void *) git_atomic__compare_and_swap( + void * volatile *ptr, void *oldval, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); +#elif defined(GIT_BUILTIN_ATOMIC) + void *foundval = oldval; + __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return foundval; +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(ptr, oldval, newval); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(volatile void *) git_atomic__swap( + void * volatile *ptr, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedExchangePointer(ptr, newval); +#elif defined(GIT_BUILTIN_ATOMIC) + void * foundval = NULL; + __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); + return foundval; +#elif defined(GIT_BUILTIN_SYNC) + return (volatile void *)__sync_lock_test_and_set(ptr, newval); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) +{ +#if defined(GIT_WIN32) + void *newval = NULL, *oldval = NULL; + return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); +#elif defined(GIT_BUILTIN_ATOMIC) + return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#ifdef GIT_ARCH_64 + +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) +{ +#if defined(GIT_WIN32) + return InterlockedAdd64(&a->val, addend); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, addend); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically sets the contents of *a to be val. + */ +GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) +{ +#if defined(GIT_WIN32) + InterlockedExchange64(&a->val, val); +#elif defined(GIT_BUILTIN_ATOMIC) + __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ +GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) +{ +#if defined(GIT_WIN32) + return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(&a->val, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#endif + +#else + +#define git_threads_global_init git__noop + +#define git_thread unsigned int +#define git_thread_create(thread, start_routine, arg) git__noop() +#define git_thread_join(id, status) git__noop() + +/* Pthreads Mutex */ +#define git_mutex unsigned int +#define git_mutex_init(a) git__noop() +#define git_mutex_init(a) git__noop() +#define git_mutex_lock(a) git__noop() +#define git_mutex_unlock(a) git__noop() +#define git_mutex_free(a) git__noop() + +/* Pthreads condition vars */ +#define git_cond unsigned int +#define git_cond_init(c) git__noop() +#define git_cond_free(c) git__noop() +#define git_cond_wait(c, l) git__noop() +#define git_cond_signal(c) git__noop() +#define git_cond_broadcast(c) git__noop() + +/* Pthreads rwlock */ +#define git_rwlock unsigned int +#define git_rwlock_init(a) git__noop() +#define git_rwlock_rdlock(a) git__noop() +#define git_rwlock_rdunlock(a) git__noop() +#define git_rwlock_wrlock(a) git__noop() +#define git_rwlock_wrunlock(a) git__noop() +#define git_rwlock_free(a) git__noop() +#define GIT_RWLOCK_STATIC_INIT 0 + + +GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) +{ + a->val = val; +} + +GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) +{ + return ++a->val; +} + +GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) +{ + a->val += addend; + return a->val; +} + +GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) +{ + return --a->val; +} + +GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) +{ + return (int)a->val; +} + +GIT_INLINE(void *) git_atomic__compare_and_swap( + void * volatile *ptr, void *oldval, void *newval) +{ + void *foundval = *ptr; + if (foundval == oldval) + *ptr = newval; + return foundval; +} + +GIT_INLINE(volatile void *) git_atomic__swap( + void * volatile *ptr, void *newval) +{ + volatile void *old = *ptr; + *ptr = newval; + return old; +} + +GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) +{ + return *ptr; +} + +#ifdef GIT_ARCH_64 + +GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) +{ + a->val += addend; + return a->val; +} + +GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) +{ + a->val = val; +} + +GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) +{ + return (int64_t)a->val; +} + +#endif + +#endif + +/* + * Atomically replace the contents of *ptr (if they are equal to oldval) with + * newval. ptr must point to a pointer or a value that is the same size as a + * pointer. This is semantically compatible with: + * + * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ + * ({ \ + * void *foundval = *ptr; \ + * if (foundval == oldval) \ + * *ptr = newval; \ + * foundval; \ + * }) + * + * @return the original contents of *ptr. + */ +#define git_atomic_compare_and_swap(ptr, oldval, newval) \ + git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) + +/* + * Atomically replace the contents of v with newval. v must be the same size as + * a pointer. This is semantically compatible with: + * + * #define git_atomic_swap(v, newval) \ + * ({ \ + * volatile void *old = v; \ + * v = newval; \ + * old; \ + * }) + * + * @return the original contents of v. + */ +#define git_atomic_swap(v, newval) \ + (void *)git_atomic__swap((void * volatile *)&(v), newval) + +/* + * Atomically reads the contents of v. v must be the same size as a pointer. + * This is semantically compatible with: + * + * #define git_atomic_load(v) v + * + * @return the contents of v. + */ +#define git_atomic_load(v) \ + (void *)git_atomic__load((void * volatile *)&(v)) + +#if defined(GIT_THREADS) + +# if defined(GIT_WIN32) +# define GIT_MEMORY_BARRIER MemoryBarrier() +# elif defined(GIT_BUILTIN_ATOMIC) +# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST) +# elif defined(GIT_BUILTIN_SYNC) +# define GIT_MEMORY_BARRIER __sync_synchronize() +# endif + +#else + +# define GIT_MEMORY_BARRIER /* noop */ + +#endif + +/* Thread-local data */ + +#if !defined(GIT_THREADS) +# define git_tlsdata_key int +#elif defined(GIT_WIN32) +# define git_tlsdata_key DWORD +#elif defined(_POSIX_THREADS) +# define git_tlsdata_key pthread_key_t +#else +# error unknown threading model +#endif + +/** + * Create a thread-local data key. The destroy function will be + * called upon thread exit. On some platforms, it may be called + * when all threads have deleted their keys. + * + * Note that the tlsdata functions do not set an error message on + * failure; this is because the error handling in libgit2 is itself + * handled by thread-local data storage. + * + * @param key the tlsdata key + * @param destroy_fn function pointer called upon thread exit + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)); + +/** + * Set a the thread-local value for the given key. + * + * @param key the tlsdata key to store data on + * @param value the pointer to store + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_set(git_tlsdata_key key, void *value); + +/** + * Get the thread-local value for the given key. + * + * @param key the tlsdata key to retrieve the value of + * @return the pointer stored with git_tlsdata_set + */ +void *git_tlsdata_get(git_tlsdata_key key); + +/** + * Delete the given thread-local key. + * + * @param key the tlsdata key to dispose + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_dispose(git_tlsdata_key key); + +#endif diff --git a/src/util/tsort.c b/src/util/tsort.c new file mode 100644 index 000000000..2ef03d03a --- /dev/null +++ b/src/util/tsort.c @@ -0,0 +1,382 @@ +/* + * 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_util.h" + +/** + * An array-of-pointers implementation of Python's Timsort + * Based on code by Christopher Swenson under the MIT license + * + * Copyright (c) 2010 Christopher Swenson + * Copyright (c) 2011 Vicent Marti + */ + +#ifndef MAX +# define MAX(x,y) (((x) > (y) ? (x) : (y))) +#endif + +#ifndef MIN +# define MIN(x,y) (((x) < (y) ? (x) : (y))) +#endif + +static int binsearch( + void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) +{ + int l, c, r; + void *lx, *cx; + + l = 0; + r = (int)size - 1; + c = r >> 1; + lx = dst[l]; + + /* check for beginning conditions */ + if (cmp(x, lx, payload) < 0) + return 0; + + else if (cmp(x, lx, payload) == 0) { + int i = 1; + while (cmp(x, dst[i], payload) == 0) + i++; + return i; + } + + /* guaranteed not to be >= rx */ + cx = dst[c]; + while (1) { + const int val = cmp(x, cx, payload); + if (val < 0) { + if (c - l <= 1) return c; + r = c; + } else if (val > 0) { + if (r - c <= 1) return c + 1; + l = c; + lx = cx; + } else { + do { + cx = dst[++c]; + } while (cmp(x, cx, payload) == 0); + return c; + } + c = l + ((r - l) >> 1); + cx = dst[c]; + } +} + +/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ +static void bisort( + void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) +{ + size_t i; + void *x; + int location; + + for (i = start; i < size; i++) { + int j; + /* If this entry is already correct, just move along */ + if (cmp(dst[i - 1], dst[i], payload) <= 0) + continue; + + /* Else we need to find the right place, shift everything over, and squeeze in */ + x = dst[i]; + location = binsearch(dst, x, i, cmp, payload); + for (j = (int)i - 1; j >= location; j--) { + dst[j + 1] = dst[j]; + } + dst[location] = x; + } +} + + +/* timsort implementation, based on timsort.txt */ +struct tsort_run { + ssize_t start; + ssize_t length; +}; + +struct tsort_store { + size_t alloc; + git__sort_r_cmp cmp; + void *payload; + void **storage; +}; + +static void reverse_elements(void **dst, ssize_t start, ssize_t end) +{ + while (start < end) { + void *tmp = dst[start]; + dst[start] = dst[end]; + dst[end] = tmp; + + start++; + end--; + } +} + +static ssize_t count_run( + void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +{ + ssize_t curr = start + 2; + + if (size - start == 1) + return 1; + + if (start >= size - 2) { + if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { + void *tmp = dst[size - 1]; + dst[size - 1] = dst[size - 2]; + dst[size - 2] = tmp; + } + + return 2; + } + + if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) + curr++; + + return curr - start; + } else { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) + curr++; + + /* reverse in-place */ + reverse_elements(dst, start, curr - 1); + return curr - start; + } +} + +static size_t compute_minrun(size_t n) +{ + int r = 0; + while (n >= 64) { + r |= n & 1; + n >>= 1; + } + return n + r; +} + +static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) +{ + if (stack_curr < 2) + return 1; + + else if (stack_curr == 2) { + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + return (A > B); + } else { + const ssize_t A = stack[stack_curr - 3].length; + const ssize_t B = stack[stack_curr - 2].length; + const ssize_t C = stack[stack_curr - 1].length; + return !((A <= B + C) || (B <= C)); + } +} + +static int resize(struct tsort_store *store, size_t new_size) +{ + if (store->alloc < new_size) { + void **tempstore; + + tempstore = git__reallocarray(store->storage, new_size, sizeof(void *)); + + /** + * Do not propagate on OOM; this will abort the sort and + * leave the array unsorted, but no error code will be + * raised + */ + if (tempstore == NULL) + return -1; + + store->storage = tempstore; + store->alloc = new_size; + } + + return 0; +} + +static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) +{ + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + const ssize_t curr = stack[stack_curr - 2].start; + + void **storage; + ssize_t i, j, k; + + if (resize(store, MIN(A, B)) < 0) + return; + + storage = store->storage; + + /* left merge */ + if (A < B) { + memcpy(storage, &dst[curr], A * sizeof(void *)); + i = 0; + j = curr + A; + + for (k = curr; k < curr + A + B; k++) { + if ((i < A) && (j < curr + A + B)) { + if (store->cmp(storage[i], dst[j], store->payload) <= 0) + dst[k] = storage[i++]; + else + dst[k] = dst[j++]; + } else if (i < A) { + dst[k] = storage[i++]; + } else + dst[k] = dst[j++]; + } + } else { + memcpy(storage, &dst[curr + A], B * sizeof(void *)); + i = B - 1; + j = curr + A - 1; + + for (k = curr + A + B - 1; k >= curr; k--) { + if ((i >= 0) && (j >= curr)) { + if (store->cmp(dst[j], storage[i], store->payload) > 0) + dst[k] = dst[j--]; + else + dst[k] = storage[i--]; + } else if (i >= 0) + dst[k] = storage[i--]; + else + dst[k] = dst[j--]; + } + } +} + +static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) +{ + ssize_t A, B, C; + + while (1) { + /* if the stack only has one thing on it, we are done with the collapse */ + if (stack_curr <= 1) + break; + + /* if this is the last merge, just do it */ + if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + + /* check if the invariant is off for a stack of 2 elements */ + else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + else if (stack_curr == 2) + break; + + A = stack[stack_curr - 3].length; + B = stack[stack_curr - 2].length; + C = stack[stack_curr - 1].length; + + /* check first invariant */ + if (A <= B + C) { + if (A < C) { + merge(dst, stack, stack_curr - 1, store); + stack[stack_curr - 3].length += stack[stack_curr - 2].length; + stack[stack_curr - 2] = stack[stack_curr - 1]; + stack_curr--; + } else { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } + } else if (B <= C) { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } else + break; + } + + return stack_curr; +} + +#define PUSH_NEXT() do {\ + len = count_run(dst, curr, size, store);\ + run = minrun;\ + if (run > (ssize_t)size - curr) run = size - curr;\ + if (run > len) {\ + bisort(&dst[curr], len, run, cmp, payload);\ + len = run;\ + }\ + run_stack[stack_curr].start = curr;\ + run_stack[stack_curr++].length = len;\ + curr += len;\ + if (curr == (ssize_t)size) {\ + /* finish up */ \ + while (stack_curr > 1) { \ + merge(dst, run_stack, stack_curr, store); \ + run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ + stack_curr--; \ + } \ + if (store->storage != NULL) {\ + git__free(store->storage);\ + store->storage = NULL;\ + }\ + return;\ + }\ +}\ +while (0) + +void git__tsort_r( + void **dst, size_t size, git__sort_r_cmp cmp, void *payload) +{ + struct tsort_store _store, *store = &_store; + struct tsort_run run_stack[128]; + + ssize_t stack_curr = 0; + ssize_t len, run; + ssize_t curr = 0; + ssize_t minrun; + + if (size < 64) { + bisort(dst, 1, size, cmp, payload); + return; + } + + /* compute the minimum run length */ + minrun = (ssize_t)compute_minrun(size); + + /* temporary storage for merges */ + store->alloc = 0; + store->storage = NULL; + store->cmp = cmp; + store->payload = payload; + + PUSH_NEXT(); + PUSH_NEXT(); + PUSH_NEXT(); + + while (1) { + if (!check_invariant(run_stack, stack_curr)) { + stack_curr = collapse(dst, run_stack, stack_curr, store, size); + continue; + } + + PUSH_NEXT(); + } +} + +static int tsort_r_cmp(const void *a, const void *b, void *payload) +{ + return ((git__tsort_cmp)payload)(a, b); +} + +void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) +{ + git__tsort_r(dst, size, tsort_r_cmp, cmp); +} diff --git a/src/util/unix/map.c b/src/util/unix/map.c new file mode 100644 index 000000000..933077689 --- /dev/null +++ b/src/util/unix/map.c @@ -0,0 +1,76 @@ +/* + * 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_util.h" + +#if !defined(GIT_WIN32) && !defined(NO_MMAP) + +#include "map.h" +#include +#include +#include + +int git__page_size(size_t *page_size) +{ + long sc_page_size = sysconf(_SC_PAGE_SIZE); + if (sc_page_size < 0) { + git_error_set(GIT_ERROR_OS, "can't determine system page size"); + return -1; + } + *page_size = (size_t) sc_page_size; + return 0; +} + +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, off64_t offset) +{ + int mprot = PROT_READ; + int mflag = 0; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + out->data = NULL; + out->len = 0; + + if (prot & GIT_PROT_WRITE) + mprot |= PROT_WRITE; + + if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) + mflag = MAP_SHARED; + else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) + mflag = MAP_PRIVATE; + else + mflag = MAP_SHARED; + + out->data = mmap(NULL, len, mprot, mflag, fd, offset); + + if (!out->data || out->data == MAP_FAILED) { + git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data"); + return -1; + } + + out->len = len; + + return 0; +} + +int p_munmap(git_map *map) +{ + GIT_ASSERT_ARG(map); + munmap(map->data, map->len); + map->data = NULL; + map->len = 0; + + return 0; +} + +#endif + diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h new file mode 100644 index 000000000..778477e8e --- /dev/null +++ b/src/util/unix/posix.h @@ -0,0 +1,104 @@ +/* + * 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_unix_posix_h__ +#define INCLUDE_unix_posix_h__ + +#include "git2_util.h" + +#include +#include +#include +#include +#include + +typedef int GIT_SOCKET; +#define INVALID_SOCKET -1 + +#define p_lseek(f,n,w) lseek(f, n, w) +#define p_fstat(f,b) fstat(f, b) +#define p_lstat(p,b) lstat(p,b) +#define p_stat(p,b) stat(p, b) + +#if defined(GIT_USE_STAT_MTIMESPEC) +# define st_atime_nsec st_atimespec.tv_nsec +# define st_mtime_nsec st_mtimespec.tv_nsec +# define st_ctime_nsec st_ctimespec.tv_nsec +#elif defined(GIT_USE_STAT_MTIM) +# 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_NSEC) +# error GIT_USE_NSEC defined but unknown struct stat nanosecond type +#endif + +#define p_utimes(f, t) utimes(f, t) + +#define p_readlink(a, b, c) readlink(a, b, c) +#define p_symlink(o,n) symlink(o, n) +#define p_link(o,n) link(o, n) +#define p_unlink(p) unlink(p) +#define p_mkdir(p,m) mkdir(p, m) +extern char *p_realpath(const char *, char *); + +GIT_INLINE(int) p_fsync(int fd) +{ + p_fsync__cnt++; + return fsync(fd); +} + +#define p_recv(s,b,l,f) recv(s,b,l,f) +#define p_send(s,b,l,f) send(s,b,l,f) +#define p_inet_pton(a, b, c) inet_pton(a, b, c) + +#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 snprintf +#define p_chdir(p) chdir(p) +#define p_rmdir(p) rmdir(p) +#define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) + +/* + * Pre-Android 5 did not implement a virtual filesystem atop FAT + * partitions for Unix permissions, which causes chmod to fail. However, + * Unix permissions have no effect on Android anyway as file permissions + * are not actually managed this way, so treating it as a no-op across + * all Android is safe. + */ +#ifdef __ANDROID__ +# define p_chmod(p,m) 0 +#else +# define p_chmod(p,m) chmod(p, m) +#endif + +/* see win32/posix.h for explanation about why this exists */ +#define p_lstat_posixly(p,b) lstat(p,b) + +#define p_localtime_r(c, r) localtime_r(c, r) +#define p_gmtime_r(c, r) gmtime_r(c, r) + +#define p_timeval timeval + +#ifdef GIT_USE_FUTIMENS +GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) +{ + struct timespec s[2]; + s[0].tv_sec = t[0].tv_sec; + s[0].tv_nsec = t[0].tv_usec * 1000; + s[1].tv_sec = t[1].tv_sec; + s[1].tv_nsec = t[1].tv_usec * 1000; + return futimens(f, s); +} +#else +# define p_futimes futimes +#endif + +#define p_pread(f, d, s, o) pread(f, d, s, o) +#define p_pwrite(f, d, s, o) pwrite(f, d, s, o) + +#endif diff --git a/src/util/unix/pthread.h b/src/util/unix/pthread.h new file mode 100644 index 000000000..55f4ae227 --- /dev/null +++ b/src/util/unix/pthread.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_unix_pthread_h__ +#define INCLUDE_unix_pthread_h__ + +typedef struct { + pthread_t thread; +} git_thread; + +GIT_INLINE(int) git_threads_global_init(void) { return 0; } + +#define git_thread_create(git_thread_ptr, start_routine, arg) \ + pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) +#define git_thread_join(git_thread_ptr, status) \ + pthread_join((git_thread_ptr)->thread, status) +#define git_thread_currentid() ((size_t)(pthread_self())) +#define git_thread_exit(retval) pthread_exit(retval) + +/* Git Mutex */ +#define git_mutex pthread_mutex_t +#define git_mutex_init(a) pthread_mutex_init(a, NULL) +#define git_mutex_lock(a) pthread_mutex_lock(a) +#define git_mutex_unlock(a) pthread_mutex_unlock(a) +#define git_mutex_free(a) pthread_mutex_destroy(a) + +/* Git condition vars */ +#define git_cond pthread_cond_t +#define git_cond_init(c) pthread_cond_init(c, NULL) +#define git_cond_free(c) pthread_cond_destroy(c) +#define git_cond_wait(c, l) pthread_cond_wait(c, l) +#define git_cond_signal(c) pthread_cond_signal(c) +#define git_cond_broadcast(c) pthread_cond_broadcast(c) + +/* Pthread (-ish) rwlock + * + * This differs from normal pthreads rwlocks in two ways: + * 1. Separate APIs for releasing read locks and write locks (as + * opposed to the pure POSIX API which only has one unlock fn) + * 2. You should not use recursive read locks (i.e. grabbing a read + * lock in a thread that already holds a read lock) because the + * Windows implementation doesn't support it + */ +#define git_rwlock pthread_rwlock_t +#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) +#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) +#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) +#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_free(a) pthread_rwlock_destroy(a) +#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER + +#endif diff --git a/src/util/unix/realpath.c b/src/util/unix/realpath.c new file mode 100644 index 000000000..9e31a63b9 --- /dev/null +++ b/src/util/unix/realpath.c @@ -0,0 +1,32 @@ +/* + * 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_util.h" + +#ifndef GIT_WIN32 + +#include +#include +#include +#include + +char *p_realpath(const char *pathname, char *resolved) +{ + char *ret; + if ((ret = realpath(pathname, resolved)) == NULL) + return NULL; + +#ifdef __OpenBSD__ + /* The OpenBSD realpath function behaves differently, + * figure out if the file exists */ + if (access(ret, F_OK) < 0) + ret = NULL; +#endif + return ret; +} + +#endif diff --git a/src/util/utf8.c b/src/util/utf8.c new file mode 100644 index 000000000..c566fdf20 --- /dev/null +++ b/src/util/utf8.c @@ -0,0 +1,150 @@ +/* + * 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 "utf8.h" + +#include "git2_util.h" + +/* + * git_utf8_iterate is taken from the utf8proc project, + * http://www.public-software-group.org/utf8proc + * + * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the ""Software""), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * 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. + */ + +static const uint8_t utf8proc_utf8class[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int utf8_charlen(const uint8_t *str, size_t str_len) +{ + uint8_t length; + size_t i; + + length = utf8proc_utf8class[str[0]]; + if (!length) + return -1; + + if (str_len > 0 && length > str_len) + return -1; + + for (i = 1; i < length; i++) { + if ((str[i] & 0xC0) != 0x80) + return -1; + } + + return (int)length; +} + +int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + uint32_t uc = 0; + int length; + + *out = 0; + + if ((length = utf8_charlen(str, str_len)) < 0) + return -1; + + switch (length) { + case 1: + uc = str[0]; + break; + case 2: + uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); + if (uc < 0x80) uc = -1; + break; + case 3: + uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + + (str[2] & 0x3F); + if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || + (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; + break; + case 4: + uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) + + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); + if (uc < 0x10000 || uc >= 0x110000) uc = -1; + break; + default: + return -1; + } + + if ((uc & 0xFFFF) >= 0xFFFE) + return -1; + + *out = uc; + return length; +} + +size_t git_utf8_char_length(const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + size_t offset = 0, count = 0; + + while (offset < str_len) { + int length = utf8_charlen(str + offset, str_len - offset); + + if (length < 0) + length = 1; + + offset += length; + count++; + } + + return count; +} + +size_t git_utf8_valid_buf_length(const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + size_t offset = 0; + + while (offset < str_len) { + int length = utf8_charlen(str + offset, str_len - offset); + + if (length < 0) + break; + + offset += length; + } + + return offset; +} diff --git a/src/util/utf8.h b/src/util/utf8.h new file mode 100644 index 000000000..753ab07e2 --- /dev/null +++ b/src/util/utf8.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_utf8_h__ +#define INCLUDE_utf8_h__ + +#include "git2_util.h" + +/* + * Iterate through an UTF-8 string, yielding one codepoint at a time. + * + * @param out pointer where to store the current codepoint + * @param str current position in the string + * @param str_len size left in the string + * @return length in bytes of the read codepoint; -1 if the codepoint was invalid + */ +extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len); + +/** + * Returns the number of characters in the given string. + * + * This function will count invalid codepoints; if any given byte is + * not part of a valid UTF-8 codepoint, then it will be counted toward + * the length in characters. + * + * In other words: + * 0x24 (U+0024 "$") has length 1 + * 0xc2 0xa2 (U+00A2 "¢") has length 1 + * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2 + * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1 + * 0x24 0xc0 0xc1 0x34 (U+0024 "4) has length 4 + * + * @param str string to scan + * @param str_len size of the string + * @return length in characters of the string + */ +extern size_t git_utf8_char_length(const char *str, size_t str_len); + +/** + * Iterate through an UTF-8 string and stops after finding any invalid UTF-8 + * codepoints. + * + * @param str string to scan + * @param str_len size of the string + * @return length in bytes of the string that contains valid data + */ +extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len); + +#endif diff --git a/src/util/util.c b/src/util/util.c new file mode 100644 index 000000000..aee95fddf --- /dev/null +++ b/src/util/util.c @@ -0,0 +1,819 @@ +/* + * 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 "util.h" + +#include "git2_util.h" + +#ifdef GIT_WIN32 +# include "win32/utf-conv.h" +# include "win32/w32_buffer.h" + +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include + +# ifdef GIT_QSORT_S +# include +# endif +#endif + +#ifdef _MSC_VER +# include +#endif + +#if defined(hpux) || defined(__hpux) || defined(_hpux) +# include +#endif + +int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) +{ + const char *p; + int64_t n, nn, v; + int c, ovfl, neg, ndig; + + p = nptr; + neg = 0; + n = 0; + ndig = 0; + ovfl = 0; + + /* + * White space + */ + while (nptr_len && git__isspace(*p)) + p++, nptr_len--; + + if (!nptr_len) + goto Return; + + /* + * Sign + */ + if (*p == '-' || *p == '+') { + if (*p == '-') + neg = 1; + p++; + nptr_len--; + } + + if (!nptr_len) + goto Return; + + /* + * Automatically detect the base if none was given to us. + * Right now, we assume that a number starting with '0x' + * is hexadecimal and a number starting with '0' is + * octal. + */ + if (base == 0) { + if (*p != '0') + base = 10; + else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X')) + base = 16; + else + base = 8; + } + + if (base < 0 || 36 < base) + goto Return; + + /* + * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no + * need to do the same for '0'-prefixed octal numbers as a + * leading '0' does not have any impact. Also, if we skip a + * leading '0' in such a string, then we may end up with no + * digits left and produce an error later on which isn't one. + */ + if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + nptr_len -= 2; + } + + /* + * Non-empty sequence of digits + */ + for (; nptr_len > 0; p++,ndig++,nptr_len--) { + c = *p; + v = base; + if ('0'<=c && c<='9') + v = c - '0'; + else if ('a'<=c && c<='z') + v = c - 'a' + 10; + else if ('A'<=c && c<='Z') + v = c - 'A' + 10; + if (v >= base) + break; + v = neg ? -v : v; + if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) { + ovfl = 1; + /* Keep on iterating until the end of this number */ + continue; + } + } + +Return: + if (ndig == 0) { + git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number"); + return -1; + } + + if (endptr) + *endptr = p; + + if (ovfl) { + git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error"); + return -1; + } + + *result = n; + return 0; +} + +int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) +{ + const char *tmp_endptr; + int32_t tmp_int; + int64_t tmp_long; + int error; + + if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) + return error; + + tmp_int = tmp_long & 0xFFFFFFFF; + if (tmp_int != tmp_long) { + int len = (int)(tmp_endptr - nptr); + git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); + return -1; + } + + *result = tmp_int; + if (endptr) + *endptr = tmp_endptr; + + return error; +} + +int git__strcasecmp(const char *a, const char *b) +{ + while (*a && *b && git__tolower(*a) == git__tolower(*b)) + ++a, ++b; + return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b)); +} + +int git__strcasesort_cmp(const char *a, const char *b) +{ + int cmp = 0; + + while (*a && *b) { + if (*a != *b) { + if (git__tolower(*a) != git__tolower(*b)) + break; + /* use case in sort order even if not in equivalence */ + if (!cmp) + cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); + } + + ++a, ++b; + } + + if (*a || *b) + return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b); + + return cmp; +} + +int git__strncasecmp(const char *a, const char *b, size_t sz) +{ + int al, bl; + + do { + al = (unsigned char)git__tolower(*a); + bl = (unsigned char)git__tolower(*b); + ++a, ++b; + } while (--sz && al && al == bl); + + return al - bl; +} + +void git__strntolower(char *str, size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) { + str[i] = (char)git__tolower(str[i]); + } +} + +void git__strtolower(char *str) +{ + git__strntolower(str, strlen(str)); +} + +GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) +{ + int s, p; + + while (str_n--) { + s = (unsigned char)*str++; + p = (unsigned char)*prefix++; + + if (icase) { + s = git__tolower(s); + p = git__tolower(p); + } + + if (!p) + return 0; + + if (s != p) + return s - p; + } + + return (0 - *prefix); +} + +int git__prefixcmp(const char *str, const char *prefix) +{ + 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) +{ + return prefixcmp(str, str_n, prefix, false); +} + +int git__prefixcmp_icase(const char *str, const char *prefix) +{ + return prefixcmp(str, SIZE_MAX, prefix, true); +} + +int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, true); +} + +int git__suffixcmp(const char *str, const char *suffix) +{ + size_t a = strlen(str); + size_t b = strlen(suffix); + if (a < b) + return -1; + return strcmp(str + (a - b), suffix); +} + +char *git__strtok(char **end, const char *sep) +{ + char *ptr = *end; + + while (*ptr && strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + char *start = ptr; + *end = start + 1; + + while (**end && !strchr(sep, **end)) + ++*end; + + if (**end) { + **end = '\0'; + ++*end; + } + + return start; + } + + return NULL; +} + +/* Similar to strtok, but does not collapse repeated tokens. */ +char *git__strsep(char **end, const char *sep) +{ + char *start = *end, *ptr = *end; + + while (*ptr && !strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + *end = ptr + 1; + *ptr = '\0'; + + return start; + } + + return NULL; +} + +size_t git__linenlen(const char *buffer, size_t buffer_len) +{ + char *nl = memchr(buffer, '\n', buffer_len); + return nl ? (size_t)(nl - buffer) + 1 : buffer_len; +} + +/* + * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ + */ +const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) +{ + const char *h, *n; + size_t j, k, l; + + if (needlelen > haystacklen || !haystacklen || !needlelen) + return NULL; + + h = (const char *) haystack, + n = (const char *) needle; + + if (needlelen == 1) + return memchr(haystack, *n, haystacklen); + + if (n[0] == n[1]) { + k = 2; + l = 1; + } else { + k = 1; + l = 2; + } + + j = 0; + while (j <= haystacklen - needlelen) { + if (n[1] != h[j + 1]) { + j += k; + } else { + if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && + n[0] == h[j]) + return h + j; + j += l; + } + } + + return NULL; +} + +void git__hexdump(const char *buffer, size_t len) +{ + static const size_t LINE_WIDTH = 16; + + size_t line_count, last_line, i, j; + const char *line; + + line_count = (len / LINE_WIDTH); + last_line = (len % LINE_WIDTH); + + for (i = 0; i < line_count; ++i) { + printf("%08" PRIxZ " ", (i * LINE_WIDTH)); + + line = buffer + (i * LINE_WIDTH); + for (j = 0; j < LINE_WIDTH; ++j, ++line) { + printf("%02x ", (unsigned char)*line & 0xFF); + + if (j == (LINE_WIDTH / 2)) + printf(" "); + } + + printf(" |"); + + line = buffer + (i * LINE_WIDTH); + for (j = 0; j < LINE_WIDTH; ++j, ++line) + printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); + + printf("|\n"); + } + + if (last_line > 0) { + printf("%08" PRIxZ " ", (line_count * LINE_WIDTH)); + + line = buffer + (line_count * LINE_WIDTH); + for (j = 0; j < last_line; ++j, ++line) { + printf("%02x ", (unsigned char)*line & 0xFF); + + if (j == (LINE_WIDTH / 2)) + printf(" "); + } + + if (j < (LINE_WIDTH / 2)) + printf(" "); + for (j = 0; j < (LINE_WIDTH - last_line); ++j) + printf(" "); + + printf(" |"); + + line = buffer + (line_count * LINE_WIDTH); + for (j = 0; j < last_line; ++j, ++line) + printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); + + printf("|\n"); + } + + printf("\n"); +} + +#ifdef GIT_LEGACY_HASH +uint32_t git__hash(const void *key, int len, unsigned int seed) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + uint32_t h = seed ^ len; + + const unsigned char *data = (const unsigned char *)key; + + while(len >= 4) { + uint32_t k = *(uint32_t *)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + switch(len) { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} +#else +/* + Cross-platform version of Murmurhash3 + http://code.google.com/p/smhasher/wiki/MurmurHash3 + by Austin Appleby (aappleby@gmail.com) + + This code is on the public domain. +*/ +uint32_t git__hash(const void *key, int len, uint32_t seed) +{ + +#define MURMUR_BLOCK() {\ + k1 *= c1; \ + k1 = git__rotl(k1,11);\ + k1 *= c2;\ + h1 ^= k1;\ + h1 = h1*3 + 0x52dce729;\ + c1 = c1*5 + 0x7b7d159c;\ + c2 = c2*5 + 0x6bce6396;\ +} + + const uint8_t *data = (const uint8_t*)key; + const int nblocks = len / 4; + + const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); + const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); + + uint32_t h1 = 0x971e137b ^ seed; + uint32_t k1; + + uint32_t c1 = 0x95543787; + uint32_t c2 = 0x2ad7eb25; + + int i; + + for (i = -nblocks; i; i++) { + k1 = blocks[i]; + MURMUR_BLOCK(); + } + + k1 = 0; + + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + /* fall through */ + case 2: k1 ^= tail[1] << 8; + /* fall through */ + case 1: k1 ^= tail[0]; + MURMUR_BLOCK(); + } + + h1 ^= len; + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; +} +#endif + +/** + * A modified `bsearch` from the BSD glibc. + * + * Copyright (c) 1990 Regents of the University of California. + * All rights reserved. + * 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. [rescinded 22 July 1999] + * 4. 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. + */ +int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position) +{ + size_t lim; + int cmp = -1; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare)(key, *part); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *, const void *, void *), + void *payload, + size_t *position) +{ + size_t lim; + int cmp = -1; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare_r)(key, *part, payload); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +/** + * A strcmp wrapper + * + * We don't want direct pointers to the CRT on Windows, we may + * get stdcall conflicts. + */ +int git__strcmp_cb(const void *a, const void *b) +{ + return strcmp((const char *)a, (const char *)b); +} + +int git__strcasecmp_cb(const void *a, const void *b) +{ + return strcasecmp((const char *)a, (const char *)b); +} + +int git__parse_bool(int *out, const char *value) +{ + /* A missing value means true */ + if (value == NULL || + !strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return 0; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off") || + value[0] == '\0') { + *out = 0; + return 0; + } + + return -1; +} + +size_t git__unescape(char *str) +{ + char *scan, *pos = str; + + if (!str) + return 0; + + for (scan = str; *scan; pos++, scan++) { + if (*scan == '\\' && *(scan + 1) != '\0') + scan++; /* skip '\' but include next char */ + if (pos != scan) + *pos = *scan; + } + + if (pos != scan) { + *pos = '\0'; + } + + return (pos - str); +} + +#if defined(GIT_QSORT_S) || defined(GIT_QSORT_R_BSD) +typedef struct { + git__sort_r_cmp cmp; + void *payload; +} git__qsort_r_glue; + +static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp( + void *payload, const void *a, const void *b) +{ + git__qsort_r_glue *glue = payload; + return glue->cmp(a, b, glue->payload); +} +#endif + + +#if !defined(GIT_QSORT_R_BSD) && \ + !defined(GIT_QSORT_R_GNU) && \ + !defined(GIT_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) +{ +#if defined(GIT_QSORT_R_BSD) + git__qsort_r_glue glue = { cmp, payload }; + qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); +#elif defined(GIT_QSORT_R_GNU) + qsort_r(els, nel, elsize, cmp, payload); +#elif defined(GIT_QSORT_S) + git__qsort_r_glue glue = { cmp, payload }; + qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); +#else + insertsort(els, nel, elsize, cmp, payload); +#endif +} + +#ifdef GIT_WIN32 +int git__getenv(git_str *out, const char *name) +{ + wchar_t *wide_name = NULL, *wide_value = NULL; + DWORD value_len; + int error = -1; + + git_str_clear(out); + + if (git__utf8_to_16_alloc(&wide_name, name) < 0) + return -1; + + if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { + wide_value = git__malloc(value_len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(wide_value); + + value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); + } + + if (value_len) + error = git_str_put_w(out, wide_value, value_len); + else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND) + error = GIT_ENOTFOUND; + else + git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name); + + git__free(wide_name); + git__free(wide_value); + return error; +} +#else +int git__getenv(git_str *out, const char *name) +{ + const char *val = getenv(name); + + git_str_clear(out); + + if (!val) + return GIT_ENOTFOUND; + + return git_str_puts(out, val); +} +#endif + +/* + * By doing this in two steps we can at least get + * the function to be somewhat coherent, even + * with this disgusting nest of #ifdefs. + */ +#ifndef _SC_NPROCESSORS_ONLN +# ifdef _SC_NPROC_ONLN +# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN +# elif defined _SC_CRAY_NCPU +# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU +# endif +#endif + +int git__online_cpus(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + long ncpus; +#endif + +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + + if ((int)info.dwNumberOfProcessors > 0) + return (int)info.dwNumberOfProcessors; +#elif defined(hpux) || defined(__hpux) || defined(_hpux) + struct pst_dynamic psd; + + if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) + return (int)psd.psd_proc_cnt; +#endif + +#ifdef _SC_NPROCESSORS_ONLN + if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) + return (int)ncpus; +#endif + + return 1; +} diff --git a/src/util/util.h b/src/util/util.h new file mode 100644 index 000000000..8d6d1d6b6 --- /dev/null +++ b/src/util/util.h @@ -0,0 +1,387 @@ +/* + * 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_util_h__ +#define INCLUDE_util_h__ + +#ifndef GIT_WIN32 +# include +#endif + +#include "str.h" +#include "git2_util.h" +#include "strnlen.h" +#include "thread.h" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef max +# 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 + +/** + * Return the length of a constant string. + * We are aware that `strlen` performs the same task and is usually + * optimized away by the compiler, whilst being safer because it returns + * valid values when passed a pointer instead of a constant string; however + * this macro will transparently work with wide-char and single-char strings. + */ +#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) + +#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ + ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) + +#define CASESELECT(IGNORE_CASE, ICASE, CASE) \ + ((IGNORE_CASE) ? (ICASE) : (CASE)) + +extern int git__prefixcmp(const char *str, const char *prefix); +extern int git__prefixcmp_icase(const char *str, const char *prefix); +extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); +extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); +extern int git__suffixcmp(const char *str, const char *suffix); + +GIT_INLINE(int) git__signum(int val) +{ + return ((val > 0) - (val < 0)); +} + +extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); +extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); + + +extern void git__hexdump(const char *buffer, size_t n); +extern uint32_t git__hash(const void *key, int len, uint32_t seed); + +/* 32-bit cross-platform rotl */ +#ifdef _MSC_VER /* use built-in method in MSVC */ +# define git__rotl(v, s) (uint32_t)_rotl(v, s) +#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */ +# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) +#endif + +extern char *git__strtok(char **end, const char *sep); +extern char *git__strsep(char **end, const char *sep); + +extern void git__strntolower(char *str, size_t len); +extern void git__strtolower(char *str); + +#ifdef GIT_WIN32 +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} +#else +# define git__tolower(a) tolower(a) +#endif + +extern size_t git__linenlen(const char *buffer, size_t buffer_len); + +GIT_INLINE(const char *) git__next_line(const char *s) +{ + while (*s && *s != '\n') s++; + while (*s == '\n' || *s == '\r') s++; + return s; +} + +GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) +{ + const unsigned char *cp; + + if (n != 0) { + cp = (unsigned char *)s + n; + do { + if (*(--cp) == (unsigned char)c) + return cp; + } while (--n != 0); + } + + return NULL; +} + +extern const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + +typedef int (*git__tsort_cmp)(const void *a, const void *b); + +extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); + +typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); + +extern void git__tsort_r( + void **dst, size_t size, git__sort_r_cmp cmp, void *payload); + +extern void git__qsort_r( + void *els, size_t nel, size_t elsize, 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. + * @return 0 if found; GIT_ENOTFOUND if not found + */ +extern int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *key, const void *element), + size_t *position); + +extern int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *key, const void *element, void *payload), + 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__strcasecmp(const char *a, const char *b); +extern int git__strncasecmp(const char *a, const char *b, size_t sz); + +extern int git__strcasesort_cmp(const char *a, const char *b); + +/* + * Compare some NUL-terminated `a` to a possibly non-NUL terminated + * `b` of length `b_len`; like `strncmp` but ensuring that + * `strlen(a) == b_len` as well. + */ +GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len) +{ + int cmp = strncmp(a, b, b_len); + return cmp ? cmp : (int)a[b_len]; +} + +typedef struct { + git_atomic32 refcount; + void *owner; +} git_refcount; + +typedef void (*git_refcount_freeptr)(void *r); + +#define GIT_REFCOUNT_INC(r) { \ + git_atomic32_inc(&(r)->rc.refcount); \ +} + +#define GIT_REFCOUNT_DEC(_r, do_free) { \ + git_refcount *r = &(_r)->rc; \ + int val = git_atomic32_dec(&r->refcount); \ + if (val <= 0 && r->owner == NULL) { do_free(_r); } \ +} + +#define GIT_REFCOUNT_OWN(r, o) { \ + (void)git_atomic_swap((r)->rc.owner, o); \ +} + +#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner) + +#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount) + + +static signed char from_hex[] = { +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ +}; + +GIT_INLINE(int) git__fromhex(char h) +{ + return from_hex[(unsigned char) h]; +} + +GIT_INLINE(int) git__ishex(const char *str) +{ + unsigned i; + for (i=0; str[i] != '\0'; i++) + if (git__fromhex(str[i]) < 0) + return 0; + return 1; +} + +GIT_INLINE(size_t) git__size_t_bitmask(size_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return v; +} + +GIT_INLINE(size_t) git__size_t_powerof2(size_t v) +{ + return git__size_t_bitmask(v) + 1; +} + +GIT_INLINE(bool) git__isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__isspace_nonlf(int c) +{ + return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__iswildcard(int c) +{ + return (c == '*' || c == '?' || c == '['); +} + +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +/* + * Parse a string value as a boolean, just like Core Git does. + * + * Valid values for true are: 'true', 'yes', 'on' + * Valid values for false are: 'false', 'no', 'off' + */ +extern int git__parse_bool(int *out, const char *value); + +/* + * Unescapes a string in-place. + * + * Edge cases behavior: + * - "jackie\" -> "jacky\" + * - "chan\\" -> "chan\" + */ +extern size_t git__unescape(char *str); + +/* + * Safely zero-out memory, making sure that the compiler + * doesn't optimize away the operation. + */ +GIT_INLINE(void) git__memzero(void *data, size_t size) +{ +#ifdef _MSC_VER + SecureZeroMemory((PVOID)data, size); +#else + volatile uint8_t *scan = (volatile uint8_t *)data; + + while (size--) + *scan++ = 0x0; +#endif +} + +#ifdef GIT_WIN32 + +GIT_INLINE(double) git__timer(void) +{ + /* GetTickCount64 returns the number of milliseconds that have + * elapsed since the system was started. */ + return (double) GetTickCount64() / (double) 1000; +} + +#elif __APPLE__ + +#include + +GIT_INLINE(double) git__timer(void) +{ + uint64_t time = mach_absolute_time(); + static double scaling_factor = 0; + + if (scaling_factor == 0) { + mach_timebase_info_data_t info; + (void)mach_timebase_info(&info); + scaling_factor = (double)info.numer / (double)info.denom; + } + + return (double)time * scaling_factor / 1.0E9; +} + +#elif defined(__amigaos4__) + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct TimeVal tv; + ITimer->GetUpTime(&tv); + return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6; +} + +#else + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct timeval tv; + +#ifdef CLOCK_MONOTONIC + struct timespec tp; + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; +#endif + + /* Fall back to using gettimeofday */ + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; +} + +#endif + +extern int git__getenv(git_str *out, const char *name); + +extern int git__online_cpus(void); + +GIT_INLINE(int) git__noop(void) { return 0; } + +#include "alloc.h" + +#endif diff --git a/src/util/varint.c b/src/util/varint.c new file mode 100644 index 000000000..9ffc1d744 --- /dev/null +++ b/src/util/varint.c @@ -0,0 +1,43 @@ +/* + * 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 "varint.h" + +uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) +{ + const unsigned char *buf = bufp; + unsigned char c = *buf++; + uintmax_t val = c & 127; + while (c & 128) { + val += 1; + if (!val || MSB(val, 7)) { + /* This is not a valid varint_len, so it signals + the error */ + *varint_len = 0; + return 0; /* overflow */ + } + c = *buf++; + val = (val << 7) + (c & 127); + } + *varint_len = buf - bufp; + return val; +} + +int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) +{ + unsigned char varint[16]; + unsigned pos = sizeof(varint) - 1; + varint[pos] = value & 127; + while (value >>= 7) + varint[--pos] = 128 | (--value & 127); + if (buf) { + if (bufsize < (sizeof(varint) - pos)) + return -1; + memcpy(buf, varint + pos, sizeof(varint) - pos); + } + return sizeof(varint) - pos; +} diff --git a/src/util/varint.h b/src/util/varint.h new file mode 100644 index 000000000..79b8f5548 --- /dev/null +++ b/src/util/varint.h @@ -0,0 +1,17 @@ +/* + * 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_varint_h__ +#define INCLUDE_varint_h__ + +#include "git2_util.h" + +#include + +extern int git_encode_varint(unsigned char *, size_t, uintmax_t); +extern uintmax_t git_decode_varint(const unsigned char *, size_t *); + +#endif diff --git a/src/util/vector.c b/src/util/vector.c new file mode 100644 index 000000000..4a4bc8c0e --- /dev/null +++ b/src/util/vector.c @@ -0,0 +1,431 @@ +/* + * 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 "vector.h" + +#include "integer.h" + +/* In elements, not bytes */ +#define MIN_ALLOCSIZE 8 + +GIT_INLINE(size_t) compute_new_size(git_vector *v) +{ + size_t new_size = v->_alloc_size; + + /* Use a resize factor of 1.5, which is quick to compute using integer + * instructions and less than the golden ratio (1.618...) */ + if (new_size < MIN_ALLOCSIZE) + new_size = MIN_ALLOCSIZE; + else if (new_size <= (SIZE_MAX / 3) * 2) + new_size += new_size / 2; + else + new_size = SIZE_MAX; + + return new_size; +} + +GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) +{ + void *new_contents; + + if (new_size == 0) + return 0; + + new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(new_contents); + + v->_alloc_size = new_size; + v->contents = new_contents; + + return 0; +} + +int git_vector_size_hint(git_vector *v, size_t size_hint) +{ + if (v->_alloc_size >= size_hint) + return 0; + return resize_vector(v, size_hint); +} + +int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) +{ + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(src); + + v->_alloc_size = 0; + v->contents = NULL; + v->_cmp = cmp ? cmp : src->_cmp; + v->length = src->length; + v->flags = src->flags; + if (cmp != src->_cmp) + git_vector_set_sorted(v, 0); + + if (src->length) { + size_t bytes; + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); + v->contents = git__malloc(bytes); + GIT_ERROR_CHECK_ALLOC(v->contents); + v->_alloc_size = src->length; + memcpy(v->contents, src->contents, bytes); + } + + return 0; +} + +void git_vector_free(git_vector *v) +{ + if (!v) + return; + + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; +} + +void git_vector_free_deep(git_vector *v) +{ + size_t i; + + if (!v) + return; + + for (i = 0; i < v->length; ++i) { + git__free(v->contents[i]); + v->contents[i] = NULL; + } + + git_vector_free(v); +} + +int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) +{ + GIT_ASSERT_ARG(v); + + v->_alloc_size = 0; + v->_cmp = cmp; + v->length = 0; + v->flags = GIT_VECTOR_SORTED; + v->contents = NULL; + + return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); +} + +void **git_vector_detach(size_t *size, size_t *asize, git_vector *v) +{ + void **data = v->contents; + + if (size) + *size = v->length; + if (asize) + *asize = v->_alloc_size; + + v->_alloc_size = 0; + v->length = 0; + v->contents = NULL; + + return data; +} + +int git_vector_insert(git_vector *v, void *element) +{ + GIT_ASSERT_ARG(v); + + if (v->length >= v->_alloc_size && + resize_vector(v, compute_new_size(v)) < 0) + return -1; + + v->contents[v->length++] = element; + + git_vector_set_sorted(v, v->length <= 1); + + return 0; +} + +int git_vector_insert_sorted( + git_vector *v, void *element, int (*on_dup)(void **old, void *new)) +{ + int result; + size_t pos; + + GIT_ASSERT_ARG(v); + GIT_ASSERT(v->_cmp); + + if (!git_vector_is_sorted(v)) + git_vector_sort(v); + + if (v->length >= v->_alloc_size && + resize_vector(v, compute_new_size(v)) < 0) + return -1; + + /* If we find the element and have a duplicate handler callback, + * invoke it. If it returns non-zero, then cancel insert, otherwise + * proceed with normal insert. + */ + if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) && + on_dup && (result = on_dup(&v->contents[pos], element)) < 0) + return result; + + /* shift elements to the right */ + if (pos < v->length) + memmove(v->contents + pos + 1, v->contents + pos, + (v->length - pos) * sizeof(void *)); + + v->contents[pos] = element; + v->length++; + + return 0; +} + +void git_vector_sort(git_vector *v) +{ + if (git_vector_is_sorted(v) || !v->_cmp) + return; + + if (v->length > 1) + git__tsort(v->contents, v->length, v->_cmp); + git_vector_set_sorted(v, 1); +} + +int git_vector_bsearch2( + size_t *at_pos, + git_vector *v, + git_vector_cmp key_lookup, + const void *key) +{ + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(key); + GIT_ASSERT(key_lookup); + + /* need comparison function to sort the vector */ + if (!v->_cmp) + return -1; + + git_vector_sort(v); + + return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); +} + +int git_vector_search2( + size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) +{ + size_t i; + + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(key); + GIT_ASSERT(key_lookup); + + for (i = 0; i < v->length; ++i) { + if (key_lookup(key, v->contents[i]) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } + + return GIT_ENOTFOUND; +} + +static int strict_comparison(const void *a, const void *b) +{ + return (a == b) ? 0 : -1; +} + +int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) +{ + return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); +} + +int git_vector_remove(git_vector *v, size_t idx) +{ + size_t shift_count; + + GIT_ASSERT_ARG(v); + + if (idx >= v->length) + return GIT_ENOTFOUND; + + shift_count = v->length - idx - 1; + + if (shift_count) + memmove(&v->contents[idx], &v->contents[idx + 1], + shift_count * sizeof(void *)); + + v->length--; + return 0; +} + +void git_vector_pop(git_vector *v) +{ + if (v->length > 0) + v->length--; +} + +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) +{ + git_vector_cmp cmp; + size_t i, j; + + if (v->length <= 1) + return; + + git_vector_sort(v); + cmp = v->_cmp ? v->_cmp : strict_comparison; + + for (i = 0, j = 1 ; j < v->length; ++j) + if (!cmp(v->contents[i], v->contents[j])) { + if (git_free_cb) + git_free_cb(v->contents[i]); + + v->contents[i] = v->contents[j]; + } else + v->contents[++i] = v->contents[j]; + + v->length -= j - i - 1; +} + +void git_vector_remove_matching( + git_vector *v, + int (*match)(const git_vector *v, size_t idx, void *payload), + void *payload) +{ + size_t i, j; + + for (i = 0, j = 0; j < v->length; ++j) { + v->contents[i] = v->contents[j]; + + if (!match(v, i, payload)) + i++; + } + + v->length = i; +} + +void git_vector_clear(git_vector *v) +{ + v->length = 0; + git_vector_set_sorted(v, 1); +} + +void git_vector_swap(git_vector *a, git_vector *b) +{ + git_vector t; + + if (a != b) { + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); + } +} + +int git_vector_resize_to(git_vector *v, size_t new_length) +{ + if (new_length > v->_alloc_size && + resize_vector(v, new_length) < 0) + return -1; + + if (new_length > v->length) + memset(&v->contents[v->length], 0, + sizeof(void *) * (new_length - v->length)); + + v->length = new_length; + + return 0; +} + +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) +{ + size_t new_length; + + GIT_ASSERT_ARG(insert_len > 0); + GIT_ASSERT_ARG(idx <= v->length); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); + + if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) + return -1; + + memmove(&v->contents[idx + insert_len], &v->contents[idx], + sizeof(void *) * (v->length - idx)); + memset(&v->contents[idx], 0, sizeof(void *) * insert_len); + + v->length = new_length; + return 0; +} + +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) +{ + size_t new_length = v->length - remove_len; + size_t end_idx = 0; + + GIT_ASSERT_ARG(remove_len > 0); + + if (git__add_sizet_overflow(&end_idx, idx, remove_len)) + GIT_ASSERT(0); + + GIT_ASSERT(end_idx <= v->length); + + if (end_idx < v->length) + memmove(&v->contents[idx], &v->contents[end_idx], + sizeof(void *) * (v->length - end_idx)); + + memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); + + v->length = new_length; + return 0; +} + +int git_vector_set(void **old, git_vector *v, size_t position, void *value) +{ + if (position + 1 > v->length) { + if (git_vector_resize_to(v, position + 1) < 0) + return -1; + } + + if (old != NULL) + *old = v->contents[position]; + + v->contents[position] = value; + + return 0; +} + +int git_vector_verify_sorted(const git_vector *v) +{ + size_t i; + + if (!git_vector_is_sorted(v)) + return -1; + + for (i = 1; i < v->length; ++i) { + if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0) + return -1; + } + + return 0; +} + +void git_vector_reverse(git_vector *v) +{ + size_t a, b; + + if (v->length == 0) + return; + + a = 0; + b = v->length - 1; + + while (a < b) { + void *tmp = v->contents[a]; + v->contents[a] = v->contents[b]; + v->contents[b] = tmp; + a++; + b--; + } +} diff --git a/src/util/vector.h b/src/util/vector.h new file mode 100644 index 000000000..e50cdfefc --- /dev/null +++ b/src/util/vector.h @@ -0,0 +1,128 @@ +/* + * 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_vector_h__ +#define INCLUDE_vector_h__ + +#include "git2_util.h" + +typedef int (*git_vector_cmp)(const void *, const void *); + +enum { + GIT_VECTOR_SORTED = (1u << 0), + GIT_VECTOR_FLAG_MAX = (1u << 1) +}; + +typedef struct git_vector { + size_t _alloc_size; + git_vector_cmp _cmp; + void **contents; + size_t length; + uint32_t flags; +} git_vector; + +#define GIT_VECTOR_INIT {0} + +GIT_WARN_UNUSED_RESULT int git_vector_init( + git_vector *v, size_t initial_size, git_vector_cmp cmp); +void git_vector_free(git_vector *v); +void git_vector_free_deep(git_vector *v); /* free each entry and self */ +void git_vector_clear(git_vector *v); +GIT_WARN_UNUSED_RESULT int git_vector_dup( + git_vector *v, const git_vector *src, git_vector_cmp cmp); +void git_vector_swap(git_vector *a, git_vector *b); +int git_vector_size_hint(git_vector *v, size_t size_hint); + +void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); + +void git_vector_sort(git_vector *v); + +/** Linear search for matching entry using internal comparison function */ +int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); + +/** Linear search for matching entry using explicit comparison function */ +int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); + +/** + * Binary search for matching entry using explicit comparison function that + * returns position where item would go if not found. + */ +int git_vector_bsearch2( + size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); + +/** Binary search for matching entry using internal comparison function */ +GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) +{ + return git_vector_bsearch2(at_pos, v, v->_cmp, key); +} + +GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) +{ + return (position < v->length) ? v->contents[position] : NULL; +} + +#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) + +GIT_INLINE(size_t) git_vector_length(const git_vector *v) +{ + return v->length; +} + +GIT_INLINE(void *) git_vector_last(const git_vector *v) +{ + return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; +} + +#define git_vector_foreach(v, iter, elem) \ + for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) + +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) + +int git_vector_insert(git_vector *v, void *element); +int git_vector_insert_sorted(git_vector *v, void *element, + int (*on_dup)(void **old, void *new)); +int git_vector_remove(git_vector *v, size_t idx); +void git_vector_pop(git_vector *v); +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); + +void git_vector_remove_matching( + git_vector *v, + int (*match)(const git_vector *v, size_t idx, void *payload), + void *payload); + +int git_vector_resize_to(git_vector *v, size_t new_length); +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); + +int git_vector_set(void **old, git_vector *v, size_t position, void *value); + +/** Check if vector is sorted */ +#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0) + +/** Directly set sorted state of vector */ +#define git_vector_set_sorted(V,S) do { \ + (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \ + ((V)->flags & ~GIT_VECTOR_SORTED); } while (0) + +/** Set the comparison function used for sorting the vector */ +GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) +{ + if (cmp != v->_cmp) { + v->_cmp = cmp; + git_vector_set_sorted(v, 0); + } +} + +/* Just use this in tests, not for realz. returns -1 if not sorted */ +int git_vector_verify_sorted(const git_vector *v); + +/** + * Reverse the vector in-place. + */ +void git_vector_reverse(git_vector *v); + +#endif diff --git a/src/util/wildmatch.c b/src/util/wildmatch.c new file mode 100644 index 000000000..a894e4841 --- /dev/null +++ b/src/util/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/util/wildmatch.h b/src/util/wildmatch.h new file mode 100644 index 000000000..f20640500 --- /dev/null +++ b/src/util/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 "git2_util.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/util/win32/dir.c b/src/util/win32/dir.c new file mode 100644 index 000000000..44052caf0 --- /dev/null +++ b/src/util/win32/dir.c @@ -0,0 +1,122 @@ +/* + * 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 "dir.h" + +#define GIT__WIN32_NO_WRAP_DIR +#include "posix.h" + +git__DIR *git__opendir(const char *dir) +{ + git_win32_path filter_w; + git__DIR *new = NULL; + size_t dirlen, alloclen; + + if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) + return NULL; + + dirlen = strlen(dir); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) || + GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) || + !(new = git__calloc(1, alloclen))) + return NULL; + + memcpy(new->dir, dir, dirlen); + + new->h = FindFirstFileW(filter_w, &new->f); + + if (new->h == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir); + git__free(new); + return NULL; + } + + new->first = 1; + return new; +} + +int git__readdir_ext( + git__DIR *d, + struct git__dirent *entry, + struct git__dirent **result, + int *is_dir) +{ + if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) + return -1; + + *result = NULL; + + if (d->first) + d->first = 0; + else if (!FindNextFileW(d->h, &d->f)) { + if (GetLastError() == ERROR_NO_MORE_FILES) + return 0; + git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir); + return -1; + } + + /* Convert the path to UTF-8 */ + if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) + return -1; + + entry->d_ino = 0; + + *result = entry; + + if (is_dir != NULL) + *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + + return 0; +} + +struct git__dirent *git__readdir(git__DIR *d) +{ + struct git__dirent *result; + if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) + return NULL; + return result; +} + +void git__rewinddir(git__DIR *d) +{ + git_win32_path filter_w; + + if (!d) + return; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; + d->first = 0; + } + + if (!git_win32__findfirstfile_filter(filter_w, d->dir)) + return; + + d->h = FindFirstFileW(filter_w, &d->f); + + if (d->h == INVALID_HANDLE_VALUE) + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir); + else + d->first = 1; +} + +int git__closedir(git__DIR *d) +{ + if (!d) + return 0; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; + } + + git__free(d); + return 0; +} + diff --git a/src/util/win32/dir.h b/src/util/win32/dir.h new file mode 100644 index 000000000..810111534 --- /dev/null +++ b/src/util/win32/dir.h @@ -0,0 +1,44 @@ +/* + * 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_dir_h__ +#define INCLUDE_win32_dir_h__ + +#include "git2_util.h" + +#include "w32_util.h" + +struct git__dirent { + int d_ino; + git_win32_utf8_path d_name; +}; + +typedef struct { + HANDLE h; + WIN32_FIND_DATAW f; + struct git__dirent entry; + int first; + char dir[GIT_FLEX_ARRAY]; +} git__DIR; + +extern git__DIR *git__opendir(const char *); +extern struct git__dirent *git__readdir(git__DIR *); +extern int git__readdir_ext( + git__DIR *, struct git__dirent *, struct git__dirent **, int *); +extern void git__rewinddir(git__DIR *); +extern int git__closedir(git__DIR *); + +# ifndef GIT__WIN32_NO_WRAP_DIR +# define dirent git__dirent +# define DIR git__DIR +# define opendir git__opendir +# define readdir git__readdir +# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) +# define rewinddir git__rewinddir +# define closedir git__closedir +# endif + +#endif diff --git a/src/util/win32/error.c b/src/util/win32/error.c new file mode 100644 index 000000000..3a52fb5a9 --- /dev/null +++ b/src/util/win32/error.c @@ -0,0 +1,53 @@ +/* + * 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 "error.h" + +#include "utf-conv.h" + +#ifdef GIT_WINHTTP +# include +#endif + +char *git_win32_get_error_message(DWORD error_code) +{ + LPWSTR lpMsgBuf = NULL; + HMODULE hModule = NULL; + char *utf8_msg = NULL; + DWORD dwFlags = + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; + + if (!error_code) + return NULL; + +#ifdef GIT_WINHTTP + /* Errors raised by WinHTTP are not in the system resource table */ + if (error_code >= WINHTTP_ERROR_BASE && + error_code <= WINHTTP_ERROR_LAST) + hModule = GetModuleHandleW(L"winhttp"); +#endif + + GIT_UNUSED(hModule); + + if (hModule) + dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; + else + dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; + + if (FormatMessageW(dwFlags, hModule, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, 0, NULL)) { + /* Convert the message to UTF-8. If this fails, we will + * return NULL, which is a condition expected by the caller */ + if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) + utf8_msg = NULL; + + LocalFree(lpMsgBuf); + } + + return utf8_msg; +} diff --git a/src/util/win32/error.h b/src/util/win32/error.h new file mode 100644 index 000000000..fd53b7f99 --- /dev/null +++ b/src/util/win32/error.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_win32_error_h__ +#define INCLUDE_win32_error_h__ + +#include "git2_util.h" + +extern char *git_win32_get_error_message(DWORD error_code); + +#endif diff --git a/src/util/win32/findfile.c b/src/util/win32/findfile.c new file mode 100644 index 000000000..725a90167 --- /dev/null +++ b/src/util/win32/findfile.c @@ -0,0 +1,286 @@ +/* + * 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 "findfile.h" + +#include "path_w32.h" +#include "utf-conv.h" +#include "fs_path.h" + +#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" +#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" + +static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) +{ + DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); + + if (!len || len > GIT_WIN_PATH_UTF16) + return -1; + + return 0; +} + +static int win32_path_to_8(git_str *dest, const wchar_t *src) +{ + git_win32_utf8_path utf8_path; + + if (git_win32_path_to_utf8(utf8_path, src) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); + return -1; + } + + /* Convert backslashes to forward slashes */ + git_fs_path_mkposix(utf8_path); + + return git_str_sets(dest, utf8_path); +} + +static git_win32_path mock_registry; +static bool mock_registry_set; + +extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) +{ + if (!mock_sysdir) { + mock_registry[0] = L'\0'; + mock_registry_set = false; + } else { + size_t len = wcslen(mock_sysdir); + + if (len > GIT_WIN_PATH_MAX) { + git_error_set(GIT_ERROR_INVALID, "mock path too long"); + return -1; + } + + wcscpy(mock_registry, mock_sysdir); + mock_registry_set = true; + } + + return 0; +} + +static int lookup_registry_key( + git_win32_path out, + const HKEY hive, + const wchar_t* key, + const wchar_t *value) +{ + HKEY hkey; + DWORD type, size; + int error = GIT_ENOTFOUND; + + /* + * Registry data may not be NUL terminated, provide room to do + * it ourselves. + */ + size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); + + if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) + return GIT_ENOTFOUND; + + if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && + type == REG_SZ && + size > 0 && + size < sizeof(git_win32_path)) { + size_t wsize = size / sizeof(wchar_t); + size_t len = wsize - 1; + + if (out[wsize - 1] != L'\0') { + len = wsize; + out[wsize] = L'\0'; + } + + if (out[len - 1] == L'\\') + out[len - 1] = L'\0'; + + if (_waccess(out, F_OK) == 0) + error = 0; + } + + RegCloseKey(hkey); + return error; +} + +static int find_sysdir_in_registry(git_win32_path out) +{ + if (mock_registry_set) { + if (mock_registry[0] == L'\0') + return GIT_ENOTFOUND; + + wcscpy(out, mock_registry); + return 0; + } + + if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) + return 0; + + return GIT_ENOTFOUND; +} + +static int find_sysdir_in_path(git_win32_path out) +{ + size_t out_len; + + if (git_win32_path_find_executable(out, L"git.exe") < 0 && + git_win32_path_find_executable(out, L"git.cmd") < 0) + return GIT_ENOTFOUND; + + out_len = wcslen(out); + + /* Trim the file name */ + if (out_len <= CONST_STRLEN(L"git.exe")) + return GIT_ENOTFOUND; + + out_len -= CONST_STRLEN(L"git.exe"); + + if (out_len && out[out_len - 1] == L'\\') + out_len--; + + /* + * Git for Windows usually places the command in a 'bin' or + * 'cmd' directory, trim that. + */ + if (out_len >= CONST_STRLEN(L"\\bin") && + wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) + out_len -= CONST_STRLEN(L"\\bin"); + else if (out_len >= CONST_STRLEN(L"\\cmd") && + wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) + out_len -= CONST_STRLEN(L"\\cmd"); + + if (!out_len) + return GIT_ENOTFOUND; + + out[out_len] = L'\0'; + return 0; +} + +static int win32_find_existing_dirs( + git_str* out, + const wchar_t* tmpl[]) +{ + git_win32_path path16; + git_str buf = GIT_STR_INIT; + + git_str_clear(out); + + for (; *tmpl != NULL; tmpl++) { + if (!git_win32__expand_path(path16, *tmpl) && + path16[0] != L'%' && + !_waccess(path16, F_OK)) { + win32_path_to_8(&buf, path16); + + if (buf.size) + git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); + } + } + + git_str_dispose(&buf); + + return (git_str_oom(out) ? -1 : 0); +} + +static int append_subdir(git_str *out, git_str *path, const char *subdir) +{ + static const char* architecture_roots[] = { + "", + "mingw64", + "mingw32", + NULL + }; + const char **root; + size_t orig_path_len = path->size; + + for (root = architecture_roots; *root; root++) { + if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || + git_str_joinpath(path, path->ptr, subdir) < 0) + return -1; + + if (git_fs_path_exists(path->ptr) && + git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) + return -1; + + git_str_truncate(path, orig_path_len); + } + + return 0; +} + +int git_win32__find_system_dirs(git_str *out, const char *subdir) +{ + git_win32_path pathdir, regdir; + git_str path8 = GIT_STR_INIT; + bool has_pathdir, has_regdir; + int error; + + has_pathdir = (find_sysdir_in_path(pathdir) == 0); + has_regdir = (find_sysdir_in_registry(regdir) == 0); + + if (!has_pathdir && !has_regdir) + return 0; + + /* + * Usually the git in the path is the same git in the registry, + * in this case there's no need to duplicate the paths. + */ + if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) + has_regdir = false; + + if (has_pathdir) { + if ((error = win32_path_to_8(&path8, pathdir)) < 0 || + (error = append_subdir(out, &path8, subdir)) < 0) + goto done; + } + + if (has_regdir) { + if ((error = win32_path_to_8(&path8, regdir)) < 0 || + (error = append_subdir(out, &path8, subdir)) < 0) + goto done; + } + +done: + git_str_dispose(&path8); + return error; +} + +int git_win32__find_global_dirs(git_str *out) +{ + static const wchar_t *global_tmpls[4] = { + L"%HOME%\\", + L"%HOMEDRIVE%%HOMEPATH%\\", + L"%USERPROFILE%\\", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls); +} + +int git_win32__find_xdg_dirs(git_str *out) +{ + static const wchar_t *global_tmpls[7] = { + L"%XDG_CONFIG_HOME%\\git", + L"%APPDATA%\\git", + L"%LOCALAPPDATA%\\git", + L"%HOME%\\.config\\git", + L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", + L"%USERPROFILE%\\.config\\git", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls); +} + +int git_win32__find_programdata_dirs(git_str *out) +{ + static const wchar_t *programdata_tmpls[2] = { + L"%PROGRAMDATA%\\Git", + NULL, + }; + + return win32_find_existing_dirs(out, programdata_tmpls); +} diff --git a/src/util/win32/findfile.h b/src/util/win32/findfile.h new file mode 100644 index 000000000..7b191d1fe --- /dev/null +++ b/src/util/win32/findfile.h @@ -0,0 +1,22 @@ +/* + * 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_findfile_h__ +#define INCLUDE_win32_findfile_h__ + +#include "git2_util.h" + +/** Sets the mock registry root for Git for Windows for testing. */ +extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); + +extern int git_win32__find_system_dirs(git_str *out, const char *subpath); +extern int git_win32__find_global_dirs(git_str *out); +extern int git_win32__find_xdg_dirs(git_str *out); +extern int git_win32__find_programdata_dirs(git_str *out); + +#endif + diff --git a/src/util/win32/map.c b/src/util/win32/map.c new file mode 100644 index 000000000..52e1363ea --- /dev/null +++ b/src/util/win32/map.c @@ -0,0 +1,141 @@ +/* + * 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_util.h" + +#include "map.h" +#include + +#ifndef NO_MMAP + +static DWORD get_page_size(void) +{ + static DWORD page_size; + SYSTEM_INFO sys; + + if (!page_size) { + GetSystemInfo(&sys); + page_size = sys.dwPageSize; + } + + return page_size; +} + +static DWORD get_allocation_granularity(void) +{ + static DWORD granularity; + SYSTEM_INFO sys; + + if (!granularity) { + GetSystemInfo(&sys); + granularity = sys.dwAllocationGranularity; + } + + return granularity; +} + +int git__page_size(size_t *page_size) +{ + *page_size = get_page_size(); + return 0; +} + +int git__mmap_alignment(size_t *page_size) +{ + *page_size = get_allocation_granularity(); + return 0; +} + +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(); + DWORD fmap_prot = 0; + DWORD view_prot = 0; + DWORD off_low = 0; + DWORD off_hi = 0; + off64_t page_start; + off64_t page_offset; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + out->data = NULL; + out->len = 0; + out->fmh = NULL; + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); + return -1; + } + + if (prot & GIT_PROT_WRITE) + fmap_prot |= PAGE_READWRITE; + else if (prot & GIT_PROT_READ) + fmap_prot |= PAGE_READONLY; + + if (prot & GIT_PROT_WRITE) + view_prot |= FILE_MAP_WRITE; + if (prot & GIT_PROT_READ) + view_prot |= FILE_MAP_READ; + + page_start = (offset / alignment) * alignment; + page_offset = offset - page_start; + + if (page_offset != 0) { /* offset must be multiple of the allocation granularity */ + errno = EINVAL; + git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity"); + return -1; + } + + out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); + if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); + out->fmh = NULL; + return -1; + } + + off_low = (DWORD)(page_start); + off_hi = (DWORD)(page_start >> 32); + out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); + if (!out->data) { + git_error_set(GIT_ERROR_OS, "failed to mmap. No data written"); + CloseHandle(out->fmh); + out->fmh = NULL; + return -1; + } + out->len = len; + + return 0; +} + +int p_munmap(git_map *map) +{ + int error = 0; + + GIT_ASSERT_ARG(map); + + if (map->data) { + if (!UnmapViewOfFile(map->data)) { + git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file"); + error = -1; + } + map->data = NULL; + } + + if (map->fmh) { + if (!CloseHandle(map->fmh)) { + git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle"); + error = -1; + } + map->fmh = NULL; + } + + return error; +} + +#endif diff --git a/src/util/win32/mingw-compat.h b/src/util/win32/mingw-compat.h new file mode 100644 index 000000000..aa2bef98d --- /dev/null +++ b/src/util/win32/mingw-compat.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_win32_mingw_compat_h__ +#define INCLUDE_win32_mingw_compat_h__ + +#if defined(__MINGW32__) + +#undef stat + +#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) +#undef MemoryBarrier +void __mingworg_MemoryBarrier(void); +#define MemoryBarrier __mingworg_MemoryBarrier +#define VOLUME_NAME_DOS 0x0 +#endif + +#endif + +#endif diff --git a/src/util/win32/msvc-compat.h b/src/util/win32/msvc-compat.h new file mode 100644 index 000000000..03f9f36dc --- /dev/null +++ b/src/util/win32/msvc-compat.h @@ -0,0 +1,36 @@ +/* + * 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_msvc_compat_h__ +#define INCLUDE_win32_msvc_compat_h__ + +#if defined(_MSC_VER) + +typedef unsigned short mode_t; +typedef SSIZE_T ssize_t; + +#ifdef _WIN64 +# define SSIZE_MAX _I64_MAX +#else +# define SSIZE_MAX LONG_MAX +#endif + +#define strcasecmp(s1, s2) _stricmp(s1, s2) +#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) + +#endif + +/* + * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always). + * This is useful for providing callbacks to userspace code. + * + * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on + * Win32). Useful for providing callbacks to system libraries. + */ +#define GIT_LIBGIT2_CALL __cdecl +#define GIT_SYSTEM_CALL NTAPI + +#endif diff --git a/src/util/win32/path_w32.c b/src/util/win32/path_w32.c new file mode 100644 index 000000000..d9fc8292b --- /dev/null +++ b/src/util/win32/path_w32.c @@ -0,0 +1,642 @@ +/* + * 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 "path_w32.h" + +#include "fs_path.h" +#include "utf-conv.h" +#include "posix.h" +#include "reparse.h" +#include "dir.h" + +#define PATH__NT_NAMESPACE L"\\\\?\\" +#define PATH__NT_NAMESPACE_LEN 4 + +#define PATH__ABSOLUTE_LEN 3 + +#define path__is_nt_namespace(p) \ + (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ + ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) + +#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; + + if ((len = GetCurrentDirectoryW(size, path)) == 0) { + errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; + return -1; + } else if (len > size) { + errno = ENAMETOOLONG; + return -1; + } + + /* The Win32 APIs may return "\\?\" once you've used it first. + * But it may not. What a gloriously predictable API! + */ + if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) + return len; + + len -= PATH__NT_NAMESPACE_LEN; + + memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); + return len; +} + +static wchar_t *path__skip_server(wchar_t *path) +{ + wchar_t *c; + + for (c = path; *c; c++) { + if (git_fs_path_is_dirsep(*c)) + return c + 1; + } + + return c; +} + +static wchar_t *path__skip_prefix(wchar_t *path) +{ + if (path__is_nt_namespace(path)) { + path += PATH__NT_NAMESPACE_LEN; + + if (wcsncmp(path, L"UNC\\", 4) == 0) + path = path__skip_server(path + 4); + else if (git_fs_path_is_absolute(path)) + path += PATH__ABSOLUTE_LEN; + } else if (git_fs_path_is_absolute(path)) { + path += PATH__ABSOLUTE_LEN; + } else if (path__is_unc(path)) { + path = path__skip_server(path + 2); + } + + return path; +} + +int git_win32_path_canonicalize(git_win32_path path) +{ + wchar_t *base, *from, *to, *next; + size_t len; + + base = to = path__skip_prefix(path); + + /* Unposixify if the prefix */ + for (from = path; from < to; from++) { + if (*from == L'/') + *from = L'\\'; + } + + while (*from) { + for (next = from; *next; ++next) { + if (*next == L'/') { + *next = L'\\'; + break; + } + + if (*next == L'\\') + break; + } + + len = next - from; + + if (len == 1 && from[0] == L'.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == L'.' && from[1] == L'.') { + if (to == base) { + /* no more path segments to strip, eat the "../" */ + if (*next == L'\\') + len++; + + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == L'\\') to--; + while (to > base && to[-1] != L'\\') to--; + } + } else { + if (*next == L'\\' && *from != L'\\') + len++; + + if (to != from) + memmove(to, from, sizeof(wchar_t) * len); + + to += len; + } + + from += len; + + while (*from == L'\\') from++; + } + + /* Strip trailing backslashes */ + while (to > base && to[-1] == L'\\') to--; + + *to = L'\0'; + + if ((to - path) > INT_MAX) { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return -1; + } + + return (int)(to - path); +} + +static int git_win32_path_join( + git_win32_path dest, + const wchar_t *one, + size_t one_len, + const wchar_t *two, + size_t two_len) +{ + size_t backslash = 0; + + if (one_len && two_len && one[one_len - 1] != L'\\') + backslash = 1; + + if (one_len + two_len + backslash > MAX_PATH) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + return -1; + } + + memmove(dest, one, one_len * sizeof(wchar_t)); + + if (backslash) + dest[one_len] = L'\\'; + + memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); + dest[one_len + backslash + two_len] = L'\0'; + + return 0; +} + +struct win32_path_iter { + wchar_t *env; + const wchar_t *current_dir; +}; + +static int win32_path_iter_init(struct win32_path_iter *iter) +{ + DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); + + if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + iter->env = NULL; + iter->current_dir = NULL; + return 0; + } else if (!len) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->env = git__malloc(len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(iter->env); + + len = GetEnvironmentVariableW(L"PATH", iter->env, len); + + if (len == 0) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->current_dir = iter->env; + return 0; +} + +static int win32_path_iter_next( + const wchar_t **out, + size_t *out_len, + struct win32_path_iter *iter) +{ + const wchar_t *start; + wchar_t term; + size_t len = 0; + + if (!iter->current_dir || !*iter->current_dir) + return GIT_ITEROVER; + + term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; + start = iter->current_dir; + + while (*iter->current_dir && *iter->current_dir != term) { + iter->current_dir++; + len++; + } + + *out = start; + *out_len = len; + + if (term == L'"' && *iter->current_dir) + iter->current_dir++; + + while (*iter->current_dir == L';') + iter->current_dir++; + + return 0; +} + +static void win32_path_iter_dispose(struct win32_path_iter *iter) +{ + if (!iter) + return; + + git__free(iter->env); + iter->env = NULL; + iter->current_dir = NULL; +} + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) +{ + struct win32_path_iter path_iter; + const wchar_t *dir; + size_t dir_len, exe_len = wcslen(exe); + bool found = false; + + if (win32_path_iter_init(&path_iter) < 0) + return -1; + + while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { + if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) + continue; + + if (_waccess(fullpath, 0) == 0) { + found = true; + break; + } + } + + win32_path_iter_dispose(&path_iter); + + if (found) + return 0; + + fullpath[0] = L'\0'; + return GIT_ENOTFOUND; +} + +static int win32_path_cwd(wchar_t *out, size_t len) +{ + int cwd_len; + + if (len > INT_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + if ((cwd_len = path__cwd(out, (int)len)) < 0) + return -1; + + /* UNC paths */ + if (wcsncmp(L"\\\\", out, 2) == 0) { + /* Our buffer must be at least 5 characters larger than the + * current working directory: we swallow one of the leading + * '\'s, but we we add a 'UNC' specifier to the path, plus + * a trailing directory separator, plus a NUL. + */ + if (cwd_len > GIT_WIN_PATH_MAX - 4) { + errno = ENAMETOOLONG; + return -1; + } + + memmove(out+2, out, sizeof(wchar_t) * cwd_len); + out[0] = L'U'; + out[1] = L'N'; + out[2] = L'C'; + + cwd_len += 2; + } + + /* Our buffer must be at least 2 characters larger than the current + * working directory. (One character for the directory separator, + * one for the null. + */ + else if (cwd_len > GIT_WIN_PATH_MAX - 2) { + errno = ENAMETOOLONG; + return -1; + } + + return cwd_len; +} + +int git_win32_path_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out; + + /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ + memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); + dest += PATH__NT_NAMESPACE_LEN; + + /* See if this is an absolute path (beginning with a drive letter) */ + if (git_fs_path_is_absolute(src)) { + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) + goto on_error; + } + /* File-prefixed NT-style paths beginning with \\?\ */ + else if (path__is_nt_namespace(src)) { + /* Skip the NT prefix, the destination already contains it */ + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) + goto on_error; + } + /* UNC paths */ + else if (path__is_unc(src)) { + memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); + dest += 4; + + /* Skip the leading "\\" */ + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) + goto on_error; + } + /* Absolute paths omitting the drive letter */ + else if (path__startswith_slash(src)) { + if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) + goto on_error; + + if (!git_fs_path_is_absolute(dest)) { + errno = ENOENT; + goto on_error; + } + + /* Skip the drive letter specification ("C:") */ + if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) + goto on_error; + } + /* Relative paths */ + else { + int cwd_len; + + if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) + goto on_error; + + dest[cwd_len++] = L'\\'; + + if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) + goto on_error; + } + + return git_win32_path_canonicalize(out); + +on_error: + /* set windows error code so we can use its error message */ + if (errno == ENAMETOOLONG) + SetLastError(ERROR_FILENAME_EXCED_RANGE); + + 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_fs_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, GIT_WIN_PATH_MAX, 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; + int len; + + /* Strip NT namespacing "\\?\" */ + if (path__is_nt_namespace(src)) { + src += 4; + + /* "\\?\UNC\server\share" -> "\\server\share" */ + if (wcsncmp(src, L"UNC\\", 4) == 0) { + src += 4; + + memcpy(dest, "\\\\", 2); + out = dest + 2; + } + } + + if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) + return len; + + git_fs_path_mkposix(dest); + + return len; +} + +char *git_win32_path_8dot3_name(const char *path) +{ + git_win32_path longpath, shortpath; + wchar_t *start; + char *shortname; + int len, namelen = 1; + + if (git_win32_path_from_utf8(longpath, path) < 0) + return NULL; + + len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); + + while (len && shortpath[len-1] == L'\\') + shortpath[--len] = L'\0'; + + if (len == 0 || len >= GIT_WIN_PATH_UTF16) + return NULL; + + for (start = shortpath + (len - 1); + start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; + start--) + namelen++; + + /* We may not have actually been given a short name. But if we have, + * it will be in the ASCII byte range, so we don't need to worry about + * multi-byte sequences and can allocate naively. + */ + if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) + return NULL; + + if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) + return NULL; + + return shortname; +} + +static bool path_is_volume(wchar_t *target, size_t target_len) +{ + return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); +} + +/* On success, returns the length, in characters, of the path stored in dest. + * On failure, returns a negative value. */ +int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) +{ + BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; + HANDLE handle = NULL; + DWORD ioctl_ret; + wchar_t *target; + size_t target_len; + + int error = -1; + + handle = CreateFileW(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + errno = ENOENT; + return -1; + } + + if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, + reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { + errno = EINVAL; + goto on_error; + } + + switch (reparse_buf->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: + target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + + (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); + target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); + break; + case IO_REPARSE_TAG_MOUNT_POINT: + target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + + (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); + target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); + break; + default: + errno = EINVAL; + goto on_error; + } + + if (path_is_volume(target, target_len)) { + /* This path is a reparse point that represents another volume mounted + * at this location, it is not a symbolic link our input was canonical. + */ + errno = EINVAL; + error = -1; + } else if (target_len) { + /* The path may need to have a namespace prefix removed. */ + target_len = git_win32_path_remove_namespace(target, target_len); + + /* Need one additional character in the target buffer + * for the terminating NULL. */ + if (GIT_WIN_PATH_UTF16 > target_len) { + wcscpy(dest, target); + error = (int)target_len; + } + } + +on_error: + CloseHandle(handle); + return error; +} + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len) +{ + while (1) { + if (!len || str[len - 1] != L'\\') + break; + + /* + * Don't trim backslashes from drive letter paths, which + * are 3 characters long and of the form C:\, D:\, etc. + */ + if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') + break; + + len--; + } + + str[len] = L'\0'; + + return len; +} + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) +{ + static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; + static const wchar_t nt_namespace[] = L"\\\\?\\"; + static const wchar_t unc_namespace_remainder[] = L"UNC\\"; + static const wchar_t unc_prefix[] = L"\\\\"; + + const wchar_t *prefix = NULL, *remainder = NULL; + size_t prefix_len = 0, remainder_len = 0; + + /* "\??\" -- DOS Devices prefix */ + if (len >= CONST_STRLEN(dosdevices_namespace) && + !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { + remainder = str + CONST_STRLEN(dosdevices_namespace); + remainder_len = len - CONST_STRLEN(dosdevices_namespace); + } + /* "\\?\" -- NT namespace prefix */ + else if (len >= CONST_STRLEN(nt_namespace) && + !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { + remainder = str + CONST_STRLEN(nt_namespace); + remainder_len = len - CONST_STRLEN(nt_namespace); + } + + /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ + if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && + !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { + + /* + * The proper Win32 path for a UNC share has "\\" at beginning of it + * and looks like "\\server\share\". So remove the + * UNC namespace and add a prefix of "\\" in its place. + */ + remainder += CONST_STRLEN(unc_namespace_remainder); + remainder_len -= CONST_STRLEN(unc_namespace_remainder); + + prefix = unc_prefix; + prefix_len = CONST_STRLEN(unc_prefix); + } + + /* + * Sanity check that the new string isn't longer than the old one. + * (This could only happen due to programmer error introducing a + * prefix longer than the namespace it replaces.) + */ + if (remainder && len >= remainder_len + prefix_len) { + if (prefix) + memmove(str, prefix, prefix_len * sizeof(wchar_t)); + + memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); + + len = remainder_len + prefix_len; + str[len] = L'\0'; + } + + return git_win32_path_trim_end(str, len); +} diff --git a/src/util/win32/path_w32.h b/src/util/win32/path_w32.h new file mode 100644 index 000000000..b241d5c8a --- /dev/null +++ b/src/util/win32/path_w32.h @@ -0,0 +1,91 @@ +/* + * 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_path_w32_h__ +#define INCLUDE_win32_path_w32_h__ + +#include "git2_util.h" + +/** + * 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. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +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, + * strip trailing directory separators, ensure directory separators are all + * 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 + * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) + */ +extern int git_win32_path_canonicalize(git_win32_path path); + +/** + * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. + * + * @param dest The buffer to receive the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); + +/** + * Get the short name for the terminal path component in the given path. + * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name + * for the file "Asdf.txt". + * + * @param path The given path in UTF-8 + * @return The name of the shortname for the given path + */ +extern char *git_win32_path_8dot3_name(const char *path); + +extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len); + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); + +#endif diff --git a/src/util/win32/posix.h b/src/util/win32/posix.h new file mode 100644 index 000000000..03fa2ac52 --- /dev/null +++ b/src/util/win32/posix.h @@ -0,0 +1,62 @@ +/* + * 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_posix_h__ +#define INCLUDE_win32_posix_h__ + +#include "git2_util.h" +#include "../posix.h" +#include "win32-compat.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "dir.h" + +extern unsigned long git_win32__createfile_sharemode; +extern int git_win32__retries; + +typedef SOCKET GIT_SOCKET; + +#define p_lseek(f,n,w) _lseeki64(f, n, w) + +extern int p_fstat(int fd, struct stat *buf); +extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_stat(const char *path, struct stat *buf); + +extern int p_utimes(const char *filename, const struct p_timeval times[2]); +extern int p_futimes(int fd, const struct p_timeval times[2]); + +extern int p_readlink(const char *path, char *buf, size_t bufsiz); +extern int p_symlink(const char *old, const char *new); +extern int p_link(const char *old, const char *new); +extern int p_unlink(const char *path); +extern int p_mkdir(const char *path, mode_t mode); +extern int p_fsync(int fd); +extern char *p_realpath(const char *orig_path, char *buffer); + +extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); +extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); +extern int p_inet_pton(int af, const char *src, void* dst); + +extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); +extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); +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, 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 + * entry was encountered. Making it correct is potentially expensive, + * however, so this is a separate version of p_lstat to use when correct + * POSIX ENOTDIR semantics is required. + */ +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); + +#endif diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c new file mode 100644 index 000000000..5862e5c9a --- /dev/null +++ b/src/util/win32/posix_w32.c @@ -0,0 +1,1047 @@ +/* + * 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_util.h" + +#include "../posix.h" +#include "../futils.h" +#include "fs_path.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "reparse.h" +#include +#include +#include +#include + +#ifndef FILE_NAME_NORMALIZED +# define FILE_NAME_NORMALIZED 0 +#endif + +#ifndef IO_REPARSE_TAG_SYMLINK +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif + +#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE +# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 +#endif + +#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY +# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01 +#endif + +/* Allowable mode bits on Win32. Using mode bits that are not supported on + * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it + * so we simply remove them. + */ +#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE) + +unsigned long git_win32__createfile_sharemode = + FILE_SHARE_READ | FILE_SHARE_WRITE; +int git_win32__retries = 10; + +GIT_INLINE(void) set_errno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NO_MORE_FILES: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + errno = ENOENT; + break; + case ERROR_BAD_ENVIRONMENT: + errno = E2BIG; + break; + case ERROR_BAD_FORMAT: + case ERROR_INVALID_STARTING_CODESEG: + case ERROR_INVALID_STACKSEG: + case ERROR_INVALID_MODULETYPE: + case ERROR_INVALID_EXE_SIGNATURE: + case ERROR_EXE_MARKED_INVALID: + case ERROR_BAD_EXE_FORMAT: + case ERROR_ITERATED_DATA_EXCEEDS_64k: + case ERROR_INVALID_MINALLOCSIZE: + case ERROR_DYNLINK_FROM_INVALID_RING: + case ERROR_IOPL_NOT_ENABLED: + case ERROR_INVALID_SEGDPL: + case ERROR_AUTODATASEG_EXCEEDS_64k: + case ERROR_RING2SEG_MUST_BE_MOVABLE: + case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: + case ERROR_INFLOOP_IN_RELOC_CHAIN: + errno = ENOEXEC; + break; + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_DIRECT_ACCESS_HANDLE: + errno = EBADF; + break; + case ERROR_WAIT_NO_CHILDREN: + case ERROR_CHILD_NOT_COMPLETE: + errno = ECHILD; + break; + case ERROR_NO_PROC_SLOTS: + case ERROR_MAX_THRDS_REACHED: + case ERROR_NESTING_NOT_ALLOWED: + errno = EAGAIN; + break; + case ERROR_ARENA_TRASHED: + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_NOT_ENOUGH_QUOTA: + errno = ENOMEM; + break; + case ERROR_ACCESS_DENIED: + case ERROR_CURRENT_DIRECTORY: + case ERROR_WRITE_PROTECT: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_BAD_LENGTH: + case ERROR_SEEK: + case ERROR_NOT_DOS_DISK: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_OUT_OF_PAPER: + case ERROR_WRITE_FAULT: + case ERROR_READ_FAULT: + case ERROR_GEN_FAILURE: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_WRONG_DISK: + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_FAIL_I24: + case ERROR_DRIVE_LOCKED: + case ERROR_SEEK_ON_DEVICE: + case ERROR_NOT_LOCKED: + case ERROR_LOCK_FAILED: + errno = EACCES; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + errno = EEXIST; + break; + case ERROR_NOT_SAME_DEVICE: + errno = EXDEV; + break; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_ACCESS: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_NEGATIVE_SEEK: + errno = EINVAL; + break; + case ERROR_TOO_MANY_OPEN_FILES: + errno = EMFILE; + break; + case ERROR_DISK_FULL: + errno = ENOSPC; + break; + case ERROR_BROKEN_PIPE: + errno = EPIPE; + break; + case ERROR_DIR_NOT_EMPTY: + errno = ENOTEMPTY; + break; + default: + errno = EINVAL; + } +} + +GIT_INLINE(bool) last_error_retryable(void) +{ + int os_error = GetLastError(); + + return (os_error == ERROR_SHARING_VIOLATION || + os_error == ERROR_ACCESS_DENIED); +} + +#define do_with_retries(fn, remediation) \ + do { \ + int __retry, __ret; \ + for (__retry = git_win32__retries; __retry; __retry--) { \ + if ((__ret = (fn)) != GIT_RETRY) \ + return __ret; \ + if (__retry > 1 && (__ret = (remediation)) != 0) { \ + if (__ret == GIT_RETRY) \ + continue; \ + return __ret; \ + } \ + Sleep(5); \ + } \ + return -1; \ + } while (0) \ + +static int ensure_writable(wchar_t *path) +{ + DWORD attrs; + + if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) + goto on_error; + + if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) + return 0; + + if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) + goto on_error; + + return GIT_RETRY; + +on_error: + set_errno(); + return -1; +} + +/** + * Truncate or extend file. + * + * We now take a "git_off_t" rather than "long" because + * files may be longer than 2Gb. + */ +int p_ftruncate(int fd, off64_t size) +{ + if (size < 0) { + errno = EINVAL; + return -1; + } + +#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) + return ((_chsize_s(fd, size) == 0) ? 0 : -1); +#else + /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ + if (size > INT32_MAX) { + errno = EFBIG; + return -1; + } + return _chsize(fd, (long)size); +#endif +} + +int p_mkdir(const char *path, mode_t mode) +{ + git_win32_path buf; + + GIT_UNUSED(mode); + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wmkdir(buf); +} + +int p_link(const char *old, const char *new) +{ + GIT_UNUSED(old); + GIT_UNUSED(new); + errno = ENOSYS; + return -1; +} + +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; + + set_errno(); + return -1; +} + +int p_unlink(const char *path) +{ + git_win32_path wpath; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + do_with_retries(unlink_once(wpath), ensure_writable(wpath)); +} + +int p_fsync(int fd) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + p_fsync__cnt++; + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + + if (!FlushFileBuffers(fh)) { + DWORD code = GetLastError(); + + if (code == ERROR_INVALID_HANDLE) + errno = EINVAL; + else + errno = EIO; + + return -1; + } + + return 0; +} + +#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') + +static int lstat_w( + wchar_t *path, + struct stat *buf, + bool posix_enotdir) +{ + WIN32_FILE_ATTRIBUTE_DATA fdata; + + if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { + if (!buf) + return 0; + + return git_win32__file_attribute_to_stat(buf, &fdata, path); + } + + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; + break; + } + + /* To match POSIX behavior, set ENOTDIR when any of the folders in the + * file path is a regular file, otherwise set ENOENT. + */ + if (errno == ENOENT && posix_enotdir) { + size_t path_len = wcslen(path); + + /* scan up path until we find an existing item */ + while (1) { + DWORD attrs; + + /* remove last directory component */ + for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); + + if (path_len <= 0) + break; + + path[path_len] = L'\0'; + attrs = GetFileAttributesW(path); + + if (attrs != INVALID_FILE_ATTRIBUTES) { + if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) + errno = ENOTDIR; + break; + } + } + } + + return -1; +} + +static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) +{ + git_win32_path path_w; + int len; + + if ((len = git_win32_path_from_utf8(path_w, path)) < 0) + return -1; + + git_win32_path_trim_end(path_w, len); + + return lstat_w(path_w, buf, posixly_correct); +} + +int p_lstat(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, false); +} + +int p_lstat_posixly(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, true); +} + +int p_readlink(const char *path, char *buf, size_t bufsiz) +{ + git_win32_path path_w, target_w; + git_win32_utf8_path target; + int len; + + /* readlink(2) does not NULL-terminate the string written + * to the target buffer. Furthermore, the target buffer need + * not be large enough to hold the entire result. A truncated + * result should be written in this case. Since this truncation + * could occur in the middle of the encoding of a code point, + * we need to buffer the result on the stack. */ + + if (git_win32_path_from_utf8(path_w, path) < 0 || + git_win32_path_readlink_w(target_w, path_w) < 0 || + (len = git_win32_path_to_utf8(target, target_w)) < 0) + return -1; + + bufsiz = min((size_t)len, bufsiz); + memcpy(buf, target, bufsiz); + + return (int)bufsiz; +} + +static bool target_is_dir(const char *target, const char *path) +{ + git_str resolved = GIT_STR_INIT; + git_win32_path resolved_w; + bool isdir = true; + + if (git_fs_path_is_absolute(target)) + git_win32_path_from_utf8(resolved_w, target); + else if (git_fs_path_dirname_r(&resolved, path) < 0 || + git_fs_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_str_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 something we want. + */ + if (git_win32_path_from_utf8(path_w, path) < 0 || + git_win32_path_relative_from_utf8(target_w, target) < 0) + return -1; + + dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (target_is_dir(target, path)) + dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; + + if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) + return -1; + + return 0; +} + +struct open_opts { + DWORD access; + DWORD sharing; + SECURITY_ATTRIBUTES security; + DWORD creation_disposition; + DWORD attributes; + int osf_flags; +}; + +GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) +{ + memset(opts, 0, sizeof(struct open_opts)); + + switch (flags & (O_WRONLY | O_RDWR)) { + case O_WRONLY: + opts->access = GENERIC_WRITE; + break; + case O_RDWR: + opts->access = GENERIC_READ | GENERIC_WRITE; + break; + default: + opts->access = GENERIC_READ; + break; + } + + opts->sharing = (DWORD)git_win32__createfile_sharemode; + + switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { + case O_CREAT | O_EXCL: + case O_CREAT | O_TRUNC | O_EXCL: + opts->creation_disposition = CREATE_NEW; + break; + case O_CREAT | O_TRUNC: + opts->creation_disposition = CREATE_ALWAYS; + break; + case O_TRUNC: + opts->creation_disposition = TRUNCATE_EXISTING; + break; + case O_CREAT: + opts->creation_disposition = OPEN_ALWAYS; + break; + default: + opts->creation_disposition = OPEN_EXISTING; + break; + } + + opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? + FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; + opts->osf_flags = flags & (O_RDONLY | O_APPEND); + + opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); + opts->security.lpSecurityDescriptor = NULL; + opts->security.bInheritHandle = 0; +} + +GIT_INLINE(int) open_once( + const wchar_t *path, + struct open_opts *opts) +{ + int fd; + + HANDLE handle = CreateFileW(path, opts->access, opts->sharing, + &opts->security, opts->creation_disposition, opts->attributes, 0); + + if (handle == INVALID_HANDLE_VALUE) { + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; + } + + if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) + CloseHandle(handle); + + return fd; +} + +int p_open(const char *path, int flags, ...) +{ + git_win32_path wpath; + mode_t mode = 0; + struct open_opts opts = {0}; + + #ifdef GIT_DEBUG_STRICT_OPEN + if (strstr(path, "//") != NULL) { + errno = EACCES; + return -1; + } + #endif + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + if (flags & O_CREAT) { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + open_opts_from_posix(&opts, flags, mode); + + do_with_retries( + open_once(wpath, &opts), + 0); +} + +int p_creat(const char *path, mode_t mode) +{ + return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); +} + +int p_utimes(const char *path, const struct p_timeval times[2]) +{ + git_win32_path wpath; + int fd, error; + DWORD attrs_orig, attrs_new = 0; + struct open_opts opts = { 0 }; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + attrs_orig = GetFileAttributesW(wpath); + + if (attrs_orig & FILE_ATTRIBUTE_READONLY) { + attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; + + if (!SetFileAttributesW(wpath, attrs_new)) { + git_error_set(GIT_ERROR_OS, "failed to set attributes"); + return -1; + } + } + + open_opts_from_posix(&opts, O_RDWR, 0); + + if ((fd = open_once(wpath, &opts)) < 0) { + error = -1; + goto done; + } + + error = p_futimes(fd, times); + close(fd); + +done: + if (attrs_orig != attrs_new) { + DWORD os_error = GetLastError(); + SetFileAttributesW(wpath, attrs_orig); + SetLastError(os_error); + } + + return error; +} + +int p_futimes(int fd, const struct p_timeval times[2]) +{ + HANDLE handle; + FILETIME atime = { 0 }, mtime = { 0 }; + + if (times == NULL) { + SYSTEMTIME st; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &atime); + SystemTimeToFileTime(&st, &mtime); + } + else { + git_win32__timeval_to_filetime(&atime, times[0]); + git_win32__timeval_to_filetime(&mtime, times[1]); + } + + if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) + return -1; + + if (SetFileTime(handle, NULL, &atime, &mtime) == 0) + return -1; + + return 0; +} + +int p_getcwd(char *buffer_out, size_t size) +{ + git_win32_path buf; + wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); + + if (!cwd) + return -1; + + git_win32_path_remove_namespace(cwd, wcslen(cwd)); + + /* Convert the working directory back to UTF-8 */ + if (git__utf16_to_8(buffer_out, size, cwd) < 0) { + DWORD code = GetLastError(); + + if (code == ERROR_INSUFFICIENT_BUFFER) + errno = ERANGE; + else + errno = EINVAL; + + return -1; + } + + git_fs_path_mkposix(buffer_out); + return 0; +} + +static int getfinalpath_w( + git_win32_path dest, + const wchar_t *path) +{ + HANDLE hFile; + DWORD dwChars; + + /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not + * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the + * target of the link. */ + hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (INVALID_HANDLE_VALUE == hFile) + return -1; + + /* Call GetFinalPathNameByHandle */ + dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED); + CloseHandle(hFile); + + if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) + return -1; + + /* The path may be delivered to us with a namespace prefix; remove */ + return (int)git_win32_path_remove_namespace(dest, dwChars); +} + +static int follow_and_lstat_link(git_win32_path path, struct stat *buf) +{ + git_win32_path target_w; + + if (getfinalpath_w(target_w, path) < 0) + return -1; + + return lstat_w(target_w, buf, false); +} + +int p_fstat(int fd, struct stat *buf) +{ + BY_HANDLE_FILE_INFORMATION fhInfo; + + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh == INVALID_HANDLE_VALUE || + !GetFileInformationByHandle(fh, &fhInfo)) { + errno = EBADF; + return -1; + } + + git_win32__file_information_to_stat(buf, &fhInfo); + return 0; +} + +int p_stat(const char *path, struct stat *buf) +{ + git_win32_path path_w; + int len; + + if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || + lstat_w(path_w, buf, false) < 0) + return -1; + + /* The item is a symbolic link or mount point. No need to iterate + * to follow multiple links; use GetFinalPathNameFromHandle. */ + if (S_ISLNK(buf->st_mode)) + return follow_and_lstat_link(path_w, buf); + + return 0; +} + +int p_chdir(const char *path) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wchdir(buf); +} + +int p_chmod(const char *path, mode_t mode) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wchmod(buf, mode); +} + +int p_rmdir(const char *path) +{ + git_win32_path buf; + int error; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + error = _wrmdir(buf); + + if (error == -1) { + switch (GetLastError()) { + /* _wrmdir() is documented to return EACCES if "A program has an open + * handle to the directory." This sounds like what everybody else calls + * EBUSY. Let's convert appropriate error codes. + */ + case ERROR_SHARING_VIOLATION: + errno = EBUSY; + break; + + /* This error can be returned when trying to rmdir an extant file. */ + case ERROR_DIRECTORY: + errno = ENOTDIR; + break; + } + } + + return error; +} + +char *p_realpath(const char *orig_path, char *buffer) +{ + git_win32_path orig_path_w, buffer_w; + + if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) + return NULL; + + /* Note that if the path provided is a relative path, then the current directory + * is used to resolve the path -- which is a concurrency issue because the current + * directory is a process-wide variable. */ + if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; + + return NULL; + } + + /* The path must exist. */ + if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { + errno = ENOENT; + return NULL; + } + + if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { + errno = ENOMEM; + return NULL; + } + + /* Convert the path to UTF-8. If the caller provided a buffer, then it + * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, + * then we may overflow. */ + if (git_win32_path_to_utf8(buffer, buffer_w) < 0) + return NULL; + + git_fs_path_mkposix(buffer); + + return buffer; +} + +int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ +#if defined(_MSC_VER) + int len; + + if (count == 0) + return _vscprintf(format, argptr); + + #if _MSC_VER >= 1500 + len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); + #else + len = _vsnprintf(buffer, count, format, argptr); + #endif + + if (len < 0) + return _vscprintf(format, argptr); + + return len; +#else /* MinGW */ + return vsnprintf(buffer, count, format, argptr); +#endif +} + +int p_snprintf(char *buffer, size_t count, const char *format, ...) +{ + va_list va; + int r; + + va_start(va, format); + r = p_vsnprintf(buffer, count, format, va); + va_end(va); + + return r; +} + +int p_access(const char *path, mode_t mode) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _waccess(buf, mode & WIN32_MODE_MASK); +} + +GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) +{ + if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + return 0; + + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; +} + +int p_rename(const char *from, const char *to) +{ + git_win32_path wfrom, wto; + + if (git_win32_path_from_utf8(wfrom, from) < 0 || + git_win32_path_from_utf8(wto, to) < 0) + return -1; + + do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); +} + +int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* git_error_set will be done by caller */ + + return recv(socket, buffer, (int)length, flags); +} + +int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* git_error_set will be done by caller */ + + return send(socket, buffer, (int)length, flags); +} + +/** + * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html + * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that + */ +struct tm * +p_localtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = localtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (struct tm)); + return result; +} +struct tm * +p_gmtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = gmtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (struct tm)); + return result; +} + +int p_inet_pton(int af, const char *src, void *dst) +{ + struct sockaddr_storage sin; + void *addr; + int sin_len = sizeof(struct sockaddr_storage), addr_len; + int error = 0; + + if (af == AF_INET) { + addr = &((struct sockaddr_in *)&sin)->sin_addr; + addr_len = sizeof(struct in_addr); + } else if (af == AF_INET6) { + addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; + addr_len = sizeof(struct in6_addr); + } else { + errno = EAFNOSUPPORT; + return -1; + } + + if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { + memcpy(dst, addr, addr_len); + return 1; + } + + switch(WSAGetLastError()) { + case WSAEINVAL: + return 0; + case WSAEFAULT: + errno = ENOSPC; + return -1; + case WSA_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + return -1; + } + + errno = EINVAL; + return -1; +} + +ssize_t p_pread(int fd, void *data, size_t size, off64_t offset) +{ + HANDLE fh; + DWORD rsize = 0; + OVERLAPPED ov = {0}; + LARGE_INTEGER pos = {0}; + off64_t final_offset = 0; + + /* Fail if the final offset would have overflowed to match POSIX semantics. */ + if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { + errno = EINVAL; + return -1; + } + + /* + * Truncate large writes to the maximum allowable size: the caller + * needs to always call this in a loop anyways. + */ + if (size > INT32_MAX) { + size = INT32_MAX; + } + + pos.QuadPart = offset; + ov.Offset = pos.LowPart; + ov.OffsetHigh = pos.HighPart; + fh = (HANDLE)_get_osfhandle(fd); + + if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) { + return (ssize_t)rsize; + } + + set_errno(); + return -1; +} + +ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset) +{ + HANDLE fh; + DWORD wsize = 0; + OVERLAPPED ov = {0}; + LARGE_INTEGER pos = {0}; + off64_t final_offset = 0; + + /* Fail if the final offset would have overflowed to match POSIX semantics. */ + if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { + errno = EINVAL; + return -1; + } + + /* + * Truncate large writes to the maximum allowable size: the caller + * needs to always call this in a loop anyways. + */ + if (size > INT32_MAX) { + size = INT32_MAX; + } + + pos.QuadPart = offset; + ov.Offset = pos.LowPart; + ov.OffsetHigh = pos.HighPart; + fh = (HANDLE)_get_osfhandle(fd); + + if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) { + return (ssize_t)wsize; + } + + set_errno(); + return -1; +} diff --git a/src/util/win32/precompiled.c b/src/util/win32/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/src/util/win32/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/src/util/win32/precompiled.h b/src/util/win32/precompiled.h new file mode 100644 index 000000000..1163c3d63 --- /dev/null +++ b/src/util/win32/precompiled.h @@ -0,0 +1,21 @@ +#include "git2_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#ifdef GIT_THREADS + #include "win32/thread.h" +#endif + +#include "git2.h" diff --git a/src/util/win32/reparse.h b/src/util/win32/reparse.h new file mode 100644 index 000000000..23312319f --- /dev/null +++ b/src/util/win32/reparse.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_win32_reparse_h__ +#define INCLUDE_win32_reparse_h__ + +/* This structure is defined on MSDN at +* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx +* +* It was formerly included in the Windows 2000 SDK and remains defined in +* MinGW, so we must define it with a silly name to avoid conflicting. +*/ +typedef struct _GIT_REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLink; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPoint; + struct { + UCHAR DataBuffer[1]; + } Generic; + } ReparseBuffer; +} GIT_REPARSE_DATA_BUFFER; + +#define REPARSE_DATA_HEADER_SIZE 8 +#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8 +#define REPARSE_DATA_UNION_SIZE 12 + +/* Missing in MinGW */ +#ifndef FSCTL_GET_REPARSE_POINT +# define FSCTL_GET_REPARSE_POINT 0x000900a8 +#endif + +/* Missing in MinGW */ +#ifndef FSCTL_SET_REPARSE_POINT +# define FSCTL_SET_REPARSE_POINT 0x000900a4 +#endif + +#endif diff --git a/src/util/win32/thread.c b/src/util/win32/thread.c new file mode 100644 index 000000000..f5cacd320 --- /dev/null +++ b/src/util/win32/thread.c @@ -0,0 +1,262 @@ +/* + * 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 "thread.h" +#include "runtime.h" + +#define CLEAN_THREAD_EXIT 0x6F012842 + +typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); + +static win32_srwlock_fn win32_srwlock_initialize; +static win32_srwlock_fn win32_srwlock_acquire_shared; +static win32_srwlock_fn win32_srwlock_release_shared; +static win32_srwlock_fn win32_srwlock_acquire_exclusive; +static win32_srwlock_fn win32_srwlock_release_exclusive; + +static DWORD fls_index; + +/* The thread procedure stub used to invoke the caller's procedure + * and capture the return value for later collection. Windows will + * only hold a DWORD, but we need to be able to store an entire + * void pointer. This requires the indirection. */ +static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) +{ + git_thread *thread = lpParameter; + + /* Set the current thread for `git_thread_exit` */ + FlsSetValue(fls_index, thread); + + thread->result = thread->proc(thread->param); + + return CLEAN_THREAD_EXIT; +} + +static void git_threads_global_shutdown(void) +{ + FlsFree(fls_index); +} + +int git_threads_global_init(void) +{ + HMODULE hModule = GetModuleHandleW(L"kernel32"); + + if (hModule) { + win32_srwlock_initialize = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "InitializeSRWLock"); + win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "AcquireSRWLockShared"); + win32_srwlock_release_shared = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "ReleaseSRWLockShared"); + win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "AcquireSRWLockExclusive"); + win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "ReleaseSRWLockExclusive"); + } + + if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES) + return -1; + + return git_runtime_shutdown_register(git_threads_global_shutdown); +} + +int git_thread_create( + git_thread *GIT_RESTRICT thread, + void *(*start_routine)(void*), + void *GIT_RESTRICT arg) +{ + thread->result = NULL; + thread->param = arg; + thread->proc = start_routine; + thread->thread = CreateThread( + NULL, 0, git_win32__threadproc, thread, 0, NULL); + + return thread->thread ? 0 : -1; +} + +int git_thread_join( + git_thread *thread, + void **value_ptr) +{ + DWORD exit; + + if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) + return -1; + + if (!GetExitCodeThread(thread->thread, &exit)) { + CloseHandle(thread->thread); + return -1; + } + + /* Check for the thread having exited uncleanly. If exit was unclean, + * then we don't have a return value to give back to the caller. */ + GIT_ASSERT(exit == CLEAN_THREAD_EXIT); + + if (value_ptr) + *value_ptr = thread->result; + + CloseHandle(thread->thread); + return 0; +} + +void git_thread_exit(void *value) +{ + git_thread *thread = FlsGetValue(fls_index); + + if (thread) + thread->result = value; + + ExitThread(CLEAN_THREAD_EXIT); +} + +size_t git_thread_currentid(void) +{ + return GetCurrentThreadId(); +} + +int git_mutex_init(git_mutex *GIT_RESTRICT mutex) +{ + InitializeCriticalSection(mutex); + return 0; +} + +int git_mutex_free(git_mutex *mutex) +{ + DeleteCriticalSection(mutex); + return 0; +} + +int git_mutex_lock(git_mutex *mutex) +{ + EnterCriticalSection(mutex); + return 0; +} + +int git_mutex_unlock(git_mutex *mutex) +{ + LeaveCriticalSection(mutex); + return 0; +} + +int git_cond_init(git_cond *cond) +{ + /* This is an auto-reset event. */ + *cond = CreateEventW(NULL, FALSE, FALSE, NULL); + GIT_ASSERT(*cond); + + /* If we can't create the event, claim that the reason was out-of-memory. + * The actual reason can be fetched with GetLastError(). */ + return *cond ? 0 : ENOMEM; +} + +int git_cond_free(git_cond *cond) +{ + BOOL closed; + + if (!cond) + return EINVAL; + + closed = CloseHandle(*cond); + GIT_ASSERT(closed); + GIT_UNUSED(closed); + + *cond = NULL; + return 0; +} + +int git_cond_wait(git_cond *cond, git_mutex *mutex) +{ + int error; + DWORD wait_result; + + if (!cond || !mutex) + return EINVAL; + + /* The caller must be holding the mutex. */ + error = git_mutex_unlock(mutex); + + if (error) + return error; + + wait_result = WaitForSingleObject(*cond, INFINITE); + GIT_ASSERT(WAIT_OBJECT_0 == wait_result); + GIT_UNUSED(wait_result); + + return git_mutex_lock(mutex); +} + +int git_cond_signal(git_cond *cond) +{ + BOOL signaled; + + if (!cond) + return EINVAL; + + signaled = SetEvent(*cond); + GIT_ASSERT(signaled); + GIT_UNUSED(signaled); + + return 0; +} + +int git_rwlock_init(git_rwlock *GIT_RESTRICT lock) +{ + if (win32_srwlock_initialize) + win32_srwlock_initialize(&lock->native.srwl); + else + InitializeCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_rdlock(git_rwlock *lock) +{ + if (win32_srwlock_acquire_shared) + win32_srwlock_acquire_shared(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_rdunlock(git_rwlock *lock) +{ + if (win32_srwlock_release_shared) + win32_srwlock_release_shared(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_wrlock(git_rwlock *lock) +{ + if (win32_srwlock_acquire_exclusive) + win32_srwlock_acquire_exclusive(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_wrunlock(git_rwlock *lock) +{ + if (win32_srwlock_release_exclusive) + win32_srwlock_release_exclusive(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_free(git_rwlock *lock) +{ + if (!win32_srwlock_initialize) + DeleteCriticalSection(&lock->native.csec); + git__memzero(lock, sizeof(*lock)); + return 0; +} diff --git a/src/util/win32/thread.h b/src/util/win32/thread.h new file mode 100644 index 000000000..184762e2a --- /dev/null +++ b/src/util/win32/thread.h @@ -0,0 +1,64 @@ +/* + * 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_thread_h__ +#define INCLUDE_win32_thread_h__ + +#include "git2_util.h" + +#if defined (_MSC_VER) +# define GIT_RESTRICT __restrict +#else +# define GIT_RESTRICT __restrict__ +#endif + +typedef struct { + HANDLE thread; + void *(*proc)(void *); + void *param; + void *result; +} git_thread; + +typedef CRITICAL_SECTION git_mutex; +typedef HANDLE git_cond; + +typedef struct { void *Ptr; } GIT_SRWLOCK; + +typedef struct { + union { + GIT_SRWLOCK srwl; + CRITICAL_SECTION csec; + } native; +} git_rwlock; + +int git_threads_global_init(void); + +int git_thread_create(git_thread *GIT_RESTRICT, + void *(*) (void *), + void *GIT_RESTRICT); +int git_thread_join(git_thread *, void **); +size_t git_thread_currentid(void); +void git_thread_exit(void *); + +int git_mutex_init(git_mutex *GIT_RESTRICT mutex); +int git_mutex_free(git_mutex *); +int git_mutex_lock(git_mutex *); +int git_mutex_unlock(git_mutex *); + +int git_cond_init(git_cond *); +int git_cond_free(git_cond *); +int git_cond_wait(git_cond *, git_mutex *); +int git_cond_signal(git_cond *); + +int git_rwlock_init(git_rwlock *GIT_RESTRICT lock); +int git_rwlock_rdlock(git_rwlock *); +int git_rwlock_rdunlock(git_rwlock *); +int git_rwlock_wrlock(git_rwlock *); +int git_rwlock_wrunlock(git_rwlock *); +int git_rwlock_free(git_rwlock *); + +#endif diff --git a/src/util/win32/utf-conv.c b/src/util/win32/utf-conv.c new file mode 100644 index 000000000..4bde3023a --- /dev/null +++ b/src/util/win32/utf-conv.c @@ -0,0 +1,146 @@ +/* + * 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 "utf-conv.h" + +GIT_INLINE(void) git__set_errno(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; +} + +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @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 + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) +{ + int len; + + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to + * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's + * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ + if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) + git__set_errno(); + + return len; +} + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) +{ + int len; + + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to + * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's + * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ + if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) + git__set_errno(); + + return len; +} + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to 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 + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src) +{ + int utf16_size; + + *dest = NULL; + + /* Length of -1 indicates NULL termination of the input string */ + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); + + if (!utf16_size) { + git__set_errno(); + return -1; + } + + if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { + errno = ENOMEM; + return -1; + } + + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); + + if (!utf16_size) { + git__set_errno(); + + git__free(*dest); + *dest = NULL; + } + + /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL + * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, + * so underflow is not possible */ + return utf16_size - 1; +} + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src) +{ + int utf8_size; + + *dest = NULL; + + /* Length of -1 indicates NULL termination of the input string */ + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); + + if (!utf8_size) { + git__set_errno(); + return -1; + } + + *dest = git__malloc(utf8_size); + + if (!*dest) { + errno = ENOMEM; + return -1; + } + + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); + + if (!utf8_size) { + git__set_errno(); + + git__free(*dest); + *dest = NULL; + } + + /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL + * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, + * so underflow is not possible */ + return utf8_size - 1; +} diff --git a/src/util/win32/utf-conv.h b/src/util/win32/utf-conv.h new file mode 100644 index 000000000..120d647ef --- /dev/null +++ b/src/util/win32/utf-conv.h @@ -0,0 +1,60 @@ +/* + * 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_utf_conv_h__ +#define INCLUDE_win32_utf_conv_h__ + +#include "git2_util.h" + +#include + +#ifndef WC_ERR_INVALID_CHARS +# define WC_ERR_INVALID_CHARS 0x80 +#endif + +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @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 + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to 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 + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src); + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src); + +#endif diff --git a/src/util/win32/version.h b/src/util/win32/version.h new file mode 100644 index 000000000..79667697f --- /dev/null +++ b/src/util/win32/version.h @@ -0,0 +1,37 @@ +/* + * 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_version_h__ +#define INCLUDE_win32_version_h__ + +#include + +GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) +{ + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = major; + version_test.dwMinorVersion = minor; + version_test.wServicePackMajor = (WORD)service_pack; + version_test.wServicePackMinor = 0; + + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return 0; + + return 1; +} + +#endif diff --git a/src/util/win32/w32_buffer.c b/src/util/win32/w32_buffer.c new file mode 100644 index 000000000..6fee8203c --- /dev/null +++ b/src/util/win32/w32_buffer.c @@ -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. + */ + +#include "w32_buffer.h" + +#include "utf-conv.h" + +GIT_INLINE(int) handle_wc_error(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; + + return -1; +} + +int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w) +{ + int utf8_len, utf8_write_len; + size_t new_size; + + if (!len_w) { + return 0; + } else if (len_w > INT_MAX) { + git_error_set_oom(); + return -1; + } + + GIT_ASSERT(string_w); + + /* Measure the string necessary for conversion */ + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) + return 0; + + GIT_ASSERT(utf8_len > 0); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + + if (git_str_grow(buf, new_size) < 0) + return -1; + + if ((utf8_write_len = WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) + return handle_wc_error(); + + GIT_ASSERT(utf8_write_len == utf8_len); + + buf->size += utf8_write_len; + buf->ptr[buf->size] = '\0'; + return 0; +} diff --git a/src/util/win32/w32_buffer.h b/src/util/win32/w32_buffer.h new file mode 100644 index 000000000..68ea96035 --- /dev/null +++ b/src/util/win32/w32_buffer.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_win32_w32_buffer_h__ +#define INCLUDE_win32_w32_buffer_h__ + +#include "git2_util.h" +#include "str.h" + +/** + * Convert a wide character string to UTF-8 and append the results to the + * buffer. + */ +int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w); + +#endif diff --git a/src/util/win32/w32_common.h b/src/util/win32/w32_common.h new file mode 100644 index 000000000..c20b3e85e --- /dev/null +++ b/src/util/win32/w32_common.h @@ -0,0 +1,48 @@ +/* + * 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__ + +#include + +/* + * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed + * Windows path length, however win32 Unicode APIs generally allow up to 32,767 + * if prefixed with "\\?\" (i.e. converted to an NT-style name). + */ +#define GIT_WIN_PATH_MAX GIT_PATH_MAX + +/* + * Provides a large enough buffer to support Windows Git paths: + * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095 + * 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 GIT_WIN_PATH_MAX+6. + */ +#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6 + +/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\" + * prefixes for presentation, bringing us back to 4095 (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 ((GIT_WIN_PATH_MAX - 1) * 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/util/win32/w32_leakcheck.c b/src/util/win32/w32_leakcheck.c new file mode 100644 index 000000000..0f095de12 --- /dev/null +++ b/src/util/win32/w32_leakcheck.c @@ -0,0 +1,581 @@ +/* + * 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 "w32_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) + +#include "Windows.h" +#include "Dbghelp.h" +#include "win32/posix.h" +#include "hash.h" +#include "runtime.h" + +/* Stack frames (for stack tracing, below) */ + +static bool g_win32_stack_initialized = false; +static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; +static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL; +static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL; + +int git_win32_leakcheck_stack_set_aux_cb( + git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, + git_win32_leakcheck_stack_aux_cb_lookup cb_lookup) +{ + g_aux_cb_alloc = cb_alloc; + g_aux_cb_lookup = cb_lookup; + + return 0; +} + +/** + * Load symbol table data. This should be done in the primary + * thread at startup (under a lock if there are other threads + * active). + */ +void git_win32_leakcheck_stack_init(void) +{ + if (!g_win32_stack_initialized) { + g_win32_stack_process = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES); + SymInitialize(g_win32_stack_process, NULL, TRUE); + g_win32_stack_initialized = true; + } +} + +/** + * Cleanup symbol table data. This should be done in the + * primary thead at shutdown (under a lock if there are other + * threads active). + */ +void git_win32_leakcheck_stack_cleanup(void) +{ + if (g_win32_stack_initialized) { + SymCleanup(g_win32_stack_process); + g_win32_stack_process = INVALID_HANDLE_VALUE; + g_win32_stack_initialized = false; + } +} + +int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) +{ + if (!g_win32_stack_initialized) { + git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + memset(pdata, 0, sizeof(*pdata)); + pdata->nr_frames = RtlCaptureStackBackTrace( + skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); + + /* If an "aux" data provider was registered, ask it to capture + * whatever data it needs and give us an "aux_id" to it so that + * we can refer to it later when reporting. + */ + if (g_aux_cb_alloc) + (g_aux_cb_alloc)(&pdata->aux_id); + + return 0; +} + +int git_win32_leakcheck_stack_compare( + git_win32_leakcheck_stack_raw_data *d1, + git_win32_leakcheck_stack_raw_data *d2) +{ + return memcmp(d1, d2, sizeof(*d1)); +} + +int git_win32_leakcheck_stack_format( + char *pbuf, size_t buf_len, + const git_win32_leakcheck_stack_raw_data *pdata, + const char *prefix, const char *suffix) +{ +#define MY_MAX_FILENAME 255 + + /* SYMBOL_INFO has char FileName[1] at the end. The docs say to + * to malloc it with extra space for your desired max filename. + */ + struct { + SYMBOL_INFO symbol; + char extra[MY_MAX_FILENAME + 1]; + } s; + + IMAGEHLP_LINE64 line; + size_t buf_used = 0; + unsigned int k; + char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ + size_t detail_len; + + if (!g_win32_stack_initialized) { + git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + if (!prefix) + prefix = "\t"; + if (!suffix) + suffix = "\n"; + + memset(pbuf, 0, buf_len); + + memset(&s, 0, sizeof(s)); + s.symbol.MaxNameLen = MY_MAX_FILENAME; + s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); + + memset(&line, 0, sizeof(line)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (k=0; k < pdata->nr_frames; k++) { + DWORD64 frame_k = (DWORD64)pdata->frames[k]; + DWORD dwUnused; + + if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && + SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { + const char *pslash; + const char *pfile; + + pslash = strrchr(line.FileName, '\\'); + pfile = ((pslash) ? (pslash+1) : line.FileName); + p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", + prefix, pfile, line.LineNumber, s.symbol.Name, suffix); + } else { + /* This happens when we cross into another module. + * For example, in CLAR tests, this is typically + * the CRT startup code. Just print an unknown + * frame and continue. + */ + p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); + } + detail_len = strlen(detail); + + if (buf_len < (buf_used + detail_len + 1)) { + /* we don't have room for this frame in the buffer, so just stop. */ + break; + } + + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle + * allocs that occur before the aux callbacks were registered. + */ + if (pdata->aux_id > 0) { + p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", + prefix, pdata->aux_id, suffix); + detail_len = strlen(detail); + if ((buf_used + detail_len + 1) < buf_len) { + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* If an "aux" data provider is still registered, ask it to append its detailed + * data to the end of ours using the "aux_id" it gave us when this de-duped + * item was created. + */ + if (g_aux_cb_lookup) + (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); + } + + return GIT_OK; +} + +int git_win32_leakcheck_stack( + char * pbuf, size_t buf_len, + int skip, + const char *prefix, const char *suffix) +{ + git_win32_leakcheck_stack_raw_data data; + int error; + + if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) + return error; + if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) + return error; + return 0; +} + +/* Stack tracing */ + +#define STACKTRACE_UID_LEN (15) + +/** + * The stacktrace of an allocation can be distilled + * to a unique id based upon the stackframe pointers + * and ignoring any size arguments. We will use these + * UIDs as the (char const*) __FILE__ argument we + * give to the CRT malloc routines. + */ +typedef struct { + char uid[STACKTRACE_UID_LEN + 1]; +} git_win32_leakcheck_stacktrace_uid; + +/** + * All mallocs with the same stacktrace will be de-duped + * and aggregated into this row. + */ +typedef struct { + git_win32_leakcheck_stacktrace_uid uid; /* must be first */ + git_win32_leakcheck_stack_raw_data raw_data; + unsigned int count_allocs; /* times this alloc signature seen since init */ + unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ + unsigned int transient_count_leaks; /* sum of leaks */ +} git_win32_leakcheck_stacktrace_row; + +static CRITICAL_SECTION g_crtdbg_stacktrace_cs; + +/** + * CRTDBG memory leak tracking takes a "char const * const file_name" + * and stores the pointer in the heap data (instead of allocing a copy + * for itself). Normally, this is not a problem, since we usually pass + * in __FILE__. But I'm going to lie to it and pass in the address of + * the UID in place of the file_name. Also, I do not want to alloc the + * stacktrace data (because we are called from inside our alloc routines). + * Therefore, I'm creating a very large static pool array to store row + * data. This also eliminates the temptation to realloc it (and move the + * UID pointers). + * + * And to efficiently look for duplicates we need an index on the rows + * so we can bsearch it. Again, without mallocing. + * + * If we observe more than MY_ROW_LIMIT unique malloc signatures, we + * fall through and use the traditional __FILE__ processing and don't + * try to de-dup them. If your testing hits this limit, just increase + * it and try again. + */ + +#define MY_ROW_LIMIT (2 * 1024 * 1024) +static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT]; +static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT]; + +static unsigned int g_cs_end = MY_ROW_LIMIT; +static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ +static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ +static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ +static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ +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 */ + +/** + * Compare function for bsearch on g_cs_index table. + */ +static int row_cmp(const void *v1, const void *v2) +{ + git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1; + git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2; + + return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); +} + +/** + * Unique insert the new data into the row and index tables. + * We have to sort by the stackframe data itself, not the uid. + */ +static git_win32_leakcheck_stacktrace_row * insert_unique( + const git_win32_leakcheck_stack_raw_data *pdata) +{ + size_t pos; + if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { + /* Append new unique item to row table. */ + memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); + sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); + + /* Insert pointer to it into the proper place in the index table. */ + if (pos < g_cs_ins) + memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); + g_cs_index[pos] = &g_cs_rows[g_cs_ins]; + + g_cs_ins++; + } + + g_cs_index[pos]->count_allocs++; + + return g_cs_index[pos]; +} + +/** + * Hook function to receive leak data from the CRT. (This includes + * both ":()" data, but also each of the + * various headers and fields. + * + * Scan this for the special "##" UID forms that we substituted + * for the "". Map back to the row data and + * increment its leak count. + * + * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx + * + * We suppress the actual crtdbg output. + */ +static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) +{ + static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ + unsigned int pos; + + *retVal = 0; /* do not invoke debugger */ + + if ((szMsg[0] != '#') || (szMsg[1] != '#')) + return hook_result; + + if (sscanf(&szMsg[2], "%08lx", &pos) < 1) + return hook_result; + if (pos >= g_cs_ins) + return hook_result; + + if (g_transient_leaks_since_mark) { + if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) + return hook_result; + } + + g_cs_rows[pos].transient_count_leaks++; + + if (g_cs_rows[pos].transient_count_leaks == 1) + g_transient_count_dedup_leaks++; + + g_transient_count_total_leaks++; + + return hook_result; +} + +/** + * Write leak data to all of the various places we need. + * We force the caller to sprintf() the message first + * because we want to avoid fprintf() because it allocs. + */ +static void my_output(const char *buf) +{ + fwrite(buf, strlen(buf), 1, stderr); + OutputDebugString(buf); +} + +/** + * For each row with leaks, dump a stacktrace for it. + */ +static void dump_summary(const char *label) +{ + unsigned int k; + char buf[10 * 1024]; + + if (g_transient_count_total_leaks == 0) + return; + + fflush(stdout); + fflush(stderr); + my_output("\n"); + + if (g_limit_reached) { + sprintf(buf, + "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", + MY_ROW_LIMIT); + my_output(buf); + } + + if (!label) + label = ""; + + if (g_transient_leaks_since_mark) { + sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", + g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } else { + sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", + g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } + my_output("\n"); + + for (k = 0; k < g_cs_ins; k++) { + if (g_cs_rows[k].transient_count_leaks > 0) { + sprintf(buf, "LEAK: %s leaked %d of %d times:\n", + g_cs_rows[k].uid.uid, + g_cs_rows[k].transient_count_leaks, + g_cs_rows[k].count_allocs); + my_output(buf); + + if (git_win32_leakcheck_stack_format( + buf, sizeof(buf), &g_cs_rows[k].raw_data, + NULL, NULL) >= 0) { + my_output(buf); + } + + my_output("\n"); + } + } + + fflush(stderr); +} + +/** + * Initialize our memory leak tracking and de-dup data structures. + * This should ONLY be called by git_libgit2_init(). + */ +void git_win32_leakcheck_stacktrace_init(void) +{ + InitializeCriticalSection(&g_crtdbg_stacktrace_cs); + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); +} + +int git_win32_leakcheck_stacktrace_dump( + git_win32_leakcheck_stacktrace_options opt, + const char *label) +{ + _CRT_REPORT_HOOK old; + unsigned int k; + int r = 0; + +#define IS_BIT_SET(o,b) (((o) & (b)) != 0) + + bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK); + bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK); + bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL); + bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET); + + if (b_leaks_since_mark && b_leaks_total) { + git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); + return GIT_ERROR; + } + if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { + git_error_set(GIT_ERROR_INVALID, "nothing to do."); + return GIT_ERROR; + } + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (b_leaks_since_mark || b_leaks_total) { + /* All variables with "transient" in the name are per-dump counters + * and reset before each dump. This lets us handle checkpoints. + */ + g_transient_count_total_leaks = 0; + g_transient_count_dedup_leaks = 0; + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].transient_count_leaks = 0; + } + } + + g_transient_leaks_since_mark = b_leaks_since_mark; + + old = _CrtSetReportHook(report_hook); + _CrtDumpMemoryLeaks(); + _CrtSetReportHook(old); + + if (b_leaks_since_mark || b_leaks_total) { + r = g_transient_count_dedup_leaks; + + if (!b_quiet) + dump_summary(label); + } + + if (b_set_mark) { + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; + } + + g_checkpoint_id++; + } + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return r; +} + +/** + * Shutdown our memory leak tracking and dump summary data. + * This should ONLY be called by git_libgit2_shutdown(). + * + * We explicitly call _CrtDumpMemoryLeaks() during here so + * that we can compute summary data for the leaks. We print + * the stacktrace of each unique leak. + * + * This cleanup does not happen if the app calls exit() + * without calling the libgit2 shutdown code. + * + * This info we print here is independent of any automatic + * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. + * Set it in your app if you also want traditional reporting. + */ +void git_win32_leakcheck_stacktrace_cleanup(void) +{ + /* At shutdown/cleanup, dump cumulative leak info + * with everything since startup. This might generate + * extra noise if the caller has been doing checkpoint + * dumps, but it might also eliminate some false + * positives for resources previously reported during + * checkpoints. + */ + git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, + "CLEANUP"); + + DeleteCriticalSection(&g_crtdbg_stacktrace_cs); +} + +const char *git_win32_leakcheck_stacktrace(int skip, const char *file) +{ + git_win32_leakcheck_stack_raw_data new_data; + git_win32_leakcheck_stacktrace_row *row; + const char * result = file; + + if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) + return result; + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (g_cs_ins < g_cs_end) { + row = insert_unique(&new_data); + result = row->uid.uid; + } else { + g_limit_reached = true; + } + + g_count_total_allocs++; + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return result; +} + +static void git_win32_leakcheck_global_shutdown(void) +{ + git_win32_leakcheck_stacktrace_cleanup(); + git_win32_leakcheck_stack_cleanup(); +} + +bool git_win32_leakcheck_has_leaks(void) +{ + return (g_transient_count_total_leaks > 0); +} + +int git_win32_leakcheck_global_init(void) +{ + git_win32_leakcheck_stacktrace_init(); + git_win32_leakcheck_stack_init(); + + return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); +} + +#else + +int git_win32_leakcheck_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/util/win32/w32_leakcheck.h b/src/util/win32/w32_leakcheck.h new file mode 100644 index 000000000..82d863851 --- /dev/null +++ b/src/util/win32/w32_leakcheck.h @@ -0,0 +1,222 @@ +/* + * 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_leakcheck_h__ +#define INCLUDE_win32_leakcheck_h__ + +#include "git2_util.h" + +/* Initialize the win32 leak checking system. */ +int git_win32_leakcheck_global_init(void); + +#if defined(GIT_WIN32_LEAKCHECK) + +#include +#include + +#include "git2/errors.h" +#include "strnlen.h" + +bool git_win32_leakcheck_has_leaks(void); + +/* Stack frames (for stack tracing, below) */ + +/** + * This type defines a callback to be used to augment a C stacktrace + * with "aux" data. This can be used, for example, to allow LibGit2Sharp + * (or other interpreted consumer libraries) to give us C# stacktrace + * data for the PInvoke. + * + * This callback will be called during crtdbg-instrumented allocs. + * + * @param aux_id [out] A returned "aux_id" representing a unique + * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved + * to mean no aux stacktrace data. + */ +typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id); + +/** + * This type defines a callback to be used to augment the output of + * a stacktrace. This will be used to request the C# layer format + * the C# stacktrace associated with "aux_id" into the provided + * buffer. + * + * This callback will be called during leak reporting. + * + * @param aux_id The "aux_id" key associated with a stacktrace. + * @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_leakcheck_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. + * + * This can be used, for example, to allow LibGit2Sharp (or other + * interpreted consumer libraries) to give us the C# stacktrace of + * the PInvoke. + * + * If you choose to use this feature, it should be registered during + * initialization and not changed for the duration of the process. + */ +int git_win32_leakcheck_stack_set_aux_cb( + git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, + git_win32_leakcheck_stack_aux_cb_lookup cb_lookup); + +/** + * Maximum number of stackframes to record for a + * single stacktrace. + */ +#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30 + +/** + * Wrapper containing the raw unprocessed stackframe + * data for a single stacktrace and any "aux_id". + * + * I put the aux_id first so leaks will be sorted by it. + * So, for example, if a specific callstack in C# leaks + * a repo handle, all of the pointers within the associated + * repo pointer will be grouped together. + */ +typedef struct { + unsigned int aux_id; + unsigned int nr_frames; + void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES]; +} git_win32_leakcheck_stack_raw_data; + +/** + * Capture raw stack trace data for the current process/thread. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + */ +int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip); + +/** + * Compare 2 raw stacktraces with the usual -1,0,+1 result. + * This includes any "aux_id" values in the comparison, so that + * our de-dup is also "aux" context relative. + */ +int git_win32_leakcheck_stack_compare( + git_win32_leakcheck_stack_raw_data *d1, + git_win32_leakcheck_stack_raw_data *d2); + +/** + * Format raw stacktrace data into buffer WITHOUT using any mallocs. + * + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32_leakcheck_stack_format( + char *pbuf, size_t buf_len, + const git_win32_leakcheck_stack_raw_data *pdata, + const char *prefix, const char *suffix); + +/** + * Convenience routine to capture and format stacktrace into + * a buffer WITHOUT using any mallocs. This is primarily a + * wrapper for testing. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32_leakcheck_stack( + char * pbuf, size_t buf_len, + int skip, + const char *prefix, const char *suffix); + +/* Stack tracing */ + +/* MSVC CRTDBG memory leak reporting. + * + * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC + * documentation because all allocs/frees in libgit2 already go through + * the "git__" routines defined in this file. Simply using the normal + * reporting mechanism causes all leaks to be attributed to a routine + * here in util.h (ie, the actual call to calloc()) rather than the + * caller of git__calloc(). + * + * Therefore, we declare a set of "git__crtdbg__" routines to replace + * the corresponding "git__" routines and re-define the "git__" symbols + * as macros. This allows us to get and report the file:line info of + * the real caller. + * + * We DO NOT replace the "git__free" routine because it needs to remain + * a function pointer because it is used as a function argument when + * setting up various structure "destructors". + * + * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes + * "free" to be remapped to "_free_dbg" and this causes problems for + * structures which define a field named "free". + * + * Finally, CRTDBG must be explicitly enabled and configured at program + * startup. See tests/main.c for an example. + */ + +/** + * Checkpoint options. + */ +typedef enum git_win32_leakcheck_stacktrace_options { + /** + * Set checkpoint marker. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0), + + /** + * Dump leaks since last checkpoint marker. + * May not be combined with _LEAKS_TOTAL. + * + * Note that this may generate false positives for global TLS + * error state and other global caches that aren't cleaned up + * until the thread/process terminates. So when using this + * around a region of interest, also check the final (at exit) + * dump before digging into leaks reported here. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1), + + /** + * Dump leaks since init. May not be combined + * with _LEAKS_SINCE_MARK. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2), + + /** + * Suppress printing during dumps. + * Just return leak count. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3), + +} git_win32_leakcheck_stacktrace_options; + +/** + * Checkpoint memory state and/or dump unique stack traces of + * current memory leaks. + * + * @return number of unique leaks (relative to requested starting + * point) or error. + */ +int git_win32_leakcheck_stacktrace_dump( + git_win32_leakcheck_stacktrace_options opt, + const char *label); + +/** + * Construct stacktrace and append it to the global buffer. + * Return pointer to start of this string. On any error or + * lack of buffer space, just return the given file buffer + * so it will behave as usual. + * + * This should ONLY be called by our internal memory allocations + * routines. + */ +const char *git_win32_leakcheck_stacktrace(int skip, const char *file); + +#endif +#endif diff --git a/src/util/win32/w32_util.c b/src/util/win32/w32_util.c new file mode 100644 index 000000000..fe4b75bae --- /dev/null +++ b/src/util/win32/w32_util.c @@ -0,0 +1,126 @@ +/* + * 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 "w32_util.h" + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) +{ + static const wchar_t suffix[] = L"\\*"; + int len = git_win32_path_from_utf8(dest, src); + + /* Ensure the path was converted */ + if (len < 0) + return false; + + /* Ensure that the path does not end with a trailing slash, + * because we're about to add one. Don't rely our trim_end + * helper, because we want to remove the backslash even for + * drive letter paths, in this case. */ + if (len > 0 && + (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { + dest[len - 1] = L'\0'; + len--; + } + + /* Ensure we have enough room to add the suffix */ + if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix)) + return false; + + wcscat(dest, suffix); + return true; +} + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * + * @param path The path which should receive the +H bit. + * @return 0 on success; -1 on failure + */ +int git_win32__set_hidden(const char *path, bool hidden) +{ + git_win32_path buf; + DWORD attrs, newattrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + if (hidden) + newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; + else + newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; + + if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { + git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'", + hidden ? "set" : "unset", path); + return -1; + } + + return 0; +} + +int git_win32__hidden(bool *out, const char *path) +{ + git_win32_path buf; + DWORD attrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + *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/util/win32/w32_util.h b/src/util/win32/w32_util.h new file mode 100644 index 000000000..519663720 --- /dev/null +++ b/src/util/win32/w32_util.h @@ -0,0 +1,144 @@ +/* + * 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_util_h__ +#define INCLUDE_win32_w32_util_h__ + +#include "git2_util.h" + +#include "utf-conv.h" +#include "posix.h" +#include "path_w32.h" + +/* + +#include "common.h" +#include "path.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "posix.h" +#include "reparse.h" +#include "dir.h" +*/ + + +GIT_INLINE(bool) git_win32__isalpha(wchar_t c) +{ + return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z')); +} + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set + * or unset. + * + * @param path The path that should receive the +H bit. + * @param hidden true to set +H, false to unset it + * @return 0 on success; -1 on failure + */ +extern int git_win32__set_hidden(const char *path, bool hidden); + +/** + * Determines if the given file or folder has the hidden attribute set. + * @param hidden pointer to store hidden value + * @param path The path that should be queried for hiddenness. + * @return 0 on success or an error code. + */ +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. + * + * @param FILETIME A pointer to a FILETIME + * @param ts A pointer to the timespec structure to fill in + */ +GIT_INLINE(void) git_win32__filetime_to_timespec( + const FILETIME *ft, + struct timespec *ts) +{ + int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */ + ts->tv_sec = (time_t)(winTime / 10000000); +#ifdef GIT_USE_NSEC + ts->tv_nsec = (winTime % 10000000) * 100; +#else + ts->tv_nsec = 0; +#endif +} + +GIT_INLINE(void) git_win32__timeval_to_filetime( + FILETIME *ft, const struct p_timeval tv) +{ + int64_t ticks = (tv.tv_sec * INT64_C(10000000)) + + (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000); + + ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff)); + ft->dwLowDateTime = (ticks & INT64_C(0xffffffff)); +} + +GIT_INLINE(void) git_win32__stat_init( + struct stat *st, + DWORD dwFileAttributes, + DWORD nFileSizeHigh, + DWORD nFileSizeLow, + FILETIME ftCreationTime, + FILETIME ftLastAccessTime, + FILETIME ftLastWriteTime) +{ + mode_t mode = S_IREAD; + + memset(st, 0, sizeof(struct stat)); + + if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mode |= S_IFDIR; + else + mode |= S_IFREG; + + if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + mode |= S_IWRITE; + + st->st_ino = 0; + st->st_gid = 0; + st->st_uid = 0; + st->st_nlink = 1; + st->st_mode = mode; + 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)); + git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); + git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); +} + +GIT_INLINE(void) git_win32__file_information_to_stat( + struct stat *st, + const BY_HANDLE_FILE_INFORMATION *fileinfo) +{ + git_win32__stat_init(st, + fileinfo->dwFileAttributes, + fileinfo->nFileSizeHigh, + fileinfo->nFileSizeLow, + fileinfo->ftCreationTime, + fileinfo->ftLastAccessTime, + fileinfo->ftLastWriteTime); +} + +#endif diff --git a/src/util/win32/win32-compat.h b/src/util/win32/win32-compat.h new file mode 100644 index 000000000..dee40a438 --- /dev/null +++ b/src/util/win32/win32-compat.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_win32_win32_compat_h__ +#define INCLUDE_win32_win32_compat_h__ + +#include +#include +#include +#include +#include + +typedef long suseconds_t; + +struct p_timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; + +struct p_timespec { + time_t tv_sec; + long tv_nsec; +}; + +#define timespec p_timespec + +struct p_stat { + _dev_t st_dev; + _ino_t st_ino; + mode_t st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec +#define st_atime_nsec st_atim.tv_nsec +#define st_mtime_nsec st_mtim.tv_nsec +#define st_ctime_nsec st_ctim.tv_nsec +}; + +#define stat p_stat + +#endif diff --git a/src/util/zstream.c b/src/util/zstream.c new file mode 100644 index 000000000..cb8b125ed --- /dev/null +++ b/src/util/zstream.c @@ -0,0 +1,210 @@ +/* + * 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 "zstream.h" + +#include + +#include "str.h" + +#define ZSTREAM_BUFFER_SIZE (1024 * 1024) +#define ZSTREAM_BUFFER_MIN_EXTRA 8 + +GIT_INLINE(int) zstream_seterr(git_zstream *zs) +{ + switch (zs->zerr) { + case Z_OK: + case Z_STREAM_END: + case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ + return 0; + case Z_MEM_ERROR: + git_error_set_oom(); + break; + default: + if (zs->z.msg) + git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); + else + git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); + } + + return -1; +} + +int git_zstream_init(git_zstream *zstream, git_zstream_t type) +{ + zstream->type = type; + + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflateInit(&zstream->z); + else + zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); + return zstream_seterr(zstream); +} + +void git_zstream_free(git_zstream *zstream) +{ + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateEnd(&zstream->z); + else + deflateEnd(&zstream->z); +} + +void git_zstream_reset(git_zstream *zstream) +{ + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateReset(&zstream->z); + else + deflateReset(&zstream->z); + zstream->in = NULL; + zstream->in_len = 0; + zstream->zerr = Z_STREAM_END; +} + +int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) +{ + zstream->in = in; + zstream->in_len = in_len; + zstream->zerr = Z_OK; + return 0; +} + +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) + return ZSTREAM_BUFFER_SIZE; + else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) + return zstream->in_len; + else + return ZSTREAM_BUFFER_MIN_EXTRA; +} + +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream) +{ + size_t in_queued, in_used, out_queued; + + /* set up input data */ + zstream->z.next_in = (Bytef *)zstream->in; + + /* feed as much data to zlib as it can consume, at most UINT_MAX */ + if (zstream->in_len > UINT_MAX) { + zstream->z.avail_in = UINT_MAX; + zstream->flush = Z_NO_FLUSH; + } else { + zstream->z.avail_in = (uInt)zstream->in_len; + zstream->flush = Z_FINISH; + } + in_queued = (size_t)zstream->z.avail_in; + + /* set up output data */ + zstream->z.next_out = out; + zstream->z.avail_out = (uInt)*out_len; + + if ((size_t)zstream->z.avail_out != *out_len) + zstream->z.avail_out = UINT_MAX; + out_queued = (size_t)zstream->z.avail_out; + + /* compress next chunk */ + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflate(&zstream->z, zstream->flush); + else + zstream->zerr = deflate(&zstream->z, zstream->flush); + + if (zstream_seterr(zstream)) + return -1; + + in_used = (in_queued - zstream->z.avail_in); + zstream->in_len -= in_used; + zstream->in += in_used; + + *out_len = (out_queued - zstream->z.avail_out); + + return 0; +} + +int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) +{ + size_t out_remain = *out_len; + + if (zstream->in_len && zstream->zerr == Z_STREAM_END) { + git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); + return -1; + } + + while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { + size_t out_written = out_remain; + + if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) + return -1; + + out_remain -= out_written; + out = ((char *)out) + out_written; + } + + /* either we finished the input or we did not flush the data */ + GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); + + /* set out_size to number of bytes actually written to output */ + *out_len = *out_len - out_remain; + + return 0; +} + +static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) +{ + git_zstream zs = GIT_ZSTREAM_INIT; + int error = 0; + + if ((error = git_zstream_init(&zs, type)) < 0) + return error; + + if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) + goto done; + + while (!git_zstream_done(&zs)) { + size_t step = git_zstream_suggest_output_len(&zs), written; + + if ((error = git_str_grow_by(out, step)) < 0) + goto done; + + written = out->asize - out->size; + + if ((error = git_zstream_get_output( + out->ptr + out->size, &written, &zs)) < 0) + goto done; + + out->size += written; + } + + /* NULL terminate for consistency if possible */ + if (out->size < out->asize) + out->ptr[out->size] = '\0'; + +done: + git_zstream_free(&zs); + return error; +} + +int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); +} + +int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); +} diff --git a/src/util/zstream.h b/src/util/zstream.h new file mode 100644 index 000000000..d78b11291 --- /dev/null +++ b/src/util/zstream.h @@ -0,0 +1,54 @@ +/* + * 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_zstream_h__ +#define INCLUDE_zstream_h__ + +#include "git2_util.h" + +#include + +#include "str.h" + +typedef enum { + GIT_ZSTREAM_INFLATE, + GIT_ZSTREAM_DEFLATE +} git_zstream_t; + +typedef struct { + z_stream z; + git_zstream_t type; + const char *in; + size_t in_len; + int flush; + int zerr; +} git_zstream; + +#define GIT_ZSTREAM_INIT {{0}} + +int git_zstream_init(git_zstream *zstream, git_zstream_t type); +void git_zstream_free(git_zstream *zstream); + +int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); + +size_t git_zstream_suggest_output_len(git_zstream *zstream); + +/* get as much output as is available in the input buffer */ +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream); + +/* get all the output from the entire input buffer */ +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); + +int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len); +int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len); + +#endif diff --git a/src/varint.c b/src/varint.c deleted file mode 100644 index 9ffc1d744..000000000 --- a/src/varint.c +++ /dev/null @@ -1,43 +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 "varint.h" - -uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) -{ - const unsigned char *buf = bufp; - unsigned char c = *buf++; - uintmax_t val = c & 127; - while (c & 128) { - val += 1; - if (!val || MSB(val, 7)) { - /* This is not a valid varint_len, so it signals - the error */ - *varint_len = 0; - return 0; /* overflow */ - } - c = *buf++; - val = (val << 7) + (c & 127); - } - *varint_len = buf - bufp; - return val; -} - -int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) -{ - unsigned char varint[16]; - unsigned pos = sizeof(varint) - 1; - varint[pos] = value & 127; - while (value >>= 7) - varint[--pos] = 128 | (--value & 127); - if (buf) { - if (bufsize < (sizeof(varint) - pos)) - return -1; - memcpy(buf, varint + pos, sizeof(varint) - pos); - } - return sizeof(varint) - pos; -} diff --git a/src/varint.h b/src/varint.h deleted file mode 100644 index 652e22486..000000000 --- a/src/varint.h +++ /dev/null @@ -1,17 +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_varint_h__ -#define INCLUDE_varint_h__ - -#include "common.h" - -#include - -extern int git_encode_varint(unsigned char *, size_t, uintmax_t); -extern uintmax_t git_decode_varint(const unsigned char *, size_t *); - -#endif diff --git a/src/vector.c b/src/vector.c deleted file mode 100644 index 4a4bc8c0e..000000000 --- a/src/vector.c +++ /dev/null @@ -1,431 +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 "vector.h" - -#include "integer.h" - -/* In elements, not bytes */ -#define MIN_ALLOCSIZE 8 - -GIT_INLINE(size_t) compute_new_size(git_vector *v) -{ - size_t new_size = v->_alloc_size; - - /* Use a resize factor of 1.5, which is quick to compute using integer - * instructions and less than the golden ratio (1.618...) */ - if (new_size < MIN_ALLOCSIZE) - new_size = MIN_ALLOCSIZE; - else if (new_size <= (SIZE_MAX / 3) * 2) - new_size += new_size / 2; - else - new_size = SIZE_MAX; - - return new_size; -} - -GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) -{ - void *new_contents; - - if (new_size == 0) - return 0; - - new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(new_contents); - - v->_alloc_size = new_size; - v->contents = new_contents; - - return 0; -} - -int git_vector_size_hint(git_vector *v, size_t size_hint) -{ - if (v->_alloc_size >= size_hint) - return 0; - return resize_vector(v, size_hint); -} - -int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) -{ - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(src); - - v->_alloc_size = 0; - v->contents = NULL; - v->_cmp = cmp ? cmp : src->_cmp; - v->length = src->length; - v->flags = src->flags; - if (cmp != src->_cmp) - git_vector_set_sorted(v, 0); - - if (src->length) { - size_t bytes; - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); - v->contents = git__malloc(bytes); - GIT_ERROR_CHECK_ALLOC(v->contents); - v->_alloc_size = src->length; - memcpy(v->contents, src->contents, bytes); - } - - return 0; -} - -void git_vector_free(git_vector *v) -{ - if (!v) - return; - - git__free(v->contents); - v->contents = NULL; - - v->length = 0; - v->_alloc_size = 0; -} - -void git_vector_free_deep(git_vector *v) -{ - size_t i; - - if (!v) - return; - - for (i = 0; i < v->length; ++i) { - git__free(v->contents[i]); - v->contents[i] = NULL; - } - - git_vector_free(v); -} - -int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) -{ - GIT_ASSERT_ARG(v); - - v->_alloc_size = 0; - v->_cmp = cmp; - v->length = 0; - v->flags = GIT_VECTOR_SORTED; - v->contents = NULL; - - return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); -} - -void **git_vector_detach(size_t *size, size_t *asize, git_vector *v) -{ - void **data = v->contents; - - if (size) - *size = v->length; - if (asize) - *asize = v->_alloc_size; - - v->_alloc_size = 0; - v->length = 0; - v->contents = NULL; - - return data; -} - -int git_vector_insert(git_vector *v, void *element) -{ - GIT_ASSERT_ARG(v); - - if (v->length >= v->_alloc_size && - resize_vector(v, compute_new_size(v)) < 0) - return -1; - - v->contents[v->length++] = element; - - git_vector_set_sorted(v, v->length <= 1); - - return 0; -} - -int git_vector_insert_sorted( - git_vector *v, void *element, int (*on_dup)(void **old, void *new)) -{ - int result; - size_t pos; - - GIT_ASSERT_ARG(v); - GIT_ASSERT(v->_cmp); - - if (!git_vector_is_sorted(v)) - git_vector_sort(v); - - if (v->length >= v->_alloc_size && - resize_vector(v, compute_new_size(v)) < 0) - return -1; - - /* If we find the element and have a duplicate handler callback, - * invoke it. If it returns non-zero, then cancel insert, otherwise - * proceed with normal insert. - */ - if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) && - on_dup && (result = on_dup(&v->contents[pos], element)) < 0) - return result; - - /* shift elements to the right */ - if (pos < v->length) - memmove(v->contents + pos + 1, v->contents + pos, - (v->length - pos) * sizeof(void *)); - - v->contents[pos] = element; - v->length++; - - return 0; -} - -void git_vector_sort(git_vector *v) -{ - if (git_vector_is_sorted(v) || !v->_cmp) - return; - - if (v->length > 1) - git__tsort(v->contents, v->length, v->_cmp); - git_vector_set_sorted(v, 1); -} - -int git_vector_bsearch2( - size_t *at_pos, - git_vector *v, - git_vector_cmp key_lookup, - const void *key) -{ - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(key); - GIT_ASSERT(key_lookup); - - /* need comparison function to sort the vector */ - if (!v->_cmp) - return -1; - - git_vector_sort(v); - - return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); -} - -int git_vector_search2( - size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) -{ - size_t i; - - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(key); - GIT_ASSERT(key_lookup); - - for (i = 0; i < v->length; ++i) { - if (key_lookup(key, v->contents[i]) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } - - return GIT_ENOTFOUND; -} - -static int strict_comparison(const void *a, const void *b) -{ - return (a == b) ? 0 : -1; -} - -int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) -{ - return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); -} - -int git_vector_remove(git_vector *v, size_t idx) -{ - size_t shift_count; - - GIT_ASSERT_ARG(v); - - if (idx >= v->length) - return GIT_ENOTFOUND; - - shift_count = v->length - idx - 1; - - if (shift_count) - memmove(&v->contents[idx], &v->contents[idx + 1], - shift_count * sizeof(void *)); - - v->length--; - return 0; -} - -void git_vector_pop(git_vector *v) -{ - if (v->length > 0) - v->length--; -} - -void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) -{ - git_vector_cmp cmp; - size_t i, j; - - if (v->length <= 1) - return; - - git_vector_sort(v); - cmp = v->_cmp ? v->_cmp : strict_comparison; - - for (i = 0, j = 1 ; j < v->length; ++j) - if (!cmp(v->contents[i], v->contents[j])) { - if (git_free_cb) - git_free_cb(v->contents[i]); - - v->contents[i] = v->contents[j]; - } else - v->contents[++i] = v->contents[j]; - - v->length -= j - i - 1; -} - -void git_vector_remove_matching( - git_vector *v, - int (*match)(const git_vector *v, size_t idx, void *payload), - void *payload) -{ - size_t i, j; - - for (i = 0, j = 0; j < v->length; ++j) { - v->contents[i] = v->contents[j]; - - if (!match(v, i, payload)) - i++; - } - - v->length = i; -} - -void git_vector_clear(git_vector *v) -{ - v->length = 0; - git_vector_set_sorted(v, 1); -} - -void git_vector_swap(git_vector *a, git_vector *b) -{ - git_vector t; - - if (a != b) { - memcpy(&t, a, sizeof(t)); - memcpy(a, b, sizeof(t)); - memcpy(b, &t, sizeof(t)); - } -} - -int git_vector_resize_to(git_vector *v, size_t new_length) -{ - if (new_length > v->_alloc_size && - resize_vector(v, new_length) < 0) - return -1; - - if (new_length > v->length) - memset(&v->contents[v->length], 0, - sizeof(void *) * (new_length - v->length)); - - v->length = new_length; - - return 0; -} - -int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) -{ - size_t new_length; - - GIT_ASSERT_ARG(insert_len > 0); - GIT_ASSERT_ARG(idx <= v->length); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); - - if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) - return -1; - - memmove(&v->contents[idx + insert_len], &v->contents[idx], - sizeof(void *) * (v->length - idx)); - memset(&v->contents[idx], 0, sizeof(void *) * insert_len); - - v->length = new_length; - return 0; -} - -int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) -{ - size_t new_length = v->length - remove_len; - size_t end_idx = 0; - - GIT_ASSERT_ARG(remove_len > 0); - - if (git__add_sizet_overflow(&end_idx, idx, remove_len)) - GIT_ASSERT(0); - - GIT_ASSERT(end_idx <= v->length); - - if (end_idx < v->length) - memmove(&v->contents[idx], &v->contents[end_idx], - sizeof(void *) * (v->length - end_idx)); - - memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); - - v->length = new_length; - return 0; -} - -int git_vector_set(void **old, git_vector *v, size_t position, void *value) -{ - if (position + 1 > v->length) { - if (git_vector_resize_to(v, position + 1) < 0) - return -1; - } - - if (old != NULL) - *old = v->contents[position]; - - v->contents[position] = value; - - return 0; -} - -int git_vector_verify_sorted(const git_vector *v) -{ - size_t i; - - if (!git_vector_is_sorted(v)) - return -1; - - for (i = 1; i < v->length; ++i) { - if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0) - return -1; - } - - return 0; -} - -void git_vector_reverse(git_vector *v) -{ - size_t a, b; - - if (v->length == 0) - return; - - a = 0; - b = v->length - 1; - - while (a < b) { - void *tmp = v->contents[a]; - v->contents[a] = v->contents[b]; - v->contents[b] = tmp; - a++; - b--; - } -} diff --git a/src/vector.h b/src/vector.h deleted file mode 100644 index ae3c79a4c..000000000 --- a/src/vector.h +++ /dev/null @@ -1,128 +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_vector_h__ -#define INCLUDE_vector_h__ - -#include "common.h" - -typedef int (*git_vector_cmp)(const void *, const void *); - -enum { - GIT_VECTOR_SORTED = (1u << 0), - GIT_VECTOR_FLAG_MAX = (1u << 1) -}; - -typedef struct git_vector { - size_t _alloc_size; - git_vector_cmp _cmp; - void **contents; - size_t length; - uint32_t flags; -} git_vector; - -#define GIT_VECTOR_INIT {0} - -GIT_WARN_UNUSED_RESULT int git_vector_init( - git_vector *v, size_t initial_size, git_vector_cmp cmp); -void git_vector_free(git_vector *v); -void git_vector_free_deep(git_vector *v); /* free each entry and self */ -void git_vector_clear(git_vector *v); -GIT_WARN_UNUSED_RESULT int git_vector_dup( - git_vector *v, const git_vector *src, git_vector_cmp cmp); -void git_vector_swap(git_vector *a, git_vector *b); -int git_vector_size_hint(git_vector *v, size_t size_hint); - -void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); - -void git_vector_sort(git_vector *v); - -/** Linear search for matching entry using internal comparison function */ -int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); - -/** Linear search for matching entry using explicit comparison function */ -int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); - -/** - * Binary search for matching entry using explicit comparison function that - * returns position where item would go if not found. - */ -int git_vector_bsearch2( - size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); - -/** Binary search for matching entry using internal comparison function */ -GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) -{ - return git_vector_bsearch2(at_pos, v, v->_cmp, key); -} - -GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) -{ - return (position < v->length) ? v->contents[position] : NULL; -} - -#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) - -GIT_INLINE(size_t) git_vector_length(const git_vector *v) -{ - return v->length; -} - -GIT_INLINE(void *) git_vector_last(const git_vector *v) -{ - return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; -} - -#define git_vector_foreach(v, iter, elem) \ - for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) - -#define git_vector_rforeach(v, iter, elem) \ - for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) - -int git_vector_insert(git_vector *v, void *element); -int git_vector_insert_sorted(git_vector *v, void *element, - int (*on_dup)(void **old, void *new)); -int git_vector_remove(git_vector *v, size_t idx); -void git_vector_pop(git_vector *v); -void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); - -void git_vector_remove_matching( - git_vector *v, - int (*match)(const git_vector *v, size_t idx, void *payload), - void *payload); - -int git_vector_resize_to(git_vector *v, size_t new_length); -int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); -int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); - -int git_vector_set(void **old, git_vector *v, size_t position, void *value); - -/** Check if vector is sorted */ -#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0) - -/** Directly set sorted state of vector */ -#define git_vector_set_sorted(V,S) do { \ - (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \ - ((V)->flags & ~GIT_VECTOR_SORTED); } while (0) - -/** Set the comparison function used for sorting the vector */ -GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) -{ - if (cmp != v->_cmp) { - v->_cmp = cmp; - git_vector_set_sorted(v, 0); - } -} - -/* Just use this in tests, not for realz. returns -1 if not sorted */ -int git_vector_verify_sorted(const git_vector *v); - -/** - * Reverse the vector in-place. - */ -void git_vector_reverse(git_vector *v); - -#endif diff --git a/src/wildmatch.c b/src/wildmatch.c deleted file mode 100644 index a894e4841..000000000 --- a/src/wildmatch.c +++ /dev/null @@ -1,320 +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. - * - * 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 deleted file mode 100644 index 44bb575a6..000000000 --- a/src/wildmatch.h +++ /dev/null @@ -1,23 +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_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/dir.c b/src/win32/dir.c deleted file mode 100644 index 44052caf0..000000000 --- a/src/win32/dir.c +++ /dev/null @@ -1,122 +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 "dir.h" - -#define GIT__WIN32_NO_WRAP_DIR -#include "posix.h" - -git__DIR *git__opendir(const char *dir) -{ - git_win32_path filter_w; - git__DIR *new = NULL; - size_t dirlen, alloclen; - - if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) - return NULL; - - dirlen = strlen(dir); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) || - GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) || - !(new = git__calloc(1, alloclen))) - return NULL; - - memcpy(new->dir, dir, dirlen); - - new->h = FindFirstFileW(filter_w, &new->f); - - if (new->h == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir); - git__free(new); - return NULL; - } - - new->first = 1; - return new; -} - -int git__readdir_ext( - git__DIR *d, - struct git__dirent *entry, - struct git__dirent **result, - int *is_dir) -{ - if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) - return -1; - - *result = NULL; - - if (d->first) - d->first = 0; - else if (!FindNextFileW(d->h, &d->f)) { - if (GetLastError() == ERROR_NO_MORE_FILES) - return 0; - git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir); - return -1; - } - - /* Convert the path to UTF-8 */ - if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) - return -1; - - entry->d_ino = 0; - - *result = entry; - - if (is_dir != NULL) - *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); - - return 0; -} - -struct git__dirent *git__readdir(git__DIR *d) -{ - struct git__dirent *result; - if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) - return NULL; - return result; -} - -void git__rewinddir(git__DIR *d) -{ - git_win32_path filter_w; - - if (!d) - return; - - if (d->h != INVALID_HANDLE_VALUE) { - FindClose(d->h); - d->h = INVALID_HANDLE_VALUE; - d->first = 0; - } - - if (!git_win32__findfirstfile_filter(filter_w, d->dir)) - return; - - d->h = FindFirstFileW(filter_w, &d->f); - - if (d->h == INVALID_HANDLE_VALUE) - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir); - else - d->first = 1; -} - -int git__closedir(git__DIR *d) -{ - if (!d) - return 0; - - if (d->h != INVALID_HANDLE_VALUE) { - FindClose(d->h); - d->h = INVALID_HANDLE_VALUE; - } - - git__free(d); - return 0; -} - diff --git a/src/win32/dir.h b/src/win32/dir.h deleted file mode 100644 index acd64729e..000000000 --- a/src/win32/dir.h +++ /dev/null @@ -1,44 +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_win32_dir_h__ -#define INCLUDE_win32_dir_h__ - -#include "common.h" - -#include "w32_util.h" - -struct git__dirent { - int d_ino; - git_win32_utf8_path d_name; -}; - -typedef struct { - HANDLE h; - WIN32_FIND_DATAW f; - struct git__dirent entry; - int first; - char dir[GIT_FLEX_ARRAY]; -} git__DIR; - -extern git__DIR *git__opendir(const char *); -extern struct git__dirent *git__readdir(git__DIR *); -extern int git__readdir_ext( - git__DIR *, struct git__dirent *, struct git__dirent **, int *); -extern void git__rewinddir(git__DIR *); -extern int git__closedir(git__DIR *); - -# ifndef GIT__WIN32_NO_WRAP_DIR -# define dirent git__dirent -# define DIR git__DIR -# define opendir git__opendir -# define readdir git__readdir -# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) -# define rewinddir git__rewinddir -# define closedir git__closedir -# endif - -#endif diff --git a/src/win32/error.c b/src/win32/error.c deleted file mode 100644 index 3a52fb5a9..000000000 --- a/src/win32/error.c +++ /dev/null @@ -1,53 +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 "error.h" - -#include "utf-conv.h" - -#ifdef GIT_WINHTTP -# include -#endif - -char *git_win32_get_error_message(DWORD error_code) -{ - LPWSTR lpMsgBuf = NULL; - HMODULE hModule = NULL; - char *utf8_msg = NULL; - DWORD dwFlags = - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; - - if (!error_code) - return NULL; - -#ifdef GIT_WINHTTP - /* Errors raised by WinHTTP are not in the system resource table */ - if (error_code >= WINHTTP_ERROR_BASE && - error_code <= WINHTTP_ERROR_LAST) - hModule = GetModuleHandleW(L"winhttp"); -#endif - - GIT_UNUSED(hModule); - - if (hModule) - dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; - else - dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; - - if (FormatMessageW(dwFlags, hModule, error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&lpMsgBuf, 0, NULL)) { - /* Convert the message to UTF-8. If this fails, we will - * return NULL, which is a condition expected by the caller */ - if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) - utf8_msg = NULL; - - LocalFree(lpMsgBuf); - } - - return utf8_msg; -} diff --git a/src/win32/error.h b/src/win32/error.h deleted file mode 100644 index 9e81141ce..000000000 --- a/src/win32/error.h +++ /dev/null @@ -1,15 +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_win32_error_h__ -#define INCLUDE_win32_error_h__ - -#include "common.h" - -extern char *git_win32_get_error_message(DWORD error_code); - -#endif diff --git a/src/win32/findfile.c b/src/win32/findfile.c deleted file mode 100644 index 725a90167..000000000 --- a/src/win32/findfile.c +++ /dev/null @@ -1,286 +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 "findfile.h" - -#include "path_w32.h" -#include "utf-conv.h" -#include "fs_path.h" - -#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" -#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" - -static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) -{ - DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); - - if (!len || len > GIT_WIN_PATH_UTF16) - return -1; - - return 0; -} - -static int win32_path_to_8(git_str *dest, const wchar_t *src) -{ - git_win32_utf8_path utf8_path; - - if (git_win32_path_to_utf8(utf8_path, src) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); - return -1; - } - - /* Convert backslashes to forward slashes */ - git_fs_path_mkposix(utf8_path); - - return git_str_sets(dest, utf8_path); -} - -static git_win32_path mock_registry; -static bool mock_registry_set; - -extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) -{ - if (!mock_sysdir) { - mock_registry[0] = L'\0'; - mock_registry_set = false; - } else { - size_t len = wcslen(mock_sysdir); - - if (len > GIT_WIN_PATH_MAX) { - git_error_set(GIT_ERROR_INVALID, "mock path too long"); - return -1; - } - - wcscpy(mock_registry, mock_sysdir); - mock_registry_set = true; - } - - return 0; -} - -static int lookup_registry_key( - git_win32_path out, - const HKEY hive, - const wchar_t* key, - const wchar_t *value) -{ - HKEY hkey; - DWORD type, size; - int error = GIT_ENOTFOUND; - - /* - * Registry data may not be NUL terminated, provide room to do - * it ourselves. - */ - size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); - - if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) - return GIT_ENOTFOUND; - - if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && - type == REG_SZ && - size > 0 && - size < sizeof(git_win32_path)) { - size_t wsize = size / sizeof(wchar_t); - size_t len = wsize - 1; - - if (out[wsize - 1] != L'\0') { - len = wsize; - out[wsize] = L'\0'; - } - - if (out[len - 1] == L'\\') - out[len - 1] = L'\0'; - - if (_waccess(out, F_OK) == 0) - error = 0; - } - - RegCloseKey(hkey); - return error; -} - -static int find_sysdir_in_registry(git_win32_path out) -{ - if (mock_registry_set) { - if (mock_registry[0] == L'\0') - return GIT_ENOTFOUND; - - wcscpy(out, mock_registry); - return 0; - } - - if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) - return 0; - - return GIT_ENOTFOUND; -} - -static int find_sysdir_in_path(git_win32_path out) -{ - size_t out_len; - - if (git_win32_path_find_executable(out, L"git.exe") < 0 && - git_win32_path_find_executable(out, L"git.cmd") < 0) - return GIT_ENOTFOUND; - - out_len = wcslen(out); - - /* Trim the file name */ - if (out_len <= CONST_STRLEN(L"git.exe")) - return GIT_ENOTFOUND; - - out_len -= CONST_STRLEN(L"git.exe"); - - if (out_len && out[out_len - 1] == L'\\') - out_len--; - - /* - * Git for Windows usually places the command in a 'bin' or - * 'cmd' directory, trim that. - */ - if (out_len >= CONST_STRLEN(L"\\bin") && - wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) - out_len -= CONST_STRLEN(L"\\bin"); - else if (out_len >= CONST_STRLEN(L"\\cmd") && - wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) - out_len -= CONST_STRLEN(L"\\cmd"); - - if (!out_len) - return GIT_ENOTFOUND; - - out[out_len] = L'\0'; - return 0; -} - -static int win32_find_existing_dirs( - git_str* out, - const wchar_t* tmpl[]) -{ - git_win32_path path16; - git_str buf = GIT_STR_INIT; - - git_str_clear(out); - - for (; *tmpl != NULL; tmpl++) { - if (!git_win32__expand_path(path16, *tmpl) && - path16[0] != L'%' && - !_waccess(path16, F_OK)) { - win32_path_to_8(&buf, path16); - - if (buf.size) - git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); - } - } - - git_str_dispose(&buf); - - return (git_str_oom(out) ? -1 : 0); -} - -static int append_subdir(git_str *out, git_str *path, const char *subdir) -{ - static const char* architecture_roots[] = { - "", - "mingw64", - "mingw32", - NULL - }; - const char **root; - size_t orig_path_len = path->size; - - for (root = architecture_roots; *root; root++) { - if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || - git_str_joinpath(path, path->ptr, subdir) < 0) - return -1; - - if (git_fs_path_exists(path->ptr) && - git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) - return -1; - - git_str_truncate(path, orig_path_len); - } - - return 0; -} - -int git_win32__find_system_dirs(git_str *out, const char *subdir) -{ - git_win32_path pathdir, regdir; - git_str path8 = GIT_STR_INIT; - bool has_pathdir, has_regdir; - int error; - - has_pathdir = (find_sysdir_in_path(pathdir) == 0); - has_regdir = (find_sysdir_in_registry(regdir) == 0); - - if (!has_pathdir && !has_regdir) - return 0; - - /* - * Usually the git in the path is the same git in the registry, - * in this case there's no need to duplicate the paths. - */ - if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) - has_regdir = false; - - if (has_pathdir) { - if ((error = win32_path_to_8(&path8, pathdir)) < 0 || - (error = append_subdir(out, &path8, subdir)) < 0) - goto done; - } - - if (has_regdir) { - if ((error = win32_path_to_8(&path8, regdir)) < 0 || - (error = append_subdir(out, &path8, subdir)) < 0) - goto done; - } - -done: - git_str_dispose(&path8); - return error; -} - -int git_win32__find_global_dirs(git_str *out) -{ - static const wchar_t *global_tmpls[4] = { - L"%HOME%\\", - L"%HOMEDRIVE%%HOMEPATH%\\", - L"%USERPROFILE%\\", - NULL, - }; - - return win32_find_existing_dirs(out, global_tmpls); -} - -int git_win32__find_xdg_dirs(git_str *out) -{ - static const wchar_t *global_tmpls[7] = { - L"%XDG_CONFIG_HOME%\\git", - L"%APPDATA%\\git", - L"%LOCALAPPDATA%\\git", - L"%HOME%\\.config\\git", - L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", - L"%USERPROFILE%\\.config\\git", - NULL, - }; - - return win32_find_existing_dirs(out, global_tmpls); -} - -int git_win32__find_programdata_dirs(git_str *out) -{ - static const wchar_t *programdata_tmpls[2] = { - L"%PROGRAMDATA%\\Git", - NULL, - }; - - return win32_find_existing_dirs(out, programdata_tmpls); -} diff --git a/src/win32/findfile.h b/src/win32/findfile.h deleted file mode 100644 index 61fb7dbad..000000000 --- a/src/win32/findfile.h +++ /dev/null @@ -1,22 +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_win32_findfile_h__ -#define INCLUDE_win32_findfile_h__ - -#include "common.h" - -/** Sets the mock registry root for Git for Windows for testing. */ -extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); - -extern int git_win32__find_system_dirs(git_str *out, const char *subpath); -extern int git_win32__find_global_dirs(git_str *out); -extern int git_win32__find_xdg_dirs(git_str *out); -extern int git_win32__find_programdata_dirs(git_str *out); - -#endif - diff --git a/src/win32/git2.rc b/src/win32/git2.rc deleted file mode 100644 index d273afd70..000000000 --- a/src/win32/git2.rc +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include "../../include/git2/version.h" - -#ifndef LIBGIT2_FILENAME -# ifdef __GNUC__ -# define LIBGIT2_FILENAME git2 -# else -# define LIBGIT2_FILENAME "git2" -# endif -#endif - -#ifndef LIBGIT2_COMMENTS -# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" -#endif - -#ifdef __GNUC__ -# define _STR(x) #x -# define STR(x) _STR(x) -#else -# define STR(x) x -#endif - -#ifdef __GNUC__ -VS_VERSION_INFO VERSIONINFO -#else -VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE -#endif - FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0 -#endif - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - //language ID = U.S. English, char set = Windows, Multilingual - BEGIN - VALUE "FileDescription", "libgit2 - the Git linkable library\0" - VALUE "FileVersion", LIBGIT2_VERSION "\0" - VALUE "InternalName", STR(LIBGIT2_FILENAME) ".dll\0" - VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0" - VALUE "OriginalFilename", STR(LIBGIT2_FILENAME) ".dll\0" - VALUE "ProductName", "libgit2\0" - VALUE "ProductVersion", LIBGIT2_VERSION "\0" - VALUE "Comments", LIBGIT2_COMMENTS "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409, 1252 - END -END diff --git a/src/win32/map.c b/src/win32/map.c deleted file mode 100644 index 2aabc9b15..000000000 --- a/src/win32/map.c +++ /dev/null @@ -1,141 +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 "common.h" - -#include "map.h" -#include - -#ifndef NO_MMAP - -static DWORD get_page_size(void) -{ - static DWORD page_size; - SYSTEM_INFO sys; - - if (!page_size) { - GetSystemInfo(&sys); - page_size = sys.dwPageSize; - } - - return page_size; -} - -static DWORD get_allocation_granularity(void) -{ - static DWORD granularity; - SYSTEM_INFO sys; - - if (!granularity) { - GetSystemInfo(&sys); - granularity = sys.dwAllocationGranularity; - } - - return granularity; -} - -int git__page_size(size_t *page_size) -{ - *page_size = get_page_size(); - return 0; -} - -int git__mmap_alignment(size_t *page_size) -{ - *page_size = get_allocation_granularity(); - return 0; -} - -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(); - DWORD fmap_prot = 0; - DWORD view_prot = 0; - DWORD off_low = 0; - DWORD off_hi = 0; - off64_t page_start; - off64_t page_offset; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - out->data = NULL; - out->len = 0; - out->fmh = NULL; - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); - return -1; - } - - if (prot & GIT_PROT_WRITE) - fmap_prot |= PAGE_READWRITE; - else if (prot & GIT_PROT_READ) - fmap_prot |= PAGE_READONLY; - - if (prot & GIT_PROT_WRITE) - view_prot |= FILE_MAP_WRITE; - if (prot & GIT_PROT_READ) - view_prot |= FILE_MAP_READ; - - page_start = (offset / alignment) * alignment; - page_offset = offset - page_start; - - if (page_offset != 0) { /* offset must be multiple of the allocation granularity */ - errno = EINVAL; - git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity"); - return -1; - } - - out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); - if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); - out->fmh = NULL; - return -1; - } - - off_low = (DWORD)(page_start); - off_hi = (DWORD)(page_start >> 32); - out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); - if (!out->data) { - git_error_set(GIT_ERROR_OS, "failed to mmap. No data written"); - CloseHandle(out->fmh); - out->fmh = NULL; - return -1; - } - out->len = len; - - return 0; -} - -int p_munmap(git_map *map) -{ - int error = 0; - - GIT_ASSERT_ARG(map); - - if (map->data) { - if (!UnmapViewOfFile(map->data)) { - git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file"); - error = -1; - } - map->data = NULL; - } - - if (map->fmh) { - if (!CloseHandle(map->fmh)) { - git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle"); - error = -1; - } - map->fmh = NULL; - } - - return error; -} - -#endif diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h deleted file mode 100644 index aa2bef98d..000000000 --- a/src/win32/mingw-compat.h +++ /dev/null @@ -1,23 +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_win32_mingw_compat_h__ -#define INCLUDE_win32_mingw_compat_h__ - -#if defined(__MINGW32__) - -#undef stat - -#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) -#undef MemoryBarrier -void __mingworg_MemoryBarrier(void); -#define MemoryBarrier __mingworg_MemoryBarrier -#define VOLUME_NAME_DOS 0x0 -#endif - -#endif - -#endif diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h deleted file mode 100644 index 03f9f36dc..000000000 --- a/src/win32/msvc-compat.h +++ /dev/null @@ -1,36 +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_win32_msvc_compat_h__ -#define INCLUDE_win32_msvc_compat_h__ - -#if defined(_MSC_VER) - -typedef unsigned short mode_t; -typedef SSIZE_T ssize_t; - -#ifdef _WIN64 -# define SSIZE_MAX _I64_MAX -#else -# define SSIZE_MAX LONG_MAX -#endif - -#define strcasecmp(s1, s2) _stricmp(s1, s2) -#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) - -#endif - -/* - * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always). - * This is useful for providing callbacks to userspace code. - * - * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on - * Win32). Useful for providing callbacks to system libraries. - */ -#define GIT_LIBGIT2_CALL __cdecl -#define GIT_SYSTEM_CALL NTAPI - -#endif diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c deleted file mode 100644 index d9fc8292b..000000000 --- a/src/win32/path_w32.c +++ /dev/null @@ -1,642 +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 "path_w32.h" - -#include "fs_path.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" - -#define PATH__NT_NAMESPACE L"\\\\?\\" -#define PATH__NT_NAMESPACE_LEN 4 - -#define PATH__ABSOLUTE_LEN 3 - -#define path__is_nt_namespace(p) \ - (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ - ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) - -#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; - - if ((len = GetCurrentDirectoryW(size, path)) == 0) { - errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; - return -1; - } else if (len > size) { - errno = ENAMETOOLONG; - return -1; - } - - /* The Win32 APIs may return "\\?\" once you've used it first. - * But it may not. What a gloriously predictable API! - */ - if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) - return len; - - len -= PATH__NT_NAMESPACE_LEN; - - memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); - return len; -} - -static wchar_t *path__skip_server(wchar_t *path) -{ - wchar_t *c; - - for (c = path; *c; c++) { - if (git_fs_path_is_dirsep(*c)) - return c + 1; - } - - return c; -} - -static wchar_t *path__skip_prefix(wchar_t *path) -{ - if (path__is_nt_namespace(path)) { - path += PATH__NT_NAMESPACE_LEN; - - if (wcsncmp(path, L"UNC\\", 4) == 0) - path = path__skip_server(path + 4); - else if (git_fs_path_is_absolute(path)) - path += PATH__ABSOLUTE_LEN; - } else if (git_fs_path_is_absolute(path)) { - path += PATH__ABSOLUTE_LEN; - } else if (path__is_unc(path)) { - path = path__skip_server(path + 2); - } - - return path; -} - -int git_win32_path_canonicalize(git_win32_path path) -{ - wchar_t *base, *from, *to, *next; - size_t len; - - base = to = path__skip_prefix(path); - - /* Unposixify if the prefix */ - for (from = path; from < to; from++) { - if (*from == L'/') - *from = L'\\'; - } - - while (*from) { - for (next = from; *next; ++next) { - if (*next == L'/') { - *next = L'\\'; - break; - } - - if (*next == L'\\') - break; - } - - len = next - from; - - if (len == 1 && from[0] == L'.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == L'.' && from[1] == L'.') { - if (to == base) { - /* no more path segments to strip, eat the "../" */ - if (*next == L'\\') - len++; - - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == L'\\') to--; - while (to > base && to[-1] != L'\\') to--; - } - } else { - if (*next == L'\\' && *from != L'\\') - len++; - - if (to != from) - memmove(to, from, sizeof(wchar_t) * len); - - to += len; - } - - from += len; - - while (*from == L'\\') from++; - } - - /* Strip trailing backslashes */ - while (to > base && to[-1] == L'\\') to--; - - *to = L'\0'; - - if ((to - path) > INT_MAX) { - SetLastError(ERROR_FILENAME_EXCED_RANGE); - return -1; - } - - return (int)(to - path); -} - -static int git_win32_path_join( - git_win32_path dest, - const wchar_t *one, - size_t one_len, - const wchar_t *two, - size_t two_len) -{ - size_t backslash = 0; - - if (one_len && two_len && one[one_len - 1] != L'\\') - backslash = 1; - - if (one_len + two_len + backslash > MAX_PATH) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - return -1; - } - - memmove(dest, one, one_len * sizeof(wchar_t)); - - if (backslash) - dest[one_len] = L'\\'; - - memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); - dest[one_len + backslash + two_len] = L'\0'; - - return 0; -} - -struct win32_path_iter { - wchar_t *env; - const wchar_t *current_dir; -}; - -static int win32_path_iter_init(struct win32_path_iter *iter) -{ - DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); - - if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { - iter->env = NULL; - iter->current_dir = NULL; - return 0; - } else if (!len) { - git_error_set(GIT_ERROR_OS, "could not load PATH"); - return -1; - } - - iter->env = git__malloc(len * sizeof(wchar_t)); - GIT_ERROR_CHECK_ALLOC(iter->env); - - len = GetEnvironmentVariableW(L"PATH", iter->env, len); - - if (len == 0) { - git_error_set(GIT_ERROR_OS, "could not load PATH"); - return -1; - } - - iter->current_dir = iter->env; - return 0; -} - -static int win32_path_iter_next( - const wchar_t **out, - size_t *out_len, - struct win32_path_iter *iter) -{ - const wchar_t *start; - wchar_t term; - size_t len = 0; - - if (!iter->current_dir || !*iter->current_dir) - return GIT_ITEROVER; - - term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; - start = iter->current_dir; - - while (*iter->current_dir && *iter->current_dir != term) { - iter->current_dir++; - len++; - } - - *out = start; - *out_len = len; - - if (term == L'"' && *iter->current_dir) - iter->current_dir++; - - while (*iter->current_dir == L';') - iter->current_dir++; - - return 0; -} - -static void win32_path_iter_dispose(struct win32_path_iter *iter) -{ - if (!iter) - return; - - git__free(iter->env); - iter->env = NULL; - iter->current_dir = NULL; -} - -int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) -{ - struct win32_path_iter path_iter; - const wchar_t *dir; - size_t dir_len, exe_len = wcslen(exe); - bool found = false; - - if (win32_path_iter_init(&path_iter) < 0) - return -1; - - while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { - if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) - continue; - - if (_waccess(fullpath, 0) == 0) { - found = true; - break; - } - } - - win32_path_iter_dispose(&path_iter); - - if (found) - return 0; - - fullpath[0] = L'\0'; - return GIT_ENOTFOUND; -} - -static int win32_path_cwd(wchar_t *out, size_t len) -{ - int cwd_len; - - if (len > INT_MAX) { - errno = ENAMETOOLONG; - return -1; - } - - if ((cwd_len = path__cwd(out, (int)len)) < 0) - return -1; - - /* UNC paths */ - if (wcsncmp(L"\\\\", out, 2) == 0) { - /* Our buffer must be at least 5 characters larger than the - * current working directory: we swallow one of the leading - * '\'s, but we we add a 'UNC' specifier to the path, plus - * a trailing directory separator, plus a NUL. - */ - if (cwd_len > GIT_WIN_PATH_MAX - 4) { - errno = ENAMETOOLONG; - return -1; - } - - memmove(out+2, out, sizeof(wchar_t) * cwd_len); - out[0] = L'U'; - out[1] = L'N'; - out[2] = L'C'; - - cwd_len += 2; - } - - /* Our buffer must be at least 2 characters larger than the current - * working directory. (One character for the directory separator, - * one for the null. - */ - else if (cwd_len > GIT_WIN_PATH_MAX - 2) { - errno = ENAMETOOLONG; - return -1; - } - - return cwd_len; -} - -int git_win32_path_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out; - - /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ - memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); - dest += PATH__NT_NAMESPACE_LEN; - - /* See if this is an absolute path (beginning with a drive letter) */ - if (git_fs_path_is_absolute(src)) { - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) - goto on_error; - } - /* File-prefixed NT-style paths beginning with \\?\ */ - else if (path__is_nt_namespace(src)) { - /* Skip the NT prefix, the destination already contains it */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) - goto on_error; - } - /* UNC paths */ - else if (path__is_unc(src)) { - memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); - dest += 4; - - /* Skip the leading "\\" */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) - goto on_error; - } - /* Absolute paths omitting the drive letter */ - else if (path__startswith_slash(src)) { - if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) - goto on_error; - - if (!git_fs_path_is_absolute(dest)) { - errno = ENOENT; - goto on_error; - } - - /* Skip the drive letter specification ("C:") */ - if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) - goto on_error; - } - /* Relative paths */ - else { - int cwd_len; - - if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) - goto on_error; - - dest[cwd_len++] = L'\\'; - - if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) - goto on_error; - } - - return git_win32_path_canonicalize(out); - -on_error: - /* set windows error code so we can use its error message */ - if (errno == ENAMETOOLONG) - SetLastError(ERROR_FILENAME_EXCED_RANGE); - - 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_fs_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, GIT_WIN_PATH_MAX, 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; - int len; - - /* Strip NT namespacing "\\?\" */ - if (path__is_nt_namespace(src)) { - src += 4; - - /* "\\?\UNC\server\share" -> "\\server\share" */ - if (wcsncmp(src, L"UNC\\", 4) == 0) { - src += 4; - - memcpy(dest, "\\\\", 2); - out = dest + 2; - } - } - - if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) - return len; - - git_fs_path_mkposix(dest); - - return len; -} - -char *git_win32_path_8dot3_name(const char *path) -{ - git_win32_path longpath, shortpath; - wchar_t *start; - char *shortname; - int len, namelen = 1; - - if (git_win32_path_from_utf8(longpath, path) < 0) - return NULL; - - len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); - - while (len && shortpath[len-1] == L'\\') - shortpath[--len] = L'\0'; - - if (len == 0 || len >= GIT_WIN_PATH_UTF16) - return NULL; - - for (start = shortpath + (len - 1); - start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; - start--) - namelen++; - - /* We may not have actually been given a short name. But if we have, - * it will be in the ASCII byte range, so we don't need to worry about - * multi-byte sequences and can allocate naively. - */ - if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) - return NULL; - - if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) - return NULL; - - return shortname; -} - -static bool path_is_volume(wchar_t *target, size_t target_len) -{ - return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); -} - -/* On success, returns the length, in characters, of the path stored in dest. - * On failure, returns a negative value. */ -int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) -{ - BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; - HANDLE handle = NULL; - DWORD ioctl_ret; - wchar_t *target; - size_t target_len; - - int error = -1; - - handle = CreateFileW(path, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (handle == INVALID_HANDLE_VALUE) { - errno = ENOENT; - return -1; - } - - if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, - reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { - errno = EINVAL; - goto on_error; - } - - switch (reparse_buf->ReparseTag) { - case IO_REPARSE_TAG_SYMLINK: - target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + - (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); - break; - case IO_REPARSE_TAG_MOUNT_POINT: - target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + - (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); - break; - default: - errno = EINVAL; - goto on_error; - } - - if (path_is_volume(target, target_len)) { - /* This path is a reparse point that represents another volume mounted - * at this location, it is not a symbolic link our input was canonical. - */ - errno = EINVAL; - error = -1; - } else if (target_len) { - /* The path may need to have a namespace prefix removed. */ - target_len = git_win32_path_remove_namespace(target, target_len); - - /* Need one additional character in the target buffer - * for the terminating NULL. */ - if (GIT_WIN_PATH_UTF16 > target_len) { - wcscpy(dest, target); - error = (int)target_len; - } - } - -on_error: - CloseHandle(handle); - return error; -} - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len) -{ - while (1) { - if (!len || str[len - 1] != L'\\') - break; - - /* - * Don't trim backslashes from drive letter paths, which - * are 3 characters long and of the form C:\, D:\, etc. - */ - if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') - break; - - len--; - } - - str[len] = L'\0'; - - return len; -} - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) -{ - static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; - static const wchar_t nt_namespace[] = L"\\\\?\\"; - static const wchar_t unc_namespace_remainder[] = L"UNC\\"; - static const wchar_t unc_prefix[] = L"\\\\"; - - const wchar_t *prefix = NULL, *remainder = NULL; - size_t prefix_len = 0, remainder_len = 0; - - /* "\??\" -- DOS Devices prefix */ - if (len >= CONST_STRLEN(dosdevices_namespace) && - !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { - remainder = str + CONST_STRLEN(dosdevices_namespace); - remainder_len = len - CONST_STRLEN(dosdevices_namespace); - } - /* "\\?\" -- NT namespace prefix */ - else if (len >= CONST_STRLEN(nt_namespace) && - !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { - remainder = str + CONST_STRLEN(nt_namespace); - remainder_len = len - CONST_STRLEN(nt_namespace); - } - - /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ - if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && - !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { - - /* - * The proper Win32 path for a UNC share has "\\" at beginning of it - * and looks like "\\server\share\". So remove the - * UNC namespace and add a prefix of "\\" in its place. - */ - remainder += CONST_STRLEN(unc_namespace_remainder); - remainder_len -= CONST_STRLEN(unc_namespace_remainder); - - prefix = unc_prefix; - prefix_len = CONST_STRLEN(unc_prefix); - } - - /* - * Sanity check that the new string isn't longer than the old one. - * (This could only happen due to programmer error introducing a - * prefix longer than the namespace it replaces.) - */ - if (remainder && len >= remainder_len + prefix_len) { - if (prefix) - memmove(str, prefix, prefix_len * sizeof(wchar_t)); - - memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); - - len = remainder_len + prefix_len; - str[len] = L'\0'; - } - - return git_win32_path_trim_end(str, len); -} diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h deleted file mode 100644 index 837b11ebd..000000000 --- a/src/win32/path_w32.h +++ /dev/null @@ -1,91 +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_win32_path_w32_h__ -#define INCLUDE_win32_path_w32_h__ - -#include "common.h" - -/** - * 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. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -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, - * strip trailing directory separators, ensure directory separators are all - * 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 - * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) - */ -extern int git_win32_path_canonicalize(git_win32_path path); - -/** - * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. - * - * @param dest The buffer to receive the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); - -/** - * Get the short name for the terminal path component in the given path. - * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name - * for the file "Asdf.txt". - * - * @param path The given path in UTF-8 - * @return The name of the shortname for the given path - */ -extern char *git_win32_path_8dot3_name(const char *path); - -extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len); - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); - -int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); - -#endif diff --git a/src/win32/posix.h b/src/win32/posix.h deleted file mode 100644 index 578347f15..000000000 --- a/src/win32/posix.h +++ /dev/null @@ -1,62 +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_win32_posix_h__ -#define INCLUDE_win32_posix_h__ - -#include "common.h" -#include "../posix.h" -#include "win32-compat.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "dir.h" - -extern unsigned long git_win32__createfile_sharemode; -extern int git_win32__retries; - -typedef SOCKET GIT_SOCKET; - -#define p_lseek(f,n,w) _lseeki64(f, n, w) - -extern int p_fstat(int fd, struct stat *buf); -extern int p_lstat(const char *file_name, struct stat *buf); -extern int p_stat(const char *path, struct stat *buf); - -extern int p_utimes(const char *filename, const struct p_timeval times[2]); -extern int p_futimes(int fd, const struct p_timeval times[2]); - -extern int p_readlink(const char *path, char *buf, size_t bufsiz); -extern int p_symlink(const char *old, const char *new); -extern int p_link(const char *old, const char *new); -extern int p_unlink(const char *path); -extern int p_mkdir(const char *path, mode_t mode); -extern int p_fsync(int fd); -extern char *p_realpath(const char *orig_path, char *buffer); - -extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); -extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); -extern int p_inet_pton(int af, const char *src, void* dst); - -extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); -extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); -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, 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 - * entry was encountered. Making it correct is potentially expensive, - * however, so this is a separate version of p_lstat to use when correct - * POSIX ENOTDIR semantics is required. - */ -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); - -#endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c deleted file mode 100644 index 5f7cd0c26..000000000 --- a/src/win32/posix_w32.c +++ /dev/null @@ -1,1047 +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 "common.h" - -#include "../posix.h" -#include "../futils.h" -#include "fs_path.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "reparse.h" -#include -#include -#include -#include - -#ifndef FILE_NAME_NORMALIZED -# define FILE_NAME_NORMALIZED 0 -#endif - -#ifndef IO_REPARSE_TAG_SYMLINK -#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) -#endif - -#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE -# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 -#endif - -#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY -# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01 -#endif - -/* Allowable mode bits on Win32. Using mode bits that are not supported on - * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it - * so we simply remove them. - */ -#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE) - -unsigned long git_win32__createfile_sharemode = - FILE_SHARE_READ | FILE_SHARE_WRITE; -int git_win32__retries = 10; - -GIT_INLINE(void) set_errno(void) -{ - switch (GetLastError()) { - case ERROR_FILE_NOT_FOUND: - case ERROR_PATH_NOT_FOUND: - case ERROR_INVALID_DRIVE: - case ERROR_NO_MORE_FILES: - case ERROR_BAD_NETPATH: - case ERROR_BAD_NET_NAME: - case ERROR_BAD_PATHNAME: - case ERROR_FILENAME_EXCED_RANGE: - errno = ENOENT; - break; - case ERROR_BAD_ENVIRONMENT: - errno = E2BIG; - break; - case ERROR_BAD_FORMAT: - case ERROR_INVALID_STARTING_CODESEG: - case ERROR_INVALID_STACKSEG: - case ERROR_INVALID_MODULETYPE: - case ERROR_INVALID_EXE_SIGNATURE: - case ERROR_EXE_MARKED_INVALID: - case ERROR_BAD_EXE_FORMAT: - case ERROR_ITERATED_DATA_EXCEEDS_64k: - case ERROR_INVALID_MINALLOCSIZE: - case ERROR_DYNLINK_FROM_INVALID_RING: - case ERROR_IOPL_NOT_ENABLED: - case ERROR_INVALID_SEGDPL: - case ERROR_AUTODATASEG_EXCEEDS_64k: - case ERROR_RING2SEG_MUST_BE_MOVABLE: - case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: - case ERROR_INFLOOP_IN_RELOC_CHAIN: - errno = ENOEXEC; - break; - case ERROR_INVALID_HANDLE: - case ERROR_INVALID_TARGET_HANDLE: - case ERROR_DIRECT_ACCESS_HANDLE: - errno = EBADF; - break; - case ERROR_WAIT_NO_CHILDREN: - case ERROR_CHILD_NOT_COMPLETE: - errno = ECHILD; - break; - case ERROR_NO_PROC_SLOTS: - case ERROR_MAX_THRDS_REACHED: - case ERROR_NESTING_NOT_ALLOWED: - errno = EAGAIN; - break; - case ERROR_ARENA_TRASHED: - case ERROR_NOT_ENOUGH_MEMORY: - case ERROR_INVALID_BLOCK: - case ERROR_NOT_ENOUGH_QUOTA: - errno = ENOMEM; - break; - case ERROR_ACCESS_DENIED: - case ERROR_CURRENT_DIRECTORY: - case ERROR_WRITE_PROTECT: - case ERROR_BAD_UNIT: - case ERROR_NOT_READY: - case ERROR_BAD_COMMAND: - case ERROR_CRC: - case ERROR_BAD_LENGTH: - case ERROR_SEEK: - case ERROR_NOT_DOS_DISK: - case ERROR_SECTOR_NOT_FOUND: - case ERROR_OUT_OF_PAPER: - case ERROR_WRITE_FAULT: - case ERROR_READ_FAULT: - case ERROR_GEN_FAILURE: - case ERROR_SHARING_VIOLATION: - case ERROR_LOCK_VIOLATION: - case ERROR_WRONG_DISK: - case ERROR_SHARING_BUFFER_EXCEEDED: - case ERROR_NETWORK_ACCESS_DENIED: - case ERROR_CANNOT_MAKE: - case ERROR_FAIL_I24: - case ERROR_DRIVE_LOCKED: - case ERROR_SEEK_ON_DEVICE: - case ERROR_NOT_LOCKED: - case ERROR_LOCK_FAILED: - errno = EACCES; - break; - case ERROR_FILE_EXISTS: - case ERROR_ALREADY_EXISTS: - errno = EEXIST; - break; - case ERROR_NOT_SAME_DEVICE: - errno = EXDEV; - break; - case ERROR_INVALID_FUNCTION: - case ERROR_INVALID_ACCESS: - case ERROR_INVALID_DATA: - case ERROR_INVALID_PARAMETER: - case ERROR_NEGATIVE_SEEK: - errno = EINVAL; - break; - case ERROR_TOO_MANY_OPEN_FILES: - errno = EMFILE; - break; - case ERROR_DISK_FULL: - errno = ENOSPC; - break; - case ERROR_BROKEN_PIPE: - errno = EPIPE; - break; - case ERROR_DIR_NOT_EMPTY: - errno = ENOTEMPTY; - break; - default: - errno = EINVAL; - } -} - -GIT_INLINE(bool) last_error_retryable(void) -{ - int os_error = GetLastError(); - - return (os_error == ERROR_SHARING_VIOLATION || - os_error == ERROR_ACCESS_DENIED); -} - -#define do_with_retries(fn, remediation) \ - do { \ - int __retry, __ret; \ - for (__retry = git_win32__retries; __retry; __retry--) { \ - if ((__ret = (fn)) != GIT_RETRY) \ - return __ret; \ - if (__retry > 1 && (__ret = (remediation)) != 0) { \ - if (__ret == GIT_RETRY) \ - continue; \ - return __ret; \ - } \ - Sleep(5); \ - } \ - return -1; \ - } while (0) \ - -static int ensure_writable(wchar_t *path) -{ - DWORD attrs; - - if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) - goto on_error; - - if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) - return 0; - - if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) - goto on_error; - - return GIT_RETRY; - -on_error: - set_errno(); - return -1; -} - -/** - * Truncate or extend file. - * - * We now take a "git_off_t" rather than "long" because - * files may be longer than 2Gb. - */ -int p_ftruncate(int fd, off64_t size) -{ - if (size < 0) { - errno = EINVAL; - return -1; - } - -#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) - return ((_chsize_s(fd, size) == 0) ? 0 : -1); -#else - /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ - if (size > INT32_MAX) { - errno = EFBIG; - return -1; - } - return _chsize(fd, (long)size); -#endif -} - -int p_mkdir(const char *path, mode_t mode) -{ - git_win32_path buf; - - GIT_UNUSED(mode); - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wmkdir(buf); -} - -int p_link(const char *old, const char *new) -{ - GIT_UNUSED(old); - GIT_UNUSED(new); - errno = ENOSYS; - return -1; -} - -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; - - set_errno(); - return -1; -} - -int p_unlink(const char *path) -{ - git_win32_path wpath; - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - do_with_retries(unlink_once(wpath), ensure_writable(wpath)); -} - -int p_fsync(int fd) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - p_fsync__cnt++; - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - return -1; - } - - if (!FlushFileBuffers(fh)) { - DWORD code = GetLastError(); - - if (code == ERROR_INVALID_HANDLE) - errno = EINVAL; - else - errno = EIO; - - return -1; - } - - return 0; -} - -#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') - -static int lstat_w( - wchar_t *path, - struct stat *buf, - bool posix_enotdir) -{ - WIN32_FILE_ATTRIBUTE_DATA fdata; - - if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { - if (!buf) - return 0; - - return git_win32__file_attribute_to_stat(buf, &fdata, path); - } - - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - errno = EACCES; - break; - default: - errno = ENOENT; - break; - } - - /* To match POSIX behavior, set ENOTDIR when any of the folders in the - * file path is a regular file, otherwise set ENOENT. - */ - if (errno == ENOENT && posix_enotdir) { - size_t path_len = wcslen(path); - - /* scan up path until we find an existing item */ - while (1) { - DWORD attrs; - - /* remove last directory component */ - for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); - - if (path_len <= 0) - break; - - path[path_len] = L'\0'; - attrs = GetFileAttributesW(path); - - if (attrs != INVALID_FILE_ATTRIBUTES) { - if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) - errno = ENOTDIR; - break; - } - } - } - - return -1; -} - -static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) -{ - git_win32_path path_w; - int len; - - if ((len = git_win32_path_from_utf8(path_w, path)) < 0) - return -1; - - git_win32_path_trim_end(path_w, len); - - return lstat_w(path_w, buf, posixly_correct); -} - -int p_lstat(const char *filename, struct stat *buf) -{ - return do_lstat(filename, buf, false); -} - -int p_lstat_posixly(const char *filename, struct stat *buf) -{ - return do_lstat(filename, buf, true); -} - -int p_readlink(const char *path, char *buf, size_t bufsiz) -{ - git_win32_path path_w, target_w; - git_win32_utf8_path target; - int len; - - /* readlink(2) does not NULL-terminate the string written - * to the target buffer. Furthermore, the target buffer need - * not be large enough to hold the entire result. A truncated - * result should be written in this case. Since this truncation - * could occur in the middle of the encoding of a code point, - * we need to buffer the result on the stack. */ - - if (git_win32_path_from_utf8(path_w, path) < 0 || - git_win32_path_readlink_w(target_w, path_w) < 0 || - (len = git_win32_path_to_utf8(target, target_w)) < 0) - return -1; - - bufsiz = min((size_t)len, bufsiz); - memcpy(buf, target, bufsiz); - - return (int)bufsiz; -} - -static bool target_is_dir(const char *target, const char *path) -{ - git_str resolved = GIT_STR_INIT; - git_win32_path resolved_w; - bool isdir = true; - - if (git_fs_path_is_absolute(target)) - git_win32_path_from_utf8(resolved_w, target); - else if (git_fs_path_dirname_r(&resolved, path) < 0 || - git_fs_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_str_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 something we want. - */ - if (git_win32_path_from_utf8(path_w, path) < 0 || - git_win32_path_relative_from_utf8(target_w, target) < 0) - return -1; - - dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - if (target_is_dir(target, path)) - dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; - - if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) - return -1; - - return 0; -} - -struct open_opts { - DWORD access; - DWORD sharing; - SECURITY_ATTRIBUTES security; - DWORD creation_disposition; - DWORD attributes; - int osf_flags; -}; - -GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) -{ - memset(opts, 0, sizeof(struct open_opts)); - - switch (flags & (O_WRONLY | O_RDWR)) { - case O_WRONLY: - opts->access = GENERIC_WRITE; - break; - case O_RDWR: - opts->access = GENERIC_READ | GENERIC_WRITE; - break; - default: - opts->access = GENERIC_READ; - break; - } - - opts->sharing = (DWORD)git_win32__createfile_sharemode; - - switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { - case O_CREAT | O_EXCL: - case O_CREAT | O_TRUNC | O_EXCL: - opts->creation_disposition = CREATE_NEW; - break; - case O_CREAT | O_TRUNC: - opts->creation_disposition = CREATE_ALWAYS; - break; - case O_TRUNC: - opts->creation_disposition = TRUNCATE_EXISTING; - break; - case O_CREAT: - opts->creation_disposition = OPEN_ALWAYS; - break; - default: - opts->creation_disposition = OPEN_EXISTING; - break; - } - - opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? - FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; - opts->osf_flags = flags & (O_RDONLY | O_APPEND); - - opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); - opts->security.lpSecurityDescriptor = NULL; - opts->security.bInheritHandle = 0; -} - -GIT_INLINE(int) open_once( - const wchar_t *path, - struct open_opts *opts) -{ - int fd; - - HANDLE handle = CreateFileW(path, opts->access, opts->sharing, - &opts->security, opts->creation_disposition, opts->attributes, 0); - - if (handle == INVALID_HANDLE_VALUE) { - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; - } - - if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) - CloseHandle(handle); - - return fd; -} - -int p_open(const char *path, int flags, ...) -{ - git_win32_path wpath; - mode_t mode = 0; - struct open_opts opts = {0}; - - #ifdef GIT_DEBUG_STRICT_OPEN - if (strstr(path, "//") != NULL) { - errno = EACCES; - return -1; - } - #endif - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - if (flags & O_CREAT) { - va_list arg_list; - - va_start(arg_list, flags); - mode = (mode_t)va_arg(arg_list, int); - va_end(arg_list); - } - - open_opts_from_posix(&opts, flags, mode); - - do_with_retries( - open_once(wpath, &opts), - 0); -} - -int p_creat(const char *path, mode_t mode) -{ - return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); -} - -int p_utimes(const char *path, const struct p_timeval times[2]) -{ - git_win32_path wpath; - int fd, error; - DWORD attrs_orig, attrs_new = 0; - struct open_opts opts = { 0 }; - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - attrs_orig = GetFileAttributesW(wpath); - - if (attrs_orig & FILE_ATTRIBUTE_READONLY) { - attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; - - if (!SetFileAttributesW(wpath, attrs_new)) { - git_error_set(GIT_ERROR_OS, "failed to set attributes"); - return -1; - } - } - - open_opts_from_posix(&opts, O_RDWR, 0); - - if ((fd = open_once(wpath, &opts)) < 0) { - error = -1; - goto done; - } - - error = p_futimes(fd, times); - close(fd); - -done: - if (attrs_orig != attrs_new) { - DWORD os_error = GetLastError(); - SetFileAttributesW(wpath, attrs_orig); - SetLastError(os_error); - } - - return error; -} - -int p_futimes(int fd, const struct p_timeval times[2]) -{ - HANDLE handle; - FILETIME atime = { 0 }, mtime = { 0 }; - - if (times == NULL) { - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st, &atime); - SystemTimeToFileTime(&st, &mtime); - } - else { - git_win32__timeval_to_filetime(&atime, times[0]); - git_win32__timeval_to_filetime(&mtime, times[1]); - } - - if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) - return -1; - - if (SetFileTime(handle, NULL, &atime, &mtime) == 0) - return -1; - - return 0; -} - -int p_getcwd(char *buffer_out, size_t size) -{ - git_win32_path buf; - wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); - - if (!cwd) - return -1; - - git_win32_path_remove_namespace(cwd, wcslen(cwd)); - - /* Convert the working directory back to UTF-8 */ - if (git__utf16_to_8(buffer_out, size, cwd) < 0) { - DWORD code = GetLastError(); - - if (code == ERROR_INSUFFICIENT_BUFFER) - errno = ERANGE; - else - errno = EINVAL; - - return -1; - } - - git_fs_path_mkposix(buffer_out); - return 0; -} - -static int getfinalpath_w( - git_win32_path dest, - const wchar_t *path) -{ - HANDLE hFile; - DWORD dwChars; - - /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not - * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the - * target of the link. */ - hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (INVALID_HANDLE_VALUE == hFile) - return -1; - - /* Call GetFinalPathNameByHandle */ - dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED); - CloseHandle(hFile); - - if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) - return -1; - - /* The path may be delivered to us with a namespace prefix; remove */ - return (int)git_win32_path_remove_namespace(dest, dwChars); -} - -static int follow_and_lstat_link(git_win32_path path, struct stat *buf) -{ - git_win32_path target_w; - - if (getfinalpath_w(target_w, path) < 0) - return -1; - - return lstat_w(target_w, buf, false); -} - -int p_fstat(int fd, struct stat *buf) -{ - BY_HANDLE_FILE_INFORMATION fhInfo; - - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - if (fh == INVALID_HANDLE_VALUE || - !GetFileInformationByHandle(fh, &fhInfo)) { - errno = EBADF; - return -1; - } - - git_win32__file_information_to_stat(buf, &fhInfo); - return 0; -} - -int p_stat(const char *path, struct stat *buf) -{ - git_win32_path path_w; - int len; - - if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || - lstat_w(path_w, buf, false) < 0) - return -1; - - /* The item is a symbolic link or mount point. No need to iterate - * to follow multiple links; use GetFinalPathNameFromHandle. */ - if (S_ISLNK(buf->st_mode)) - return follow_and_lstat_link(path_w, buf); - - return 0; -} - -int p_chdir(const char *path) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wchdir(buf); -} - -int p_chmod(const char *path, mode_t mode) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wchmod(buf, mode); -} - -int p_rmdir(const char *path) -{ - git_win32_path buf; - int error; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - error = _wrmdir(buf); - - if (error == -1) { - switch (GetLastError()) { - /* _wrmdir() is documented to return EACCES if "A program has an open - * handle to the directory." This sounds like what everybody else calls - * EBUSY. Let's convert appropriate error codes. - */ - case ERROR_SHARING_VIOLATION: - errno = EBUSY; - break; - - /* This error can be returned when trying to rmdir an extant file. */ - case ERROR_DIRECTORY: - errno = ENOTDIR; - break; - } - } - - return error; -} - -char *p_realpath(const char *orig_path, char *buffer) -{ - git_win32_path orig_path_w, buffer_w; - - if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) - return NULL; - - /* Note that if the path provided is a relative path, then the current directory - * is used to resolve the path -- which is a concurrency issue because the current - * directory is a process-wide variable. */ - if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; - - return NULL; - } - - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; - return NULL; - } - - if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { - errno = ENOMEM; - return NULL; - } - - /* Convert the path to UTF-8. If the caller provided a buffer, then it - * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, - * then we may overflow. */ - if (git_win32_path_to_utf8(buffer, buffer_w) < 0) - return NULL; - - git_fs_path_mkposix(buffer); - - return buffer; -} - -int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) -{ -#if defined(_MSC_VER) - int len; - - if (count == 0) - return _vscprintf(format, argptr); - - #if _MSC_VER >= 1500 - len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); - #else - len = _vsnprintf(buffer, count, format, argptr); - #endif - - if (len < 0) - return _vscprintf(format, argptr); - - return len; -#else /* MinGW */ - return vsnprintf(buffer, count, format, argptr); -#endif -} - -int p_snprintf(char *buffer, size_t count, const char *format, ...) -{ - va_list va; - int r; - - va_start(va, format); - r = p_vsnprintf(buffer, count, format, va); - va_end(va); - - return r; -} - -int p_access(const char *path, mode_t mode) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _waccess(buf, mode & WIN32_MODE_MASK); -} - -GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) -{ - if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) - return 0; - - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; -} - -int p_rename(const char *from, const char *to) -{ - git_win32_path wfrom, wto; - - if (git_win32_path_from_utf8(wfrom, from) < 0 || - git_win32_path_from_utf8(wto, to) < 0) - return -1; - - do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); -} - -int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) -{ - if ((size_t)((int)length) != length) - return -1; /* git_error_set will be done by caller */ - - return recv(socket, buffer, (int)length, flags); -} - -int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) -{ - if ((size_t)((int)length) != length) - return -1; /* git_error_set will be done by caller */ - - return send(socket, buffer, (int)length, flags); -} - -/** - * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html - * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that - */ -struct tm * -p_localtime_r (const time_t *timer, struct tm *result) -{ - struct tm *local_result; - local_result = localtime (timer); - - if (local_result == NULL || result == NULL) - return NULL; - - memcpy (result, local_result, sizeof (struct tm)); - return result; -} -struct tm * -p_gmtime_r (const time_t *timer, struct tm *result) -{ - struct tm *local_result; - local_result = gmtime (timer); - - if (local_result == NULL || result == NULL) - return NULL; - - memcpy (result, local_result, sizeof (struct tm)); - return result; -} - -int p_inet_pton(int af, const char *src, void *dst) -{ - struct sockaddr_storage sin; - void *addr; - int sin_len = sizeof(struct sockaddr_storage), addr_len; - int error = 0; - - if (af == AF_INET) { - addr = &((struct sockaddr_in *)&sin)->sin_addr; - addr_len = sizeof(struct in_addr); - } else if (af == AF_INET6) { - addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; - addr_len = sizeof(struct in6_addr); - } else { - errno = EAFNOSUPPORT; - return -1; - } - - if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { - memcpy(dst, addr, addr_len); - return 1; - } - - switch(WSAGetLastError()) { - case WSAEINVAL: - return 0; - case WSAEFAULT: - errno = ENOSPC; - return -1; - case WSA_NOT_ENOUGH_MEMORY: - errno = ENOMEM; - return -1; - } - - errno = EINVAL; - return -1; -} - -ssize_t p_pread(int fd, void *data, size_t size, off64_t offset) -{ - HANDLE fh; - DWORD rsize = 0; - OVERLAPPED ov = {0}; - LARGE_INTEGER pos = {0}; - off64_t final_offset = 0; - - /* Fail if the final offset would have overflowed to match POSIX semantics. */ - if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { - errno = EINVAL; - return -1; - } - - /* - * Truncate large writes to the maximum allowable size: the caller - * needs to always call this in a loop anyways. - */ - if (size > INT32_MAX) { - size = INT32_MAX; - } - - pos.QuadPart = offset; - ov.Offset = pos.LowPart; - ov.OffsetHigh = pos.HighPart; - fh = (HANDLE)_get_osfhandle(fd); - - if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) { - return (ssize_t)rsize; - } - - set_errno(); - return -1; -} - -ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset) -{ - HANDLE fh; - DWORD wsize = 0; - OVERLAPPED ov = {0}; - LARGE_INTEGER pos = {0}; - off64_t final_offset = 0; - - /* Fail if the final offset would have overflowed to match POSIX semantics. */ - if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { - errno = EINVAL; - return -1; - } - - /* - * Truncate large writes to the maximum allowable size: the caller - * needs to always call this in a loop anyways. - */ - if (size > INT32_MAX) { - size = INT32_MAX; - } - - pos.QuadPart = offset; - ov.Offset = pos.LowPart; - ov.OffsetHigh = pos.HighPart; - fh = (HANDLE)_get_osfhandle(fd); - - if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) { - return (ssize_t)wsize; - } - - set_errno(); - return -1; -} diff --git a/src/win32/precompiled.c b/src/win32/precompiled.c deleted file mode 100644 index 5f656a45d..000000000 --- a/src/win32/precompiled.c +++ /dev/null @@ -1 +0,0 @@ -#include "precompiled.h" diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h deleted file mode 100644 index 806b1698a..000000000 --- a/src/win32/precompiled.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "common.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#ifdef GIT_THREADS - #include "win32/thread.h" -#endif - -#include "git2.h" diff --git a/src/win32/reparse.h b/src/win32/reparse.h deleted file mode 100644 index 23312319f..000000000 --- a/src/win32/reparse.h +++ /dev/null @@ -1,57 +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_win32_reparse_h__ -#define INCLUDE_win32_reparse_h__ - -/* This structure is defined on MSDN at -* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx -* -* It was formerly included in the Windows 2000 SDK and remains defined in -* MinGW, so we must define it with a silly name to avoid conflicting. -*/ -typedef struct _GIT_REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - } SymbolicLink; - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPoint; - struct { - UCHAR DataBuffer[1]; - } Generic; - } ReparseBuffer; -} GIT_REPARSE_DATA_BUFFER; - -#define REPARSE_DATA_HEADER_SIZE 8 -#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8 -#define REPARSE_DATA_UNION_SIZE 12 - -/* Missing in MinGW */ -#ifndef FSCTL_GET_REPARSE_POINT -# define FSCTL_GET_REPARSE_POINT 0x000900a8 -#endif - -/* Missing in MinGW */ -#ifndef FSCTL_SET_REPARSE_POINT -# define FSCTL_SET_REPARSE_POINT 0x000900a4 -#endif - -#endif diff --git a/src/win32/thread.c b/src/win32/thread.c deleted file mode 100644 index f5cacd320..000000000 --- a/src/win32/thread.c +++ /dev/null @@ -1,262 +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 "thread.h" -#include "runtime.h" - -#define CLEAN_THREAD_EXIT 0x6F012842 - -typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); - -static win32_srwlock_fn win32_srwlock_initialize; -static win32_srwlock_fn win32_srwlock_acquire_shared; -static win32_srwlock_fn win32_srwlock_release_shared; -static win32_srwlock_fn win32_srwlock_acquire_exclusive; -static win32_srwlock_fn win32_srwlock_release_exclusive; - -static DWORD fls_index; - -/* The thread procedure stub used to invoke the caller's procedure - * and capture the return value for later collection. Windows will - * only hold a DWORD, but we need to be able to store an entire - * void pointer. This requires the indirection. */ -static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) -{ - git_thread *thread = lpParameter; - - /* Set the current thread for `git_thread_exit` */ - FlsSetValue(fls_index, thread); - - thread->result = thread->proc(thread->param); - - return CLEAN_THREAD_EXIT; -} - -static void git_threads_global_shutdown(void) -{ - FlsFree(fls_index); -} - -int git_threads_global_init(void) -{ - HMODULE hModule = GetModuleHandleW(L"kernel32"); - - if (hModule) { - win32_srwlock_initialize = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "InitializeSRWLock"); - win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "AcquireSRWLockShared"); - win32_srwlock_release_shared = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "ReleaseSRWLockShared"); - win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "AcquireSRWLockExclusive"); - win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "ReleaseSRWLockExclusive"); - } - - if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES) - return -1; - - return git_runtime_shutdown_register(git_threads_global_shutdown); -} - -int git_thread_create( - git_thread *GIT_RESTRICT thread, - void *(*start_routine)(void*), - void *GIT_RESTRICT arg) -{ - thread->result = NULL; - thread->param = arg; - thread->proc = start_routine; - thread->thread = CreateThread( - NULL, 0, git_win32__threadproc, thread, 0, NULL); - - return thread->thread ? 0 : -1; -} - -int git_thread_join( - git_thread *thread, - void **value_ptr) -{ - DWORD exit; - - if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) - return -1; - - if (!GetExitCodeThread(thread->thread, &exit)) { - CloseHandle(thread->thread); - return -1; - } - - /* Check for the thread having exited uncleanly. If exit was unclean, - * then we don't have a return value to give back to the caller. */ - GIT_ASSERT(exit == CLEAN_THREAD_EXIT); - - if (value_ptr) - *value_ptr = thread->result; - - CloseHandle(thread->thread); - return 0; -} - -void git_thread_exit(void *value) -{ - git_thread *thread = FlsGetValue(fls_index); - - if (thread) - thread->result = value; - - ExitThread(CLEAN_THREAD_EXIT); -} - -size_t git_thread_currentid(void) -{ - return GetCurrentThreadId(); -} - -int git_mutex_init(git_mutex *GIT_RESTRICT mutex) -{ - InitializeCriticalSection(mutex); - return 0; -} - -int git_mutex_free(git_mutex *mutex) -{ - DeleteCriticalSection(mutex); - return 0; -} - -int git_mutex_lock(git_mutex *mutex) -{ - EnterCriticalSection(mutex); - return 0; -} - -int git_mutex_unlock(git_mutex *mutex) -{ - LeaveCriticalSection(mutex); - return 0; -} - -int git_cond_init(git_cond *cond) -{ - /* This is an auto-reset event. */ - *cond = CreateEventW(NULL, FALSE, FALSE, NULL); - GIT_ASSERT(*cond); - - /* If we can't create the event, claim that the reason was out-of-memory. - * The actual reason can be fetched with GetLastError(). */ - return *cond ? 0 : ENOMEM; -} - -int git_cond_free(git_cond *cond) -{ - BOOL closed; - - if (!cond) - return EINVAL; - - closed = CloseHandle(*cond); - GIT_ASSERT(closed); - GIT_UNUSED(closed); - - *cond = NULL; - return 0; -} - -int git_cond_wait(git_cond *cond, git_mutex *mutex) -{ - int error; - DWORD wait_result; - - if (!cond || !mutex) - return EINVAL; - - /* The caller must be holding the mutex. */ - error = git_mutex_unlock(mutex); - - if (error) - return error; - - wait_result = WaitForSingleObject(*cond, INFINITE); - GIT_ASSERT(WAIT_OBJECT_0 == wait_result); - GIT_UNUSED(wait_result); - - return git_mutex_lock(mutex); -} - -int git_cond_signal(git_cond *cond) -{ - BOOL signaled; - - if (!cond) - return EINVAL; - - signaled = SetEvent(*cond); - GIT_ASSERT(signaled); - GIT_UNUSED(signaled); - - return 0; -} - -int git_rwlock_init(git_rwlock *GIT_RESTRICT lock) -{ - if (win32_srwlock_initialize) - win32_srwlock_initialize(&lock->native.srwl); - else - InitializeCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_rdlock(git_rwlock *lock) -{ - if (win32_srwlock_acquire_shared) - win32_srwlock_acquire_shared(&lock->native.srwl); - else - EnterCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_rdunlock(git_rwlock *lock) -{ - if (win32_srwlock_release_shared) - win32_srwlock_release_shared(&lock->native.srwl); - else - LeaveCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_wrlock(git_rwlock *lock) -{ - if (win32_srwlock_acquire_exclusive) - win32_srwlock_acquire_exclusive(&lock->native.srwl); - else - EnterCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_wrunlock(git_rwlock *lock) -{ - if (win32_srwlock_release_exclusive) - win32_srwlock_release_exclusive(&lock->native.srwl); - else - LeaveCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_free(git_rwlock *lock) -{ - if (!win32_srwlock_initialize) - DeleteCriticalSection(&lock->native.csec); - git__memzero(lock, sizeof(*lock)); - return 0; -} diff --git a/src/win32/thread.h b/src/win32/thread.h deleted file mode 100644 index 8305036b4..000000000 --- a/src/win32/thread.h +++ /dev/null @@ -1,64 +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_win32_thread_h__ -#define INCLUDE_win32_thread_h__ - -#include "common.h" - -#if defined (_MSC_VER) -# define GIT_RESTRICT __restrict -#else -# define GIT_RESTRICT __restrict__ -#endif - -typedef struct { - HANDLE thread; - void *(*proc)(void *); - void *param; - void *result; -} git_thread; - -typedef CRITICAL_SECTION git_mutex; -typedef HANDLE git_cond; - -typedef struct { void *Ptr; } GIT_SRWLOCK; - -typedef struct { - union { - GIT_SRWLOCK srwl; - CRITICAL_SECTION csec; - } native; -} git_rwlock; - -int git_threads_global_init(void); - -int git_thread_create(git_thread *GIT_RESTRICT, - void *(*) (void *), - void *GIT_RESTRICT); -int git_thread_join(git_thread *, void **); -size_t git_thread_currentid(void); -void git_thread_exit(void *); - -int git_mutex_init(git_mutex *GIT_RESTRICT mutex); -int git_mutex_free(git_mutex *); -int git_mutex_lock(git_mutex *); -int git_mutex_unlock(git_mutex *); - -int git_cond_init(git_cond *); -int git_cond_free(git_cond *); -int git_cond_wait(git_cond *, git_mutex *); -int git_cond_signal(git_cond *); - -int git_rwlock_init(git_rwlock *GIT_RESTRICT lock); -int git_rwlock_rdlock(git_rwlock *); -int git_rwlock_rdunlock(git_rwlock *); -int git_rwlock_wrlock(git_rwlock *); -int git_rwlock_wrunlock(git_rwlock *); -int git_rwlock_free(git_rwlock *); - -#endif diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c deleted file mode 100644 index 4bde3023a..000000000 --- a/src/win32/utf-conv.c +++ /dev/null @@ -1,146 +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 "utf-conv.h" - -GIT_INLINE(void) git__set_errno(void) -{ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; -} - -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @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 - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) -{ - int len; - - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ - if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) - git__set_errno(); - - return len; -} - -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) -{ - int len; - - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ - if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) - git__set_errno(); - - return len; -} - -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to 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 - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src) -{ - int utf16_size; - - *dest = NULL; - - /* Length of -1 indicates NULL termination of the input string */ - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); - - if (!utf16_size) { - git__set_errno(); - return -1; - } - - if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { - errno = ENOMEM; - return -1; - } - - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); - - if (!utf16_size) { - git__set_errno(); - - git__free(*dest); - *dest = NULL; - } - - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf16_size - 1; -} - -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src) -{ - int utf8_size; - - *dest = NULL; - - /* Length of -1 indicates NULL termination of the input string */ - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); - return -1; - } - - *dest = git__malloc(utf8_size); - - if (!*dest) { - errno = ENOMEM; - return -1; - } - - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); - - git__free(*dest); - *dest = NULL; - } - - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf8_size - 1; -} diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h deleted file mode 100644 index 6090a4b35..000000000 --- a/src/win32/utf-conv.h +++ /dev/null @@ -1,60 +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_win32_utf_conv_h__ -#define INCLUDE_win32_utf_conv_h__ - -#include "common.h" - -#include - -#ifndef WC_ERR_INVALID_CHARS -# define WC_ERR_INVALID_CHARS 0x80 -#endif - -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @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 - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); - -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); - -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to 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 - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src); - -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src); - -#endif diff --git a/src/win32/version.h b/src/win32/version.h deleted file mode 100644 index 79667697f..000000000 --- a/src/win32/version.h +++ /dev/null @@ -1,37 +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_win32_version_h__ -#define INCLUDE_win32_version_h__ - -#include - -GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) -{ - OSVERSIONINFOEX version_test = {0}; - DWORD version_test_mask; - DWORDLONG version_condition_mask = 0; - - version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - version_test.dwMajorVersion = major; - version_test.dwMinorVersion = minor; - version_test.wServicePackMajor = (WORD)service_pack; - version_test.wServicePackMinor = 0; - - version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); - - VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) - return 0; - - return 1; -} - -#endif diff --git a/src/win32/w32_buffer.c b/src/win32/w32_buffer.c deleted file mode 100644 index 6fee8203c..000000000 --- a/src/win32/w32_buffer.c +++ /dev/null @@ -1,57 +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 "w32_buffer.h" - -#include "utf-conv.h" - -GIT_INLINE(int) handle_wc_error(void) -{ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; - - return -1; -} - -int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w) -{ - int utf8_len, utf8_write_len; - size_t new_size; - - if (!len_w) { - return 0; - } else if (len_w > INT_MAX) { - git_error_set_oom(); - return -1; - } - - GIT_ASSERT(string_w); - - /* Measure the string necessary for conversion */ - if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) - return 0; - - GIT_ASSERT(utf8_len > 0); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - - if (git_str_grow(buf, new_size) < 0) - return -1; - - if ((utf8_write_len = WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) - return handle_wc_error(); - - GIT_ASSERT(utf8_write_len == utf8_len); - - buf->size += utf8_write_len; - buf->ptr[buf->size] = '\0'; - return 0; -} diff --git a/src/win32/w32_buffer.h b/src/win32/w32_buffer.h deleted file mode 100644 index 4227296d8..000000000 --- a/src/win32/w32_buffer.h +++ /dev/null @@ -1,19 +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_win32_w32_buffer_h__ -#define INCLUDE_win32_w32_buffer_h__ - -#include "common.h" -#include "str.h" - -/** - * Convert a wide character string to UTF-8 and append the results to the - * buffer. - */ -int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w); - -#endif diff --git a/src/win32/w32_common.h b/src/win32/w32_common.h deleted file mode 100644 index c20b3e85e..000000000 --- a/src/win32/w32_common.h +++ /dev/null @@ -1,48 +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_win32_w32_common_h__ -#define INCLUDE_win32_w32_common_h__ - -#include - -/* - * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed - * Windows path length, however win32 Unicode APIs generally allow up to 32,767 - * if prefixed with "\\?\" (i.e. converted to an NT-style name). - */ -#define GIT_WIN_PATH_MAX GIT_PATH_MAX - -/* - * Provides a large enough buffer to support Windows Git paths: - * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095 - * 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 GIT_WIN_PATH_MAX+6. - */ -#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6 - -/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\" - * prefixes for presentation, bringing us back to 4095 (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 ((GIT_WIN_PATH_MAX - 1) * 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_leakcheck.c b/src/win32/w32_leakcheck.c deleted file mode 100644 index 0f095de12..000000000 --- a/src/win32/w32_leakcheck.c +++ /dev/null @@ -1,581 +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 "w32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) - -#include "Windows.h" -#include "Dbghelp.h" -#include "win32/posix.h" -#include "hash.h" -#include "runtime.h" - -/* Stack frames (for stack tracing, below) */ - -static bool g_win32_stack_initialized = false; -static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; -static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL; -static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL; - -int git_win32_leakcheck_stack_set_aux_cb( - git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, - git_win32_leakcheck_stack_aux_cb_lookup cb_lookup) -{ - g_aux_cb_alloc = cb_alloc; - g_aux_cb_lookup = cb_lookup; - - return 0; -} - -/** - * Load symbol table data. This should be done in the primary - * thread at startup (under a lock if there are other threads - * active). - */ -void git_win32_leakcheck_stack_init(void) -{ - if (!g_win32_stack_initialized) { - g_win32_stack_process = GetCurrentProcess(); - SymSetOptions(SYMOPT_LOAD_LINES); - SymInitialize(g_win32_stack_process, NULL, TRUE); - g_win32_stack_initialized = true; - } -} - -/** - * Cleanup symbol table data. This should be done in the - * primary thead at shutdown (under a lock if there are other - * threads active). - */ -void git_win32_leakcheck_stack_cleanup(void) -{ - if (g_win32_stack_initialized) { - SymCleanup(g_win32_stack_process); - g_win32_stack_process = INVALID_HANDLE_VALUE; - g_win32_stack_initialized = false; - } -} - -int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) -{ - if (!g_win32_stack_initialized) { - git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); - return GIT_ERROR; - } - - memset(pdata, 0, sizeof(*pdata)); - pdata->nr_frames = RtlCaptureStackBackTrace( - skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); - - /* If an "aux" data provider was registered, ask it to capture - * whatever data it needs and give us an "aux_id" to it so that - * we can refer to it later when reporting. - */ - if (g_aux_cb_alloc) - (g_aux_cb_alloc)(&pdata->aux_id); - - return 0; -} - -int git_win32_leakcheck_stack_compare( - git_win32_leakcheck_stack_raw_data *d1, - git_win32_leakcheck_stack_raw_data *d2) -{ - return memcmp(d1, d2, sizeof(*d1)); -} - -int git_win32_leakcheck_stack_format( - char *pbuf, size_t buf_len, - const git_win32_leakcheck_stack_raw_data *pdata, - const char *prefix, const char *suffix) -{ -#define MY_MAX_FILENAME 255 - - /* SYMBOL_INFO has char FileName[1] at the end. The docs say to - * to malloc it with extra space for your desired max filename. - */ - struct { - SYMBOL_INFO symbol; - char extra[MY_MAX_FILENAME + 1]; - } s; - - IMAGEHLP_LINE64 line; - size_t buf_used = 0; - unsigned int k; - char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ - size_t detail_len; - - if (!g_win32_stack_initialized) { - git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); - return GIT_ERROR; - } - - if (!prefix) - prefix = "\t"; - if (!suffix) - suffix = "\n"; - - memset(pbuf, 0, buf_len); - - memset(&s, 0, sizeof(s)); - s.symbol.MaxNameLen = MY_MAX_FILENAME; - s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); - - memset(&line, 0, sizeof(line)); - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - for (k=0; k < pdata->nr_frames; k++) { - DWORD64 frame_k = (DWORD64)pdata->frames[k]; - DWORD dwUnused; - - if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && - SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { - const char *pslash; - const char *pfile; - - pslash = strrchr(line.FileName, '\\'); - pfile = ((pslash) ? (pslash+1) : line.FileName); - p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", - prefix, pfile, line.LineNumber, s.symbol.Name, suffix); - } else { - /* This happens when we cross into another module. - * For example, in CLAR tests, this is typically - * the CRT startup code. Just print an unknown - * frame and continue. - */ - p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); - } - detail_len = strlen(detail); - - if (buf_len < (buf_used + detail_len + 1)) { - /* we don't have room for this frame in the buffer, so just stop. */ - break; - } - - memcpy(&pbuf[buf_used], detail, detail_len); - buf_used += detail_len; - } - - /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle - * allocs that occur before the aux callbacks were registered. - */ - if (pdata->aux_id > 0) { - p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", - prefix, pdata->aux_id, suffix); - detail_len = strlen(detail); - if ((buf_used + detail_len + 1) < buf_len) { - memcpy(&pbuf[buf_used], detail, detail_len); - buf_used += detail_len; - } - - /* If an "aux" data provider is still registered, ask it to append its detailed - * data to the end of ours using the "aux_id" it gave us when this de-duped - * item was created. - */ - if (g_aux_cb_lookup) - (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); - } - - return GIT_OK; -} - -int git_win32_leakcheck_stack( - char * pbuf, size_t buf_len, - int skip, - const char *prefix, const char *suffix) -{ - git_win32_leakcheck_stack_raw_data data; - int error; - - if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) - return error; - if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) - return error; - return 0; -} - -/* Stack tracing */ - -#define STACKTRACE_UID_LEN (15) - -/** - * The stacktrace of an allocation can be distilled - * to a unique id based upon the stackframe pointers - * and ignoring any size arguments. We will use these - * UIDs as the (char const*) __FILE__ argument we - * give to the CRT malloc routines. - */ -typedef struct { - char uid[STACKTRACE_UID_LEN + 1]; -} git_win32_leakcheck_stacktrace_uid; - -/** - * All mallocs with the same stacktrace will be de-duped - * and aggregated into this row. - */ -typedef struct { - git_win32_leakcheck_stacktrace_uid uid; /* must be first */ - git_win32_leakcheck_stack_raw_data raw_data; - unsigned int count_allocs; /* times this alloc signature seen since init */ - unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ - unsigned int transient_count_leaks; /* sum of leaks */ -} git_win32_leakcheck_stacktrace_row; - -static CRITICAL_SECTION g_crtdbg_stacktrace_cs; - -/** - * CRTDBG memory leak tracking takes a "char const * const file_name" - * and stores the pointer in the heap data (instead of allocing a copy - * for itself). Normally, this is not a problem, since we usually pass - * in __FILE__. But I'm going to lie to it and pass in the address of - * the UID in place of the file_name. Also, I do not want to alloc the - * stacktrace data (because we are called from inside our alloc routines). - * Therefore, I'm creating a very large static pool array to store row - * data. This also eliminates the temptation to realloc it (and move the - * UID pointers). - * - * And to efficiently look for duplicates we need an index on the rows - * so we can bsearch it. Again, without mallocing. - * - * If we observe more than MY_ROW_LIMIT unique malloc signatures, we - * fall through and use the traditional __FILE__ processing and don't - * try to de-dup them. If your testing hits this limit, just increase - * it and try again. - */ - -#define MY_ROW_LIMIT (2 * 1024 * 1024) -static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT]; -static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT]; - -static unsigned int g_cs_end = MY_ROW_LIMIT; -static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ -static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ -static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ -static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ -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 */ - -/** - * Compare function for bsearch on g_cs_index table. - */ -static int row_cmp(const void *v1, const void *v2) -{ - git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1; - git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2; - - return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); -} - -/** - * Unique insert the new data into the row and index tables. - * We have to sort by the stackframe data itself, not the uid. - */ -static git_win32_leakcheck_stacktrace_row * insert_unique( - const git_win32_leakcheck_stack_raw_data *pdata) -{ - size_t pos; - if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { - /* Append new unique item to row table. */ - memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); - sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); - - /* Insert pointer to it into the proper place in the index table. */ - if (pos < g_cs_ins) - memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); - g_cs_index[pos] = &g_cs_rows[g_cs_ins]; - - g_cs_ins++; - } - - g_cs_index[pos]->count_allocs++; - - return g_cs_index[pos]; -} - -/** - * Hook function to receive leak data from the CRT. (This includes - * both ":()" data, but also each of the - * various headers and fields. - * - * Scan this for the special "##" UID forms that we substituted - * for the "". Map back to the row data and - * increment its leak count. - * - * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx - * - * We suppress the actual crtdbg output. - */ -static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) -{ - static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ - unsigned int pos; - - *retVal = 0; /* do not invoke debugger */ - - if ((szMsg[0] != '#') || (szMsg[1] != '#')) - return hook_result; - - if (sscanf(&szMsg[2], "%08lx", &pos) < 1) - return hook_result; - if (pos >= g_cs_ins) - return hook_result; - - if (g_transient_leaks_since_mark) { - if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) - return hook_result; - } - - g_cs_rows[pos].transient_count_leaks++; - - if (g_cs_rows[pos].transient_count_leaks == 1) - g_transient_count_dedup_leaks++; - - g_transient_count_total_leaks++; - - return hook_result; -} - -/** - * Write leak data to all of the various places we need. - * We force the caller to sprintf() the message first - * because we want to avoid fprintf() because it allocs. - */ -static void my_output(const char *buf) -{ - fwrite(buf, strlen(buf), 1, stderr); - OutputDebugString(buf); -} - -/** - * For each row with leaks, dump a stacktrace for it. - */ -static void dump_summary(const char *label) -{ - unsigned int k; - char buf[10 * 1024]; - - if (g_transient_count_total_leaks == 0) - return; - - fflush(stdout); - fflush(stderr); - my_output("\n"); - - if (g_limit_reached) { - sprintf(buf, - "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", - MY_ROW_LIMIT); - my_output(buf); - } - - if (!label) - label = ""; - - if (g_transient_leaks_since_mark) { - sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", - g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); - my_output(buf); - } else { - sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", - g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); - my_output(buf); - } - my_output("\n"); - - for (k = 0; k < g_cs_ins; k++) { - if (g_cs_rows[k].transient_count_leaks > 0) { - sprintf(buf, "LEAK: %s leaked %d of %d times:\n", - g_cs_rows[k].uid.uid, - g_cs_rows[k].transient_count_leaks, - g_cs_rows[k].count_allocs); - my_output(buf); - - if (git_win32_leakcheck_stack_format( - buf, sizeof(buf), &g_cs_rows[k].raw_data, - NULL, NULL) >= 0) { - my_output(buf); - } - - my_output("\n"); - } - } - - fflush(stderr); -} - -/** - * Initialize our memory leak tracking and de-dup data structures. - * This should ONLY be called by git_libgit2_init(). - */ -void git_win32_leakcheck_stacktrace_init(void) -{ - InitializeCriticalSection(&g_crtdbg_stacktrace_cs); - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - - _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); -} - -int git_win32_leakcheck_stacktrace_dump( - git_win32_leakcheck_stacktrace_options opt, - const char *label) -{ - _CRT_REPORT_HOOK old; - unsigned int k; - int r = 0; - -#define IS_BIT_SET(o,b) (((o) & (b)) != 0) - - bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK); - bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK); - bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL); - bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET); - - if (b_leaks_since_mark && b_leaks_total) { - git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); - return GIT_ERROR; - } - if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { - git_error_set(GIT_ERROR_INVALID, "nothing to do."); - return GIT_ERROR; - } - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - if (b_leaks_since_mark || b_leaks_total) { - /* All variables with "transient" in the name are per-dump counters - * and reset before each dump. This lets us handle checkpoints. - */ - g_transient_count_total_leaks = 0; - g_transient_count_dedup_leaks = 0; - for (k = 0; k < g_cs_ins; k++) { - g_cs_rows[k].transient_count_leaks = 0; - } - } - - g_transient_leaks_since_mark = b_leaks_since_mark; - - old = _CrtSetReportHook(report_hook); - _CrtDumpMemoryLeaks(); - _CrtSetReportHook(old); - - if (b_leaks_since_mark || b_leaks_total) { - r = g_transient_count_dedup_leaks; - - if (!b_quiet) - dump_summary(label); - } - - if (b_set_mark) { - for (k = 0; k < g_cs_ins; k++) { - g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; - } - - g_checkpoint_id++; - } - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); - - return r; -} - -/** - * Shutdown our memory leak tracking and dump summary data. - * This should ONLY be called by git_libgit2_shutdown(). - * - * We explicitly call _CrtDumpMemoryLeaks() during here so - * that we can compute summary data for the leaks. We print - * the stacktrace of each unique leak. - * - * This cleanup does not happen if the app calls exit() - * without calling the libgit2 shutdown code. - * - * This info we print here is independent of any automatic - * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. - * Set it in your app if you also want traditional reporting. - */ -void git_win32_leakcheck_stacktrace_cleanup(void) -{ - /* At shutdown/cleanup, dump cumulative leak info - * with everything since startup. This might generate - * extra noise if the caller has been doing checkpoint - * dumps, but it might also eliminate some false - * positives for resources previously reported during - * checkpoints. - */ - git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, - "CLEANUP"); - - DeleteCriticalSection(&g_crtdbg_stacktrace_cs); -} - -const char *git_win32_leakcheck_stacktrace(int skip, const char *file) -{ - git_win32_leakcheck_stack_raw_data new_data; - git_win32_leakcheck_stacktrace_row *row; - const char * result = file; - - if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) - return result; - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - if (g_cs_ins < g_cs_end) { - row = insert_unique(&new_data); - result = row->uid.uid; - } else { - g_limit_reached = true; - } - - g_count_total_allocs++; - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); - - return result; -} - -static void git_win32_leakcheck_global_shutdown(void) -{ - git_win32_leakcheck_stacktrace_cleanup(); - git_win32_leakcheck_stack_cleanup(); -} - -bool git_win32_leakcheck_has_leaks(void) -{ - return (g_transient_count_total_leaks > 0); -} - -int git_win32_leakcheck_global_init(void) -{ - git_win32_leakcheck_stacktrace_init(); - git_win32_leakcheck_stack_init(); - - return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); -} - -#else - -int git_win32_leakcheck_global_init(void) -{ - return 0; -} - -#endif diff --git a/src/win32/w32_leakcheck.h b/src/win32/w32_leakcheck.h deleted file mode 100644 index cb45e3675..000000000 --- a/src/win32/w32_leakcheck.h +++ /dev/null @@ -1,222 +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_win32_leakcheck_h__ -#define INCLUDE_win32_leakcheck_h__ - -#include "common.h" - -/* Initialize the win32 leak checking system. */ -int git_win32_leakcheck_global_init(void); - -#if defined(GIT_WIN32_LEAKCHECK) - -#include -#include - -#include "git2/errors.h" -#include "strnlen.h" - -bool git_win32_leakcheck_has_leaks(void); - -/* Stack frames (for stack tracing, below) */ - -/** - * This type defines a callback to be used to augment a C stacktrace - * with "aux" data. This can be used, for example, to allow LibGit2Sharp - * (or other interpreted consumer libraries) to give us C# stacktrace - * data for the PInvoke. - * - * This callback will be called during crtdbg-instrumented allocs. - * - * @param aux_id [out] A returned "aux_id" representing a unique - * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved - * to mean no aux stacktrace data. - */ -typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id); - -/** - * This type defines a callback to be used to augment the output of - * a stacktrace. This will be used to request the C# layer format - * the C# stacktrace associated with "aux_id" into the provided - * buffer. - * - * This callback will be called during leak reporting. - * - * @param aux_id The "aux_id" key associated with a stacktrace. - * @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_leakcheck_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. - * - * This can be used, for example, to allow LibGit2Sharp (or other - * interpreted consumer libraries) to give us the C# stacktrace of - * the PInvoke. - * - * If you choose to use this feature, it should be registered during - * initialization and not changed for the duration of the process. - */ -int git_win32_leakcheck_stack_set_aux_cb( - git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, - git_win32_leakcheck_stack_aux_cb_lookup cb_lookup); - -/** - * Maximum number of stackframes to record for a - * single stacktrace. - */ -#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30 - -/** - * Wrapper containing the raw unprocessed stackframe - * data for a single stacktrace and any "aux_id". - * - * I put the aux_id first so leaks will be sorted by it. - * So, for example, if a specific callstack in C# leaks - * a repo handle, all of the pointers within the associated - * repo pointer will be grouped together. - */ -typedef struct { - unsigned int aux_id; - unsigned int nr_frames; - void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES]; -} git_win32_leakcheck_stack_raw_data; - -/** - * Capture raw stack trace data for the current process/thread. - * - * @param skip Number of initial frames to skip. Pass 0 to - * begin with the caller of this routine. Pass 1 to begin - * with its caller. And so on. - */ -int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip); - -/** - * Compare 2 raw stacktraces with the usual -1,0,+1 result. - * This includes any "aux_id" values in the comparison, so that - * our de-dup is also "aux" context relative. - */ -int git_win32_leakcheck_stack_compare( - git_win32_leakcheck_stack_raw_data *d1, - git_win32_leakcheck_stack_raw_data *d2); - -/** - * Format raw stacktrace data into buffer WITHOUT using any mallocs. - * - * @param prefix String written before each frame; defaults to "\t". - * @param suffix String written after each frame; defaults to "\n". - */ -int git_win32_leakcheck_stack_format( - char *pbuf, size_t buf_len, - const git_win32_leakcheck_stack_raw_data *pdata, - const char *prefix, const char *suffix); - -/** - * Convenience routine to capture and format stacktrace into - * a buffer WITHOUT using any mallocs. This is primarily a - * wrapper for testing. - * - * @param skip Number of initial frames to skip. Pass 0 to - * begin with the caller of this routine. Pass 1 to begin - * with its caller. And so on. - * @param prefix String written before each frame; defaults to "\t". - * @param suffix String written after each frame; defaults to "\n". - */ -int git_win32_leakcheck_stack( - char * pbuf, size_t buf_len, - int skip, - const char *prefix, const char *suffix); - -/* Stack tracing */ - -/* MSVC CRTDBG memory leak reporting. - * - * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC - * documentation because all allocs/frees in libgit2 already go through - * the "git__" routines defined in this file. Simply using the normal - * reporting mechanism causes all leaks to be attributed to a routine - * here in util.h (ie, the actual call to calloc()) rather than the - * caller of git__calloc(). - * - * Therefore, we declare a set of "git__crtdbg__" routines to replace - * the corresponding "git__" routines and re-define the "git__" symbols - * as macros. This allows us to get and report the file:line info of - * the real caller. - * - * We DO NOT replace the "git__free" routine because it needs to remain - * a function pointer because it is used as a function argument when - * setting up various structure "destructors". - * - * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes - * "free" to be remapped to "_free_dbg" and this causes problems for - * structures which define a field named "free". - * - * Finally, CRTDBG must be explicitly enabled and configured at program - * startup. See tests/main.c for an example. - */ - -/** - * Checkpoint options. - */ -typedef enum git_win32_leakcheck_stacktrace_options { - /** - * Set checkpoint marker. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0), - - /** - * Dump leaks since last checkpoint marker. - * May not be combined with _LEAKS_TOTAL. - * - * Note that this may generate false positives for global TLS - * error state and other global caches that aren't cleaned up - * until the thread/process terminates. So when using this - * around a region of interest, also check the final (at exit) - * dump before digging into leaks reported here. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1), - - /** - * Dump leaks since init. May not be combined - * with _LEAKS_SINCE_MARK. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2), - - /** - * Suppress printing during dumps. - * Just return leak count. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3), - -} git_win32_leakcheck_stacktrace_options; - -/** - * Checkpoint memory state and/or dump unique stack traces of - * current memory leaks. - * - * @return number of unique leaks (relative to requested starting - * point) or error. - */ -int git_win32_leakcheck_stacktrace_dump( - git_win32_leakcheck_stacktrace_options opt, - const char *label); - -/** - * Construct stacktrace and append it to the global buffer. - * Return pointer to start of this string. On any error or - * lack of buffer space, just return the given file buffer - * so it will behave as usual. - * - * This should ONLY be called by our internal memory allocations - * routines. - */ -const char *git_win32_leakcheck_stacktrace(int skip, const char *file); - -#endif -#endif diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c deleted file mode 100644 index fe4b75bae..000000000 --- a/src/win32/w32_util.c +++ /dev/null @@ -1,126 +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 "w32_util.h" - -/** - * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. - * The filter string enumerates all items in the directory. - * - * @param dest The buffer to receive the filter string. - * @param src The UTF-8 path of the directory to enumerate. - * @return True if the filter string was created successfully; false otherwise - */ -bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) -{ - static const wchar_t suffix[] = L"\\*"; - int len = git_win32_path_from_utf8(dest, src); - - /* Ensure the path was converted */ - if (len < 0) - return false; - - /* Ensure that the path does not end with a trailing slash, - * because we're about to add one. Don't rely our trim_end - * helper, because we want to remove the backslash even for - * drive letter paths, in this case. */ - if (len > 0 && - (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { - dest[len - 1] = L'\0'; - len--; - } - - /* Ensure we have enough room to add the suffix */ - if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix)) - return false; - - wcscat(dest, suffix); - return true; -} - -/** - * Ensures the given path (file or folder) has the +H (hidden) attribute set. - * - * @param path The path which should receive the +H bit. - * @return 0 on success; -1 on failure - */ -int git_win32__set_hidden(const char *path, bool hidden) -{ - git_win32_path buf; - DWORD attrs, newattrs; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - attrs = GetFileAttributesW(buf); - - /* Ensure the path exists */ - if (attrs == INVALID_FILE_ATTRIBUTES) - return -1; - - if (hidden) - newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; - else - newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; - - if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { - git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'", - hidden ? "set" : "unset", path); - return -1; - } - - return 0; -} - -int git_win32__hidden(bool *out, const char *path) -{ - git_win32_path buf; - DWORD attrs; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - attrs = GetFileAttributesW(buf); - - /* Ensure the path exists */ - if (attrs == INVALID_FILE_ATTRIBUTES) - return -1; - - *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 deleted file mode 100644 index 1321d30e6..000000000 --- a/src/win32/w32_util.h +++ /dev/null @@ -1,144 +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_win32_w32_util_h__ -#define INCLUDE_win32_w32_util_h__ - -#include "common.h" - -#include "utf-conv.h" -#include "posix.h" -#include "path_w32.h" - -/* - -#include "common.h" -#include "path.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" -*/ - - -GIT_INLINE(bool) git_win32__isalpha(wchar_t c) -{ - return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z')); -} - -/** - * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. - * The filter string enumerates all items in the directory. - * - * @param dest The buffer to receive the filter string. - * @param src The UTF-8 path of the directory to enumerate. - * @return True if the filter string was created successfully; false otherwise - */ -bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); - -/** - * Ensures the given path (file or folder) has the +H (hidden) attribute set - * or unset. - * - * @param path The path that should receive the +H bit. - * @param hidden true to set +H, false to unset it - * @return 0 on success; -1 on failure - */ -extern int git_win32__set_hidden(const char *path, bool hidden); - -/** - * Determines if the given file or folder has the hidden attribute set. - * @param hidden pointer to store hidden value - * @param path The path that should be queried for hiddenness. - * @return 0 on success or an error code. - */ -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. - * - * @param FILETIME A pointer to a FILETIME - * @param ts A pointer to the timespec structure to fill in - */ -GIT_INLINE(void) git_win32__filetime_to_timespec( - const FILETIME *ft, - struct timespec *ts) -{ - int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */ - ts->tv_sec = (time_t)(winTime / 10000000); -#ifdef GIT_USE_NSEC - ts->tv_nsec = (winTime % 10000000) * 100; -#else - ts->tv_nsec = 0; -#endif -} - -GIT_INLINE(void) git_win32__timeval_to_filetime( - FILETIME *ft, const struct p_timeval tv) -{ - int64_t ticks = (tv.tv_sec * INT64_C(10000000)) + - (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000); - - ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff)); - ft->dwLowDateTime = (ticks & INT64_C(0xffffffff)); -} - -GIT_INLINE(void) git_win32__stat_init( - struct stat *st, - DWORD dwFileAttributes, - DWORD nFileSizeHigh, - DWORD nFileSizeLow, - FILETIME ftCreationTime, - FILETIME ftLastAccessTime, - FILETIME ftLastWriteTime) -{ - mode_t mode = S_IREAD; - - memset(st, 0, sizeof(struct stat)); - - if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - mode |= S_IFDIR; - else - mode |= S_IFREG; - - if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) - mode |= S_IWRITE; - - st->st_ino = 0; - st->st_gid = 0; - st->st_uid = 0; - st->st_nlink = 1; - st->st_mode = mode; - 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)); - git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); - git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); -} - -GIT_INLINE(void) git_win32__file_information_to_stat( - struct stat *st, - const BY_HANDLE_FILE_INFORMATION *fileinfo) -{ - git_win32__stat_init(st, - fileinfo->dwFileAttributes, - fileinfo->nFileSizeHigh, - fileinfo->nFileSizeLow, - fileinfo->ftCreationTime, - fileinfo->ftLastAccessTime, - fileinfo->ftLastWriteTime); -} - -#endif diff --git a/src/win32/win32-compat.h b/src/win32/win32-compat.h deleted file mode 100644 index dee40a438..000000000 --- a/src/win32/win32-compat.h +++ /dev/null @@ -1,52 +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_win32_win32_compat_h__ -#define INCLUDE_win32_win32_compat_h__ - -#include -#include -#include -#include -#include - -typedef long suseconds_t; - -struct p_timeval { - time_t tv_sec; - suseconds_t tv_usec; -}; - -struct p_timespec { - time_t tv_sec; - long tv_nsec; -}; - -#define timespec p_timespec - -struct p_stat { - _dev_t st_dev; - _ino_t st_ino; - mode_t st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - __int64 st_size; - struct timespec st_atim; - struct timespec st_mtim; - struct timespec st_ctim; -#define st_atime st_atim.tv_sec -#define st_mtime st_mtim.tv_sec -#define st_ctime st_ctim.tv_sec -#define st_atime_nsec st_atim.tv_nsec -#define st_mtime_nsec st_mtim.tv_nsec -#define st_ctime_nsec st_ctim.tv_nsec -}; - -#define stat p_stat - -#endif diff --git a/src/worktree.c b/src/worktree.c deleted file mode 100644 index 2ac2274f1..000000000 --- a/src/worktree.c +++ /dev/null @@ -1,652 +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 "worktree.h" - -#include "buf.h" -#include "repository.h" -#include "path.h" - -#include "git2/branch.h" -#include "git2/commit.h" -#include "git2/worktree.h" - -static bool is_worktree_dir(const char *dir) -{ - git_str buf = GIT_STR_INIT; - int error; - - if (git_str_sets(&buf, dir) < 0) - return -1; - - error = git_fs_path_contains_file(&buf, "commondir") - && git_fs_path_contains_file(&buf, "gitdir") - && git_fs_path_contains_file(&buf, "HEAD"); - - git_str_dispose(&buf); - return error; -} - -int git_worktree_list(git_strarray *wts, git_repository *repo) -{ - git_vector worktrees = GIT_VECTOR_INIT; - git_str path = GIT_STR_INIT; - char *worktree; - size_t i, len; - int error; - - GIT_ASSERT_ARG(wts); - GIT_ASSERT_ARG(repo); - - wts->count = 0; - wts->strings = NULL; - - if ((error = git_str_joinpath(&path, repo->commondir, "worktrees/")) < 0) - goto exit; - if (!git_fs_path_exists(path.ptr) || git_fs_path_is_empty_dir(path.ptr)) - goto exit; - if ((error = git_fs_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0) - goto exit; - - len = path.size; - - git_vector_foreach(&worktrees, i, worktree) { - git_str_truncate(&path, len); - git_str_puts(&path, worktree); - - if (!is_worktree_dir(path.ptr)) { - git_vector_remove(&worktrees, i); - git__free(worktree); - } - } - - wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees); - -exit: - git_str_dispose(&path); - - return error; -} - -char *git_worktree__read_link(const char *base, const char *file) -{ - git_str path = GIT_STR_INIT, buf = GIT_STR_INIT; - - GIT_ASSERT_ARG_WITH_RETVAL(base, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(file, NULL); - - if (git_str_joinpath(&path, base, file) < 0) - goto err; - if (git_futils_readbuffer(&buf, path.ptr) < 0) - goto err; - git_str_dispose(&path); - - git_str_rtrim(&buf); - - if (!git_fs_path_is_relative(buf.ptr)) - return git_str_detach(&buf); - - if (git_str_sets(&path, base) < 0) - goto err; - if (git_fs_path_apply_relative(&path, buf.ptr) < 0) - goto err; - git_str_dispose(&buf); - - return git_str_detach(&path); - -err: - git_str_dispose(&buf); - git_str_dispose(&path); - - return NULL; -} - -static int write_wtfile(const char *base, const char *file, const git_str *buf) -{ - git_str path = GIT_STR_INIT; - int err; - - GIT_ASSERT_ARG(base); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(buf); - - if ((err = git_str_joinpath(&path, base, file)) < 0) - goto out; - - if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) - goto out; - -out: - git_str_dispose(&path); - - return err; -} - -static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name) -{ - git_str gitdir = GIT_STR_INIT; - git_worktree *wt = NULL; - int error = 0; - - if (!is_worktree_dir(dir)) { - error = -1; - goto out; - } - - if ((error = git_path_validate_length(NULL, dir)) < 0) - goto out; - - if ((wt = git__calloc(1, sizeof(*wt))) == NULL) { - error = -1; - 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_fs_path_dirname(wt->gitlink_path)) == NULL) { - error = -1; - goto out; - } - - if ((error = git_fs_path_prettify_dir(&gitdir, dir, NULL)) < 0) - goto out; - wt->gitdir_path = git_str_detach(&gitdir); - - if ((error = git_worktree_is_locked(NULL, wt)) < 0) - goto out; - wt->locked = !!error; - error = 0; - - *out = wt; - -out: - if (error) - git_worktree_free(wt); - git_str_dispose(&gitdir); - - return error; -} - -int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) -{ - git_str path = GIT_STR_INIT; - git_worktree *wt = NULL; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - *out = NULL; - - if ((error = git_str_join3(&path, '/', repo->commondir, "worktrees", name)) < 0) - goto out; - - if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0) - goto out; - -out: - git_str_dispose(&path); - - if (error) - git_worktree_free(wt); - - return error; -} - -int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) -{ - git_str parent = GIT_STR_INIT; - const char *gitdir, *commondir; - char *name = NULL; - int error = 0; - - if (!git_repository_is_worktree(repo)) { - git_error_set(GIT_ERROR_WORKTREE, "cannot open worktree of a non-worktree repo"); - error = -1; - goto out; - } - - gitdir = git_repository_path(repo); - commondir = git_repository_commondir(repo); - - if ((error = git_fs_path_prettify_dir(&parent, "..", commondir)) < 0) - goto out; - - /* The name is defined by the last component in '.git/worktree/%s' */ - name = git_fs_path_basename(gitdir); - - if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0) - goto out; - -out: - git__free(name); - git_str_dispose(&parent); - - return error; -} - -void git_worktree_free(git_worktree *wt) -{ - if (!wt) - return; - - git__free(wt->commondir_path); - git__free(wt->worktree_path); - git__free(wt->gitlink_path); - git__free(wt->gitdir_path); - git__free(wt->parent_path); - git__free(wt->name); - git__free(wt); -} - -int git_worktree_validate(const git_worktree *wt) -{ - GIT_ASSERT_ARG(wt); - - if (!is_worktree_dir(wt->gitdir_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree gitdir ('%s') is not valid", - wt->gitlink_path); - return GIT_ERROR; - } - - if (wt->parent_path && !git_fs_path_exists(wt->parent_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree parent directory ('%s') does not exist ", - wt->parent_path); - return GIT_ERROR; - } - - if (!git_fs_path_exists(wt->commondir_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree common directory ('%s') does not exist ", - wt->commondir_path); - return GIT_ERROR; - } - - if (!git_fs_path_exists(wt->worktree_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree directory '%s' does not exist", - wt->worktree_path); - return GIT_ERROR; - } - - return 0; -} - -int git_worktree_add_options_init(git_worktree_add_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, - git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_worktree_add_init_options(git_worktree_add_options *opts, - unsigned int version) -{ - return git_worktree_add_options_init(opts, version); -} -#endif - -int git_worktree_add(git_worktree **out, git_repository *repo, - const char *name, const char *worktree, - const git_worktree_add_options *opts) -{ - git_str gitdir = GIT_STR_INIT, wddir = GIT_STR_INIT, buf = GIT_STR_INIT; - git_reference *ref = NULL, *head = NULL; - git_commit *commit = NULL; - git_repository *wt = NULL; - git_checkout_options coopts; - git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT; - int err; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options"); - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(worktree); - - *out = NULL; - - if (opts) - memcpy(&wtopts, opts, sizeof(wtopts)); - - memcpy(&coopts, &wtopts.checkout_options, sizeof(coopts)); - - 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_str_joinpath(&gitdir, repo->commondir, "worktrees")) < 0) - goto out; - if (!git_fs_path_exists(gitdir.ptr)) - if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) - goto out; - if ((err = git_str_joinpath(&gitdir, gitdir.ptr, name)) < 0) - goto out; - if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) - goto out; - if ((err = git_fs_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0) - goto out; - - /* Create worktree work dir */ - if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0) - goto out; - if ((err = git_fs_path_prettify_dir(&wddir, worktree, NULL)) < 0) - goto out; - - if (wtopts.lock) { - int fd; - - if ((err = git_str_joinpath(&buf, gitdir.ptr, "locked")) < 0) - goto out; - - if ((fd = p_creat(buf.ptr, 0644)) < 0) { - err = fd; - goto out; - } - - p_close(fd); - git_str_clear(&buf); - } - - /* Create worktree .git file */ - if ((err = git_str_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) - goto out; - if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0) - goto out; - - /* Create gitdir files */ - if ((err = git_fs_path_prettify_dir(&buf, repo->commondir, NULL) < 0) - || (err = git_str_putc(&buf, '\n')) < 0 - || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0) - goto out; - if ((err = git_str_joinpath(&buf, wddir.ptr, ".git")) < 0 - || (err = git_str_putc(&buf, '\n')) < 0 - || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) - goto out; - - /* Set up worktree reference */ - if (wtopts.ref) { - if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) - goto out; - } else { - if ((err = git_repository_head(&head, repo)) < 0) - goto out; - if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0) - goto out; - if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0) - goto out; - } - - /* Set worktree's HEAD */ - if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) - goto out; - if ((err = git_repository_open(&wt, wddir.ptr)) < 0) - goto out; - - /* Checkout worktree's HEAD */ - if ((err = git_checkout_head(wt, &coopts)) < 0) - goto out; - - /* Load result */ - if ((err = git_worktree_lookup(out, repo, name)) < 0) - goto out; - -out: - git_str_dispose(&gitdir); - git_str_dispose(&wddir); - git_str_dispose(&buf); - git_reference_free(ref); - git_reference_free(head); - git_commit_free(commit); - git_repository_free(wt); - - return err; -} - -int git_worktree_lock(git_worktree *wt, const char *reason) -{ - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(wt); - - if ((error = git_worktree_is_locked(NULL, wt)) < 0) - goto out; - if (error) { - error = GIT_ELOCKED; - goto out; - } - - if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) - goto out; - - if (reason) - git_str_attach_notowned(&buf, reason, strlen(reason)); - - if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) - goto out; - - wt->locked = 1; - -out: - git_str_dispose(&path); - - return error; -} - -int git_worktree_unlock(git_worktree *wt) -{ - git_str path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(wt); - - if ((error = git_worktree_is_locked(NULL, wt)) < 0) - return error; - if (!error) - return 1; - - if (git_str_joinpath(&path, wt->gitdir_path, "locked") < 0) - return -1; - - if (p_unlink(path.ptr) != 0) { - git_str_dispose(&path); - return -1; - } - - wt->locked = 0; - - git_str_dispose(&path); - - return 0; -} - -static int git_worktree__is_locked(git_str *reason, const git_worktree *wt) -{ - git_str path = GIT_STR_INIT; - int error, locked; - - GIT_ASSERT_ARG(wt); - - if (reason) - git_str_clear(reason); - - if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) - goto out; - locked = git_fs_path_exists(path.ptr); - if (locked && reason && - (error = git_futils_readbuffer(reason, path.ptr)) < 0) - goto out; - - error = locked; -out: - git_str_dispose(&path); - - return error; -} - -int git_worktree_is_locked(git_buf *reason, const git_worktree *wt) -{ - git_str str = GIT_STR_INIT; - int error = 0; - - if (reason && (error = git_buf_tostr(&str, reason)) < 0) - return error; - - error = git_worktree__is_locked(reason ? &str : NULL, wt); - - if (error >= 0 && reason) { - if (git_buf_fromstr(reason, &str) < 0) - error = -1; - } - - git_str_dispose(&str); - return error; -} - -const char *git_worktree_name(const git_worktree *wt) -{ - GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); - return wt->name; -} - -const char *git_worktree_path(const git_worktree *wt) -{ - GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); - return wt->worktree_path; -} - -int git_worktree_prune_options_init( - git_worktree_prune_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, - git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_worktree_prune_init_options(git_worktree_prune_options *opts, - unsigned int version) -{ - return git_worktree_prune_options_init(opts, version); -} -#endif - -int git_worktree_is_prunable(git_worktree *wt, - git_worktree_prune_options *opts) -{ - git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, - "git_worktree_prune_options"); - - if (opts) - memcpy(&popts, opts, sizeof(popts)); - - if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) { - git_str reason = GIT_STR_INIT; - int error; - - if ((error = git_worktree__is_locked(&reason, wt)) < 0) - return error; - - if (error) { - if (!reason.size) - git_str_attach_notowned(&reason, "no reason given", 15); - git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr); - git_str_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"); - return 0; - } - - return 1; -} - -int git_worktree_prune(git_worktree *wt, - git_worktree_prune_options *opts) -{ - git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_str path = GIT_STR_INIT; - char *wtpath; - int err; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, - "git_worktree_prune_options"); - - if (opts) - memcpy(&popts, opts, sizeof(popts)); - - if (!git_worktree_is_prunable(wt, &popts)) { - err = -1; - goto out; - } - - /* Delete gitdir in parent repository */ - if ((err = git_str_join3(&path, '/', wt->commondir_path, "worktrees", wt->name)) < 0) - goto out; - if (!git_fs_path_exists(path.ptr)) - { - git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr); - err = -1; - goto out; - } - if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) - goto out; - - /* Skip deletion of the actual working tree if it does - * not exist or deletion was not requested */ - if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || - !git_fs_path_exists(wt->gitlink_path)) - { - goto out; - } - - if ((wtpath = git_fs_path_dirname(wt->gitlink_path)) == NULL) - goto out; - git_str_attach(&path, wtpath, 0); - if (!git_fs_path_exists(path.ptr)) - { - git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr); - err = -1; - goto out; - } - if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) - goto out; - -out: - git_str_dispose(&path); - - return err; -} diff --git a/src/worktree.h b/src/worktree.h deleted file mode 100644 index 587189f81..000000000 --- a/src/worktree.h +++ /dev/null @@ -1,39 +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_worktree_h__ -#define INCLUDE_worktree_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/worktree.h" - -struct git_worktree { - /* Name of the working tree. This is the name of the - * containing directory in the `$PARENT/.git/worktrees/` - * directory. */ - char *name; - - /* Path to the where the worktree lives in the filesystem */ - char *worktree_path; - /* Path to the .git file in the working tree's repository */ - char *gitlink_path; - /* Path to the .git directory inside the parent's - * worktrees directory */ - char *gitdir_path; - /* Path to the common directory contained in the parent - * repository */ - char *commondir_path; - /* Path to the parent's working directory */ - char *parent_path; - - unsigned int locked:1; -}; - -char *git_worktree__read_link(const char *base, const char *file); - -#endif diff --git a/src/xdiff/git-xdiff.h b/src/xdiff/git-xdiff.h deleted file mode 100644 index b75dba819..000000000 --- a/src/xdiff/git-xdiff.h +++ /dev/null @@ -1,53 +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 provides the necessary indirection between xdiff and - * libgit2. libgit2-specific functionality should live here, so - * that git and libgit2 can share a common xdiff implementation. - */ - -#ifndef INCLUDE_git_xdiff_h__ -#define INCLUDE_git_xdiff_h__ - -#include "regexp.h" - -/* Work around C90-conformance issues */ -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# if defined(_MSC_VER) -# define inline __inline -# elif defined(__GNUC__) -# define inline __inline__ -# else -# define inline -# endif -#endif - -#define xdl_malloc(x) git__malloc(x) -#define xdl_free(ptr) git__free(ptr) -#define xdl_realloc(ptr, x) git__realloc(ptr, x) - -#define XDL_BUG(msg) GIT_ASSERT(msg) - -#define xdl_regex_t git_regexp -#define xdl_regmatch_t git_regmatch - -GIT_INLINE(int) xdl_regexec_buf( - const xdl_regex_t *preg, const char *buf, size_t size, - size_t nmatch, xdl_regmatch_t pmatch[], int eflags) -{ - GIT_UNUSED(preg); - GIT_UNUSED(buf); - GIT_UNUSED(size); - GIT_UNUSED(nmatch); - GIT_UNUSED(pmatch); - GIT_UNUSED(eflags); - GIT_ASSERT("not implemented"); - return -1; -} - -#endif diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h deleted file mode 100644 index fb47f63fb..000000000 --- a/src/xdiff/xdiff.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XDIFF_H) -#define XDIFF_H - -#ifdef __cplusplus -extern "C" { -#endif /* #ifdef __cplusplus */ - -#include "git-xdiff.h" - -/* xpparm_t.flags */ -#define XDF_NEED_MINIMAL (1 << 0) - -#define XDF_IGNORE_WHITESPACE (1 << 1) -#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2) -#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3) -#define XDF_IGNORE_CR_AT_EOL (1 << 4) -#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \ - XDF_IGNORE_WHITESPACE_CHANGE | \ - XDF_IGNORE_WHITESPACE_AT_EOL | \ - XDF_IGNORE_CR_AT_EOL) - -#define XDF_IGNORE_BLANK_LINES (1 << 7) - -#define XDF_PATIENCE_DIFF (1 << 14) -#define XDF_HISTOGRAM_DIFF (1 << 15) -#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) -#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) - -#define XDF_INDENT_HEURISTIC (1 << 23) - -/* xdemitconf_t.flags */ -#define XDL_EMIT_FUNCNAMES (1 << 0) -#define XDL_EMIT_NO_HUNK_HDR (1 << 1) -#define XDL_EMIT_FUNCCONTEXT (1 << 2) - -/* merge simplification levels */ -#define XDL_MERGE_MINIMAL 0 -#define XDL_MERGE_EAGER 1 -#define XDL_MERGE_ZEALOUS 2 -#define XDL_MERGE_ZEALOUS_ALNUM 3 - -/* merge favor modes */ -#define XDL_MERGE_FAVOR_OURS 1 -#define XDL_MERGE_FAVOR_THEIRS 2 -#define XDL_MERGE_FAVOR_UNION 3 - -/* merge output styles */ -#define XDL_MERGE_DIFF3 1 -#define XDL_MERGE_ZEALOUS_DIFF3 2 - -typedef struct s_mmfile { - char *ptr; - long size; -} mmfile_t; - -typedef struct s_mmbuffer { - char *ptr; - long size; -} mmbuffer_t; - -typedef struct s_xpparam { - unsigned long flags; - - /* -I */ - xdl_regex_t **ignore_regex; - size_t ignore_regex_nr; - - /* See Documentation/diff-options.txt. */ - char **anchors; - size_t anchors_nr; -} xpparam_t; - -typedef struct s_xdemitcb { - void *priv; - int (*out_hunk)(void *, - long old_begin, long old_nr, - long new_begin, long new_nr, - const char *func, long funclen); - int (*out_line)(void *, mmbuffer_t *, int); -} xdemitcb_t; - -typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); - -typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a, - long start_b, long count_b, - void *cb_data); - -typedef struct s_xdemitconf { - long ctxlen; - long interhunkctxlen; - unsigned long flags; - find_func_t find_func; - void *find_func_priv; - xdl_emit_hunk_consume_func_t hunk_func; -} xdemitconf_t; - -typedef struct s_bdiffparam { - long bsize; -} bdiffparam_t; - - -void *xdl_mmfile_first(mmfile_t *mmf, long *size); -long xdl_mmfile_size(mmfile_t *mmf); - -int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *ecb); - -typedef struct s_xmparam { - xpparam_t xpp; - int marker_size; - int level; - int favor; - int style; - const char *ancestor; /* label for orig */ - const char *file1; /* label for mf1 */ - const char *file2; /* label for mf2 */ -} xmparam_t; - -#define DEFAULT_CONFLICT_MARKER_SIZE 7 - -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, - xmparam_t const *xmp, mmbuffer_t *result); - -#ifdef __cplusplus -} -#endif /* #ifdef __cplusplus */ - -#endif /* #if !defined(XDIFF_H) */ diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c deleted file mode 100644 index af31b7f4b..000000000 --- a/src/xdiff/xdiffi.c +++ /dev/null @@ -1,1088 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - -#define XDL_MAX_COST_MIN 256 -#define XDL_HEUR_MIN_COST 256 -#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) -#define XDL_SNAKE_CNT 20 -#define XDL_K_HEUR 4 - -typedef struct s_xdpsplit { - long i1, i2; - int min_lo, min_hi; -} xdpsplit_t; - -/* - * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. - * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both - * the forward diagonal starting from (off1, off2) and the backward diagonal - * starting from (lim1, lim2). If the K values on the same diagonal crosses - * returns the furthest point of reach. We might encounter expensive edge cases - * using this algorithm, so a little bit of heuristic is needed to cut the - * search and to return a suboptimal point. - */ -static long xdl_split(unsigned long const *ha1, long off1, long lim1, - unsigned long const *ha2, long off2, long lim2, - long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, - xdalgoenv_t *xenv) { - long dmin = off1 - lim2, dmax = lim1 - off2; - long fmid = off1 - off2, bmid = lim1 - lim2; - long odd = (fmid - bmid) & 1; - long fmin = fmid, fmax = fmid; - long bmin = bmid, bmax = bmid; - long ec, d, i1, i2, prev1, best, dd, v, k; - - /* - * Set initial diagonal values for both forward and backward path. - */ - kvdf[fmid] = off1; - kvdb[bmid] = lim1; - - for (ec = 1;; ec++) { - int got_snake = 0; - - /* - * We need to extend the diagonal "domain" by one. If the next - * values exits the box boundaries we need to change it in the - * opposite direction because (max - min) must be a power of - * two. - * - * Also we initialize the external K value to -1 so that we can - * avoid extra conditions in the check inside the core loop. - */ - if (fmin > dmin) - kvdf[--fmin - 1] = -1; - else - ++fmin; - if (fmax < dmax) - kvdf[++fmax + 1] = -1; - else - --fmax; - - for (d = fmax; d >= fmin; d -= 2) { - if (kvdf[d - 1] >= kvdf[d + 1]) - i1 = kvdf[d - 1] + 1; - else - i1 = kvdf[d + 1]; - prev1 = i1; - i2 = i1 - d; - for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); - if (i1 - prev1 > xenv->snake_cnt) - got_snake = 1; - kvdf[d] = i1; - if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { - spl->i1 = i1; - spl->i2 = i2; - spl->min_lo = spl->min_hi = 1; - return ec; - } - } - - /* - * We need to extend the diagonal "domain" by one. If the next - * values exits the box boundaries we need to change it in the - * opposite direction because (max - min) must be a power of - * two. - * - * Also we initialize the external K value to -1 so that we can - * avoid extra conditions in the check inside the core loop. - */ - if (bmin > dmin) - kvdb[--bmin - 1] = XDL_LINE_MAX; - else - ++bmin; - if (bmax < dmax) - kvdb[++bmax + 1] = XDL_LINE_MAX; - else - --bmax; - - for (d = bmax; d >= bmin; d -= 2) { - if (kvdb[d - 1] < kvdb[d + 1]) - i1 = kvdb[d - 1]; - else - i1 = kvdb[d + 1] - 1; - prev1 = i1; - i2 = i1 - d; - for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); - if (prev1 - i1 > xenv->snake_cnt) - got_snake = 1; - kvdb[d] = i1; - if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { - spl->i1 = i1; - spl->i2 = i2; - spl->min_lo = spl->min_hi = 1; - return ec; - } - } - - if (need_min) - continue; - - /* - * If the edit cost is above the heuristic trigger and if - * we got a good snake, we sample current diagonals to see - * if some of them have reached an "interesting" path. Our - * measure is a function of the distance from the diagonal - * corner (i1 + i2) penalized with the distance from the - * mid diagonal itself. If this value is above the current - * edit cost times a magic factor (XDL_K_HEUR) we consider - * it interesting. - */ - if (got_snake && ec > xenv->heur_min) { - for (best = 0, d = fmax; d >= fmin; d -= 2) { - dd = d > fmid ? d - fmid: fmid - d; - i1 = kvdf[d]; - i2 = i1 - d; - v = (i1 - off1) + (i2 - off2) - dd; - - if (v > XDL_K_HEUR * ec && v > best && - off1 + xenv->snake_cnt <= i1 && i1 < lim1 && - off2 + xenv->snake_cnt <= i2 && i2 < lim2) { - for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) - if (k == xenv->snake_cnt) { - best = v; - spl->i1 = i1; - spl->i2 = i2; - break; - } - } - } - if (best > 0) { - spl->min_lo = 1; - spl->min_hi = 0; - return ec; - } - - for (best = 0, d = bmax; d >= bmin; d -= 2) { - dd = d > bmid ? d - bmid: bmid - d; - i1 = kvdb[d]; - i2 = i1 - d; - v = (lim1 - i1) + (lim2 - i2) - dd; - - if (v > XDL_K_HEUR * ec && v > best && - off1 < i1 && i1 <= lim1 - xenv->snake_cnt && - off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { - for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) - if (k == xenv->snake_cnt - 1) { - best = v; - spl->i1 = i1; - spl->i2 = i2; - break; - } - } - } - if (best > 0) { - spl->min_lo = 0; - spl->min_hi = 1; - return ec; - } - } - - /* - * Enough is enough. We spent too much time here and now we - * collect the furthest reaching path using the (i1 + i2) - * measure. - */ - if (ec >= xenv->mxcost) { - long fbest, fbest1, bbest, bbest1; - - fbest = fbest1 = -1; - for (d = fmax; d >= fmin; d -= 2) { - i1 = XDL_MIN(kvdf[d], lim1); - i2 = i1 - d; - if (lim2 < i2) - i1 = lim2 + d, i2 = lim2; - if (fbest < i1 + i2) { - fbest = i1 + i2; - fbest1 = i1; - } - } - - bbest = bbest1 = XDL_LINE_MAX; - for (d = bmax; d >= bmin; d -= 2) { - i1 = XDL_MAX(off1, kvdb[d]); - i2 = i1 - d; - if (i2 < off2) - i1 = off2 + d, i2 = off2; - if (i1 + i2 < bbest) { - bbest = i1 + i2; - bbest1 = i1; - } - } - - if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { - spl->i1 = fbest1; - spl->i2 = fbest - fbest1; - spl->min_lo = 1; - spl->min_hi = 0; - } else { - spl->i1 = bbest1; - spl->i2 = bbest - bbest1; - spl->min_lo = 0; - spl->min_hi = 1; - } - return ec; - } - } -} - - -/* - * Rule: "Divide et Impera" (divide & conquer). Recursively split the box in - * sub-boxes by calling the box splitting function. Note that the real job - * (marking changed lines) is done in the two boundary reaching checks. - */ -int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, - diffdata_t *dd2, long off2, long lim2, - long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { - unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; - - /* - * Shrink the box by walking through each diagonal snake (SW and NE). - */ - for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); - for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); - - /* - * If one dimension is empty, then all records on the other one must - * be obviously changed. - */ - if (off1 == lim1) { - char *rchg2 = dd2->rchg; - long *rindex2 = dd2->rindex; - - for (; off2 < lim2; off2++) - rchg2[rindex2[off2]] = 1; - } else if (off2 == lim2) { - char *rchg1 = dd1->rchg; - long *rindex1 = dd1->rindex; - - for (; off1 < lim1; off1++) - rchg1[rindex1[off1]] = 1; - } else { - xdpsplit_t spl; - spl.i1 = spl.i2 = 0; - - /* - * Divide ... - */ - if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, - need_min, &spl, xenv) < 0) { - - return -1; - } - - /* - * ... et Impera. - */ - if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, - kvdf, kvdb, spl.min_lo, xenv) < 0 || - xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, - kvdf, kvdb, spl.min_hi, xenv) < 0) { - - return -1; - } - } - - return 0; -} - - -int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe) { - long ndiags; - long *kvd, *kvdf, *kvdb; - xdalgoenv_t xenv; - diffdata_t dd1, dd2; - - if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF) - return xdl_do_patience_diff(mf1, mf2, xpp, xe); - - if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF) - return xdl_do_histogram_diff(mf1, mf2, xpp, xe); - - if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { - - return -1; - } - - /* - * Allocate and setup K vectors to be used by the differential - * algorithm. - * - * One is to store the forward path and one to store the backward path. - */ - ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; - if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { - - xdl_free_env(xe); - return -1; - } - kvdf = kvd; - kvdb = kvdf + ndiags; - kvdf += xe->xdf2.nreff + 1; - kvdb += xe->xdf2.nreff + 1; - - xenv.mxcost = xdl_bogosqrt(ndiags); - if (xenv.mxcost < XDL_MAX_COST_MIN) - xenv.mxcost = XDL_MAX_COST_MIN; - xenv.snake_cnt = XDL_SNAKE_CNT; - xenv.heur_min = XDL_HEUR_MIN_COST; - - dd1.nrec = xe->xdf1.nreff; - dd1.ha = xe->xdf1.ha; - dd1.rchg = xe->xdf1.rchg; - dd1.rindex = xe->xdf1.rindex; - dd2.nrec = xe->xdf2.nreff; - dd2.ha = xe->xdf2.ha; - dd2.rchg = xe->xdf2.rchg; - dd2.rindex = xe->xdf2.rindex; - - if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, - kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { - - xdl_free(kvd); - xdl_free_env(xe); - return -1; - } - - xdl_free(kvd); - - return 0; -} - - -static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { - xdchange_t *xch; - - if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) - return NULL; - - xch->next = xscr; - xch->i1 = i1; - xch->i2 = i2; - xch->chg1 = chg1; - xch->chg2 = chg2; - xch->ignore = 0; - - return xch; -} - - -static int recs_match(xrecord_t *rec1, xrecord_t *rec2) -{ - return (rec1->ha == rec2->ha); -} - -/* - * If a line is indented more than this, get_indent() just returns this value. - * This avoids having to do absurd amounts of work for data that are not - * human-readable text, and also ensures that the output of get_indent fits - * within an int. - */ -#define MAX_INDENT 200 - -/* - * Return the amount of indentation of the specified line, treating TAB as 8 - * columns. Return -1 if line is empty or contains only whitespace. Clamp the - * output value at MAX_INDENT. - */ -static int get_indent(xrecord_t *rec) -{ - long i; - int ret = 0; - - for (i = 0; i < rec->size; i++) { - char c = rec->ptr[i]; - - if (!XDL_ISSPACE(c)) - return ret; - else if (c == ' ') - ret += 1; - else if (c == '\t') - ret += 8 - ret % 8; - /* ignore other whitespace characters */ - - if (ret >= MAX_INDENT) - return MAX_INDENT; - } - - /* The line contains only whitespace. */ - return -1; -} - -/* - * If more than this number of consecutive blank rows are found, just return - * this value. This avoids requiring O(N^2) work for pathological cases, and - * also ensures that the output of score_split fits in an int. - */ -#define MAX_BLANKS 20 - -/* Characteristics measured about a hypothetical split position. */ -struct split_measurement { - /* - * Is the split at the end of the file (aside from any blank lines)? - */ - int end_of_file; - - /* - * How much is the line immediately following the split indented (or -1 - * if the line is blank): - */ - int indent; - - /* - * How many consecutive lines above the split are blank? - */ - int pre_blank; - - /* - * How much is the nearest non-blank line above the split indented (or - * -1 if there is no such line)? - */ - int pre_indent; - - /* - * How many lines after the line following the split are blank? - */ - int post_blank; - - /* - * How much is the nearest non-blank line after the line following the - * split indented (or -1 if there is no such line)? - */ - int post_indent; -}; - -struct split_score { - /* The effective indent of this split (smaller is preferred). */ - int effective_indent; - - /* Penalty for this split (smaller is preferred). */ - int penalty; -}; - -/* - * Fill m with information about a hypothetical split of xdf above line split. - */ -static void measure_split(const xdfile_t *xdf, long split, - struct split_measurement *m) -{ - long i; - - if (split >= xdf->nrec) { - m->end_of_file = 1; - m->indent = -1; - } else { - m->end_of_file = 0; - m->indent = get_indent(xdf->recs[split]); - } - - m->pre_blank = 0; - m->pre_indent = -1; - for (i = split - 1; i >= 0; i--) { - m->pre_indent = get_indent(xdf->recs[i]); - if (m->pre_indent != -1) - break; - m->pre_blank += 1; - if (m->pre_blank == MAX_BLANKS) { - m->pre_indent = 0; - break; - } - } - - m->post_blank = 0; - m->post_indent = -1; - for (i = split + 1; i < xdf->nrec; i++) { - m->post_indent = get_indent(xdf->recs[i]); - if (m->post_indent != -1) - break; - m->post_blank += 1; - if (m->post_blank == MAX_BLANKS) { - m->post_indent = 0; - break; - } - } -} - -/* - * The empirically-determined weight factors used by score_split() below. - * Larger values means that the position is a less favorable place to split. - * - * Note that scores are only ever compared against each other, so multiplying - * all of these weight/penalty values by the same factor wouldn't change the - * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*. - * In practice, these numbers are chosen to be large enough that they can be - * adjusted relative to each other with sufficient precision despite using - * integer math. - */ - -/* Penalty if there are no non-blank lines before the split */ -#define START_OF_FILE_PENALTY 1 - -/* Penalty if there are no non-blank lines after the split */ -#define END_OF_FILE_PENALTY 21 - -/* Multiplier for the number of blank lines around the split */ -#define TOTAL_BLANK_WEIGHT (-30) - -/* Multiplier for the number of blank lines after the split */ -#define POST_BLANK_WEIGHT 6 - -/* - * Penalties applied if the line is indented more than its predecessor - */ -#define RELATIVE_INDENT_PENALTY (-4) -#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10 - -/* - * Penalties applied if the line is indented less than both its predecessor and - * its successor - */ -#define RELATIVE_OUTDENT_PENALTY 24 -#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17 - -/* - * Penalties applied if the line is indented less than its predecessor but not - * less than its successor - */ -#define RELATIVE_DEDENT_PENALTY 23 -#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17 - -/* - * We only consider whether the sum of the effective indents for splits are - * less than (-1), equal to (0), or greater than (+1) each other. The resulting - * value is multiplied by the following weight and combined with the penalty to - * determine the better of two scores. - */ -#define INDENT_WEIGHT 60 - -/* - * How far do we slide a hunk at most? - */ -#define INDENT_HEURISTIC_MAX_SLIDING 100 - -/* - * Compute a badness score for the hypothetical split whose measurements are - * stored in m. The weight factors were determined empirically using the tools - * and corpus described in - * - * https://github.com/mhagger/diff-slider-tools - * - * Also see that project if you want to improve the weights based on, for - * example, a larger or more diverse corpus. - */ -static void score_add_split(const struct split_measurement *m, struct split_score *s) -{ - /* - * A place to accumulate penalty factors (positive makes this index more - * favored): - */ - int post_blank, total_blank, indent, any_blanks; - - if (m->pre_indent == -1 && m->pre_blank == 0) - s->penalty += START_OF_FILE_PENALTY; - - if (m->end_of_file) - s->penalty += END_OF_FILE_PENALTY; - - /* - * Set post_blank to the number of blank lines following the split, - * including the line immediately after the split: - */ - post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; - total_blank = m->pre_blank + post_blank; - - /* Penalties based on nearby blank lines: */ - s->penalty += TOTAL_BLANK_WEIGHT * total_blank; - s->penalty += POST_BLANK_WEIGHT * post_blank; - - if (m->indent != -1) - indent = m->indent; - else - indent = m->post_indent; - - any_blanks = (total_blank != 0); - - /* Note that the effective indent is -1 at the end of the file: */ - s->effective_indent += indent; - - if (indent == -1) { - /* No additional adjustments needed. */ - } else if (m->pre_indent == -1) { - /* No additional adjustments needed. */ - } else if (indent > m->pre_indent) { - /* - * The line is indented more than its predecessor. - */ - s->penalty += any_blanks ? - RELATIVE_INDENT_WITH_BLANK_PENALTY : - RELATIVE_INDENT_PENALTY; - } else if (indent == m->pre_indent) { - /* - * The line has the same indentation level as its predecessor. - * No additional adjustments needed. - */ - } else { - /* - * The line is indented less than its predecessor. It could be - * the block terminator of the previous block, but it could - * also be the start of a new block (e.g., an "else" block, or - * maybe the previous block didn't have a block terminator). - * Try to distinguish those cases based on what comes next: - */ - if (m->post_indent != -1 && m->post_indent > indent) { - /* - * The following line is indented more. So it is likely - * that this line is the start of a block. - */ - s->penalty += any_blanks ? - RELATIVE_OUTDENT_WITH_BLANK_PENALTY : - RELATIVE_OUTDENT_PENALTY; - } else { - /* - * That was probably the end of a block. - */ - s->penalty += any_blanks ? - RELATIVE_DEDENT_WITH_BLANK_PENALTY : - RELATIVE_DEDENT_PENALTY; - } - } -} - -static int score_cmp(struct split_score *s1, struct split_score *s2) -{ - /* -1 if s1.effective_indent < s2->effective_indent, etc. */ - int cmp_indents = ((s1->effective_indent > s2->effective_indent) - - (s1->effective_indent < s2->effective_indent)); - - return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty); -} - -/* - * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group - * of lines that was inserted or deleted from the corresponding version of the - * file). We consider there to be such a group at the beginning of the file, at - * the end of the file, and between any two unchanged lines, though most such - * groups will usually be empty. - * - * If the first line in a group is equal to the line following the group, then - * the group can be slid down. Similarly, if the last line in a group is equal - * to the line preceding the group, then the group can be slid up. See - * group_slide_down() and group_slide_up(). - * - * Note that loops that are testing for changed lines in xdf->rchg do not need - * index bounding since the array is prepared with a zero at position -1 and N. - */ -struct xdlgroup { - /* - * The index of the first changed line in the group, or the index of - * the unchanged line above which the (empty) group is located. - */ - long start; - - /* - * The index of the first unchanged line after the group. For an empty - * group, end is equal to start. - */ - long end; -}; - -/* - * Initialize g to point at the first group in xdf. - */ -static void group_init(xdfile_t *xdf, struct xdlgroup *g) -{ - g->start = g->end = 0; - while (xdf->rchg[g->end]) - g->end++; -} - -/* - * Move g to describe the next (possibly empty) group in xdf and return 0. If g - * is already at the end of the file, do nothing and return -1. - */ -static inline int group_next(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->end == xdf->nrec) - return -1; - - g->start = g->end + 1; - for (g->end = g->start; xdf->rchg[g->end]; g->end++) - ; - - return 0; -} - -/* - * Move g to describe the previous (possibly empty) group in xdf and return 0. - * If g is already at the beginning of the file, do nothing and return -1. - */ -static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->start == 0) - return -1; - - g->end = g->start - 1; - for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--) - ; - - return 0; -} - -/* - * If g can be slid toward the end of the file, do so, and if it bumps into a - * following group, expand this group to include it. Return 0 on success or -1 - * if g cannot be slid down. - */ -static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->end < xdf->nrec && - recs_match(xdf->recs[g->start], xdf->recs[g->end])) { - xdf->rchg[g->start++] = 0; - xdf->rchg[g->end++] = 1; - - while (xdf->rchg[g->end]) - g->end++; - - return 0; - } else { - return -1; - } -} - -/* - * If g can be slid toward the beginning of the file, do so, and if it bumps - * into a previous group, expand this group to include it. Return 0 on success - * or -1 if g cannot be slid up. - */ -static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->start > 0 && - recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) { - xdf->rchg[--g->start] = 1; - xdf->rchg[--g->end] = 0; - - while (xdf->rchg[g->start - 1]) - g->start--; - - return 0; - } else { - return -1; - } -} - -/* - * Move back and forward change groups for a consistent and pretty diff output. - * This also helps in finding joinable change groups and reducing the diff - * size. - */ -int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { - struct xdlgroup g, go; - long earliest_end, end_matching_other; - long groupsize; - - group_init(xdf, &g); - group_init(xdfo, &go); - - while (1) { - /* - * If the group is empty in the to-be-compacted file, skip it: - */ - if (g.end == g.start) - goto next; - - /* - * Now shift the change up and then down as far as possible in - * each direction. If it bumps into any other changes, merge - * them. - */ - do { - groupsize = g.end - g.start; - - /* - * Keep track of the last "end" index that causes this - * group to align with a group of changed lines in the - * other file. -1 indicates that we haven't found such - * a match yet: - */ - end_matching_other = -1; - - /* Shift the group backward as much as possible: */ - while (!group_slide_up(xdf, &g)) - if (group_previous(xdfo, &go)) - XDL_BUG("group sync broken sliding up"); - - /* - * This is this highest that this group can be shifted. - * Record its end index: - */ - earliest_end = g.end; - - if (go.end > go.start) - end_matching_other = g.end; - - /* Now shift the group forward as far as possible: */ - while (1) { - if (group_slide_down(xdf, &g)) - break; - if (group_next(xdfo, &go)) - XDL_BUG("group sync broken sliding down"); - - if (go.end > go.start) - end_matching_other = g.end; - } - } while (groupsize != g.end - g.start); - - /* - * If the group can be shifted, then we can possibly use this - * freedom to produce a more intuitive diff. - * - * The group is currently shifted as far down as possible, so - * the heuristics below only have to handle upwards shifts. - */ - - if (g.end == earliest_end) { - /* no shifting was possible */ - } else if (end_matching_other != -1) { - /* - * Move the possibly merged group of changes back to - * line up with the last group of changes from the - * other file that it can align with. - */ - while (go.end == go.start) { - if (group_slide_up(xdf, &g)) - XDL_BUG("match disappeared"); - if (group_previous(xdfo, &go)) - XDL_BUG("group sync broken sliding to match"); - } - } else if (flags & XDF_INDENT_HEURISTIC) { - /* - * Indent heuristic: a group of pure add/delete lines - * implies two splits, one between the end of the - * "before" context and the start of the group, and - * another between the end of the group and the - * beginning of the "after" context. Some splits are - * aesthetically better and some are worse. We compute - * a badness "score" for each split, and add the scores - * for the two splits to define a "score" for each - * position that the group can be shifted to. Then we - * pick the shift with the lowest score. - */ - long shift, best_shift = -1; - struct split_score best_score; - - shift = earliest_end; - if (g.end - groupsize - 1 > shift) - shift = g.end - groupsize - 1; - if (g.end - INDENT_HEURISTIC_MAX_SLIDING > shift) - shift = g.end - INDENT_HEURISTIC_MAX_SLIDING; - for (; shift <= g.end; shift++) { - struct split_measurement m; - struct split_score score = {0, 0}; - - measure_split(xdf, shift, &m); - score_add_split(&m, &score); - measure_split(xdf, shift - groupsize, &m); - score_add_split(&m, &score); - if (best_shift == -1 || - score_cmp(&score, &best_score) <= 0) { - best_score.effective_indent = score.effective_indent; - best_score.penalty = score.penalty; - best_shift = shift; - } - } - - while (g.end > best_shift) { - if (group_slide_up(xdf, &g)) - XDL_BUG("best shift unreached"); - if (group_previous(xdfo, &go)) - XDL_BUG("group sync broken sliding to blank line"); - } - } - - next: - /* Move past the just-processed group: */ - if (group_next(xdf, &g)) - break; - if (group_next(xdfo, &go)) - XDL_BUG("group sync broken moving to next group"); - } - - if (!group_next(xdfo, &go)) - XDL_BUG("group sync broken at end of file"); - - return 0; -} - - -int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { - xdchange_t *cscr = NULL, *xch; - char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; - long i1, i2, l1, l2; - - /* - * Trivial. Collects "groups" of changes and creates an edit script. - */ - for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) - if (rchg1[i1 - 1] || rchg2[i2 - 1]) { - for (l1 = i1; rchg1[i1 - 1]; i1--); - for (l2 = i2; rchg2[i2 - 1]; i2--); - - if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { - xdl_free_script(cscr); - return -1; - } - cscr = xch; - } - - *xscr = cscr; - - return 0; -} - - -void xdl_free_script(xdchange_t *xscr) { - xdchange_t *xch; - - while ((xch = xscr) != NULL) { - xscr = xscr->next; - xdl_free(xch); - } -} - -static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg) -{ - xdchange_t *xch, *xche; - - for (xch = xscr; xch; xch = xche->next) { - xche = xdl_get_hunk(&xch, xecfg); - if (!xch) - break; - if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1, - xch->i2, xche->i2 + xche->chg2 - xch->i2, - ecb->priv) < 0) - return -1; - } - return 0; -} - -static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags) -{ - xdchange_t *xch; - - for (xch = xscr; xch; xch = xch->next) { - int ignore = 1; - xrecord_t **rec; - long i; - - rec = &xe->xdf1.recs[xch->i1]; - for (i = 0; i < xch->chg1 && ignore; i++) - ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); - - rec = &xe->xdf2.recs[xch->i2]; - for (i = 0; i < xch->chg2 && ignore; i++) - ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); - - xch->ignore = ignore; - } -} - -static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) { - xdl_regmatch_t regmatch; - int i; - - for (i = 0; i < xpp->ignore_regex_nr; i++) - if (!xdl_regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1, - ®match, 0)) - return 1; - - return 0; -} - -static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe, - xpparam_t const *xpp) -{ - xdchange_t *xch; - - for (xch = xscr; xch; xch = xch->next) { - xrecord_t **rec; - int ignore = 1; - long i; - - /* - * Do not override --ignore-blank-lines. - */ - if (xch->ignore) - continue; - - rec = &xe->xdf1.recs[xch->i1]; - for (i = 0; i < xch->chg1 && ignore; i++) - ignore = record_matches_regex(rec[i], xpp); - - rec = &xe->xdf2.recs[xch->i2]; - for (i = 0; i < xch->chg2 && ignore; i++) - ignore = record_matches_regex(rec[i], xpp); - - xch->ignore = ignore; - } -} - -int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *ecb) { - xdchange_t *xscr; - xdfenv_t xe; - emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff; - - if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { - - return -1; - } - if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe, &xscr) < 0) { - - xdl_free_env(&xe); - return -1; - } - if (xscr) { - if (xpp->flags & XDF_IGNORE_BLANK_LINES) - xdl_mark_ignorable_lines(xscr, &xe, xpp->flags); - - if (xpp->ignore_regex) - xdl_mark_ignorable_regex(xscr, &xe, xpp); - - if (ef(&xe, xscr, ecb, xecfg) < 0) { - - xdl_free_script(xscr); - xdl_free_env(&xe); - return -1; - } - xdl_free_script(xscr); - } - xdl_free_env(&xe); - - return 0; -} diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h deleted file mode 100644 index 8f1c7c8b0..000000000 --- a/src/xdiff/xdiffi.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XDIFFI_H) -#define XDIFFI_H - - -typedef struct s_diffdata { - long nrec; - unsigned long const *ha; - long *rindex; - char *rchg; -} diffdata_t; - -typedef struct s_xdalgoenv { - long mxcost; - long snake_cnt; - long heur_min; -} xdalgoenv_t; - -typedef struct s_xdchange { - struct s_xdchange *next; - long i1, i2; - long chg1, chg2; - int ignore; -} xdchange_t; - - - -int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, - diffdata_t *dd2, long off2, long lim2, - long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); -int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe); -int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); -int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); -void xdl_free_script(xdchange_t *xscr); -int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg); -int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *env); -int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *env); - -#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c deleted file mode 100644 index 1cbf2b982..000000000 --- a/src/xdiff/xemit.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - -static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { - - *rec = xdf->recs[ri]->ptr; - - return xdf->recs[ri]->size; -} - - -static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { - long size, psize = strlen(pre); - char const *rec; - - size = xdl_get_rec(xdf, ri, &rec); - if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { - - return -1; - } - - return 0; -} - - -/* - * Starting at the passed change atom, find the latest change atom to be included - * inside the differential hunk according to the specified configuration. - * Also advance xscr if the first changes must be discarded. - */ -xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) -{ - xdchange_t *xch, *xchp, *lxch; - long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; - long max_ignorable = xecfg->ctxlen; - unsigned long ignored = 0; /* number of ignored blank lines */ - - /* remove ignorable changes that are too far before other changes */ - for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { - xch = xchp->next; - - if (xch == NULL || - xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable) - *xscr = xch; - } - - if (*xscr == NULL) - return NULL; - - lxch = *xscr; - - for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) { - long distance = xch->i1 - (xchp->i1 + xchp->chg1); - if (distance > max_common) - break; - - if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) { - lxch = xch; - ignored = 0; - } else if (distance < max_ignorable && xch->ignore) { - ignored += xch->chg2; - } else if (lxch != xchp && - xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) { - break; - } else if (!xch->ignore) { - lxch = xch; - ignored = 0; - } else { - ignored += xch->chg2; - } - } - - return lxch; -} - - -static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) -{ - if (len > 0 && - (isalpha((unsigned char)*rec) || /* identifier? */ - *rec == '_' || /* also identifier? */ - *rec == '$')) { /* identifiers from VMS and other esoterico */ - if (len > sz) - len = sz; - while (0 < len && isspace((unsigned char)rec[len - 1])) - len--; - memcpy(buf, rec, len); - return len; - } - return -1; -} - -static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, - char *buf, long sz) -{ - const char *rec; - long len = xdl_get_rec(xdf, ri, &rec); - if (!xecfg->find_func) - return def_ff(rec, len, buf, sz, xecfg->find_func_priv); - return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv); -} - -static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) -{ - char dummy[1]; - return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0; -} - -struct func_line { - long len; - char buf[80]; -}; - -static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, - struct func_line *func_line, long start, long limit) -{ - long l, size, step = (start > limit) ? -1 : 1; - char *buf, dummy[1]; - - buf = func_line ? func_line->buf : dummy; - size = func_line ? sizeof(func_line->buf) : sizeof(dummy); - - for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { - long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size); - if (len >= 0) { - if (func_line) - func_line->len = len; - return l; - } - } - return -1; -} - -static int is_empty_rec(xdfile_t *xdf, long ri) -{ - const char *rec; - long len = xdl_get_rec(xdf, ri, &rec); - - while (len > 0 && XDL_ISSPACE(*rec)) { - rec++; - len--; - } - return !len; -} - -int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg) { - long s1, s2, e1, e2, lctx; - xdchange_t *xch, *xche; - long funclineprev = -1; - struct func_line func_line = { 0 }; - - for (xch = xscr; xch; xch = xche->next) { - xdchange_t *xchp = xch; - xche = xdl_get_hunk(&xch, xecfg); - if (!xch) - break; - -pre_context_calculation: - s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); - s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); - - if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { - long fs1, i1 = xch->i1; - - /* Appended chunk? */ - if (i1 >= xe->xdf1.nrec) { - long i2 = xch->i2; - - /* - * We don't need additional context if - * a whole function was added. - */ - while (i2 < xe->xdf2.nrec) { - if (is_func_rec(&xe->xdf2, xecfg, i2)) - goto post_context_calculation; - i2++; - } - - /* - * Otherwise get more context from the - * pre-image. - */ - i1 = xe->xdf1.nrec - 1; - } - - fs1 = get_func_line(xe, xecfg, NULL, i1, -1); - while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) && - !is_func_rec(&xe->xdf1, xecfg, fs1 - 1)) - fs1--; - if (fs1 < 0) - fs1 = 0; - if (fs1 < s1) { - s2 = XDL_MAX(s2 - (s1 - fs1), 0); - s1 = fs1; - - /* - * Did we extend context upwards into an - * ignored change? - */ - while (xchp != xch && - xchp->i1 + xchp->chg1 <= s1 && - xchp->i2 + xchp->chg2 <= s2) - xchp = xchp->next; - - /* If so, show it after all. */ - if (xchp != xch) { - xch = xchp; - goto pre_context_calculation; - } - } - } - - post_context_calculation: - lctx = xecfg->ctxlen; - lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); - lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); - - e1 = xche->i1 + xche->chg1 + lctx; - e2 = xche->i2 + xche->chg2 + lctx; - - if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { - long fe1 = get_func_line(xe, xecfg, NULL, - xche->i1 + xche->chg1, - xe->xdf1.nrec); - while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1)) - fe1--; - if (fe1 < 0) - fe1 = xe->xdf1.nrec; - if (fe1 > e1) { - e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec); - e1 = fe1; - } - - /* - * Overlap with next change? Then include it - * in the current hunk and start over to find - * its new end. - */ - if (xche->next) { - long l = XDL_MIN(xche->next->i1, - xe->xdf1.nrec - 1); - if (l - xecfg->ctxlen <= e1 || - get_func_line(xe, xecfg, NULL, l, e1) < 0) { - xche = xche->next; - goto post_context_calculation; - } - } - } - - /* - * Emit current hunk header. - */ - - if (xecfg->flags & XDL_EMIT_FUNCNAMES) { - get_func_line(xe, xecfg, &func_line, - s1 - 1, funclineprev); - funclineprev = s1 - 1; - } - if (!(xecfg->flags & XDL_EMIT_NO_HUNK_HDR) && - xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, - func_line.buf, func_line.len, ecb) < 0) - return -1; - - /* - * Emit pre-context. - */ - for (; s2 < xch->i2; s2++) - if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) - return -1; - - for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { - /* - * Merge previous with current change atom. - */ - for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) - if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) - return -1; - - /* - * Removes lines from the first file. - */ - for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) - if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) - return -1; - - /* - * Adds lines from the second file. - */ - for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) - if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) - return -1; - - if (xch == xche) - break; - s1 = xch->i1 + xch->chg1; - s2 = xch->i2 + xch->chg2; - } - - /* - * Emit post-context. - */ - for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) - if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) - return -1; - } - - return 0; -} diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h deleted file mode 100644 index 1b9887e67..000000000 --- a/src/xdiff/xemit.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XEMIT_H) -#define XEMIT_H - - -typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg); - -xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg); -int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg); - - - -#endif /* #if !defined(XEMIT_H) */ diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c deleted file mode 100644 index 80794748b..000000000 --- a/src/xdiff/xhistogram.c +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * and other copyright owners as documented in JGit's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * 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. - * - * - Neither the name of the Eclipse Foundation, Inc. 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 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. - */ - -#include "xinclude.h" - -#define MAX_PTR UINT_MAX -#define MAX_CNT UINT_MAX - -#define LINE_END(n) (line##n + count##n - 1) -#define LINE_END_PTR(n) (*line##n + *count##n - 1) - -struct histindex { - struct record { - unsigned int ptr, cnt; - struct record *next; - } **records, /* an occurrence */ - **line_map; /* map of line to record chain */ - chastore_t rcha; - unsigned int *next_ptrs; - unsigned int table_bits, - records_size, - line_map_size; - - unsigned int max_chain_length, - key_shift, - ptr_shift; - - unsigned int cnt, - has_common; - - xdfenv_t *env; - xpparam_t const *xpp; -}; - -struct region { - unsigned int begin1, end1; - unsigned int begin2, end2; -}; - -#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) - -#define NEXT_PTR(index, ptr) \ - (index->next_ptrs[(ptr) - index->ptr_shift]) - -#define CNT(index, ptr) \ - ((LINE_MAP(index, ptr))->cnt) - -#define REC(env, s, l) \ - (env->xdf##s.recs[l - 1]) - -static int cmp_recs(xrecord_t *r1, xrecord_t *r2) -{ - return r1->ha == r2->ha; - -} - -#define CMP(i, s1, l1, s2, l2) \ - (cmp_recs(REC(i->env, s1, l1), REC(i->env, s2, l2))) - -#define TABLE_HASH(index, side, line) \ - XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) - -static int scanA(struct histindex *index, int line1, int count1) -{ - unsigned int ptr, tbl_idx; - unsigned int chain_len; - struct record **rec_chain, *rec; - - for (ptr = LINE_END(1); line1 <= ptr; ptr--) { - tbl_idx = TABLE_HASH(index, 1, ptr); - rec_chain = index->records + tbl_idx; - rec = *rec_chain; - - chain_len = 0; - while (rec) { - if (CMP(index, 1, rec->ptr, 1, ptr)) { - /* - * ptr is identical to another element. Insert - * it onto the front of the existing element - * chain. - */ - NEXT_PTR(index, ptr) = rec->ptr; - rec->ptr = ptr; - /* cap rec->cnt at MAX_CNT */ - rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); - LINE_MAP(index, ptr) = rec; - goto continue_scan; - } - - rec = rec->next; - chain_len++; - } - - if (chain_len == index->max_chain_length) - return -1; - - /* - * This is the first time we have ever seen this particular - * element in the sequence. Construct a new chain for it. - */ - if (!(rec = xdl_cha_alloc(&index->rcha))) - return -1; - rec->ptr = ptr; - rec->cnt = 1; - rec->next = *rec_chain; - *rec_chain = rec; - LINE_MAP(index, ptr) = rec; - -continue_scan: - ; /* no op */ - } - - return 0; -} - -static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr, - int line1, int count1, int line2, int count2) -{ - unsigned int b_next = b_ptr + 1; - struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; - unsigned int as, ae, bs, be, np, rc; - int should_break; - - for (; rec; rec = rec->next) { - if (rec->cnt > index->cnt) { - if (!index->has_common) - index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); - continue; - } - - as = rec->ptr; - if (!CMP(index, 1, as, 2, b_ptr)) - continue; - - index->has_common = 1; - for (;;) { - should_break = 0; - np = NEXT_PTR(index, as); - bs = b_ptr; - ae = as; - be = bs; - rc = rec->cnt; - - while (line1 < as && line2 < bs - && CMP(index, 1, as - 1, 2, bs - 1)) { - as--; - bs--; - if (1 < rc) - rc = XDL_MIN(rc, CNT(index, as)); - } - while (ae < LINE_END(1) && be < LINE_END(2) - && CMP(index, 1, ae + 1, 2, be + 1)) { - ae++; - be++; - if (1 < rc) - rc = XDL_MIN(rc, CNT(index, ae)); - } - - if (b_next <= be) - b_next = be + 1; - if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { - lcs->begin1 = as; - lcs->begin2 = bs; - lcs->end1 = ae; - lcs->end2 = be; - index->cnt = rc; - } - - if (np == 0) - break; - - while (np <= ae) { - np = NEXT_PTR(index, np); - if (np == 0) { - should_break = 1; - break; - } - } - - if (should_break) - break; - - as = np; - } - } - return b_next; -} - -static int fall_back_to_classic_diff(xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2) -{ - xpparam_t xpparam; - - memset(&xpparam, 0, sizeof(xpparam)); - xpparam.flags = xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; - - return xdl_fall_back_diff(env, &xpparam, - line1, count1, line2, count2); -} - -static inline void free_index(struct histindex *index) -{ - xdl_free(index->records); - xdl_free(index->line_map); - xdl_free(index->next_ptrs); - xdl_cha_free(&index->rcha); -} - -static int find_lcs(xpparam_t const *xpp, xdfenv_t *env, - struct region *lcs, - int line1, int count1, int line2, int count2) -{ - int b_ptr; - int sz, ret = -1; - struct histindex index; - - memset(&index, 0, sizeof(index)); - - index.env = env; - index.xpp = xpp; - - index.records = NULL; - index.line_map = NULL; - /* in case of early xdl_cha_free() */ - index.rcha.head = NULL; - - index.table_bits = xdl_hashbits(count1); - sz = index.records_size = 1 << index.table_bits; - sz *= sizeof(struct record *); - if (!(index.records = (struct record **) xdl_malloc(sz))) - goto cleanup; - memset(index.records, 0, sz); - - sz = index.line_map_size = count1; - sz *= sizeof(struct record *); - if (!(index.line_map = (struct record **) xdl_malloc(sz))) - goto cleanup; - memset(index.line_map, 0, sz); - - sz = index.line_map_size; - sz *= sizeof(unsigned int); - if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) - goto cleanup; - memset(index.next_ptrs, 0, sz); - - /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ - if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) - goto cleanup; - - index.ptr_shift = line1; - index.max_chain_length = 64; - - if (scanA(&index, line1, count1)) - goto cleanup; - - index.cnt = index.max_chain_length + 1; - - for (b_ptr = line2; b_ptr <= LINE_END(2); ) - b_ptr = try_lcs(&index, lcs, b_ptr, line1, count1, line2, count2); - - if (index.has_common && index.max_chain_length < index.cnt) - ret = 1; - else - ret = 0; - -cleanup: - free_index(&index); - return ret; -} - -static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2) -{ - struct region lcs; - int lcs_found; - int result; -redo: - result = -1; - - if (count1 <= 0 && count2 <= 0) - return 0; - - if (LINE_END(1) >= MAX_PTR) - return -1; - - if (!count1) { - while(count2--) - env->xdf2.rchg[line2++ - 1] = 1; - return 0; - } else if (!count2) { - while(count1--) - env->xdf1.rchg[line1++ - 1] = 1; - return 0; - } - - memset(&lcs, 0, sizeof(lcs)); - lcs_found = find_lcs(xpp, env, &lcs, line1, count1, line2, count2); - if (lcs_found < 0) - goto out; - else if (lcs_found) - result = fall_back_to_classic_diff(xpp, env, line1, count1, line2, count2); - else { - if (lcs.begin1 == 0 && lcs.begin2 == 0) { - while (count1--) - env->xdf1.rchg[line1++ - 1] = 1; - while (count2--) - env->xdf2.rchg[line2++ - 1] = 1; - result = 0; - } else { - result = histogram_diff(xpp, env, - line1, lcs.begin1 - line1, - line2, lcs.begin2 - line2); - if (result) - goto out; - /* - * result = histogram_diff(xpp, env, - * lcs.end1 + 1, LINE_END(1) - lcs.end1, - * lcs.end2 + 1, LINE_END(2) - lcs.end2); - * but let's optimize tail recursion ourself: - */ - count1 = LINE_END(1) - lcs.end1; - line1 = lcs.end1 + 1; - count2 = LINE_END(2) - lcs.end2; - line2 = lcs.end2 + 1; - goto redo; - } - } -out: - return result; -} - -int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env) -{ - if (xdl_prepare_env(file1, file2, xpp, env) < 0) - return -1; - - return histogram_diff(xpp, env, - env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, - env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); -} diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h deleted file mode 100644 index 75db1d8f3..000000000 --- a/src/xdiff/xinclude.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XINCLUDE_H) -#define XINCLUDE_H - -#include "git-xdiff.h" -#include "xmacros.h" -#include "xdiff.h" -#include "xtypes.h" -#include "xutils.h" -#include "xprepare.h" -#include "xdiffi.h" -#include "xemit.h" - - -#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h deleted file mode 100644 index 2809a28ca..000000000 --- a/src/xdiff/xmacros.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XMACROS_H) -#define XMACROS_H - - - - -#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) -#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) -#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) -#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') -#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) -#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) -#define XDL_MASKBITS(b) ((1UL << (b)) - 1) -#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) -#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) -#define XDL_LE32_PUT(p, v) \ -do { \ - unsigned char *__p = (unsigned char *) (p); \ - *__p++ = (unsigned char) (v); \ - *__p++ = (unsigned char) ((v) >> 8); \ - *__p++ = (unsigned char) ((v) >> 16); \ - *__p = (unsigned char) ((v) >> 24); \ -} while (0) -#define XDL_LE32_GET(p, v) \ -do { \ - unsigned char const *__p = (unsigned char const *) (p); \ - (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ - ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ -} while (0) - - -#endif /* #if !defined(XMACROS_H) */ diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c deleted file mode 100644 index 433e2d741..000000000 --- a/src/xdiff/xmerge.c +++ /dev/null @@ -1,737 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - -typedef struct s_xdmerge { - struct s_xdmerge *next; - /* - * 0 = conflict, - * 1 = no conflict, take first, - * 2 = no conflict, take second. - * 3 = no conflict, take both. - */ - int mode; - /* - * These point at the respective postimages. E.g. is - * how side #1 wants to change the common ancestor; if there is no - * overlap, lines before i1 in the postimage of side #1 appear - * in the merge result as a region touched by neither side. - */ - long i1, i2; - long chg1, chg2; - /* - * These point at the preimage; of course there is just one - * preimage, that is from the shared common ancestor. - */ - long i0; - long chg0; -} xdmerge_t; - -static int xdl_append_merge(xdmerge_t **merge, int mode, - long i0, long chg0, - long i1, long chg1, - long i2, long chg2) -{ - xdmerge_t *m = *merge; - if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { - if (mode != m->mode) - m->mode = 0; - m->chg0 = i0 + chg0 - m->i0; - m->chg1 = i1 + chg1 - m->i1; - m->chg2 = i2 + chg2 - m->i2; - } else { - m = xdl_malloc(sizeof(xdmerge_t)); - if (!m) - return -1; - m->next = NULL; - m->mode = mode; - m->i0 = i0; - m->chg0 = chg0; - m->i1 = i1; - m->chg1 = chg1; - m->i2 = i2; - m->chg2 = chg2; - if (*merge) - (*merge)->next = m; - *merge = m; - } - return 0; -} - -static int xdl_cleanup_merge(xdmerge_t *c) -{ - int count = 0; - xdmerge_t *next_c; - - /* were there conflicts? */ - for (; c; c = next_c) { - if (c->mode == 0) - count++; - next_c = c->next; - xdl_free(c); - } - return count; -} - -static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, - int line_count, long flags) -{ - int i; - xrecord_t **rec1 = xe1->xdf2.recs + i1; - xrecord_t **rec2 = xe2->xdf2.recs + i2; - - for (i = 0; i < line_count; i++) { - int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, - rec2[i]->ptr, rec2[i]->size, flags); - if (!result) - return -1; - } - return 0; -} - -static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) -{ - xrecord_t **recs; - int size = 0; - - recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; - - if (count < 1) - return 0; - - for (i = 0; i < count; size += recs[i++]->size) - if (dest) - memcpy(dest + size, recs[i]->ptr, recs[i]->size); - if (add_nl) { - i = recs[count - 1]->size; - if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { - if (needs_cr) { - if (dest) - dest[size] = '\r'; - size++; - } - - if (dest) - dest[size] = '\n'; - size++; - } - } - return size; -} - -static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) -{ - return xdl_recs_copy_0(0, xe, i, count, needs_cr, add_nl, dest); -} - -static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) -{ - return xdl_recs_copy_0(1, xe, i, count, needs_cr, add_nl, dest); -} - -/* - * Returns 1 if the i'th line ends in CR/LF (if it is the last line and - * has no eol, the preceding line, if any), 0 if it ends in LF-only, and - * -1 if the line ending cannot be determined. - */ -static int is_eol_crlf(xdfile_t *file, int i) -{ - long size; - - if (i < file->nrec - 1) - /* All lines before the last *must* end in LF */ - return (size = file->recs[i]->size) > 1 && - file->recs[i]->ptr[size - 2] == '\r'; - if (!file->nrec) - /* Cannot determine eol style from empty file */ - return -1; - if ((size = file->recs[i]->size) && - file->recs[i]->ptr[size - 1] == '\n') - /* Last line; ends in LF; Is it CR/LF? */ - return size > 1 && - file->recs[i]->ptr[size - 2] == '\r'; - if (!i) - /* The only line has no eol */ - return -1; - /* Determine eol from second-to-last line */ - return (size = file->recs[i - 1]->size) > 1 && - file->recs[i - 1]->ptr[size - 2] == '\r'; -} - -static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m) -{ - int needs_cr; - - /* Match post-images' preceding, or first, lines' end-of-line style */ - needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0); - if (needs_cr) - needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0); - /* Look at pre-image's first line, unless we already settled on LF */ - if (needs_cr) - needs_cr = is_eol_crlf(&xe1->xdf1, 0); - /* If still undecided, use LF-only */ - return needs_cr < 0 ? 0 : needs_cr; -} - -static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, - const char *name3, - int size, int i, int style, - xdmerge_t *m, char *dest, int marker_size) -{ - int marker1_size = (name1 ? strlen(name1) + 1 : 0); - int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int marker3_size = (name3 ? strlen(name3) + 1 : 0); - int needs_cr = is_cr_needed(xe1, xe2, m); - - if (marker_size <= 0) - marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - - /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, - dest ? dest + size : NULL); - - if (!dest) { - size += marker_size + 1 + needs_cr + marker1_size; - } else { - memset(dest + size, '<', marker_size); - size += marker_size; - if (marker1_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name1, marker1_size - 1); - size += marker1_size; - } - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - - /* Postimage from side #1 */ - size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, 1, - dest ? dest + size : NULL); - - if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { - /* Shared preimage */ - if (!dest) { - size += marker_size + 1 + needs_cr + marker3_size; - } else { - memset(dest + size, '|', marker_size); - size += marker_size; - if (marker3_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name3, marker3_size - 1); - size += marker3_size; - } - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - size += xdl_orig_copy(xe1, m->i0, m->chg0, needs_cr, 1, - dest ? dest + size : NULL); - } - - if (!dest) { - size += marker_size + 1 + needs_cr; - } else { - memset(dest + size, '=', marker_size); - size += marker_size; - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - - /* Postimage from side #2 */ - size += xdl_recs_copy(xe2, m->i2, m->chg2, needs_cr, 1, - dest ? dest + size : NULL); - if (!dest) { - size += marker_size + 1 + needs_cr + marker2_size; - } else { - memset(dest + size, '>', marker_size); - size += marker_size; - if (marker2_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name2, marker2_size - 1); - size += marker2_size; - } - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - return size; -} - -static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, - const char *ancestor_name, - int favor, - xdmerge_t *m, char *dest, int style, - int marker_size) -{ - int size, i; - - for (size = i = 0; m; m = m->next) { - if (favor && !m->mode) - m->mode = favor; - - if (m->mode == 0) - size = fill_conflict_hunk(xe1, name1, xe2, name2, - ancestor_name, - size, i, style, m, dest, - marker_size); - else if (m->mode & 3) { - /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, - dest ? dest + size : NULL); - /* Postimage from side #1 */ - if (m->mode & 1) { - int needs_cr = is_cr_needed(xe1, xe2, m); - - size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, (m->mode & 2), - dest ? dest + size : NULL); - } - /* Postimage from side #2 */ - if (m->mode & 2) - size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, 0, - dest ? dest + size : NULL); - } else - continue; - i = m->i1 + m->chg1; - } - size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0, - dest ? dest + size : NULL); - return size; -} - -static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags) -{ - return xdl_recmatch(rec1->ptr, rec1->size, - rec2->ptr, rec2->size, flags); -} - -/* - * Remove any common lines from the beginning and end of the conflicted region. - */ -static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, - xpparam_t const *xpp) -{ - xrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs; - for (; m; m = m->next) { - /* let's handle just the conflicts */ - if (m->mode) - continue; - - while(m->chg1 && m->chg2 && - recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) { - m->chg1--; - m->chg2--; - m->i1++; - m->i2++; - } - while (m->chg1 && m->chg2 && - recmatch(rec1[m->i1 + m->chg1 - 1], - rec2[m->i2 + m->chg2 - 1], xpp->flags)) { - m->chg1--; - m->chg2--; - } - } -} - -/* - * Sometimes, changes are not quite identical, but differ in only a few - * lines. Try hard to show only these few lines as conflicting. - */ -static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, - xpparam_t const *xpp) -{ - for (; m; m = m->next) { - mmfile_t t1, t2; - xdfenv_t xe; - xdchange_t *xscr, *x; - int i1 = m->i1, i2 = m->i2; - - /* let's handle just the conflicts */ - if (m->mode) - continue; - - /* no sense refining a conflict when one side is empty */ - if (m->chg1 == 0 || m->chg2 == 0) - continue; - - /* - * This probably does not work outside git, since - * we have a very simple mmfile structure. - */ - t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; - t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr - + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; - t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; - t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr - + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; - if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) - return -1; - if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe, &xscr) < 0) { - xdl_free_env(&xe); - return -1; - } - if (!xscr) { - /* If this happens, the changes are identical. */ - xdl_free_env(&xe); - m->mode = 4; - continue; - } - x = xscr; - m->i1 = xscr->i1 + i1; - m->chg1 = xscr->chg1; - m->i2 = xscr->i2 + i2; - m->chg2 = xscr->chg2; - while (xscr->next) { - xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); - if (!m2) { - xdl_free_env(&xe); - xdl_free_script(x); - return -1; - } - xscr = xscr->next; - m2->next = m->next; - m->next = m2; - m = m2; - m->mode = 0; - m->i1 = xscr->i1 + i1; - m->chg1 = xscr->chg1; - m->i2 = xscr->i2 + i2; - m->chg2 = xscr->chg2; - } - xdl_free_env(&xe); - xdl_free_script(x); - } - return 0; -} - -static int line_contains_alnum(const char *ptr, long size) -{ - while (size--) - if (isalnum((unsigned char)*(ptr++))) - return 1; - return 0; -} - -static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) -{ - for (; chg; chg--, i++) - if (line_contains_alnum(xe->xdf2.recs[i]->ptr, - xe->xdf2.recs[i]->size)) - return 1; - return 0; -} - -/* - * This function merges m and m->next, marking everything between those hunks - * as conflicting, too. - */ -static void xdl_merge_two_conflicts(xdmerge_t *m) -{ - xdmerge_t *next_m = m->next; - m->chg1 = next_m->i1 + next_m->chg1 - m->i1; - m->chg2 = next_m->i2 + next_m->chg2 - m->i2; - m->next = next_m->next; - xdl_free(next_m); -} - -/* - * If there are less than 3 non-conflicting lines between conflicts, - * it appears simpler -- because it takes up less (or as many) lines -- - * if the lines are moved into the conflicts. - */ -static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, - int simplify_if_no_alnum) -{ - int result = 0; - - if (!m) - return result; - for (;;) { - xdmerge_t *next_m = m->next; - int begin, end; - - if (!next_m) - return result; - - begin = m->i1 + m->chg1; - end = next_m->i1; - - if (m->mode != 0 || next_m->mode != 0 || - (end - begin > 3 && - (!simplify_if_no_alnum || - lines_contain_alnum(xe1, begin, end - begin)))) { - m = next_m; - } else { - result++; - xdl_merge_two_conflicts(m); - } - } -} - -/* - * level == 0: mark all overlapping changes as conflict - * level == 1: mark overlapping changes as conflict only if not identical - * level == 2: analyze non-identical changes for minimal conflict set - * level == 3: analyze non-identical changes for minimal conflict set, but - * treat hunks not containing any letter or number as conflicting - * - * returns < 0 on error, == 0 for no conflicts, else number of conflicts - */ -static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, - xdfenv_t *xe2, xdchange_t *xscr2, - xmparam_t const *xmp, mmbuffer_t *result) -{ - xdmerge_t *changes, *c; - xpparam_t const *xpp = &xmp->xpp; - const char *const ancestor_name = xmp->ancestor; - const char *const name1 = xmp->file1; - const char *const name2 = xmp->file2; - int i0, i1, i2, chg0, chg1, chg2; - int level = xmp->level; - int style = xmp->style; - int favor = xmp->favor; - - /* - * XDL_MERGE_DIFF3 does not attempt to refine conflicts by looking - * at common areas of sides 1 & 2, because the base (side 0) does - * not match and is being shown. Similarly, simplification of - * non-conflicts is also skipped due to the skipping of conflict - * refinement. - * - * XDL_MERGE_ZEALOUS_DIFF3, on the other hand, will attempt to - * refine conflicts looking for common areas of sides 1 & 2. - * However, since the base is being shown and does not match, - * it will only look for common areas at the beginning or end - * of the conflict block. Since XDL_MERGE_ZEALOUS_DIFF3's - * conflict refinement is much more limited in this fashion, the - * conflict simplification will be skipped. - */ - if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { - /* - * "diff3 -m" output does not make sense for anything - * more aggressive than XDL_MERGE_EAGER. - */ - if (XDL_MERGE_EAGER < level) - level = XDL_MERGE_EAGER; - } - - c = changes = NULL; - - while (xscr1 && xscr2) { - if (!changes) - changes = c; - if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { - i0 = xscr1->i1; - i1 = xscr1->i2; - i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; - chg0 = xscr1->chg1; - chg1 = xscr1->chg2; - chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr1 = xscr1->next; - continue; - } - if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { - i0 = xscr2->i1; - i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; - i2 = xscr2->i2; - chg0 = xscr2->chg1; - chg1 = xscr2->chg1; - chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr2 = xscr2->next; - continue; - } - if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || - xscr1->chg1 != xscr2->chg1 || - xscr1->chg2 != xscr2->chg2 || - xdl_merge_cmp_lines(xe1, xscr1->i2, - xe2, xscr2->i2, - xscr1->chg2, xpp->flags)) { - /* conflict */ - int off = xscr1->i1 - xscr2->i1; - int ffo = off + xscr1->chg1 - xscr2->chg1; - - i0 = xscr1->i1; - i1 = xscr1->i2; - i2 = xscr2->i2; - if (off > 0) { - i0 -= off; - i1 -= off; - } - else - i2 += off; - chg0 = xscr1->i1 + xscr1->chg1 - i0; - chg1 = xscr1->i2 + xscr1->chg2 - i1; - chg2 = xscr2->i2 + xscr2->chg2 - i2; - if (ffo < 0) { - chg0 -= ffo; - chg1 -= ffo; - } else - chg2 += ffo; - if (xdl_append_merge(&c, 0, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - } - - i1 = xscr1->i1 + xscr1->chg1; - i2 = xscr2->i1 + xscr2->chg1; - - if (i1 >= i2) - xscr2 = xscr2->next; - if (i2 >= i1) - xscr1 = xscr1->next; - } - while (xscr1) { - if (!changes) - changes = c; - i0 = xscr1->i1; - i1 = xscr1->i2; - i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; - chg0 = xscr1->chg1; - chg1 = xscr1->chg2; - chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr1 = xscr1->next; - } - while (xscr2) { - if (!changes) - changes = c; - i0 = xscr2->i1; - i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; - i2 = xscr2->i2; - chg0 = xscr2->chg1; - chg1 = xscr2->chg1; - chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr2 = xscr2->next; - } - if (!changes) - changes = c; - /* refine conflicts */ - if (style == XDL_MERGE_ZEALOUS_DIFF3) { - xdl_refine_zdiff3_conflicts(xe1, xe2, changes, xpp); - } else if (XDL_MERGE_ZEALOUS <= level && - (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || - xdl_simplify_non_conflicts(xe1, changes, - XDL_MERGE_ZEALOUS < level) < 0)) { - xdl_cleanup_merge(changes); - return -1; - } - /* output */ - if (result) { - int marker_size = xmp->marker_size; - int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, - ancestor_name, - favor, changes, NULL, style, - marker_size); - result->ptr = xdl_malloc(size); - if (!result->ptr) { - xdl_cleanup_merge(changes); - return -1; - } - result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, - ancestor_name, favor, changes, - result->ptr, style, marker_size); - } - return xdl_cleanup_merge(changes); -} - -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, - xmparam_t const *xmp, mmbuffer_t *result) -{ - xdchange_t *xscr1, *xscr2; - xdfenv_t xe1, xe2; - int status; - xpparam_t const *xpp = &xmp->xpp; - - result->ptr = NULL; - result->size = 0; - - if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) { - return -1; - } - if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { - xdl_free_env(&xe1); - return -1; - } - if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe1, &xscr1) < 0) { - xdl_free_env(&xe1); - return -1; - } - if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe2, &xscr2) < 0) { - xdl_free_script(xscr1); - xdl_free_env(&xe1); - xdl_free_env(&xe2); - return -1; - } - status = 0; - if (!xscr1) { - result->ptr = xdl_malloc(mf2->size); - memcpy(result->ptr, mf2->ptr, mf2->size); - result->size = mf2->size; - } else if (!xscr2) { - result->ptr = xdl_malloc(mf1->size); - memcpy(result->ptr, mf1->ptr, mf1->size); - result->size = mf1->size; - } else { - status = xdl_do_merge(&xe1, xscr1, - &xe2, xscr2, - xmp, result); - } - xdl_free_script(xscr1); - xdl_free_script(xscr2); - - xdl_free_env(&xe1); - xdl_free_env(&xe2); - - return status; -} diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c deleted file mode 100644 index c5d48e80a..000000000 --- a/src/xdiff/xpatience.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ -#include "xinclude.h" - -/* - * The basic idea of patience diff is to find lines that are unique in - * both files. These are intuitively the ones that we want to see as - * common lines. - * - * The maximal ordered sequence of such line pairs (where ordered means - * that the order in the sequence agrees with the order of the lines in - * both files) naturally defines an initial set of common lines. - * - * Now, the algorithm tries to extend the set of common lines by growing - * the line ranges where the files have identical lines. - * - * Between those common lines, the patience diff algorithm is applied - * recursively, until no unique line pairs can be found; these line ranges - * are handled by the well-known Myers algorithm. - */ - -#define NON_UNIQUE ULONG_MAX - -/* - * This is a hash mapping from line hash to line numbers in the first and - * second file. - */ -struct hashmap { - int nr, alloc; - struct entry { - unsigned long hash; - /* - * 0 = unused entry, 1 = first line, 2 = second, etc. - * line2 is NON_UNIQUE if the line is not unique - * in either the first or the second file. - */ - unsigned long line1, line2; - /* - * "next" & "previous" are used for the longest common - * sequence; - * initially, "next" reflects only the order in file1. - */ - struct entry *next, *previous; - - /* - * If 1, this entry can serve as an anchor. See - * Documentation/diff-options.txt for more information. - */ - unsigned anchor : 1; - } *entries, *first, *last; - /* were common records found? */ - unsigned long has_matches; - mmfile_t *file1, *file2; - xdfenv_t *env; - xpparam_t const *xpp; -}; - -static int is_anchor(xpparam_t const *xpp, const char *line) -{ - int i; - for (i = 0; i < xpp->anchors_nr; i++) { - if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i]))) - return 1; - } - return 0; -} - -/* The argument "pass" is 1 for the first file, 2 for the second. */ -static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, - int pass) -{ - xrecord_t **records = pass == 1 ? - map->env->xdf1.recs : map->env->xdf2.recs; - xrecord_t *record = records[line - 1]; - /* - * After xdl_prepare_env() (or more precisely, due to - * xdl_classify_record()), the "ha" member of the records (AKA lines) - * is _not_ the hash anymore, but a linearized version of it. In - * other words, the "ha" member is guaranteed to start with 0 and - * the second record's ha can only be 0 or 1, etc. - * - * So we multiply ha by 2 in the hope that the hashing was - * "unique enough". - */ - int index = (int)((record->ha << 1) % map->alloc); - - while (map->entries[index].line1) { - if (map->entries[index].hash != record->ha) { - if (++index >= map->alloc) - index = 0; - continue; - } - if (pass == 2) - map->has_matches = 1; - if (pass == 1 || map->entries[index].line2) - map->entries[index].line2 = NON_UNIQUE; - else - map->entries[index].line2 = line; - return; - } - if (pass == 2) - return; - map->entries[index].line1 = line; - map->entries[index].hash = record->ha; - map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr); - if (!map->first) - map->first = map->entries + index; - if (map->last) { - map->last->next = map->entries + index; - map->entries[index].previous = map->last; - } - map->last = map->entries + index; - map->nr++; -} - -/* - * This function has to be called for each recursion into the inter-hunk - * parts, as previously non-unique lines can become unique when being - * restricted to a smaller part of the files. - * - * It is assumed that env has been prepared using xdl_prepare(). - */ -static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env, - struct hashmap *result, - int line1, int count1, int line2, int count2) -{ - result->file1 = file1; - result->file2 = file2; - result->xpp = xpp; - result->env = env; - - /* We know exactly how large we want the hash map */ - result->alloc = count1 * 2; - result->entries = (struct entry *) - xdl_malloc(result->alloc * sizeof(struct entry)); - if (!result->entries) - return -1; - memset(result->entries, 0, result->alloc * sizeof(struct entry)); - - /* First, fill with entries from the first file */ - while (count1--) - insert_record(xpp, line1++, result, 1); - - /* Then search for matches in the second file */ - while (count2--) - insert_record(xpp, line2++, result, 2); - - return 0; -} - -/* - * Find the longest sequence with a smaller last element (meaning a smaller - * line2, as we construct the sequence with entries ordered by line1). - */ -static int binary_search(struct entry **sequence, int longest, - struct entry *entry) -{ - int left = -1, right = longest; - - while (left + 1 < right) { - int middle = left + (right - left) / 2; - /* by construction, no two entries can be equal */ - if (sequence[middle]->line2 > entry->line2) - right = middle; - else - left = middle; - } - /* return the index in "sequence", _not_ the sequence length */ - return left; -} - -/* - * The idea is to start with the list of common unique lines sorted by - * the order in file1. For each of these pairs, the longest (partial) - * sequence whose last element's line2 is smaller is determined. - * - * For efficiency, the sequences are kept in a list containing exactly one - * item per sequence length: the sequence with the smallest last - * element (in terms of line2). - */ -static struct entry *find_longest_common_sequence(struct hashmap *map) -{ - struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); - int longest = 0, i; - struct entry *entry; - - /* - * If not -1, this entry in sequence must never be overridden. - * Therefore, overriding entries before this has no effect, so - * do not do that either. - */ - int anchor_i = -1; - - for (entry = map->first; entry; entry = entry->next) { - if (!entry->line2 || entry->line2 == NON_UNIQUE) - continue; - i = binary_search(sequence, longest, entry); - entry->previous = i < 0 ? NULL : sequence[i]; - ++i; - if (i <= anchor_i) - continue; - sequence[i] = entry; - if (entry->anchor) { - anchor_i = i; - longest = anchor_i + 1; - } else if (i == longest) { - longest++; - } - } - - /* No common unique lines were found */ - if (!longest) { - xdl_free(sequence); - return NULL; - } - - /* Iterate starting at the last element, adjusting the "next" members */ - entry = sequence[longest - 1]; - entry->next = NULL; - while (entry->previous) { - entry->previous->next = entry; - entry = entry->previous; - } - xdl_free(sequence); - return entry; -} - -static int match(struct hashmap *map, int line1, int line2) -{ - xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; - xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; - return record1->ha == record2->ha; -} - -static int patience_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2); - -static int walk_common_sequence(struct hashmap *map, struct entry *first, - int line1, int count1, int line2, int count2) -{ - int end1 = line1 + count1, end2 = line2 + count2; - int next1, next2; - - for (;;) { - /* Try to grow the line ranges of common lines */ - if (first) { - next1 = first->line1; - next2 = first->line2; - while (next1 > line1 && next2 > line2 && - match(map, next1 - 1, next2 - 1)) { - next1--; - next2--; - } - } else { - next1 = end1; - next2 = end2; - } - while (line1 < next1 && line2 < next2 && - match(map, line1, line2)) { - line1++; - line2++; - } - - /* Recurse */ - if (next1 > line1 || next2 > line2) { - if (patience_diff(map->file1, map->file2, - map->xpp, map->env, - line1, next1 - line1, - line2, next2 - line2)) - return -1; - } - - if (!first) - return 0; - - while (first->next && - first->next->line1 == first->line1 + 1 && - first->next->line2 == first->line2 + 1) - first = first->next; - - line1 = first->line1 + 1; - line2 = first->line2 + 1; - - first = first->next; - } -} - -static int fall_back_to_classic_diff(struct hashmap *map, - int line1, int count1, int line2, int count2) -{ - xpparam_t xpp; - - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; - - return xdl_fall_back_diff(map->env, &xpp, - line1, count1, line2, count2); -} - -/* - * Recursively find the longest common sequence of unique lines, - * and if none was found, ask xdl_do_diff() to do the job. - * - * This function assumes that env was prepared with xdl_prepare_env(). - */ -static int patience_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2) -{ - struct hashmap map; - struct entry *first; - int result = 0; - - /* trivial case: one side is empty */ - if (!count1) { - while(count2--) - env->xdf2.rchg[line2++ - 1] = 1; - return 0; - } else if (!count2) { - while(count1--) - env->xdf1.rchg[line1++ - 1] = 1; - return 0; - } - - memset(&map, 0, sizeof(map)); - if (fill_hashmap(file1, file2, xpp, env, &map, - line1, count1, line2, count2)) - return -1; - - /* are there any matching lines at all? */ - if (!map.has_matches) { - while(count1--) - env->xdf1.rchg[line1++ - 1] = 1; - while(count2--) - env->xdf2.rchg[line2++ - 1] = 1; - xdl_free(map.entries); - return 0; - } - - first = find_longest_common_sequence(&map); - if (first) - result = walk_common_sequence(&map, first, - line1, count1, line2, count2); - else - result = fall_back_to_classic_diff(&map, - line1, count1, line2, count2); - - xdl_free(map.entries); - return result; -} - -int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env) -{ - if (xdl_prepare_env(file1, file2, xpp, env) < 0) - return -1; - - /* environment is cleaned up in xdl_diff() */ - return patience_diff(file1, file2, xpp, env, - 1, env->xdf1.nrec, 1, env->xdf2.nrec); -} diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c deleted file mode 100644 index 4527a4a07..000000000 --- a/src/xdiff/xprepare.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - - -#define XDL_KPDIS_RUN 4 -#define XDL_MAX_EQLIMIT 1024 -#define XDL_SIMSCAN_WINDOW 100 -#define XDL_GUESS_NLINES1 256 -#define XDL_GUESS_NLINES2 20 - - -typedef struct s_xdlclass { - struct s_xdlclass *next; - unsigned long ha; - char const *line; - long size; - long idx; - long len1, len2; -} xdlclass_t; - -typedef struct s_xdlclassifier { - unsigned int hbits; - long hsize; - xdlclass_t **rchash; - chastore_t ncha; - xdlclass_t **rcrecs; - long alloc; - long count; - long flags; -} xdlclassifier_t; - - - - -static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); -static void xdl_free_classifier(xdlclassifier_t *cf); -static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, - unsigned int hbits, xrecord_t *rec); -static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, - xdlclassifier_t *cf, xdfile_t *xdf); -static void xdl_free_ctx(xdfile_t *xdf); -static int xdl_clean_mmatch(char const *dis, long i, long s, long e); -static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); -static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); -static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); - - - - -static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { - cf->flags = flags; - - cf->hbits = xdl_hashbits((unsigned int) size); - cf->hsize = 1 << cf->hbits; - - if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { - - return -1; - } - if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { - - xdl_cha_free(&cf->ncha); - return -1; - } - memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); - - cf->alloc = size; - if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { - - xdl_free(cf->rchash); - xdl_cha_free(&cf->ncha); - return -1; - } - - cf->count = 0; - - return 0; -} - - -static void xdl_free_classifier(xdlclassifier_t *cf) { - - xdl_free(cf->rcrecs); - xdl_free(cf->rchash); - xdl_cha_free(&cf->ncha); -} - - -static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, - unsigned int hbits, xrecord_t *rec) { - long hi; - char const *line; - xdlclass_t *rcrec; - xdlclass_t **rcrecs; - - line = rec->ptr; - hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); - for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) - if (rcrec->ha == rec->ha && - xdl_recmatch(rcrec->line, rcrec->size, - rec->ptr, rec->size, cf->flags)) - break; - - if (!rcrec) { - if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { - - return -1; - } - rcrec->idx = cf->count++; - if (cf->count > cf->alloc) { - cf->alloc *= 2; - if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { - - return -1; - } - cf->rcrecs = rcrecs; - } - cf->rcrecs[rcrec->idx] = rcrec; - rcrec->line = line; - rcrec->size = rec->size; - rcrec->ha = rec->ha; - rcrec->len1 = rcrec->len2 = 0; - rcrec->next = cf->rchash[hi]; - cf->rchash[hi] = rcrec; - } - - (pass == 1) ? rcrec->len1++ : rcrec->len2++; - - rec->ha = (unsigned long) rcrec->idx; - - hi = (long) XDL_HASHLONG(rec->ha, hbits); - rec->next = rhash[hi]; - rhash[hi] = rec; - - return 0; -} - - -static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, - xdlclassifier_t *cf, xdfile_t *xdf) { - unsigned int hbits; - long nrec, hsize, bsize; - unsigned long hav; - char const *blk, *cur, *top, *prev; - xrecord_t *crec; - xrecord_t **recs, **rrecs; - xrecord_t **rhash; - unsigned long *ha; - char *rchg; - long *rindex; - - ha = NULL; - rindex = NULL; - rchg = NULL; - rhash = NULL; - recs = NULL; - - if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) - goto abort; - if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) - goto abort; - - hbits = xdl_hashbits((unsigned int) narec); - hsize = 1 << hbits; - if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) - goto abort; - memset(rhash, 0, hsize * sizeof(xrecord_t *)); - - nrec = 0; - if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { - for (top = blk + bsize; cur < top; ) { - prev = cur; - hav = xdl_hash_record(&cur, top, xpp->flags); - if (nrec >= narec) { - narec *= 2; - if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) - goto abort; - recs = rrecs; - } - if (!(crec = xdl_cha_alloc(&xdf->rcha))) - goto abort; - crec->ptr = prev; - crec->size = (long) (cur - prev); - crec->ha = hav; - recs[nrec++] = crec; - if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) - goto abort; - } - } - - if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) - goto abort; - memset(rchg, 0, (nrec + 2) * sizeof(char)); - - if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && - (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) { - if (!(rindex = xdl_malloc((nrec + 1) * sizeof(*rindex)))) - goto abort; - if (!(ha = xdl_malloc((nrec + 1) * sizeof(*ha)))) - goto abort; - } - - xdf->nrec = nrec; - xdf->recs = recs; - xdf->hbits = hbits; - xdf->rhash = rhash; - xdf->rchg = rchg + 1; - xdf->rindex = rindex; - xdf->nreff = 0; - xdf->ha = ha; - xdf->dstart = 0; - xdf->dend = nrec - 1; - - return 0; - -abort: - xdl_free(ha); - xdl_free(rindex); - xdl_free(rchg); - xdl_free(rhash); - xdl_free(recs); - xdl_cha_free(&xdf->rcha); - return -1; -} - - -static void xdl_free_ctx(xdfile_t *xdf) { - - xdl_free(xdf->rhash); - xdl_free(xdf->rindex); - xdl_free(xdf->rchg - 1); - xdl_free(xdf->ha); - xdl_free(xdf->recs); - xdl_cha_free(&xdf->rcha); -} - - -int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe) { - long enl1, enl2, sample; - xdlclassifier_t cf; - - memset(&cf, 0, sizeof(cf)); - - /* - * For histogram diff, we can afford a smaller sample size and - * thus a poorer estimate of the number of lines, as the hash - * table (rhash) won't be filled up/grown. The number of lines - * (nrecs) will be updated correctly anyway by - * xdl_prepare_ctx(). - */ - sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF - ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1); - - enl1 = xdl_guess_lines(mf1, sample) + 1; - enl2 = xdl_guess_lines(mf2, sample) + 1; - - if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) - return -1; - - if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { - - xdl_free_classifier(&cf); - return -1; - } - if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { - - xdl_free_ctx(&xe->xdf1); - xdl_free_classifier(&cf); - return -1; - } - - if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && - (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) && - xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { - - xdl_free_ctx(&xe->xdf2); - xdl_free_ctx(&xe->xdf1); - xdl_free_classifier(&cf); - return -1; - } - - xdl_free_classifier(&cf); - - return 0; -} - - -void xdl_free_env(xdfenv_t *xe) { - - xdl_free_ctx(&xe->xdf2); - xdl_free_ctx(&xe->xdf1); -} - - -static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { - long r, rdis0, rpdis0, rdis1, rpdis1; - - /* - * Limits the window the is examined during the similar-lines - * scan. The loops below stops when dis[i - r] == 1 (line that - * has no match), but there are corner cases where the loop - * proceed all the way to the extremities by causing huge - * performance penalties in case of big files. - */ - if (i - s > XDL_SIMSCAN_WINDOW) - s = i - XDL_SIMSCAN_WINDOW; - if (e - i > XDL_SIMSCAN_WINDOW) - e = i + XDL_SIMSCAN_WINDOW; - - /* - * Scans the lines before 'i' to find a run of lines that either - * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). - * Note that we always call this function with dis[i] > 1, so the - * current line (i) is already a multimatch line. - */ - for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { - if (!dis[i - r]) - rdis0++; - else if (dis[i - r] == 2) - rpdis0++; - else - break; - } - /* - * If the run before the line 'i' found only multimatch lines, we - * return 0 and hence we don't make the current line (i) discarded. - * We want to discard multimatch lines only when they appear in the - * middle of runs with nomatch lines (dis[j] == 0). - */ - if (rdis0 == 0) - return 0; - for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { - if (!dis[i + r]) - rdis1++; - else if (dis[i + r] == 2) - rpdis1++; - else - break; - } - /* - * If the run after the line 'i' found only multimatch lines, we - * return 0 and hence we don't make the current line (i) discarded. - */ - if (rdis1 == 0) - return 0; - rdis1 += rdis0; - rpdis1 += rpdis0; - - return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); -} - - -/* - * Try to reduce the problem complexity, discard records that have no - * matches on the other file. Also, lines that have multiple matches - * might be potentially discarded if they happear in a run of discardable. - */ -static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - long i, nm, nreff, mlim; - xrecord_t **recs; - xdlclass_t *rcrec; - char *dis, *dis1, *dis2; - - if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { - - return -1; - } - memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); - dis1 = dis; - dis2 = dis1 + xdf1->nrec + 1; - - if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; - for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { - rcrec = cf->rcrecs[(*recs)->ha]; - nm = rcrec ? rcrec->len2 : 0; - dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; - } - - if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; - for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { - rcrec = cf->rcrecs[(*recs)->ha]; - nm = rcrec ? rcrec->len1 : 0; - dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; - } - - for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; - i <= xdf1->dend; i++, recs++) { - if (dis1[i] == 1 || - (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { - xdf1->rindex[nreff] = i; - xdf1->ha[nreff] = (*recs)->ha; - nreff++; - } else - xdf1->rchg[i] = 1; - } - xdf1->nreff = nreff; - - for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; - i <= xdf2->dend; i++, recs++) { - if (dis2[i] == 1 || - (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { - xdf2->rindex[nreff] = i; - xdf2->ha[nreff] = (*recs)->ha; - nreff++; - } else - xdf2->rchg[i] = 1; - } - xdf2->nreff = nreff; - - xdl_free(dis); - - return 0; -} - - -/* - * Early trim initial and terminal matching records. - */ -static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { - long i, lim; - xrecord_t **recs1, **recs2; - - recs1 = xdf1->recs; - recs2 = xdf2->recs; - for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; - i++, recs1++, recs2++) - if ((*recs1)->ha != (*recs2)->ha) - break; - - xdf1->dstart = xdf2->dstart = i; - - recs1 = xdf1->recs + xdf1->nrec - 1; - recs2 = xdf2->recs + xdf2->nrec - 1; - for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) - if ((*recs1)->ha != (*recs2)->ha) - break; - - xdf1->dend = xdf1->nrec - i - 1; - xdf2->dend = xdf2->nrec - i - 1; - - return 0; -} - - -static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - - if (xdl_trim_ends(xdf1, xdf2) < 0 || - xdl_cleanup_records(cf, xdf1, xdf2) < 0) { - - return -1; - } - - return 0; -} diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h deleted file mode 100644 index 947d9fc1b..000000000 --- a/src/xdiff/xprepare.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XPREPARE_H) -#define XPREPARE_H - - - -int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe); -void xdl_free_env(xdfenv_t *xe); - - - -#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h deleted file mode 100644 index 8442bd436..000000000 --- a/src/xdiff/xtypes.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XTYPES_H) -#define XTYPES_H - - - -typedef struct s_chanode { - struct s_chanode *next; - long icurr; -} chanode_t; - -typedef struct s_chastore { - chanode_t *head, *tail; - long isize, nsize; - chanode_t *ancur; - chanode_t *sncur; - long scurr; -} chastore_t; - -typedef struct s_xrecord { - struct s_xrecord *next; - char const *ptr; - long size; - unsigned long ha; -} xrecord_t; - -typedef struct s_xdfile { - chastore_t rcha; - long nrec; - unsigned int hbits; - xrecord_t **rhash; - long dstart, dend; - xrecord_t **recs; - char *rchg; - long *rindex; - long nreff; - unsigned long *ha; -} xdfile_t; - -typedef struct s_xdfenv { - xdfile_t xdf1, xdf2; -} xdfenv_t; - - - -#endif /* #if !defined(XTYPES_H) */ diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c deleted file mode 100644 index cfa6e2220..000000000 --- a/src/xdiff/xutils.c +++ /dev/null @@ -1,434 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - - -long xdl_bogosqrt(long n) { - long i; - - /* - * Classical integer square root approximation using shifts. - */ - for (i = 1; n > 0; n >>= 2) - i <<= 1; - - return i; -} - - -int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, - xdemitcb_t *ecb) { - int i = 2; - mmbuffer_t mb[3]; - - mb[0].ptr = (char *) pre; - mb[0].size = psize; - mb[1].ptr = (char *) rec; - mb[1].size = size; - if (size > 0 && rec[size - 1] != '\n') { - mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; - mb[2].size = strlen(mb[2].ptr); - i++; - } - if (ecb->out_line(ecb->priv, mb, i) < 0) { - - return -1; - } - - return 0; -} - -void *xdl_mmfile_first(mmfile_t *mmf, long *size) -{ - *size = mmf->size; - return mmf->ptr; -} - - -long xdl_mmfile_size(mmfile_t *mmf) -{ - return mmf->size; -} - - -int xdl_cha_init(chastore_t *cha, long isize, long icount) { - - cha->head = cha->tail = NULL; - cha->isize = isize; - cha->nsize = icount * isize; - cha->ancur = cha->sncur = NULL; - cha->scurr = 0; - - return 0; -} - - -void xdl_cha_free(chastore_t *cha) { - chanode_t *cur, *tmp; - - for (cur = cha->head; (tmp = cur) != NULL;) { - cur = cur->next; - xdl_free(tmp); - } -} - - -void *xdl_cha_alloc(chastore_t *cha) { - chanode_t *ancur; - void *data; - - if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { - if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { - - return NULL; - } - ancur->icurr = 0; - ancur->next = NULL; - if (cha->tail) - cha->tail->next = ancur; - if (!cha->head) - cha->head = ancur; - cha->tail = ancur; - cha->ancur = ancur; - } - - data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; - ancur->icurr += cha->isize; - - return data; -} - -long xdl_guess_lines(mmfile_t *mf, long sample) { - long nl = 0, size, tsize = 0; - char const *data, *cur, *top; - - if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { - for (top = data + size; nl < sample && cur < top; ) { - nl++; - if (!(cur = memchr(cur, '\n', top - cur))) - cur = top; - else - cur++; - } - tsize += (long) (cur - data); - } - - if (nl && tsize) - nl = xdl_mmfile_size(mf) / (tsize / nl); - - return nl + 1; -} - -int xdl_blankline(const char *line, long size, long flags) -{ - long i; - - if (!(flags & XDF_WHITESPACE_FLAGS)) - return (size <= 1); - - for (i = 0; i < size && XDL_ISSPACE(line[i]); i++) - ; - - return (i == size); -} - -/* - * Have we eaten everything on the line, except for an optional - * CR at the very end? - */ -static int ends_with_optional_cr(const char *l, long s, long i) -{ - int complete = s && l[s-1] == '\n'; - - if (complete) - s--; - if (s == i) - return 1; - /* do not ignore CR at the end of an incomplete line */ - if (complete && s == i + 1 && l[i] == '\r') - return 1; - return 0; -} - -int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) -{ - int i1, i2; - - if (s1 == s2 && !memcmp(l1, l2, s1)) - return 1; - if (!(flags & XDF_WHITESPACE_FLAGS)) - return 0; - - i1 = 0; - i2 = 0; - - /* - * -w matches everything that matches with -b, and -b in turn - * matches everything that matches with --ignore-space-at-eol, - * which in turn matches everything that matches with --ignore-cr-at-eol. - * - * Each flavor of ignoring needs different logic to skip whitespaces - * while we have both sides to compare. - */ - if (flags & XDF_IGNORE_WHITESPACE) { - goto skip_ws; - while (i1 < s1 && i2 < s2) { - if (l1[i1++] != l2[i2++]) - return 0; - skip_ws: - while (i1 < s1 && XDL_ISSPACE(l1[i1])) - i1++; - while (i2 < s2 && XDL_ISSPACE(l2[i2])) - i2++; - } - } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { - while (i1 < s1 && i2 < s2) { - if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { - /* Skip matching spaces and try again */ - while (i1 < s1 && XDL_ISSPACE(l1[i1])) - i1++; - while (i2 < s2 && XDL_ISSPACE(l2[i2])) - i2++; - continue; - } - if (l1[i1++] != l2[i2++]) - return 0; - } - } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { - while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { - i1++; - i2++; - } - } else if (flags & XDF_IGNORE_CR_AT_EOL) { - /* Find the first difference and see how the line ends */ - while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { - i1++; - i2++; - } - return (ends_with_optional_cr(l1, s1, i1) && - ends_with_optional_cr(l2, s2, i2)); - } - - /* - * After running out of one side, the remaining side must have - * nothing but whitespace for the lines to match. Note that - * ignore-whitespace-at-eol case may break out of the loop - * while there still are characters remaining on both lines. - */ - if (i1 < s1) { - while (i1 < s1 && XDL_ISSPACE(l1[i1])) - i1++; - if (s1 != i1) - return 0; - } - if (i2 < s2) { - while (i2 < s2 && XDL_ISSPACE(l2[i2])) - i2++; - return (s2 == i2); - } - return 1; -} - -static unsigned long xdl_hash_record_with_whitespace(char const **data, - char const *top, long flags) { - unsigned long ha = 5381; - char const *ptr = *data; - int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; - - for (; ptr < top && *ptr != '\n'; ptr++) { - if (cr_at_eol_only) { - /* do not ignore CR at the end of an incomplete line */ - if (*ptr == '\r' && - (ptr + 1 < top && ptr[1] == '\n')) - continue; - } - else if (XDL_ISSPACE(*ptr)) { - const char *ptr2 = ptr; - int at_eol; - while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) - && ptr[1] != '\n') - ptr++; - at_eol = (top <= ptr + 1 || ptr[1] == '\n'); - if (flags & XDF_IGNORE_WHITESPACE) - ; /* already handled */ - else if (flags & XDF_IGNORE_WHITESPACE_CHANGE - && !at_eol) { - ha += (ha << 5); - ha ^= (unsigned long) ' '; - } - else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL - && !at_eol) { - while (ptr2 != ptr + 1) { - ha += (ha << 5); - ha ^= (unsigned long) *ptr2; - ptr2++; - } - } - continue; - } - ha += (ha << 5); - ha ^= (unsigned long) *ptr; - } - *data = ptr < top ? ptr + 1: ptr; - - return ha; -} - -unsigned long xdl_hash_record(char const **data, char const *top, long flags) { - unsigned long ha = 5381; - char const *ptr = *data; - - if (flags & XDF_WHITESPACE_FLAGS) - return xdl_hash_record_with_whitespace(data, top, flags); - - for (; ptr < top && *ptr != '\n'; ptr++) { - ha += (ha << 5); - ha ^= (unsigned long) *ptr; - } - *data = ptr < top ? ptr + 1: ptr; - - return ha; -} - -unsigned int xdl_hashbits(unsigned int size) { - unsigned int val = 1, bits = 0; - - for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); - return bits ? bits: 1; -} - - -int xdl_num_out(char *out, long val) { - char *ptr, *str = out; - char buf[32]; - - ptr = buf + sizeof(buf) - 1; - *ptr = '\0'; - if (val < 0) { - *--ptr = '-'; - val = -val; - } - for (; val && ptr > buf; val /= 10) - *--ptr = "0123456789"[val % 10]; - if (*ptr) - for (; *ptr; ptr++, str++) - *str = *ptr; - else - *str++ = '0'; - *str = '\0'; - - return str - out; -} - -static int xdl_format_hunk_hdr(long s1, long c1, long s2, long c2, - const char *func, long funclen, - xdemitcb_t *ecb) { - int nb = 0; - mmbuffer_t mb; - char buf[128]; - - memcpy(buf, "@@ -", 4); - nb += 4; - - nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); - - if (c1 != 1) { - memcpy(buf + nb, ",", 1); - nb += 1; - - nb += xdl_num_out(buf + nb, c1); - } - - memcpy(buf + nb, " +", 2); - nb += 2; - - nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); - - if (c2 != 1) { - memcpy(buf + nb, ",", 1); - nb += 1; - - nb += xdl_num_out(buf + nb, c2); - } - - memcpy(buf + nb, " @@", 3); - nb += 3; - if (func && funclen) { - buf[nb++] = ' '; - if (funclen > sizeof(buf) - nb - 1) - funclen = sizeof(buf) - nb - 1; - memcpy(buf + nb, func, funclen); - nb += funclen; - } - buf[nb++] = '\n'; - - mb.ptr = buf; - mb.size = nb; - if (ecb->out_line(ecb->priv, &mb, 1) < 0) - return -1; - return 0; -} - -int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, - const char *func, long funclen, - xdemitcb_t *ecb) { - if (!ecb->out_hunk) - return xdl_format_hunk_hdr(s1, c1, s2, c2, func, funclen, ecb); - if (ecb->out_hunk(ecb->priv, - c1 ? s1 : s1 - 1, c1, - c2 ? s2 : s2 - 1, c2, - func, funclen) < 0) - return -1; - return 0; -} - -int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, - int line1, int count1, int line2, int count2) -{ - /* - * This probably does not work outside Git, since - * we have a very simple mmfile structure. - * - * Note: ideally, we would reuse the prepared environment, but - * the libxdiff interface does not (yet) allow for diffing only - * ranges of lines instead of the whole files. - */ - mmfile_t subfile1, subfile2; - xdfenv_t env; - - subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; - subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + - diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; - subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; - subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + - diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; - if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) - return -1; - - memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); - memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); - - xdl_free_env(&env); - - return 0; -} diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h deleted file mode 100644 index fba7bae03..000000000 --- a/src/xdiff/xutils.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XUTILS_H) -#define XUTILS_H - - - -long xdl_bogosqrt(long n); -int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, - xdemitcb_t *ecb); -int xdl_cha_init(chastore_t *cha, long isize, long icount); -void xdl_cha_free(chastore_t *cha); -void *xdl_cha_alloc(chastore_t *cha); -long xdl_guess_lines(mmfile_t *mf, long sample); -int xdl_blankline(const char *line, long size, long flags); -int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); -unsigned long xdl_hash_record(char const **data, char const *top, long flags); -unsigned int xdl_hashbits(unsigned int size); -int xdl_num_out(char *out, long val); -int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, - const char *func, long funclen, xdemitcb_t *ecb); -int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, - int line1, int count1, int line2, int count2); - - - -#endif /* #if !defined(XUTILS_H) */ diff --git a/src/zstream.c b/src/zstream.c deleted file mode 100644 index cb8b125ed..000000000 --- a/src/zstream.c +++ /dev/null @@ -1,210 +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 "zstream.h" - -#include - -#include "str.h" - -#define ZSTREAM_BUFFER_SIZE (1024 * 1024) -#define ZSTREAM_BUFFER_MIN_EXTRA 8 - -GIT_INLINE(int) zstream_seterr(git_zstream *zs) -{ - switch (zs->zerr) { - case Z_OK: - case Z_STREAM_END: - case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ - return 0; - case Z_MEM_ERROR: - git_error_set_oom(); - break; - default: - if (zs->z.msg) - git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); - else - git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); - } - - return -1; -} - -int git_zstream_init(git_zstream *zstream, git_zstream_t type) -{ - zstream->type = type; - - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflateInit(&zstream->z); - else - zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); - return zstream_seterr(zstream); -} - -void git_zstream_free(git_zstream *zstream) -{ - if (zstream->type == GIT_ZSTREAM_INFLATE) - inflateEnd(&zstream->z); - else - deflateEnd(&zstream->z); -} - -void git_zstream_reset(git_zstream *zstream) -{ - if (zstream->type == GIT_ZSTREAM_INFLATE) - inflateReset(&zstream->z); - else - deflateReset(&zstream->z); - zstream->in = NULL; - zstream->in_len = 0; - zstream->zerr = Z_STREAM_END; -} - -int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) -{ - zstream->in = in; - zstream->in_len = in_len; - zstream->zerr = Z_OK; - return 0; -} - -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) - return ZSTREAM_BUFFER_SIZE; - else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) - return zstream->in_len; - else - return ZSTREAM_BUFFER_MIN_EXTRA; -} - -int git_zstream_get_output_chunk( - void *out, size_t *out_len, git_zstream *zstream) -{ - size_t in_queued, in_used, out_queued; - - /* set up input data */ - zstream->z.next_in = (Bytef *)zstream->in; - - /* feed as much data to zlib as it can consume, at most UINT_MAX */ - if (zstream->in_len > UINT_MAX) { - zstream->z.avail_in = UINT_MAX; - zstream->flush = Z_NO_FLUSH; - } else { - zstream->z.avail_in = (uInt)zstream->in_len; - zstream->flush = Z_FINISH; - } - in_queued = (size_t)zstream->z.avail_in; - - /* set up output data */ - zstream->z.next_out = out; - zstream->z.avail_out = (uInt)*out_len; - - if ((size_t)zstream->z.avail_out != *out_len) - zstream->z.avail_out = UINT_MAX; - out_queued = (size_t)zstream->z.avail_out; - - /* compress next chunk */ - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflate(&zstream->z, zstream->flush); - else - zstream->zerr = deflate(&zstream->z, zstream->flush); - - if (zstream_seterr(zstream)) - return -1; - - in_used = (in_queued - zstream->z.avail_in); - zstream->in_len -= in_used; - zstream->in += in_used; - - *out_len = (out_queued - zstream->z.avail_out); - - return 0; -} - -int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) -{ - size_t out_remain = *out_len; - - if (zstream->in_len && zstream->zerr == Z_STREAM_END) { - git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); - return -1; - } - - while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { - size_t out_written = out_remain; - - if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) - return -1; - - out_remain -= out_written; - out = ((char *)out) + out_written; - } - - /* either we finished the input or we did not flush the data */ - GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); - - /* set out_size to number of bytes actually written to output */ - *out_len = *out_len - out_remain; - - return 0; -} - -static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) -{ - git_zstream zs = GIT_ZSTREAM_INIT; - int error = 0; - - if ((error = git_zstream_init(&zs, type)) < 0) - return error; - - if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) - goto done; - - while (!git_zstream_done(&zs)) { - size_t step = git_zstream_suggest_output_len(&zs), written; - - if ((error = git_str_grow_by(out, step)) < 0) - goto done; - - written = out->asize - out->size; - - if ((error = git_zstream_get_output( - out->ptr + out->size, &written, &zs)) < 0) - goto done; - - out->size += written; - } - - /* NULL terminate for consistency if possible */ - if (out->size < out->asize) - out->ptr[out->size] = '\0'; - -done: - git_zstream_free(&zs); - return error; -} - -int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) -{ - return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); -} - -int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) -{ - return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); -} diff --git a/src/zstream.h b/src/zstream.h deleted file mode 100644 index 3f8b1c72f..000000000 --- a/src/zstream.h +++ /dev/null @@ -1,54 +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_zstream_h__ -#define INCLUDE_zstream_h__ - -#include "common.h" - -#include - -#include "str.h" - -typedef enum { - GIT_ZSTREAM_INFLATE, - GIT_ZSTREAM_DEFLATE -} git_zstream_t; - -typedef struct { - z_stream z; - git_zstream_t type; - const char *in; - size_t in_len; - int flush; - int zerr; -} git_zstream; - -#define GIT_ZSTREAM_INIT {{0}} - -int git_zstream_init(git_zstream *zstream, git_zstream_t type); -void git_zstream_free(git_zstream *zstream); - -int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); - -size_t git_zstream_suggest_output_len(git_zstream *zstream); - -/* get as much output as is available in the input buffer */ -int git_zstream_get_output_chunk( - void *out, size_t *out_len, git_zstream *zstream); - -/* get all the output from the entire input buffer */ -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); - -int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len); -int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len); - -#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f293c158d..df100e980 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,96 +1,6 @@ -set(Python_ADDITIONAL_VERSIONS 3 2.7) -find_package(PythonInterp) +# The main libgit2 tests tree: this CMakeLists.txt includes the +# subprojects that make up core libgit2 support. -if(NOT PYTHONINTERP_FOUND) - message(FATAL_ERROR "Could not find a python interpreter, which is needed to build the tests. " - "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") -ENDIF() - -set(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/resources/") -set(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}") -add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") -add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") -add_definitions(-DCLAR_WIN32_LONGPATHS) -add_definitions(-D_FILE_OFFSET_BITS=64) - -# Ensure that we do not use deprecated functions internally -add_definitions(-DGIT_DEPRECATE_HARD) - -set(TEST_INCLUDES "${CLAR_PATH}" "${CMAKE_CURRENT_BINARY_DIR}") -file(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) -set(SRC_CLAR "main.c" "clar_libgit2.c" "clar_libgit2_trace.c" "clar_libgit2_timer.c" "clar.c") - -if(MSVC_IDE) - list(APPEND SRC_CLAR "precompiled.c") -endif() - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h - COMMAND ${PYTHON_EXECUTABLE} generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f -xonline -xstress -xperf . - DEPENDS ${SRC_TEST} - WORKING_DIRECTORY ${CLAR_PATH} -) - -set_source_files_properties( - ${CLAR_PATH}/clar.c - PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite) - -add_executable(libgit2_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) - -set_target_properties(libgit2_tests PROPERTIES C_STANDARD 90) -set_target_properties(libgit2_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - -target_include_directories(libgit2_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) -target_include_directories(libgit2_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) -target_link_libraries(libgit2_tests ${LIBGIT2_SYSTEM_LIBS}) - -ide_split_sources(libgit2_tests) - -# -# Old versions of gcc require us to declare our test functions; don't do -# this on newer compilers to avoid unnecessary recompilation. -# -if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - target_compile_options(libgit2_tests PRIVATE -include "clar_suite.h") -endif() - -if(MSVC_IDE) - # Precompiled headers - set_target_properties(libgit2_tests PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") -endif() - -function(ADD_CLAR_TEST name) - if(NOT USE_LEAK_CHECKER STREQUAL "OFF") - add_test(${name} "${PROJECT_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) - else() - add_test(${name} "${PROJECT_BINARY_DIR}/libgit2_tests" ${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 -xonline::customcert) -add_clar_test(online_customcert -v -sonline::customcert) -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) - -# -# Header file validation project: ensure that we do not publish any sloppy -# definitions in our headers and that a consumer can include -# even when they have aggressive C90 warnings enabled. -# - -add_executable(headertest headertest.c) -set_target_properties(headertest PROPERTIES C_STANDARD 90) -set_target_properties(headertest PROPERTIES C_EXTENSIONS OFF) -target_include_directories(headertest PRIVATE ${LIBGIT2_INCLUDES}) - -if (MSVC) - target_compile_options(headertest PUBLIC /W4 /WX) -else() - target_compile_options(headertest PUBLIC -Wall -Wextra -pedantic -Werror) -endif() +add_subdirectory(headertest) +add_subdirectory(libgit2) +add_subdirectory(util) diff --git a/tests/README.md b/tests/README.md index 4369a8f33..460e045e3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,33 +1,58 @@ -Writing Clar tests for libgit2 -============================== - -For information on the Clar testing framework and a detailed introduction -please visit: - -https://github.com/vmg/clar - - -* Write your modules and tests. Use good, meaningful names. - -* Make sure you actually build the tests by setting: - - cmake -DBUILD_TESTS=ON build/ - -* Test: - - ./build/libgit2_tests - -* Make sure everything is fine. - -* Send your pull request. That's it. - - -Memory leak checks ------------------- +# libgit2 tests + +These are the unit and integration tests for the libgit2 projects. + +* `benchmarks` + These are benchmark tests that excercise the CLI. +* `clar` + This is [clar](https://github.com/clar-test/clar) the common test framework. +* `headertest` + This is a simple project that ensures that our public headers are + compatible with extremely strict compilation options. +* `libgit2` + These tests exercise the core git functionality in libgit2 itself. +* `resources` + These are the resources for the tests, including files and git + repositories. +* `util` + These are tests of the common utility library. + +## Writing tests for libgit2 + +libgit2 uses the [clar test framework](http://github.com/clar-test/clar), a +C testing framework. + +The best resources for learning clar are [clar itself](https://github.com/clar-test/clar) +and the existing tests within libgit2. In general: + +* If you place a `.c` file into a test directory, it is eligible to contain +test cases. +* The function name for your test is important; test function names begin + with `test_`, followed by the folder path (underscore separated), two + underscores as a delimiter, then the test name. For example, a file + `merge/analysis.c` may contain a test `uptodate`: + + ``` + void test_merge_analysis__uptodate(void) + { + ... + } + ``` + +* You can run an individual test by passing `-s` to the test runner. Tests + are referred to by their function names; for example, the function + `test_merge_analysis__uptodate` is referred to as `merge::analysis::uptodate`. + To run only that function you can use the `-s` option on the test runner: + + ``` + libgit2_tests -smerge::analysis::uptodate + ``` + +## Memory leak checking These are automatically run as part of CI, but if you want to check locally: -#### Linux +### Linux Uses [`valgrind`](http://www.valgrind.org/): @@ -38,7 +63,7 @@ $ valgrind --leak-check=full --show-reachable=yes --num-callers=50 --suppression ./libgit2_tests ``` -#### macOS +### macOS Uses [`leaks`](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/FindingLeaks.html), which requires XCode installed: @@ -46,3 +71,13 @@ Uses [`leaks`](https://developer.apple.com/library/archive/documentation/Perform $ MallocStackLogging=1 MallocScribble=1 MallocLogFile=/dev/null CLAR_AT_EXIT="leaks -quiet \$PPID" \ ./libgit2_tests ``` + +### Windows + +Build with the `WIN32_LEAKCHECK` option: + +```console +$ cmake -DBUILD_TESTS=ON -DWIN32_LEAKCHECK=ON .. +$ cmake --build . +$ ./libgit2_tests +``` diff --git a/tests/apply/apply_helpers.c b/tests/apply/apply_helpers.c deleted file mode 100644 index 91cc51a71..000000000 --- a/tests/apply/apply_helpers.c +++ /dev/null @@ -1,135 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -struct iterator_compare_data { - struct merge_index_entry *expected; - size_t cnt; - size_t idx; -}; - -static int iterator_compare(const git_index_entry *entry, void *_data) -{ - git_oid expected_id; - - struct iterator_compare_data *data = (struct iterator_compare_data *)_data; - - cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry), data->expected[data->idx].stage); - cl_git_pass(git_oid_fromstr(&expected_id, data->expected[data->idx].oid_str)); - cl_assert_equal_oid(&entry->id, &expected_id); - cl_assert_equal_i(entry->mode, data->expected[data->idx].mode); - cl_assert_equal_s(entry->path, data->expected[data->idx].path); - - if (data->idx >= data->cnt) - return -1; - - data->idx++; - - return 0; -} - -void validate_apply_workdir( - git_repository *repo, - struct merge_index_entry *workdir_entries, - size_t workdir_cnt) -{ - git_index *index; - git_iterator *iterator; - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - struct iterator_compare_data data = { workdir_entries, workdir_cnt }; - - opts.flags |= GIT_ITERATOR_INCLUDE_HASH; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_iterator_for_workdir(&iterator, repo, index, NULL, &opts)); - - cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); - cl_assert_equal_i(data.idx, data.cnt); - - git_iterator_free(iterator); - git_index_free(index); -} - -void validate_apply_index( - git_repository *repo, - struct merge_index_entry *index_entries, - size_t index_cnt) -{ - git_index *index; - git_iterator *iterator; - struct iterator_compare_data data = { index_entries, index_cnt }; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_iterator_for_index(&iterator, repo, index, NULL)); - - cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); - cl_assert_equal_i(data.idx, data.cnt); - - git_iterator_free(iterator); - git_index_free(index); -} - -static int iterator_eq(const git_index_entry **entry, void *_data) -{ - GIT_UNUSED(_data); - - if (!entry[0] || !entry[1]) - return -1; - - cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry[0]), GIT_INDEX_ENTRY_STAGE(entry[1])); - cl_assert_equal_oid(&entry[0]->id, &entry[1]->id); - cl_assert_equal_i(entry[0]->mode, entry[1]->mode); - cl_assert_equal_s(entry[0]->path, entry[1]->path); - - return 0; -} - -void validate_index_unchanged(git_repository *repo) -{ - git_tree *head; - git_index *index; - git_iterator *head_iterator, *index_iterator, *iterators[2]; - - cl_git_pass(git_repository_head_tree(&head, repo)); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); - cl_git_pass(git_iterator_for_index(&index_iterator, repo, index, NULL)); - - iterators[0] = head_iterator; - iterators[1] = index_iterator; - - cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); - - git_iterator_free(head_iterator); - git_iterator_free(index_iterator); - - git_tree_free(head); - git_index_free(index); -} - -void validate_workdir_unchanged(git_repository *repo) -{ - git_tree *head; - git_index *index; - git_iterator *head_iterator, *workdir_iterator, *iterators[2]; - git_iterator_options workdir_opts = GIT_ITERATOR_OPTIONS_INIT; - - cl_git_pass(git_repository_head_tree(&head, repo)); - cl_git_pass(git_repository_index(&index, repo)); - - workdir_opts.flags |= GIT_ITERATOR_INCLUDE_HASH; - - cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); - cl_git_pass(git_iterator_for_workdir(&workdir_iterator, repo, index, NULL, &workdir_opts)); - - iterators[0] = head_iterator; - iterators[1] = workdir_iterator; - - cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); - - git_iterator_free(head_iterator); - git_iterator_free(workdir_iterator); - - git_tree_free(head); - git_index_free(index); -} diff --git a/tests/apply/apply_helpers.h b/tests/apply/apply_helpers.h deleted file mode 100644 index 82094773e..000000000 --- a/tests/apply/apply_helpers.h +++ /dev/null @@ -1,497 +0,0 @@ -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "merge-recursive" - -#define DIFF_MODIFY_TWO_FILES \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "index f516580..ffb36e5 100644\n" \ - "--- a/asparagus.txt\n" \ - "+++ b/asparagus.txt\n" \ - "@@ -1 +1 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "+ASPARAGUS SOUP.\n" \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01..a7b0665 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1 +1 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP.\n" \ - "@@ -7 +7 @@ occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "-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" \ - "index c4e6cca..0000000\n" \ - "--- a/gravy.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,8 +0,0 @@\n" \ - "-GRAVY SOUP.\n" \ - "-\n" \ - "-Get eight pounds of coarse lean beef--wash it clean and lay it in your\n" \ - "-pot, put in the same ingredients as for the shin soup, with the same\n" \ - "-quantity of water, and follow the process directed for that. Strain the\n" \ - "-soup through a sieve, and serve it up clear, with nothing more than\n" \ - "-toasted bread in it; two table-spoonsful of mushroom catsup will add a\n" \ - "-fine flavour to the soup.\n" - -#define DIFF_ADD_FILE \ - "diff --git a/newfile.txt b/newfile.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..6370543\n" \ - "--- /dev/null\n" \ - "+++ b/newfile.txt\n" \ - "@@ -0,0 +1,2 @@\n" \ - "+This is a new file!\n" \ - "+Added by a patch.\n" - -#define DIFF_EXECUTABLE_FILE \ - "diff --git a/beef.txt b/beef.txt\n" \ - "old mode 100644\n" \ - "new mode 100755\n" - -#define DIFF_MANY_CHANGES_ONE \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01..c9d7d5d 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,2 +1,2 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - " \n" \ - "@@ -4,3 +4,2 @@\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "-slices of lean ham; let it boil steadily two hours; skim it\n" \ - " occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "@@ -8,3 +7,3 @@\n" \ - " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ - "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ - "+OF FLOUR very nicely, and the yelks of two eggs beaten well, strain this\n" \ - " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ - "@@ -12,2 +11,3 @@\n" \ - " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ - "+Inserted line.\n" \ - " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ - "@@ -15,3 +15,3 @@\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - "-hot water, when they may be easily peeled. When made in this way you\n" \ - "+Changed line.\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" - -#define DIFF_MANY_CHANGES_TWO \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01..6b943d6 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,2 +1,2 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP!!!\n" \ - " \n" \ - "@@ -4,3 +4,2 @@\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "-slices of lean ham; let it boil steadily two hours; skim it\n" \ - " occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "@@ -8,3 +7,3 @@\n" \ - " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ - "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ - "+of flour very nicely, AND the yelks of two eggs beaten well, strain this\n" \ - " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ - "@@ -12,2 +11,3 @@\n" \ - " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ - "+New line.\n" \ - " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ - "@@ -15,4 +15,5 @@\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - "-hot water, when they may be easily peeled. When made in this way you\n" \ - "-must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+HOT water, when they may be easily peeled. When made in this way you\n" \ - "+must THICKEN it with the flour only. Any part of the veal may be used,\n" \ - "+but the shin OR knuckle is the nicest.\n" \ - "+Another new line.\n" \ - -#define DIFF_RENAME_FILE \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to notbeef.txt\n" - -#define DIFF_RENAME_AND_MODIFY_FILE \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 97%\n" \ - "rename from beef.txt\n" \ - "rename to notbeef.txt\n" \ - "index 68f6182..6fa1014 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/notbeef.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-BEEF SOUP.\n" \ - "+THIS IS NOT BEEF SOUP, IT HAS A NEW NAME.\n" \ - "\n" \ - " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - " which must be taken away entirely, or the soup will be greasy. Wash the\n" - -#define DIFF_RENAME_A_TO_B_TO_C \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 68f6182..f516580 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -1,22 +1,10 @@\n" \ - "-BEEF SOUP.\n" \ - "+ASPARAGUS SOUP!\n" \ - "\n" \ - "-Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - "-which must be taken away entirely, or the soup will be greasy. Wash the\n" \ - "-meat clean and lay it in a pot, sprinkle over it one small\n" \ - "-table-spoonful of pounded black pepper, and two of salt; three onions\n" \ - "-the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ - "-up, two small turnips pared and cut into dice; pour on three quarts of\n" \ - "-water, cover the pot close, and keep it gently and steadily boiling five\n" \ - "-hours, which will leave about three pints of clear soup; do not let the\n" \ - "-pot boil over, but take off the scum carefully, as it rises. When it has\n" \ - "-boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ - "-pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ - "-These latter ingredients would lose their delicate flavour if boiled too\n" \ - "-much. Just before you take it up, brown it in the following manner: put\n" \ - "-a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ - "-on the fire and stir it till it melts and looks very dark, pour into it\n" \ - "-a ladle full of the soup, a little at a time; stirring it all the while.\n" \ - "-Strain this browning and mix it well with the soup; take out the bundle\n" \ - "-of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ - "-pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ - "-and serve it up.\n" \ - "+Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "+of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "+fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "+add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "+pulp them through a sieve, and strain the water to it, which must be put\n" \ - "+back in the pot; put into it a chicken cut up, with the tops of\n" \ - "+asparagus which had been laid by, boil it until these last articles are\n" \ - "+sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/notbeef.txt b/notbeef.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..68f6182\n" \ - "--- /dev/null\n" \ - "+++ b/notbeef.txt\n" \ - "@@ -0,0 +1,22 @@\n" \ - "+BEEF SOUP.\n" \ - "+\n" \ - "+Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - "+which must be taken away entirely, or the soup will be greasy. Wash the\n" \ - "+meat clean and lay it in a pot, sprinkle over it one small\n" \ - "+table-spoonful of pounded black pepper, and two of salt; three onions\n" \ - "+the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ - "+up, two small turnips pared and cut into dice; pour on three quarts of\n" \ - "+water, cover the pot close, and keep it gently and steadily boiling five\n" \ - "+hours, which will leave about three pints of clear soup; do not let the\n" \ - "+pot boil over, but take off the scum carefully, as it rises. When it has\n" \ - "+boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ - "+pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ - "+These latter ingredients would lose their delicate flavour if boiled too\n" \ - "+much. Just before you take it up, brown it in the following manner: put\n" \ - "+a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ - "+on the fire and stir it till it melts and looks very dark, pour into it\n" \ - "+a ladle full of the soup, a little at a time; stirring it all the while.\n" \ - "+Strain this browning and mix it well with the soup; take out the bundle\n" \ - "+of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ - "+pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ - "+and serve it up.\n" - -#define DIFF_RENAME_A_TO_B_TO_C_EXACT \ - "diff --git a/asparagus.txt b/beef.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to beef.txt\n" \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to notbeef.txt\n" - -#define DIFF_RENAME_CIRCULAR \ - "diff --git a/asparagus.txt b/beef.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to beef.txt\n" \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to asparagus.txt\n" - -#define DIFF_RENAME_2_TO_1 \ - "diff --git a/asparagus.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to 2.txt\n" \ - "diff --git a/beef.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to 2.txt\n" - -#define DIFF_RENAME_1_TO_2 \ - "diff --git a/asparagus.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to 1.txt\n" \ - "diff --git a/asparagus.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to 2.txt\n" - -#define DIFF_TWO_DELTAS_ONE_FILE \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 68f6182..235069d 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-BEEF SOUP.\n" \ - "+BEEF SOUP!\n" \ - "\n" \ - " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - " which must be taken away entirely, or the soup will be greasy. Wash the\n" \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 68f6182..e059eb5 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -19,4 +19,4 @@ a ladle full of the soup, a little at a time; stirring it all the while.\n" \ - " Strain this browning and mix it well with the soup; take out the bundle\n" \ - " of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ - " pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ - "-and serve it up.\n" \ - "+and serve it up!\n" - -#define DIFF_TWO_DELTAS_ONE_NEW_FILE \ - "diff --git a/newfile.txt b/newfile.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..6434b13\n" \ - "--- /dev/null\n" \ - "+++ b/newfile.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+This is a new file.\n" \ - "diff --git a/newfile.txt b/newfile.txt\n" \ - "index 6434b13..08d4c44 100644\n" \ - "--- a/newfile.txt\n" \ - "+++ b/newfile.txt\n" \ - "@@ -1 +1,3 @@\n" \ - " This is a new file.\n" \ - "+\n" \ - "+This is another change to a new file.\n" - -#define DIFF_RENAME_AND_MODIFY_DELTAS \ - "diff --git a/veal.txt b/asdf.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to asdf.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/asdf.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" \ - "diff --git a/asdf.txt b/asdf.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/asdf.txt\n" \ - "+++ b/asdf.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" - -#define DIFF_RENAME_AFTER_MODIFY \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "diff --git a/veal.txt b/other.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to other.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/other.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" - -#define DIFF_RENAME_AFTER_MODIFY_TARGET_PATH \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "diff --git a/veal.txt b/beef.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to beef.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" - -#define DIFF_RENAME_AND_MODIFY_SOURCE_PATH \ - "diff --git a/veal.txt b/asdf.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to asdf.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/asdf.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" - -#define DIFF_DELETE_AND_READD_FILE \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..2dc7f8b\n" \ - "--- /dev/null\n" \ - "+++ b/asparagus.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+New file.\n" \ - -#define DIFF_REMOVE_FILE_TWICE \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" - -#define DIFF_ADD_INVALID_FILENAME \ - "diff --git a/.git/hello_world.txt b/.git/hello_world.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..f75ba05\n" \ - "--- /dev/null\n" \ - "+++ b/.git/hello_world.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+Hello, world.\n" - -void validate_apply_workdir( - git_repository *repo, - struct merge_index_entry *workdir_entries, - size_t workdir_cnt); - -void validate_apply_index( - git_repository *repo, - struct merge_index_entry *index_entries, - size_t index_cnt); - -void validate_index_unchanged(git_repository *repo); -void validate_workdir_unchanged(git_repository *repo); diff --git a/tests/apply/both.c b/tests/apply/both.c deleted file mode 100644 index 108963270..000000000 --- a/tests/apply/both.c +++ /dev/null @@ -1,747 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_both__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_both__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_both__generated_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; - - struct merge_index_entry both_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - 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)); - - 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, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - 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_both__parsed_diff(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - 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_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__removes_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, - strlen(DIFF_DELETE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__adds_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_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" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__application_failure_leaves_index_unmodified(void) -{ - git_diff *diff; - git_index *index; - - 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); - - 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_BOTH, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_both__index_must_match_workdir(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - /* - * Append a line to the end of the file in both the index and the - * working directory. Although the appended line would allow for - * patch application in each, the line appended is different in - * each, so the application should not be allowed. - */ - cl_git_append2file("merge-recursive/asparagus.txt", - "This is a modification.\n"); - - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "asparagus.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_write(index)); - git_index_free(index); - - 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_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__index_mode_must_match_workdir(void) -{ - git_diff *diff; - - if (!cl_is_chmod_supported()) - clar__skip(); - - /* Set a file in the working directory executable. */ - cl_must_pass(p_chmod("merge-recursive/asparagus.txt", 0755)); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_MODIFY_TWO_FILES, - strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__application_failure_leaves_workdir_unmodified(void) -{ - git_diff *diff; - git_index *index; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry workdir_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" }, - { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the workdir */ - cl_git_rewritefile("merge-recursive/veal.txt", - "This is a modification.\n"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "veal.txt")); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - 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_BOTH, NULL)); - - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__keeps_nonconflicting_changes(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index */ - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "beef.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - /* and mutate the working directory */ - cl_git_rmfile("merge-recursive/oyster.txt"); - cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__can_apply_nonconflicting_file_changes(void) -{ - git_diff *diff; - git_index *index; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry both_expected[] = { - { 0100644, "f8a701c8a1a22c1729ee50faff1111f2d64f96fc", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - /* - * Replace the workdir file with a version that is different than - * HEAD but such that the patch still applies cleanly. This item - * has a new line appended. - */ - cl_git_append2file("merge-recursive/asparagus.txt", - "This line is added in the index and the workdir.\n"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "asparagus.txt")); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__honors_crlf_attributes(void) -{ - git_diff *diff; - git_oid oid; - git_commit *commit; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "176a458f94e0ea5272ce67c36bf30b6be9caf623", 0, ".gitattributes" }, - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_mkfile("merge-recursive/.gitattributes", "* text=auto\n"); - - cl_git_rmfile("merge-recursive/asparagus.txt"); - cl_git_rmfile("merge-recursive/veal.txt"); - - 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); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_FILE, - strlen(DIFF_RENAME_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_and_modify(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "6fa10147f00fe1fab1d5e835529a9dad53db8552", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_FILE, - strlen(DIFF_RENAME_AND_MODIFY_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_a_to_b_to_c(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_A_TO_B_TO_C, - strlen(DIFF_RENAME_A_TO_B_TO_C))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_a_to_b_to_c_exact(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_A_TO_B_TO_C_EXACT, - strlen(DIFF_RENAME_A_TO_B_TO_C_EXACT))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_circular(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "asparagus.txt" }, - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_CIRCULAR, - strlen(DIFF_RENAME_CIRCULAR))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_2_to_1(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "2.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_2_TO_1, - strlen(DIFF_RENAME_2_TO_1))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_1_to_2(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "1.txt" }, - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "2.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_1_TO_2, - strlen(DIFF_RENAME_1_TO_2))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__two_deltas_one_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "0a9fd4415635e72573f0f6b5e68084cfe18f5075", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_FILE, - strlen(DIFF_TWO_DELTAS_ONE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__two_deltas_one_new_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "08d4c445cf0078f3d9b604b82f32f4d87e083325", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_NEW_FILE, - strlen(DIFF_TWO_DELTAS_ONE_NEW_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_and_modify_deltas(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "61c686bed39684eee8a2757ceb1291004a21333f", 0, "asdf.txt" }, - { 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 both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_DELTAS, - strlen(DIFF_RENAME_AND_MODIFY_DELTAS))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_delta_after_modify_delta(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "292cb60ce5e25c337c5b6e12957bbbfe1be4bf49", 0, "other.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "c8c120f466591bbe3b8867361d5ec3cdd9fda756", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY, - strlen(DIFF_RENAME_AFTER_MODIFY))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__cant_rename_after_modify_nonexistent_target_path(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY_TARGET_PATH, - strlen(DIFF_RENAME_AFTER_MODIFY_TARGET_PATH))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__cant_modify_source_path_after_rename(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_SOURCE_PATH, - strlen(DIFF_RENAME_AND_MODIFY_SOURCE_PATH))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__readd_deleted_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "2dc7f8b24ba27f3888368bd180df03ff4c6c6fab", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_AND_READD_FILE, - strlen(DIFF_DELETE_AND_READD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__cant_remove_file_twice(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_REMOVE_FILE_TWICE, - strlen(DIFF_REMOVE_FILE_TWICE))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__cant_add_invalid_filename(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_ADD_INVALID_FILENAME, - strlen(DIFF_ADD_INVALID_FILENAME))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} diff --git a/tests/apply/callbacks.c b/tests/apply/callbacks.c deleted file mode 100644 index 1b759dc9b..000000000 --- a/tests/apply/callbacks.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_callbacks__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_callbacks__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int delta_abort_cb(const git_diff_delta *delta, void *payload) -{ - GIT_UNUSED(payload); - - if (!strcmp(delta->old_file.path, "veal.txt")) - return -99; - - return 0; -} - -void test_apply_callbacks__delta_aborts(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - opts.delta_cb = delta_abort_cb; - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_fail_with(-99, - git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); - - validate_index_unchanged(repo); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -static int delta_skip_cb(const git_diff_delta *delta, void *payload) -{ - GIT_UNUSED(payload); - - if (!strcmp(delta->old_file.path, "asparagus.txt")) - return 1; - - return 0; -} - -void test_apply_callbacks__delta_can_skip(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - struct merge_index_entry workdir_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" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - opts.delta_cb = delta_skip_cb; - - 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_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -static int hunk_skip_odds_cb(const git_diff_hunk *hunk, void *payload) -{ - int *count = (int *)payload; - GIT_UNUSED(hunk); - - return ((*count)++ % 2 == 1); -} - -void test_apply_callbacks__hunk_can_skip(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - int count = 0; - - struct merge_index_entry workdir_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" }, - { 0100644, "06f751b6ba4f017ddbf4248015768300268e092a", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - opts.hunk_cb = hunk_skip_odds_cb; - opts.payload = &count; - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} diff --git a/tests/apply/check.c b/tests/apply/check.c deleted file mode 100644 index 9e42365ed..000000000 --- a/tests/apply/check.c +++ /dev/null @@ -1,121 +0,0 @@ -#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 deleted file mode 100644 index 9da6ac923..000000000 --- a/tests/apply/fromdiff.c +++ /dev/null @@ -1,378 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "apply.h" -#include "repository.h" - -#include "../patch/patch_common.h" - -static git_repository *repo = NULL; -static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT; - -void test_apply_fromdiff__initialize(void) -{ - repo = cl_git_sandbox_init("renames"); - - binary_opts.flags |= GIT_DIFF_SHOW_BINARY; -} - -void test_apply_fromdiff__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int apply_gitbuf( - const git_str *old, - const char *oldname, - const git_str *new, - const char *newname, - const char *patch_expected, - const git_diff_options *diff_opts) -{ - git_patch *patch; - git_str result = GIT_STR_INIT; - git_buf patchbuf = GIT_BUF_INIT; - char *filename; - unsigned int mode; - int error; - - cl_git_pass(git_patch_from_buffers(&patch, - old ? old->ptr : NULL, old ? old->size : 0, - oldname, - new ? new->ptr : NULL, new ? new->size : 0, - newname, - diff_opts)); - - if (patch_expected) { - cl_git_pass(git_patch_to_buf(&patchbuf, patch)); - cl_assert_equal_s(patch_expected, patchbuf.ptr); - } - - error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch, NULL); - - if (error == 0 && new == NULL) { - cl_assert_equal_i(0, result.size); - cl_assert_equal_p(NULL, filename); - cl_assert_equal_i(0, mode); - } - else if (error == 0) { - cl_assert_equal_s(new->ptr, result.ptr); - cl_assert_equal_s(newname ? newname : oldname, filename); - cl_assert_equal_i(0100644, mode); - } - - git__free(filename); - git_str_dispose(&result); - git_buf_dispose(&patchbuf); - git_patch_free(patch); - - return error; -} - -static int apply_buf( - const char *old, - const char *oldname, - const char *new, - const char *newname, - const char *patch_expected, - const git_diff_options *diff_opts) -{ - git_str o = GIT_STR_INIT, n = GIT_STR_INIT, - *optr = NULL, *nptr = NULL; - - if (old) { - o.ptr = (char *)old; - o.size = strlen(old); - optr = &o; - } - - if (new) { - n.ptr = (char *)new; - n.size = strlen(new); - nptr = &n; - } - - return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts); -} - -void test_apply_fromdiff__change_middle(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_MIDDLE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL)); -} - -void test_apply_fromdiff__change_middle_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, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__change_firstline(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_FIRSTLINE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL)); -} - -void test_apply_fromdiff__lastline(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_LASTLINE, "file.txt", - 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( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND, "file.txt", - PATCH_ORIGINAL_TO_PREPEND, NULL)); -} - -void test_apply_fromdiff__prepend_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_PREPEND, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__prepend_and_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE, NULL)); -} - -void test_apply_fromdiff__prepend_and_change_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_PREPEND_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__delete_and_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_DELETE_AND_CHANGE, NULL)); -} - -void test_apply_fromdiff__delete_and_change_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_DELETE_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__delete_firstline(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_FIRSTLINE, "file.txt", - PATCH_ORIGINAL_TO_DELETE_FIRSTLINE, NULL)); -} - -void test_apply_fromdiff__append(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_APPEND, "file.txt", - PATCH_ORIGINAL_TO_APPEND, NULL)); -} - -void test_apply_fromdiff__append_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_APPEND, "file.txt", - PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__prepend_and_append(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_APPEND, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL)); -} - -void test_apply_fromdiff__to_empty_file(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - "", NULL, - PATCH_ORIGINAL_TO_EMPTY_FILE, NULL)); -} - -void test_apply_fromdiff__from_empty_file(void) -{ - cl_git_pass(apply_buf( - "", NULL, - FILE_ORIGINAL, "file.txt", - PATCH_EMPTY_FILE_TO_ORIGINAL, NULL)); -} - -void test_apply_fromdiff__add(void) -{ - cl_git_pass(apply_buf( - NULL, NULL, - FILE_ORIGINAL, "file.txt", - PATCH_ADD_ORIGINAL, NULL)); -} - -void test_apply_fromdiff__delete(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - NULL, NULL, - PATCH_DELETE_ORIGINAL, NULL)); -} - -void test_apply_fromdiff__no_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_ORIGINAL, "file.txt", - "", NULL)); -} - -void test_apply_fromdiff__binary_add(void) -{ - git_str newfile = GIT_STR_INIT; - - newfile.ptr = FILE_BINARY_DELTA_MODIFIED; - newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - NULL, NULL, - &newfile, "binary.bin", - NULL, &binary_opts)); -} - -void test_apply_fromdiff__binary_no_change(void) -{ - git_str original = GIT_STR_INIT; - - original.ptr = FILE_BINARY_DELTA_ORIGINAL; - original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - &original, "binary.bin", - "", &binary_opts)); -} - -void test_apply_fromdiff__binary_change_delta(void) -{ - git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; - - original.ptr = FILE_BINARY_DELTA_ORIGINAL; - original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; - - modified.ptr = FILE_BINARY_DELTA_MODIFIED; - modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - &modified, "binary.bin", - NULL, &binary_opts)); -} - -void test_apply_fromdiff__binary_change_literal(void) -{ - git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; - - original.ptr = FILE_BINARY_LITERAL_ORIGINAL; - original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN; - - modified.ptr = FILE_BINARY_LITERAL_MODIFIED; - modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - &modified, "binary.bin", - NULL, &binary_opts)); -} - -void test_apply_fromdiff__binary_delete(void) -{ - git_str original = GIT_STR_INIT; - - original.ptr = FILE_BINARY_DELTA_MODIFIED; - original.size = FILE_BINARY_DELTA_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - NULL, NULL, - NULL, &binary_opts)); -} - -void test_apply_fromdiff__patching_correctly_truncates_source(void) -{ - git_str original = GIT_STR_INIT, patched = GIT_STR_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_str_dispose(&original); - git_str_dispose(&patched); - git_patch_free(patch); - git__free(path); -} diff --git a/tests/apply/fromfile.c b/tests/apply/fromfile.c deleted file mode 100644 index 8cde62392..000000000 --- a/tests/apply/fromfile.c +++ /dev/null @@ -1,449 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "apply.h" -#include "patch.h" -#include "patch_parse.h" -#include "repository.h" - -#include "../patch/patch_common.h" - -static git_repository *repo = NULL; - -void test_apply_fromfile__initialize(void) -{ - repo = cl_git_sandbox_init("renames"); -} - -void test_apply_fromfile__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int apply_patchfile( - const char *old, - size_t old_len, - const char *new, - size_t new_len, - const char *patchfile, - const char *filename_expected, - unsigned int mode_expected) -{ - git_patch *patch; - git_str result = GIT_STR_INIT; - git_str patchbuf = GIT_STR_INIT; - char *filename; - unsigned int mode; - int error; - - cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL)); - - error = git_apply__patch(&result, &filename, &mode, old, old_len, patch, NULL); - - if (error == 0) { - cl_assert_equal_i(new_len, result.size); - if (new_len) - cl_assert(memcmp(new, result.ptr, new_len) == 0); - - cl_assert_equal_s(filename_expected, filename); - cl_assert_equal_i(mode_expected, mode); - } - - git__free(filename); - git_str_dispose(&result); - git_str_dispose(&patchbuf); - git_patch_free(patch); - - return error; -} - -static int validate_and_apply_patchfile( - const char *old, - size_t old_len, - const char *new, - size_t new_len, - const char *patchfile, - const git_diff_options *diff_opts, - const char *filename_expected, - unsigned int mode_expected) -{ - git_patch *patch_fromdiff; - git_buf validated = GIT_BUF_INIT; - int error; - - cl_git_pass(git_patch_from_buffers(&patch_fromdiff, - old, old_len, "file.txt", - new, new_len, "file.txt", - diff_opts)); - cl_git_pass(git_patch_to_buf(&validated, patch_fromdiff)); - - cl_assert_equal_s(patchfile, validated.ptr); - - error = apply_patchfile(old, old_len, new, new_len, patchfile, filename_expected, mode_expected); - - git_buf_dispose(&validated); - git_patch_free(patch_fromdiff); - - return error; -} - -void test_apply_fromfile__change_middle(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, - &diff_opts, "file.txt", 0100644)); -} - - -void test_apply_fromfile__change_firstline(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_FIRSTLINE, strlen(FILE_CHANGE_FIRSTLINE), - PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__lastline(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_LASTLINE, strlen(FILE_CHANGE_LASTLINE), - PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_shrink(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_shrink_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), - PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_grow(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_grow_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), - PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__prepend(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_PREPEND, strlen(FILE_PREPEND), - PATCH_ORIGINAL_TO_PREPEND, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__prepend_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_PREPEND, strlen(FILE_PREPEND), - PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__append(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_APPEND, strlen(FILE_APPEND), - PATCH_ORIGINAL_TO_APPEND, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__append_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_APPEND, strlen(FILE_APPEND), - PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__prepend_and_append(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_PREPEND_AND_APPEND, strlen(FILE_PREPEND_AND_APPEND), - PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__to_empty_file(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - "", 0, - PATCH_ORIGINAL_TO_EMPTY_FILE, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__from_empty_file(void) -{ - cl_git_pass(validate_and_apply_patchfile( - "", 0, - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_EMPTY_FILE_TO_ORIGINAL, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__add(void) -{ - cl_git_pass(validate_and_apply_patchfile( - NULL, 0, - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_ADD_ORIGINAL, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__delete(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - NULL, 0, - PATCH_DELETE_ORIGINAL, NULL, NULL, 0)); -} - - -void test_apply_fromfile__rename_exact(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_RENAME_EXACT, "newfile.txt", 0100644)); -} - -void test_apply_fromfile__rename_similar(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_RENAME_SIMILAR, "newfile.txt", 0100644)); -} - -void test_apply_fromfile__rename_similar_quotedname(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_RENAME_SIMILAR_QUOTEDNAME, "foo\"bar.txt", 0100644)); -} - -void test_apply_fromfile__modechange(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_MODECHANGE_UNCHANGED, "file.txt", 0100755)); -} - -void test_apply_fromfile__modechange_with_modification(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_MODECHANGE_MODIFIED, "file.txt", 0100755)); -} - -void test_apply_fromfile__noisy(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_NOISY, "file.txt", 0100644)); -} - -void test_apply_fromfile__noisy_nocontext(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_NOISY_NOCONTEXT, "file.txt", 0100644)); -} - -void test_apply_fromfile__fail_truncated_1(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_1, - strlen(PATCH_TRUNCATED_1), NULL)); -} - -void test_apply_fromfile__fail_truncated_2(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_2, - strlen(PATCH_TRUNCATED_2), NULL)); -} - -void test_apply_fromfile__fail_truncated_3(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_3, - strlen(PATCH_TRUNCATED_3), NULL)); -} - -void test_apply_fromfile__fail_corrupt_githeader(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, - strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); -} - -void test_apply_fromfile__empty_context(void) -{ - cl_git_pass(apply_patchfile( - FILE_EMPTY_CONTEXT_ORIGINAL, strlen(FILE_EMPTY_CONTEXT_ORIGINAL), - FILE_EMPTY_CONTEXT_MODIFIED, strlen(FILE_EMPTY_CONTEXT_MODIFIED), - PATCH_EMPTY_CONTEXT, - "file.txt", 0100644)); -} - -void test_apply_fromfile__append_no_nl(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_APPEND_NO_NL, strlen(FILE_APPEND_NO_NL), - PATCH_APPEND_NO_NL, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__fail_missing_new_file(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_NEW_FILE, - strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); -} - -void test_apply_fromfile__fail_missing_old_file(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_OLD_FILE, - strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); -} - -void test_apply_fromfile__fail_no_changes(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_NO_CHANGES, - strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); -} - -void test_apply_fromfile__fail_missing_hunk_header(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_HUNK_HEADER, - strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); -} - -void test_apply_fromfile__fail_not_a_patch(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, - strlen(PATCH_NOT_A_PATCH), NULL)); -} - -void test_apply_fromfile__binary_add(void) -{ - cl_git_pass(apply_patchfile( - NULL, 0, - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - PATCH_BINARY_ADD, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_change_delta(void) -{ - cl_git_pass(apply_patchfile( - FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - PATCH_BINARY_DELTA, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_change_literal(void) -{ - cl_git_pass(apply_patchfile( - FILE_BINARY_LITERAL_ORIGINAL, FILE_BINARY_LITERAL_ORIGINAL_LEN, - FILE_BINARY_LITERAL_MODIFIED, FILE_BINARY_LITERAL_MODIFIED_LEN, - PATCH_BINARY_LITERAL, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_delete(void) -{ - cl_git_pass(apply_patchfile( - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - NULL, 0, - PATCH_BINARY_DELETE, NULL, 0)); -} - -void test_apply_fromfile__binary_change_does_not_apply(void) -{ - /* try to apply patch backwards, ensure it does not apply */ - cl_git_fail(apply_patchfile( - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, - PATCH_BINARY_DELTA, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_change_must_be_reversible(void) -{ - cl_git_fail(apply_patchfile( - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - NULL, 0, - PATCH_BINARY_NOT_REVERSIBLE, NULL, 0)); -} - -void test_apply_fromfile__empty_file_not_allowed(void) -{ - git_patch *patch; - - cl_git_fail(git_patch_from_buffer(&patch, "", 0, NULL)); - cl_git_fail(git_patch_from_buffer(&patch, NULL, 0, NULL)); -} diff --git a/tests/apply/index.c b/tests/apply/index.c deleted file mode 100644 index 9c9094cce..000000000 --- a/tests/apply/index.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_index__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_index__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_index__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; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - 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)); - - 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_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - 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_index__parsed_diff(void) -{ - git_diff *diff; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - 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, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__removes_file(void) -{ - git_diff *diff; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, - strlen(DIFF_DELETE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__adds_file(void) -{ - git_diff *diff; - - 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, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__modified_workdir_with_unmodified_index_is_ok(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "veal.txt" } - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the workdir and leave the index matching HEAD */ - cl_git_rmfile("merge-recursive/asparagus.txt"); - cl_git_rewritefile("merge-recursive/veal.txt", "Hello, world.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_index__application_failure_leaves_index_unmodified(void) -{ - git_diff *diff; - git_index *index; - - 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); - - 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, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_index__keeps_nonconflicting_changes(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.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)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "beef.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__can_apply_nonconflicting_file_changes(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "4f2d1645dee99ced096877911de540c65ade2ef8", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - /* - * Replace the index entry with a version that is different than - * HEAD but such that the patch still applies cleanly. This item - * has a new line appended. - */ - - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "asparagus.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__change_mode(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_EXECUTABLE_FILE; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} diff --git a/tests/apply/partial.c b/tests/apply/partial.c deleted file mode 100644 index fd4908d4b..000000000 --- a/tests/apply/partial.c +++ /dev/null @@ -1,231 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "apply.h" -#include "repository.h" - -#include "../patch/patch_common.h" - -static git_repository *repo = NULL; - -void test_apply_partial__initialize(void) -{ - repo = cl_git_sandbox_init("renames"); -} - -void test_apply_partial__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int skip_addition( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines > hunk->old_lines) ? 1 : 0; -} - -static int skip_deletion( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines < hunk->old_lines) ? 1 : 0; -} - -static int skip_change( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines == hunk->old_lines) ? 1 : 0; -} - -static int abort_addition( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines > hunk->old_lines) ? GIT_EUSER : 0; -} - -static int abort_deletion( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines < hunk->old_lines) ? GIT_EUSER : 0; -} - -static int abort_change( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines == hunk->old_lines) ? GIT_EUSER : 0; -} - -static int apply_buf( - const char *old, - const char *oldname, - const char *new, - const char *newname, - const char *expected, - const git_diff_options *diff_opts, - git_apply_hunk_cb hunk_cb, - void *payload) -{ - git_patch *patch; - git_str result = GIT_STR_INIT; - git_str patchbuf = GIT_STR_INIT; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - char *filename; - unsigned int mode; - int error; - size_t oldsize = strlen(old); - size_t newsize = strlen(new); - - opts.hunk_cb = hunk_cb; - opts.payload = payload; - - cl_git_pass(git_patch_from_buffers(&patch, old, oldsize, oldname, new, newsize, newname, diff_opts)); - if ((error = git_apply__patch(&result, &filename, &mode, old, oldsize, patch, &opts)) == 0) { - cl_assert_equal_s(expected, result.ptr); - cl_assert_equal_s(newname, filename); - cl_assert_equal_i(0100644, mode); - } - - git__free(filename); - git_str_dispose(&result); - git_str_dispose(&patchbuf); - git_patch_free(patch); - - return error; -} - -void test_apply_partial__prepend_and_change_skip_addition(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_ORIGINAL, NULL, skip_addition, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_skip_addition(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_PREPEND_AND_CHANGE, "file.txt", - FILE_CHANGE_MIDDLE, &diff_opts, skip_addition, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_abort_addition(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_ORIGINAL, &diff_opts, abort_addition, NULL)); -} - -void test_apply_partial__prepend_and_change_skip_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_PREPEND_AND_CHANGE, NULL, skip_change, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_skip_change(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_PREPEND_AND_CHANGE, "file.txt", - FILE_PREPEND, &diff_opts, skip_change, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_abort_change(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_PREPEND, &diff_opts, abort_change, NULL)); -} - -void test_apply_partial__delete_and_change_skip_deletion(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_ORIGINAL, NULL, skip_deletion, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_skip_deletion(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_DELETE_AND_CHANGE, "file.txt", - FILE_CHANGE_MIDDLE, &diff_opts, skip_deletion, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_abort_deletion(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_ORIGINAL, &diff_opts, abort_deletion, NULL)); -} - -void test_apply_partial__delete_and_change_skip_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_DELETE_AND_CHANGE, NULL, skip_change, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_skip_change(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_DELETE_AND_CHANGE, "file.txt", - FILE_DELETE_FIRSTLINE, &diff_opts, skip_change, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_abort_change(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_DELETE_FIRSTLINE, &diff_opts, abort_change, NULL)); -} diff --git a/tests/apply/tree.c b/tests/apply/tree.c deleted file mode 100644 index 5154f134f..000000000 --- a/tests/apply/tree.c +++ /dev/null @@ -1,94 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" -#include "../merge/merge_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - - -void test_apply_tree__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_apply_tree__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_tree__one(void) -{ - git_oid a_oid, b_oid; - git_commit *a_commit, *b_commit; - git_tree *a_tree, *b_tree; - git_diff *diff; - git_index *index = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - - struct merge_index_entry expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - 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)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); - - cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); - merge_test_index(index, expected, 6); - - git_index_free(index); - 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_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/apply/workdir.c b/tests/apply/workdir.c deleted file mode 100644 index d0d9c1aba..000000000 --- a/tests/apply/workdir.c +++ /dev/null @@ -1,358 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_workdir__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_workdir__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_workdir__generated_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 opts = GIT_DIFF_OPTIONS_INIT; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - 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)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - 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_workdir__parsed_diff(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - 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_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__removes_file(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, - strlen(DIFF_DELETE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__adds_file(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_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" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__modified_index_with_unmodified_workdir_is_ok(void) -{ - git_index *index; - git_index_entry idx_entry = {{0}}; - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "veal.txt" } - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index and leave the workdir matching HEAD */ - cl_git_pass(git_repository_index(&index, repo)); - - idx_entry.mode = 0100644; - idx_entry.path = "veal.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d")); - - cl_git_pass(git_index_add(index, &idx_entry)); - cl_git_pass(git_index_remove(index, "asparagus.txt", 0)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_index_free(index); - git_diff_free(diff); -} - -void test_apply_workdir__application_failure_leaves_workdir_unmodified(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry workdir_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" }, - { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the workdir */ - cl_git_rewritefile("merge-recursive/veal.txt", - "This is a modification.\n"); - - 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_WORKDIR, NULL)); - - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__keeps_nonconflicting_changes(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_rmfile("merge-recursive/oyster.txt"); - cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); - - 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_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__can_apply_nonconflicting_file_changes(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "5db1a0fef164cb66cc0c00d35cc5af979ddc1a64", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* - * Replace the workdir file with a version that is different than - * HEAD but such that the patch still applies cleanly. This item - * has a new line appended. - */ - cl_git_append2file("merge-recursive/asparagus.txt", - "This line is added in the workdir.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__change_mode(void) -{ -#ifndef GIT_WIN32 - git_diff *diff; - - const char *diff_file = DIFF_EXECUTABLE_FILE; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -#endif -} - -void test_apply_workdir__apply_many_changes_one(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - struct merge_index_entry workdir_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" }, - { 0100644, "c9d7d5d58088bc91f6e06f17ca3a205091568d3a", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__apply_many_changes_two(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - struct merge_index_entry workdir_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" }, - { 0100644, "6b943d65af6d8db74d747284fa4ca7d716ad5bbb", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MANY_CHANGES_TWO, strlen(DIFF_MANY_CHANGES_TWO))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} diff --git a/tests/attr/attr_expect.h b/tests/attr/attr_expect.h deleted file mode 100644 index faf7a1181..000000000 --- a/tests/attr/attr_expect.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef __CLAR_TEST_ATTR_EXPECT__ -#define __CLAR_TEST_ATTR_EXPECT__ - -enum attr_expect_t { - EXPECT_FALSE, - EXPECT_TRUE, - EXPECT_UNDEFINED, - EXPECT_STRING -}; - -struct attr_expected { - const char *path; - const char *attr; - enum attr_expect_t expected; - const char *expected_str; -}; - -GIT_INLINE(void) attr_check_expected( - enum attr_expect_t expected, - const char *expected_str, - const char *name, - const char *value) -{ - switch (expected) { - case EXPECT_TRUE: - cl_assert_(GIT_ATTR_IS_TRUE(value), name); - break; - - case EXPECT_FALSE: - cl_assert_(GIT_ATTR_IS_FALSE(value), name); - break; - - case EXPECT_UNDEFINED: - cl_assert_(GIT_ATTR_IS_UNSPECIFIED(value), name); - break; - - case EXPECT_STRING: - cl_assert_equal_s(expected_str, value); - break; - } -} - -#endif diff --git a/tests/attr/file.c b/tests/attr/file.c deleted file mode 100644 index 1ac48c0fe..000000000 --- a/tests/attr/file.c +++ /dev/null @@ -1,243 +0,0 @@ -#include "clar_libgit2.h" -#include "attr_file.h" -#include "attr_expect.h" - -#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X))) -#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y))) - -void test_attr_file__simple_read(void) -{ - git_attr_file *file; - git_attr_assignment *assign; - git_attr_rule *rule; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0"))); - - cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path); - cl_assert(file->rules.length == 1); - - rule = get_rule(0); - cl_assert(rule != NULL); - cl_assert_equal_s("*", rule->match.pattern); - cl_assert(rule->match.length == 1); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - - cl_assert(rule->assigns.length == 1); - assign = get_assign(rule, 0); - cl_assert(assign != NULL); - cl_assert_equal_s("binary", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - - git_attr_file__free(file); -} - -void test_attr_file__match_variants(void) -{ - git_attr_file *file; - git_attr_rule *rule; - git_attr_assignment *assign; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1"))); - - cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path); - cl_assert(file->rules.length == 10); - - /* let's do a thorough check of this rule, then just verify - * the things that are unique for the later rules - */ - rule = get_rule(0); - cl_assert(rule); - cl_assert_equal_s("pat0", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat0")); - cl_assert(rule->assigns.length == 1); - 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_IS_TRUE(assign->value)); - - rule = get_rule(1); - cl_assert_equal_s("pat1", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat1")); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0); - - rule = get_rule(2); - cl_assert_equal_s("pat2", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat2")); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0); - - rule = get_rule(3); - cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) != 0); - - rule = get_rule(4); - cl_assert_equal_s("pat4.*", rule->match.pattern); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - - rule = get_rule(5); - cl_assert_equal_s("*.pat5", rule->match.pattern); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - - rule = get_rule(7); - cl_assert_equal_s("pat7[a-e]??[xyz]", rule->match.pattern); - cl_assert(rule->assigns.length == 1); - 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_IS_TRUE(assign->value)); - - rule = get_rule(8); - cl_assert_equal_s("pat8 with spaces", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat8 with spaces")); - - rule = get_rule(9); - cl_assert_equal_s("pat9", rule->match.pattern); - - git_attr_file__free(file); -} - -static void check_one_assign( - git_attr_file *file, - int rule_idx, - int assign_idx, - const char *pattern, - const char *name, - enum attr_expect_t expected, - const char *expected_str) -{ - git_attr_rule *rule = get_rule(rule_idx); - git_attr_assignment *assign = get_assign(rule, assign_idx); - - cl_assert_equal_s(pattern, rule->match.pattern); - cl_assert(rule->assigns.length == 1); - cl_assert_equal_s(name, assign->name); - cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); - - attr_check_expected(expected, expected_str, assign->name, assign->value); -} - -void test_attr_file__assign_variants(void) -{ - git_attr_file *file; - git_attr_rule *rule; - git_attr_assignment *assign; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2"))); - - cl_assert_equal_s(cl_fixture("attr/attr2"), file->entry->path); - cl_assert(file->rules.length == 11); - - check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL); - check_one_assign(file, 1, 0, "pat1", "neg", EXPECT_FALSE, NULL); - check_one_assign(file, 2, 0, "*", "notundef", EXPECT_TRUE, NULL); - check_one_assign(file, 3, 0, "pat2", "notundef", EXPECT_UNDEFINED, NULL); - check_one_assign(file, 4, 0, "pat3", "assigned", EXPECT_STRING, "test-value"); - check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars"); - check_one_assign(file, 6, 0, "pat5", "empty", EXPECT_TRUE, NULL); - check_one_assign(file, 7, 0, "pat6", "negempty", EXPECT_FALSE, NULL); - - rule = get_rule(8); - cl_assert_equal_s("pat7", rule->match.pattern); - cl_assert(rule->assigns.length == 5); - /* assignments will be sorted by hash value, so we have to do - * lookups by search instead of by position - */ - assign = git_attr_rule__lookup_assignment(rule, "multiple"); - cl_assert(assign); - cl_assert_equal_s("multiple", assign->name); - 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_IS_FALSE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "values"); - cl_assert(assign); - cl_assert_equal_s("values", assign->name); - cl_assert_equal_s("1", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "also"); - cl_assert(assign); - cl_assert_equal_s("also", assign->name); - cl_assert_equal_s("a-really-long-value/*", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "happy"); - cl_assert(assign); - cl_assert_equal_s("happy", assign->name); - cl_assert_equal_s("yes!", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "other"); - cl_assert(!assign); - - rule = get_rule(9); - cl_assert_equal_s("pat8", rule->match.pattern); - cl_assert(rule->assigns.length == 2); - assign = git_attr_rule__lookup_assignment(rule, "again"); - cl_assert(assign); - cl_assert_equal_s("again", assign->name); - 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); - cl_assert_equal_s("12321", assign->value); - - check_one_assign(file, 10, 0, "pat9", "at-eof", EXPECT_FALSE, NULL); - - git_attr_file__free(file); -} - -static void assert_examples(git_attr_file *file) -{ - git_attr_rule *rule; - git_attr_assignment *assign; - - rule = get_rule(0); - cl_assert_equal_s("*.java", rule->match.pattern); - cl_assert(rule->assigns.length == 3); - assign = git_attr_rule__lookup_assignment(rule, "diff"); - cl_assert_equal_s("diff", assign->name); - 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_IS_FALSE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "myAttr"); - cl_assert_equal_s("myAttr", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "missing"); - cl_assert(assign == NULL); - - rule = get_rule(1); - cl_assert_equal_s("NoMyAttr.java", rule->match.pattern); - cl_assert(rule->assigns.length == 1); - assign = get_assign(rule, 0); - cl_assert_equal_s("myAttr", assign->name); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(assign->value)); - - rule = get_rule(2); - cl_assert_equal_s("README", rule->match.pattern); - cl_assert(rule->assigns.length == 1); - 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 deleted file mode 100644 index 6470ec732..000000000 --- a/tests/attr/flags.c +++ /dev/null @@ -1,108 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/attr.h" - -void test_attr_flags__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_attr_flags__bare(void) -{ - git_repository *repo = cl_git_sandbox_init("testrepo.git"); - const char *value; - - cl_assert(git_repository_is_bare(repo)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM, "README.md", "diff")); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); -} - -void test_attr_flags__index_vs_workdir(void) -{ - git_repository *repo = cl_git_sandbox_init("attr_index"); - const char *value; - - cl_assert(!git_repository_is_bare(repo)); - - /* wd then index */ - 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_IS_FALSE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "README.md", "blargh")); - cl_assert_equal_s(value, "goop"); - - 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_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_IS_TRUE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "README.md", "blargh")); - cl_assert_equal_s(value, "garble"); - - 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_IS_TRUE(value)); -} - -void test_attr_flags__subdir(void) -{ - git_repository *repo = cl_git_sandbox_init("attr_index"); - const char *value; - - /* wd then index */ - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.md", "bar")); - cl_assert_equal_s(value, "1234"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.txt", "another")); - cl_assert_equal_s(value, "one"); - - 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_IS_TRUE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.txt", "beep")); - cl_assert_equal_s(value, "10"); - - /* index then wd */ - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.md", "bar")); - cl_assert_equal_s(value, "1337"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.txt", "another")); - cl_assert_equal_s(value, "one"); - - 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_IS_TRUE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.txt", "beep")); - cl_assert_equal_s(value, "5"); -} - diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c deleted file mode 100644 index f19f38fbb..000000000 --- a/tests/attr/lookup.c +++ /dev/null @@ -1,263 +0,0 @@ -#include "clar_libgit2.h" -#include "attr_file.h" - -#include "attr_expect.h" - -void test_attr_lookup__simple(void) -{ - git_attr_file *file; - git_attr_path path; - const char *value = NULL; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0"))); - cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path); - cl_assert(file->rules.length == 1); - - cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN)); - cl_assert_equal_s("test", path.path); - cl_assert_equal_s("test", path.basename); - cl_assert(!path.is_dir); - - cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); - cl_assert(GIT_ATTR_IS_TRUE(value)); - - cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); - cl_assert(!value); - - git_attr_path__free(&path); - git_attr_file__free(file); -} - -static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int force_dir) -{ - git_attr_path path; - const char *value = NULL; - struct attr_expected *c; - int error; - - for (c = cases; c->path != NULL; c++) { - cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN)); - - if (force_dir) - path.is_dir = 1; - - error = git_attr_file__lookup_one(file,&path,c->attr,&value); - cl_git_pass(error); - - attr_check_expected(c->expected, c->expected_str, c->attr, value); - - git_attr_path__free(&path); - } -} - -void test_attr_lookup__match_variants(void) -{ - git_attr_file *file; - git_attr_path path; - - struct attr_expected dir_cases[] = { - { "pat2", "attr2", EXPECT_TRUE, NULL }, - { "/testing/for/pat2", "attr2", EXPECT_TRUE, NULL }, - { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, - { "/fun/fun/fun/pat4.dir", "attr4", EXPECT_TRUE, NULL }, - { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, - { NULL, NULL, 0, NULL } - }; - - struct attr_expected cases[] = { - /* pat0 -> simple match */ - { "pat0", "attr0", EXPECT_TRUE, NULL }, - { "/testing/for/pat0", "attr0", EXPECT_TRUE, NULL }, - { "relative/to/pat0", "attr0", EXPECT_TRUE, NULL }, - { "this-contains-pat0-inside", "attr0", EXPECT_UNDEFINED, NULL }, - { "this-aint-right", "attr0", EXPECT_UNDEFINED, NULL }, - { "/this/pat0/dont/match", "attr0", EXPECT_UNDEFINED, NULL }, - /* negative match */ - { "pat0", "attr1", EXPECT_TRUE, NULL }, - { "pat1", "attr1", EXPECT_UNDEFINED, NULL }, - { "/testing/for/pat1", "attr1", EXPECT_UNDEFINED, NULL }, - { "/testing/for/pat0", "attr1", EXPECT_TRUE, NULL }, - { "/testing/for/pat1/inside", "attr1", EXPECT_TRUE, NULL }, - { "misc", "attr1", EXPECT_TRUE, NULL }, - /* dir match */ - { "pat2", "attr2", EXPECT_UNDEFINED, NULL }, - { "/testing/for/pat2", "attr2", EXPECT_UNDEFINED, NULL }, - { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, - /* path match */ - { "pat3file", "attr3", EXPECT_UNDEFINED, NULL }, - { "/pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, - { "pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, - /* pattern* match */ - { "pat4.txt", "attr4", EXPECT_TRUE, NULL }, - { "/fun/fun/fun/pat4.c", "attr4", EXPECT_TRUE, NULL }, - { "pat4.", "attr4", EXPECT_TRUE, NULL }, - { "pat4", "attr4", EXPECT_UNDEFINED, NULL }, - /* *pattern match */ - { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, - { "/this/is/ok.pat5", "attr5", EXPECT_TRUE, NULL }, - { "/this/is/bad.pat5/yousee.txt", "attr5", EXPECT_UNDEFINED, NULL }, - { "foo.pat5", "attr100", EXPECT_UNDEFINED, NULL }, - /* glob match with slashes */ - { "foo.pat6", "attr6", EXPECT_UNDEFINED, NULL }, - { "pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, - { "pat6/pat6/.pat6", "attr6", EXPECT_TRUE, NULL }, - { "pat6/pat6/extra/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, - { "/prefix/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, - { "/pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, - /* complex pattern */ - { "pat7a12z", "attr7", EXPECT_TRUE, NULL }, - { "pat7e__x", "attr7", EXPECT_TRUE, NULL }, - { "pat7b/1y", "attr7", EXPECT_UNDEFINED, NULL }, /* ? does not match / */ - { "pat7e_x", "attr7", EXPECT_UNDEFINED, NULL }, - { "pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, - { "pat7zzzz", "attr7", EXPECT_UNDEFINED, NULL }, - { "/this/can/be/anything/pat7a12z", "attr7", EXPECT_TRUE, NULL }, - { "but/it/still/must/match/pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, - { "pat7aaay.fail", "attr7", EXPECT_UNDEFINED, NULL }, - /* pattern with spaces */ - { "pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, - { "/gotta love/pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, - { "failing pat8 with spaces", "attr8", EXPECT_UNDEFINED, NULL }, - { "spaces", "attr8", EXPECT_UNDEFINED, NULL }, - /* pattern at eof */ - { "pat9", "attr9", EXPECT_TRUE, NULL }, - { "/eof/pat9", "attr9", EXPECT_TRUE, NULL }, - { "pat", "attr9", EXPECT_UNDEFINED, NULL }, - { "at9", "attr9", EXPECT_UNDEFINED, NULL }, - { "pat9.fail", "attr9", EXPECT_UNDEFINED, NULL }, - /* sentinel at end */ - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1"))); - cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path); - cl_assert(file->rules.length == 10); - - cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN)); - cl_assert_equal_s("pat0", path.basename); - - run_test_cases(file, cases, 0); - run_test_cases(file, dir_cases, 1); - - git_attr_file__free(file); - git_attr_path__free(&path); -} - -void test_attr_lookup__assign_variants(void) -{ - git_attr_file *file; - - struct attr_expected cases[] = { - /* pat0 -> simple assign */ - { "pat0", "simple", EXPECT_TRUE, NULL }, - { "/testing/pat0", "simple", EXPECT_TRUE, NULL }, - { "pat0", "fail", EXPECT_UNDEFINED, NULL }, - { "/testing/pat0", "fail", EXPECT_UNDEFINED, NULL }, - /* negative assign */ - { "pat1", "neg", EXPECT_FALSE, NULL }, - { "/testing/pat1", "neg", EXPECT_FALSE, NULL }, - { "pat1", "fail", EXPECT_UNDEFINED, NULL }, - { "/testing/pat1", "fail", EXPECT_UNDEFINED, NULL }, - /* forced undef */ - { "pat1", "notundef", EXPECT_TRUE, NULL }, - { "pat2", "notundef", EXPECT_UNDEFINED, NULL }, - { "/lead/in/pat1", "notundef", EXPECT_TRUE, NULL }, - { "/lead/in/pat2", "notundef", EXPECT_UNDEFINED, NULL }, - /* assign value */ - { "pat3", "assigned", EXPECT_STRING, "test-value" }, - { "pat3", "notassigned", EXPECT_UNDEFINED, NULL }, - /* assign value */ - { "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars" }, - { "pat4", "notassigned-rule-with-more-chars", EXPECT_UNDEFINED, NULL }, - /* empty assignments */ - { "pat5", "empty", EXPECT_TRUE, NULL }, - { "pat6", "negempty", EXPECT_FALSE, NULL }, - /* multiple assignment */ - { "pat7", "multiple", EXPECT_TRUE, NULL }, - { "pat7", "single", EXPECT_FALSE, NULL }, - { "pat7", "values", EXPECT_STRING, "1" }, - { "pat7", "also", EXPECT_STRING, "a-really-long-value/*" }, - { "pat7", "happy", EXPECT_STRING, "yes!" }, - { "pat8", "again", EXPECT_TRUE, NULL }, - { "pat8", "another", EXPECT_STRING, "12321" }, - /* bad assignment */ - { "patbad0", "simple", EXPECT_UNDEFINED, NULL }, - { "patbad0", "notundef", EXPECT_TRUE, NULL }, - { "patbad1", "simple", EXPECT_UNDEFINED, NULL }, - /* eof assignment */ - { "pat9", "at-eof", EXPECT_FALSE, NULL }, - /* sentinel at end */ - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2"))); - cl_assert(file->rules.length == 11); - - run_test_cases(file, cases, 0); - - git_attr_file__free(file); -} - -void test_attr_lookup__check_attr_examples(void) -{ - git_attr_file *file; - - struct attr_expected cases[] = { - { "foo.java", "diff", EXPECT_STRING, "java" }, - { "foo.java", "crlf", EXPECT_FALSE, NULL }, - { "foo.java", "myAttr", EXPECT_TRUE, NULL }, - { "foo.java", "other", EXPECT_UNDEFINED, NULL }, - { "/prefix/dir/foo.java", "diff", EXPECT_STRING, "java" }, - { "/prefix/dir/foo.java", "crlf", EXPECT_FALSE, NULL }, - { "/prefix/dir/foo.java", "myAttr", EXPECT_TRUE, NULL }, - { "/prefix/dir/foo.java", "other", EXPECT_UNDEFINED, NULL }, - { "NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, - { "NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, - { "NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, - { "/prefix/dir/NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, - { "/prefix/dir/NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, - { "/prefix/dir/NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, - { "README", "caveat", EXPECT_STRING, "unspecified" }, - { "/specific/path/README", "caveat", EXPECT_STRING, "unspecified" }, - { "README", "missing", EXPECT_UNDEFINED, NULL }, - { "/specific/path/README", "missing", EXPECT_UNDEFINED, NULL }, - /* sentinel at end */ - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); - cl_assert(file->rules.length == 3); - - run_test_cases(file, cases, 0); - - git_attr_file__free(file); -} - -void test_attr_lookup__from_buffer(void) -{ - git_attr_file *file; - git_attr_file_source source = {0}; - - struct attr_expected cases[] = { - { "abc", "foo", EXPECT_TRUE, NULL }, - { "abc", "bar", EXPECT_TRUE, NULL }, - { "abc", "baz", EXPECT_TRUE, NULL }, - { "aaa", "foo", EXPECT_TRUE, NULL }, - { "aaa", "bar", EXPECT_UNDEFINED, NULL }, - { "aaa", "baz", EXPECT_TRUE, NULL }, - { "qqq", "foo", EXPECT_UNDEFINED, NULL }, - { "qqq", "bar", EXPECT_UNDEFINED, NULL }, - { "qqq", "baz", EXPECT_TRUE, NULL }, - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__new(&file, NULL, &source)); - - cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", true)); - - cl_assert(file->rules.length == 3); - - run_test_cases(file, cases, 0); - - git_attr_file__free(file); -} diff --git a/tests/attr/macro.c b/tests/attr/macro.c deleted file mode 100644 index 1fbfd137f..000000000 --- a/tests/attr/macro.c +++ /dev/null @@ -1,197 +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 "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 deleted file mode 100644 index abd238154..000000000 --- a/tests/attr/repo.c +++ /dev/null @@ -1,405 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "git2/attr.h" -#include "attr.h" -#include "sysdir.h" - -#include "attr_expect.h" -#include "git2/sys/repository.h" - -static git_repository *g_repo = NULL; - -void test_attr_repo__initialize(void) -{ - g_repo = cl_git_sandbox_init("attr"); -} - -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[] = { - { "root_test1", "repoattr", EXPECT_TRUE, NULL }, - { "root_test1", "rootattr", EXPECT_TRUE, NULL }, - { "root_test1", "missingattr", EXPECT_UNDEFINED, NULL }, - { "root_test1", "subattr", EXPECT_UNDEFINED, NULL }, - { "root_test1", "negattr", EXPECT_UNDEFINED, NULL }, - { "root_test2", "repoattr", EXPECT_TRUE, NULL }, - { "root_test2", "rootattr", EXPECT_FALSE, NULL }, - { "root_test2", "missingattr", EXPECT_UNDEFINED, NULL }, - { "root_test2", "multiattr", EXPECT_FALSE, NULL }, - { "root_test3", "repoattr", EXPECT_TRUE, NULL }, - { "root_test3", "rootattr", EXPECT_UNDEFINED, NULL }, - { "root_test3", "multiattr", EXPECT_STRING, "3" }, - { "root_test3", "multi2", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" }, - { "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL }, - { "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" }, - { "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL }, - { "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" }, - { "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL }, - { "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" }, - { "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL }, - { "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL }, - { "does-not-exist", "foo", EXPECT_STRING, "yes" }, - { "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL }, - { "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" }, - { "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL }, -}; - -void test_attr_repo__get_one(void) -{ - int i; - - for (i = 0; i < (int)ARRAY_SIZE(get_one_test_cases); ++i) { - struct attr_expected *scan = &get_one_test_cases[i]; - const char *value; - - cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); - attr_check_expected( - scan->expected, scan->expected_str, scan->attr, value); - } - - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/attributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitattributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, "sub/.gitattributes")); -} - -void test_attr_repo__get_one_start_deep(void) -{ - int i; - - for (i = (int)ARRAY_SIZE(get_one_test_cases) - 1; i >= 0; --i) { - struct attr_expected *scan = &get_one_test_cases[i]; - const char *value; - - cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); - attr_check_expected( - scan->expected, scan->expected_str, scan->attr, value); - } - - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/attributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitattributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, "sub/.gitattributes")); -} - -void test_attr_repo__get_many(void) -{ - const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; - const char *values[4]; - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test1", 4, names)); - - 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_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_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]); -} - -void test_attr_repo__get_many_in_place(void) -{ - const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; - - /* it should be legal to look up values into the same array that has - * the attribute names, overwriting each name as the value is found. - */ - - cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals)); - - 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]); -} - -static int count_attrs( - const char *name, - const char *value, - void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(value); - - *((int *)payload) += 1; - - return 0; -} - -#define CANCEL_VALUE 12345 - -static int cancel_iteration( - const char *name, - const char *value, - void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(value); - - *((int *)payload) -= 1; - - if (*((int *)payload) < 0) - return CANCEL_VALUE; - - return 0; -} - -void test_attr_repo__foreach(void) -{ - int count; - - count = 0; - cl_git_pass(git_attr_foreach( - g_repo, 0, "root_test1", &count_attrs, &count)); - cl_assert(count == 2); - - count = 0; - cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test1", - &count_attrs, &count)); - cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */ - - count = 0; - cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test2.txt", - &count_attrs, &count)); - cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */ - - count = 2; - cl_assert_equal_i( - CANCEL_VALUE, git_attr_foreach( - g_repo, 0, "sub/subdir_test1", &cancel_iteration, &count) - ); -} - -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_IS_TRUE(value)); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "bar")); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "baz")); - 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_IS_UNSPECIFIED(value)); -} - -#define CONTENT "I'm going to be dynamically processed\r\n" \ - "And my line endings...\r\n" \ - "...are going to be\n" \ - "normalized!\r\n" - -#define GITATTR "* text=auto\n" \ - "*.txt text\n" \ - "*.data binary\n" - -static void add_to_workdir(const char *filename, const char *content) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&buf, "attr", filename)); - cl_git_rewritefile(git_str_cstr(&buf), content); - - git_str_dispose(&buf); -} - -static void assert_proper_normalization(git_index *index, const char *filename, const char *expected_sha) -{ - size_t index_pos; - const git_index_entry *entry; - - add_to_workdir(filename, CONTENT); - cl_git_pass(git_index_add_bypath(index, filename)); - - cl_assert(!git_index_find(&index_pos, index, filename)); - - entry = git_index_get_byindex(index, index_pos); - cl_assert_equal_i(0, git_oid_streq(&entry->id, expected_sha)); -} - -void test_attr_repo__staging_properly_normalizes_line_endings_according_to_gitattributes_directives(void) -{ - git_index* index; - - cl_git_pass(git_repository_index(&index, g_repo)); - - add_to_workdir(".gitattributes", GITATTR); - - assert_proper_normalization(index, "text.txt", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); - assert_proper_normalization(index, "huh.dunno", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); - assert_proper_normalization(index, "binary.data", "66eeff1fcbacf589e6d70aa70edd3fce5be2b37c"); - - git_index_free(index); -} - -void test_attr_repo__bare_repo_with_index(void) -{ - const char *names[4] = { "test1", "test2", "test3", "test4" }; - const char *values[4]; - git_index *index; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_mkfile( - "attr/.gitattributes", - "*.txt test1 test2=foobar -test3\n" - "trial.txt -test1 test2=barfoo !test3 test4\n"); - cl_git_pass(git_index_add_bypath(index, ".gitattributes")); - git_index_free(index); - - cl_must_pass(p_unlink("attr/.gitattributes")); - cl_assert(!git_fs_path_exists("attr/.gitattributes")); - - cl_git_pass(git_repository_set_bare(g_repo)); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "file.txt", 4, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert_equal_s("foobar", values[1]); - 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_IS_FALSE(values[0])); - cl_assert_equal_s("barfoo", values[1]); - 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_IS_TRUE(values[0])); - cl_assert_equal_s("foobar", values[1]); - cl_assert(GIT_ATTR_IS_FALSE(values[2])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); -} - -void test_attr_repo__sysdir(void) -{ - git_str sysdir = GIT_STR_INIT; - const char *value; - - cl_git_pass(p_mkdir("system", 0777)); - cl_git_rewritefile("system/gitattributes", "file merge=foo"); - cl_git_pass(git_str_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_str_dispose(&sysdir); -} - -void test_attr_repo__sysdir_with_session(void) -{ - const char *values[2], *attrs[2] = { "foo", "bar" }; - git_str sysdir = GIT_STR_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_str_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, NULL, "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_str_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_str sysdir = GIT_STR_INIT; - const char *value; - - cl_git_pass(p_mkdir("system", 0777)); - cl_git_pass(git_str_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_str_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/benchmarks/README.md b/tests/benchmarks/README.md new file mode 100644 index 000000000..f66b27aea --- /dev/null +++ b/tests/benchmarks/README.md @@ -0,0 +1,121 @@ +# libgit2 benchmarks + +This folder contains the individual benchmark tests for libgit2, +meant for understanding the performance characteristics of libgit2, +comparing your development code to the existing libgit2 code, or +comparing libgit2 to the git reference implementation. + +## Running benchmark tests + +Benchmark tests can be run in several different ways: running all +benchmarks, running one (or more) suite of benchmarks, or running a +single individual benchmark. You can target either an individual +version of a CLI, or you can A/B test a baseline CLI against a test +CLI. + +### Specifying the command-line interface to test + +By default, the `git` in your path is benchmarked. Use the +`-c` (or `--cli`) option to specify the command-line interface +to test. + +Example: `libgit2_bench --cli git2_cli` will run the tests against +`git2_cli`. + +### Running tests to compare two different implementations + +You can compare a baseline command-line interface against a test +command-line interface using the `-b (or `--baseline-cli`) option. + +Example: `libgit2_bench --baseline-cli git --cli git2_cli` will +run the tests against both `git` and `git2_cli`. + +### Running individual benchmark tests + +Similar to how a test suite or individual test is specified in +[clar](https://github.com/clar-test/clar), the `-s` (or `--suite`) +option may be used to specify the suite or individual test to run. +Like clar, the suite and test name are separated by `::`, and like +clar, this is a prefix match. + +Examples: +* `libgit2_bench -shash_object` will run the tests in the + `hash_object` suite. +* `libgit2_bench -shash_object::random_1kb` will run the + `hash_object::random_1kb` test. +* `libgit2_bench -shash_object::random` will run all the tests that + begin with `hash_object::random`. + +## Writing benchmark tests + +Benchmark tests are meant to be easy to write. Each individual +benchmark is a shell script that allows it to do set up (eg, creating +or cloning a repository, creating temporary files, etc), then running +benchmarks, then teardown. + +The `benchmark_helpers.sh` script provides many helpful utility +functions to allow for cross-platform benchmarking, as well as a +wrapper for `hyperfine` that is suited to testing libgit2. +Note that the helper script must be included first, at the beginning +of the benchmark test. + +### Benchmark example + +This simplistic example compares the speed of running the `git help` +command in the baseline CLI to the test CLI. + +```bash +#!/bin/bash -e + +# include the benchmark library +. "$(dirname "$0")/benchmark_helpers.sh" + +# run the "help" command; this will benchmark `git2_cli help` +gitbench help +``` + +### Naming + +The filename of the benchmark itself is important. A benchmark's +filename should be the name of the benchmark suite, followed by two +underscores, followed by the name of the benchmark. For example, +`hash-object__random_1kb` is the `random_1kb` test in the `hash-object` +suite. + +### Options + +The `gitbench` function accepts several options. + +* `--sandbox ` + The name of a test resource (in the `tests/resources` directory). + This will be copied as-is to the sandbox location before test + execution. This is copied _before_ the `prepare` script is run. + This option may be specified multiple times. +* `--repository ` + The name of a test resource repository (in the `tests/resources` + directory). This repository will be copied into a sandbox location + before test execution, and your test will run in this directory. + This is copied _before_ the `prepare` script is run. +* `--prepare