diff options
Diffstat (limited to '')
307 files changed, 12611 insertions, 4904 deletions
diff --git a/.gitattributes b/.gitattributes index 3d90b7d..3788dc9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ * text=auto +ci/**/*.sh text eol=lf +script/**/*.sh text eol=lf tests/resources/** linguist-vendored diff --git a/.github/actions/download-or-build-container/action.yml b/.github/actions/download-or-build-container/action.yml new file mode 100644 index 0000000..9c83a98 --- /dev/null +++ b/.github/actions/download-or-build-container/action.yml @@ -0,0 +1,109 @@ +# Run a build step in a container or directly on the Actions runner +name: Download or Build Container +description: Download a container from the package registry, or build it if it's not found + +inputs: + container: + description: Container name + type: string + required: true + dockerfile: + description: Dockerfile + type: string + base: + description: Container base + type: string + registry: + description: Docker registry to read and publish to + type: string + default: ghcr.io + config-path: + description: Path to Dockerfiles + type: string + github_token: + description: GitHub Token + type: string + +runs: + using: 'composite' + steps: + - name: Download container + run: | + IMAGE_NAME="${{ inputs.container }}" + DOCKERFILE_PATH="${{ inputs.dockerfile }}" + DOCKER_REGISTRY="${{ inputs.registry }}" + DOCKERFILE_ROOT="${{ inputs.config-path }}" + + if [ "${DOCKERFILE_PATH}" = "" ]; then + DOCKERFILE_PATH="${DOCKERFILE_ROOT}/${IMAGE_NAME}" + else + DOCKERFILE_PATH="${DOCKERFILE_ROOT}/${DOCKERFILE_PATH}" + fi + + GIT_WORKTREE=$(cd "${GITHUB_ACTION_PATH}" && git rev-parse --show-toplevel) + echo "::: git worktree is ${GIT_WORKTREE}" + cd "${GIT_WORKTREE}" + + DOCKER_CONTAINER="${GITHUB_REPOSITORY}/${IMAGE_NAME}" + DOCKER_REGISTRY_CONTAINER="${DOCKER_REGISTRY}/${DOCKER_CONTAINER}" + + echo "dockerfile=${DOCKERFILE_PATH}" >> $GITHUB_ENV + echo "docker-container=${DOCKER_CONTAINER}" >> $GITHUB_ENV + echo "docker-registry-container=${DOCKER_REGISTRY_CONTAINER}" >> $GITHUB_ENV + + # Identify the last git commit that touched the Dockerfiles + # Use this as a hash to identify the resulting docker containers + echo "::: dockerfile path is ${DOCKERFILE_PATH}" + + DOCKER_SHA=$(git log -1 --pretty=format:"%h" -- "${DOCKERFILE_PATH}") + echo "docker-sha=${DOCKER_SHA}" >> $GITHUB_ENV + + echo "::: docker sha is ${DOCKER_SHA}" + + 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 + + if [ "${exists}" = "true" ]; then + echo "::: docker container exists in registry" + echo "docker-container-exists=true" >> $GITHUB_ENV + else + echo "::: docker container does not exist in registry" + echo "docker-container-exists=false" >> $GITHUB_ENV + fi + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + - name: Create container + run: | + if [ "${{ inputs.base }}" != "" ]; then + BASE_ARG="--build-arg BASE=${{ inputs.base }}" + fi + + GIT_WORKTREE=$(cd "${GITHUB_ACTION_PATH}" && git rev-parse --show-toplevel) + echo "::: git worktree is ${GIT_WORKTREE}" + cd "${GIT_WORKTREE}" + + 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 }} + shell: bash + working-directory: source/${{ inputs.config-path }} + if: env.docker-container-exists != 'true' + - name: Publish container + run: | + docker push ${{ env.docker-registry-container-sha }} + docker push ${{ env.docker-registry-container-latest }} + shell: bash + if: env.docker-container-exists != 'true' && github.event_name != 'pull_request' diff --git a/.github/actions/run-build/action.yml b/.github/actions/run-build/action.yml index 41145d3..9afcfb1 100644 --- a/.github/actions/run-build/action.yml +++ b/.github/actions/run-build/action.yml @@ -5,14 +5,19 @@ description: Run a build step in a container or directly on the Actions runner inputs: command: description: Command to run - required: true type: string + required: true container: description: Optional container to run in type: string container-version: description: Version of the container to run type: string + shell: + description: Shell to use + type: string + required: true + default: 'bash' runs: using: 'composite' @@ -35,6 +40,7 @@ runs: -e PKG_CONFIG_PATH \ -e SKIP_NEGOTIATE_TESTS \ -e SKIP_SSH_TESTS \ + -e SKIP_PUSHOPTIONS_TESTS \ -e TSAN_OPTIONS \ -e UBSAN_OPTIONS \ ${{ inputs.container-version }} \ @@ -42,4 +48,4 @@ runs: else ${{ inputs.command }} fi - shell: bash + shell: ${{ inputs.shell != '' && inputs.shell || 'bash' }} diff --git a/.github/release.yml b/.github/release.yml index 7a00321..4d4e318 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -21,9 +21,15 @@ changelog: - title: Documentation improvements labels: - documentation + - title: Platform compatibility fixes + labels: + - compatibility - title: Git compatibility fixes labels: - git compatibility + - title: Dependency updates + labels: + - dependency - title: Other changes labels: - '*' diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index bf21674..6ee492a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -6,10 +6,14 @@ on: schedule: - cron: '15 4 * * *' +permissions: + contents: read + 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). + # Run our benchmarks. We build a matrix with the various build + # targets and their details. Unlike our CI builds, we run these + # directly on the VM instead of in containers since we do not + # need the breadth of platform diversity. build: # Only run scheduled workflows on the main repository; prevents people # from using build minutes on their forks. @@ -27,7 +31,7 @@ jobs: os: ubuntu-latest setup-script: ubuntu - name: "macOS" - os: macos-11 + os: macos-12 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 @@ -45,12 +49,12 @@ jobs: id: windows setup-script: win32 fail-fast: false - name: "Build ${{ matrix.platform.name }}" + name: "Benchmark ${{ matrix.platform.name }}" env: ${{ matrix.platform.env }} runs-on: ${{ matrix.platform.os }} steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -72,11 +76,65 @@ jobs: fi mkdir benchmark && cd benchmark - ../source/tests/benchmarks/benchmark.sh --baseline-cli "git" --cli "${GIT2_CLI}" --json benchmarks.json --zip benchmarks.zip + ../source/tests/benchmarks/benchmark.sh --baseline-cli "git" --cli "${GIT2_CLI}" --name libgit2 --json benchmarks.json --zip benchmarks.zip shell: bash - name: Upload results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: benchmark-${{ matrix.platform.id }} path: benchmark if: always() + + # Publish the results + publish: + name: Publish results + needs: [ build ] + if: ${{ always() && github.repository == 'libgit2/libgit2' }} + runs-on: ubuntu-latest + steps: + - name: Check out benchmark repository + uses: actions/checkout@v4 + with: + repository: libgit2/benchmarks + path: site + fetch-depth: 0 + ssh-key: ${{ secrets.BENCHMARKS_PUBLISH_KEY }} + - name: Download test results + uses: actions/download-artifact@v4 + - name: Publish API + run: | + # Move today's benchmark run into the right place + for platform in linux macos windows; do + TIMESTAMP=$(jq .time.start < "benchmark-${platform}/benchmarks.json") + TIMESTAMP_LEN=$(echo -n ${TIMESTAMP} | wc -c | xargs) + DENOMINATOR=1 + if [ "${TIMESTAMP_LEN}" = "19" ]; then + DENOMINATOR="1000000000" + elif [ "${TIMESTAMP_LEN}" = "13" ]; then + DENOMINATOR="1000" + else + echo "unknown timestamp" + exit 1 + fi + + if [[ "$(uname -s)" == "Darwin" ]]; then + DATE=$(date -R -r $(("${TIMESTAMP}/${DENOMINATOR}")) +"%Y-%m-%d") + else + DATE=$(date -d @$(("${TIMESTAMP}/${DENOMINATOR}")) +"%Y-%m-%d") + fi + + mkdir -p "site/public/api/runs/${DATE}" + cp "benchmark-${platform}/benchmarks.json" "site/public/api/runs/${DATE}/${platform}.json" + done + + (cd site && node scripts/aggregate.js) + + ( + cd site && + git config user.name 'Benchmark Site Generation' && + git config user.email 'libgit2@users.noreply.github.com' && + git add . && + git commit --allow-empty -m"benchmark update ${DATE}" && + git push origin main + ) + shell: bash diff --git a/.github/workflows/build-containers.yml b/.github/workflows/build-containers.yml index 767798b..b52571c 100644 --- a/.github/workflows/build-containers.yml +++ b/.github/workflows/build-containers.yml @@ -24,6 +24,7 @@ jobs: - name: xenial - name: bionic - name: focal + - name: noble - name: docurium - name: bionic-x86 dockerfile: bionic @@ -39,11 +40,12 @@ jobs: qemu: true - name: centos7 - name: centos8 + - name: fedora runs-on: ubuntu-latest name: "Create container: ${{ matrix.container.name }}" steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml new file mode 100644 index 0000000..5bfea2c --- /dev/null +++ b/.github/workflows/experimental.yml @@ -0,0 +1,118 @@ +# Validation builds for experimental features; these shouldn't be +# required for pull request approval. +name: Experimental Features + +on: + push: + branches: [ main, maint/* ] + pull_request: + branches: [ main, maint/* ] + workflow_dispatch: + +env: + docker-registry: ghcr.io + docker-config-path: ci/docker + +permissions: + contents: write + packages: write + +jobs: + # Run our CI/CD 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: + strategy: + matrix: + platform: + # All builds: experimental SHA256 support + - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" + id: linux-sha256 + os: ubuntu-latest + container: + name: xenial + env: + CC: clang + CMAKE_GENERATOR: Ninja + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DEXPERIMENTAL_SHA256=ON + - name: "macOS (SHA256)" + id: macos-sha256 + os: macos-12 + setup-script: osx + env: + CC: clang + CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON -DEXPERIMENTAL_SHA256=ON + CMAKE_GENERATOR: Ninja + PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (SHA256, amd64, Visual Studio)" + id: windows-sha256 + os: windows-2019 + env: + ARCH: amd64 + CMAKE_GENERATOR: Visual Studio 16 2019 + CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DEXPERIMENTAL_SHA256=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + fail-fast: false + env: ${{ matrix.platform.env }} + runs-on: ${{ matrix.platform.os }} + name: "Build: ${{ matrix.platform.name }}" + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + path: source + fetch-depth: 0 + - name: Set up build environment + run: source/ci/setup-${{ matrix.platform.setup-script }}-build.sh + shell: bash + if: matrix.platform.setup-script != '' + - name: Setup QEMU + run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + if: matrix.platform.container.qemu == true + - name: Set up container + uses: ./source/.github/actions/download-or-build-container + with: + registry: ${{ env.docker-registry }} + config-path: ${{ env.docker-config-path }} + container: ${{ matrix.platform.container.name }} + github_token: ${{ secrets.github_token }} + dockerfile: ${{ matrix.platform.container.dockerfile }} + if: matrix.platform.container.name != '' + - name: Prepare build + run: mkdir build + - name: Build + uses: ./source/.github/actions/run-build + with: + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/build.sh + container: ${{ matrix.platform.container.name }} + container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} + - name: Test + uses: ./source/.github/actions/run-build + with: + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/test.sh + container: ${{ matrix.platform.container.name }} + container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} + - name: Upload test results + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: test-results-${{ matrix.platform.id }} + path: build/results_*.xml + + test_results: + name: Test results + needs: [ build ] + if: always() + runs-on: ubuntu-latest + steps: + - name: Download test results + uses: actions/download-artifact@v3 + - name: Generate test summary + uses: test-summary/action@v2 + with: + paths: 'test-results-*/*.xml' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d84ded0..87e834f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,66 +11,68 @@ on: env: docker-registry: ghcr.io - docker-config-path: source/ci/docker + docker-config-path: ci/docker -jobs: - containers: - uses: ./.github/workflows/build-containers.yml +permissions: + contents: write + packages: write +jobs: # Run our CI/CD 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: - needs: [ containers ] strategy: matrix: platform: - - name: "Linux (Xenial, GCC, OpenSSL)" - id: xenial-gcc-openssl + # All builds: core platforms + - name: "Linux (Noble, GCC, OpenSSL, libssh2)" + id: noble-gcc-openssl + os: ubuntu-latest container: - name: xenial + name: noble env: CC: gcc CMAKE_GENERATOR: Ninja - 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 + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2 -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON + - name: "Linux (Noble, Clang, mbedTLS, OpenSSH)" + id: noble-clang-mbedtls os: ubuntu-latest - - name: Linux (Xenial, GCC, mbedTLS) - id: xenial-gcc-mbedtls container: - name: xenial + name: noble env: - CC: gcc + CC: clang + CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=exec CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + - name: "Linux (Xenial, GCC, OpenSSL, OpenSSH)" + id: xenial-gcc-openssl os: ubuntu-latest - - name: "Linux (Xenial, Clang, OpenSSL)" - id: xenial-clang-openssl container: name: xenial env: - CC: clang + CC: gcc CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=exec -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON + - name: "Linux (Xenial, Clang, mbedTLS, libssh2)" + id: xenial-gcc-mbedtls os: ubuntu-latest - - name: "Linux (Xenial, Clang, mbedTLS)" - id: xenial-clang-mbedtls container: name: xenial env: CC: clang - CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja - os: ubuntu-latest + CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2 - name: "macOS" id: macos - os: macos-11 + os: macos-12 + setup-script: osx env: CC: clang CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON + CMAKE_GENERATOR: Ninja PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - setup-script: osx - name: "Windows (amd64, Visual Studio, Schannel)" id: windows-amd64-vs os: windows-2019 @@ -120,13 +122,15 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - # Sanitizers + # All builds: sanitizers - name: "Sanitizer (Memory)" - id: memorysanitizer + id: sanitizer-memory + os: ubuntu-latest + setup-script: sanitizer container: - name: focal + name: noble env: - CC: clang-10 + CC: clang CFLAGS: -fsanitize=memory -fsanitize-memory-track-origins=2 -fsanitize-blacklist=/home/libgit2/source/script/sanitizers.supp -fno-optimize-sibling-calls -fno-omit-frame-pointer CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local/msan -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja @@ -134,73 +138,59 @@ jobs: SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 + - name: "Sanitizer (Address)" + id: sanitizer-address os: ubuntu-latest - - name: "Sanitizer (UndefinedBehavior)" - id: ubsanitizer + setup-script: sanitizer container: - name: focal + name: noble env: - CC: clang-10 - CFLAGS: -fsanitize=undefined,nullability -fno-sanitize-recover=undefined,nullability -fsanitize-blacklist=/home/libgit2/source/script/sanitizers.supp -fno-optimize-sibling-calls -fno-omit-frame-pointer - CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON + CC: clang + CFLAGS: -fsanitize=address -ggdb -fsanitize-blacklist=/home/libgit2/source/script/sanitizers.supp -fno-optimize-sibling-calls -fno-omit-frame-pointer + CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 + - name: "Sanitizer (UndefinedBehavior)" + id: sanitizer-ub os: ubuntu-latest - - name: "Sanitizer (Thread)" - id: threadsanitizer + setup-script: sanitizer container: - name: focal + name: noble env: - CC: clang-10 - CFLAGS: -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer + CC: clang + CFLAGS: -fsanitize=undefined,nullability -fno-sanitize-recover=undefined,nullability -fsanitize-blacklist=/home/libgit2/source/script/sanitizers.supp -fno-optimize-sibling-calls -fno-omit-frame-pointer CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 - TSAN_OPTIONS: suppressions=/home/libgit2/source/script/thread-sanitizer.supp second_deadlock_stack=1 + - name: "Sanitizer (Thread)" + id: sanitizer-thread os: ubuntu-latest - - # Experimental: SHA256 support - - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" - id: xenial-clang-openssl + setup-script: sanitizer container: - name: xenial + name: noble env: CC: clang + CFLAGS: -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer + CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DEXPERIMENTAL_SHA256=ON - os: ubuntu-latest - - name: "macOS (SHA256)" - id: macos - os: macos-11 - env: - CC: clang - CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON -DEXPERIMENTAL_SHA256=ON - PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - setup-script: osx - - name: "Windows (SHA256, amd64, Visual Studio)" - id: windows-amd64-vs - os: windows-2019 - env: - ARCH: amd64 - CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DEXPERIMENTAL_SHA256=ON SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true + ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 + UBSAN_OPTIONS: print_stacktrace=1 + TSAN_OPTIONS: suppressions=/home/libgit2/source/script/thread-sanitizer.supp second_deadlock_stack=1 fail-fast: false env: ${{ matrix.platform.env }} runs-on: ${{ matrix.platform.os }} name: "Build: ${{ matrix.platform.name }}" steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -211,38 +201,33 @@ jobs: - name: Setup QEMU run: docker run --rm --privileged multiarch/qemu-user-static:register --reset if: matrix.platform.container.qemu == true - - name: Download container - run: | - "${{ github.workspace }}/source/ci/getcontainer.sh" "${{ matrix.platform.container.name }}" "${{ matrix.platform.container.dockerfile }}" - env: - DOCKER_REGISTRY: ${{ env.docker-registry }} - GITHUB_TOKEN: ${{ secrets.github_token }} - working-directory: ${{ env.docker-config-path }} + - name: Set up container + uses: ./source/.github/actions/download-or-build-container + with: + registry: ${{ env.docker-registry }} + config-path: ${{ env.docker-config-path }} + container: ${{ matrix.platform.container.name }} + github_token: ${{ secrets.github_token }} + dockerfile: ${{ matrix.platform.container.dockerfile }} if: matrix.platform.container.name != '' - - name: Create container - 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: Prepare build run: mkdir build - name: Build uses: ./source/.github/actions/run-build with: - command: cd build && ../source/ci/build.sh + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/build.sh container: ${{ matrix.platform.container.name }} container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} - name: Test uses: ./source/.github/actions/run-build with: - command: cd build && ../source/ci/test.sh + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/test.sh container: ${{ matrix.platform.container.name }} container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} - name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() || failure() with: name: test-results-${{ matrix.platform.id }} @@ -269,15 +254,22 @@ jobs: # published to our documentation site. documentation: name: Generate documentation - needs: [ containers ] if: success() || failure() runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 + - name: Set up container + uses: ./source/.github/actions/download-or-build-container + with: + registry: ${{ env.docker-registry }} + config-path: ${{ env.docker-config-path }} + container: docurium + github_token: ${{ secrets.github_token }} + dockerfile: ${{ matrix.platform.container.dockerfile }} - name: Generate documentation working-directory: source run: | @@ -293,7 +285,7 @@ jobs: cm doc api.docurium git checkout gh-pages zip --exclude .git/\* --exclude .gitignore --exclude .gitattributes -r api-documentation.zip . - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload artifact with: name: api-documentation diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 18328a7..28a0618 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,7 +8,11 @@ on: env: docker-registry: ghcr.io - docker-config-path: source/ci/docker + docker-config-path: ci/docker + +permissions: + contents: read + packages: write jobs: # Run our nightly builds. We build a matrix with the various build @@ -22,59 +26,112 @@ jobs: strategy: matrix: platform: - - name: Linux (Xenial, GCC, OpenSSL) - container: - name: xenial - env: - CC: gcc - CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + # All builds: core platforms + - name: "Linux (Noble, GCC, OpenSSL, libssh2)" + id: noble-gcc-openssl os: ubuntu-latest - - name: "Linux (Xenial, GCC, mbedTLS)" container: - name: xenial + name: noble env: CC: gcc CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2 -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON + - name: "Linux (Noble, Clang, mbedTLS, OpenSSH)" + id: noble-clang-mbedtls os: ubuntu-latest - - name: "Linux (Xenial, Clang, OpenSSL)" container: - name: xenial + name: noble env: CC: clang + CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=exec CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + - name: "Linux (Xenial, GCC, OpenSSL, OpenSSH)" + id: xenial-gcc-openssl os: ubuntu-latest - - name: "Linux (Xenial, Clang, mbedTLS)" container: name: xenial env: - CC: clang - CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CC: gcc CMAKE_GENERATOR: Ninja + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=exec -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON + - name: "Linux (Xenial, Clang, mbedTLS, libssh2)" + id: xenial-gcc-mbedtls os: ubuntu-latest - - name: "Linux (no threads)" container: name: xenial env: - CC: gcc - CMAKE_OPTIONS: -DTHREADSAFE=OFF -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CC: clang CMAKE_GENERATOR: Ninja - os: ubuntu-latest - - name: "Linux (dynamically-loaded OpenSSL)" - container: - name: xenial + CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2 + - name: "macOS" + id: macos + os: macos-12 + setup-script: osx env: CC: clang - CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL-Dynamic -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON CMAKE_GENERATOR: Ninja + PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (amd64, Visual Studio, Schannel)" + id: windows-amd64-vs + os: windows-2019 + setup-script: win32 + env: + ARCH: amd64 + CMAKE_GENERATOR: Visual Studio 16 2019 + CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2 + BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin + BUILD_TEMP: D:\Temp + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (x86, Visual Studio, WinHTTP)" + id: windows-x86-vs + os: windows-2019 + setup-script: win32 + env: + ARCH: x86 + CMAKE_GENERATOR: Visual Studio 16 2019 + CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2 + BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin + BUILD_TEMP: D:\Temp + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (amd64, mingw, WinHTTP)" + id: windows-amd64-mingw + os: windows-2019 + setup-script: mingw + env: + ARCH: amd64 + CMAKE_GENERATOR: MinGW Makefiles + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON + BUILD_TEMP: D:\Temp + BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (x86, mingw, Schannel)" + id: windows-x86-mingw + os: windows-2019 + setup-script: mingw + env: + ARCH: x86 + CMAKE_GENERATOR: MinGW Makefiles + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel + BUILD_TEMP: D:\Temp + BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + + # All builds: sanitizers + - name: "Sanitizer (Memory)" + id: memorysanitizer os: ubuntu-latest - - name: "Linux (MemorySanitizer)" + setup-script: sanitizer container: - name: focal + name: noble env: - CC: clang-10 + CC: clang-17 CFLAGS: -fsanitize=memory -fsanitize-memory-track-origins=2 -fsanitize-blacklist=/home/libgit2/source/script/sanitizers.supp -fno-optimize-sibling-calls -fno-omit-frame-pointer CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local/msan -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja @@ -82,60 +139,62 @@ jobs: SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 + - name: "Sanitizer (UndefinedBehavior)" + id: ubsanitizer os: ubuntu-latest - - name: "Linux (UndefinedBehaviorSanitizer)" + setup-script: sanitizer container: - name: focal + name: noble env: - CC: clang-10 + CC: clang-17 CFLAGS: -fsanitize=undefined,nullability -fno-sanitize-recover=undefined,nullability -fsanitize-blacklist=/home/libgit2/source/script/sanitizers.supp -fno-optimize-sibling-calls -fno-omit-frame-pointer - CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON + CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 + UBSAN_OPTIONS: print_stacktrace=1 + - name: "Sanitizer (Thread)" + id: threadsanitizer os: ubuntu-latest - - name: "Linux (ThreadSanitizer)" + setup-script: sanitizer container: - name: focal + name: noble env: - CC: clang-10 + CC: clang-17 CFLAGS: -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer - CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON + CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local -DUSE_HTTPS=OpenSSL -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 + UBSAN_OPTIONS: print_stacktrace=1 TSAN_OPTIONS: suppressions=/home/libgit2/source/script/thread-sanitizer.supp second_deadlock_stack=1 + + # Nightly builds: extended platforms + - name: "Linux (CentOS 7, OpenSSL)" + id: centos7-openssl os: ubuntu-latest - - name: "Linux (no mmap)" - container: - name: focal - env: - CC: clang-10 - CFLAGS: -DNO_MMAP - CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local - CMAKE_GENERATOR: Ninja - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - os: ubuntu-latest - - name: "Linux (CentOS 7)" container: name: centos7 env: CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON PKG_CONFIG_PATH: /usr/local/lib/pkgconfig SKIP_NEGOTIATE_TESTS: true - os: ubuntu-latest + SKIP_PUSHOPTIONS_TESTS: true - name: "Linux (CentOS 7, dynamically-loaded OpenSSL)" + id: centos7-dynamicopenssl + os: ubuntu-latest container: name: centos7 env: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL-Dynamic -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON PKG_CONFIG_PATH: /usr/local/lib/pkgconfig SKIP_NEGOTIATE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true + - name: "Linux (CentOS 8, OpenSSL)" + id: centos8-openssl os: ubuntu-latest - - name: "Linux (CentOS 8)" container: name: centos8 env: @@ -143,8 +202,9 @@ jobs: PKG_CONFIG_PATH: /usr/local/lib/pkgconfig SKIP_NEGOTIATE_TESTS: true SKIP_SSH_TESTS: true - os: ubuntu-latest - name: "Linux (CentOS 8, dynamically-loaded OpenSSL)" + id: centos8-dynamicopenssl + os: ubuntu-latest container: name: centos8 env: @@ -152,80 +212,18 @@ jobs: PKG_CONFIG_PATH: /usr/local/lib/pkgconfig SKIP_NEGOTIATE_TESTS: true SKIP_SSH_TESTS: true - os: ubuntu-latest - - name: "macOS" - os: macos-11 - env: - CC: clang - CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON - PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - setup-script: osx - - name: "Windows (amd64, Visual Studio, WinHTTP)" - os: windows-2019 - env: - ARCH: amd64 - CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, Visual Studio, WinHTTP)" - os: windows-2019 - env: - ARCH: x86 - CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (amd64, Visual Studio, Schannel)" - os: windows-2019 - env: - ARCH: amd64 - CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, Visual Studio, Schannel)" - os: windows-2019 - env: - ARCH: x86 - CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_BUNDLED_ZLIB=ON - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (amd64, mingw, WinHTTP)" - os: windows-2019 - setup-script: mingw - env: - ARCH: amd64 - CMAKE_GENERATOR: MinGW Makefiles - CMAKE_OPTIONS: -DDEPRECATE_HARD=ON - BUILD_TEMP: D:\Temp - BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, mingw, Schannel)" - os: windows-2019 - setup-script: mingw - env: ARCH: x86 - CMAKE_GENERATOR: MinGW Makefiles - CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel - BUILD_TEMP: D:\Temp - BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (no mmap)" - os: windows-2019 + - name: "Linux (Fedora, llhttp)" + id: fedora + os: ubuntu-latest + container: + name: fedora env: - ARCH: amd64 - CMAKE_GENERATOR: Visual Studio 16 2019 - CFLAGS: -DNO_MMAP - CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true + CC: gcc + CMAKE_GENERATOR: Ninja + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=pcre2 -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2 -DUSE_HTTP_PARSER=llhttp - name: "Linux (Bionic, GCC, dynamically-loaded OpenSSL)" + id: bionic-gcc-dynamicopenssl container: name: bionic dockerfile: bionic @@ -234,8 +232,10 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL-Dynamic -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (x86, Bionic, Clang, OpenSSL)" + id: bionic-x86-clang-openssl container: name: bionic-x86 dockerfile: bionic @@ -245,8 +245,10 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (x86, Bionic, GCC, OpenSSL)" + id: bionic-x86-gcc-openssl container: name: bionic-x86 dockerfile: bionic @@ -255,8 +257,10 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (arm32, Bionic, GCC, OpenSSL)" + id: bionic-arm32-gcc-openssl container: name: bionic-arm32 dockerfile: bionic @@ -267,9 +271,11 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true SKIP_PROXY_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true GITTEST_FLAKY_STAT: true os: ubuntu-latest - name: "Linux (arm64, Bionic, GCC, OpenSSL)" + id: bionic-arm64-gcc-openssl container: name: bionic-arm64 dockerfile: bionic @@ -280,11 +286,57 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true SKIP_PROXY_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - # Experimental: SHA256 support + # Nightly builds: ensure we fallback when missing core functionality + - name: "Linux (no threads)" + id: xenial-nothreads + os: ubuntu-latest + container: + name: xenial + env: + CC: gcc + CMAKE_OPTIONS: -DTHREADSAFE=OFF -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CMAKE_GENERATOR: Ninja + SKIP_PUSHOPTIONS_TESTS: true + - name: "Linux (no mmap)" + id: noble-nommap + os: ubuntu-latest + container: + name: noble + env: + CC: gcc + CFLAGS: -DNO_MMAP + CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=/usr/local + CMAKE_GENERATOR: Ninja + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (no mmap)" + id: windows-nommap + os: windows-2019 + env: + ARCH: amd64 + CMAKE_GENERATOR: Visual Studio 16 2019 + CFLAGS: -DNO_MMAP + CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + + # Nightly builds: extended SSL support + - name: "Linux (dynamically-loaded OpenSSL)" + id: xenial-dynamicopenssl + os: ubuntu-latest + container: + name: xenial + env: + CC: clang + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL-Dynamic -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON + CMAKE_GENERATOR: Ninja + + # All builds: experimental SHA256 support - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" - id: xenial-clang-openssl + id: linux-sha256 container: name: xenial env: @@ -293,17 +345,17 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON os: ubuntu-latest - name: "macOS (SHA256)" - id: macos - os: macos-10.15 + id: macos-sha256 + os: macos-12 + setup-script: osx env: CC: clang CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON -DEXPERIMENTAL_SHA256=ON PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - setup-script: osx - name: "Windows (SHA256, amd64, Visual Studio)" - id: windows-amd64-vs + id: windows-sha256 os: windows-2019 env: ARCH: amd64 @@ -317,7 +369,7 @@ jobs: name: "Build ${{ matrix.platform.name }}" steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -328,32 +380,50 @@ jobs: - name: Setup QEMU run: docker run --rm --privileged multiarch/qemu-user-static:register --reset if: matrix.platform.container.qemu == true - - name: Download container - run: | - "${{ github.workspace }}/source/ci/getcontainer.sh" "${{ matrix.platform.container.name }}" "${{ matrix.platform.container.dockerfile }}" - env: - DOCKER_REGISTRY: ${{ env.docker-registry }} - GITHUB_TOKEN: ${{ secrets.github_token }} - working-directory: ${{ env.docker-config-path }} + - name: Set up container + uses: ./source/.github/actions/download-or-build-container + with: + registry: ${{ env.docker-registry }} + config-path: ${{ env.docker-config-path }} + container: ${{ matrix.platform.container.name }} + github_token: ${{ secrets.github_token }} + dockerfile: ${{ matrix.platform.container.dockerfile }} if: matrix.platform.container.name != '' - - name: Create container - run: docker build -t ${{ env.docker-registry-container-sha }} -f ${{ env.dockerfile }} . - working-directory: ${{ env.docker-config-path }} - if: matrix.platform.container.name != '' && env.docker-container-exists != 'true' - name: Prepare build run: mkdir build - name: Build uses: ./source/.github/actions/run-build with: - command: cd build && ../source/ci/build.sh + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/build.sh container: ${{ matrix.platform.container.name }} container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} - name: Test uses: ./source/.github/actions/run-build with: - command: cd build && ../source/ci/test.sh + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/test.sh container: ${{ matrix.platform.container.name }} container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} + - name: Upload test results + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: test-results-${{ matrix.platform.id }} + path: build/results_*.xml + + test_results: + name: Test results + needs: [ build ] + if: ${{ always() && github.repository == 'libgit2/libgit2' }} + runs-on: ubuntu-latest + steps: + - name: Download test results + uses: actions/download-artifact@v3 + - name: Generate test summary + uses: test-summary/action@v2 + with: + paths: 'test-results-*/*.xml' coverity: # Only run scheduled workflows on the main repository; prevents people @@ -364,17 +434,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 - - name: Download container - run: | - "${{ github.workspace }}/source/ci/getcontainer.sh" xenial - env: - DOCKER_REGISTRY: ${{ env.docker-registry }} - GITHUB_TOKEN: ${{ secrets.github_token }} - working-directory: ${{ env.docker-config-path }} + - name: Set up container + uses: ./source/.github/actions/download-or-build-container + with: + registry: ${{ env.docker-registry }} + config-path: ${{ env.docker-config-path }} + container: xenial + github_token: ${{ secrets.github_token }} + if: matrix.platform.container.name != '' - name: Run Coverity run: source/ci/coverity.sh env: @@ -385,11 +456,16 @@ jobs: # from using build minutes on their forks. if: github.repository == 'libgit2/libgit2' + permissions: + actions: read + contents: read + security-events: write + name: CodeQL runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d2714..9ca8882 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.5.1) -project(libgit2 VERSION "1.7.2" LANGUAGES C) +project(libgit2 VERSION "1.8.1" LANGUAGES C) # Add find modules to the path set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") @@ -30,7 +30,7 @@ option(USE_THREADS "Use threads for parallel processing when possibl option(USE_NSEC "Support nanosecond precision file mtimes and ctimes" ON) # Backend selection -option(USE_SSH "Link with libssh2 to enable SSH support" OFF) +option(USE_SSH "Enable SSH support. Can be set to a specific backend" 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" ON) option(USE_SHA256 "Enable SHA256. Can be set to HTTPS/Builtin" ON) @@ -365,7 +365,7 @@ Public License instead of this License. The bundled ZLib code is licensed under the ZLib license: -Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + (C) 1995-2022 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1214,3 +1214,197 @@ 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 bundled ntlmclient code is licensed under the MIT license: + +Copyright (c) Edward Thomson. All rights reserved. + +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. + +---------------------------------------------------------------------- + +Portions of this software derived from Team Explorer Everywhere: + +Copyright (c) Microsoft Corporation + +All rights reserved. + +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. + +--------------------------------------------------------------------------- + +Portions of this software derived from the LLVM Compiler Infrastructure: + +Copyright (c) 2003-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +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: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +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 +CONTRIBUTORS 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 WITH THE +SOFTWARE. + +--------------------------------------------------------------------------- + +Portions of this software derived from Unicode, Inc: + +Copyright 2001-2004 Unicode, Inc. + +Disclaimer + +This source code is provided as is by Unicode, Inc. No claims are +made as to fitness for any particular purpose. No warranties of any +kind are expressed or implied. The recipient agrees to determine +applicability of information provided. If this file has been +purchased on magnetic or optical media from Unicode, Inc., the +sole remedy for any claim will be exchange of defective media +within 90 days of receipt. + +Limitations on Rights to Redistribute This Code + +Unicode, Inc. hereby grants the right to freely use the information +supplied in this file in the creation of products supporting the +Unicode Standard, and to make copies of this file in any form +for internal or external distribution as long as this notice +remains attached. + +--------------------------------------------------------------------------- + +Portions of this software derived from sheredom/utf8.h: + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to <http://unlicense.org/> + +--------------------------------------------------------------------------- + +Portions of this software derived from RFC 1320: + +Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +---------------------------------------------------------------------- + +The bundled llhttp dependency is licensed under the MIT license: + +Copyright Fedor Indutny, 2018. + +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. @@ -4,8 +4,8 @@ libgit2 - the Git linkable library | Build Status | | | ------------ | - | | **main** branch CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush) | +| **v1.8 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.8&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.8) | | **v1.7 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.7&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.7) | -| **v1.6 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.6&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.6) | | **Nightly** builds | [![Nightly Build](https://github.com/libgit2/libgit2/workflows/Nightly%20Build/badge.svg)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22Nightly+Build%22) [![Coverity Scan Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) | `libgit2` is a portable, pure C implementation of the Git core methods @@ -18,7 +18,7 @@ functionality into your application. Language bindings like in your favorite language. `libgit2` is used to power Git GUI clients like -[GitKraken](https://gitkraken.com/) and [gmaster](https://gmaster.io/) +[GitKraken](https://gitkraken.com/) and [GitButler](https://gitbutler.com/) and on Git hosting providers like [GitHub](https://github.com/), [GitLab](https://gitlab.com/) and [Azure DevOps](https://azure.com/devops). @@ -68,7 +68,7 @@ But if you _do_ want to use libgit2 directly - because you're building an application in C - then you may be able use an existing binary. There are packages for the [vcpkg](https://github.com/Microsoft/vcpkg) and -[conan](https://conan.io/center/libgit2) +[conan](https://conan.io/center/recipes/libgit2) package managers. And libgit2 is available in [Homebrew](https://formulae.brew.sh/formula/libgit2) and most Linux distributions. @@ -113,7 +113,7 @@ Getting Help **Getting Help** If you have questions about the library, please be sure to check out the -[API documentation](http://libgit2.github.com/libgit2/). If you still have +[API documentation](https://libgit2.org/libgit2/). If you still have questions, reach out to us on Slack or post a question on [StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) (with the `libgit2` tag). diff --git a/ci/build.sh b/ci/build.sh index 80e7a61..a9b66f6 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -61,6 +61,8 @@ if test -n "${CC}"; then "${CC}" --version 2>&1 | indent fi echo "Environment:" +echo "PATH=${BUILD_PATH}" | indent + if test -n "${CC}"; then echo "CC=${CC}" | indent fi diff --git a/ci/docker/bionic b/ci/docker/bionic index f1b69ed..f42c6d2 100644 --- a/ci/docker/bionic +++ b/ci/docker/bionic @@ -12,7 +12,6 @@ RUN apt-get update && \ libcurl4-openssl-dev \ libkrb5-dev \ libpcre3-dev \ - libssh2-1-dev \ libssl-dev \ libz-dev \ ninja-build \ @@ -37,7 +36,16 @@ RUN cd /tmp && \ cd .. && \ rm -rf mbedtls-mbedtls-2.16.2 -FROM mbedtls AS adduser +FROM mbedtls AS libssh2 +RUN cd /tmp && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON . && \ + ninja install && \ + cd .. && \ + rm -rf libssh2-1.11.0 + +FROM libssh2 AS adduser ARG UID="" ARG GID="" RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ diff --git a/ci/docker/centos7 b/ci/docker/centos7 index 28ed650..45c299d 100644 --- a/ci/docker/centos7 +++ b/ci/docker/centos7 @@ -18,13 +18,13 @@ RUN yum install -y \ FROM yum AS libssh2 RUN cd /tmp && \ - curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.0.tar.gz | tar -xz && \ - cd libssh2-1.8.0 && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ ./configure && \ make && \ make install && \ cd .. && \ - rm -rf libssh-1.8.0 + rm -rf libssh-1.11.0 FROM libssh2 AS valgrind RUN cd /tmp && \ diff --git a/ci/docker/centos8 b/ci/docker/centos8 index 81f0c3c..c2ac5f0 100644 --- a/ci/docker/centos8 +++ b/ci/docker/centos8 @@ -24,13 +24,13 @@ RUN yum install -y \ FROM yum AS libssh2 RUN cd /tmp && \ - curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.0.tar.gz | tar -xz && \ - cd libssh2-1.8.0 && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ ./configure && \ make && \ make install && \ cd .. && \ - rm -rf libssh2-1.8.0 + rm -rf libssh2-1.11.0 FROM libssh2 AS valgrind RUN cd /tmp && \ diff --git a/ci/docker/fedora b/ci/docker/fedora new file mode 100644 index 0000000..1db3ceb --- /dev/null +++ b/ci/docker/fedora @@ -0,0 +1,52 @@ +ARG BASE=fedora:rawhide + +FROM ${BASE} AS stream +RUN dnf -y distro-sync + +FROM stream AS yum +RUN yum install -y \ + which \ + bzip2 \ + git \ + libarchive \ + cmake \ + gcc \ + make \ + openssl-devel \ + openssh-server \ + git-daemon \ + java-1.8.0-openjdk-headless \ + sudo \ + python3 \ + valgrind \ + krb5-workstation \ + krb5-libs \ + krb5-devel \ + pcre2-devel \ + zlib-devel \ + ninja-build \ + llhttp-devel + +FROM yum AS libssh2 +RUN cd /tmp && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + ./configure && \ + make && \ + make install && \ + cd .. && \ + rm -rf libssh2-1.11.0 + +FROM libssh2 AS adduser +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 +RUN mkdir /var/run/sshd +RUN echo "/usr/local/lib" > /etc/ld.so.conf.d/local && \ + ldconfig diff --git a/ci/docker/focal b/ci/docker/focal index b3a402c..62f5b63 100644 --- a/ci/docker/focal +++ b/ci/docker/focal @@ -53,7 +53,7 @@ RUN cd /tmp && \ cd libssh2-1.9.0 && \ mkdir build build-msan && \ cd build && \ - CC=clang-10 CFLAGS="-fPIC" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=Libgcrypt -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ + CC=clang-10 CFLAGS="-fPIC" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ ninja install && \ cd ../build-msan && \ CC=clang-10 CFLAGS="-fPIC -fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" LDFLAGS="-fsanitize=memory" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=mbedTLS -DCMAKE_PREFIX_PATH=/usr/local/msan -DCMAKE_INSTALL_PREFIX=/usr/local/msan .. && \ diff --git a/ci/docker/noble b/ci/docker/noble new file mode 100644 index 0000000..05cd276 --- /dev/null +++ b/ci/docker/noble @@ -0,0 +1,88 @@ +ARG BASE=ubuntu:noble + +FROM ${BASE} AS apt +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + bzip2 \ + clang \ + cmake \ + curl \ + gcc \ + git \ + krb5-user \ + libclang-rt-17-dev \ + libcurl4-gnutls-dev \ + libgcrypt20-dev \ + libkrb5-dev \ + libpcre3-dev \ + libssl-dev \ + libz-dev \ + llvm-17 \ + make \ + ninja-build \ + openjdk-8-jre-headless \ + openssh-server \ + openssl \ + pkgconf \ + python3 \ + sudo \ + valgrind \ + && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir /usr/local/msan + +FROM apt AS mbedtls +RUN cd /tmp && \ + curl --location --silent --show-error https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/mbedtls-2.28.6.tar.gz | \ + tar -xz && \ + cd mbedtls-mbedtls-2.28.6 && \ + scripts/config.pl unset MBEDTLS_AESNI_C && \ + scripts/config.pl set MBEDTLS_MD4_C 1 && \ + mkdir build build-msan && \ + cd build && \ + CC=clang-17 CFLAGS="-fPIC" cmake -G Ninja -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=ON -DUSE_STATIC_MBEDTLS_LIBRARY=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ + ninja install && \ + cd ../build-msan && \ + CC=clang-17 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-mbedtls-2.28.6 + +FROM mbedtls AS libssh2 +RUN cd /tmp && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + mkdir build build-msan && \ + cd build && \ + CC=clang-17 CFLAGS="-fPIC" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ + ninja install && \ + cd ../build-msan && \ + CC=clang-17 CFLAGS="-fPIC -fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" LDFLAGS="-fsanitize=memory" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=mbedTLS -DCMAKE_PREFIX_PATH=/usr/local/msan -DCMAKE_INSTALL_PREFIX=/usr/local/msan .. && \ + ninja install && \ + cd .. && \ + rm -rf libssh2-1.11.0 + +FROM libssh2 AS valgrind +RUN cd /tmp && \ + curl --insecure --location --silent --show-error https://sourceware.org/pub/valgrind/valgrind-3.22.0.tar.bz2 | \ + tar -xj && \ + cd valgrind-3.22.0 && \ + CC=clang-17 ./configure && \ + make MAKEFLAGS="-j -l$(grep -c ^processor /proc/cpuinfo)" && \ + make install && \ + cd .. && \ + rm -rf valgrind-3.22.0 + +FROM valgrind AS adduser +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 ldconfig +RUN ldconfig + +FROM ldconfig AS configure +RUN mkdir /var/run/sshd diff --git a/ci/docker/xenial b/ci/docker/xenial index 578f0a9..793df4b 100644 --- a/ci/docker/xenial +++ b/ci/docker/xenial @@ -53,12 +53,12 @@ RUN cd /tmp && \ FROM mbedtls AS libssh2 RUN cd /tmp && \ - curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.2.tar.gz | tar -xz && \ - cd libssh2-1.8.2 && \ - CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=Libgcrypt . && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON . && \ ninja install && \ cd .. && \ - rm -rf libssh2-1.8.2 + rm -rf libssh2-1.11.0 FROM libssh2 AS valgrind RUN cd /tmp && \ diff --git a/ci/getcontainer.sh b/ci/getcontainer.sh deleted file mode 100755 index 81d0c1d..0000000 --- a/ci/getcontainer.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -set -e - -IMAGE_NAME=$1 -DOCKERFILE_PATH=$2 - -if [ "${IMAGE_NAME}" = "" ]; then - echo "usage: $0 image_name [dockerfile]" - exit 1 -fi - -if [ "${DOCKERFILE_PATH}" = "" ]; then - DOCKERFILE_PATH="${IMAGE_NAME}" -fi - -if [ "${DOCKER_REGISTRY}" = "" ]; then - echo "DOCKER_REGISTRY environment variable is unset." - echo "Not running inside GitHub Actions or misconfigured?" - exit 1 -fi - -DOCKER_CONTAINER="${GITHUB_REPOSITORY}/${IMAGE_NAME}" -DOCKER_REGISTRY_CONTAINER="${DOCKER_REGISTRY}/${DOCKER_CONTAINER}" - -echo "dockerfile=${DOCKERFILE_PATH}" >> $GITHUB_ENV -echo "docker-container=${DOCKER_CONTAINER}" >> $GITHUB_ENV -echo "docker-registry-container=${DOCKER_REGISTRY_CONTAINER}" >> $GITHUB_ENV - -# Identify the last git commit that touched the Dockerfiles -# Use this as a hash to identify the resulting docker containers -DOCKER_SHA=$(git log -1 --pretty=format:"%h" -- "${DOCKERFILE_PATH}") -echo "docker-sha=${DOCKER_SHA}" >> $GITHUB_ENV - -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 - -if [ "${exists}" = "true" ]; then - echo "docker-container-exists=true" >> $GITHUB_ENV -else - echo "docker-container-exists=false" >> $GITHUB_ENV -fi diff --git a/ci/setup-osx-build.sh b/ci/setup-osx-build.sh index 0b95e76..511d886 100755 --- a/ci/setup-osx-build.sh +++ b/ci/setup-osx-build.sh @@ -3,6 +3,6 @@ set -ex brew update -brew install pkgconfig zlib curl openssl libssh2 ninja +brew install pkgconfig libssh2 ninja ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib diff --git a/ci/setup-sanitizer-build.sh b/ci/setup-sanitizer-build.sh new file mode 100755 index 0000000..e4591f8 --- /dev/null +++ b/ci/setup-sanitizer-build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -ex + +# Linux updated its ASLR randomization in a way that is incompatible with +# TSAN. See https://github.com/google/sanitizers/issues/1716 +sudo sysctl vm.mmap_rnd_bits=28 @@ -3,7 +3,14 @@ set -e if [ -n "$SKIP_TESTS" ]; then - exit 0 + if [ -z "$SKIP_OFFLINE_TESTS" ]; then SKIP_OFFLINE_TESTS=1; fi + if [ -z "$SKIP_ONLINE_TESTS" ]; then SKIP_ONLINE_TESTS=1; fi + if [ -z "$SKIP_GITDAEMON_TESTS" ]; then SKIP_GITDAEMON_TESTS=1; fi + if [ -z "$SKIP_PROXY_TESTS" ]; then SKIP_PROXY_TESTS=1; fi + if [ -z "$SKIP_NTLM_TESTS" ]; then SKIP_NTLM_TESTS=1; fi + if [ -z "$SKIP_NEGOTIATE_TESTS" ]; then SKIP_NEGOTIATE_TESTS=1; fi + if [ -z "$SKIP_SSH_TESTS" ]; then SKIP_SSH_TESTS=1; fi + if [ -z "$SKIP_FUZZERS" ]; then SKIP_FUZZERS=1; fi fi # Windows doesn't run the NTLM tests properly (yet) @@ -11,6 +18,11 @@ if [[ "$(uname -s)" == MINGW* ]]; then SKIP_NTLM_TESTS=1 fi +# older versions of git don't support push options +if [ -z "$SKIP_PUSHOPTIONS_TESTS" ]; then + export GITTEST_PUSH_OPTIONS=true +fi + SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} BUILD_DIR=$(pwd) BUILD_PATH=${BUILD_PATH:=$PATH} @@ -18,12 +30,24 @@ CTEST=$(which ctest) TMPDIR=${TMPDIR:-/tmp} USER=${USER:-$(whoami)} +GITTEST_SSH_KEYTYPE=${GITTEST_SSH_KEYTYPE:="ecdsa"} + HOME=`mktemp -d ${TMPDIR}/home.XXXXXXXX` export CLAR_HOMEDIR=${HOME} SUCCESS=1 CONTINUE_ON_FAILURE=0 +should_run() { + eval "skip=\${SKIP_${1}}" + [ -z "$skip" \ + -o "$skip" == "no" -o "$skip" == "NO" \ + -o "$skip" == "n" -o "$skip" == "N" \ + -o "$skip" == "false" -o "$skip" == "FALSE" \ + -o "$skip" == "f" -o "$skip" == "F" \ + -o "$skip" == "0" ] +} + cleanup() { echo "Cleaning up..." @@ -78,6 +102,8 @@ run_test() { echo "" echo "Re-running flaky ${1} tests..." echo "" + + sleep 2 fi RETURN_CODE=0 @@ -138,11 +164,12 @@ echo "########################################################################## echo "" -if [ -z "$SKIP_GITDAEMON_TESTS" ]; then +if should_run "GITDAEMON_TESTS"; then echo "Starting git daemon (standard)..." GIT_STANDARD_DIR=`mktemp -d ${TMPDIR}/git_standard.XXXXXXXX` - git init --bare "${GIT_STANDARD_DIR}/test.git" >/dev/null + cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${GIT_STANDARD_DIR}/test.git" git daemon --listen=localhost --export-all --enable=receive-pack --base-path="${GIT_STANDARD_DIR}" "${GIT_STANDARD_DIR}" 2>/dev/null & + GIT_STANDARD_PID=$! echo "Starting git daemon (namespace)..." @@ -158,7 +185,7 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then GIT_SHA256_PID=$! fi -if [ -z "$SKIP_PROXY_TESTS" ]; then +if should_run "PROXY_TESTS"; then curl --location --silent --show-error https://github.com/ethomson/poxyproxy/releases/download/v0.7.0/poxyproxy-0.7.0.jar >poxyproxy.jar echo "Starting HTTP proxy (Basic)..." @@ -170,25 +197,27 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then PROXY_NTLM_PID=$! fi -if [ -z "$SKIP_NTLM_TESTS" -o -z "$SKIP_ONLINE_TESTS" ]; then +if should_run "NTLM_TESTS" || should_run "ONLINE_TESTS"; then curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.6.0/poxygit-0.6.0.jar >poxygit.jar echo "Starting HTTP server..." HTTP_DIR=`mktemp -d ${TMPDIR}/http.XXXXXXXX` - git init --bare "${HTTP_DIR}/test.git" + cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${HTTP_DIR}/test.git" + java -jar poxygit.jar --address 127.0.0.1 --port 9000 --credentials foo:baz --quiet "${HTTP_DIR}" & HTTP_PID=$! fi -if [ -z "$SKIP_SSH_TESTS" ]; then +if should_run "SSH_TESTS"; then echo "Starting SSH server..." SSHD_DIR=`mktemp -d ${TMPDIR}/sshd.XXXXXXXX` - git init --bare "${SSHD_DIR}/test.git" >/dev/null + cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${SSHD_DIR}/test.git" + cat >"${SSHD_DIR}/sshd_config" <<-EOF Port 2222 ListenAddress 0.0.0.0 Protocol 2 - HostKey ${SSHD_DIR}/id_rsa + HostKey ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE} PidFile ${SSHD_DIR}/pid AuthorizedKeysFile ${HOME}/.ssh/authorized_keys LogLevel DEBUG @@ -197,19 +226,21 @@ if [ -z "$SKIP_SSH_TESTS" ]; then PubkeyAuthentication yes ChallengeResponseAuthentication no StrictModes no + HostCertificate ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}.pub + HostKey ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE} # Required here as sshd will simply close connection otherwise UsePAM no EOF - ssh-keygen -t rsa -f "${SSHD_DIR}/id_rsa" -N "" -q + ssh-keygen -t "${GITTEST_SSH_KEYTYPE}" -f "${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}" -N "" -q /usr/sbin/sshd -f "${SSHD_DIR}/sshd_config" -E "${SSHD_DIR}/log" # Set up keys mkdir "${HOME}/.ssh" - ssh-keygen -t rsa -f "${HOME}/.ssh/id_rsa" -N "" -q - cat "${HOME}/.ssh/id_rsa.pub" >>"${HOME}/.ssh/authorized_keys" + ssh-keygen -t "${GITTEST_SSH_KEYTYPE}" -f "${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}" -N "" -q + cat "${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}.pub" >>"${HOME}/.ssh/authorized_keys" while read algorithm key comment; do echo "[localhost]:2222 $algorithm $key" >>"${HOME}/.ssh/known_hosts" - done <"${SSHD_DIR}/id_rsa.pub" + done <"${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}.pub" # Append the github.com keys for the tests that don't override checks. # We ask for ssh-rsa to test that the selection based off of known_hosts @@ -228,7 +259,7 @@ fi # Run the tests that do not require network connectivity. -if [ -z "$SKIP_OFFLINE_TESTS" ]; then +if should_run "OFFLINE_TESTS"; then echo "" echo "##############################################################################" echo "## Running core tests" @@ -259,7 +290,11 @@ if [ -n "$RUN_INVASIVE_TESTS" ]; then unset GITTEST_INVASIVE_SPEED fi -if [ -z "$SKIP_ONLINE_TESTS" ]; then +# the various network tests can fail due to network connectivity problems; +# allow them to retry up to 5 times +export GITTEST_FLAKY_RETRY=5 + +if should_run "ONLINE_TESTS"; then # Run the online tests. The "online" test suite only includes the # default online tests that do not require additional configuration. # The "proxy" and "ssh" test suites require further setup. @@ -288,7 +323,7 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then run_test online_customcert fi -if [ -z "$SKIP_GITDAEMON_TESTS" ]; then +if should_run "GITDAEMON_TESTS"; then echo "" echo "Running gitdaemon (standard) tests" echo "" @@ -316,7 +351,7 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then unset GITTEST_REMOTE_URL fi -if [ -z "$SKIP_PROXY_TESTS" ]; then +if should_run "PROXY_TESTS"; then echo "" echo "Running proxy tests (Basic authentication)" echo "" @@ -342,7 +377,7 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then unset GITTEST_REMOTE_PROXY_PASS fi -if [ -z "$SKIP_NTLM_TESTS" ]; then +if should_run "NTLM_TESTS"; then echo "" echo "Running NTLM tests (IIS emulation)" echo "" @@ -368,7 +403,7 @@ if [ -z "$SKIP_NTLM_TESTS" ]; then unset GITTEST_REMOTE_PASS fi -if [ -z "$SKIP_NEGOTIATE_TESTS" -a -n "$GITTEST_NEGOTIATE_PASSWORD" ]; then +if should_run "NEGOTIATE_TESTS" && -n "$GITTEST_NEGOTIATE_PASSWORD" ; then echo "" echo "Running SPNEGO tests" echo "" @@ -401,13 +436,15 @@ if [ -z "$SKIP_NEGOTIATE_TESTS" -a -n "$GITTEST_NEGOTIATE_PASSWORD" ]; then kdestroy -A fi -if [ -z "$SKIP_SSH_TESTS" ]; then +if should_run "SSH_TESTS"; then export GITTEST_REMOTE_USER=$USER - export GITTEST_REMOTE_SSH_KEY="${HOME}/.ssh/id_rsa" - export GITTEST_REMOTE_SSH_PUBKEY="${HOME}/.ssh/id_rsa.pub" + export GITTEST_REMOTE_SSH_KEY="${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}" + export GITTEST_REMOTE_SSH_PUBKEY="${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}.pub" export GITTEST_REMOTE_SSH_PASSPHRASE="" export GITTEST_REMOTE_SSH_FINGERPRINT="${SSH_FINGERPRINT}" + export GITTEST_SSH_CMD="ssh -i ${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE} -o UserKnownHostsFile=${HOME}/.ssh/known_hosts" + echo "" echo "Running ssh tests" echo "" @@ -424,6 +461,8 @@ if [ -z "$SKIP_SSH_TESTS" ]; then run_test ssh unset GITTEST_REMOTE_URL + unset GITTEST_SSH_CMD + unset GITTEST_REMOTE_USER unset GITTEST_REMOTE_SSH_KEY unset GITTEST_REMOTE_SSH_PUBKEY @@ -431,7 +470,9 @@ if [ -z "$SKIP_SSH_TESTS" ]; then unset GITTEST_REMOTE_SSH_FINGERPRINT fi -if [ -z "$SKIP_FUZZERS" ]; then +unset GITTEST_FLAKY_RETRY + +if should_run "FUZZERS"; then echo "" echo "##############################################################################" echo "## Running fuzzers" diff --git a/cmake/FindIconv.cmake b/cmake/FindIntlIconv.cmake index 9e6ded9..9e6ded9 100644 --- a/cmake/FindIconv.cmake +++ b/cmake/FindIntlIconv.cmake diff --git a/cmake/FindLLHTTP.cmake b/cmake/FindLLHTTP.cmake new file mode 100644 index 0000000..a87d335 --- /dev/null +++ b/cmake/FindLLHTTP.cmake @@ -0,0 +1,39 @@ +# - Try to find llhttp +# +# Defines the following variables: +# +# LLHTTP_FOUND - system has llhttp +# LLHTTP_INCLUDE_DIR - the llhttp include directory +# LLHTTP_LIBRARIES - Link these to use llhttp +# LLHTTP_VERSION_MAJOR - major version +# LLHTTP_VERSION_MINOR - minor version +# LLHTTP_VERSION_STRING - the version of llhttp found + +# Find the header and library +find_path(LLHTTP_INCLUDE_DIR NAMES llhttp.h) +find_library(LLHTTP_LIBRARY NAMES llhttp libllhttp) + +# Found the header, read version +if(LLHTTP_INCLUDE_DIR AND EXISTS "${LLHTTP_INCLUDE_DIR}/llhttp.h") + file(READ "${LLHTTP_INCLUDE_DIR}/llhttp.h" LLHTTP_H) + if(LLHTTP_H) + string(REGEX REPLACE ".*#define[\t ]+LLHTTP_VERSION_MAJOR[\t ]+([0-9]+).*" "\\1" LLHTTP_VERSION_MAJOR "${LLHTTP_H}") + string(REGEX REPLACE ".*#define[\t ]+LLHTTP_VERSION_MINOR[\t ]+([0-9]+).*" "\\1" LLHTTP_VERSION_MINOR "${LLHTTP_H}") + set(LLHTTP_VERSION_STRING "${LLHTTP_VERSION_MAJOR}.${LLHTTP_VERSION_MINOR}") + endif() + unset(LLHTTP_H) +endif() + +# Handle the QUIETLY and REQUIRED arguments and set LLHTTP_FOUND +# to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LLHTTP REQUIRED_VARS LLHTTP_INCLUDE_DIR LLHTTP_LIBRARY) + +# Hide advanced variables +mark_as_advanced(LLHTTP_INCLUDE_DIR LLHTTP_LIBRARY) + +# Set standard variables +if(LLHTTP_FOUND) + set(LLHTTP_LIBRARIES ${LLHTTP_LIBRARY}) + set(LLHTTP_INCLUDE_DIRS ${LLHTTP_INCLUDE_DIR}) +endif() diff --git a/cmake/SelectHTTPParser.cmake b/cmake/SelectHTTPParser.cmake index 955aea3..4fc1f69 100644 --- a/cmake/SelectHTTPParser.cmake +++ b/cmake/SelectHTTPParser.cmake @@ -1,19 +1,32 @@ # Optional external dependency: http-parser -if(USE_HTTP_PARSER STREQUAL "system") +if(USE_HTTP_PARSER STREQUAL "http-parser") find_package(HTTPParser) if(HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) list(APPEND LIBGIT2_SYSTEM_LIBS ${HTTP_PARSER_LIBRARIES}) list(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") - add_feature_info(http-parser ON "http-parser support (system)") + set(GIT_HTTPPARSER_HTTPPARSER 1) + add_feature_info(http-parser ON "using http-parser (system)") else() message(FATAL_ERROR "http-parser support was requested but not found") endif() +elseif(USE_HTTP_PARSER STREQUAL "llhttp") + find_package(LLHTTP) + + if(LLHTTP_FOUND AND LLHTTP_VERSION_MAJOR EQUAL 9) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${LLHTTP_INCLUDE_DIRS}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${LLHTTP_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS "-lllhttp") + set(GIT_HTTPPARSER_LLHTTP 1) + add_feature_info(http-parser ON "using llhttp (system)") + else() + message(FATAL_ERROR "llhttp support was requested but not found") + endif() else() - message(STATUS "http-parser version 2 was not found or disabled; using bundled 3rd-party sources.") - add_subdirectory("${PROJECT_SOURCE_DIR}/deps/http-parser" "${PROJECT_BINARY_DIR}/deps/http-parser") - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/http-parser") - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$<TARGET_OBJECTS:http-parser>") - add_feature_info(http-parser ON "http-parser support (bundled)") + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/llhttp" "${PROJECT_BINARY_DIR}/deps/llhttp") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/llhttp") + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$<TARGET_OBJECTS:llhttp>") + set(GIT_HTTPPARSER_BUILTIN 1) + add_feature_info(http-parser ON "using bundled parser") endif() diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index d149416..d293001 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -55,6 +55,10 @@ if(USE_HTTPS) set(GIT_OPENSSL 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES}) + # Static OpenSSL (lib crypto.a) requires libdl, include it explicitly + if(LINK_WITH_STATIC_LIBRARIES STREQUAL ON) + list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_DL_LIBS}) + endif() list(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) list(APPEND LIBGIT2_PC_REQUIRES "openssl") elseif(USE_HTTPS STREQUAL "mbedTLS") @@ -109,8 +113,8 @@ if(USE_HTTPS) elseif(USE_HTTPS STREQUAL "Schannel") set(GIT_SCHANNEL 1) - list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") - list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") elseif(USE_HTTPS STREQUAL "WinHTTP") set(GIT_WINHTTP 1) @@ -125,8 +129,8 @@ if(USE_HTTPS) list(APPEND LIBGIT2_PC_LIBS "-lwinhttp") endif() - list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") - list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic") set(GIT_OPENSSL 1) set(GIT_OPENSSL_DYNAMIC 1) diff --git a/cmake/SelectSSH.cmake b/cmake/SelectSSH.cmake index 23dfc97..079857f 100644 --- a/cmake/SelectSSH.cmake +++ b/cmake/SelectSSH.cmake @@ -1,6 +1,11 @@ -# Optional external dependency: libssh2 -if(USE_SSH) +if(USE_SSH STREQUAL "exec") + set(GIT_SSH 1) + set(GIT_SSH_EXEC 1) + + add_feature_info(SSH ON "using OpenSSH exec support") +elseif(USE_SSH STREQUAL ON OR USE_SSH STREQUAL "libssh2") find_pkglibraries(LIBSSH2 libssh2) + if(NOT LIBSSH2_FOUND) find_package(LibSSH2) set(LIBSSH2_INCLUDE_DIRS ${LIBSSH2_INCLUDE_DIR}) @@ -12,30 +17,28 @@ if(USE_SSH) if(NOT LIBSSH2_FOUND) message(FATAL_ERROR "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") endif() -endif() -if(LIBSSH2_FOUND) - set(GIT_SSH 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${LIBSSH2_INCLUDE_DIRS}) list(APPEND LIBGIT2_SYSTEM_LIBS ${LIBSSH2_LIBRARIES}) list(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS}) check_library_exists("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS) if(HAVE_LIBSSH2_MEMORY_CREDENTIALS) - set(GIT_SSH_MEMORY_CREDENTIALS 1) + set(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1) endif() -else() - message(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") -endif() -if(WIN32 AND EMBED_SSH_PATH) - file(GLOB SSH_SRC "${EMBED_SSH_PATH}/src/*.c") - list(SORT SSH_SRC) - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS ${SSH_SRC}) + if(WIN32 AND EMBED_SSH_PATH) + file(GLOB SSH_SRC "${EMBED_SSH_PATH}/src/*.c") + list(SORT SSH_SRC) + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS ${SSH_SRC}) + + list(APPEND LIBGIT2_DEPENDENCY_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\"") + endif() - list(APPEND LIBGIT2_DEPENDENCY_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) + set(GIT_SSH_LIBSSH2 1) + add_feature_info(SSH ON "using libssh2") +else() + add_feature_info(SSH OFF "SSH transport support") endif() - -add_feature_info(SSH GIT_SSH "SSH transport support") diff --git a/deps/xdiff/xmerge.c b/deps/xdiff/xmerge.c index af40c88..6ebf73a 100644 --- a/deps/xdiff/xmerge.c +++ b/deps/xdiff/xmerge.c @@ -88,7 +88,7 @@ static int xdl_cleanup_merge(xdmerge_t *c) if (c->mode == 0) count++; next_c = c->next; - free(c); + xdl_free(c); } return count; } @@ -456,7 +456,7 @@ static void xdl_merge_two_conflicts(xdmerge_t *m) 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; - free(next_m); + xdl_free(next_m); } /* diff --git a/docs/changelog.md b/docs/changelog.md index 1748309..a35a389 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,42 +1,408 @@ -v1.7.2 +v1.8.1 ------ +This release primarily includes straightforward bugfixes, as well as +new functionality to have more control over the HTTP User-Agent header. +However, there is an API change from v1.8 that was required for +improved compatibility. + +In v1.8, libgit2 introduced the `report_unchanged ` member in the +`git_fetch_options` structure. We mistakenly introduced this as a +bitfield, which is not suitable for our public API. To correct this +mistake, we have _removed_ the `report_unchanged ` member. To support +the report unchanged tips option, users can set the `update_fetchhead` +member to include the `GIT_REMOTE_UPDATE_REPORT_UNCHANGED` value. + +The libgit2 projects regrets the API change, but this was required to +support cross-platform compatibility. + ## What's Changed -This release fixes three bugs that can cause undefined behavior when given well-crafted inputs, either in input files or over network connections. These bugs may be able to be leveraged to cause denial of service attacks or unauthorized code execution. +### New features -Two of these issues were discovered and reported by security engineers at Amazon Web Services. We thank the AWS Security team for their efforts to identify these issues, provide helpful reproduction cases, and responsibly disclose their findings. +* Allow more control over the user-agent by @ethomson in + https://github.com/libgit2/libgit2/pull/6788 -### Security fixes +### Bug fixes -* transport: safely handle messages with no caps -* revparse: fix parsing bug for trailing `@` -* index: correct index has_dir_name check +* commit: Fix git_commit_create_from_stage without author and + committer by @florianpircher in + https://github.com/libgit2/libgit2/pull/6781 +* process.c: fix environ for macOS by @barracuda156 in + https://github.com/libgit2/libgit2/pull/6792 +* Bounds check for pack index read by @ConradIrwin in + https://github.com/libgit2/libgit2/pull/6796 +* transport: provide a useful error message during cancellation + by @ethomson in https://github.com/libgit2/libgit2/pull/6802 +* transport: support sha256 oids by @ethomson in + https://github.com/libgit2/libgit2/pull/6803 +* Revparse: Correctly accept ref with '@' at the end by @csware in + https://github.com/libgit2/libgit2/pull/6809 +* remote: drop bitfields in git_remote_fetch_options by @ethomson in + https://github.com/libgit2/libgit2/pull/6806 +* examples: fix memory leak in for-each-ref.c by @qaqland in + https://github.com/libgit2/libgit2/pull/6808 +* xdiff: use proper free function by @ethomson in + https://github.com/libgit2/libgit2/pull/6810 +* rand: avoid uninitialized loadavg warnings by @ethomson in + https://github.com/libgit2/libgit2/pull/6812 +* cli: include alloca on illumos / solaris / sunos by @ethomson in + https://github.com/libgit2/libgit2/pull/6813 +* Update git_array allocator to obey strict aliasing rules + by @ethomson in https://github.com/libgit2/libgit2/pull/6814 +* tree: avoid mixed signedness comparison by @ethomson in + https://github.com/libgit2/libgit2/pull/6815 -**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.1...v1.7.2 +### Build and CI improvements -v1.7.1 ------- +* ci: update nightly workflows by @ethomson in + https://github.com/libgit2/libgit2/pull/6773 +* ci: give all nightly builds a unique id by @ethomson in + https://github.com/libgit2/libgit2/pull/6782 +* cmake: remove workaround that isn't compatible with Windows on + ARM by @hackhaslam in https://github.com/libgit2/libgit2/pull/6794 -## What's Changed +### Documentation improvements + +* Docs meta-updates by @ethomson in + https://github.com/libgit2/libgit2/pull/6787 + +### Dependency updates + +* Enable llhttp for HTTP parsing by @sgallagher in + https://github.com/libgit2/libgit2/pull/6713 + +## New Contributors + +* @florianpircher made their first contribution in + https://github.com/libgit2/libgit2/pull/6781 +* @barracuda156 made their first contribution in + https://github.com/libgit2/libgit2/pull/6792 +* @sgallagher made their first contribution in + https://github.com/libgit2/libgit2/pull/6713 +* @ConradIrwin made their first contribution in + https://github.com/libgit2/libgit2/pull/6796 +* @qaqland made their first contribution in + https://github.com/libgit2/libgit2/pull/6808 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.8.0...v1.8.1 + +v1.8 +---- + +This is release v1.8.0, "Das Fliegende Klassenzimmer". This release +includes optional, experimental support for invoking OpenSSH to fetch +and push, an easier mechanism to perform the default behavior of +`git commit`, and has many improvements for worktrees. This release +also includes many other new features and bugfixes. + +## Major changes + +* **Executable SSH (OpenSSH) support** + libgit2 can now invoke the command-line OpenSSH to fetch from and push + to remotes over SSH. This support takes the place of libssh2 support. + To use it, configure libgit2 with `cmake -DUSE_SSH=exec`, and please + report any problems that you discover. By @ethomson in + https://github.com/libgit2/libgit2/pull/6617 + +* **Simplified commit creation** + The `git_commit_create_from_stage` API was introduced to allow users to + better emulate the behavior of `git commit` without needing to provide + unnecessary information. The current state of the index is committed to + the current branch. By @ethomson in + https://github.com/libgit2/libgit2/pull/6716 + +* **Worktree improvements** + A number of worktree improvements have been made for better + compatibility with core git. First, libgit2 now understands per-worktree + references, thanks to @csware in + https://github.com/libgit2/libgit2/pull/6387. Worktree-specific + configuration is now supported, thanks to @vermiculus in + https://github.com/libgit2/libgit2/pull/6202. And improved compatibility + with `git worktree add` is now supported, thanks to @herrerog in + https://github.com/libgit2/libgit2/pull/5319. + +## Breaking changes + +* **Adding `WORKTREE` configuration level** (ABI breaking change) + To support worktree configurations at the appropriate level (higher + priority than local configuration, but lower priority than app-specific + configuration), the `GIT_CONFIG_LEVEL_WORKTREE` level was introduced at + priority 6. `GIT_CONFIG_LEVEL_APP` now begins at priority 7. + +* **Changes to `git_config_entry`** (ABI breaking change) + The `git_config_entry` structure now contains information about the + `backend_type` and `origin_path`. The unused `payload` value has been + removed. + +* **`git_push_options` includes remote push options** (ABI breaking change) + The `git_push_options` structure now contains a value for remote push + options. + +## Other changes + +### New features + +* config: provide an "origin" for config entries by @ethomson in + https://github.com/libgit2/libgit2/pull/6615 +* cli: add a `git config` command by @ethomson in + https://github.com/libgit2/libgit2/pull/6616 +* Add OpenSSH support by @ethomson in + https://github.com/libgit2/libgit2/pull/6617 +* remote: optionally report unchanged tips by @ethomson in + https://github.com/libgit2/libgit2/pull/6645 +* Support setting oid type for in-memory repositories by @kcsaul in + https://github.com/libgit2/libgit2/pull/6671 +* cli: add `index-pack` command by @ethomson in + https://github.com/libgit2/libgit2/pull/6681 +* Add `git_repository_commit_parents` to identify the parents of the next + commit given the repository state by @ethomson in + https://github.com/libgit2/libgit2/pull/6707 +* commit: introduce `git_commit_create_from_stage` by @ethomson in + https://github.com/libgit2/libgit2/pull/6716 +* set SSH timeout by @vafada in + https://github.com/libgit2/libgit2/pull/6721 +* Implement push options on push by @russell in + https://github.com/libgit2/libgit2/pull/6439 +* Support index.skipHash true config by @parnic in + https://github.com/libgit2/libgit2/pull/6738 +* worktree: mimic 'git worktree add' behavior. by @herrerog in + https://github.com/libgit2/libgit2/pull/5319 +* Support the extension for worktree-specific config by @vermiculus in + https://github.com/libgit2/libgit2/pull/6202 +* Separate config reader and writer backend priorities (for worktree + configs) by @ethomson in https://github.com/libgit2/libgit2/pull/6756 +* fetch: enable deepening/shortening shallow clones by @kempniu in + https://github.com/libgit2/libgit2/pull/6662 ### Bug fixes -* proxy: Return an error for invalid proxy URLs instead of crashing. by @lrm29 in https://github.com/libgit2/libgit2/pull/6597 -* ssh: fix known_hosts leak in _git_ssh_setup_conn by @steven9724 in https://github.com/libgit2/libgit2/pull/6599 -* repository: make cleanup safe for re-use with grafts by @carlosmn in https://github.com/libgit2/libgit2/pull/6600 -* fix: Add missing include for oidarray. by @dvzrv in https://github.com/libgit2/libgit2/pull/6608 -* Revert "CMake: Search for ssh2 instead of libssh2." by @ethomson in https://github.com/libgit2/libgit2/pull/6619 +* repository: make cleanup safe for re-use with grafts by @carlosmn in + https://github.com/libgit2/libgit2/pull/6600 +* fix: Add missing include for `oidarray`. by @dvzrv in + https://github.com/libgit2/libgit2/pull/6608 +* ssh: fix `known_hosts` leak in `_git_ssh_setup_conn` by @steven9724 in + https://github.com/libgit2/libgit2/pull/6599 +* proxy: Return an error for invalid proxy URLs instead of crashing by + @lrm29 in https://github.com/libgit2/libgit2/pull/6597 +* errors: refactoring - never return `NULL` in `git_error_last()` by + @ethomson in https://github.com/libgit2/libgit2/pull/6625 +* Reject potential option injections over ssh by @carlosmn in + https://github.com/libgit2/libgit2/pull/6636 +* remote: fix memory leak in `git_remote_download()` by @7Ji in + https://github.com/libgit2/libgit2/pull/6651 +* git2: Fix crash when called w/o parameters by @csware in + https://github.com/libgit2/libgit2/pull/6673 +* Avoid macro redefinition of `ENABLE_INTSAFE_SIGNED_FUNCTIONS` by @csware + in https://github.com/libgit2/libgit2/pull/6666 +* util: suppress some uninitialized variable warnings by @boretrk in + https://github.com/libgit2/libgit2/pull/6659 +* push: set generic error in `push_negotiation` cb by @ethomson in + https://github.com/libgit2/libgit2/pull/6675 +* process: test `/usr/bin/false` on BSDs by @ethomson in + https://github.com/libgit2/libgit2/pull/6677 +* clone: don't mix up "http://url" with "http:/url" when figuring out if we + should do a local clone by @boretrk in + https://github.com/libgit2/libgit2/pull/6361 +* Several compatibility fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6678 +* Git blame buffer gives the wrong result in many cases where there are + by @thosey in https://github.com/libgit2/libgit2/pull/6572 +* Fix 'path cannot exist in repository' during diff for in-memory repository + by @kcsaul in https://github.com/libgit2/libgit2/pull/6683 +* process: don't try to close the status by @ethomson in + https://github.com/libgit2/libgit2/pull/6693 +* Minor bug fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6695 +* Bypass shallow clone support for in-memory repositories by @kcsaul in + https://github.com/libgit2/libgit2/pull/6684 +* examples: use `unsigned` int for bitfields by @ethomson in + https://github.com/libgit2/libgit2/pull/6699 +* Fix some bugs caught by UBscan by @ethomson in + https://github.com/libgit2/libgit2/pull/6700 +* `git_diff_find_similar` doesn't always remove unmodified deltas by @yori + in https://github.com/libgit2/libgit2/pull/6642 +* httpclient: clear `client->parser.data` after use by @ethomson in + https://github.com/libgit2/libgit2/pull/6705 +* Do not normalize `safe.directory` paths by @csware in + https://github.com/libgit2/libgit2/pull/6668 +* clone: don't swallow error in `should_checkout` by @ethomson in + https://github.com/libgit2/libgit2/pull/6727 +* Correct index add directory/file conflict detection by @ethomson in + https://github.com/libgit2/libgit2/pull/6729 +* Correct `git_revparse_single` and add revparse fuzzing by @ethomson in + https://github.com/libgit2/libgit2/pull/6730 +* config: properly delete or rename section containing multivars by + @samueltardieu in https://github.com/libgit2/libgit2/pull/6723 +* revparse: ensure bare '@' is truly bare by @ethomson in + https://github.com/libgit2/libgit2/pull/6742 +* repo: ensure we can initialize win32 paths by @ethomson in + https://github.com/libgit2/libgit2/pull/6743 +* Swap `GIT_DIFF_LINE_(ADD|DEL)_EOFNL` to match other Diffs by @xphoniex in + https://github.com/libgit2/libgit2/pull/6240 +* diff: fix test for SHA256 support in `diff_from_buffer` by @ethomson in + https://github.com/libgit2/libgit2/pull/6745 +* http: support empty http.proxy config setting by @ethomson in + https://github.com/libgit2/libgit2/pull/6744 +* More `safe.directory` improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6739 +* Ensure that completely ignored diff is empty by @ethomson in + https://github.com/libgit2/libgit2/pull/5893 +* Fix broken regexp that matches submodule names containing ".path" by + @csware in https://github.com/libgit2/libgit2/pull/6749 +* Fix memory leaks by @csware in + https://github.com/libgit2/libgit2/pull/6748 +* Make `refdb_fs` (hopefully) fully aware of per worktree refs by @csware in + https://github.com/libgit2/libgit2/pull/6387 +* fix log example by @albfan in https://github.com/libgit2/libgit2/pull/6359 +* fetch: fail on depth for local transport by @ethomson in + https://github.com/libgit2/libgit2/pull/6757 +* Fix message trailer parsing by @ethomson in + https://github.com/libgit2/libgit2/pull/6761 +* config: correct fetching the `HIGHEST_LEVEL` config by @ethomson in + https://github.com/libgit2/libgit2/pull/6766 +* Avoid some API breaking changes in v1.8 by @ethomson in + https://github.com/libgit2/libgit2/pull/6768 + +### Build and CI improvements + +* meta: update version numbers to v1.8 by @ethomson in + https://github.com/libgit2/libgit2/pull/6596 +* Revert "CMake: Search for ssh2 instead of libssh2." by @ethomson in + https://github.com/libgit2/libgit2/pull/6619 +* cmake: fix openssl build on win32 by @lazka in + https://github.com/libgit2/libgit2/pull/6626 +* ci: retry flaky online tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6628 +* ci: update to macOS 12 by @ethomson in + https://github.com/libgit2/libgit2/pull/6629 +* Use `#!/bin/bash` for script with bash-specific commands by @roehling in + https://github.com/libgit2/libgit2/pull/6581 +* ci: overwrite nonsense in `/usr/local` during macOS setup by @ethomson in + https://github.com/libgit2/libgit2/pull/6664 +* release: add a compatibility label by @ethomson in + https://github.com/libgit2/libgit2/pull/6676 +* actions: set permissions by @ethomson in + https://github.com/libgit2/libgit2/pull/6680 +* cmake: rename FindIconv to avoid collision with cmake by @ethomson in + https://github.com/libgit2/libgit2/pull/6682 +* ci: allow workflows to read and write packages by @ethomson in + https://github.com/libgit2/libgit2/pull/6687 +* ci: allow workflows to push changes by @ethomson in + https://github.com/libgit2/libgit2/pull/6688 +* tests: remove test for strcasecmp by @boretrk in + https://github.com/libgit2/libgit2/pull/6691 +* CI fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6694 +* ci: improvements to prepare for Cygwin support by @ethomson in + https://github.com/libgit2/libgit2/pull/6696 +* Yet more CI improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6697 +* Fix nightly builds by @ethomson in + https://github.com/libgit2/libgit2/pull/6709 +* Benchmarks: add a site to view results by @ethomson in + https://github.com/libgit2/libgit2/pull/6715 +* `GIT_RAND_GETENTROPY`: do not include `sys/random.h` by @semarie in + https://github.com/libgit2/libgit2/pull/6736 +* add dl to `LIBGIT2_SYSTEM_LIBS` by @christopherfujino in + https://github.com/libgit2/libgit2/pull/6631 +* meta: add dependency tag to release.yml by @ethomson in + https://github.com/libgit2/libgit2/pull/6740 +* CI: fix our nightlies by @ethomson in + https://github.com/libgit2/libgit2/pull/6751 +* trace: Re-enable tests as tracing is now enabled by default by @lrm29 in + https://github.com/libgit2/libgit2/pull/6752 +* tests: don't free an unininitialized repo by @ethomson in + https://github.com/libgit2/libgit2/pull/6763 +* ci: reduce ASLR randomization for TSAN by @ethomson in + https://github.com/libgit2/libgit2/pull/6764 +* packbuilder: adjust nondeterministic tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6762 +* Allow libgit2 to be compiled with mbedtls3. by @adamharrison in + https://github.com/libgit2/libgit2/pull/6759 +* build: update to latest actions versions by @ethomson in + https://github.com/libgit2/libgit2/pull/6765 +* ctype: cast characters to unsigned when classifying characters by + @boretrk in https://github.com/libgit2/libgit2/pull/6679 and + @ethomson in https://github.com/libgit2/libgit2/pull/6770 +* valgrind: suppress OpenSSL warnings by @ethomson in https://github.com/libgit2/libgit2/pull/6769 + +### Documentation improvements + +* README.md: Fix link to conan packages by @lrm29 in + https://github.com/libgit2/libgit2/pull/6621 +* README: replace gmaster with GitButler by @ethomson in + https://github.com/libgit2/libgit2/pull/6692 +* blame example: Fix support for line range in CLI by @wetneb in + https://github.com/libgit2/libgit2/pull/6638 +* Support authentication in push example by @pluehne in + https://github.com/libgit2/libgit2/pull/5904 +* docs: fix mistake in attr.h by @DavHau in + https://github.com/libgit2/libgit2/pull/6714 +* Fix broken links by @csware in + https://github.com/libgit2/libgit2/pull/6747 + +### Platform compatibility fixes + +* stransport: macOS: replace `errSSLNetworkTimeout`, with hard-coded + value by @mascguy in https://github.com/libgit2/libgit2/pull/6610 + +### Git compatibility fixes + +* Do not trim dots from usernames by @georgthegreat in + https://github.com/libgit2/libgit2/pull/6657 +* merge: fix incorrect rename detection for empty files by @herrerog in + https://github.com/libgit2/libgit2/pull/6717 -### Compatibility improvements +### Dependency updates -* stransport: macOS: replace errSSLNetworkTimeout, with hard-coded value by @mascguy in https://github.com/libgit2/libgit2/pull/6610 +* zlib: upgrade bundled zlib to v1.3 by @ethomson in + https://github.com/libgit2/libgit2/pull/6698 +* ntlmclient: update to latest upstream ntlmclient by @ethomson in + https://github.com/libgit2/libgit2/pull/6704 ## New Contributors -* @dvzrv made their first contribution in https://github.com/libgit2/libgit2/pull/6608 -* @steven9724 made their first contribution in https://github.com/libgit2/libgit2/pull/6599 -**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.0...v1.7.1 +* @dvzrv made their first contribution in + https://github.com/libgit2/libgit2/pull/6608 +* @mascguy made their first contribution in + https://github.com/libgit2/libgit2/pull/6610 +* @steven9724 made their first contribution in + https://github.com/libgit2/libgit2/pull/6599 +* @lazka made their first contribution in + https://github.com/libgit2/libgit2/pull/6626 +* @roehling made their first contribution in + https://github.com/libgit2/libgit2/pull/6581 +* @7Ji made their first contribution in + https://github.com/libgit2/libgit2/pull/6651 +* @kempniu made their first contribution in + https://github.com/libgit2/libgit2/pull/6662 +* @thosey made their first contribution in + https://github.com/libgit2/libgit2/pull/6572 +* @wetneb made their first contribution in + https://github.com/libgit2/libgit2/pull/6638 +* @yori made their first contribution in + https://github.com/libgit2/libgit2/pull/6642 +* @pluehne made their first contribution in + https://github.com/libgit2/libgit2/pull/5904 +* @DavHau made their first contribution in + https://github.com/libgit2/libgit2/pull/6714 +* @vafada made their first contribution in + https://github.com/libgit2/libgit2/pull/6721 +* @semarie made their first contribution in + https://github.com/libgit2/libgit2/pull/6736 +* @christopherfujino made their first contribution in + https://github.com/libgit2/libgit2/pull/6631 +* @parnic made their first contribution in + https://github.com/libgit2/libgit2/pull/6738 +* @samueltardieu made their first contribution in + https://github.com/libgit2/libgit2/pull/6723 +* @xphoniex made their first contribution in + https://github.com/libgit2/libgit2/pull/6240 +* @adamharrison made their first contribution in + https://github.com/libgit2/libgit2/pull/6759 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.0...v1.8.0 v1.7 ---- diff --git a/examples/README.md b/examples/README.md index 769c4b2..0f1f253 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,8 +15,8 @@ so there are no restrictions on their use. For annotated HTML versions, see the "Examples" section of: - http://libgit2.github.com/libgit2 + https://libgit2.org/libgit2 such as: - http://libgit2.github.com/libgit2/ex/HEAD/general.html + https://libgit2.org/libgit2/ex/HEAD/general.html diff --git a/examples/args.h b/examples/args.h index d626f98..4db0493 100644 --- a/examples/args.h +++ b/examples/args.h @@ -8,7 +8,7 @@ struct args_info { int argc; char **argv; int pos; - int opts_done : 1; /**< Did we see a -- separator */ + unsigned int opts_done : 1; /**< Did we see a -- separator */ }; #define ARGS_INFO_INIT { argc, argv, 0, 0 } #define ARGS_CURRENT(args) args->argv[args->pos] diff --git a/examples/blame.c b/examples/blame.c index 77087a5..0996e7a 100644 --- a/examples/blame.c +++ b/examples/blame.c @@ -47,6 +47,10 @@ int lg2_blame(git_repository *repo, int argc, char *argv[]) if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT; + if (o.start_line && o.end_line) { + blameopts.min_line = o.start_line; + blameopts.max_line = o.end_line; + } /** * The commit range comes in "committish" form. Use the rev-parse API to diff --git a/examples/checkout.c b/examples/checkout.c index ac7b742..82567cd 100644 --- a/examples/checkout.c +++ b/examples/checkout.c @@ -35,9 +35,9 @@ */ typedef struct { - int force : 1; - int progress : 1; - int perf : 1; + unsigned int force : 1; + unsigned int progress : 1; + unsigned int perf : 1; } checkout_options; static void print_usage(void) diff --git a/examples/for-each-ref.c b/examples/for-each-ref.c index a070674..f745bc3 100644 --- a/examples/for-each-ref.c +++ b/examples/for-each-ref.c @@ -25,6 +25,8 @@ static int show_ref(git_reference *ref, void *data) git_object_type2string(git_object_type(obj)), git_reference_name(ref)); + git_object_free(obj); + git_reference_free(ref); if (resolved) git_reference_free(resolved); return 0; diff --git a/examples/general.c b/examples/general.c index 7f44cd7..0275f84 100644 --- a/examples/general.c +++ b/examples/general.c @@ -31,8 +31,8 @@ * Git Internals that you will need to know to work with Git at this level, * check out [Chapter 10][pg] of the Pro Git book. * - * [lg]: http://libgit2.github.com - * [ap]: http://libgit2.github.com/libgit2 + * [lg]: https://libgit2.org + * [ap]: https://libgit2.org/libgit2 * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain */ @@ -97,7 +97,7 @@ int lg2_general(git_repository *repo, int argc, char** argv) * * (Try running this program against tests/resources/testrepo.git.) * - * [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + * [me]: https://libgit2.org/libgit2/#HEAD/group/repository */ repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; @@ -173,7 +173,7 @@ static void oid_parsing(git_oid *oid) * working with raw objects, we'll need to get this structure from the * repository. * - * [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb + * [odb]: https://libgit2.org/libgit2/#HEAD/group/odb */ static void object_database(git_repository *repo, git_oid *oid) { @@ -262,7 +262,7 @@ static void object_database(git_repository *repo, git_oid *oid) * of them here. You can read about the other ones in the [commit API * docs][cd]. * - * [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit + * [cd]: https://libgit2.org/libgit2/#HEAD/group/commit */ static void commit_writing(git_repository *repo) { @@ -347,7 +347,7 @@ static void commit_writing(git_repository *repo) * data in the commit - the author (name, email, datetime), committer * (same), tree, message, encoding and parent(s). * - * [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit + * [pco]: https://libgit2.org/libgit2/#HEAD/group/commit */ static void commit_parsing(git_repository *repo) { @@ -418,7 +418,7 @@ static void commit_parsing(git_repository *repo) * functions very similarly to the commit lookup, parsing and creation * methods, since the objects themselves are very similar. * - * [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag + * [tm]: https://libgit2.org/libgit2/#HEAD/group/tag */ static void tag_parsing(git_repository *repo) { @@ -472,7 +472,7 @@ static void tag_parsing(git_repository *repo) * object type in Git, but a useful structure for parsing and traversing * tree entries. * - * [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree + * [tp]: https://libgit2.org/libgit2/#HEAD/group/tree */ static void tree_parsing(git_repository *repo) { @@ -536,7 +536,7 @@ static void tree_parsing(git_repository *repo) * from disk and writing it to the db and getting the oid back so you * don't have to do all those steps yourself. * - * [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob + * [ba]: https://libgit2.org/libgit2/#HEAD/group/blob */ static void blob_parsing(git_repository *repo) { @@ -578,7 +578,7 @@ static void blob_parsing(git_repository *repo) * that were ancestors of (reachable from) a given starting point. This * can allow you to create `git log` type functionality. * - * [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk + * [rw]: https://libgit2.org/libgit2/#HEAD/group/revwalk */ static void revwalking(git_repository *repo) { @@ -643,7 +643,7 @@ static void revwalking(git_repository *repo) * The [index file API][gi] allows you to read, traverse, update and write * the Git index file (sometimes thought of as the staging area). * - * [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index + * [gi]: https://libgit2.org/libgit2/#HEAD/group/index */ static void index_walking(git_repository *repo) { @@ -687,7 +687,7 @@ static void index_walking(git_repository *repo) * references such as branches, tags and remote references (everything in * the .git/refs directory). * - * [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference + * [ref]: https://libgit2.org/libgit2/#HEAD/group/reference */ static void reference_listing(git_repository *repo) { @@ -740,7 +740,7 @@ static void reference_listing(git_repository *repo) * The [config API][config] allows you to list and update config values * in any of the accessible config file locations (system, global, local). * - * [config]: http://libgit2.github.com/libgit2/#HEAD/group/config + * [config]: https://libgit2.org/libgit2/#HEAD/group/config */ static void config_files(const char *repo_path, git_repository* repo) { diff --git a/examples/log.c b/examples/log.c index 4b0a95d..62a6eb5 100644 --- a/examples/log.c +++ b/examples/log.c @@ -50,6 +50,7 @@ static int add_revision(struct log_state *s, const char *revstr); /** log_options holds other command line options that affect log output */ struct log_options { int show_diff; + int show_oneline; int show_log_size; int skip, limit; int min_parents, max_parents; @@ -81,9 +82,11 @@ int lg2_log(git_repository *repo, int argc, char *argv[]) git_commit *commit = NULL; git_pathspec *ps = NULL; + memset(&s, 0, sizeof(s)); + /** Parse arguments and set up revwalker. */ - last_arg = parse_options(&s, &opt, argc, argv); s.repo = repo; + last_arg = parse_options(&s, &opt, argc, argv); diffopts.pathspec.strings = &argv[last_arg]; diffopts.pathspec.count = argc - last_arg; @@ -335,34 +338,45 @@ static void print_commit(git_commit *commit, struct log_options *opts) const char *scan, *eol; git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); - printf("commit %s\n", buf); - if (opts->show_log_size) { - printf("log size %d\n", (int)strlen(git_commit_message(commit))); - } + if (opts->show_oneline) { + printf("%s ", buf); + } else { + printf("commit %s\n", buf); - if ((count = (int)git_commit_parentcount(commit)) > 1) { - printf("Merge:"); - for (i = 0; i < count; ++i) { - git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); - printf(" %s", buf); + if (opts->show_log_size) { + printf("log size %d\n", (int)strlen(git_commit_message(commit))); + } + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); } - printf("\n"); - } - if ((sig = git_commit_author(commit)) != NULL) { - printf("Author: %s <%s>\n", sig->name, sig->email); - print_time(&sig->when, "Date: "); + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); } - printf("\n"); for (scan = git_commit_message(commit); scan && *scan; ) { for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; - printf(" %.*s\n", (int)(eol - scan), scan); + if (opts->show_oneline) + printf("%.*s\n", (int)(eol - scan), scan); + else + printf(" %.*s\n", (int)(eol - scan), scan); scan = *eol ? eol + 1 : NULL; + if (opts->show_oneline) + break; } - printf("\n"); + if (!opts->show_oneline) + printf("\n"); } /** Helper to find how many files in a commit changed from its nth parent. */ @@ -407,8 +421,6 @@ static int parse_options( struct log_state *s, struct log_options *opt, int argc, char **argv) { struct args_info args = ARGS_INFO_INIT; - - memset(s, 0, sizeof(*s)); s->sorting = GIT_SORT_TIME; memset(opt, 0, sizeof(*opt)); @@ -424,7 +436,7 @@ static int parse_options( else /** Try failed revision parse as filename. */ break; - } else if (!match_arg_separator(&args)) { + } else if (match_arg_separator(&args)) { break; } else if (!strcmp(a, "--date-order")) @@ -474,6 +486,8 @@ static int parse_options( opt->show_diff = 1; else if (!strcmp(a, "--log-size")) opt->show_log_size = 1; + else if (!strcmp(a, "--oneline")) + opt->show_oneline = 1; else usage("Unsupported argument", a); } diff --git a/examples/merge.c b/examples/merge.c index 460c06a..718c767 100644 --- a/examples/merge.c +++ b/examples/merge.c @@ -30,7 +30,7 @@ struct merge_options { git_annotated_commit **annotated; size_t annotated_count; - int no_commit : 1; + unsigned int no_commit : 1; }; static void print_usage(void) @@ -263,7 +263,7 @@ static int create_merge_commit(git_repository *repo, git_index *index, struct me sign, sign, NULL, msg, tree, - opts->annotated_count + 1, (const git_commit **)parents); + opts->annotated_count + 1, parents); check_lg2(err, "failed to create commit", NULL); /* We're done merging, cleanup the repository state */ diff --git a/examples/push.c b/examples/push.c index bcf3076..5113eed 100644 --- a/examples/push.c +++ b/examples/push.c @@ -32,6 +32,7 @@ /** Entry point for this command */ int lg2_push(git_repository *repo, int argc, char **argv) { git_push_options options; + git_remote_callbacks callbacks; git_remote* remote = NULL; char *refspec = "refs/heads/master"; const git_strarray refspecs = { @@ -47,7 +48,11 @@ int lg2_push(git_repository *repo, int argc, char **argv) { check_lg2(git_remote_lookup(&remote, repo, "origin" ), "Unable to lookup remote", NULL); + check_lg2(git_remote_init_callbacks(&callbacks, GIT_REMOTE_CALLBACKS_VERSION), "Error initializing remote callbacks", NULL); + callbacks.credentials = cred_acquire_cb; + check_lg2(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION ), "Error initializing push", NULL); + options.callbacks = callbacks; check_lg2(git_remote_push(remote, &refspecs, &options), "Error pushing", NULL); diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt index a2c19ed..01f0f51 100644 --- a/fuzzers/CMakeLists.txt +++ b/fuzzers/CMakeLists.txt @@ -12,10 +12,13 @@ foreach(fuzz_target_src ${SRC_FUZZERS}) string(REPLACE ".c" "" fuzz_target_name ${fuzz_target_src}) string(REPLACE "_fuzzer" "" fuzz_name ${fuzz_target_name}) - set(${fuzz_target_name}_SOURCES ${fuzz_target_src} ${LIBGIT2_OBJECTS}) + set(${fuzz_target_name}_SOURCES + ${fuzz_target_src} "fuzzer_utils.c" ${LIBGIT2_OBJECTS}) + if(USE_STANDALONE_FUZZERS) list(APPEND ${fuzz_target_name}_SOURCES "standalone_driver.c") endif() + add_executable(${fuzz_target_name} ${${fuzz_target_name}_SOURCES}) set_target_properties(${fuzz_target_name} PROPERTIES C_STANDARD 90) diff --git a/fuzzers/config_file_fuzzer.c b/fuzzers/config_file_fuzzer.c index 890adbf..7630369 100644 --- a/fuzzers/config_file_fuzzer.c +++ b/fuzzers/config_file_fuzzer.c @@ -43,7 +43,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) goto out; } - if ((err = git_config_backend_from_string(&backend, (const char*)data, size)) != 0) { + if ((err = git_config_backend_from_string(&backend, (const char*)data, size, NULL)) != 0) { goto out; } if ((err = git_config_add_backend(cfg, backend, 0, NULL, 0)) != 0) { diff --git a/fuzzers/corpora/revparse/head b/fuzzers/corpora/revparse/head new file mode 100644 index 0000000..e5517e4 --- /dev/null +++ b/fuzzers/corpora/revparse/head @@ -0,0 +1 @@ +HEAD
\ No newline at end of file diff --git a/fuzzers/corpora/revparse/revat b/fuzzers/corpora/revparse/revat new file mode 100644 index 0000000..382ffc0 --- /dev/null +++ b/fuzzers/corpora/revparse/revat @@ -0,0 +1 @@ +xxxxxxxxxxxxxxxx@
\ No newline at end of file diff --git a/fuzzers/download_refs_fuzzer.c b/fuzzers/download_refs_fuzzer.c index ff95cd1..c2b80cc 100644 --- a/fuzzers/download_refs_fuzzer.c +++ b/fuzzers/download_refs_fuzzer.c @@ -16,6 +16,7 @@ #include "futils.h" #include "standalone_driver.h" +#include "fuzzer_utils.h" #define UNUSED(x) (void)(x) @@ -157,33 +158,10 @@ static int fuzzer_transport_cb(git_transport **out, git_remote *owner, void *par return git_transport_smart(out, owner, &def); } -static void fuzzer_git_abort(const char *op) -{ - const git_error *err = git_error_last(); - fprintf(stderr, "unexpected libgit error: %s: %s\n", - op, err ? err->message : "<none>"); - abort(); -} - int LLVMFuzzerInitialize(int *argc, char ***argv) { -#if defined(_WIN32) - char tmpdir[MAX_PATH], path[MAX_PATH]; - - if (GetTempPath((DWORD)sizeof(tmpdir), tmpdir) == 0) - abort(); - - if (GetTempFileName(tmpdir, "lg2", 1, path) == 0) - abort(); - - if (git_futils_mkdir(path, 0700, 0) < 0) - abort(); -#else - char path[] = "/tmp/git2.XXXXXX"; - - if (mkdtemp(path) != path) - abort(); -#endif + UNUSED(argc); + UNUSED(argv); if (git_libgit2_init() < 0) abort(); @@ -191,12 +169,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) if (git_libgit2_opts(GIT_OPT_SET_PACK_MAX_OBJECTS, 10000000) < 0) abort(); - UNUSED(argc); - UNUSED(argv); - - if (git_repository_init(&repo, path, 1) < 0) - fuzzer_git_abort("git_repository_init"); - + repo = fuzzer_repo_init(); return 0; } diff --git a/fuzzers/fuzzer_utils.c b/fuzzers/fuzzer_utils.c new file mode 100644 index 0000000..cde5065 --- /dev/null +++ b/fuzzers/fuzzer_utils.c @@ -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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "git2.h" +#include "futils.h" + +#include "fuzzer_utils.h" + +void fuzzer_git_abort(const char *op) +{ + const git_error *err = git_error_last(); + fprintf(stderr, "unexpected libgit error: %s: %s\n", + op, err ? err->message : "<none>"); + abort(); +} + +git_repository *fuzzer_repo_init(void) +{ + git_repository *repo; + +#if defined(_WIN32) + char tmpdir[MAX_PATH], path[MAX_PATH]; + + if (GetTempPath((DWORD)sizeof(tmpdir), tmpdir) == 0) + abort(); + + if (GetTempFileName(tmpdir, "lg2", 1, path) == 0) + abort(); + + if (git_futils_mkdir(path, 0700, 0) < 0) + abort(); +#else + char path[] = "/tmp/git2.XXXXXX"; + + if (mkdtemp(path) != path) + abort(); +#endif + + if (git_repository_init(&repo, path, 1) < 0) + fuzzer_git_abort("git_repository_init"); + + return repo; +} diff --git a/src/libgit2/transports/ssh.h b/fuzzers/fuzzer_utils.h index d3e741f..6b67c9a 100644 --- a/src/libgit2/transports/ssh.h +++ b/fuzzers/fuzzer_utils.h @@ -4,11 +4,11 @@ * This file 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" +#ifndef INCLUDE_fuzzer_utils_h__ +#define INCLUDE_fuzzer_utils_h__ -int git_transport_ssh_global_init(void); +extern void fuzzer_git_abort(const char *op); +extern git_repository *fuzzer_repo_init(void); #endif diff --git a/fuzzers/revparse_fuzzer.c b/fuzzers/revparse_fuzzer.c new file mode 100644 index 0000000..37c22e2 --- /dev/null +++ b/fuzzers/revparse_fuzzer.c @@ -0,0 +1,52 @@ +/* + * libgit2 revparse fuzzer target. + * + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> +#include <string.h> + +#include "git2.h" + +#include "standalone_driver.h" +#include "fuzzer_utils.h" + +#define UNUSED(x) (void)(x) + +static git_repository *repo; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + UNUSED(argc); + UNUSED(argv); + + if (git_libgit2_init() < 0) + abort(); + + if (git_libgit2_opts(GIT_OPT_SET_PACK_MAX_OBJECTS, 10000000) < 0) + abort(); + + repo = fuzzer_repo_init(); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + git_object *obj = NULL; + char *c; + + if ((c = calloc(1, size + 1)) == NULL) + abort(); + + memcpy(c, data, size); + + git_revparse_single(&obj, repo, c); + git_object_free(obj); + free(c); + + return 0; +} diff --git a/include/git2/attr.h b/include/git2/attr.h index 0c83872..69929b3 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -116,14 +116,12 @@ GIT_EXTERN(git_attr_value_t) git_attr_value(const char *attr); */ #define GIT_ATTR_CHECK_FILE_THEN_INDEX 0 #define GIT_ATTR_CHECK_INDEX_THEN_FILE 1 -#define GIT_ATTR_CHECK_INDEX_ONLY 2 +#define GIT_ATTR_CHECK_INDEX_ONLY 2 /** * Check attribute flags: controlling extended attribute behavior. * * Normally, attribute checks include looking in the /etc (or system - * equivalent) directory for a `gitattributes` file. Passing this - * flag will cause attribute checks to ignore that file. * equivalent) directory for a `gitattributes` file. Passing the * `GIT_ATTR_CHECK_NO_SYSTEM` flag will cause attribute checks to * ignore that file. diff --git a/include/git2/commit.h b/include/git2/commit.h index 67170cb..ef38c66 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -366,7 +366,7 @@ GIT_EXTERN(int) git_commit_create( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[]); + git_commit * const parents[]); /** * Create new commit in the repository using a variable argument list. @@ -394,6 +394,49 @@ GIT_EXTERN(int) git_commit_create_v( size_t parent_count, ...); +typedef struct { + unsigned int version; + + /** + * Flags for creating the commit. + * + * If `allow_empty_commit` is specified, a commit with no changes + * from the prior commit (and "empty" commit) is allowed. Otherwise, + * commit creation will be stopped. + */ + unsigned int allow_empty_commit : 1; + + /** The commit author, or NULL for the default. */ + const git_signature *author; + + /** The committer, or NULL for the default. */ + const git_signature *committer; + + /** Encoding for the commit message; leave NULL for default. */ + const char *message_encoding; +} git_commit_create_options; + +#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1 +#define GIT_COMMIT_CREATE_OPTIONS_INIT { GIT_COMMIT_CREATE_OPTIONS_VERSION } + +/** + * Commits the staged changes in the repository; this is a near analog to + * `git commit -m message`. + * + * By default, empty commits are not allowed. + * + * @param id pointer to store the new commit's object id + * @param repo repository to commit changes in + * @param message the commit message + * @param opts options for creating the commit + * @return 0 on success, GIT_EUNCHANGED if there were no changes to commit, or an error code + */ +GIT_EXTERN(int) git_commit_create_from_stage( + git_oid *id, + git_repository *repo, + const char *message, + const git_commit_create_options *opts); + /** * Amend an existing commit by replacing only non-NULL values. * @@ -469,7 +512,7 @@ GIT_EXTERN(int) git_commit_create_buffer( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[]); + git_commit * const parents[]); /** * Create a commit object from the given buffer and signature @@ -538,9 +581,27 @@ typedef int (*git_commit_create_cb)( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[], + git_commit * const parents[], void *payload); +/** An array of commits returned from the library */ +typedef struct git_commitarray { + git_commit *const *commits; + size_t count; +} git_commitarray; + +/** + * Free the commits contained in a commit array. This method should + * be called on `git_commitarray` objects that were provided by the + * library. Not doing so will result in a memory leak. + * + * This does not free the `git_commitarray` itself, since the library + * will never allocate that object directly itself. + * + * @param array The git_commitarray that contains commits to free + */ +GIT_EXTERN(void) git_commitarray_dispose(git_commitarray *array); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/common.h b/include/git2/common.h index ab6bc13..b7cf20b 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -228,7 +228,9 @@ typedef enum { GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, GIT_OPT_GET_SERVER_CONNECT_TIMEOUT, GIT_OPT_SET_SERVER_TIMEOUT, - GIT_OPT_GET_SERVER_TIMEOUT + GIT_OPT_GET_SERVER_TIMEOUT, + GIT_OPT_SET_USER_AGENT_PRODUCT, + GIT_OPT_GET_USER_AGENT_PRODUCT } git_libgit2_opt_t; /** @@ -337,11 +339,35 @@ typedef enum { * * * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent) * - * > Set the value of the User-Agent header. This value will be - * > appended to "git/1.0", for compatibility with other git clients. + * > Set the value of the comment section of the User-Agent header. + * > This can be information about your product and its version. + * > By default this is "libgit2" followed by the libgit2 version. * > - * > - `user_agent` is the value that will be delivered as the - * > User-Agent header on HTTP requests. + * > This value will be appended to User-Agent _product_, which + * > is typically set to "git/2.0". + * > + * > Set to the empty string ("") to not send any information in the + * > comment section, or set to NULL to restore the default. + * + * * opts(GIT_OPT_GET_USER_AGENT, git_buf *out) + * + * > Get the value of the User-Agent header. + * > The User-Agent is written to the `out` buffer. + * + * * opts(GIT_OPT_SET_USER_AGENT_PRODUCT, const char *user_agent_product) + * + * > Set the value of the product portion of the User-Agent header. + * > This defaults to "git/2.0", for compatibility with other git + * > clients. It is recommended to keep this as git/<version> for + * > compatibility with servers that do user-agent detection. + * > + * > Set to the empty string ("") to not send any user-agent string, + * > or set to NULL to restore the default. + * + * * opts(GIT_OPT_GET_USER_AGENT_PRODUCT, git_buf *out) + * + * > Get the value of the User-Agent product header. + * > The User-Agent product is written to the `out` buffer. * * * opts(GIT_OPT_SET_WINDOWS_SHAREMODE, unsigned long value) * @@ -377,11 +403,6 @@ typedef enum { * > * > - `ciphers` is the list of ciphers that are eanbled. * - * * opts(GIT_OPT_GET_USER_AGENT, git_buf *out) - * - * > Get the value of the User-Agent header. - * > The User-Agent is written to the `out` buffer. - * * * opts(GIT_OPT_ENABLE_OFS_DELTA, int enabled) * * > Enable or disable the use of "offset deltas" when creating packfiles, @@ -490,10 +511,9 @@ typedef enum { * * opts(GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, int timeout) * > Sets the timeout (in milliseconds) to attempt connections to - * > a remote server. This is supported only for HTTP(S) connections - * > and is not supported by SSH. Set to 0 to use the system default. - * > Note that this may not be able to be configured longer than the - * > system default, typically 75 seconds. + * > a remote server. Set to 0 to use the system default. Note that + * > this may not be able to be configured longer than the system + * > default, typically 75 seconds. * * opts(GIT_OPT_GET_SERVER_TIMEOUT, int *timeout) * > Gets the timeout (in milliseconds) for reading from and writing @@ -501,9 +521,7 @@ typedef enum { * * opts(GIT_OPT_SET_SERVER_TIMEOUT, int timeout) * > Sets the timeout (in milliseconds) for reading from and writing - * > to a remote server. This is supported only for HTTP(S) - * > connections and is not supported by SSH. Set to 0 to use the - * > system default. + * > to a remote server. Set to 0 to use the system default. * * @param option Option key * @param ... value to set the option diff --git a/include/git2/config.h b/include/git2/config.h index cfab0c7..3236143 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -22,8 +22,19 @@ GIT_BEGIN_DECL /** * Priority level of a config file. + * * These priority levels correspond to the natural escalation logic - * (from higher to lower) when searching for config entries in git.git. + * (from higher to lower) when reading or searching for config entries + * in git.git. Meaning that for the same key, the configuration in + * the local configuration is preferred over the configuration in + * the system configuration file. + * + * Callers can add their own custom configuration, beginning at the + * `GIT_CONFIG_LEVEL_APP` level. + * + * Writes, by default, occur in the highest priority level backend + * that is writable. This ordering can be overridden with + * `git_config_set_writeorder`. * * git_config_open_default() and git_repository_config() honor those * priority levels as well. @@ -48,9 +59,13 @@ typedef enum { */ GIT_CONFIG_LEVEL_LOCAL = 5, + /** Worktree specific configuration file; $GIT_DIR/config.worktree + */ + GIT_CONFIG_LEVEL_WORKTREE = 6, + /** Application specific configuration file; freely defined by applications */ - GIT_CONFIG_LEVEL_APP = 6, + GIT_CONFIG_LEVEL_APP = 7, /** Represents the highest level available config file (i.e. the most * specific config file available that actually is loaded) @@ -62,12 +77,32 @@ typedef enum { * An entry in a configuration file */ typedef struct git_config_entry { - const char *name; /**< Name of the entry (normalised) */ - const char *value; /**< String value of the entry */ - unsigned int include_depth; /**< Depth of includes where this variable was found */ - git_config_level_t level; /**< Which config file this was found in */ - void GIT_CALLBACK(free)(struct git_config_entry *entry); /**< Free function for this entry */ - void *payload; /**< Opaque value for the free function. Do not read or write */ + /** Name of the configuration entry (normalized) */ + const char *name; + + /** Literal (string) value of the entry */ + const char *value; + + /** The type of backend that this entry exists in (eg, "file") */ + const char *backend_type; + + /** + * The path to the origin of this entry. For config files, this is + * the path to the file. + */ + const char *origin_path; + + /** Depth of includes where this variable was found */ + unsigned int include_depth; + + /** Configuration level for the file this was found in */ + git_config_level_t level; + + /** + * Free function for this entry; for internal purposes. Callers + * should call `git_config_entry_free` to free data. + */ + void GIT_CALLBACK(free)(struct git_config_entry *entry); } git_config_entry; /** @@ -276,6 +311,11 @@ GIT_EXTERN(int) git_config_open_level( */ GIT_EXTERN(int) git_config_open_global(git_config **out, git_config *config); +GIT_EXTERN(int) git_config_set_writeorder( + git_config *cfg, + git_config_level_t *levels, + size_t len); + /** * Create a snapshot of the configuration * diff --git a/include/git2/email.h b/include/git2/email.h index 2039365..3389353 100644 --- a/include/git2/email.h +++ b/include/git2/email.h @@ -8,6 +8,7 @@ #define INCLUDE_git_email_h__ #include "common.h" +#include "diff.h" /** * @file git2/email.h diff --git a/include/git2/errors.h b/include/git2/errors.h index 7180852..52fa5f0 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -19,20 +19,20 @@ GIT_BEGIN_DECL /** Generic return codes */ typedef enum { - GIT_OK = 0, /**< No error */ + GIT_OK = 0, /**< No error */ - GIT_ERROR = -1, /**< Generic error */ - GIT_ENOTFOUND = -3, /**< Requested object could not be found */ - GIT_EEXISTS = -4, /**< Object exists preventing operation */ - GIT_EAMBIGUOUS = -5, /**< More than one object matches */ - GIT_EBUFS = -6, /**< Output buffer too short to hold data */ + GIT_ERROR = -1, /**< Generic error */ + GIT_ENOTFOUND = -3, /**< Requested object could not be found */ + GIT_EEXISTS = -4, /**< Object exists preventing operation */ + GIT_EAMBIGUOUS = -5, /**< More than one object matches */ + GIT_EBUFS = -6, /**< Output buffer too short to hold data */ /** * GIT_EUSER is a special error that is never generated by libgit2 * code. You can return it from a callback (e.g to stop an iteration) * to know that it was generated by the callback and not by libgit2. */ - GIT_EUSER = -7, + GIT_EUSER = -7, GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */ GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */ @@ -59,7 +59,10 @@ typedef enum { GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */ GIT_EAPPLYFAIL = -35, /**< Patch application failed */ GIT_EOWNER = -36, /**< The object is not owned by the current user */ - GIT_TIMEOUT = -37 /**< The operation timed out */ + GIT_TIMEOUT = -37, /**< The operation timed out */ + GIT_EUNCHANGED = -38, /**< There were no changes */ + GIT_ENOTSUPPORTED = -39, /**< An option is not supported */ + GIT_EREADONLY = -40 /**< The subject is read-only */ } git_error_code; /** @@ -118,63 +121,22 @@ typedef enum { * Return the last `git_error` object that was generated for the * current thread. * - * The default behaviour of this function is to return NULL if no previous error has occurred. - * However, libgit2's error strings are not cleared aggressively, so a prior - * (unrelated) error may be returned. This can be avoided by only calling - * this function if the prior call to a libgit2 API returned an error. + * This function will never return NULL. * - * @return A git_error object. - */ -GIT_EXTERN(const git_error *) git_error_last(void); - -/** - * Clear the last library error that occurred for this thread. - */ -GIT_EXTERN(void) git_error_clear(void); - -/** - * 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 - * will never need to call this function -- actually, calling it in most - * circumstances (for example, calling from within a callback function) - * will just end up having the value overwritten by libgit2 internals. + * Callers should not rely on this to determine whether an error has + * occurred. For error checking, callers should examine the return + * codes of libgit2 functions. * - * This error message is stored in thread-local storage and only applies - * to the particular thread that this libgit2 call is made from. + * This call can only reliably report error messages when an error + * has occurred. (It may contain stale information if it is called + * after a different function that succeeds.) * - * @param error_class One of the `git_error_t` enum above describing the - * general subsystem that is responsible for the error. - * @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. + * The memory for this object is managed by libgit2. It should not + * be freed. * - * @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); - -/** - * Set the error message to a special value for memory allocation failure. - * - * The normal `git_error_set_str()` function attempts to `strdup()` the - * string that is passed in. This is not a good idea when the error in - * question is a memory allocation failure. That circumstance has a - * special setter function that sets the error string to a known and - * statically allocated internal value. + * @return A git_error object. */ -GIT_EXTERN(void) git_error_set_oom(void); +GIT_EXTERN(const git_error *) git_error_last(void); /** @} */ GIT_END_DECL diff --git a/include/git2/refspec.h b/include/git2/refspec.h index eaf7747..e708713 100644 --- a/include/git2/refspec.h +++ b/include/git2/refspec.h @@ -58,7 +58,7 @@ GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec); * Get the refspec's string * * @param refspec the refspec - * @returns the refspec's original string + * @return the refspec's original string */ GIT_EXTERN(const char *) git_refspec_string(const git_refspec *refspec); diff --git a/include/git2/remote.h b/include/git2/remote.h index e9065b2..5505f6c 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -77,6 +77,17 @@ typedef enum { } git_remote_create_flags; /** + * How to handle reference updates. + */ +typedef enum { + /* Write the fetch results to FETCH_HEAD. */ + GIT_REMOTE_UPDATE_FETCHHEAD = (1 << 0), + + /* Report unchanged tips in the update_tips callback. */ + GIT_REMOTE_UPDATE_REPORT_UNCHANGED = (1 << 1) +} git_remote_update_flags; + +/** * Remote creation options structure * * Initialize with `GIT_REMOTE_CREATE_OPTIONS_INIT`. Alternatively, you can @@ -733,10 +744,9 @@ typedef struct { git_fetch_prune_t prune; /** - * Whether to write the results to FETCH_HEAD. Defaults to - * on. Leave this default in order to behave like git. + * How to handle reference updates; see `git_remote_update_flags`. */ - int update_fetchhead; + unsigned int update_fetchhead; /** * Determines how to behave regarding tags on the remote, such @@ -775,8 +785,13 @@ typedef struct { } git_fetch_options; #define GIT_FETCH_OPTIONS_VERSION 1 -#define GIT_FETCH_OPTIONS_INIT { GIT_FETCH_OPTIONS_VERSION, GIT_REMOTE_CALLBACKS_INIT, GIT_FETCH_PRUNE_UNSPECIFIED, 1, \ - GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, GIT_PROXY_OPTIONS_INIT } +#define GIT_FETCH_OPTIONS_INIT { \ + GIT_FETCH_OPTIONS_VERSION, \ + GIT_REMOTE_CALLBACKS_INIT, \ + GIT_FETCH_PRUNE_UNSPECIFIED, \ + GIT_REMOTE_UPDATE_FETCHHEAD, \ + GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, \ + GIT_PROXY_OPTIONS_INIT } /** * Initialize git_fetch_options structure @@ -830,6 +845,11 @@ typedef struct { * Extra headers for this push operation */ git_strarray custom_headers; + + /** + * "Push options" to deliver to the remote. + */ + git_strarray remote_push_options; } git_push_options; #define GIT_PUSH_OPTIONS_VERSION 1 @@ -1001,7 +1021,7 @@ GIT_EXTERN(int) git_remote_upload( * the name of the remote (or its url, for in-memory remotes). This * parameter is ignored when pushing. * @param callbacks pointer to the callback structure to use or NULL - * @param update_fetchhead whether to write to FETCH_HEAD. Pass 1 to behave like git. + * @param update_flags the git_remote_update_flags for these tips. * @param download_tags what the behaviour for downloading tags is for this fetch. This is * ignored for push. This must be the same value passed to `git_remote_download()`. * @return 0 or an error code @@ -1009,7 +1029,7 @@ GIT_EXTERN(int) git_remote_upload( GIT_EXTERN(int) git_remote_update_tips( git_remote *remote, const git_remote_callbacks *callbacks, - int update_fetchhead, + unsigned int update_flags, git_remote_autotag_option_t download_tags, const char *reflog_message); diff --git a/include/git2/repository.h b/include/git2/repository.h index 6ec2ac8..0afda72 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -11,6 +11,7 @@ #include "types.h" #include "oid.h" #include "buffer.h" +#include "commit.h" /** * @file git2/repository.h @@ -503,6 +504,7 @@ typedef enum { GIT_REPOSITORY_ITEM_LOGS, GIT_REPOSITORY_ITEM_MODULES, GIT_REPOSITORY_ITEM_WORKTREES, + GIT_REPOSITORY_ITEM_WORKTREE_CONFIG, GIT_REPOSITORY_ITEM__LAST } git_repository_item_t; @@ -978,6 +980,17 @@ GIT_EXTERN(int) git_repository_set_ident(git_repository *repo, const char *name, */ GIT_EXTERN(git_oid_t) git_repository_oid_type(git_repository *repo); +/** + * Gets the parents of the next commit, given the current repository state. + * Generally, this is the HEAD commit, except when performing a merge, in + * which case it is two or more commits. + * + * @param commits a `git_commitarray` that will contain the commit parents + * @param repo the repository + * @return 0 or an error code + */ +GIT_EXTERN(int) git_repository_commit_parents(git_commitarray *commits, git_repository *repo); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 0a9005e..75d2075 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -125,6 +125,57 @@ GIT_EXTERN(int) git_config_add_backend( const git_repository *repo, int force); +/** Options for in-memory configuration backends. */ +typedef struct { + unsigned int version; + + /** + * The type of this backend (eg, "command line"). If this is + * NULL, then this will be "in-memory". + */ + const char *backend_type; + + /** + * The path to the origin; if this is NULL then it will be + * left unset in the resulting configuration entries. + */ + const char *origin_path; +} git_config_backend_memory_options; + +#define GIT_CONFIG_BACKEND_MEMORY_OPTIONS_VERSION 1 +#define GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT { GIT_CONFIG_BACKEND_MEMORY_OPTIONS_VERSION } + + +/** + * Create an in-memory configuration backend from a string in standard + * git configuration file format. + * + * @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` + * @param opts the options to initialize this backend with, or NULL + */ +extern int git_config_backend_from_string( + git_config_backend **out, + const char *cfg, + size_t len, + git_config_backend_memory_options *opts); + +/** + * Create an in-memory configuration backend from a list of name/value + * pairs. + * + * @param out the new backend + * @param values the configuration values to set (in "key=value" format) + * @param len the length of the values array + * @param opts the options to initialize this backend with, or NULL + */ +extern int git_config_backend_from_values( + git_config_backend **out, + const char **values, + size_t len, + git_config_backend_memory_options *opts); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/email.h b/include/git2/sys/email.h index 6f4a286..5029f9a 100644 --- a/include/git2/sys/email.h +++ b/include/git2/sys/email.h @@ -7,6 +7,11 @@ #ifndef INCLUDE_sys_git_email_h__ #define INCLUDE_sys_git_email_h__ +#include "git2/common.h" +#include "git2/diff.h" +#include "git2/email.h" +#include "git2/types.h" + /** * @file git2/sys/email.h * @brief Advanced git email creation routines diff --git a/include/git2/sys/errors.h b/include/git2/sys/errors.h new file mode 100644 index 0000000..3ae1215 --- /dev/null +++ b/include/git2/sys/errors.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_sys_git_errors_h__ +#define INCLUDE_sys_git_errors_h__ + +#include "git2/common.h" + +GIT_BEGIN_DECL + +/** + * Clear the last library error that occurred for this thread. + */ +GIT_EXTERN(void) git_error_clear(void); + +/** + * 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 + * will never need to call this function -- actually, calling it in most + * circumstances (for example, calling from within a callback function) + * will just end up having the value overwritten by libgit2 internals. + * + * This error message is stored in thread-local storage and only applies + * to the particular thread that this libgit2 call is made from. + * + * @param error_class One of the `git_error_t` enum above describing the + * general subsystem that is responsible for the error. + * @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); + +/** + * Set the error message to a special value for memory allocation failure. + * + * The normal `git_error_set_str()` function attempts to `strdup()` the + * string that is passed in. This is not a good idea when the error in + * question is a memory allocation failure. That circumstance has a + * special setter function that sets the error string to a known and + * statically allocated internal value. + */ +GIT_EXTERN(void) git_error_set_oom(void); + +GIT_END_DECL + +#endif diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h index 0eae923..58950e1 100644 --- a/include/git2/sys/remote.h +++ b/include/git2/sys/remote.h @@ -20,12 +20,18 @@ GIT_BEGIN_DECL +/** + * A remote's capabilities. + */ typedef enum { /** Remote supports fetching an advertised object by ID. */ GIT_REMOTE_CAPABILITY_TIP_OID = (1 << 0), /** Remote supports fetching an individual reachable object. */ GIT_REMOTE_CAPABILITY_REACHABLE_OID = (1 << 1), + + /** Remote supports push options. */ + GIT_REMOTE_CAPABILITY_PUSH_OPTIONS = (1 << 2), } git_remote_capability_t; /** diff --git a/include/git2/sys/repository.h b/include/git2/sys/repository.h index 892be66..080a404 100644 --- a/include/git2/sys/repository.h +++ b/include/git2/sys/repository.h @@ -9,6 +9,7 @@ #include "git2/common.h" #include "git2/types.h" +#include "git2/oid.h" /** * @file git2/sys/repository.h @@ -32,7 +33,11 @@ GIT_BEGIN_DECL * @param out The blank repository * @return 0 on success, or an error code */ +#ifdef GIT_EXPERIMENTAL_SHA256 +GIT_EXTERN(int) git_repository_new(git_repository **out, git_oid_t oid_type); +#else GIT_EXTERN(int) git_repository_new(git_repository **out); +#endif /** * Reset all the internal state in a repository. diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index 3d28d09..3277088 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -29,8 +29,8 @@ GIT_BEGIN_DECL typedef struct git_stream { int version; - int encrypted : 1, - proxy_support : 1; + unsigned int encrypted : 1, + proxy_support : 1; /** * Timeout for read and write operations; can be set to `0` to diff --git a/include/git2/version.h b/include/git2/version.h index d6aba3b..33c9625 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -11,16 +11,16 @@ * The version string for libgit2. This string follows semantic * versioning (v2) guidelines. */ -#define LIBGIT2_VERSION "1.7.2" +#define LIBGIT2_VERSION "1.8.1" /** 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 7 +#define LIBGIT2_VER_MINOR 8 /** The revision ("teeny") version number for this version of libgit2. */ -#define LIBGIT2_VER_REVISION 2 +#define LIBGIT2_VER_REVISION 1 /** The Windows DLL patch number for this version of libgit2. */ #define LIBGIT2_VER_PATCH 0 @@ -33,7 +33,11 @@ */ #define LIBGIT2_VER_PRERELEASE NULL -/** The library ABI soversion for this version of libgit2. */ -#define LIBGIT2_SOVERSION "1.7" +/** + * The library ABI soversion for this version of libgit2. This should + * only be changed when the library has a breaking ABI change, and so + * may trail the library's version number. + */ +#define LIBGIT2_SOVERSION "1.8" #endif diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 9193eaf..a6e5d17 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -11,6 +11,7 @@ #include "buffer.h" #include "types.h" #include "strarray.h" +#include "checkout.h" /** * @file git2/worktrees.h @@ -85,8 +86,9 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); typedef struct git_worktree_add_options { unsigned int version; - int lock; /**< lock newly created worktree */ - git_reference *ref; /**< reference to use for the new worktree HEAD */ + int lock; /**< lock newly created worktree */ + int checkout_existing; /**< allow checkout of existing branch matching worktree name */ + git_reference *ref; /**< reference to use for the new worktree HEAD */ /** * Options for the checkout. @@ -95,7 +97,8 @@ typedef struct git_worktree_add_options { } git_worktree_add_options; #define GIT_WORKTREE_ADD_OPTIONS_VERSION 1 -#define GIT_WORKTREE_ADD_OPTIONS_INIT {GIT_WORKTREE_ADD_OPTIONS_VERSION,0,NULL,GIT_CHECKOUT_OPTIONS_INIT} +#define GIT_WORKTREE_ADD_OPTIONS_INIT { GIT_WORKTREE_ADD_OPTIONS_VERSION, \ + 0, 0, NULL, GIT_CHECKOUT_OPTIONS_INIT } /** * Initialize git_worktree_add_options structure diff --git a/package.json b/package.json index ed0ab4f..6c1cb28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "1.7.2", + "version": "1.8.1", "repo": "https://github.com/libgit2/libgit2", "description": " A cross-platform, linkable library implementation of Git that you can use in your application.", "install": "mkdir build && cd build && cmake .. && cmake --build ." diff --git a/script/valgrind.sh b/script/valgrind.sh index b5deed2..aacd767 100755 --- a/script/valgrind.sh +++ b/script/valgrind.sh @@ -1,2 +1,2 @@ #!/bin/bash -exec valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@" +exec valgrind --leak-check=full --show-reachable=yes --child-silent-after-fork=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@" diff --git a/script/valgrind.supp b/script/valgrind.supp index 8c4549f..79e8378 100644 --- a/script/valgrind.supp +++ b/script/valgrind.supp @@ -81,6 +81,13 @@ } { + ignore-glibc-getaddrinfo-fn + Memcheck:Leak + ... + fun:getaddrinfo +} + +{ ignore-curl-global-init Memcheck:Leak ... @@ -192,6 +199,16 @@ } { + ignore-openssl-undefined-in-connect + Memcheck:Cond + ... + obj:*libcrypto.so* + ... + fun:openssl_connect + ... +} + +{ ignore-libssh2-rsa-sha1-sign Memcheck:Leak ... diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8525acd..ed3f4a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -135,7 +135,8 @@ endif() # platform libraries if(WIN32) - list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) + list(APPEND LIBGIT2_SYSTEM_LIBS "ws2_32" "secur32") + list(APPEND LIBGIT2_PC_LIBS "-lws2_32" "-lsecur32") endif() if(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") @@ -183,7 +184,7 @@ add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") # iconv if(USE_ICONV) - find_package(Iconv) + find_package(IntlIconv) endif() if(ICONV_FOUND) set(GIT_USE_ICONV 1) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 84b6c19..97797e3 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -4,7 +4,8 @@ set(CLI_INCLUDES "${libgit2_SOURCE_DIR}/src/util" "${libgit2_SOURCE_DIR}/src/cli" "${libgit2_SOURCE_DIR}/include" - "${LIBGIT2_DEPENDENCY_INCLUDES}") + "${LIBGIT2_DEPENDENCY_INCLUDES}" + "${LIBGIT2_SYSTEM_INCLUDES}") if(WIN32 AND NOT CYGWIN) file(GLOB CLI_SRC_OS win32/*.c) diff --git a/src/cli/cli.h b/src/cli/cli.h deleted file mode 100644 index 7dede67..0000000 --- a/src/cli/cli.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 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 index 2a7e71c..0b1fafb 100644 --- a/src/cli/cmd.c +++ b/src/cli/cmd.c @@ -5,7 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "cli.h" +#include "common.h" #include "cmd.h" const cli_cmd_spec *cli_cmd_spec_byname(const char *name) diff --git a/src/cli/cmd.h b/src/cli/cmd.h index 8b1a1b3..bd88122 100644 --- a/src/cli/cmd.h +++ b/src/cli/cmd.h @@ -27,7 +27,9 @@ 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_config(int argc, char **argv); extern int cmd_hash_object(int argc, char **argv); extern int cmd_help(int argc, char **argv); +extern int cmd_index_pack(int argc, char **argv); #endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_cat_file.c b/src/cli/cmd_cat_file.c index fb53a72..90ee603 100644 --- a/src/cli/cmd_cat_file.c +++ b/src/cli/cmd_cat_file.c @@ -6,7 +6,7 @@ */ #include <git2.h> -#include "cli.h" +#include "common.h" #include "cmd.h" #define COMMAND_NAME "cat-file" @@ -24,9 +24,7 @@ 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_COMMON_OPT, { CLI_OPT_TYPE_SWITCH, NULL, 't', &display, DISPLAY_TYPE, CLI_OPT_USAGE_REQUIRED, NULL, "display the type of the object" }, @@ -139,6 +137,7 @@ static int print_pretty(git_object *object) int cmd_cat_file(int argc, char **argv) { + cli_repository_open_options open_opts = { argv + 1, argc - 1}; git_repository *repo = NULL; git_object *object = NULL; git_object_t type; @@ -153,7 +152,7 @@ int cmd_cat_file(int argc, char **argv) return 0; } - if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) + if (cli_repository_open(&repo, &open_opts) < 0) return cli_error_git(); if ((giterr = git_revparse_single(&object, repo, object_spec)) < 0) { diff --git a/src/cli/cmd_clone.c b/src/cli/cmd_clone.c index e477625..7d9736f 100644 --- a/src/cli/cmd_clone.c +++ b/src/cli/cmd_clone.c @@ -7,7 +7,7 @@ #include <stdio.h> #include <git2.h> -#include "cli.h" +#include "common.h" #include "cmd.h" #include "error.h" #include "sighandler.h" @@ -24,9 +24,7 @@ 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_COMMON_OPT, { CLI_OPT_TYPE_SWITCH, "quiet", 'q', &quiet, 1, CLI_OPT_USAGE_DEFAULT, NULL, "display the type of the object" }, diff --git a/src/cli/cmd_config.c b/src/cli/cmd_config.c new file mode 100644 index 0000000..6b9d373 --- /dev/null +++ b/src/cli/cmd_config.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 <git2.h> + +#include "common.h" +#include "cmd.h" + +#define COMMAND_NAME "config" + +typedef enum { + ACTION_NONE = 0, + ACTION_GET, + ACTION_ADD, + ACTION_REPLACE_ALL, + ACTION_LIST +} action_t; + +static action_t action = ACTION_NONE; +static int show_origin; +static int show_scope; +static int show_help; +static int null_separator; +static int config_level; +static char *config_filename; +static char *name, *value, *value_pattern; + +static const cli_opt_spec opts[] = { + CLI_COMMON_OPT, \ + + { CLI_OPT_TYPE_SWITCH, "null", 'z', &null_separator, 1, + 0, NULL, "use NUL as a separator" }, + + { CLI_OPT_TYPE_SWITCH, "system", 0, &config_level, GIT_CONFIG_LEVEL_SYSTEM, + 0, NULL, "read/write to system configuration" }, + { CLI_OPT_TYPE_SWITCH, "global", 0, &config_level, GIT_CONFIG_LEVEL_GLOBAL, + CLI_OPT_USAGE_CHOICE, NULL, "read/write to global configuration" }, + { CLI_OPT_TYPE_SWITCH, "local", 0, &config_level, GIT_CONFIG_LEVEL_LOCAL, + CLI_OPT_USAGE_CHOICE, NULL, "read/write to local configuration" }, + { CLI_OPT_TYPE_VALUE, "file", 0, &config_filename, 0, + CLI_OPT_USAGE_CHOICE, "filename", "read/write to specified configuration file" }, + + { CLI_OPT_TYPE_SWITCH, "get", 0, &action, ACTION_GET, + CLI_OPT_USAGE_REQUIRED, NULL, "get a configuration value" }, + { CLI_OPT_TYPE_SWITCH, "add", 0, &action, ACTION_ADD, + CLI_OPT_USAGE_CHOICE, NULL, "add a configuration value" }, + { CLI_OPT_TYPE_SWITCH, "replace-all", 0, &action, ACTION_REPLACE_ALL, + CLI_OPT_USAGE_CHOICE, NULL, "add a configuration value, replacing any old values" }, + { CLI_OPT_TYPE_SWITCH, "list", 'l', &action, ACTION_LIST, + CLI_OPT_USAGE_CHOICE | CLI_OPT_USAGE_SHOW_LONG, + NULL, "list all configuration entries" }, + { CLI_OPT_TYPE_SWITCH, "show-origin", 0, &show_origin, 1, + 0, NULL, "show origin of configuration" }, + { CLI_OPT_TYPE_SWITCH, "show-scope", 0, &show_scope, 1, + 0, NULL, "show scope of configuration" }, + { CLI_OPT_TYPE_ARG, "name", 0, &name, 0, + 0, "name", "name of configuration entry" }, + { CLI_OPT_TYPE_ARG, "value", 0, &value, 0, + 0, "value", "value of configuration entry" }, + { CLI_OPT_TYPE_ARG, "regexp", 0, &value_pattern, 0, + 0, "regexp", "regular expression of values to replace" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Query and set configuration options.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int get_config(git_config *config) +{ + git_buf value = GIT_BUF_INIT; + char sep = null_separator ? '\0' : '\n'; + int error; + + error = git_config_get_string_buf(&value, config, name); + + if (error && error != GIT_ENOTFOUND) + return cli_error_git(); + + else if (error == GIT_ENOTFOUND) + return 1; + + printf("%s%c", value.ptr, sep); + return 0; +} + +static int add_config(git_config *config) +{ + if (git_config_set_multivar(config, name, "$^", value) < 0) + return cli_error_git(); + + return 0; +} + +static int replace_all_config(git_config *config) +{ + if (git_config_set_multivar(config, name, value_pattern ? value_pattern : ".*", value) < 0) + return cli_error_git(); + + return 0; +} + +static const char *level_name(git_config_level_t level) +{ + switch (level) { + case GIT_CONFIG_LEVEL_PROGRAMDATA: + return "programdata"; + case GIT_CONFIG_LEVEL_SYSTEM: + return "system"; + case GIT_CONFIG_LEVEL_XDG: + return "global"; + case GIT_CONFIG_LEVEL_GLOBAL: + return "global"; + case GIT_CONFIG_LEVEL_LOCAL: + return "local"; + case GIT_CONFIG_LEVEL_APP: + return "command"; + default: + return "unknown"; + } +} + +static int list_config(git_config *config) +{ + git_config_iterator *iterator; + git_config_entry *entry; + char data_separator = null_separator ? '\0' : '\t'; + char kv_separator = null_separator ? '\n' : '='; + char entry_separator = null_separator ? '\0' : '\n'; + int error; + + if (git_config_iterator_new(&iterator, config) < 0) + return cli_error_git(); + + while ((error = git_config_next(&entry, iterator)) == 0) { + if (show_scope) + printf("%s%c", + level_name(entry->level), + data_separator); + + if (show_origin) + printf("%s%s%s%c", + entry->backend_type ? entry->backend_type : "", + entry->backend_type && entry->origin_path ? ":" : "", + entry->origin_path ? entry->origin_path : "", + data_separator); + + printf("%s%c%s%c", entry->name, kv_separator, entry->value, + entry_separator); + } + + if (error != GIT_ITEROVER) + return cli_error_git(); + + git_config_iterator_free(iterator); + return 0; +} + +int cmd_config(int argc, char **argv) +{ + git_repository *repo = NULL; + git_config *config = NULL; + cli_repository_open_options open_opts = { argv + 1, argc - 1}; + cli_opt invalid_opt; + 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 (config_filename) { + if (git_config_new(&config) < 0 || + git_config_add_file_ondisk(config, config_filename, + GIT_CONFIG_LEVEL_APP, NULL, 0) < 0) { + ret = cli_error_git(); + goto done; + } + } else { + if (cli_repository_open(&repo, &open_opts) < 0 || + git_repository_config(&config, repo) < 0) { + ret = cli_error_git(); + goto done; + } + + if (config_level && + git_config_open_level(&config, config, config_level) < 0) { + ret = cli_error_git(); + goto done; + } + } + + switch (action) { + case ACTION_ADD: + if (!name || !value || value_pattern) + ret = cli_error_usage("%s --add requires two arguments", COMMAND_NAME); + else + ret = add_config(config); + break; + case ACTION_REPLACE_ALL: + if (!name || !value) + ret = cli_error_usage("%s --replace-all requires two or three arguments", COMMAND_NAME); + else + ret = replace_all_config(config); + break; + case ACTION_GET: + if (!name) + ret = cli_error_usage("%s --get requires an argument", COMMAND_NAME); + else + ret = get_config(config); + break; + case ACTION_LIST: + if (name) + ret = cli_error_usage("%s --list does not take an argument", COMMAND_NAME); + else + ret = list_config(config); + break; + default: + ret = cli_error_usage("unknown action"); + } + +done: + git_config_free(config); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/cmd_hash_object.c b/src/cli/cmd_hash_object.c index 93b980d..741debb 100644 --- a/src/cli/cmd_hash_object.c +++ b/src/cli/cmd_hash_object.c @@ -6,7 +6,7 @@ */ #include <git2.h> -#include "cli.h" +#include "common.h" #include "cmd.h" #include "futils.h" @@ -19,9 +19,7 @@ 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_COMMON_OPT, { CLI_OPT_TYPE_VALUE, NULL, 't', &type_name, 0, CLI_OPT_USAGE_DEFAULT, "type", "the type of object to hash (default: \"blob\")" }, @@ -92,6 +90,7 @@ static int hash_buf( int cmd_hash_object(int argc, char **argv) { + cli_repository_open_options open_opts = { argv + 1, argc - 1}; git_repository *repo = NULL; git_odb *odb = NULL; git_oid_t oid_type; @@ -113,7 +112,7 @@ int cmd_hash_object(int argc, char **argv) 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 || + (cli_repository_open(&repo, &open_opts) < 0 || git_repository_odb(&odb, repo) < 0)) { ret = cli_error_git(); goto done; diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c index 7ee9822..5e877e0 100644 --- a/src/cli/cmd_help.c +++ b/src/cli/cmd_help.c @@ -7,7 +7,7 @@ #include <stdio.h> #include <git2.h> -#include "cli.h" +#include "common.h" #include "cmd.h" #define COMMAND_NAME "help" @@ -16,8 +16,8 @@ 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_COMMON_OPT, + { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" }, { 0 }, diff --git a/src/cli/cmd_index_pack.c b/src/cli/cmd_index_pack.c new file mode 100644 index 0000000..09685c5 --- /dev/null +++ b/src/cli/cmd_index_pack.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <git2.h> +#include "common.h" +#include "cmd.h" +#include "progress.h" + +#define COMMAND_NAME "index-pack" + +#define BUFFER_SIZE (1024 * 1024) + +static int show_help, verbose, read_stdin; +static char *filename; +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, "verbose", 'v', &verbose, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display progress output" }, + + { CLI_OPT_TYPE_LITERAL }, + + { CLI_OPT_TYPE_SWITCH, "stdin", 0, &read_stdin, 1, + CLI_OPT_USAGE_REQUIRED, NULL, "read from stdin" }, + { CLI_OPT_TYPE_ARG, "pack-file", 0, &filename, 0, + CLI_OPT_USAGE_CHOICE, "pack-file", "packfile path" }, + + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Indexes a packfile and writes the index to disk.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +int cmd_index_pack(int argc, char **argv) +{ + cli_opt invalid_opt; + git_indexer *idx = NULL; + git_indexer_options idx_opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer_progress stats = {0}; + char buf[BUFFER_SIZE]; + ssize_t read_len; + int fd, ret; + + 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 (verbose) { + idx_opts.progress_cb = cli_progress_indexer; + idx_opts.progress_cb_payload = &progress; + } + + if (read_stdin) { + fd = fileno(stdin); + } else if ((fd = p_open(filename, O_RDONLY)) < 0) { + ret = cli_error_git(); + goto done; + } + +#ifdef GIT_EXPERIMENTAL_SHA256 + ret = git_indexer_new(&idx, ".", GIT_OID_SHA1, &idx_opts); +#else + ret = git_indexer_new(&idx, ".", 0, NULL, &idx_opts); +#endif + + if (ret < 0) { + ret = cli_error_git(); + goto done; + } + + while ((read_len = p_read(fd, buf, sizeof(buf))) > 0) { + if (git_indexer_append(idx, buf, (size_t)read_len, &stats) < 0) { + ret = cli_error_git(); + goto done; + } + } + + if (git_indexer_commit(idx, &stats) < 0) { + ret = cli_error_git(); + goto done; + } + + cli_progress_finish(&progress); + +done: + if (!read_stdin && fd >= 0) + p_close(fd); + + cli_progress_dispose(&progress); + git_indexer_free(idx); + return ret; +} diff --git a/src/cli/common.c b/src/cli/common.c new file mode 100644 index 0000000..60b0358 --- /dev/null +++ b/src/cli/common.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 <git2.h> +#include <git2/sys/config.h> + +#include "git2_util.h" +#include "vector.h" + +#include "common.h" +#include "error.h" + +static int parse_option(cli_opt *opt, void *data) +{ + git_str kv = GIT_STR_INIT, env = GIT_STR_INIT; + git_vector *cmdline_config = data; + int error = 0; + + if (opt->spec && opt->spec->alias == 'c') { + if (git_str_puts(&kv, opt->value) < 0) { + error = cli_error_git(); + goto done; + } + } + + else if (opt->spec && !strcmp(opt->spec->name, "config-env")) { + char *val = strchr(opt->value, '='); + + if (val == NULL || *(val + 1) == '\0') { + error = cli_error("invalid config format: '%s'", opt->value); + goto done; + } + + if (git_str_put(&kv, opt->value, (val - opt->value)) < 0) { + error = cli_error_git(); + goto done; + } + + val++; + + if ((error = git__getenv(&env, val)) == GIT_ENOTFOUND) { + error = cli_error("missing environment variable '%s' for configuration '%s'", val, kv.ptr); + goto done; + } else if (error) { + error = cli_error_git(); + goto done; + } + + if (git_str_putc(&kv, '=') < 0 || + git_str_puts(&kv, env.ptr) < 0) { + error = cli_error_git(); + goto done; + } + } + + if (kv.size > 0 && + git_vector_insert(cmdline_config, git_str_detach(&kv)) < 0) + error = cli_error_git(); + +done: + git_str_dispose(&env); + git_str_dispose(&kv); + return error; +} + +static int parse_common_options( + git_repository *repo, + cli_repository_open_options *opts) +{ + cli_opt_spec common_opts[] = { + { CLI_COMMON_OPT_CONFIG }, + { CLI_COMMON_OPT_CONFIG_ENV }, + { 0 } + }; + git_config_backend_memory_options config_opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + git_vector cmdline = GIT_VECTOR_INIT; + git_config *config = NULL; + git_config_backend *backend = NULL; + int error = 0; + + config_opts.backend_type = "command line"; + + if ((error = cli_opt_foreach(common_opts, opts->args, + opts->args_len, CLI_OPT_PARSE_GNU, parse_option, + &cmdline)) < 0) + goto done; + + if (git_vector_length(&cmdline) == 0) + goto done; + + if (git_repository_config(&config, repo) < 0 || + git_config_backend_from_values(&backend, + (const char **)cmdline.contents, cmdline.length, + &config_opts) < 0 || + git_config_add_backend(config, backend, GIT_CONFIG_LEVEL_APP, + repo, 0) < 0) + error = cli_error_git(); + +done: + if (error && backend) + backend->free(backend); + git_config_free(config); + git_vector_free_deep(&cmdline); + return error; +} + +int cli_repository_open( + git_repository **out, + cli_repository_open_options *opts) +{ + git_repository *repo; + + if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) + return -1; + + if (opts && parse_common_options(repo, opts) < 0) + return -1; + + *out = repo; + return 0; +} diff --git a/src/cli/common.h b/src/cli/common.h new file mode 100644 index 0000000..3aed8ad --- /dev/null +++ b/src/cli/common.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This 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_common_h__ +#define CLI_common_h__ + +#define PROGRAM_NAME "git2" + +#include "git2_util.h" + +#include "error.h" +#include "opt.h" +#include "opt_usage.h" + +/* + * Common command arguments. + */ + +#define CLI_COMMON_OPT_HELP \ + CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, \ + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING +#define CLI_COMMON_OPT_CONFIG \ + CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0, \ + CLI_OPT_USAGE_HIDDEN +#define CLI_COMMON_OPT_CONFIG_ENV \ + CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0, \ + CLI_OPT_USAGE_HIDDEN + +#define CLI_COMMON_OPT \ + { CLI_COMMON_OPT_HELP }, \ + { CLI_COMMON_OPT_CONFIG }, \ + { CLI_COMMON_OPT_CONFIG_ENV } + +typedef struct { + char **args; + int args_len; +} cli_repository_open_options; + +extern int cli_repository_open( + git_repository **out, + cli_repository_open_options *opts); + +/* + * Common command arguments. + */ + +#define CLI_COMMON_OPT_HELP \ + CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, \ + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING +#define CLI_COMMON_OPT_CONFIG \ + CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0, \ + CLI_OPT_USAGE_HIDDEN +#define CLI_COMMON_OPT_CONFIG_ENV \ + CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0, \ + CLI_OPT_USAGE_HIDDEN + +#define CLI_COMMON_OPT \ + { CLI_COMMON_OPT_HELP }, \ + { CLI_COMMON_OPT_CONFIG }, \ + { CLI_COMMON_OPT_CONFIG_ENV } + +#endif /* CLI_common_h__ */ diff --git a/src/cli/error.h b/src/cli/error.h index cce7a54..abf8a51 100644 --- a/src/cli/error.h +++ b/src/cli/error.h @@ -8,7 +8,7 @@ #ifndef CLI_error_h__ #define CLI_error_h__ -#include "cli.h" +#include "common.h" #include <stdio.h> #define CLI_EXIT_OK 0 diff --git a/src/cli/main.c b/src/cli/main.c index cbfc50e..c7a6fcf 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -7,7 +7,7 @@ #include <stdio.h> #include <git2.h> -#include "cli.h" +#include "common.h" #include "cmd.h" static int show_help = 0; @@ -16,8 +16,12 @@ 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, "help", 0, &show_help, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display help information" }, + { CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0, + CLI_OPT_USAGE_DEFAULT, "key=value", "add configuration value" }, + { CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0, + CLI_OPT_USAGE_DEFAULT, "key=value", "set configuration value to environment variable" }, { 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, @@ -30,19 +34,40 @@ const cli_opt_spec cli_common_opts[] = { 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" }, + { "config", cmd_config, "View or set configuration values " }, { "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" }, { "help", cmd_help, "Display help information" }, + { "index-pack", cmd_index_pack, "Create an index for a packfile" }, { NULL } }; +/* + * Reorder the argv as it was given, since git has the notion of global + * options (like `--help` or `-c key=val`) that we want to pass to the + * subcommand, and that can appear early in the arguments, before the + * command name. Put the command-name in argv[1] to allow easier parsing. + */ +static void reorder_args(char **argv, size_t first) +{ + char *tmp; + size_t i; + + if (first == 1) + return; + + tmp = argv[first]; + + for (i = first; i > 1; i--) + argv[i] = argv[i - 1]; + + argv[1] = tmp; +} + 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) { @@ -66,8 +91,7 @@ int main(int argc, char **argv) * remaining arguments as args for the command itself. */ if (command) { - args = &argv[optparser.idx]; - args_len = (int)(argc - optparser.idx); + reorder_args(argv, optparser.idx); break; } } @@ -77,19 +101,9 @@ int main(int argc, char **argv) goto done; } - /* - * If `--help <command>` 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 (!command) { + ret = cmd_help(argc, argv); + goto done; } if ((cmd = cli_cmd_spec_byname(command)) == NULL) { @@ -98,7 +112,7 @@ int main(int argc, char **argv) goto done; } - ret = cmd->fn(args_len, args); + ret = cmd->fn(argc - 1, &argv[1]); done: git_libgit2_shutdown(); diff --git a/src/cli/opt.c b/src/cli/opt.c index 62a3430..9242e22 100644 --- a/src/cli/opt.c +++ b/src/cli/opt.c @@ -10,7 +10,7 @@ * 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 + * ./rename.pl cli_opt --filename=opt --include=common.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #include <stdlib.h> @@ -19,7 +19,11 @@ #include <limits.h> #include <assert.h> -#include "cli.h" +#if defined(__sun) || defined(__illumos__) +# include <alloca.h> +#endif + +#include "common.h" #include "opt.h" #ifdef _WIN32 @@ -73,7 +77,7 @@ GIT_INLINE(const cli_opt_spec *) spec_for_long( /* Handle --option=value arguments */ if (spec->type == CLI_OPT_TYPE_VALUE && - eql && + spec->name && eql && strncmp(arg, spec->name, eql_pos) == 0 && spec->name[eql_pos] == '\0') { *has_value = 1; @@ -575,6 +579,28 @@ cli_opt_status_t cli_opt_parse( return validate_required(opt, specs, given_specs); } +int cli_opt_foreach( + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags, + int (*callback)(cli_opt *, void *), + void *callback_data) +{ + cli_opt_parser parser; + cli_opt opt; + int ret; + + cli_opt_parser_init(&parser, specs, args, args_len, flags); + + while (cli_opt_parser_next(&opt, &parser)) { + if ((ret = callback(&opt, callback_data)) != 0) + return ret; + } + + return 0; +} + static int spec_name_fprint(FILE *file, const cli_opt_spec *spec) { int error; diff --git a/src/cli/opt.h b/src/cli/opt.h index 6c1d460..226f74d 100644 --- a/src/cli/opt.h +++ b/src/cli/opt.h @@ -10,7 +10,7 @@ * 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 + * ./rename.pl cli_opt --filename=opt --include=common.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #ifndef CLI_opt_h__ @@ -275,8 +275,8 @@ typedef struct cli_opt_parser { size_t arg_idx; size_t in_args; size_t in_short; - int needs_sort : 1, - in_literal : 1; + unsigned int needs_sort : 1, + in_literal : 1; } cli_opt_parser; /** @@ -301,6 +301,24 @@ cli_opt_status_t cli_opt_parse( unsigned int flags); /** + * Quickly executes the given callback for each argument. + * + * @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 + * @param callback The callback to invoke for each specified option + * @param callback_data Data to be provided to the callback + */ +int cli_opt_foreach( + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags, + int (*callback)(cli_opt *, void *), + void *callback_data); + +/** * Initializes a parser that parses the given arguments according to the * given specifications. * diff --git a/src/cli/opt_usage.c b/src/cli/opt_usage.c index 478b416..8374f51 100644 --- a/src/cli/opt_usage.c +++ b/src/cli/opt_usage.c @@ -5,7 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "cli.h" +#include "common.h" #include "str.h" static int print_spec_name(git_str *out, const cli_opt_spec *spec) diff --git a/src/cli/progress.c b/src/cli/progress.c index ddfbafb..d975b09 100644 --- a/src/cli/progress.c +++ b/src/cli/progress.c @@ -242,7 +242,21 @@ static int fetch_receiving( done ? ", done." : ""); } -static int fetch_resolving( +static int indexer_indexing( + cli_progress *progress, + const git_indexer_progress *stats) +{ + bool done = (stats->received_objects == stats->total_objects); + + return progress_printf(progress, false, + "Indexing objects: %3d%% (%d/%d)%s\r", + percent(stats->received_objects, stats->total_objects), + stats->received_objects, + stats->total_objects, + done ? ", done." : ""); +} + +static int indexer_resolving( cli_progress *progress, const git_indexer_progress *stats) { @@ -283,7 +297,42 @@ int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload /* fall through */ case CLI_PROGRESS_RESOLVING: - error = fetch_resolving(progress, stats); + error = indexer_resolving(progress, stats); + break; + + default: + /* should not be reached */ + GIT_ASSERT(!"unexpected progress state"); + } + + return error; +} + +int cli_progress_indexer( + 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_INDEXING; + /* fall through */ + + case CLI_PROGRESS_INDEXING: + if ((error = indexer_indexing(progress, stats)) < 0) + break; + + if (stats->indexed_deltas == stats->total_deltas) + break; + + progress_complete(progress); + progress->action = CLI_PROGRESS_RESOLVING; + /* fall through */ + + case CLI_PROGRESS_RESOLVING: + error = indexer_resolving(progress, stats); break; default: diff --git a/src/cli/progress.h b/src/cli/progress.h index 886fef8..f08d68f 100644 --- a/src/cli/progress.h +++ b/src/cli/progress.h @@ -22,6 +22,7 @@ typedef enum { CLI_PROGRESS_NONE, CLI_PROGRESS_RECEIVING, + CLI_PROGRESS_INDEXING, CLI_PROGRESS_RESOLVING, CLI_PROGRESS_CHECKING_OUT } cli_progress_t; @@ -75,6 +76,17 @@ extern int cli_progress_fetch_transfer( void *payload); /** + * Prints indexer progress to the console. Suitable for a + * `progress_cb` callback for `git_indexer_options`. + * + * @param stats The indexer stats + * @param payload A pointer to the cli_progress + */ +extern int cli_progress_indexer( + const git_indexer_progress *stats, + void *payload); + +/** * Prints checkout progress to the console. Suitable for a * `progress_cb` callback for `git_checkout_options`. * diff --git a/src/cli/unix/sighandler.c b/src/cli/unix/sighandler.c index 6b4982d..05ac867 100644 --- a/src/cli/unix/sighandler.c +++ b/src/cli/unix/sighandler.c @@ -8,7 +8,8 @@ #include <stdint.h> #include <signal.h> #include "git2_util.h" -#include "cli.h" +#include "common.h" +#include "sighandler.h" static void (*interrupt_handler)(void) = NULL; diff --git a/src/cli/win32/precompiled.h b/src/cli/win32/precompiled.h index b0309b8..031370e 100644 --- a/src/cli/win32/precompiled.h +++ b/src/cli/win32/precompiled.h @@ -1,3 +1,3 @@ #include <git2.h> -#include "cli.h" +#include "common.h" diff --git a/src/cli/win32/sighandler.c b/src/cli/win32/sighandler.c index cc0b646..05a67fb 100644 --- a/src/cli/win32/sighandler.c +++ b/src/cli/win32/sighandler.c @@ -8,7 +8,7 @@ #include "git2_util.h" #include <windows.h> -#include "cli.h" +#include "sighandler.h" static void (*interrupt_handler)(void) = NULL; diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 876a703..bc7cb5b 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -65,12 +65,6 @@ set_target_properties(libgit2package PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJE 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) diff --git a/src/libgit2/attr.c b/src/libgit2/attr.c index 1623b1d..1db90b5 100644 --- a/src/libgit2/attr.c +++ b/src/libgit2/attr.c @@ -424,9 +424,13 @@ static int attr_setup( goto out; if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = preload_attr_source(repo, attr_session, &index_source)) < 0) + (error = preload_attr_source(repo, attr_session, &index_source)) < 0) { + if (error != GIT_ENOTFOUND) goto out; + error = 0; + } + if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) && (error = preload_attr_source(repo, attr_session, &head_source)) < 0) goto out; diff --git a/src/libgit2/blame.c b/src/libgit2/blame.c index d93dd5e..2ed7d20 100644 --- a/src/libgit2/blame.c +++ b/src/libgit2/blame.c @@ -117,12 +117,12 @@ static git_blame_hunk *dup_hunk(git_blame_hunk *hunk, git_blame *blame) 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; + for (i = 0; i < v->length; i++) { + git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; + if(hunk->final_start_line_number < start_line){ + continue; } + hunk->final_start_line_number += shift_by; } } @@ -444,21 +444,20 @@ static int buffer_hunk_cb( GIT_UNUSED(delta); - wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; + wedge_line = (hunk->new_start >= hunk->old_start || 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, blame); + blame->current_diff_line++; 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, + wedge_line - blame->current_hunk->final_start_line_number, true, blame); GIT_ERROR_CHECK_ALLOC(blame->current_hunk); } @@ -484,13 +483,12 @@ static int buffer_line_cb( 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); + shift_hunks_by(&blame->hunks, blame->current_diff_line, 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, blame); GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); } blame->current_diff_line++; @@ -498,15 +496,16 @@ static int buffer_line_cb( 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; + size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk; if (--(blame->current_hunk->lines_in_hunk) == 0) { size_t i; - shift_base--; + size_t i_next; 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); + i_next = min( i , blame->hunks.length -1); + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i_next); } } shift_hunks_by(&blame->hunks, shift_base, -1); diff --git a/src/libgit2/clone.c b/src/libgit2/clone.c index fca0ca0..d62c77a 100644 --- a/src/libgit2/clone.c +++ b/src/libgit2/clone.c @@ -22,6 +22,7 @@ #include "fs_path.h" #include "repository.h" #include "odb.h" +#include "net.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); @@ -336,8 +337,9 @@ static int create_and_configure_origin( 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 the path is local and exists it should be the absolute path. */ + if (!git_net_str_is_url(url) && git_fs_path_root(url) < 0 && + git_fs_path_exists(url)) { if (p_realpath(url, buf) == NULL) return -1; @@ -360,25 +362,29 @@ on_error: return error; } -static bool should_checkout( +static int should_checkout( + bool *out, git_repository *repo, bool is_bare, const git_checkout_options *opts) { - if (is_bare) - return false; + int error; - if (!opts) - return false; + if (!opts || is_bare || opts->checkout_strategy == GIT_CHECKOUT_NONE) { + *out = 0; + return 0; + } - if (opts->checkout_strategy == GIT_CHECKOUT_NONE) - return false; + if ((error = git_repository_head_unborn(repo)) < 0) + return error; - return !git_repository_head_unborn(repo); + *out = !error; + return 0; } static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) { + bool checkout; int error; if (branch) @@ -387,7 +393,13 @@ static int checkout_branch(git_repository *repo, git_remote *remote, const git_c else error = update_head_to_remote(repo, remote, reflog_message); - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + if (error < 0) + return error; + + if ((error = should_checkout(&checkout, repo, git_repository_is_bare(repo), co_opts)) < 0) + return error; + + if (checkout) error = git_checkout_head(repo, co_opts); return error; @@ -458,26 +470,25 @@ cleanup: 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; + bool 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; - } + if (git_net_str_is_url(url_or_path)) { + /* If GIT_CLONE_LOCAL_AUTO is specified, any url should be treated as remote */ + if (local == GIT_CLONE_LOCAL_AUTO || + !git_fs_path_is_local_file_url(url_or_path)) + return 0; - path = fromurl.ptr; + if (git_fs_path_fromurl(&fromurl, url_or_path) == 0) + is_local = git_fs_path_isdir(git_str_cstr(&fromurl)); + else + is_local = -1; + git_str_dispose(&fromurl); + } else { + is_local = git_fs_path_isdir(url_or_path); } - - is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && - git_fs_path_isdir(path); - -done: - git_str_dispose(&fromurl); return is_local; } @@ -542,15 +553,15 @@ static int git__clone( } if (error != 0) { - git_error_state last_error = {0}; - git_error_state_capture(&last_error, error); + git_error *last_error; + git_error_save(&last_error); git_repository_free(repo); repo = NULL; (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); - git_error_state_restore(&last_error); + git_error_restore(last_error); } *out = repo; diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c index f7be73a..47f6fed 100644 --- a/src/libgit2/commit.c +++ b/src/libgit2/commit.c @@ -281,7 +281,7 @@ int git_commit_create_from_ids( typedef struct { size_t total; - const git_commit **parents; + git_commit * const *parents; git_repository *repo; } commit_parent_data; @@ -307,7 +307,7 @@ int git_commit_create( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[]) + git_commit * const parents[]) { commit_parent_data data = { parent_count, parents, repo }; @@ -945,7 +945,7 @@ int git_commit_create_buffer( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[]) + git_commit * const parents[]) { GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo, author, committer, message_encoding, message, @@ -961,7 +961,7 @@ int git_commit__create_buffer( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[]) + git_commit * const parents[]) { int error; commit_parent_data data = { parent_count, parents, repo }; @@ -1086,6 +1086,82 @@ cleanup: return error; } +int git_commit_create_from_stage( + git_oid *out, + git_repository *repo, + const char *message, + const git_commit_create_options *given_opts) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_signature *default_signature = NULL; + const git_signature *author, *committer; + git_index *index = NULL; + git_diff *diff = NULL; + git_oid tree_id; + git_tree *head_tree = NULL, *tree = NULL; + git_commitarray parents = { 0 }; + int error = -1; + + GIT_ASSERT_ARG(out && repo); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_commit_create_options)); + + author = opts.author; + committer = opts.committer; + + if (!author || !committer) { + if (git_signature_default(&default_signature, repo) < 0) + goto done; + + if (!author) + author = default_signature; + + if (!committer) + committer = default_signature; + } + + if (git_repository_index(&index, repo) < 0) + goto done; + + if (!opts.allow_empty_commit) { + error = git_repository_head_tree(&head_tree, repo); + + if (error && error != GIT_EUNBORNBRANCH) + goto done; + + error = -1; + + if (git_diff_tree_to_index(&diff, repo, head_tree, index, NULL) < 0) + goto done; + + if (git_diff_num_deltas(diff) == 0) { + git_error_set(GIT_ERROR_REPOSITORY, + "no changes are staged for commit"); + error = GIT_EUNCHANGED; + goto done; + } + } + + if (git_index_write_tree(&tree_id, index) < 0 || + git_tree_lookup(&tree, repo, &tree_id) < 0 || + git_repository_commit_parents(&parents, repo) < 0) + goto done; + + error = git_commit_create(out, repo, "HEAD", author, committer, + opts.message_encoding, message, + tree, parents.count, parents.commits); + +done: + git_commitarray_dispose(&parents); + git_signature_free(default_signature); + git_tree_free(tree); + git_tree_free(head_tree); + git_diff_free(diff); + git_index_free(index); + return error; +} + int git_commit_committer_with_mailmap( git_signature **out, const git_commit *commit, const git_mailmap *mailmap) { @@ -1097,3 +1173,18 @@ int git_commit_author_with_mailmap( { return git_mailmap_resolve_signature(out, mailmap, commit->author); } + +void git_commitarray_dispose(git_commitarray *array) +{ + size_t i; + + if (array == NULL) + return; + + for (i = 0; i < array->count; i++) + git_commit_free(array->commits[i]); + + git__free((git_commit **)array->commits); + + memset(array, 0, sizeof(*array)); +} diff --git a/src/libgit2/commit.h b/src/libgit2/commit.h index c25fee3..53128ba 100644 --- a/src/libgit2/commit.h +++ b/src/libgit2/commit.h @@ -64,7 +64,7 @@ int git_commit__create_buffer( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[]); + git_commit * const parents[]); int git_commit__parse( void *commit, diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 23a8f9f..1e4e175 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -22,6 +22,32 @@ #include <ctype.h> +/* + * A refcounted instance of a config_backend that can be shared across + * a configuration instance, any snapshots, and individual configuration + * levels (from `git_config_open_level`). + */ +typedef struct { + git_refcount rc; + git_config_backend *backend; +} backend_instance; + +/* + * An entry in the readers or writers vector in the configuration. + * This is kept separate from the refcounted instance so that different + * views of the configuration can have different notions of levels or + * write orders. + * + * (eg, a standard configuration has a priority ordering of writers, a + * snapshot has *no* writers, and an individual level has a single + * writer.) + */ +typedef struct { + backend_instance *instance; + git_config_level_t level; + int write_order; +} backend_entry; + void git_config_entry_free(git_config_entry *entry) { if (!entry) @@ -30,75 +56,75 @@ void git_config_entry_free(git_config_entry *entry) 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) +static void backend_instance_free(backend_instance *instance) { git_config_backend *backend; - backend = internal->backend; + backend = instance->backend; backend->free(backend); - git__free(internal); + git__free(instance); } -static void config_free(git_config *cfg) +static void config_free(git_config *config) { size_t i; - backend_internal *internal; + backend_entry *entry; - for (i = 0; i < cfg->backends.length; ++i) { - internal = git_vector_get(&cfg->backends, i); - GIT_REFCOUNT_DEC(internal, backend_internal_free); + git_vector_foreach(&config->readers, i, entry) { + GIT_REFCOUNT_DEC(entry->instance, backend_instance_free); + git__free(entry); } - git_vector_free(&cfg->backends); - - git__memzero(cfg, sizeof(*cfg)); - git__free(cfg); + git_vector_free(&config->readers); + git_vector_free(&config->writers); + git__free(config); } -void git_config_free(git_config *cfg) +void git_config_free(git_config *config) { - if (cfg == NULL) + if (config == NULL) return; - GIT_REFCOUNT_DEC(cfg, config_free); + GIT_REFCOUNT_DEC(config, config_free); } -static int config_backend_cmp(const void *a, const void *b) +static int reader_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); + const backend_entry *a = _a; + const backend_entry *b = _b; - return bk_b->level - bk_a->level; + return b->level - a->level; } -int git_config_new(git_config **out) +static int writer_cmp(const void *_a, const void *_b) { - git_config *cfg; + const backend_entry *a = _a; + const backend_entry *b = _b; - cfg = git__malloc(sizeof(git_config)); - GIT_ERROR_CHECK_ALLOC(cfg); + return b->write_order - a->write_order; +} - memset(cfg, 0x0, sizeof(git_config)); +int git_config_new(git_config **out) +{ + git_config *config; + + config = git__calloc(1, sizeof(git_config)); + GIT_ERROR_CHECK_ALLOC(config); - if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { - git__free(cfg); + if (git_vector_init(&config->readers, 8, reader_cmp) < 0 || + git_vector_init(&config->writers, 8, writer_cmp) < 0) { + config_free(config); return -1; } - *out = cfg; - GIT_REFCOUNT_INC(cfg); + GIT_REFCOUNT_INC(config); + + *out = config; return 0; } int git_config_add_file_ondisk( - git_config *cfg, + git_config *config, const char *path, git_config_level_t level, const git_repository *repo, @@ -108,7 +134,7 @@ int git_config_add_file_ondisk( struct stat st; int res; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); GIT_ASSERT_ARG(path); res = p_stat(path, &st); @@ -120,7 +146,7 @@ int git_config_add_file_ondisk( if (git_config_backend_from_file(&file, path) < 0) return -1; - if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { + if ((res = git_config_add_backend(config, file, level, repo, force)) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup @@ -154,7 +180,7 @@ int git_config_snapshot(git_config **out, git_config *in) { int error = 0; size_t i; - backend_internal *internal; + backend_entry *entry; git_config *config; *out = NULL; @@ -162,18 +188,20 @@ int git_config_snapshot(git_config **out, git_config *in) if (git_config_new(&config) < 0) return -1; - git_vector_foreach(&in->backends, i, internal) { + git_vector_foreach(&in->readers, i, entry) { git_config_backend *b; - if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) + if ((error = entry->instance->backend->snapshot(&b, entry->instance->backend)) < 0) break; - if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { + if ((error = git_config_add_backend(config, b, entry->level, NULL, 0)) < 0) { b->free(b); break; } } + git_config_set_writeorder(config, NULL, 0); + if (error < 0) git_config_free(config); else @@ -183,141 +211,162 @@ int git_config_snapshot(git_config **out, git_config *in) } static int find_backend_by_level( - backend_internal **out, - const git_config *cfg, + backend_instance **out, + const git_config *config, git_config_level_t level) { - int pos = -1; - backend_internal *internal; + backend_entry *entry, *found = NULL; 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. + /* + * 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; + found = git_vector_get(&config->readers, 0); } else { - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; + git_vector_foreach(&config->readers, i, entry) { + if (entry->level == level) { + found = entry; + break; + } } } - if (pos == -1) { + if (!found) { git_error_set(GIT_ERROR_CONFIG, - "no configuration exists for the given level '%i'", (int)level); + "no configuration exists for the given level '%d'", level); return GIT_ENOTFOUND; } - *out = git_vector_get(&cfg->backends, pos); - + *out = found->instance; return 0; } -static int duplicate_level(void **old_raw, void *new_raw) +static int duplicate_level(void **_old, void *_new) { - backend_internal **old = (backend_internal **)old_raw; + backend_entry **old = (backend_entry **)_old; - GIT_UNUSED(new_raw); + GIT_UNUSED(_new); - git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); + git_error_set(GIT_ERROR_CONFIG, "configuration at level %d already exists", (*old)->level); return GIT_EEXISTS; } static void try_remove_existing_backend( - git_config *cfg, + git_config *config, git_config_level_t level) { - int pos = -1; - backend_internal *internal; + backend_entry *entry, *found = NULL; size_t i; - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; + git_vector_foreach(&config->readers, i, entry) { + if (entry->level == level) { + git_vector_remove(&config->readers, i); + found = entry; + break; + } } - if (pos == -1) + if (!found) return; - internal = git_vector_get(&cfg->backends, pos); - - if (git_vector_remove(&cfg->backends, pos) < 0) - return; + git_vector_foreach(&config->writers, i, entry) { + if (entry->level == level) { + git_vector_remove(&config->writers, i); + break; + } + } - GIT_REFCOUNT_DEC(internal, backend_internal_free); + GIT_REFCOUNT_DEC(found->instance, backend_instance_free); + git__free(found); } -static int git_config__add_internal( - git_config *cfg, - backend_internal *internal, +static int git_config__add_instance( + git_config *config, + backend_instance *instance, git_config_level_t level, int force) { + backend_entry *entry; int result; /* delete existing config backend for level if it exists */ if (force) - try_remove_existing_backend(cfg, level); + try_remove_existing_backend(config, level); - if ((result = git_vector_insert_sorted(&cfg->backends, - internal, &duplicate_level)) < 0) - return result; + entry = git__malloc(sizeof(backend_entry)); + GIT_ERROR_CHECK_ALLOC(entry); - git_vector_sort(&cfg->backends); - internal->backend->cfg = cfg; + entry->instance = instance; + entry->level = level; + entry->write_order = level; - GIT_REFCOUNT_INC(internal); + if ((result = git_vector_insert_sorted(&config->readers, + entry, &duplicate_level)) < 0 || + (result = git_vector_insert_sorted(&config->writers, + entry, NULL)) < 0) { + git__free(entry); + return result; + } + + GIT_REFCOUNT_INC(entry->instance); return 0; } -int git_config_open_global(git_config **cfg_out, git_config *cfg) +int git_config_open_global(git_config **out, git_config *config) { - if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) + int error; + + error = git_config_open_level(out, config, GIT_CONFIG_LEVEL_XDG); + + if (error == 0) return 0; + else if (error != GIT_ENOTFOUND) + return error; - return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); + return git_config_open_level(out, config, GIT_CONFIG_LEVEL_GLOBAL); } int git_config_open_level( - git_config **cfg_out, - const git_config *cfg_parent, + git_config **out, + const git_config *parent, git_config_level_t level) { - git_config *cfg; - backend_internal *internal; + git_config *config; + backend_instance *instance; int res; - if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) + if ((res = find_backend_by_level(&instance, parent, level)) < 0) return res; - if ((res = git_config_new(&cfg)) < 0) + if ((res = git_config_new(&config)) < 0) return res; - if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { - git_config_free(cfg); + if ((res = git_config__add_instance(config, instance, level, true)) < 0) { + git_config_free(config); return res; } - *cfg_out = cfg; + *out = config; return 0; } int git_config_add_backend( - git_config *cfg, + git_config *config, git_config_backend *backend, git_config_level_t level, const git_repository *repo, int force) { - backend_internal *internal; + backend_instance *instance; int result; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); GIT_ASSERT_ARG(backend); GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); @@ -325,22 +374,50 @@ int git_config_add_backend( if ((result = backend->open(backend, level, repo)) < 0) return result; - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); + instance = git__calloc(1, sizeof(backend_instance)); + GIT_ERROR_CHECK_ALLOC(instance); - memset(internal, 0x0, sizeof(backend_internal)); + instance->backend = backend; + instance->backend->cfg = config; - internal->backend = backend; - internal->level = level; - - if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { - git__free(internal); + if ((result = git_config__add_instance(config, instance, level, force)) < 0) { + git__free(instance); return result; } return 0; } +int git_config_set_writeorder( + git_config *config, + git_config_level_t *levels, + size_t len) +{ + backend_entry *entry; + size_t i, j; + + GIT_ASSERT(len < INT_MAX); + + git_vector_foreach(&config->readers, i, entry) { + bool found = false; + + for (j = 0; j < len; j++) { + if (levels[j] == entry->level) { + entry->write_order = (int)j; + found = true; + break; + } + } + + if (!found) + entry->write_order = -1; + } + + git_vector_sort(&config->writers); + + return 0; +} + /* * Loop over all the variables */ @@ -348,37 +425,20 @@ int git_config_add_backend( typedef struct { git_config_iterator parent; git_config_iterator *current; - const git_config *cfg; + const git_config *config; 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) +static int all_iter_next(git_config_entry **out, git_config_iterator *_iter) { all_iter *iter = (all_iter *) _iter; - backend_internal *internal; + backend_entry *entry; git_config_backend *backend; - size_t i; int error = 0; if (iter->current != NULL && - (error = iter->current->next(entry, iter->current)) == 0) { + (error = iter->current->next(out, iter->current)) == 0) { return 0; } @@ -386,12 +446,14 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) return error; do { - if (find_next_backend(&i, iter->cfg, iter->i) < 0) + if (iter->i == 0) return GIT_ITEROVER; - internal = git_vector_get(&iter->cfg->backends, i - 1); - backend = internal->backend; - iter->i = i - 1; + entry = git_vector_get(&iter->config->readers, iter->i - 1); + GIT_ASSERT(entry && entry->instance && entry->instance->backend); + + backend = entry->instance->backend; + iter->i--; if (iter->current) iter->current->free(iter->current); @@ -404,7 +466,7 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) if (error < 0) return error; - error = iter->current->next(entry, iter->current); + error = iter->current->next(out, iter->current); /* If this backend is empty, then keep going */ if (error == GIT_ITEROVER) continue; @@ -423,7 +485,7 @@ static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_it /* * We use the "normal" function to grab the next one across - * backends and then apply the regex + * readers and then apply the regex */ while ((error = all_iter_next(entry, _iter)) == 0) { /* skip non-matching keys if regexp was provided */ @@ -455,7 +517,7 @@ static void all_iter_glob_free(git_config_iterator *_iter) all_iter_free(_iter); } -int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +int git_config_iterator_new(git_config_iterator **out, const git_config *config) { all_iter *iter; @@ -465,21 +527,21 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) iter->parent.free = all_iter_free; iter->parent.next = all_iter_next; - iter->i = cfg->backends.length; - iter->cfg = cfg; + iter->i = config->readers.length; + iter->config = config; *out = (git_config_iterator *) iter; return 0; } -int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *config, const char *regexp) { all_iter *iter; int result; if (regexp == NULL) - return git_config_iterator_new(out, cfg); + return git_config_iterator_new(out, config); iter = git__calloc(1, sizeof(all_iter)); GIT_ERROR_CHECK_ALLOC(iter); @@ -491,8 +553,8 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf iter->parent.next = all_iter_glob_next; iter->parent.free = all_iter_glob_free; - iter->i = cfg->backends.length; - iter->cfg = cfg; + iter->i = config->readers.length; + iter->config = config; *out = (git_config_iterator *) iter; @@ -500,9 +562,9 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf } int git_config_foreach( - const git_config *cfg, git_config_foreach_cb cb, void *payload) + const git_config *config, git_config_foreach_cb cb, void *payload) { - return git_config_foreach_match(cfg, NULL, cb, payload); + return git_config_foreach_match(config, NULL, cb, payload); } int git_config_backend_foreach_match( @@ -548,7 +610,7 @@ int git_config_backend_foreach_match( } int git_config_foreach_match( - const git_config *cfg, + const git_config *config, const char *regexp, git_config_foreach_cb cb, void *payload) @@ -557,7 +619,7 @@ int git_config_foreach_match( git_config_iterator *iter; git_config_entry *entry; - if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + if ((error = git_config_iterator_glob_new(&iter, config, regexp)) < 0) return error; while (!(error = git_config_next(&entry, iter))) { @@ -579,72 +641,59 @@ int git_config_foreach_match( * 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) -{ + static backend_instance *get_writer_instance(git_config *config) + { + backend_entry *entry; size_t i; - backend_internal *backend; - *out = NULL; + git_vector_foreach(&config->writers, i, entry) { + if (entry->instance->backend->readonly) + continue; - 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; - } + if (entry->write_order < 0) + continue; - git_vector_foreach(&cfg->backends, i, backend) { - if (!backend->backend->readonly) { - *out = backend->backend; - return 0; - } + return entry->instance; } - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when all config backends are readonly", - uses[use], name); - return GIT_ENOTFOUND; + return NULL; + } + +static git_config_backend *get_writer(git_config *config) +{ + backend_instance *instance = get_writer_instance(config); + + return instance ? instance->backend : NULL; } -int git_config_delete_entry(git_config *cfg, const char *name) +int git_config_delete_entry(git_config *config, const char *name) { git_config_backend *backend; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((backend = get_writer(config)) == NULL) + return GIT_EREADONLY; return backend->del(backend, name); } -int git_config_set_int64(git_config *cfg, const char *name, int64_t value) +int git_config_set_int64(git_config *config, 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); + return git_config_set_string(config, name, str_value); } -int git_config_set_int32(git_config *cfg, const char *name, int32_t value) +int git_config_set_int32(git_config *config, const char *name, int32_t value) { - return git_config_set_int64(cfg, name, (int64_t)value); + return git_config_set_int64(config, name, (int64_t)value); } -int git_config_set_bool(git_config *cfg, const char *name, int value) +int git_config_set_bool(git_config *config, const char *name, int value) { - return git_config_set_string(cfg, name, value ? "true" : "false"); + return git_config_set_string(config, name, value ? "true" : "false"); } -int git_config_set_string(git_config *cfg, const char *name, const char *value) +int git_config_set_string(git_config *config, const char *name, const char *value) { int error; git_config_backend *backend; @@ -654,13 +703,15 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return -1; } - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); + return GIT_EREADONLY; + } error = backend->set(backend, name, value); - if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) - git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); + if (!error && GIT_REFCOUNT_OWNER(config) != NULL) + git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(config)); return error; } @@ -714,16 +765,17 @@ enum { static int get_entry( git_config_entry **out, - const git_config *cfg, + const git_config *config, const char *name, bool normalize_name, int want_errors) { + backend_entry *entry; + git_config_backend *backend; int res = GIT_ENOTFOUND; const char *key = name; char *normalized = NULL; size_t i; - backend_internal *internal; *out = NULL; @@ -734,11 +786,12 @@ static int get_entry( } res = GIT_ENOTFOUND; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; + git_vector_foreach(&config->readers, i, entry) { + GIT_ASSERT(entry->instance && entry->instance->backend); + + backend = entry->instance->backend; + res = backend->get(backend, key, out); - res = internal->backend->get(internal->backend, key, out); if (res != GIT_ENOTFOUND) break; } @@ -746,9 +799,9 @@ static int get_entry( git__free(normalized); cleanup: - if (res == GIT_ENOTFOUND) + if (res == GIT_ENOTFOUND) { res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); - else if (res && (want_errors == GET_NO_ERRORS)) { + } else if (res && (want_errors == GET_NO_ERRORS)) { git_error_clear(); res = 0; } @@ -757,24 +810,24 @@ cleanup: } int git_config_get_entry( - git_config_entry **out, const git_config *cfg, const char *name) + git_config_entry **out, const git_config *config, const char *name) { - return get_entry(out, cfg, name, true, GET_ALL_ERRORS); + return get_entry(out, config, name, true, GET_ALL_ERRORS); } int git_config__lookup_entry( git_config_entry **out, - const git_config *cfg, + const git_config *config, const char *key, bool no_errors) { return get_entry( - out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); + out, config, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); } int git_config_get_mapped( int *out, - const git_config *cfg, + const git_config *config, const char *name, const git_configmap *maps, size_t map_n) @@ -782,7 +835,7 @@ int git_config_get_mapped( git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_lookup_map_value(out, maps, map_n, entry->value); @@ -791,12 +844,12 @@ int git_config_get_mapped( return ret; } -int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) +int git_config_get_int64(int64_t *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_int64(out, entry->value); @@ -805,12 +858,12 @@ int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) return ret; } -int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) +int git_config_get_int32(int32_t *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_int32(out, entry->value); @@ -819,12 +872,12 @@ int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) return ret; } -int git_config_get_bool(int *out, const git_config *cfg, const char *name) +int git_config_get_bool(int *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_bool(out, entry->value); @@ -833,16 +886,15 @@ int git_config_get_bool(int *out, const git_config *cfg, const char *name) return ret; } -static int is_readonly(const git_config *cfg) +static int is_readonly(const git_config *config) { + backend_entry *entry; size_t i; - backend_internal *internal; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; + git_vector_foreach(&config->writers, i, entry) { + GIT_ASSERT(entry->instance && entry->instance->backend); - if (!internal->backend->readonly) + if (!entry->instance->backend->readonly) return 0; } @@ -873,21 +925,21 @@ int git_config_parse_path(git_buf *out, const char *value) int git_config_get_path( git_buf *out, - const git_config *cfg, + const git_config *config, const char *name) { - GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); + GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, config, name); } int git_config__get_path( git_str *out, - const git_config *cfg, + const git_config *config, const char *name) { git_config_entry *entry; int error; - if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((error = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return error; error = git_config__parse_path(out, entry->value); @@ -897,17 +949,17 @@ int git_config__get_path( } int git_config_get_string( - const char **out, const git_config *cfg, const char *name) + const char **out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if (!is_readonly(cfg)) { + if (!is_readonly(config)) { 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); + ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS); *out = !ret ? (entry->value ? entry->value : "") : NULL; git_config_entry_free(entry); @@ -916,22 +968,22 @@ int git_config_get_string( } int git_config_get_string_buf( - git_buf *out, const git_config *cfg, const char *name) + git_buf *out, const git_config *config, const char *name) { - GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); + GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, config, name); } int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name) + git_str *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; const char *str; GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS); str = !ret ? (entry->value ? entry->value : "") : NULL; if (str) @@ -943,12 +995,12 @@ int git_config__get_string_buf( } char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value) + const git_config *config, const char *key, const char *fallback_value) { git_config_entry *entry; char *ret; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, 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); @@ -956,12 +1008,12 @@ char *git_config__get_string_force( } int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value) + const git_config *config, const char *key, int fallback_value) { int val = fallback_value; git_config_entry *entry; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, key, false, GET_NO_ERRORS); if (entry && git_config_parse_bool(&val, entry->value) < 0) git_error_clear(); @@ -971,12 +1023,12 @@ int git_config__get_bool_force( } int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value) + const git_config *config, 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); + get_entry(&entry, config, key, false, GET_NO_ERRORS); if (entry && git_config_parse_int32(&val, entry->value) < 0) git_error_clear(); @@ -986,14 +1038,14 @@ int git_config__get_int_force( } int git_config_get_multivar_foreach( - const git_config *cfg, const char *name, const char *regexp, + const git_config *config, 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) + if ((err = git_config_multivar_iterator_new(&iter, config, name, regexp)) < 0) return err; found = 0; @@ -1055,13 +1107,13 @@ static void multivar_iter_free(git_config_iterator *_iter) git__free(iter); } -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *config, 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) + if ((error = git_config_iterator_new(&inner, config)) < 0) return error; iter = git__calloc(1, sizeof(multivar_iter)); @@ -1092,22 +1144,24 @@ on_error: return error; } -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) +int git_config_set_multivar(git_config *config, 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; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); + return GIT_EREADONLY; + } return backend->set_multivar(backend, name, regexp, value); } -int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) +int git_config_delete_multivar(git_config *config, 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; + if ((backend = get_writer(config)) == NULL) + return GIT_EREADONLY; return backend->del_multivar(backend, name, regexp); } @@ -1218,79 +1272,77 @@ int git_config__global_location(git_str *buf) int git_config_open_default(git_config **out) { int error; - git_config *cfg = NULL; + git_config *config = NULL; git_str buf = GIT_STR_INIT; - if ((error = git_config_new(&cfg)) < 0) + if ((error = git_config_new(&config)) < 0) return error; if (!git_config__find_global(&buf) || !git_config__global_location(&buf)) { - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); } if (!error && !git_config__find_xdg(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_XDG, NULL, 0); if (!error && !git_config__find_system(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); if (!error && !git_config__find_programdata(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); git_str_dispose(&buf); if (error) { - git_config_free(cfg); - cfg = NULL; + git_config_free(config); + config = NULL; } - *out = cfg; + *out = config; return error; } -int git_config_lock(git_transaction **out, git_config *cfg) +int git_config_lock(git_transaction **out, git_config *config) { + backend_instance *instance; int error; - git_config_backend *backend; - backend_internal *internal; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - 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; + if ((instance = get_writer_instance(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock: the configuration is read-only"); + return GIT_EREADONLY; } - backend = internal->backend; - if ((error = backend->lock(backend)) < 0) + if ((error = instance->backend->lock(instance->backend)) < 0 || + (error = git_transaction_config_new(out, config, instance)) < 0) return error; - return git_transaction_config_new(out, cfg); + GIT_REFCOUNT_INC(instance); + return 0; } -int git_config_unlock(git_config *cfg, int commit) +int git_config_unlock( + git_config *config, + void *data, + int commit) { - git_config_backend *backend; - backend_internal *internal; - - GIT_ASSERT_ARG(cfg); + backend_instance *instance = data; + int error; - 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; - } + GIT_ASSERT_ARG(config && data); + GIT_UNUSED(config); - backend = internal->backend; + error = instance->backend->unlock(instance->backend, commit); + GIT_REFCOUNT_DEC(instance, backend_instance_free); - return backend->unlock(backend, commit); + return error; } /*********** @@ -1447,7 +1499,7 @@ static int normalize_section(char *start, char *end) for (scan = start; *scan; ++scan) { if (end && scan >= end) break; - if (isalnum(*scan)) + if (git__isalnum(*scan)) *scan = (char)git__tolower(*scan); else if (*scan != '-' || scan == start) return GIT_EINVALIDSPEC; @@ -1509,19 +1561,32 @@ static int rename_config_entries_cb( int error = 0; struct rename_data *data = (struct rename_data *)payload; size_t base_len = git_str_len(data->name); + git_str value = GIT_STR_INIT; + + if (base_len > 0) { + if ((error = git_str_puts(data->name, + entry->name + data->old_len)) < 0 || + (error = git_config_set_multivar( + data->config, git_str_cstr(data->name), "^$", + entry->value)) < 0) + goto cleanup; + } - 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_putc(&value, '^'); + git_str_puts_escape_regex(&value, entry->value); + git_str_putc(&value, '$'); - git_str_truncate(data->name, base_len); + if (git_str_oom(&value)) { + error = -1; + goto cleanup; } - if (!error) - error = git_config_delete_entry(data->config, entry->name); + error = git_config_delete_multivar( + data->config, entry->name, git_str_cstr(&value)); + cleanup: + git_str_truncate(data->name, base_len); + git_str_dispose(&value); return error; } diff --git a/src/libgit2/config.h b/src/libgit2/config.h index 01b84b1..5003cbf 100644 --- a/src/libgit2/config.h +++ b/src/libgit2/config.h @@ -24,7 +24,8 @@ struct git_config { git_refcount rc; - git_vector backends; + git_vector readers; + git_vector writers; }; extern int git_config__global_location(git_str *buf); @@ -94,17 +95,21 @@ int git_config_lookup_map_enum(git_configmap_t *type_out, size_t map_n, int enum_val); /** - * Unlock the backend with the highest priority + * Unlock the given backend that was previously locked. * * 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 config the config instance + * @param data the config data passed to git_transaction_new * @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); +GIT_EXTERN(int) git_config_unlock( + git_config *config, + void *data, + int commit); #endif diff --git a/src/libgit2/config_backend.h b/src/libgit2/config_backend.h index dbb1905..37d25ab 100644 --- a/src/libgit2/config_backend.h +++ b/src/libgit2/config_backend.h @@ -37,15 +37,6 @@ extern int git_config_backend_from_file(git_config_backend **out, const char *pa */ 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); diff --git a/src/libgit2/config_entries.c b/src/libgit2/config_entries.c deleted file mode 100644 index 66aae09..0000000 --- a/src/libgit2/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/libgit2/config_entries.h b/src/libgit2/config_entries.h deleted file mode 100644 index 832379e..0000000 --- a/src/libgit2/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/libgit2/config_file.c b/src/libgit2/config_file.c index 716924d..340e856 100644 --- a/src/libgit2/config_file.c +++ b/src/libgit2/config_file.c @@ -13,7 +13,7 @@ #include "array.h" #include "str.h" #include "config_backend.h" -#include "config_entries.h" +#include "config_list.h" #include "config_parse.h" #include "filebuf.h" #include "regexp.h" @@ -24,6 +24,8 @@ /* Max depth for [include] directives */ #define MAX_INCLUDE_DEPTH 10 +#define CONFIG_FILE_TYPE "file" + typedef struct config_file { git_futils_filestamp stamp; unsigned char checksum[GIT_HASH_SHA256_SIZE]; @@ -34,7 +36,7 @@ typedef struct config_file { typedef struct { git_config_backend parent; git_mutex values_mutex; - git_config_entries *entries; + git_config_list *config_list; const git_repository *repo; git_config_level_t level; @@ -50,13 +52,13 @@ typedef struct { typedef struct { const git_repository *repo; config_file *file; - git_config_entries *entries; + git_config_list *config_list; 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_read(git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, int depth); +static int config_file_read_buffer(git_config_list *config_list, 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); @@ -65,7 +67,7 @@ static char *escape_value(const char *ptr); * 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) +static int config_file_take_list(git_config_list **out, config_file_backend *b) { int error; @@ -74,8 +76,8 @@ static int config_file_entries_take(git_config_entries **out, config_file_backen return error; } - git_config_entries_incref(b->entries); - *out = b->entries; + git_config_list_incref(b->config_list); + *out = b->config_list; git_mutex_unlock(&b->values_mutex); @@ -106,7 +108,7 @@ static int config_file_open(git_config_backend *cfg, git_config_level_t level, c b->level = level; b->repo = repo; - if ((res = git_config_entries_new(&b->entries)) < 0) + if ((res = git_config_list_new(&b->config_list)) < 0) return res; if (!git_fs_path_exists(b->file.path)) @@ -121,9 +123,9 @@ static int config_file_open(git_config_backend *cfg, git_config_level_t level, c 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; + if (res < 0 || (res = config_file_read(b->config_list, repo, &b->file, level, 0)) < 0) { + git_config_list_free(b->config_list); + b->config_list = NULL; } return res; @@ -175,10 +177,10 @@ static void config_file_clear_includes(config_file_backend *cfg) git_array_clear(cfg->file.includes); } -static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) +static int config_file_set_entries(git_config_backend *cfg, git_config_list *config_list) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *old = NULL; + git_config_list *old = NULL; int error; if (b->parent.readonly) { @@ -191,40 +193,40 @@ static int config_file_set_entries(git_config_backend *cfg, git_config_entries * goto out; } - old = b->entries; - b->entries = entries; + old = b->config_list; + b->config_list = config_list; git_mutex_unlock(&b->values_mutex); out: - git_config_entries_free(old); + git_config_list_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; + git_config_list *config_list = 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, + if ((error = git_config_list_new(&config_list)) < 0 || + (error = config_file_read_buffer(config_list, b->repo, &b->file, b->level, 0, buf, buflen)) < 0 || - (error = config_file_set_entries(cfg, entries)) < 0) + (error = config_file_set_entries(cfg, config_list)) < 0) goto out; - entries = NULL; + config_list = NULL; out: - git_config_entries_free(entries); + git_config_list_free(config_list); 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; + git_config_list *config_list = NULL; int error, modified; if (cfg->readonly) @@ -238,14 +240,14 @@ static int config_file_refresh(git_config_backend *cfg) 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) + if ((error = git_config_list_new(&config_list)) < 0 || + (error = config_file_read(config_list, b->repo, &b->file, b->level, 0)) < 0 || + (error = config_file_set_entries(cfg, config_list)) < 0) goto out; - entries = NULL; + config_list = NULL; out: - git_config_entries_free(entries); + git_config_list_free(config_list); return (error == GIT_ENOTFOUND) ? 0 : error; } @@ -258,7 +260,7 @@ static void config_file_free(git_config_backend *_backend) return; config_file_clear(&backend->file); - git_config_entries_free(backend->entries); + git_config_list_free(backend->config_list); git_mutex_free(&backend->values_mutex); git__free(backend); } @@ -268,19 +270,19 @@ static int config_file_iterator( struct git_config_backend *backend) { config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); - git_config_entries *dupped = NULL, *entries = NULL; + git_config_list *dupped = NULL, *config_list = 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) + (error = config_file_take_list(&config_list, b)) < 0 || + (error = git_config_list_dup(&dupped, config_list)) < 0 || + (error = git_config_list_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); + /* Let iterator delete duplicated config_list when it's done */ + git_config_list_free(config_list); + git_config_list_free(dupped); return error; } @@ -292,24 +294,24 @@ static int config_file_snapshot(git_config_backend **out, git_config_backend *ba 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; + git_config_list *config_list; + git_config_list_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) + if ((error = config_file_take_list(&config_list, 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_config_list_get_unique(&existing, config_list, key)) < 0) { if (error != GIT_ENOTFOUND) goto out; error = 0; - } else if ((!existing->value && !value) || - (existing->value && value && !strcmp(existing->value, value))) { + } else if ((!existing->base.value && !value) || + (existing->base.value && value && !strcmp(existing->base.value, value))) { /* don't update if old and new values already match */ error = 0; goto out; @@ -325,43 +327,34 @@ static int config_file_set(git_config_backend *cfg, const char *name, const char goto out; out: - git_config_entries_free(entries); + git_config_list_free(config_list); 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; + git_config_list *config_list = NULL; + git_config_list_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) + if ((error = config_file_take_list(&config_list, h)) < 0) return error; - if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { - git_config_entries_free(entries); + if ((error = (git_config_list_get(&entry, config_list, key))) < 0) { + git_config_list_free(config_list); return error; } - entry->free = config_file_entry_free; - entry->payload = entries; - *out = entry; + *out = &entry->base; return 0; } @@ -396,29 +389,29 @@ out: 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; + git_config_list *config_list = NULL; + git_config_list_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) + if ((error = config_file_take_list(&config_list, 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_config_list_get_unique(&entry, config_list, 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) + if ((error = config_file_write(b, name, entry->base.name, NULL, NULL)) < 0) goto out; out: - git_config_entries_free(entries); + git_config_list_free(config_list); git__free(key); return error; } @@ -426,8 +419,8 @@ out: 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_config_list *config_list = NULL; + git_config_list_entry *entry = NULL; git_regexp preg = GIT_REGEX_INIT; char *key = NULL; int result; @@ -435,10 +428,10 @@ static int config_file_delete_multivar(git_config_backend *cfg, const char *name if ((result = git_config__normalize_name(name, &key)) < 0) goto out; - if ((result = config_file_entries_take(&entries, b)) < 0) + if ((result = config_file_take_list(&config_list, b)) < 0) goto out; - if ((result = git_config_entries_get(&entry, entries, key)) < 0) { + if ((result = git_config_list_get(&entry, config_list, key)) < 0) { if (result == GIT_ENOTFOUND) git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); goto out; @@ -451,7 +444,7 @@ static int config_file_delete_multivar(git_config_backend *cfg, const char *name goto out; out: - git_config_entries_free(entries); + git_config_list_free(config_list); git__free(key); git_regexp_dispose(&preg); return result; @@ -591,7 +584,7 @@ static int parse_include(config_file_parse_data *parse_data, const char *file) git_array_init(include->includes); include->path = git_str_detach(&path); - result = config_file_read(parse_data->entries, parse_data->repo, include, + result = config_file_read(parse_data->config_list, parse_data->repo, include, parse_data->level, parse_data->depth+1); if (result == GIT_ENOTFOUND) { @@ -776,7 +769,7 @@ static int read_on_variable( { config_file_parse_data *parse_data = (config_file_parse_data *)data; git_str buf = GIT_STR_INIT; - git_config_entry *entry; + git_config_list_entry *entry; const char *c; int result = 0; @@ -799,30 +792,45 @@ static int read_on_variable( if (git_str_oom(&buf)) return -1; - entry = git__calloc(1, sizeof(git_config_entry)); + entry = git__calloc(1, sizeof(git_config_list_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) + entry->base.name = git_str_detach(&buf); + GIT_ERROR_CHECK_ALLOC(entry->base.name); + + if (var_value) { + entry->base.value = git__strdup(var_value); + GIT_ERROR_CHECK_ALLOC(entry->base.value); + } + + entry->base.backend_type = git_config_list_add_string(parse_data->config_list, CONFIG_FILE_TYPE); + GIT_ERROR_CHECK_ALLOC(entry->base.backend_type); + + entry->base.origin_path = git_config_list_add_string(parse_data->config_list, parse_data->file->path); + GIT_ERROR_CHECK_ALLOC(entry->base.origin_path); + + entry->base.level = parse_data->level; + entry->base.include_depth = parse_data->depth; + entry->base.free = git_config_list_entry_free; + entry->config_list = parse_data->config_list; + + if ((result = git_config_list_append(parse_data->config_list, 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); + if (!git__strcmp(entry->base.name, "include.path")) + result = parse_include(parse_data, entry->base.value); + else if (!git__prefixcmp(entry->base.name, "includeif.") && + !git__suffixcmp(entry->base.name, ".path")) + result = parse_conditional_include(parse_data, entry->base.name, entry->base.value); return result; } static int config_file_read_buffer( - git_config_entries *entries, + git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, @@ -851,7 +859,7 @@ static int config_file_read_buffer( parse_data.repo = repo; parse_data.file = file; - parse_data.entries = entries; + parse_data.config_list = config_list; parse_data.level = level; parse_data.depth = depth; @@ -862,7 +870,7 @@ out: } static int config_file_read( - git_config_entries *entries, + git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, @@ -884,7 +892,7 @@ static int config_file_read( if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA256)) < 0) goto out; - if ((error = config_file_read_buffer(entries, repo, file, level, depth, + if ((error = config_file_read_buffer(config_list, repo, file, level, depth, contents.ptr, contents.size)) < 0) goto out; diff --git a/src/libgit2/config_list.c b/src/libgit2/config_list.c new file mode 100644 index 0000000..0b7a4f3 --- /dev/null +++ b/src/libgit2/config_list.c @@ -0,0 +1,288 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This 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_list.h" + +typedef struct config_entry_list { + struct config_entry_list *next; + struct config_entry_list *last; + git_config_list_entry *entry; +} config_entry_list; + +typedef struct { + git_config_list_entry *entry; + bool multivar; +} config_entry_map_head; + +typedef struct config_list_iterator { + git_config_iterator parent; + git_config_list *list; + config_entry_list *head; +} config_list_iterator; + +struct git_config_list { + git_refcount rc; + + /* Interned strings - paths to config files or backend types */ + git_strmap *strings; + + /* Config entries */ + git_strmap *map; + config_entry_list *entries; +}; + +int git_config_list_new(git_config_list **out) +{ + git_config_list *config_list; + + config_list = git__calloc(1, sizeof(git_config_list)); + GIT_ERROR_CHECK_ALLOC(config_list); + GIT_REFCOUNT_INC(config_list); + + if (git_strmap_new(&config_list->strings) < 0 || + git_strmap_new(&config_list->map) < 0) { + git_strmap_free(config_list->strings); + git_strmap_free(config_list->map); + git__free(config_list); + + return -1; + } + + *out = config_list; + return 0; +} + +int git_config_list_dup_entry(git_config_list *config_list, const git_config_entry *entry) +{ + git_config_list_entry *duplicated; + int error; + + duplicated = git__calloc(1, sizeof(git_config_list_entry)); + GIT_ERROR_CHECK_ALLOC(duplicated); + + duplicated->base.name = git__strdup(entry->name); + GIT_ERROR_CHECK_ALLOC(duplicated->base.name); + + if (entry->value) { + duplicated->base.value = git__strdup(entry->value); + GIT_ERROR_CHECK_ALLOC(duplicated->base.value); + } + + duplicated->base.backend_type = git_config_list_add_string(config_list, entry->backend_type); + GIT_ERROR_CHECK_ALLOC(duplicated->base.backend_type); + + if (entry->origin_path) { + duplicated->base.origin_path = git_config_list_add_string(config_list, entry->origin_path); + GIT_ERROR_CHECK_ALLOC(duplicated->base.origin_path); + } + + duplicated->base.level = entry->level; + duplicated->base.include_depth = entry->include_depth; + duplicated->base.free = git_config_list_entry_free; + duplicated->config_list = config_list; + + if ((error = git_config_list_append(config_list, duplicated)) < 0) + goto out; + +out: + if (error && duplicated) { + git__free((char *) duplicated->base.name); + git__free((char *) duplicated->base.value); + git__free(duplicated); + } + return error; +} + +int git_config_list_dup(git_config_list **out, git_config_list *config_list) +{ + git_config_list *result = NULL; + config_entry_list *head; + int error; + + if ((error = git_config_list_new(&result)) < 0) + goto out; + + for (head = config_list->entries; head; head = head->next) + if ((git_config_list_dup_entry(result, &head->entry->base)) < 0) + goto out; + + *out = result; + result = NULL; + +out: + git_config_list_free(result); + return error; +} + +void git_config_list_incref(git_config_list *config_list) +{ + GIT_REFCOUNT_INC(config_list); +} + +static void config_list_free(git_config_list *config_list) +{ + config_entry_list *entry_list = NULL, *next; + config_entry_map_head *head; + char *str; + + git_strmap_foreach_value(config_list->strings, str, { + git__free(str); + }); + git_strmap_free(config_list->strings); + + git_strmap_foreach_value(config_list->map, head, { + git__free((char *) head->entry->base.name); + git__free(head); + }); + git_strmap_free(config_list->map); + + entry_list = config_list->entries; + while (entry_list != NULL) { + next = entry_list->next; + git__free((char *) entry_list->entry->base.value); + git__free(entry_list->entry); + git__free(entry_list); + entry_list = next; + } + + git__free(config_list); +} + +void git_config_list_free(git_config_list *config_list) +{ + if (config_list) + GIT_REFCOUNT_DEC(config_list, config_list_free); +} + +int git_config_list_append(git_config_list *config_list, git_config_list_entry *entry) +{ + config_entry_list *list_head; + config_entry_map_head *map_head; + + if ((map_head = git_strmap_get(config_list->map, entry->base.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 list, we can just free + * all except the first entry's name and just re-use it. + */ + git__free((char *) entry->base.name); + entry->base.name = map_head->entry->base.name; + } else { + map_head = git__calloc(1, sizeof(*map_head)); + if ((git_strmap_set(config_list->map, entry->base.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 (config_list->entries) + config_list->entries->last->next = list_head; + else + config_list->entries = list_head; + config_list->entries->last = list_head; + + return 0; +} + +int git_config_list_get(git_config_list_entry **out, git_config_list *config_list, const char *key) +{ + config_entry_map_head *entry; + + if ((entry = git_strmap_get(config_list->map, key)) == NULL) + return GIT_ENOTFOUND; + + *out = entry->entry; + return 0; +} + +int git_config_list_get_unique(git_config_list_entry **out, git_config_list *config_list, const char *key) +{ + config_entry_map_head *entry; + + if ((entry = git_strmap_get(config_list->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->base.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_list_iterator *it = (config_list_iterator *) iter; + git_config_list_free(it->list); + git__free(it); +} + +static int config_iterator_next( + git_config_entry **entry, + git_config_iterator *iter) +{ + config_list_iterator *it = (config_list_iterator *) iter; + + if (!it->head) + return GIT_ITEROVER; + + *entry = &it->head->entry->base; + it->head = it->head->next; + + return 0; +} + +int git_config_list_iterator_new(git_config_iterator **out, git_config_list *config_list) +{ + config_list_iterator *it; + + it = git__calloc(1, sizeof(config_list_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + it->parent.next = config_iterator_next; + it->parent.free = config_iterator_free; + it->head = config_list->entries; + it->list = config_list; + + git_config_list_incref(config_list); + *out = &it->parent; + + return 0; +} + +/* release the map containing the entry as an equivalent to freeing it */ +void git_config_list_entry_free(git_config_entry *e) +{ + git_config_list_entry *entry = (git_config_list_entry *)e; + git_config_list_free(entry->config_list); +} + +const char *git_config_list_add_string( + git_config_list *config_list, + const char *str) +{ + const char *s; + + if ((s = git_strmap_get(config_list->strings, str)) != NULL) + return s; + + if ((s = git__strdup(str)) == NULL || + git_strmap_set(config_list->strings, s, (void *)s) < 0) + return NULL; + + return s; +} diff --git a/src/libgit2/config_list.h b/src/libgit2/config_list.h new file mode 100644 index 0000000..023bca1 --- /dev/null +++ b/src/libgit2/config_list.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. + */ + +#include "common.h" + +#include "git2/sys/config.h" +#include "config.h" + +typedef struct git_config_list git_config_list; + +typedef struct { + git_config_entry base; + git_config_list *config_list; +} git_config_list_entry; + +int git_config_list_new(git_config_list **out); +int git_config_list_dup(git_config_list **out, git_config_list *list); +int git_config_list_dup_entry(git_config_list *list, const git_config_entry *entry); +void git_config_list_incref(git_config_list *list); +void git_config_list_free(git_config_list *list); +/* Add or append the new config option */ +int git_config_list_append(git_config_list *list, git_config_list_entry *entry); +int git_config_list_get(git_config_list_entry **out, git_config_list *list, const char *key); +int git_config_list_get_unique(git_config_list_entry **out, git_config_list *list, const char *key); +int git_config_list_iterator_new(git_config_iterator **out, git_config_list *list); +const char *git_config_list_add_string(git_config_list *list, const char *str); + +void git_config_list_entry_free(git_config_entry *entry); diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c index 560229c..406aa83 100644 --- a/src/libgit2/config_mem.c +++ b/src/libgit2/config_mem.c @@ -9,16 +9,29 @@ #include "config_backend.h" #include "config_parse.h" -#include "config_entries.h" +#include "config_list.h" +#include "strlist.h" typedef struct { git_config_backend parent; - git_config_entries *entries; + + char *backend_type; + char *origin_path; + + git_config_list *config_list; + + /* Configuration data in the config file format */ git_str cfg; + + /* Array of key=value pairs */ + char **values; + size_t values_len; } config_memory_backend; typedef struct { - git_config_entries *entries; + const char *backend_type; + const char *origin_path; + git_config_list *config_list; git_config_level_t level; } config_memory_parse_data; @@ -39,7 +52,7 @@ static int read_variable_cb( { config_memory_parse_data *parse_data = (config_memory_parse_data *) payload; git_str buf = GIT_STR_INIT; - git_config_entry *entry; + git_config_list_entry *entry; const char *c; int result; @@ -62,35 +75,46 @@ static int read_variable_cb( if (git_str_oom(&buf)) return -1; - entry = git__calloc(1, sizeof(git_config_entry)); + entry = git__calloc(1, sizeof(git_config_list_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) + entry->base.name = git_str_detach(&buf); + entry->base.value = var_value ? git__strdup(var_value) : NULL; + entry->base.level = parse_data->level; + entry->base.include_depth = 0; + entry->base.backend_type = parse_data->backend_type; + entry->base.origin_path = parse_data->origin_path; + entry->base.free = git_config_list_entry_free; + entry->config_list = parse_data->config_list; + + if ((result = git_config_list_append(parse_data->config_list, entry)) < 0) return result; return result; } -static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) +static int parse_config( + config_memory_backend *memory_backend, + git_config_level_t level) { - 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) + 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.backend_type = git_config_list_add_string( + memory_backend->config_list, memory_backend->backend_type); + parse_data.origin_path = memory_backend->origin_path ? + git_config_list_add_string(memory_backend->config_list, + memory_backend->origin_path) : + NULL; + parse_data.config_list = memory_backend->config_list; parse_data.level = level; - if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0) + if ((error = git_config_parse(&parser, NULL, read_variable_cb, + NULL, NULL, &parse_data)) < 0) goto out; out: @@ -98,10 +122,85 @@ out: return error; } +static int parse_values( + config_memory_backend *memory_backend, + git_config_level_t level) +{ + git_config_list_entry *entry; + const char *eql, *backend_type, *origin_path; + size_t name_len, i; + + backend_type = git_config_list_add_string( + memory_backend->config_list, memory_backend->backend_type); + GIT_ERROR_CHECK_ALLOC(backend_type); + + origin_path = memory_backend->origin_path ? + git_config_list_add_string(memory_backend->config_list, + memory_backend->origin_path) : + NULL; + + for (i = 0; i < memory_backend->values_len; i++) { + eql = strchr(memory_backend->values[i], '='); + name_len = eql - memory_backend->values[i]; + + if (name_len == 0) { + git_error_set(GIT_ERROR_CONFIG, "empty config key"); + return -1; + } + + entry = git__calloc(1, sizeof(git_config_list_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->base.name = git__strndup(memory_backend->values[i], name_len); + GIT_ERROR_CHECK_ALLOC(entry->base.name); + + if (eql) { + entry->base.value = git__strdup(eql + 1); + GIT_ERROR_CHECK_ALLOC(entry->base.value); + } + + entry->base.level = level; + entry->base.include_depth = 0; + entry->base.backend_type = backend_type; + entry->base.origin_path = origin_path; + entry->base.free = git_config_list_entry_free; + entry->config_list = memory_backend->config_list; + + if (git_config_list_append(memory_backend->config_list, entry) < 0) + return -1; + } + + return 0; +} + +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_UNUSED(repo); + + if (memory_backend->cfg.size > 0 && + parse_config(memory_backend, level) < 0) + return -1; + + if (memory_backend->values_len > 0 && + parse_values(memory_backend, level) < 0) + return -1; + + return 0; +} + 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); + git_config_list_entry *entry; + int error; + + if ((error = git_config_list_get(&entry, memory_backend->config_list, key)) != 0) + return error; + + *out = &entry->base; + return 0; } static int config_memory_iterator( @@ -109,18 +208,18 @@ static int config_memory_iterator( git_config_backend *backend) { config_memory_backend *memory_backend = (config_memory_backend *) backend; - git_config_entries *entries; + git_config_list *config_list; int error; - if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0) + if ((error = git_config_list_dup(&config_list, memory_backend->config_list)) < 0) goto out; - if ((error = git_config_entries_iterator_new(iter, entries)) < 0) + if ((error = git_config_list_iterator_new(iter, config_list)) < 0) goto out; out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); + /* Let iterator delete duplicated config_list when it's done */ + git_config_list_free(config_list); return error; } @@ -177,28 +276,24 @@ static void config_memory_free(git_config_backend *_backend) if (backend == NULL) return; - git_config_entries_free(backend->entries); + git__free(backend->origin_path); + git__free(backend->backend_type); + git_config_list_free(backend->config_list); + git_strlist_free(backend->values, backend->values_len); git_str_dispose(&backend->cfg); git__free(backend); } -int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len) +static config_memory_backend *config_backend_new( + git_config_backend_memory_options *opts) { config_memory_backend *backend; - backend = git__calloc(1, sizeof(config_memory_backend)); - GIT_ERROR_CHECK_ALLOC(backend); + if ((backend = git__calloc(1, sizeof(config_memory_backend))) == NULL) + return NULL; - 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; - } + if (git_config_list_new(&backend->config_list) < 0) + goto on_error; backend->parent.version = GIT_CONFIG_BACKEND_VERSION; backend->parent.readonly = 1; @@ -214,7 +309,66 @@ int git_config_backend_from_string(git_config_backend **out, const char *cfg, si backend->parent.snapshot = git_config_backend_snapshot; backend->parent.free = config_memory_free; + backend->backend_type = git__strdup(opts && opts->backend_type ? + opts->backend_type : "in-memory"); + + if (backend->backend_type == NULL) + goto on_error; + + if (opts && opts->origin_path && + (backend->origin_path = git__strdup(opts->origin_path)) == NULL) + goto on_error; + + return backend; + +on_error: + git_config_list_free(backend->config_list); + git__free(backend->origin_path); + git__free(backend->backend_type); + git__free(backend); + return NULL; +} + +int git_config_backend_from_string( + git_config_backend **out, + const char *cfg, + size_t len, + git_config_backend_memory_options *opts) +{ + config_memory_backend *backend; + + if ((backend = config_backend_new(opts)) == NULL) + return -1; + + if (git_str_set(&backend->cfg, cfg, len) < 0) { + git_config_list_free(backend->config_list); + git__free(backend); + return -1; + } + *out = (git_config_backend *)backend; + return 0; +} + +int git_config_backend_from_values( + git_config_backend **out, + const char **values, + size_t len, + git_config_backend_memory_options *opts) +{ + config_memory_backend *backend; + if ((backend = config_backend_new(opts)) == NULL) + return -1; + + if (git_strlist_copy(&backend->values, values, len) < 0) { + git_config_list_free(backend->config_list); + git__free(backend); + return -1; + } + + backend->values_len = len; + + *out = (git_config_backend *)backend; return 0; } diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c index 0693136..7f933db 100644 --- a/src/libgit2/config_parse.c +++ b/src/libgit2/config_parse.c @@ -25,9 +25,9 @@ static void set_parse_error(git_config_parser *reader, int col, const char *erro } -GIT_INLINE(int) config_keychar(int c) +GIT_INLINE(int) config_keychar(char c) { - return isalnum(c) || c == '-'; + return git__isalnum(c) || c == '-'; } static int strip_comments(char *line, int in_quotes) @@ -158,9 +158,10 @@ end_error: static int parse_section_header(git_config_parser *reader, char **section_out) { char *name, *name_end; - int name_length, c, pos; + int name_length, pos; int result; char *line; + char c; size_t line_len; git_parse_advance_ws(&reader->ctx); @@ -279,8 +280,7 @@ static int skip_bom(git_parse_ctx *parser) */ /* '\"' -> '"' etc */ -static int unescape_line( - char **out, bool *is_multi, const char *ptr, int quote_count) +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; @@ -296,7 +296,8 @@ static int unescape_line( while (*ptr != '\0') { if (*ptr == '"') { - quote_count++; + if (quote_count) + (*quote_count)++; } else if (*ptr != '\\') { *fixed++ = *ptr; } else { @@ -358,7 +359,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_str *value, i goto next; if ((error = unescape_line(&proc_line, &multiline, - line, in_quotes)) < 0) + line, &in_quotes)) < 0) goto out; /* Add this line to the multiline var */ @@ -382,7 +383,7 @@ out: GIT_INLINE(bool) is_namechar(char c) { - return isalnum(c) || c == '-'; + return git__isalnum(c) || c == '-'; } static int parse_name( @@ -445,7 +446,7 @@ static int parse_variable(git_config_parser *reader, char **var_name, char **var while (git__isspace(value_start[0])) value_start++; - if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0) + if ((error = unescape_line(&value, &multiline, value_start, NULL)) < 0) goto out; if (multiline) { diff --git a/src/libgit2/config_snapshot.c b/src/libgit2/config_snapshot.c index e295d2f..d8b8733 100644 --- a/src/libgit2/config_snapshot.c +++ b/src/libgit2/config_snapshot.c @@ -8,12 +8,12 @@ #include "config_backend.h" #include "config.h" -#include "config_entries.h" +#include "config_list.h" typedef struct { git_config_backend parent; git_mutex values_mutex; - git_config_entries *entries; + git_config_list *config_list; git_config_backend *source; } config_snapshot_backend; @@ -28,31 +28,24 @@ static int config_snapshot_iterator( struct git_config_backend *backend) { config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent); - git_config_entries *entries = NULL; + git_config_list *config_list = NULL; int error; - if ((error = git_config_entries_dup(&entries, b->entries)) < 0 || - (error = git_config_entries_iterator_new(iter, entries)) < 0) + if ((error = git_config_list_dup(&config_list, b->config_list)) < 0 || + (error = git_config_list_iterator_new(iter, config_list)) < 0) goto out; out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); + /* Let iterator delete duplicated config_list when it's done */ + git_config_list_free(config_list); 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; + git_config_list *config_list = NULL; + git_config_list_entry *entry; int error = 0; if (git_mutex_lock(&b->values_mutex) < 0) { @@ -60,19 +53,16 @@ static int config_snapshot_get(git_config_backend *cfg, const char *key, git_con return -1; } - entries = b->entries; - git_config_entries_incref(entries); + config_list = b->config_list; + git_config_list_incref(config_list); git_mutex_unlock(&b->values_mutex); - if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { - git_config_entries_free(entries); + if ((error = (git_config_list_get(&entry, config_list, key))) < 0) { + git_config_list_free(config_list); return error; } - entry->free = config_snapshot_entry_free; - entry->payload = entries; - *out = entry; - + *out = &entry->base; return 0; } @@ -135,7 +125,7 @@ static void config_snapshot_free(git_config_backend *_backend) if (backend == NULL) return; - git_config_entries_free(backend->entries); + git_config_list_free(backend->config_list); git_mutex_free(&backend->values_mutex); git__free(backend); } @@ -143,7 +133,7 @@ static void config_snapshot_free(git_config_backend *_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_list *config_list = NULL; git_config_iterator *it = NULL; git_config_entry *entry; int error; @@ -152,12 +142,12 @@ static int config_snapshot_open(git_config_backend *cfg, git_config_level_t leve GIT_UNUSED(level); GIT_UNUSED(repo); - if ((error = git_config_entries_new(&entries)) < 0 || + if ((error = git_config_list_new(&config_list)) < 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) + if ((error = git_config_list_dup_entry(config_list, entry)) < 0) goto out; if (error < 0) { @@ -166,12 +156,12 @@ static int config_snapshot_open(git_config_backend *cfg, git_config_level_t leve error = 0; } - b->entries = entries; + b->config_list = config_list; out: git_config_iterator_free(it); if (error) - git_config_entries_free(entries); + git_config_list_free(config_list); return error; } diff --git a/src/libgit2/diff_print.c b/src/libgit2/diff_print.c index 32c9368..daeefca 100644 --- a/src/libgit2/diff_print.c +++ b/src/libgit2/diff_print.c @@ -29,6 +29,7 @@ typedef struct { const char *new_prefix; uint32_t flags; int id_strlen; + unsigned int sent_file_header; git_oid_t oid_type; int (*strcomp)(const char *, const char *); @@ -579,6 +580,30 @@ static int diff_print_patch_file_binary( return error; } +GIT_INLINE(int) should_force_header(const git_diff_delta *delta) +{ + if (delta->old_file.mode != delta->new_file.mode) + return 1; + + if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) + return 1; + + return 0; +} + +GIT_INLINE(int) flush_file_header(const git_diff_delta *delta, diff_print_info *pi) +{ + if (pi->sent_file_header) + return 0; + + 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); + pi->sent_file_header = 1; + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { @@ -609,15 +634,22 @@ static int diff_print_patch_file( (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) return 0; + pi->sent_file_header = 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); + /* + * pi->buf now contains the file header data. Go ahead and send it + * if there's useful data in there, like similarity. Otherwise, we + * should queue it to send when we see the first hunk. This prevents + * us from sending a header when all hunks were ignored. + */ + if (should_force_header(delta) && (error = flush_file_header(delta, pi)) < 0) + return error; - return pi->print_cb(delta, NULL, &pi->line, pi->payload); + return 0; } static int diff_print_patch_binary( @@ -632,6 +664,9 @@ static int diff_print_patch_binary( pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; int error; + if ((error = flush_file_header(delta, pi)) < 0) + return error; + git_str_clear(pi->buf); if ((error = diff_print_patch_file_binary( @@ -651,10 +686,14 @@ static int diff_print_patch_hunk( void *data) { diff_print_info *pi = data; + int error; if (S_ISDIR(d->new_file.mode)) return 0; + if ((error = flush_file_header(d, pi)) < 0) + return error; + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; pi->line.content = h->header; pi->line.content_len = h->header_len; @@ -669,10 +708,14 @@ static int diff_print_patch_line( void *data) { diff_print_info *pi = data; + int error; if (S_ISDIR(delta->new_file.mode)) return 0; + if ((error = flush_file_header(delta, pi)) < 0) + return error; + return pi->print_cb(delta, hunk, line, pi->payload); } diff --git a/src/libgit2/diff_tform.c b/src/libgit2/diff_tform.c index 4a156c7..9fa3cef 100644 --- a/src/libgit2/diff_tform.c +++ b/src/libgit2/diff_tform.c @@ -653,6 +653,23 @@ static int calc_self_similarity( return 0; } +static void handle_non_blob( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx) +{ + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + /* skip things that are blobs */ + if (GIT_MODE_ISBLOB(delta->old_file.mode)) + return; + + /* honor "remove unmodified" flag for non-blobs (eg submodules) */ + if (delta->status == GIT_DELTA_UNMODIFIED && + FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED)) + delta->flags |= GIT_DIFF_FLAG__TO_DELETE; +} + static bool is_rename_target( git_diff *diff, const git_diff_find_options *opts, @@ -810,7 +827,8 @@ int git_diff_find_similar( 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 num_rewrites = 0, num_updates = 0, num_bumped = 0, + num_to_delete = 0; size_t sigcache_size; void **sigcache = NULL; /* cache of similarity metric file signatures */ diff_find_match *tgt2src = NULL; @@ -844,6 +862,8 @@ int git_diff_find_similar( * mark them for splitting if break-rewrites is enabled */ git_vector_foreach(&diff->deltas, t, tgt) { + handle_non_blob(diff, &opts, t); + if (is_rename_source(diff, &opts, t, sigcache)) ++num_srcs; @@ -852,11 +872,14 @@ int git_diff_find_similar( if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) num_rewrites++; + + if ((tgt->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) + num_to_delete++; } - /* if there are no candidate srcs or tgts, we're done */ + /* If there are no candidate srcs or tgts, no need to find matches */ if (!num_srcs || !num_tgts) - goto cleanup; + goto split_and_delete; src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); GIT_ERROR_CHECK_ALLOC(src2tgt); @@ -1093,15 +1116,20 @@ find_best_matches: } } +split_and_delete: /* * Actually split and delete entries as needed */ - if (num_rewrites > 0 || num_updates > 0) + if (num_rewrites > 0 || num_updates > 0 || num_to_delete > 0) { + size_t apply_len = diff->deltas.length - + num_rewrites - num_to_delete; + error = apply_splits_and_deletes( - diff, diff->deltas.length - num_rewrites, + diff, apply_len, FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); + } cleanup: git__free(tgt2src); diff --git a/src/libgit2/errors.c b/src/libgit2/errors.c deleted file mode 100644 index 2e58948..0000000 --- a/src/libgit2/errors.c +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This 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 oom_error = { - "Out of memory", - GIT_ERROR_NOMEMORY -}; - -static git_error uninitialized_error = { - "libgit2 has not been initialized; you must call git_libgit2_init", - GIT_ERROR_INVALID -}; - -static git_error tlsdata_error = { - "thread-local data initialization failure", - GIT_ERROR -}; - -static void set_error_from_buffer(int error_class) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_error *error; - git_str *buf; - - if (!threadstate) - return; - - error = &threadstate->error_t; - buf = &threadstate->error_buf; - - error->message = buf->ptr; - error->klass = error_class; - - threadstate->last_error = error; -} - -static void set_error(int error_class, char *string) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_str *buf; - - if (!threadstate) - return; - - buf = &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 *threadstate = git_threadstate_get(); - - if (!threadstate) - return; - - threadstate->last_error = &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 - - git_threadstate *threadstate = git_threadstate_get(); - int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; - git_str *buf; - - if (!threadstate) - return; - - buf = &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_threadstate *threadstate = git_threadstate_get(); - git_str *buf; - - GIT_ASSERT_ARG(string); - - if (!threadstate) - return -1; - - buf = &threadstate->error_buf; - - 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) -{ - git_threadstate *threadstate = git_threadstate_get(); - - if (!threadstate) - return; - - if (threadstate->last_error != NULL) { - set_error(0, NULL); - threadstate->last_error = NULL; - } - - errno = 0; -#ifdef GIT_WIN32 - SetLastError(0); -#endif -} - -const git_error *git_error_last(void) -{ - git_threadstate *threadstate; - - /* If the library is not initialized, return a static error. */ - if (!git_libgit2_init_count()) - return &uninitialized_error; - - if ((threadstate = git_threadstate_get()) == NULL) - return &tlsdata_error; - - return threadstate->last_error; -} - -int git_error_state_capture(git_error_state *state, int error_code) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_error *error; - git_str *error_buf; - - if (!threadstate) - return -1; - - error = threadstate->last_error; - error_buf = &threadstate->error_buf; - - memset(state, 0, sizeof(git_error_state)); - - if (!error_code) - return 0; - - state->error_code = error_code; - state->oom = (error == &oom_error); - - if (error) { - state->error_msg.klass = error->klass; - - if (state->oom) - state->error_msg.message = 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/fetch.c b/src/libgit2/fetch.c index d74abb4..8e2660f 100644 --- a/src/libgit2/fetch.c +++ b/src/libgit2/fetch.c @@ -60,9 +60,11 @@ static int mark_local(git_remote *remote) git_vector_foreach(&remote->refs, i, head) { /* If we have the object, mark it so we don't ask for it. - However if we are unshallowing, we need to ask for it - even though the head exists locally. */ - if (remote->nego.depth != INT_MAX && git_odb_exists(odb, &head->oid)) + However if we are unshallowing or changing history + depth, we need to ask for it even though the head + exists locally. */ + if (remote->nego.depth == GIT_FETCH_DEPTH_FULL && + git_odb_exists(odb, &head->oid)) head->local = 1; else remote->need_pack = 1; diff --git a/src/libgit2/filter.c b/src/libgit2/filter.c index 80a3cae..fdfc409 100644 --- a/src/libgit2/filter.c +++ b/src/libgit2/filter.c @@ -908,7 +908,7 @@ 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}; + git_error *last_error; int error; GIT_ASSERT_ARG(buffered_stream); @@ -946,9 +946,9 @@ static int buffered_stream_close(git_writestream *s) } else { /* close stream before erroring out taking care * to preserve the original error */ - git_error_state_capture(&error_state, error); + git_error_save(&last_error); buffered_stream->target->close(buffered_stream->target); - git_error_state_restore(&error_state); + git_error_restore(last_error); return error; } diff --git a/src/libgit2/git2.rc b/src/libgit2/git2.rc index d273afd..b94ecaf 100644 --- a/src/libgit2/git2.rc +++ b/src/libgit2/git2.rc @@ -10,7 +10,7 @@ #endif #ifndef LIBGIT2_COMMENTS -# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" +# define LIBGIT2_COMMENTS "For more information visit https://libgit2.org/" #endif #ifdef __GNUC__ diff --git a/src/libgit2/index.c b/src/libgit2/index.c index ccb3823..670fbc5 100644 --- a/src/libgit2/index.c +++ b/src/libgit2/index.c @@ -1612,15 +1612,17 @@ int git_index_add_bypath(git_index *index, const char *path) if (ret == GIT_EDIRECTORY) { git_submodule *sm; - git_error_state err; + git_error *last_error; - git_error_state_capture(&err, ret); + git_error_save(&last_error); ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); - if (ret == GIT_ENOTFOUND) - return git_error_state_restore(&err); + if (ret == GIT_ENOTFOUND) { + git_error_restore(last_error); + return GIT_EDIRECTORY; + } - git_error_state_free(&err); + git_error_free(last_error); /* * EEXISTS means that there is a repository at that path, but it's not known @@ -2758,6 +2760,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) unsigned int i; struct index_header header = { 0 }; unsigned char checksum[GIT_HASH_MAX_SIZE]; + unsigned char zero_checksum[GIT_HASH_MAX_SIZE] = { 0 }; size_t checksum_size = git_hash_size(git_oid_algorithm(index->oid_type)); const char *last = NULL; const char *empty = ""; @@ -2847,8 +2850,11 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* * SHA-1 or SHA-256 (depending on the repository's object format) * over the content of the index file before this checksum. + * Note: checksum may be 0 if the index was written by a client + * where index.skipHash was set to true. */ - if (memcmp(checksum, buffer, checksum_size) != 0) { + if (memcmp(zero_checksum, buffer, checksum_size) != 0 && + memcmp(checksum, buffer, checksum_size) != 0) { error = index_error_invalid( "calculated checksum does not match expected"); goto done; diff --git a/src/libgit2/iterator.c b/src/libgit2/iterator.c index 95ded10..bef9c60 100644 --- a/src/libgit2/iterator.c +++ b/src/libgit2/iterator.c @@ -26,9 +26,10 @@ #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) { + int (*vector_cmp)(const void *a, const void *b); + if (ignore_case) iter->flags |= GIT_ITERATOR_IGNORE_CASE; else @@ -39,7 +40,9 @@ static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) 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); + vector_cmp = ignore_case ? git__strcasecmp_cb : git__strcmp_cb; + + git_vector_set_cmp(&iter->pathlist, vector_cmp); } static int iterator_range_init( @@ -299,6 +302,7 @@ typedef enum { static iterator_pathlist_search_t iterator_pathlist_search( git_iterator *iter, const char *path, size_t path_len) { + int (*vector_cmp)(const void *a, const void *b); const char *p; size_t idx; int error; @@ -308,8 +312,10 @@ static iterator_pathlist_search_t iterator_pathlist_search( git_vector_sort(&iter->pathlist); - error = git_vector_bsearch2(&idx, &iter->pathlist, - (git_vector_cmp)iter->strcomp, path); + vector_cmp = (iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? + git__strcasecmp_cb : git__strcmp_cb; + + error = git_vector_bsearch2(&idx, &iter->pathlist, vector_cmp, path); /* the given path was found in the pathlist. since the pathlist only * matches directories when they're suffixed with a '/', analyze the diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c index ce28714..1b6f1a1 100644 --- a/src/libgit2/libgit2.c +++ b/src/libgit2/libgit2.c @@ -5,67 +5,32 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "libgit2.h" - #include <git2.h> #include "alloc.h" #include "buf.h" -#include "cache.h" #include "common.h" #include "filter.h" -#include "grafts.h" #include "hash.h" -#include "index.h" #include "merge_driver.h" #include "pool.h" #include "mwindow.h" -#include "object.h" -#include "odb.h" +#include "oid.h" #include "rand.h" -#include "refs.h" #include "runtime.h" +#include "settings.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 "streams/socket.h" -#include "transports/smart.h" -#include "transports/http.h" -#include "transports/ssh.h" +#include "transports/ssh_libssh2.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; -extern int git_socket_stream__connect_timeout; -extern int git_socket_stream__timeout; - -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[] = { @@ -73,31 +38,27 @@ int git_libgit2_init(void) git_win32_leakcheck_global_init, #endif git_allocator_global_init, - git_threadstate_global_init, + git_error_global_init, git_threads_global_init, + git_oid_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_transport_ssh_libssh2_global_init, git_stream_registry_global_init, git_socket_stream_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 + git_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(); @@ -126,358 +87,11 @@ int git_libgit2_features(void) #ifdef GIT_HTTPS | GIT_FEATURE_HTTPS #endif -#if defined(GIT_SSH) +#ifdef GIT_SSH | GIT_FEATURE_SSH #endif -#if defined(GIT_USE_NSEC) +#ifdef 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; - - case GIT_OPT_GET_HOMEDIR: - { - 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_HOME)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_HOMEDIR: - error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *)); - break; - - case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: - *(va_arg(ap, int *)) = git_socket_stream__connect_timeout; - break; - - case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: - { - int timeout = va_arg(ap, int); - - if (timeout < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); - error = -1; - } else { - git_socket_stream__connect_timeout = timeout; - } - } - break; - - case GIT_OPT_GET_SERVER_TIMEOUT: - *(va_arg(ap, int *)) = git_socket_stream__timeout; - break; - - case GIT_OPT_SET_SERVER_TIMEOUT: - { - int timeout = va_arg(ap, int); - - if (timeout < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid timeout"); - error = -1; - } else { - git_socket_stream__timeout = timeout; - } - } - 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 deleted file mode 100644 index a898367..0000000 --- a/src/libgit2/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/merge.c b/src/libgit2/merge.c index 0114e4b..21e5ef6 100644 --- a/src/libgit2/merge.c +++ b/src/libgit2/merge.c @@ -1225,6 +1225,13 @@ static int merge_diff_mark_similarity_exact( if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) continue; + /* + * Ignore empty files because it has always the same blob sha1 + * and will lead to incorrect matches between all entries. + */ + if (git_oid_equal(&conflict_src->ancestor_entry.id, &git_oid__empty_blob_sha1)) + 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) diff --git a/src/libgit2/midx.c b/src/libgit2/midx.c index d73a1da..71bbb1d 100644 --- a/src/libgit2/midx.c +++ b/src/libgit2/midx.c @@ -703,9 +703,9 @@ static int midx_write( hash_cb_data.ctx = &ctx; oid_size = git_oid_size(w->oid_type); - - GIT_ASSERT((checksum_type = git_oid_algorithm(w->oid_type))); + checksum_type = git_oid_algorithm(w->oid_type); checksum_size = git_hash_size(checksum_type); + GIT_ASSERT(oid_size && checksum_type && checksum_size); if ((error = git_hash_ctx_init(&ctx, checksum_type)) < 0) return error; diff --git a/src/libgit2/notes.c b/src/libgit2/notes.c index 13ca382..2dd1945 100644 --- a/src/libgit2/notes.c +++ b/src/libgit2/notes.c @@ -303,7 +303,7 @@ static int note_write( error = git_commit_create(&oid, repo, notes_ref, author, committer, NULL, GIT_NOTES_DEFAULT_MSG_ADD, - tree, *parents == NULL ? 0 : 1, (const git_commit **) parents); + tree, *parents == NULL ? 0 : 1, parents); if (notes_commit_out) git_oid_cpy(notes_commit_out, &oid); @@ -394,7 +394,7 @@ static int note_remove( NULL, GIT_NOTES_DEFAULT_MSG_RM, tree_after_removal, *parents == NULL ? 0 : 1, - (const git_commit **) parents); + parents); if (error < 0) goto cleanup; diff --git a/src/libgit2/oid.c b/src/libgit2/oid.c index 631a566..2bb7a6f 100644 --- a/src/libgit2/oid.c +++ b/src/libgit2/oid.c @@ -9,7 +9,7 @@ #include "git2/oid.h" #include "repository.h" -#include "threadstate.h" +#include "runtime.h" #include <string.h> #include <limits.h> @@ -153,15 +153,42 @@ int git_oid_pathfmt(char *str, const git_oid *oid) return 0; } +static git_tlsdata_key thread_str_key; + +static void GIT_SYSTEM_CALL thread_str_free(void *s) +{ + char *str = (char *)s; + git__free(str); +} + +static void thread_str_global_shutdown(void) +{ + char *str = git_tlsdata_get(thread_str_key); + git_tlsdata_set(thread_str_key, NULL); + + git__free(str); + git_tlsdata_dispose(thread_str_key); +} + +int git_oid_global_init(void) +{ + if (git_tlsdata_init(&thread_str_key, thread_str_free) != 0) + return -1; + + return git_runtime_shutdown_register(thread_str_global_shutdown); +} + char *git_oid_tostr_s(const git_oid *oid) { - git_threadstate *threadstate = git_threadstate_get(); char *str; - if (!threadstate) - return NULL; + if ((str = git_tlsdata_get(thread_str_key)) == NULL) { + if ((str = git__malloc(GIT_OID_MAX_HEXSIZE + 1)) == NULL) + return NULL; + + git_tlsdata_set(thread_str_key, str); + } - str = threadstate->oid_fmt; git_oid_nfmt(str, git_oid_hexsize(git_oid_type(oid)) + 1, oid); return str; } diff --git a/src/libgit2/oid.h b/src/libgit2/oid.h index 7b6b09d..f25a899 100644 --- a/src/libgit2/oid.h +++ b/src/libgit2/oid.h @@ -270,4 +270,6 @@ int git_oid__fromstrn( int git_oid__fromraw(git_oid *out, const unsigned char *raw, git_oid_t type); +int git_oid_global_init(void); + #endif diff --git a/src/libgit2/pack.c b/src/libgit2/pack.c index eff7398..1ff0eb0 100644 --- a/src/libgit2/pack.c +++ b/src/libgit2/pack.c @@ -1499,6 +1499,7 @@ static int pack_entry_find_offset( size_t len) { const uint32_t *level1_ofs; + size_t ofs_delta = 0; const unsigned char *index; unsigned hi, lo, stride; int pos, found = 0; @@ -1524,9 +1525,15 @@ static int pack_entry_find_offset( if (p->index_version > 1) { level1_ofs += 2; + ofs_delta = 2; index += 8; } + if ((size_t)short_oid->id[0] + ofs_delta >= p->index_map.len) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->short_oid->[0] out of bounds"); + goto cleanup; + } + 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])); diff --git a/src/libgit2/patch_parse.c b/src/libgit2/patch_parse.c index c069155..04f2a58 100644 --- a/src/libgit2/patch_parse.c +++ b/src/libgit2/patch_parse.c @@ -562,9 +562,9 @@ fail: 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; + if (origin == GIT_DIFF_LINE_DELETION) + return GIT_DIFF_LINE_ADD_EOFNL; return GIT_DIFF_LINE_CONTEXT_EOFNL; } diff --git a/src/libgit2/path.c b/src/libgit2/path.c index a19340e..4b584fb 100644 --- a/src/libgit2/path.c +++ b/src/libgit2/path.c @@ -202,7 +202,7 @@ GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char * { size_t count = 0; - while (len > 0 && tolower(*str) == tolower(*prefix)) { + while (len > 0 && git__tolower(*str) == git__tolower(*prefix)) { count++; str++; prefix++; diff --git a/src/libgit2/push.c b/src/libgit2/push.c index 8b47abc..e065858 100644 --- a/src/libgit2/push.c +++ b/src/libgit2/push.c @@ -68,6 +68,14 @@ int git_push_new(git_push **out, git_remote *remote, const git_push_options *opt return -1; } + if (git_vector_init(&p->remote_push_options, 0, git__strcmp_cb) < 0) { + git_vector_free(&p->status); + git_vector_free(&p->specs); + git_vector_free(&p->updates); + git__free(p); + return -1; + } + *out = p; return 0; } @@ -444,10 +452,21 @@ static int do_push(git_push *push) 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 (callbacks && callbacks->push_negotiation) { + git_error_clear(); + + error = callbacks->push_negotiation( + (const git_push_update **) push->updates.contents, + push->updates.length, callbacks->payload); + + if (error < 0) { + git_error_set_after_callback_function(error, + "push_negotiation"); + goto on_error; + } + + error = 0; + } if ((error = queue_objects(push)) < 0 || (error = transport->push(transport, push)) < 0) @@ -479,12 +498,24 @@ static int filter_refs(git_remote *remote) int git_push_finish(git_push *push) { int error; + unsigned int remote_caps; if (!git_remote_connected(push->remote)) { git_error_set(GIT_ERROR_NET, "remote is disconnected"); return -1; } + if ((error = git_remote_capabilities(&remote_caps, push->remote)) < 0) { + git_error_set(GIT_ERROR_INVALID, "remote capabilities not available"); + return -1; + } + + if (git_vector_length(&push->remote_push_options) > 0 && + !(remote_caps & GIT_REMOTE_CAPABILITY_PUSH_OPTIONS)) { + git_error_set(GIT_ERROR_INVALID, "push-options not supported by remote"); + return -1; + } + if ((error = filter_refs(push->remote)) < 0 || (error = do_push(push)) < 0) return error; @@ -528,6 +559,7 @@ void git_push_free(git_push *push) push_spec *spec; push_status *status; git_push_update *update; + char *option; unsigned int i; if (push == NULL) @@ -550,6 +582,11 @@ void git_push_free(git_push *push) } git_vector_free(&push->updates); + git_vector_foreach(&push->remote_push_options, i, option) { + git__free(option); + } + git_vector_free(&push->remote_push_options); + git__free(push); } diff --git a/src/libgit2/push.h b/src/libgit2/push.h index fc72e84..40a1823 100644 --- a/src/libgit2/push.h +++ b/src/libgit2/push.h @@ -34,6 +34,7 @@ struct git_push { git_vector specs; git_vector updates; bool report_status; + git_vector remote_push_options; /* report-status */ bool unpack_ok; diff --git a/src/libgit2/rebase.c b/src/libgit2/rebase.c index 77e442e..2fce3e7 100644 --- a/src/libgit2/rebase.c +++ b/src/libgit2/rebase.c @@ -952,7 +952,7 @@ static int create_signed( const char *message, git_tree *tree, size_t parent_count, - const git_commit **parents) + git_commit * const *parents) { git_str commit_content = GIT_STR_INIT; git_buf commit_signature = { NULL, 0, 0 }, @@ -1040,8 +1040,7 @@ static int rebase_commit__create( 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); + tree, 1, &parent_commit, rebase->options.payload); git_error_set_after_callback_function(error, "commit_create_cb"); @@ -1050,14 +1049,14 @@ static int rebase_commit__create( else if (rebase->options.signing_cb) { error = create_signed(&commit_id, rebase, author, committer, message_encoding, message, tree, - 1, (const git_commit **)&parent_commit); + 1, &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); + tree, 1, &parent_commit); if (error) goto done; diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c index e34a714..9a5c38e 100644 --- a/src/libgit2/refdb_fs.c +++ b/src/libgit2/refdb_fs.c @@ -62,8 +62,8 @@ typedef struct refdb_fs_backend { git_oid_t oid_type; - int fsync : 1, - sorted : 1; + unsigned int fsync : 1, + sorted : 1; int peeling_mode; git_iterator_flag_t iterator_flags; uint32_t direach_flags; @@ -410,7 +410,9 @@ static const char *loose_parse_symbolic(git_str *file_content) static bool is_per_worktree_ref(const char *ref_name) { return git__prefixcmp(ref_name, "refs/") != 0 || - git__prefixcmp(ref_name, "refs/bisect/") == 0; + git__prefixcmp(ref_name, "refs/bisect/") == 0 || + git__prefixcmp(ref_name, "refs/worktree/") == 0 || + git__prefixcmp(ref_name, "refs/rewritten/") == 0; } static int loose_lookup( @@ -805,83 +807,149 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) git__free(iter); } -static int iter_load_loose_paths( - refdb_fs_backend *backend, - refdb_fs_iter *iter) +struct iter_load_context { + refdb_fs_backend *backend; + refdb_fs_iter *iter; + + /* + * If we have a glob with a prefix (eg `refs/heads/ *`) then we can + * optimize our prefix to avoid walking refs that we know won't + * match. This is that prefix. + */ + const char *ref_prefix; + size_t ref_prefix_len; + + /* Temporary variables to avoid unnecessary allocations */ + git_str ref_name; + git_str path; +}; + +static void iter_load_optimize_prefix(struct iter_load_context *ctx) { - 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); + const char *pos, *last_sep = NULL; - if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ - return 0; + if (!ctx->iter->glob) + return; - fsit_opts.flags = backend->iterator_flags; - fsit_opts.oid_type = backend->oid_type; - - 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; - } + for (pos = ctx->iter->glob; *pos; pos++) { + switch (*pos) { + case '?': + case '*': + case '[': + case '\\': break; + case '/': + last_sep = pos; + /* FALLTHROUGH */ + default: + continue; } - if (last_sep) { - ref_prefix = iter->glob; - ref_prefix_len = (last_sep - ref_prefix) + 1; - } + break; } - 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 (last_sep) { + ctx->ref_prefix = ctx->iter->glob; + ctx->ref_prefix_len = (last_sep - ctx->ref_prefix) + 1; } +} + +static int iter_load_paths( + struct iter_load_context *ctx, + const char *root_path, + bool worktree) +{ + git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error = 0; + + fsit_opts.flags = ctx->backend->iterator_flags; + + git_str_clear(&ctx->path); + git_str_puts(&ctx->path, root_path); + git_str_put(&ctx->path, ctx->ref_prefix, ctx->ref_prefix_len); + + fsit_opts.flags = ctx->backend->iterator_flags; + fsit_opts.oid_type = ctx->backend->oid_type; + + if ((error = git_iterator_for_filesystem(&fsit, ctx->path.ptr, &fsit_opts)) < 0) { + /* + * Subdirectories - either glob provided or per-worktree refs - need + * not exist. + */ + if ((worktree || ctx->iter->glob) && error == GIT_ENOTFOUND) + error = 0; - if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { - git_str_dispose(&path); - return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; + goto done; } - error = git_str_sets(&path, ref_prefix); + git_str_clear(&ctx->ref_name); + git_str_put(&ctx->ref_name, ctx->ref_prefix, ctx->ref_prefix_len); - while (!error && !git_iterator_advance(&entry, fsit)) { - const char *ref_name; + while (git_iterator_advance(&entry, fsit) == 0) { char *ref_dup; - git_str_truncate(&path, ref_prefix_len); - git_str_puts(&path, entry->path); - ref_name = git_str_cstr(&path); + git_str_truncate(&ctx->ref_name, ctx->ref_prefix_len); + git_str_puts(&ctx->ref_name, entry->path); - if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) + if (worktree) { + if (!is_per_worktree_ref(ctx->ref_name.ptr)) + continue; + } else { + if (git_repository_is_worktree(ctx->backend->repo) && + is_per_worktree_ref(ctx->ref_name.ptr)) + continue; + } + + if (git__suffixcmp(ctx->ref_name.ptr, ".lock") == 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); + if (ctx->iter->glob && wildmatch(ctx->iter->glob, ctx->ref_name.ptr, 0)) + continue; + + ref_dup = git_pool_strdup(&ctx->iter->pool, ctx->ref_name.ptr); + GIT_ERROR_CHECK_ALLOC(ref_dup); + + if ((error = git_vector_insert(&ctx->iter->loose, ref_dup)) < 0) + goto done; } +done: git_iterator_free(fsit); - git_str_dispose(&path); + return error; +} +#define iter_load_context_init(b, i) { b, i, GIT_REFS_DIR, CONST_STRLEN(GIT_REFS_DIR) } +#define iter_load_context_dispose(ctx) do { \ + git_str_dispose(&((ctx)->path)); \ + git_str_dispose(&((ctx)->ref_name)); \ +} while(0) + +static int iter_load_loose_paths( + refdb_fs_backend *backend, + refdb_fs_iter *iter) +{ + struct iter_load_context ctx = iter_load_context_init(backend, iter); + + int error = 0; + + if (!backend->commonpath) + return 0; + + iter_load_optimize_prefix(&ctx); + + if ((error = iter_load_paths(&ctx, + backend->commonpath, false)) < 0) + goto done; + + if (git_repository_is_worktree(backend->repo)) { + if ((error = iter_load_paths(&ctx, + backend->gitpath, true)) < 0) + goto done; + } + +done: + iter_load_context_dispose(&ctx); return error; } diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c index 5e2fe97..c1ed04d 100644 --- a/src/libgit2/refs.c +++ b/src/libgit2/refs.c @@ -1080,6 +1080,12 @@ int git_reference_cmp( return git_oid__cmp(&ref1->target.oid, &ref2->target.oid); } +int git_reference__cmp_cb(const void *a, const void *b) +{ + return git_reference_cmp( + (const git_reference *)a, (const git_reference *)b); +} + /* * Starting with the reference given by `ref_name`, follows symbolic * references until a direct reference is found and updated the OID diff --git a/src/libgit2/refs.h b/src/libgit2/refs.h index cb888bf..588af82 100644 --- a/src/libgit2/refs.h +++ b/src/libgit2/refs.h @@ -92,6 +92,12 @@ 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); +/* + * A `git_reference_cmp` wrapper suitable for passing to generic + * comparators, like `vector_cmp` / `tsort` / etc. + */ +int git_reference__cmp_cb(const void *a, const void *b); + /** * Lookup a reference by name and try to resolve to an OID. * diff --git a/src/libgit2/remote.c b/src/libgit2/remote.c index fee2a7f..8b486ea 100644 --- a/src/libgit2/remote.c +++ b/src/libgit2/remote.c @@ -1339,7 +1339,11 @@ int git_remote_download( if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) return error; - return git_remote__download(remote, refspecs, opts); + error = git_remote__download(remote, refspecs, opts); + + git_remote_connect_options_dispose(&connect_opts); + + return error; } int git_remote_fetch( @@ -1348,13 +1352,14 @@ int git_remote_fetch( 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; unsigned int capabilities; git_oid_t oid_type; + unsigned int update_flags = GIT_REMOTE_UPDATE_FETCHHEAD; + int error; GIT_ASSERT_ARG(remote); @@ -1371,7 +1376,7 @@ int git_remote_fetch( return error; if (opts) { - update_fetchhead = opts->update_fetchhead; + update_flags = opts->update_fetchhead; tagopt = opts->download_tags; } @@ -1398,8 +1403,14 @@ int git_remote_fetch( } /* 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)); + error = git_remote_update_tips(remote, + &connect_opts.callbacks, + update_flags, + tagopt, + git_str_cstr(&reflog_msg_buf)); + git_str_dispose(&reflog_msg_buf); + if (error < 0) goto done; @@ -1774,6 +1785,7 @@ static int update_one_tip( git_refspec *spec, git_remote_head *head, git_refspec *tagspec, + unsigned int update_flags, git_remote_autotag_option_t tagopt, const char *log_message, const git_remote_callbacks *callbacks) @@ -1781,7 +1793,7 @@ static int update_one_tip( git_odb *odb; git_str refname = GIT_STR_INIT; git_reference *ref = NULL; - bool autotag = false; + bool autotag = false, updated = false; git_oid old; int valid; int error; @@ -1855,21 +1867,21 @@ static int update_one_tip( goto done; } - if (!git_oid__cmp(&old, &head->oid)) - goto done; + if ((updated = !git_oid_equal(&old, &head->oid))) { + /* In autotag mode, don't overwrite any locally-existing tags */ + error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, + log_message); - /* 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; + if (error < 0) { + if (error == GIT_EEXISTS) + error = 0; - goto done; + goto done; + } } if (callbacks && callbacks->update_tips != NULL && + (updated || (update_flags & GIT_REMOTE_UPDATE_REPORT_UNCHANGED)) && (error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0) git_error_set_after_callback_function(error, "git_remote_fetch"); @@ -1882,7 +1894,7 @@ done: static int update_tips_for_spec( git_remote *remote, const git_remote_callbacks *callbacks, - int update_fetchhead, + unsigned int update_flags, git_remote_autotag_option_t tagopt, git_refspec *spec, git_vector *refs, @@ -1905,7 +1917,10 @@ static int update_tips_for_spec( /* 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) + if (update_one_tip(&update_heads, + remote, spec, head, &tagspec, + update_flags, tagopt, log_message, + callbacks) < 0) goto on_error; } @@ -1927,7 +1942,7 @@ static int update_tips_for_spec( goto on_error; } - if (update_fetchhead && + if ((update_flags & GIT_REMOTE_UPDATE_FETCHHEAD) && (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) goto on_error; @@ -2058,11 +2073,11 @@ static int truncate_fetch_head(const char *gitdir) } 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_remote *remote, + const git_remote_callbacks *callbacks, + unsigned int update_flags, + git_remote_autotag_option_t download_tags, + const char *reflog_message) { git_refspec *spec, tagspec; git_vector refs = GIT_VECTOR_INIT; @@ -2091,7 +2106,7 @@ int git_remote_update_tips( 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) + if ((error = update_tips_for_spec(remote, callbacks, update_flags, tagopt, &tagspec, &refs, reflog_message)) < 0) goto out; } @@ -2099,7 +2114,7 @@ int git_remote_update_tips( if (spec->push) continue; - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0) + if ((error = update_tips_for_spec(remote, callbacks, update_flags, tagopt, spec, &refs, reflog_message)) < 0) goto out; } @@ -2967,6 +2982,15 @@ int git_remote_upload( } } + if (opts && opts->remote_push_options.count > 0) + for (i = 0; i < opts->remote_push_options.count; ++i) { + char *optstr = git__strdup(opts->remote_push_options.strings[i]); + GIT_ERROR_CHECK_ALLOC(optstr); + + if ((error = git_vector_insert(&push->remote_push_options, optstr)) < 0) + goto cleanup; + } + if ((error = git_push_finish(push)) < 0) goto cleanup; diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 05ece6e..8e449a6 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -62,7 +62,8 @@ static const struct { { 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 } + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", false } }; static int check_repositoryformatversion(int *version, git_config *config); @@ -328,7 +329,7 @@ on_error: return NULL; } -int git_repository_new(git_repository **out) +int git_repository__new(git_repository **out, git_oid_t oid_type) { git_repository *repo; @@ -337,10 +338,23 @@ int git_repository_new(git_repository **out) repo->is_bare = 1; repo->is_worktree = 0; + repo->oid_type = oid_type; return 0; } +#ifdef GIT_EXPERIMENTAL_SHA256 +int git_repository_new(git_repository **out, git_oid_t oid_type) +{ + return git_repository__new(out, oid_type); +} +#else +int git_repository_new(git_repository** out) +{ + return git_repository__new(out, GIT_OID_SHA1); +} +#endif + static int load_config_data(git_repository *repo, const git_config *config) { int is_bare; @@ -545,25 +559,39 @@ typedef struct { static int validate_ownership_cb(const git_config_entry *entry, void *payload) { validate_ownership_data *data = payload; + const char *test_path; if (strcmp(entry->value, "") == 0) { *data->is_safe = false; } else if (strcmp(entry->value, "*") == 0) { *data->is_safe = true; } else { - const char *test_path = entry->value; + if (git_str_sets(&data->tmp, entry->value) < 0) + return -1; + + if (!git_fs_path_is_root(data->tmp.ptr)) { + /* Input must not have trailing backslash. */ + if (!data->tmp.size || + data->tmp.ptr[data->tmp.size - 1] == '/') + return 0; + + if (git_fs_path_to_dir(&data->tmp) < 0) + return -1; + } + + test_path = data->tmp.ptr; -#ifdef GIT_WIN32 /* - * Git for Windows does some truly bizarre things with - * paths that start with a forward slash; and expects you - * to escape that with `%(prefix)`. This syntax generally - * means to add the prefix that Git was installed to -- eg - * `/usr/local` -- unless it's an absolute path, in which - * case the leading `%(prefix)/` is just removed. And Git - * for Windows expects you to use this syntax for absolute - * Unix-style paths (in "Git Bash" or Windows Subsystem for - * Linux). + * Git - and especially, Git for Windows - does some + * truly bizarre things with paths that start with a + * forward slash; and expects you to escape that with + * `%(prefix)`. This syntax generally means to add the + * prefix that Git was installed to (eg `/usr/local`) + * unless it's an absolute path, in which case the + * leading `%(prefix)/` is just removed. And Git for + * Windows expects you to use this syntax for absolute + * Unix-style paths (in "Git Bash" or Windows Subsystem + * for Linux). * * Worse, the behavior used to be that a leading `/` was * not absolute. It would indicate that Git for Windows @@ -578,13 +606,8 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) */ if (strncmp(test_path, "%(prefix)//", strlen("%(prefix)//")) == 0) test_path += strlen("%(prefix)/"); - else if (strncmp(test_path, "//", 2) == 0 && - strncmp(test_path, "//wsl.localhost/", strlen("//wsl.localhost/")) != 0) - test_path++; -#endif - if (git_fs_path_prettify_dir(&data->tmp, test_path, NULL) == 0 && - strcmp(data->tmp.ptr, data->repo_path) == 0) + if (strcmp(test_path, data->repo_path) == 0) *data->is_safe = true; } @@ -681,9 +704,12 @@ static int validate_ownership(git_repository *repo) goto done; if (!is_safe) { + size_t path_len = git_fs_path_is_root(path) ? + strlen(path) : git_fs_path_dirlen(path); + git_error_set(GIT_ERROR_CONFIG, - "repository path '%s' is not owned by current user", - path); + "repository path '%.*s' is not owned by current user", + (int)min(path_len, INT_MAX), path); error = GIT_EOWNER; } @@ -852,8 +878,30 @@ static int load_grafts(git_repository *repo) git_str path = GIT_STR_INIT; int error; - if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = git_str_joinpath(&path, path.ptr, "grafts")) < 0 || + /* refresh if they've both been opened previously */ + if (repo->grafts && repo->shallow_grafts) { + if ((error = git_grafts_refresh(repo->grafts)) < 0 || + (error = git_grafts_refresh(repo->shallow_grafts)) < 0) + return error; + } + + /* resolve info path, which may not be found for inmemory repository */ + if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0) { + if (error != GIT_ENOTFOUND) + return error; + + /* create empty/inmemory grafts for inmemory repository */ + if (!repo->grafts && (error = git_grafts_new(&repo->grafts, repo->oid_type)) < 0) + return error; + + if (!repo->shallow_grafts && (error = git_grafts_new(&repo->shallow_grafts, repo->oid_type)) < 0) + return error; + + return 0; + } + + /* load grafts from disk */ + if ((error = git_str_joinpath(&path, path.ptr, "grafts")) < 0 || (error = git_grafts_open_or_refresh(&repo->grafts, path.ptr, repo->oid_type)) < 0) goto error; @@ -1226,6 +1274,24 @@ int git_repository_discover( return error; } +static int has_config_worktree(bool *out, git_config *cfg) +{ + int worktreeconfig = 0, error; + + *out = false; + + error = git_config_get_bool(&worktreeconfig, cfg, "extensions.worktreeconfig"); + + if (error == 0) + *out = worktreeconfig; + else if (error == GIT_ENOTFOUND) + *out = false; + else + return error; + + return 0; +} + static int load_config( git_config **out, git_repository *repo, @@ -1234,9 +1300,11 @@ static int load_config( const char *system_config_path, const char *programdata_path) { - int error; git_str config_path = GIT_STR_INIT; git_config *cfg = NULL; + git_config_level_t write_order; + bool has_worktree; + int error; GIT_ASSERT_ARG(out); @@ -1250,6 +1318,14 @@ static int load_config( if (error && error != GIT_ENOTFOUND) goto on_error; + if ((error = has_config_worktree(&has_worktree, cfg)) == 0 && + has_worktree && + (error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0); + + if (error && error != GIT_ENOTFOUND) + goto on_error; + git_str_dispose(&config_path); } @@ -1279,6 +1355,11 @@ static int load_config( git_error_clear(); /* clear any lingering ENOTFOUND errors */ + write_order = GIT_CONFIG_LEVEL_LOCAL; + + if ((error = git_config_set_writeorder(cfg, &write_order, 1)) < 0) + goto on_error; + *out = cfg; return 0; @@ -1798,7 +1879,8 @@ static int check_repositoryformatversion(int *version, git_config *config) static const char *builtin_extensions[] = { "noop", - "objectformat" + "objectformat", + "worktreeconfig", }; static git_vector user_extensions = { 0, git__strcmp_cb }; @@ -2630,6 +2712,8 @@ static int repo_init_directories( if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) return -1; + git_fs_path_mkposix(repo_path->ptr); + has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); if (has_dotgit) opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; @@ -3197,14 +3281,18 @@ int git_repository_set_workdir( if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) return -1; - if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) { + git_str_dispose(&path); return 0; + } if (update_gitlink) { git_config *config; - if (git_repository_config__weakptr(&config, repo) < 0) + if (git_repository_config__weakptr(&config, repo) < 0) { + git_str_dispose(&path); return -1; + } error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); @@ -3226,6 +3314,7 @@ int git_repository_set_workdir( git__free(old_workdir); } + git_str_dispose(&path); return error; } @@ -3268,6 +3357,25 @@ int git_repository_set_bare(git_repository *repo) return 0; } +int git_repository_head_commit(git_commit **commit, 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_COMMIT)) < 0) + goto cleanup; + + *commit = (git_commit *)obj; + +cleanup: + git_reference_free(head); + return error; +} + int git_repository_head_tree(git_tree **tree, git_repository *repo) { git_reference *head; @@ -3839,3 +3947,65 @@ git_oid_t git_repository_oid_type(git_repository *repo) { return repo ? repo->oid_type : 0; } + +struct mergehead_data { + git_repository *repo; + git_vector *parents; +}; + +static int insert_mergehead(const git_oid *oid, void *payload) +{ + git_commit *commit; + struct mergehead_data *data = (struct mergehead_data *)payload; + + if (git_commit_lookup(&commit, data->repo, oid) < 0) + return -1; + + return git_vector_insert(data->parents, commit); +} + +int git_repository_commit_parents(git_commitarray *out, git_repository *repo) +{ + git_commit *first_parent = NULL, *commit; + git_reference *head_ref = NULL; + git_vector parents = GIT_VECTOR_INIT; + struct mergehead_data data; + size_t i; + int error; + + GIT_ASSERT_ARG(out && repo); + + out->count = 0; + out->commits = NULL; + + error = git_revparse_ext((git_object **)&first_parent, &head_ref, repo, "HEAD"); + + if (error != 0) { + if (error == GIT_ENOTFOUND) + error = 0; + + goto done; + } + + if ((error = git_vector_insert(&parents, first_parent)) < 0) + goto done; + + data.repo = repo; + data.parents = &parents; + + error = git_repository_mergehead_foreach(repo, insert_mergehead, &data); + + if (error == GIT_ENOTFOUND) + error = 0; + else if (error != 0) + goto done; + + out->commits = (git_commit **)git_vector_detach(&out->count, NULL, &parents); + +done: + git_vector_foreach(&parents, i, commit) + git__free(commit); + + git_reference_free(head_ref); + return error; +} diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h index 6d2b64c..f45a359 100644 --- a/src/libgit2/repository.h +++ b/src/libgit2/repository.h @@ -173,6 +173,7 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) return repo->attrcache; } +int git_repository_head_commit(git_commit **commit, git_repository *repo); int git_repository_head_tree(git_tree **tree, git_repository *repo); int git_repository_create_head(const char *git_dir, const char *ref_name); @@ -280,4 +281,7 @@ int git_repository__set_objectformat( git_repository *repo, git_oid_t oid_type); +/* SHA256-aware internal functions */ +int git_repository__new(git_repository **out, git_oid_t oid_type); + #endif diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c index 06d92f8..9083e7a 100644 --- a/src/libgit2/revparse.c +++ b/src/libgit2/revparse.c @@ -816,7 +816,7 @@ static int revparse( if (temp_object != NULL) base_rev = temp_object; break; - } else if (spec[pos+1] == '\0') { + } else if (spec[pos + 1] == '\0' && !pos) { spec = "HEAD"; identifier_len = 4; parsed = true; @@ -935,7 +935,7 @@ int git_revparse( * allowed. */ if (!git__strcmp(spec, "..")) { - git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); + git_error_set(GIT_ERROR_INVALID, "invalid pattern '..'"); return GIT_EINVALIDSPEC; } diff --git a/src/libgit2/settings.c b/src/libgit2/settings.c new file mode 100644 index 0000000..4a41830 --- /dev/null +++ b/src/libgit2/settings.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "settings.h" + +#include <git2.h> +#include "alloc.h" +#include "buf.h" +#include "cache.h" +#include "common.h" +#include "filter.h" +#include "grafts.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 "git2/global.h" +#include "streams/registry.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/socket.h" +#include "transports/smart.h" +#include "transports/http.h" +#include "transports/ssh_libssh2.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; +extern int git_socket_stream__connect_timeout; +extern int git_socket_stream__timeout; + +char *git__user_agent; +char *git__user_agent_product; +char *git__ssl_ciphers; + +static void settings_global_shutdown(void) +{ + git__free(git__user_agent); + git__free(git__user_agent_product); + + git__free(git__ssl_ciphers); + git_repository__free_extensions(); +} + +int git_settings_global_init(void) +{ + return git_runtime_shutdown_register(settings_global_shutdown); +} + +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_settings__user_agent_product(void) +{ + return git__user_agent_product ? git__user_agent_product : + "git/2.0"; +} + +const char *git_settings__user_agent(void) +{ + return git__user_agent ? git__user_agent : + "libgit2 " LIBGIT2_VERSION; +} + +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: + { + const char *new_agent = va_arg(ap, const char *); + + git__free(git__user_agent); + + if (new_agent) { + git__user_agent= git__strdup(new_agent); + + if (!git__user_agent) + error = -1; + } else { + git__user_agent = NULL; + } + } + 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_settings__user_agent())) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_USER_AGENT_PRODUCT: + { + const char *new_agent = va_arg(ap, const char *); + + git__free(git__user_agent_product); + + if (new_agent) { + git__user_agent_product = git__strdup(new_agent); + + if (!git__user_agent_product) + error = -1; + } else { + git__user_agent_product = NULL; + } + } + break; + + case GIT_OPT_GET_USER_AGENT_PRODUCT: + { + 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_settings__user_agent_product())) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + 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_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; + + case GIT_OPT_GET_HOMEDIR: + { + 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_HOME)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_HOMEDIR: + error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *)); + break; + + case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: + *(va_arg(ap, int *)) = git_socket_stream__connect_timeout; + break; + + case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: + { + int timeout = va_arg(ap, int); + + if (timeout < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); + error = -1; + } else { + git_socket_stream__connect_timeout = timeout; + } + } + break; + + case GIT_OPT_GET_SERVER_TIMEOUT: + *(va_arg(ap, int *)) = git_socket_stream__timeout; + break; + + case GIT_OPT_SET_SERVER_TIMEOUT: + { + int timeout = va_arg(ap, int); + + if (timeout < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid timeout"); + error = -1; + } else { + git_socket_stream__timeout = timeout; + } + } + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid option key"); + error = -1; + } + + va_end(ap); + + return error; +} diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h index dc42ce9..2929366 100644 --- a/src/libgit2/settings.h +++ b/src/libgit2/settings.h @@ -4,8 +4,12 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ +#ifndef INCLUDE_settings_h__ +#define INCLUDE_settings_h__ extern int git_settings_global_init(void); -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); +extern const char *git_settings__user_agent(void); +extern const char *git_settings__user_agent_product(void); + +#endif diff --git a/src/libgit2/signature.c b/src/libgit2/signature.c index 5d6ab57..12d2b5f 100644 --- a/src/libgit2/signature.c +++ b/src/libgit2/signature.c @@ -43,7 +43,6 @@ static bool contains_angle_brackets(const char *input) static bool is_crud(unsigned char c) { return c <= 32 || - c == '.' || c == ',' || c == ':' || c == ';' || diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c index b49e95c..a0a72de 100644 --- a/src/libgit2/stash.c +++ b/src/libgit2/stash.c @@ -124,7 +124,7 @@ static int commit_index( git_index *index, const git_signature *stasher, const char *message, - const git_commit *parent) + git_commit *parent) { git_tree *i_tree = NULL; git_oid i_commit_oid; @@ -423,7 +423,7 @@ static int build_stash_commit_from_tree( git_commit *u_commit, const git_tree *tree) { - const git_commit *parents[] = { NULL, NULL, NULL }; + git_commit *parents[] = { NULL, NULL, NULL }; parents[0] = b_commit; parents[1] = i_commit; diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c index 49aa76c..1b27807 100644 --- a/src/libgit2/streams/mbedtls.c +++ b/src/libgit2/streams/mbedtls.c @@ -32,7 +32,6 @@ # endif #endif -#include <mbedtls/config.h> #include <mbedtls/ssl.h> #include <mbedtls/error.h> #include <mbedtls/entropy.h> @@ -43,9 +42,15 @@ #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; + +static bool initialized = false; +static mbedtls_ssl_config mbedtls_config; +static mbedtls_ctr_drbg_context mbedtls_rng; +static mbedtls_entropy_context mbedtls_entropy; + +static bool has_ca_chain = false; +static mbedtls_x509_crt mbedtls_ca_chain; /** * This function aims to clean-up the SSL context which @@ -53,19 +58,16 @@ static mbedtls_entropy_context *mbedtls_entropy; */ 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 (has_ca_chain) { + mbedtls_x509_crt_free(&mbedtls_ca_chain); + has_ca_chain = false; } - if (mbedtls_entropy) { - mbedtls_entropy_free(mbedtls_entropy); - git__free(mbedtls_entropy); - mbedtls_entropy = NULL; + + if (initialized) { + mbedtls_ctr_drbg_free(&mbedtls_rng); + mbedtls_ssl_config_free(&mbedtls_config); + mbedtls_entropy_free(&mbedtls_entropy); + initialized = false; } } @@ -74,32 +76,33 @@ 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(&mbedtls_config); + mbedtls_entropy_init(&mbedtls_entropy); + mbedtls_ctr_drbg_init(&mbedtls_rng); - 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) { + if (mbedtls_ssl_config_defaults(&mbedtls_config, + 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); + /* configure TLSv1.1 */ +#ifdef MBEDTLS_SSL_MINOR_VERSION_2 + mbedtls_ssl_conf_min_version(&mbedtls_config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_2); +#endif /* 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); + mbedtls_ssl_conf_authmode(&mbedtls_config, MBEDTLS_SSL_VERIFY_OPTIONAL); /* set the list of allowed ciphersuites */ ciphers_known = 0; @@ -123,42 +126,33 @@ int git_mbedtls_stream_global_init(void) git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); goto cleanup; } - mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); + mbedtls_ssl_conf_ciphersuites(&mbedtls_config, 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) { + if (mbedtls_ctr_drbg_seed(&mbedtls_rng, 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); + mbedtls_ssl_conf_rng(&mbedtls_config, mbedtls_ctr_drbg_random, &mbedtls_rng); /* 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); + initialized = true; + 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; + mbedtls_ctr_drbg_free(&mbedtls_rng); + mbedtls_ssl_config_free(&mbedtls_config); + mbedtls_entropy_free(&mbedtls_entropy); return -1; } @@ -192,7 +186,7 @@ static int ssl_set_error(mbedtls_ssl_context *ssl, int 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); + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, mbedtls_ssl_get_verify_result(ssl), errbuf); ret = GIT_ECERTIFICATE; break; @@ -374,7 +368,7 @@ static int mbedtls_stream_wrap( 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)) { + if (mbedtls_ssl_setup(st->ssl, &mbedtls_config)) { git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); error = -1; goto out_err; @@ -441,30 +435,30 @@ 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); + if (has_ca_chain) + mbedtls_x509_crt_free(&mbedtls_ca_chain); + + mbedtls_x509_crt_init(&mbedtls_ca_chain); - mbedtls_x509_crt_init(cacert); if (file) - ret = mbedtls_x509_crt_parse_file(cacert, file); + ret = mbedtls_x509_crt_parse_file(&mbedtls_ca_chain, file); + if (ret >= 0 && path) - ret = mbedtls_x509_crt_parse_path(cacert, path); + ret = mbedtls_x509_crt_parse_path(&mbedtls_ca_chain, 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_x509_crt_free(&mbedtls_ca_chain); 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); + mbedtls_ssl_conf_ca_chain(&mbedtls_config, &mbedtls_ca_chain, NULL); + has_ca_chain = true; return 0; } diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c index 9db911e..7cb8f7f 100644 --- a/src/libgit2/streams/openssl.c +++ b/src/libgit2/streams/openssl.c @@ -36,6 +36,8 @@ # include <openssl/bio.h> #endif +extern char *git__ssl_ciphers; + 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" @@ -105,7 +107,7 @@ static void git_openssl_free(void *mem) static int openssl_init(void) { long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - const char *ciphers = git_libgit2__ssl_ciphers(); + const char *ciphers = git__ssl_ciphers; #ifdef VALGRIND static bool allocators_initialized = false; #endif diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c index 95ea84f..830d41c 100644 --- a/src/libgit2/submodule.c +++ b/src/libgit2/submodule.c @@ -196,7 +196,7 @@ static void free_submodule_names(git_strmap *names) */ static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) { - const char *key = "submodule\\..*\\.path"; + const char *key = "^submodule\\..*\\.path$"; git_config_iterator *iter = NULL; git_config_entry *entry; git_str buf = GIT_STR_INIT; @@ -332,7 +332,7 @@ int git_submodule__lookup_with_cache( /* 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"; + const char *pattern = "^submodule\\..*\\.path$"; git_str path = GIT_STR_INIT; fbp_data data = { NULL, NULL }; diff --git a/src/libgit2/threadstate.c b/src/libgit2/threadstate.c deleted file mode 100644 index ed9bb9b..0000000 --- a/src/libgit2/threadstate.c +++ /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. - */ - -#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; - - /* - * Avoid git__malloc here, since if it fails, it sets an error - * message, which requires thread state, which would allocate - * here, which would fail, which would set an error message... - */ - - if ((threadstate = git__allocator.gmalloc(sizeof(git_threadstate), - __FILE__, __LINE__)) == NULL) - return NULL; - - memset(threadstate, 0, sizeof(git_threadstate)); - - if (git_str_init(&threadstate->error_buf, 0) < 0) { - git__allocator.gfree(threadstate); - return NULL; - } - - git_tlsdata_set(tls_key, threadstate); - return threadstate; -} diff --git a/src/libgit2/threadstate.h b/src/libgit2/threadstate.h deleted file mode 100644 index 6ef0419..0000000 --- a/src/libgit2/threadstate.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_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_MAX_HEXSIZE+1]; -} git_threadstate; - -extern int git_threadstate_global_init(void); -extern git_threadstate *git_threadstate_get(void); - -#endif diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c index 4761c99..c7579fb 100644 --- a/src/libgit2/trailer.c +++ b/src/libgit2/trailer.c @@ -24,7 +24,7 @@ static const char *const git_generated_prefixes[] = { static int is_blank_line(const char *str) { const char *s = str; - while (*s && *s != '\n' && isspace(*s)) + while (*s && *s != '\n' && git__isspace(*s)) s++; return !*s || *s == '\n'; } @@ -93,7 +93,7 @@ static bool find_separator(size_t *out, const char *line, const char *separators return true; } - if (!whitespace_found && (isalnum(*c) || *c == '-')) + if (!whitespace_found && (git__isalnum(*c) || *c == '-')) continue; if (c != line && (*c == ' ' || *c == '\t')) { whitespace_found = 1; @@ -158,7 +158,7 @@ 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) + if (git__prefixcmp(s, "---") == 0 && git__isspace(s[3])) return s - str; } @@ -233,12 +233,12 @@ static size_t find_trailer_start(const char *buf, size_t len) } find_separator(&separator_pos, bol, TRAILER_SEPARATORS); - if (separator_pos >= 1 && !isspace(bol[0])) { + if (separator_pos >= 1 && !git__isspace(bol[0])) { trailer_lines++; possible_continuation_lines = 0; if (recognized_prefix) continue; - } else if (isspace(bol[0])) + } else if (git__isspace(bol[0])) possible_continuation_lines++; else { non_trailer_lines++; @@ -323,7 +323,7 @@ int git_message_trailers(git_message_trailer_array *trailer_arr, const char *mes goto ret; } - if (isalnum(*ptr) || *ptr == '-') { + if (git__isalnum(*ptr) || *ptr == '-') { /* legal key character */ NEXT(S_KEY); } diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c index ccffa99..9634161 100644 --- a/src/libgit2/transaction.c +++ b/src/libgit2/transaction.c @@ -49,12 +49,16 @@ struct git_transaction { git_repository *repo; git_refdb *db; git_config *cfg; + void *cfg_data; git_strmap *locks; git_pool pool; }; -int git_transaction_config_new(git_transaction **out, git_config *cfg) +int git_transaction_config_new( + git_transaction **out, + git_config *cfg, + void *data) { git_transaction *tx; @@ -66,6 +70,8 @@ int git_transaction_config_new(git_transaction **out, git_config *cfg) tx->type = TRANSACTION_CONFIG; tx->cfg = cfg; + tx->cfg_data = data; + *out = tx; return 0; } @@ -333,8 +339,9 @@ int git_transaction_commit(git_transaction *tx) GIT_ASSERT_ARG(tx); if (tx->type == TRANSACTION_CONFIG) { - error = git_config_unlock(tx->cfg, true); + error = git_config_unlock(tx->cfg, tx->cfg_data, true); tx->cfg = NULL; + tx->cfg_data = NULL; return error; } @@ -369,10 +376,8 @@ void git_transaction_free(git_transaction *tx) return; if (tx->type == TRANSACTION_CONFIG) { - if (tx->cfg) { - git_config_unlock(tx->cfg, false); - git_config_free(tx->cfg); - } + if (tx->cfg) + git_config_unlock(tx->cfg, tx->cfg_data, false); git__free(tx); return; diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h index 780c068..cb26017 100644 --- a/src/libgit2/transaction.h +++ b/src/libgit2/transaction.h @@ -9,6 +9,9 @@ #include "common.h" -int git_transaction_config_new(git_transaction **out, git_config *cfg); +int git_transaction_config_new( + git_transaction **out, + git_config *cfg, + void *data); #endif diff --git a/src/libgit2/transport.c b/src/libgit2/transport.c index 640ccac..c61d0a6 100644 --- a/src/libgit2/transport.c +++ b/src/libgit2/transport.c @@ -22,6 +22,7 @@ typedef struct 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 @@ -33,11 +34,13 @@ static transport_definition transports[] = { { "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 } }; diff --git a/src/libgit2/transports/credential.c b/src/libgit2/transports/credential.c index 6e00b02..b47bd63 100644 --- a/src/libgit2/transports/credential.c +++ b/src/libgit2/transports/credential.c @@ -204,7 +204,7 @@ int git_credential_ssh_key_memory_new( const char *privatekey, const char *passphrase) { -#ifdef GIT_SSH_MEMORY_CREDENTIALS +#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS return git_credential_ssh_key_type_new( cred, username, diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index 8437674..ea81995 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -9,7 +9,6 @@ #ifndef GIT_WINHTTP -#include "http_parser.h" #include "net.h" #include "remote.h" #include "smart.h" @@ -334,7 +333,7 @@ static int lookup_proxy( return 0; } - if (!proxy || + if (!proxy || !*proxy || (error = git_net_url_parse_http(&transport->proxy.url, proxy)) < 0) goto done; diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h index 8e8e722..7410202 100644 --- a/src/libgit2/transports/http.h +++ b/src/libgit2/transports/http.h @@ -15,14 +15,4 @@ 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 index a20b594..a0c4002 100644 --- a/src/libgit2/transports/httpclient.c +++ b/src/libgit2/transports/httpclient.c @@ -7,7 +7,7 @@ #include "common.h" #include "git2.h" -#include "http_parser.h" + #include "vector.h" #include "trace.h" #include "httpclient.h" @@ -21,6 +21,7 @@ #include "streams/socket.h" #include "streams/tls.h" #include "auth.h" +#include "httpparser.h" static git_http_auth_scheme auth_schemes[] = { { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate }, @@ -108,7 +109,7 @@ struct git_http_client { git_http_server_t current_server; http_client_state state; - http_parser parser; + git_http_parser parser; git_http_server server; git_http_server proxy; @@ -154,7 +155,7 @@ void git_http_response_dispose(git_http_response *response) memset(response, 0, sizeof(git_http_response)); } -static int on_header_complete(http_parser *parser) +static int on_header_complete(git_http_parser *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; git_http_client *client = ctx->client; @@ -219,7 +220,7 @@ static int on_header_complete(http_parser *parser) return 0; } -static int on_header_field(http_parser *parser, const char *str, size_t len) +static int on_header_field(git_http_parser *parser, const char *str, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -254,7 +255,7 @@ static int on_header_field(http_parser *parser, const char *str, size_t len) return 0; } -static int on_header_value(http_parser *parser, const char *str, size_t len) +static int on_header_value(git_http_parser *parser, const char *str, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -342,7 +343,7 @@ static int resend_needed(git_http_client *client, git_http_response *response) return 0; } -static int on_headers_complete(http_parser *parser) +static int on_headers_complete(git_http_parser *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -364,8 +365,8 @@ static int on_headers_complete(http_parser *parser) return ctx->parse_status = PARSE_STATUS_ERROR; } - ctx->response->status = parser->status_code; - ctx->client->keepalive = http_should_keep_alive(parser); + ctx->response->status = git_http_parser_status_code(parser); + ctx->client->keepalive = git_http_parser_keep_alive(parser); /* Prepare for authentication */ collect_authinfo(&ctx->response->server_auth_schemetypes, @@ -378,18 +379,15 @@ static int on_headers_complete(http_parser *parser) 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; + return git_http_parser_pause(parser); } -static int on_body(http_parser *parser, const char *buf, size_t len) +static int on_body(git_http_parser *parser, const char *buf, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; size_t max_len; @@ -411,7 +409,7 @@ static int on_body(http_parser *parser, const char *buf, size_t len) return 0; } -static int on_message_complete(http_parser *parser) +static int on_message_complete(git_http_parser *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -651,6 +649,30 @@ static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) return git_str_oom(buf) ? -1 : 0; } +static int append_user_agent(git_str *buf) +{ + const char *product = git_settings__user_agent_product(); + const char *comment = git_settings__user_agent(); + + GIT_ASSERT(product && comment); + + if (!*product) + return 0; + + git_str_puts(buf, "User-Agent: "); + git_str_puts(buf, product); + + if (*comment) { + git_str_puts(buf, " ("); + git_str_puts(buf, comment); + git_str_puts(buf, ")"); + } + + git_str_puts(buf, "\r\n"); + + return git_str_oom(buf) ? -1 : 0; +} + static int generate_connect_request( git_http_client *client, git_http_request *request) @@ -665,9 +687,7 @@ static int generate_connect_request( 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"); + append_user_agent(buf); git_str_puts(buf, "Host: "); puts_host_and_port(buf, &client->server.url, true); @@ -711,9 +731,7 @@ static int generate_request( 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"); + append_user_agent(buf); git_str_puts(buf, "Host: "); puts_host_and_port(buf, request->url, false); @@ -768,25 +786,37 @@ static int check_certificate( void *cert_cb_payload) { git_cert *cert; - git_error_state last_error = {0}; + git_error *last_error; int error; if ((error = git_stream_certificate(&cert, stream)) < 0) return error; - git_error_state_capture(&last_error, GIT_ECERTIFICATE); + /* + * Allow callers to set an error - but save ours and clear + * it, so that we can detect if they set one and restore it + * if we need to. + */ + git_error_save(&last_error); + git_error_clear(); 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); + if (error == GIT_PASSTHROUGH) { + error = is_valid ? 0 : -1; - git_error_state_free(&last_error); + if (error) { + git_error_restore(last_error); + last_error = NULL; + } + } else if (error) { + if (!git_error_exists()) + git_error_set(GIT_ERROR_HTTP, + "user rejected certificate for %s", + url->host); + } + + git_error_free(last_error); return error; } @@ -864,9 +894,29 @@ GIT_INLINE(int) server_setup_from_url( return 0; } +static bool parser_settings_initialized; +static git_http_parser_settings parser_settings; + +GIT_INLINE(git_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; +} + static void reset_parser(git_http_client *client) { - http_parser_init(&client->parser, HTTP_RESPONSE); + git_http_parser_init(&client->parser, + GIT_HTTP_PARSER_RESPONSE, + http_client_parser_settings()); } static int setup_hosts( @@ -1109,27 +1159,9 @@ GIT_INLINE(int) client_read(git_http_client *client) 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; + git_http_parser *parser = &client->parser; http_parser_context *ctx = (http_parser_context *) parser->data; unsigned char http_errno; int read_len; @@ -1143,11 +1175,10 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) if (!client->read_buf.size && (read_len = client_read(client)) < 0) return read_len; - parsed_len = http_parser_execute(parser, - http_client_parser_settings(), + parsed_len = git_http_parser_execute(parser, client->read_buf.ptr, client->read_buf.size); - http_errno = client->parser.http_errno; + http_errno = git_http_parser_errno(parser); if (parsed_len > INT_MAX) { git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse"); @@ -1166,26 +1197,29 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) * (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) { + if (http_errno == GIT_HTTP_PARSER_PAUSED) { + size_t additional_size; + + git_http_parser_resume(parser); + /* - * 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 + * http-parser has a "feature" where it will not deliver + * the final byte when paused in a callback. Consume + * that byte. */ - GIT_ASSERT(client->read_buf.size > parsed_len); + if ((additional_size = git_http_parser_remain_after_pause(parser)) > 0) { + GIT_ASSERT((client->read_buf.size - parsed_len) >= additional_size); - http_parser_pause(parser, 0); - - parsed_len += http_parser_execute(parser, - http_client_parser_settings(), - client->read_buf.ptr + parsed_len, - 1); + parsed_len += git_http_parser_execute(parser, + client->read_buf.ptr + parsed_len, + additional_size); + } } /* Most failures will be reported in http_errno */ - else if (parser->http_errno != HPE_OK) { + else if (git_http_parser_errno(parser) != GIT_HTTP_PARSER_OK) { git_error_set(GIT_ERROR_HTTP, "http parser error: %s", - http_errno_description(http_errno)); + git_http_parser_errmsg(parser, http_errno)); return -1; } @@ -1193,7 +1227,7 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) 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)); + git_http_parser_errmsg(parser, http_errno)); return -1; } @@ -1232,7 +1266,7 @@ static void complete_response_body(git_http_client *client) /* If there was an error, just close the connection. */ if (client_read_and_parse(client) < 0 || - parser_context.error != HPE_OK || + parser_context.error != GIT_HTTP_PARSER_OK || (parser_context.parse_status != PARSE_STATUS_OK && parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { git_error_clear(); @@ -1240,6 +1274,7 @@ static void complete_response_body(git_http_client *client) } done: + client->parser.data = NULL; git_str_clear(&client->read_buf); } @@ -1429,6 +1464,7 @@ int git_http_client_read_response( done: git_str_dispose(&parser_context.parse_header_name); git_str_dispose(&parser_context.parse_header_value); + client->parser.data = NULL; return error; } @@ -1484,6 +1520,8 @@ done: if (error < 0) client->connected = 0; + client->parser.data = NULL; + return error; } @@ -1506,7 +1544,7 @@ int git_http_client_skip_body(git_http_client *client) do { error = client_read_and_parse(client); - if (parser_context.error != HPE_OK || + if (parser_context.error != GIT_HTTP_PARSER_OK || (parser_context.parse_status != PARSE_STATUS_OK && parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { git_error_set(GIT_ERROR_HTTP, @@ -1518,6 +1556,8 @@ int git_http_client_skip_body(git_http_client *client) if (error < 0) client->connected = 0; + client->parser.data = NULL; + return error; } diff --git a/src/libgit2/transports/httpparser.c b/src/libgit2/transports/httpparser.c new file mode 100644 index 0000000..50ba6d2 --- /dev/null +++ b/src/libgit2/transports/httpparser.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 "httpparser.h" + +#include <string.h> + +#if defined(GIT_HTTPPARSER_HTTPPARSER) + +#include "http_parser.h" + +static int on_message_begin(http_parser *p) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_message_begin(parser); +} + +static int on_url(http_parser *p, const char *str, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_url(parser, str, len); +} + +static int on_header_field(http_parser *p, const char *str, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_header_field(parser, str, len); +} + +static int on_header_value(http_parser *p, const char *str, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_header_value(parser, str, len); +} + +static int on_headers_complete(http_parser *p) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_headers_complete(parser); +} + +static int on_body(http_parser *p, const char *buf, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_body(parser, buf, len); +} + +static int on_message_complete(http_parser *p) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_message_complete(parser); +} + +void git_http_parser_init( + git_http_parser *parser, + git_http_parser_t type, + git_http_parser_settings *settings) +{ + http_parser_init(&parser->parser, (enum http_parser_type)type); + memcpy(&parser->settings, settings, sizeof(git_http_parser_settings)); +} + +size_t git_http_parser_execute( + git_http_parser *parser, + const char *data, + size_t len) +{ + struct http_parser_settings settings_proxy; + + settings_proxy.on_message_begin = parser->settings.on_message_begin ? on_message_begin : NULL; + settings_proxy.on_url = parser->settings.on_url ? on_url : NULL; + settings_proxy.on_header_field = parser->settings.on_header_field ? on_header_field : NULL; + settings_proxy.on_header_value = parser->settings.on_header_value ? on_header_value : NULL; + settings_proxy.on_headers_complete = parser->settings.on_headers_complete ? on_headers_complete : NULL; + settings_proxy.on_body = parser->settings.on_body ? on_body : NULL; + settings_proxy.on_message_complete = parser->settings.on_message_complete ? on_message_complete : NULL; + + return http_parser_execute(&parser->parser, &settings_proxy, data, len); +} + +#elif defined(GIT_HTTPPARSER_LLHTTP) || defined(GIT_HTTPPARSER_BUILTIN) + +# include <llhttp.h> + +size_t git_http_parser_execute( + git_http_parser *parser, + const char* data, + size_t len) +{ + llhttp_errno_t error; + size_t parsed_len; + + /* + * Unlike http_parser, which returns the number of parsed + * bytes in the _execute() call, llhttp returns an error + * code. + */ + + if (data == NULL || len == 0) + error = llhttp_finish(parser); + else + error = llhttp_execute(parser, data, len); + + parsed_len = len; + + /* + * Adjust number of parsed bytes in case of error. + */ + if (error != HPE_OK) { + parsed_len = llhttp_get_error_pos(parser) - data; + + /* This isn't a real pause, just a way to stop parsing early. */ + if (error == HPE_PAUSED_UPGRADE) + llhttp_resume_after_upgrade(parser); + } + + return parsed_len; +} + +#else +# error unknown http-parser +#endif diff --git a/src/libgit2/transports/httpparser.h b/src/libgit2/transports/httpparser.h new file mode 100644 index 0000000..1fe0dcf --- /dev/null +++ b/src/libgit2/transports/httpparser.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_transports_httpparser_h__ +#define INCLUDE_transports_httpparser_h__ + +#include "git2_util.h" + +#if defined(GIT_HTTPPARSER_HTTPPARSER) + +# include <http_parser.h> + +typedef enum { + GIT_HTTP_PARSER_OK = HPE_OK, + GIT_HTTP_PARSER_PAUSED = HPE_PAUSED, +} git_http_parser_error_t; + +typedef enum { + GIT_HTTP_PARSER_REQUEST = HTTP_REQUEST, + GIT_HTTP_PARSER_RESPONSE = HTTP_RESPONSE, +} git_http_parser_t; + +typedef struct git_http_parser git_http_parser; + +typedef struct { + int (*on_message_begin)(git_http_parser *); + int (*on_url)(git_http_parser *, const char *, size_t); + int (*on_header_field)(git_http_parser *, const char *, size_t); + int (*on_header_value)(git_http_parser *, const char *, size_t); + int (*on_headers_complete)(git_http_parser *); + int (*on_body)(git_http_parser *, const char *, size_t); + int (*on_message_complete)(git_http_parser *); +} git_http_parser_settings; + +struct git_http_parser { + http_parser parser; + git_http_parser_settings settings; + void *data; +}; + +void git_http_parser_init( + git_http_parser *parser, + git_http_parser_t type, + git_http_parser_settings *settings); + +size_t git_http_parser_execute( + git_http_parser *parser, + const char *data, + size_t len); + +# define git_http_parser_status_code(parser) parser->parser.status_code +# define git_http_parser_keep_alive(parser) http_should_keep_alive(&parser->parser) +# define git_http_parser_pause(parser) (http_parser_pause(&parser->parser, 1), 0) +# define git_http_parser_resume(parser) http_parser_pause(&parser->parser, 0) +# define git_http_parser_remain_after_pause(parser) 1 +# define git_http_parser_errno(parser) parser->parser.http_errno +# define git_http_parser_errmsg(parser, errno) http_errno_description(errno) + +#elif defined(GIT_HTTPPARSER_LLHTTP) || defined(GIT_HTTPPARSER_BUILTIN) + +# include <llhttp.h> + +typedef enum { + GIT_HTTP_PARSER_OK = HPE_OK, + GIT_HTTP_PARSER_PAUSED = HPE_PAUSED, +} git_http_parser_error_t; + +typedef enum { + GIT_HTTP_PARSER_REQUEST = HTTP_REQUEST, + GIT_HTTP_PARSER_RESPONSE = HTTP_RESPONSE, +} git_http_parser_t; + +typedef llhttp_t git_http_parser; +typedef llhttp_settings_t git_http_parser_settings; + +# define git_http_parser_init(parser, direction, settings) llhttp_init(parser, (llhttp_type_t)direction, settings) + +size_t git_http_parser_execute( + git_http_parser *parser, + const char *data, + size_t len); + +# define git_http_parser_status_code(parser) parser->status_code +# define git_http_parser_keep_alive(parser) llhttp_should_keep_alive(parser) +# define git_http_parser_pause(parser) (llhttp_pause(parser), GIT_HTTP_PARSER_PAUSED) +# define git_http_parser_resume(parser) llhttp_resume(parser) +# define git_http_parser_remain_after_pause(parser) 0 +# define git_http_parser_errno(parser) parser->error +# define git_http_parser_errmsg(parser, errno) llhttp_get_error_reason(parser) + +#else +# error unknown http-parser +#endif + +#endif diff --git a/src/libgit2/transports/local.c b/src/libgit2/transports/local.c index 64c21af..68ff1c1 100644 --- a/src/libgit2/transports/local.c +++ b/src/libgit2/transports/local.c @@ -303,6 +303,11 @@ static int local_negotiate_fetch( GIT_UNUSED(wants); + if (wants->depth) { + git_error_set(GIT_ERROR_NET, "shallow fetch is not supported by the local transport"); + return GIT_ENOTSUPPORTED; + } + /* Fill in the loids */ git_vector_foreach(&t->refs, i, rhead) { git_object *obj; @@ -453,7 +458,7 @@ static int local_push( default: last = git_error_last(); - if (last && last->message) + if (last->klass != GIT_ERROR_NONE) status->msg = git__strdup(last->message); else status->msg = git__strdup("Unspecified error encountered"); diff --git a/src/libgit2/transports/smart.c b/src/libgit2/transports/smart.c index 5372728..be0cb7b 100644 --- a/src/libgit2/transports/smart.c +++ b/src/libgit2/transports/smart.c @@ -249,6 +249,9 @@ static int git_smart__capabilities(unsigned int *capabilities, git_transport *tr *capabilities = 0; + if (t->caps.push_options) + *capabilities |= GIT_REMOTE_CAPABILITY_PUSH_OPTIONS; + if (t->caps.want_tip_sha1) *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID; @@ -370,17 +373,27 @@ static int git_smart__close(git_transport *transport) git_vector *common = &t->common; unsigned int i; git_pkt *p; + git_smart_service_t service; int ret; git_smart_subtransport_stream *stream; const char flush[] = "0000"; + if (t->direction == GIT_DIRECTION_FETCH) { + service = GIT_SERVICE_UPLOADPACK; + } else if (t->direction == GIT_DIRECTION_PUSH) { + service = GIT_SERVICE_RECEIVEPACK; + } else { + git_error_set(GIT_ERROR_NET, "invalid direction"); + return -1; + } + /* * 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->wrapped->action(&stream, t->wrapped, t->url, service)) { t->current_stream->write(t->current_stream, flush, 4); } @@ -513,7 +526,6 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) definition->callback(&t->wrapped, &t->parent, definition->param) < 0) { git_vector_free(&t->refs); git_vector_free(&t->heads); - t->wrapped->free(t->wrapped); git__free(t); return -1; } diff --git a/src/libgit2/transports/smart.h b/src/libgit2/transports/smart.h index 52c7553..c987d93 100644 --- a/src/libgit2/transports/smart.h +++ b/src/libgit2/transports/smart.h @@ -38,6 +38,7 @@ #define GIT_CAP_SHALLOW "shallow" #define GIT_CAP_OBJECT_FORMAT "object-format=" #define GIT_CAP_AGENT "agent=" +#define GIT_CAP_PUSH_OPTIONS "push-options" extern bool git_smart__ofs_delta_enabled; @@ -146,7 +147,8 @@ typedef struct transport_smart_caps { thin_pack:1, want_tip_sha1:1, want_reachable_sha1:1, - shallow:1; + shallow:1, + push_options:1; char *object_format; char *agent; } transport_smart_caps; @@ -203,7 +205,7 @@ int git_smart__update_heads(transport_smart *t, git_vector *symrefs); /* smart_pkt.c */ typedef struct { git_oid_t oid_type; - int seen_capabilities: 1; + unsigned int seen_capabilities: 1; } git_pkt_parse_data; int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen, git_pkt_parse_data *data); diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c index 3307acf..7ea8676 100644 --- a/src/libgit2/transports/smart_pkt.c +++ b/src/libgit2/transports/smart_pkt.c @@ -536,10 +536,10 @@ static int parse_len(size_t *out, const char *line, size_t linelen) num[PKT_LEN_SIZE] = '\0'; for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit(num[i])) { + if (!git__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])) { + if(!git__isprint(num[k])) { num[k] = '.'; } } diff --git a/src/libgit2/transports/smart_protocol.c b/src/libgit2/transports/smart_protocol.c index c9c422d..df1c190 100644 --- a/src/libgit2/transports/smart_protocol.c +++ b/src/libgit2/transports/smart_protocol.c @@ -59,7 +59,7 @@ int git_smart__store_refs(transport_smart *t, int flushes) return recvd; if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); + git_error_set(GIT_ERROR_NET, "could not read refs from remote repository"); return GIT_EEOF; } @@ -194,6 +194,12 @@ int git_smart__detect_caps( continue; } + if (!git__prefixcmp(ptr, GIT_CAP_PUSH_OPTIONS)) { + caps->common = caps->push_options = 1; + ptr += strlen(GIT_CAP_PUSH_OPTIONS); + continue; + } + if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { caps->common = caps->thin_pack = 1; ptr += strlen(GIT_CAP_THIN_PACK); @@ -285,7 +291,7 @@ static int recv_pkt( if ((ret = git_smart__recv(t)) < 0) { return ret; } else if (ret == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); + git_error_set(GIT_ERROR_NET, "could not read from remote repository"); return GIT_EEOF; } } while (error); @@ -778,33 +784,54 @@ done: static int gen_pktline(git_str *buf, git_push *push) { push_spec *spec; + char *option; size_t i, len; - char old_id[GIT_OID_SHA1_HEXSIZE+1], new_id[GIT_OID_SHA1_HEXSIZE+1]; - - old_id[GIT_OID_SHA1_HEXSIZE] = '\0'; new_id[GIT_OID_SHA1_HEXSIZE] = '\0'; + char old_id[GIT_OID_MAX_HEXSIZE + 1], new_id[GIT_OID_MAX_HEXSIZE + 1]; + size_t old_id_len, new_id_len; git_vector_foreach(&push->specs, i, spec) { - len = 2*GIT_OID_SHA1_HEXSIZE + 7 + strlen(spec->refspec.dst); + len = strlen(spec->refspec.dst) + 7; if (i == 0) { - ++len; /* '\0' */ + /* Need a leading \0 */ + ++len; + if (push->report_status) len += strlen(GIT_CAP_REPORT_STATUS) + 1; + + if (git_vector_length(&push->remote_push_options) > 0) + len += strlen(GIT_CAP_PUSH_OPTIONS) + 1; + len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; } + old_id_len = git_oid_hexsize(git_oid_type(&spec->roid)); + new_id_len = git_oid_hexsize(git_oid_type(&spec->loid)); + + len += (old_id_len + new_id_len); + git_oid_fmt(old_id, &spec->roid); + old_id[old_id_len] = '\0'; + git_oid_fmt(new_id, &spec->loid); + new_id[new_id_len] = '\0'; - git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst); + git_str_printf(buf, "%04"PRIxZ"%.*s %.*s %s", len, + (int)old_id_len, old_id, (int)new_id_len, 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); } + if (git_vector_length(&push->remote_push_options) > 0) { + git_str_putc(buf, ' '); + git_str_printf(buf, GIT_CAP_PUSH_OPTIONS); + } git_str_putc(buf, ' '); git_str_printf(buf, GIT_CAP_SIDE_BAND_64K); } @@ -812,6 +839,13 @@ static int gen_pktline(git_str *buf, git_push *push) git_str_putc(buf, '\n'); } + if (git_vector_length(&push->remote_push_options) > 0) { + git_str_printf(buf, "0000"); + git_vector_foreach(&push->remote_push_options, i, option) { + git_str_printf(buf, "%04"PRIxZ"%s", strlen(option) + 4 , option); + } + } + git_str_puts(buf, "0000"); return git_str_oom(buf) ? -1 : 0; } @@ -940,7 +974,7 @@ static int parse_report(transport_smart *transport, git_push *push) } if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); + git_error_set(GIT_ERROR_NET, "could not read report from remote repository"); error = GIT_EEOF; goto done; } @@ -1157,7 +1191,7 @@ int git_smart__push(git_transport *transport, git_push *push) #ifdef PUSH_DEBUG { git_remote_head *head; - char hex[GIT_OID_SHA1_HEXSIZE+1]; hex[GIT_OID_SHA1_HEXSIZE] = '\0'; + char hex[GIT_OID_MAX_HEXSIZE+1], hex[GIT_OID_MAX_HEXSIZE] = '\0'; git_vector_foreach(&push->remote->refs, i, head) { git_oid_fmt(hex, &head->oid); diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c index de63d45..3f3a127 100644 --- a/src/libgit2/transports/ssh.c +++ b/src/libgit2/transports/ssh.c @@ -5,1090 +5,67 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "ssh.h" +#include "ssh_exec.h" +#include "ssh_libssh2.h" -#ifdef GIT_SSH -#include <libssh2.h> -#endif - -#include "runtime.h" -#include "net.h" -#include "smart.h" -#include "streams/socket.h" -#include "sysdir.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) { - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - 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; +#include "transports/smart.h" - /* 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; -} - -#define SSH_DIR ".ssh" -#define KNOWN_HOSTS_FILE "known_hosts" - -/* - * Load the known_hosts file. - * - * Returns success but leaves the output NULL if we couldn't find the file. - */ -static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session) -{ - git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT; - LIBSSH2_KNOWNHOSTS *known_hosts = NULL; - int error; - - GIT_ASSERT_ARG(hosts); - - if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 || - (error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0) - goto out; - - if ((known_hosts = libssh2_knownhost_init(session)) == NULL) { - ssh_error(session, "error initializing known hosts"); - error = -1; - goto out; - } - - /* - * Try to read the file and consider not finding it as not trusting the - * host rather than an error. - */ - error = libssh2_knownhost_readfile(known_hosts, git_str_cstr(&path), LIBSSH2_KNOWNHOST_FILE_OPENSSH); - if (error == LIBSSH2_ERROR_FILE) - error = 0; - if (error < 0) - ssh_error(session, "error reading known_hosts"); - -out: - *hosts = known_hosts; - - git_str_dispose(&sshdir); - git_str_dispose(&path); - - return error; -} - -static void add_hostkey_pref_if_avail( - LIBSSH2_KNOWNHOSTS *known_hosts, - const char *hostname, - int port, - git_str *prefs, - int type, - const char *type_name) -{ - struct libssh2_knownhost *host = NULL; - const char key = '\0'; - int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type; - int error; - - error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host); - if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { - if (git_str_len(prefs) > 0) { - git_str_putc(prefs, ','); - } - git_str_puts(prefs, type_name); - } -} - -/* - * We figure out what kind of key we want to ask the remote for by trying to - * look it up with a nonsense key and using that mismatch to figure out what key - * we do have stored for the host. - * - * Populates prefs with the string to pass to libssh2_session_method_pref. - */ -static void find_hostkey_preference( - LIBSSH2_KNOWNHOSTS *known_hosts, - const char *hostname, - int port, - git_str *prefs) -{ - /* - * The order here is important as it indicates the priority of what will - * be preferred. - */ -#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519"); -#endif -#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256 - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256"); - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384"); - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521"); -#endif - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa"); -} - -static int _git_ssh_session_create( - LIBSSH2_SESSION **session, - LIBSSH2_KNOWNHOSTS **hosts, - const char *hostname, - int port, - git_stream *io) -{ - git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); - LIBSSH2_SESSION *s; - LIBSSH2_KNOWNHOSTS *known_hosts; - git_str prefs = GIT_STR_INIT; - int rc = 0; - - GIT_ASSERT_ARG(session); - GIT_ASSERT_ARG(hosts); - - s = libssh2_session_init(); - if (!s) { - git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); - return -1; - } - - if ((rc = load_known_hosts(&known_hosts, s)) < 0) { - ssh_error(s, "error loading known_hosts"); - libssh2_session_free(s); - return -1; - } - - find_hostkey_preference(known_hosts, hostname, port, &prefs); - if (git_str_len(&prefs) > 0) { - do { - rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs)); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - if (rc != LIBSSH2_ERROR_NONE) { - ssh_error(s, "failed to set hostkey preference"); - goto on_error; - } - } - git_str_dispose(&prefs); - - 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"); - goto on_error; - } - - libssh2_session_set_blocking(s, 1); - - *session = s; - *hosts = known_hosts; - - return 0; - -on_error: - libssh2_knownhost_free(known_hosts); - libssh2_session_free(s); - return -1; -} - - -/* - * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on - * the type of key that libssh2_session_hostkey returns. - */ -static int fingerprint_type_mask(int keytype) -{ - int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW; - return mask; - - switch (keytype) { - case LIBSSH2_HOSTKEY_TYPE_RSA: - mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; - break; - case LIBSSH2_HOSTKEY_TYPE_DSS: - mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; - break; -#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 - case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: - mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: - mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: - mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521; - break; -#endif -#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 - case LIBSSH2_HOSTKEY_TYPE_ED25519: - mask |= LIBSSH2_KNOWNHOST_KEY_ED25519; - break; -#endif - } - - return mask; -} - -/* - * Check the host against the user's known_hosts file. - * - * Returns 1/0 for valid/''not-valid or <0 for an error - */ -static int check_against_known_hosts( - LIBSSH2_SESSION *session, - LIBSSH2_KNOWNHOSTS *known_hosts, - const char *hostname, - int port, - const char *key, - size_t key_len, - int key_type) -{ - int check, typemask, ret = 0; - struct libssh2_knownhost *host = NULL; - - if (known_hosts == NULL) - return 0; - - typemask = fingerprint_type_mask(key_type); - check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host); - if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) { - ssh_error(session, "error checking for known host"); - return -1; - } - - ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0; - - return ret; -} - -/* - * Perform the check for the session's certificate against known hosts if - * possible and then ask the user if they have a callback. - * - * Returns 1/0 for valid/not-valid or <0 for an error - */ -static int check_certificate( - LIBSSH2_SESSION *session, - LIBSSH2_KNOWNHOSTS *known_hosts, - git_transport_certificate_check_cb check_cb, - void *check_cb_payload, - const char *host, - int port) -{ - git_cert_hostkey cert = {{ 0 }}; - const char *key; - size_t cert_len; - int cert_type, cert_valid = 0, error = 0; - - if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) { - ssh_error(session, "failed to retrieve hostkey"); - return -1; - } - - if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0) - return -1; - - cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; - 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"); - return -1; - } - - git_error_clear(); - error = 0; - if (!cert_valid) { - git_error_set(GIT_ERROR_SSH, "invalid or unknown remote ssh hostkey"); - error = GIT_ECERTIFICATE; - } - - if (check_cb != NULL) { - git_cert_hostkey *cert_ptr = &cert; - git_error_state previous_error = {0}; - - git_error_state_capture(&previous_error, error); - error = check_cb((git_cert *) cert_ptr, cert_valid, host, check_cb_payload); - if (error == GIT_PASSTHROUGH) { - error = git_error_state_restore(&previous_error); - } else if (error < 0 && !git_error_last()) { - git_error_set(GIT_ERROR_NET, "unknown remote host key"); - } - - git_error_state_free(&previous_error); - } - - return error; -} - -#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, port; - ssh_stream *s; - git_credential *cred = NULL; - LIBSSH2_SESSION *session=NULL; - LIBSSH2_CHANNEL *channel=NULL; - LIBSSH2_KNOWNHOSTS *known_hosts = 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 ((error = git_net_url_parse_standard_or_scp(&s->url, url)) < 0 || - (error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || - (error = git_stream_connect(s->io)) < 0) - goto done; - - /* - * Try to parse the port as a number, if we can't then fall back to - * default. It would be nice if we could get the port that was resolved - * as part of the stream connection, but that's not something that's - * exposed. - */ - if (git__strntol32(&port, s->url.port, strlen(s->url.port), NULL, 10) < 0) { - git_error_set(GIT_ERROR_NET, "invalid port to ssh: %s", s->url.port); - error = -1; - goto done; - } - - if ((error = _git_ssh_session_create(&session, &known_hosts, s->url.host, port, s->io)) < 0) - goto done; - - if ((error = check_certificate(session, known_hosts, t->owner->connect_opts.callbacks.certificate_check, t->owner->connect_opts.callbacks.payload, s->url.host, port)) < 0) - 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 (known_hosts) - libssh2_knownhost_free(known_hosts); - - 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); - } +int git_smart_subtransport_ssh( + git_smart_subtransport **out, + git_transport *owner, + void *param) +{ +#ifdef GIT_SSH_LIBSSH2 + return git_smart_subtransport_ssh_libssh2(out, owner, param); +#elif GIT_SSH_EXEC + return git_smart_subtransport_ssh_exec(out, owner, param); +#else + GIT_UNUSED(out); + GIT_UNUSED(owner); + GIT_UNUSED(param); - *stream = NULL; + git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport; library was built without SSH support"); 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, "remote rejected authentication"); - 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) +static int transport_set_paths(git_transport *t, git_strarray *paths) { -#ifdef GIT_SSH - ssh_subtransport *t; + transport_smart *smart = (transport_smart *)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; +#ifdef GIT_SSH_LIBSSH2 + return git_smart_subtransport_ssh_libssh2_set_paths( + (git_smart_subtransport *)smart->wrapped, + paths->strings[0], + paths->strings[1]); +#elif GIT_SSH_EXEC + return git_smart_subtransport_ssh_exec_set_paths( + (git_smart_subtransport *)smart->wrapped, + paths->strings[0], + paths->strings[1]); #else - GIT_UNUSED(owner); - GIT_UNUSED(param); - - GIT_ASSERT_ARG(out); - *out = NULL; + GIT_UNUSED(t); + GIT_UNUSED(smart); + GIT_UNUSED(paths); - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); + GIT_ASSERT(!"cannot create SSH library; library was built without SSH support"); return -1; #endif } -int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) +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, + NULL }; if (paths->count != 2) { @@ -1099,49 +76,10 @@ int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *p 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); + if ((error = transport_set_paths(transport, paths)) < 0) + return error; *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_exec.c b/src/libgit2/transports/ssh_exec.c new file mode 100644 index 0000000..a09c1db --- /dev/null +++ b/src/libgit2/transports/ssh_exec.c @@ -0,0 +1,346 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This 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_exec.h" + +#ifdef GIT_SSH_EXEC + +#include "common.h" + +#include "config.h" +#include "net.h" +#include "path.h" +#include "futils.h" +#include "process.h" +#include "transports/smart.h" + +typedef struct { + git_smart_subtransport_stream parent; +} ssh_exec_subtransport_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + + ssh_exec_subtransport_stream *current_stream; + + char *cmd_uploadpack; + char *cmd_receivepack; + + git_smart_service_t action; + git_process *process; +} ssh_exec_subtransport; + +static int ssh_exec_subtransport_stream_read( + git_smart_subtransport_stream *s, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + ssh_exec_subtransport *transport; + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + ssize_t ret; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT(stream->parent.subtransport); + + transport = (ssh_exec_subtransport *)stream->parent.subtransport; + + if ((ret = git_process_read(transport->process, buffer, buf_size)) < 0) { + return (int)ret; + } + + *bytes_read = (size_t)ret; + return 0; +} + +static int ssh_exec_subtransport_stream_write( + git_smart_subtransport_stream *s, + const char *buffer, + size_t len) +{ + ssh_exec_subtransport *transport; + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + ssize_t ret; + + GIT_ASSERT(stream && stream->parent.subtransport); + + transport = (ssh_exec_subtransport *)stream->parent.subtransport; + + while (len > 0) { + if ((ret = git_process_write(transport->process, buffer, len)) < 0) + return (int)ret; + + len -= ret; + } + + return 0; +} + +static void ssh_exec_subtransport_stream_free(git_smart_subtransport_stream *s) +{ + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + + git__free(stream); +} + +static int ssh_exec_subtransport_stream_init( + ssh_exec_subtransport_stream **out, + ssh_exec_subtransport *transport) +{ + GIT_ASSERT_ARG(out); + + *out = git__calloc(sizeof(ssh_exec_subtransport_stream), 1); + GIT_ERROR_CHECK_ALLOC(*out); + + (*out)->parent.subtransport = &transport->parent; + (*out)->parent.read = ssh_exec_subtransport_stream_read; + (*out)->parent.write = ssh_exec_subtransport_stream_write; + (*out)->parent.free = ssh_exec_subtransport_stream_free; + + return 0; +} + +GIT_INLINE(int) ensure_transport_state( + ssh_exec_subtransport *transport, + git_smart_service_t expected, + git_smart_service_t next) +{ + if (transport->action != expected && transport->action != next) { + git_error_set(GIT_ERROR_NET, "invalid transport state"); + + return -1; + } + + return 0; +} + +static int get_ssh_cmdline( + git_str *out, + ssh_exec_subtransport *transport, + git_net_url *url, + const char *command) +{ + git_remote *remote = ((transport_smart *)transport->owner)->owner; + git_repository *repo = remote->repo; + git_config *cfg; + git_str ssh_cmd = GIT_STR_INIT; + const char *default_ssh_cmd = "ssh"; + int error; + + /* + * Safety check: like git, we forbid paths that look like an + * option as that could lead to injection to ssh that can make + * us do unexpected things + */ + if (git_process__is_cmdline_option(url->username)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: username '%s' is ambiguous with command-line option", url->username); + return -1; + } else if (git_process__is_cmdline_option(url->host)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: host '%s' is ambiguous with command-line option", url->host); + return -1; + } else if (git_process__is_cmdline_option(url->path)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: path '%s' is ambiguous with command-line option", url->path); + return -1; + } + + if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) + return error; + + if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0) + ; + else if (error != GIT_ENOTFOUND) + goto done; + else if ((error = git_config__get_string_buf(&ssh_cmd, cfg, "core.sshcommand")) < 0 && error != GIT_ENOTFOUND) + goto done; + + error = git_str_printf(out, "%s -p %s \"%s%s%s\" \"%s\" \"%s\"", + ssh_cmd.size > 0 ? ssh_cmd.ptr : default_ssh_cmd, + url->port, + url->username ? url->username : "", + url->username ? "@" : "", + url->host, + command, + url->path); + +done: + git_str_dispose(&ssh_cmd); + git_config_free(cfg); + return error; +} + +static int start_ssh( + ssh_exec_subtransport *transport, + git_smart_service_t action, + const char *sshpath) +{ + const char *env[] = { "GIT_DIR=" }; + + git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT; + git_net_url url = GIT_NET_URL_INIT; + git_str ssh_cmdline = GIT_STR_INIT; + const char *command; + int error; + + process_opts.capture_in = 1; + process_opts.capture_out = 1; + process_opts.capture_err = 0; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + command = transport->cmd_uploadpack ? + transport->cmd_uploadpack : "git-upload-pack"; + break; + case GIT_SERVICE_RECEIVEPACK_LS: + command = transport->cmd_receivepack ? + transport->cmd_receivepack : "git-receive-pack"; + break; + default: + git_error_set(GIT_ERROR_NET, "invalid action"); + error = -1; + goto done; + } + + if (git_net_str_is_url(sshpath)) + error = git_net_url_parse(&url, sshpath); + else + error = git_net_url_parse_scp(&url, sshpath); + + if (error < 0) + goto done; + + if ((error = get_ssh_cmdline(&ssh_cmdline, transport, &url, command)) < 0) + goto done; + + if ((error = git_process_new_from_cmdline(&transport->process, + ssh_cmdline.ptr, env, ARRAY_SIZE(env), &process_opts)) < 0 || + (error = git_process_start(transport->process)) < 0) { + git_process_free(transport->process); + transport->process = NULL; + goto done; + } + +done: + git_str_dispose(&ssh_cmdline); + git_net_url_dispose(&url); + return error; +} + +static int ssh_exec_subtransport_action( + git_smart_subtransport_stream **out, + git_smart_subtransport *t, + const char *sshpath, + git_smart_service_t action) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + ssh_exec_subtransport_stream *stream = NULL; + git_smart_service_t expected; + int error; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + case GIT_SERVICE_RECEIVEPACK_LS: + if ((error = ensure_transport_state(transport, 0, 0)) < 0 || + (error = ssh_exec_subtransport_stream_init(&stream, transport)) < 0 || + (error = start_ssh(transport, action, sshpath)) < 0) + goto on_error; + + transport->current_stream = stream; + break; + + case GIT_SERVICE_UPLOADPACK: + case GIT_SERVICE_RECEIVEPACK: + expected = (action == GIT_SERVICE_UPLOADPACK) ? + GIT_SERVICE_UPLOADPACK_LS : GIT_SERVICE_RECEIVEPACK_LS; + + if ((error = ensure_transport_state(transport, expected, action)) < 0) + goto on_error; + + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid service request"); + goto on_error; + } + + transport->action = action; + *out = &transport->current_stream->parent; + + return 0; + +on_error: + if (stream != NULL) + ssh_exec_subtransport_stream_free(&stream->parent); + + return -1; +} + +static int ssh_exec_subtransport_close(git_smart_subtransport *t) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + + if (transport->process) { + git_process_close(transport->process); + git_process_free(transport->process); + transport->process = NULL; + } + + transport->action = 0; + + return 0; +} + +static void ssh_exec_subtransport_free(git_smart_subtransport *t) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + + git__free(transport->cmd_uploadpack); + git__free(transport->cmd_receivepack); + git__free(transport); +} + +int git_smart_subtransport_ssh_exec( + git_smart_subtransport **out, + git_transport *owner, + void *payload) +{ + ssh_exec_subtransport *transport; + + GIT_UNUSED(payload); + + transport = git__calloc(sizeof(ssh_exec_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(transport); + + transport->owner = owner; + transport->parent.action = ssh_exec_subtransport_action; + transport->parent.close = ssh_exec_subtransport_close; + transport->parent.free = ssh_exec_subtransport_free; + + *out = (git_smart_subtransport *) transport; + return 0; +} + +int git_smart_subtransport_ssh_exec_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack) +{ + ssh_exec_subtransport *t = (ssh_exec_subtransport *)subtransport; + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + + t->cmd_uploadpack = git__strdup(cmd_uploadpack); + GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); + + t->cmd_receivepack = git__strdup(cmd_receivepack); + GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + + return 0; +} + +#endif diff --git a/src/libgit2/transports/ssh_exec.h b/src/libgit2/transports/ssh_exec.h new file mode 100644 index 0000000..4bcba06 --- /dev/null +++ b/src/libgit2/transports/ssh_exec.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file 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_exec_h__ +#define INCLUDE_transports_ssh_exec_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" + +int git_smart_subtransport_ssh_exec( + git_smart_subtransport **out, + git_transport *owner, + void *param); + +int git_smart_subtransport_ssh_exec_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack); + +#endif diff --git a/src/libgit2/transports/ssh_libssh2.c b/src/libgit2/transports/ssh_libssh2.c new file mode 100644 index 0000000..1993ffe --- /dev/null +++ b/src/libgit2/transports/ssh_libssh2.c @@ -0,0 +1,1124 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This 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_libssh2.h" + +#ifdef GIT_SSH_LIBSSH2 + +#include <libssh2.h> + +#include "runtime.h" +#include "net.h" +#include "smart.h" +#include "process.h" +#include "streams/socket.h" +#include "sysdir.h" + +#include "git2/credential.h" +#include "git2/sys/credential.h" + +#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) + +extern int git_socket_stream__timeout; + +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) { + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + 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_LIBSSH2_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()->klass == GIT_ERROR_NONE) + 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; +} + +#define SSH_DIR ".ssh" +#define KNOWN_HOSTS_FILE "known_hosts" + +/* + * Load the known_hosts file. + * + * Returns success but leaves the output NULL if we couldn't find the file. + */ +static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session) +{ + git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT; + LIBSSH2_KNOWNHOSTS *known_hosts = NULL; + int error; + + GIT_ASSERT_ARG(hosts); + + if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 || + (error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0) + goto out; + + if ((known_hosts = libssh2_knownhost_init(session)) == NULL) { + ssh_error(session, "error initializing known hosts"); + error = -1; + goto out; + } + + /* + * Try to read the file and consider not finding it as not trusting the + * host rather than an error. + */ + error = libssh2_knownhost_readfile(known_hosts, git_str_cstr(&path), LIBSSH2_KNOWNHOST_FILE_OPENSSH); + if (error == LIBSSH2_ERROR_FILE) + error = 0; + if (error < 0) + ssh_error(session, "error reading known_hosts"); + +out: + *hosts = known_hosts; + + git_str_dispose(&sshdir); + git_str_dispose(&path); + + return error; +} + +static void add_hostkey_pref_if_avail( + LIBSSH2_KNOWNHOSTS *known_hosts, + const char *hostname, + int port, + git_str *prefs, + int type, + const char *type_name) +{ + struct libssh2_knownhost *host = NULL; + const char key = '\0'; + int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type; + int error; + + error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host); + if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { + if (git_str_len(prefs) > 0) { + git_str_putc(prefs, ','); + } + git_str_puts(prefs, type_name); + } +} + +/* + * We figure out what kind of key we want to ask the remote for by trying to + * look it up with a nonsense key and using that mismatch to figure out what key + * we do have stored for the host. + * + * Populates prefs with the string to pass to libssh2_session_method_pref. + */ +static void find_hostkey_preference( + LIBSSH2_KNOWNHOSTS *known_hosts, + const char *hostname, + int port, + git_str *prefs) +{ + /* + * The order here is important as it indicates the priority of what will + * be preferred. + */ +#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519"); +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256 + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256"); + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384"); + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521"); +#endif + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa"); +} + +static int _git_ssh_session_create( + LIBSSH2_SESSION **session, + LIBSSH2_KNOWNHOSTS **hosts, + const char *hostname, + int port, + git_stream *io) +{ + git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); + LIBSSH2_SESSION *s; + LIBSSH2_KNOWNHOSTS *known_hosts; + git_str prefs = GIT_STR_INIT; + int rc = 0; + + GIT_ASSERT_ARG(session); + GIT_ASSERT_ARG(hosts); + + s = libssh2_session_init(); + if (!s) { + git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); + return -1; + } + + if (git_socket_stream__timeout > 0) { + libssh2_session_set_timeout(s, git_socket_stream__timeout); + } + + if ((rc = load_known_hosts(&known_hosts, s)) < 0) { + ssh_error(s, "error loading known_hosts"); + libssh2_session_free(s); + return -1; + } + + find_hostkey_preference(known_hosts, hostname, port, &prefs); + if (git_str_len(&prefs) > 0) { + do { + rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs)); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(s, "failed to set hostkey preference"); + goto on_error; + } + } + git_str_dispose(&prefs); + + 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"); + goto on_error; + } + + libssh2_session_set_blocking(s, 1); + + *session = s; + *hosts = known_hosts; + + return 0; + +on_error: + libssh2_knownhost_free(known_hosts); + libssh2_session_free(s); + return -1; +} + + +/* + * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on + * the type of key that libssh2_session_hostkey returns. + */ +static int fingerprint_type_mask(int keytype) +{ + int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW; + return mask; + + switch (keytype) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; + break; +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: + mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + mask |= LIBSSH2_KNOWNHOST_KEY_ED25519; + break; +#endif + } + + return mask; +} + +/* + * Check the host against the user's known_hosts file. + * + * Returns 1/0 for valid/''not-valid or <0 for an error + */ +static int check_against_known_hosts( + LIBSSH2_SESSION *session, + LIBSSH2_KNOWNHOSTS *known_hosts, + const char *hostname, + int port, + const char *key, + size_t key_len, + int key_type) +{ + int check, typemask, ret = 0; + struct libssh2_knownhost *host = NULL; + + if (known_hosts == NULL) + return 0; + + typemask = fingerprint_type_mask(key_type); + check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host); + if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) { + ssh_error(session, "error checking for known host"); + return -1; + } + + ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0; + + return ret; +} + +/* + * Perform the check for the session's certificate against known hosts if + * possible and then ask the user if they have a callback. + * + * Returns 1/0 for valid/not-valid or <0 for an error + */ +static int check_certificate( + LIBSSH2_SESSION *session, + LIBSSH2_KNOWNHOSTS *known_hosts, + git_transport_certificate_check_cb check_cb, + void *check_cb_payload, + const char *host, + int port) +{ + git_cert_hostkey cert = {{ 0 }}; + const char *key; + size_t cert_len; + int cert_type, cert_valid = 0, error = GIT_ECERTIFICATE; + + if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) { + ssh_error(session, "failed to retrieve hostkey"); + return -1; + } + + if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0) + return -1; + + cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; + 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"); + return -1; + } + + if (check_cb != NULL) { + git_cert_hostkey *cert_ptr = &cert; + + error = check_cb((git_cert *)cert_ptr, cert_valid, host, + check_cb_payload); + + if (error == 0) + cert_valid = 1; + else if (error != GIT_PASSTHROUGH) + cert_valid = 0; + } + + if (!cert_valid) { + git_error_set(GIT_ERROR_SSH, "invalid or unknown remote ssh hostkey"); + return (error == GIT_PASSTHROUGH) ? GIT_ECERTIFICATE : error; + } + + 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, port; + ssh_stream *s; + git_credential *cred = NULL; + LIBSSH2_SESSION *session=NULL; + LIBSSH2_CHANNEL *channel=NULL; + LIBSSH2_KNOWNHOSTS *known_hosts = 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; + + /* Safety check: like git, we forbid paths that look like an option as + * that could lead to injection on the remote side */ + if (git_process__is_cmdline_option(s->url.path)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: path '%s' is ambiguous with command-line option", s->url.path); + error = -1; + 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; + + /* + * Try to parse the port as a number, if we can't then fall back to + * default. It would be nice if we could get the port that was resolved + * as part of the stream connection, but that's not something that's + * exposed. + */ + if (git__strntol32(&port, s->url.port, strlen(s->url.port), NULL, 10) < 0) + port = -1; + + if ((error = _git_ssh_session_create(&session, &known_hosts, s->url.host, port, s->io)) < 0) + goto done; + + if ((error = check_certificate(session, known_hosts, t->owner->connect_opts.callbacks.certificate_check, t->owner->connect_opts.callbacks.payload, s->url.host, port)) < 0) + 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 (known_hosts) + libssh2_knownhost_free(known_hosts); + + 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, "remote rejected authentication"); + 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_LIBSSH2_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; +} + +int git_smart_subtransport_ssh_libssh2( + git_smart_subtransport **out, + git_transport *owner, + void *param) +{ + 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; +} + +int git_smart_subtransport_ssh_libssh2_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack) +{ + ssh_subtransport *t = (ssh_subtransport *)subtransport; + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + + t->cmd_uploadpack = git__strdup(cmd_uploadpack); + GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); + + t->cmd_receivepack = git__strdup(cmd_receivepack); + GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + + return 0; +} + +static void shutdown_libssh2(void) +{ + libssh2_exit(); +} + +int git_transport_ssh_libssh2_global_init(void) +{ + if (libssh2_init(0) < 0) { + git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); + return -1; + } + + return git_runtime_shutdown_register(shutdown_libssh2); +} + +#else /* GIT_SSH */ + +int git_transport_ssh_libssh2_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/libgit2/transports/ssh_libssh2.h b/src/libgit2/transports/ssh_libssh2.h new file mode 100644 index 0000000..3f8cc2a --- /dev/null +++ b/src/libgit2/transports/ssh_libssh2.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_libssh2_h__ +#define INCLUDE_transports_libssh2_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" + +int git_transport_ssh_libssh2_global_init(void); + +int git_smart_subtransport_ssh_libssh2( + git_smart_subtransport **out, + git_transport *owner, + void *param); + +int git_smart_subtransport_ssh_libssh2_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack); + +#endif diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index ae572c5..b83ef99 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -293,7 +293,7 @@ static int certificate_check(winhttp_stream *s, int valid) /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { - if (!git_error_last()) + if (git_error_last()->klass == GIT_ERROR_NONE) git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); return GIT_ECERTIFICATE; @@ -317,7 +317,7 @@ static int certificate_check(winhttp_stream *s, int valid) if (error == GIT_PASSTHROUGH) error = valid ? 0 : GIT_ECERTIFICATE; - if (error < 0 && !git_error_last()) + if (error < 0 && git_error_last()->klass == GIT_ERROR_NONE) git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); return error; @@ -436,7 +436,7 @@ static int winhttp_stream_connect(winhttp_stream *s) GIT_ERROR_CHECK_ALLOC(proxy_url); } - if (proxy_url) { + if (proxy_url && *proxy_url) { git_str processed_url = GIT_STR_INIT; WINHTTP_PROXY_INFO proxy_info; wchar_t *proxy_wide; @@ -746,6 +746,33 @@ static void CALLBACK winhttp_status( } } +static int user_agent(bool *exists, git_str *out) +{ + const char *product = git_settings__user_agent_product(); + const char *comment = git_settings__user_agent(); + + GIT_ASSERT(product && comment); + + if (!*product) { + *exists = false; + return 0; + } + + git_str_puts(out, product); + + if (*comment) { + git_str_puts(out, " ("); + git_str_puts(out, comment); + git_str_puts(out, ")"); + } + + if (git_str_oom(out)) + return -1; + + *exists = true; + return 0; +} + static int winhttp_connect( winhttp_subtransport *t) { @@ -757,6 +784,7 @@ static int winhttp_connect( int error = -1; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + bool has_ua = true; DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | @@ -787,11 +815,11 @@ static int winhttp_connect( goto on_error; } - - if (git_http__user_agent(&ua) < 0) + if (user_agent(&has_ua, &ua) < 0) goto on_error; - if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { + if (has_ua && + 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; } @@ -933,7 +961,7 @@ static int send_request(winhttp_stream *s, size_t len, bool chunked) (!request_failed && s->status_sending_request_reached)) { git_error_clear(); if ((error = certificate_check(s, cert_valid)) < 0) { - if (!git_error_last()) + if (git_error_last()->klass == GIT_ERROR_NONE) git_error_set(GIT_ERROR_OS, "user cancelled certificate check"); return error; diff --git a/src/libgit2/tree.c b/src/libgit2/tree.c index 236a87f..18278d3 100644 --- a/src/libgit2/tree.c +++ b/src/libgit2/tree.c @@ -381,7 +381,7 @@ static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0) return error; - if (mode < 0 || mode > UINT16_MAX) + if (mode < 0 || (uint32_t)mode > UINT16_MAX) return -1; *mode_out = mode; diff --git a/src/libgit2/worktree.c b/src/libgit2/worktree.c index a878634..00ff9e7 100644 --- a/src/libgit2/worktree.c +++ b/src/libgit2/worktree.c @@ -335,11 +335,21 @@ int git_worktree_add(git_worktree **out, git_repository *repo, goto out; } - if (git_branch_is_checked_out(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); - err = -1; + if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) goto out; - } + } else if (wtopts.checkout_existing && git_branch_lookup(&ref, repo, name, GIT_BRANCH_LOCAL) == 0) { + /* Do nothing */ + } else if ((err = git_repository_head(&head, repo)) < 0 || + (err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0 || + (err = git_branch_create(&ref, repo, name, commit, false)) < 0) { + goto out; + } + + if (git_branch_is_checked_out(ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference %s is already checked out", + git_reference_name(ref)); + err = -1; + goto out; } /* Create gitdir directory ".git/worktrees/<name>" */ @@ -392,19 +402,6 @@ int git_worktree_add(git_worktree **out, git_repository *repo, || (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; diff --git a/src/util/array.h b/src/util/array.h index 633d598..515e6e3 100644 --- a/src/util/array.h +++ b/src/util/array.h @@ -41,39 +41,40 @@ #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) +GIT_INLINE(void *) git_array__alloc(void *arr, size_t *size, size_t *asize, size_t item_size) { - volatile git_array_generic_t *a = _a; size_t new_size; - char *new_array; + void *new_array; + + if (*size < *asize) + return arr; - if (a->size < 8) { + if (*size < 8) { new_size = 8; } else { - if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) + if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, *asize, 3)) goto on_oom; + new_size /= 2; } - if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) + if ((new_array = git__reallocarray(arr, new_size, item_size)) == NULL) goto on_oom; - a->ptr = new_array; - a->asize = new_size; - return 0; + *asize = new_size; + + return new_array; on_oom: - git_array_clear(*a); - return -1; + git__free(arr); + *size = 0; + *asize = 0; + return NULL; } #define git_array_alloc(a) \ - (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ - &(a).ptr[(a).size++] : (void *)NULL) + (((a).size < (a).asize || \ + ((a).ptr = git_array__alloc((a).ptr, &(a).size, &(a).asize, sizeof(*(a).ptr))) != NULL) ? &(a).ptr[(a).size++] : (void *)NULL) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) diff --git a/src/util/ctype_compat.h b/src/util/ctype_compat.h new file mode 100644 index 0000000..462c8a1 --- /dev/null +++ b/src/util/ctype_compat.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_ctype_compat_h__ +#define INCLUDE_ctype_compat_h__ + +/* + * The Microsoft C runtime (MSVCRT) may take a heavy lock on the + * locale in order to figure out how the `ctype` functions work. + * This is deeply slow. Provide our own to avoid that. + */ + +#ifdef GIT_WIN32 + +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} + +GIT_INLINE(int) git__toupper(int c) +{ + return (c >= 'a' && c <= 'z') ? (c - 32) : c; +} + +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__isalnum(int c) +{ + return git__isalpha(c) || git__isdigit(c); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +GIT_INLINE(bool) git__isprint(int c) +{ + return (c >= ' ' && c <= '~'); +} + +#else +# define git__tolower(a) tolower((unsigned char)(a)) +# define git__toupper(a) toupper((unsigned char)(a)) + +# define git__isalpha(a) (!!isalpha((unsigned char)(a))) +# define git__isdigit(a) (!!isdigit((unsigned char)(a))) +# define git__isalnum(a) (!!isalnum((unsigned char)(a))) +# define git__isspace(a) (!!isspace((unsigned char)(a))) +# define git__isxdigit(a) (!!isxdigit((unsigned char)(a))) +# define git__isprint(a) (!!isprint((unsigned char)(a))) +#endif + +#endif diff --git a/src/util/date.c b/src/util/date.c index 4d757e2..872cb81 100644 --- a/src/util/date.c +++ b/src/util/date.c @@ -129,9 +129,9 @@ static size_t match_string(const char *date, const char *str) for (i = 0; *date; date++, str++, i++) { if (*date == *str) continue; - if (toupper(*date) == toupper(*str)) + if (git__toupper(*date) == git__toupper(*str)) continue; - if (!isalnum(*date)) + if (!git__isalnum(*date)) break; return 0; } @@ -143,7 +143,7 @@ static int skip_alpha(const char *date) int i = 0; do { i++; - } while (isalpha(date[i])); + } while (git__isalpha(date[i])); return i; } @@ -251,7 +251,7 @@ static size_t match_multi_number(unsigned long num, char c, const char *date, ch num2 = strtol(end+1, &end, 10); num3 = -1; - if (*end == c && isdigit(end[1])) + if (*end == c && git__isdigit(end[1])) num3 = strtol(end+1, &end, 10); /* Time? Date? */ @@ -349,7 +349,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ case '.': case '/': case '-': - if (isdigit(end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(num, *end, date, end, tm); if (match) return match; @@ -364,7 +364,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ n = 0; do { n++; - } while (isdigit(date[n])); + } while (git__isdigit(date[n])); /* Four-digit year or a timezone? */ if (n == 4) { @@ -514,11 +514,11 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset if (!c || c == '\n') break; - if (isalpha(c)) + if (git__isalpha(c)) match = match_alpha(date, &tm, offset); - else if (isdigit(c)) + else if (git__isdigit(c)) match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit(date[1])) + else if ((c == '-' || c == '+') && git__isdigit(date[1])) match = match_tz(date, offset); if (!match) { @@ -682,7 +682,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm const char *end = date; int i; - while (isalpha(*++end)) + while (git__isalpha(*++end)) /* scan to non-alpha */; for (i = 0; i < 12; i++) { @@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) case '.': case '/': case '-': - if (isdigit(end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(number, *end, date, end, tm); if (match) return date + match; @@ -843,13 +843,13 @@ static git_time_t approxidate_str(const char *date, if (!c) break; date++; - if (isdigit(c)) { + if (git__isdigit(c)) { pending_number(&tm, &number); date = approxidate_digit(date-1, &tm, &number); touched = 1; continue; } - if (isalpha(c)) + if (git__isalpha(c)) date = approxidate_alpha(date-1, &tm, &now, &number, &touched); } pending_number(&tm, &number); diff --git a/src/util/errors.c b/src/util/errors.c new file mode 100644 index 0000000..feed6a8 --- /dev/null +++ b/src/util/errors.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This 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 "errors.h" +#include "posix.h" +#include "str.h" +#include "runtime.h" + +/* + * Some static error data that is used when we're out of memory, TLS + * has not been setup, or TLS has failed. + */ + +static git_error oom_error = { + "Out of memory", + GIT_ERROR_NOMEMORY +}; + +static git_error uninitialized_error = { + "library has not been initialized", + GIT_ERROR_INVALID +}; + +static git_error tlsdata_error = { + "thread-local data initialization failure", + GIT_ERROR_THREAD +}; + +static git_error no_error = { + "no error", + GIT_ERROR_NONE +}; + +#define IS_STATIC_ERROR(err) \ + ((err) == &oom_error || (err) == &uninitialized_error || \ + (err) == &tlsdata_error || (err) == &no_error) + +/* Per-thread error state (TLS) */ + +static git_tlsdata_key tls_key; + +struct error_threadstate { + /* The error message buffer. */ + git_str message; + + /* Error information, set by `git_error_set` and friends. */ + git_error error; + + /* + * The last error to occur; points to the error member of this + * struct _or_ a static error. + */ + git_error *last; +}; + +static void threadstate_dispose(struct error_threadstate *threadstate) +{ + if (!threadstate) + return; + + git_str_dispose(&threadstate->message); +} + +static struct error_threadstate *threadstate_get(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = git_tlsdata_get(tls_key)) != NULL) + return threadstate; + + /* + * Avoid git__malloc here, since if it fails, it sets an error + * message, which requires thread state, which would allocate + * here, which would fail, which would set an error message... + */ + + if ((threadstate = git__allocator.gmalloc( + sizeof(struct error_threadstate), + __FILE__, __LINE__)) == NULL) + return NULL; + + memset(threadstate, 0, sizeof(struct error_threadstate)); + + if (git_str_init(&threadstate->message, 0) < 0) { + git__allocator.gfree(threadstate); + return NULL; + } + + git_tlsdata_set(tls_key, threadstate); + return threadstate; +} + +static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) +{ + threadstate_dispose(threadstate); + git__free(threadstate); +} + +static void git_error_global_shutdown(void) +{ + struct error_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_error_global_init(void) +{ + if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) + return -1; + + return git_runtime_shutdown_register(git_error_global_shutdown); +} + +static void set_error_from_buffer(int error_class) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error; + git_str *buf; + + if (!threadstate) + return; + + error = &threadstate->error; + buf = &threadstate->message; + + error->message = buf->ptr; + error->klass = error_class; + + threadstate->last = error; +} + +static void set_error(int error_class, char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (string) + git_str_puts(buf, string); + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +void git_error_set_oom(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + threadstate->last = &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 + + struct error_threadstate *threadstate = threadstate_get(); + int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + 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) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + GIT_ASSERT_ARG(string); + + if (!threadstate) + return -1; + + buf = &threadstate->message; + + 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) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + if (threadstate->last != NULL) { + set_error(0, NULL); + threadstate->last = NULL; + } + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif +} + +bool git_error_exists(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = threadstate_get()) == NULL) + return true; + + return threadstate->last != NULL; +} + +const git_error *git_error_last(void) +{ + struct error_threadstate *threadstate; + + /* If the library is not initialized, return a static error. */ + if (!git_runtime_init_count()) + return &uninitialized_error; + + if ((threadstate = threadstate_get()) == NULL) + return &tlsdata_error; + + if (!threadstate->last) + return &no_error; + + return threadstate->last; +} + +int git_error_save(git_error **out) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error, *dup; + + if (!threadstate) { + *out = &tlsdata_error; + return -1; + } + + error = threadstate->last; + + if (!error || error == &no_error) { + *out = &no_error; + return 0; + } else if (IS_STATIC_ERROR(error)) { + *out = error; + return 0; + } + + if ((dup = git__malloc(sizeof(git_error))) == NULL) { + *out = &oom_error; + return -1; + } + + dup->klass = error->klass; + dup->message = git__strdup(error->message); + + if (!dup->message) { + *out = &oom_error; + return -1; + } + + *out = dup; + return 0; +} + +int git_error_restore(git_error *error) +{ + struct error_threadstate *threadstate = threadstate_get(); + + GIT_ASSERT_ARG(error); + + if (IS_STATIC_ERROR(error) && threadstate) + threadstate->last = error; + else + set_error(error->klass, error->message); + + git_error_free(error); + return 0; +} + +void git_error_free(git_error *error) +{ + if (!error) + return; + + if (IS_STATIC_ERROR(error)) + return; + + git__free(error->message); + git__free(error); +} + +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 + +#include "git2/deprecated.h" + +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/util/errors.h index 772c7ba..8d58775 100644 --- a/src/libgit2/errors.h +++ b/src/util/errors.h @@ -8,7 +8,11 @@ #ifndef INCLUDE_errors_h__ #define INCLUDE_errors_h__ -#include "common.h" +#include "git2_util.h" +#include "git2/sys/errors.h" + +/* Initialize the error thread-state. */ +int git_error_global_init(void); /* * `vprintf`-style formatting for the error message for this thread. @@ -16,6 +20,11 @@ void git_error_vset(int error_class, const char *fmt, va_list ap); /** + * Determines whether an error exists. + */ +bool git_error_exists(void); + +/** * Set error message for user callback if needed. * * If the error code in non-zero and no error message is set, this @@ -27,9 +36,8 @@ 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, + if (!git_error_exists()) + git_error_set(GIT_ERROR_CALLBACK, "%s callback returned %d", action, error_code); } return error_code; @@ -54,27 +62,23 @@ int git_error_system_last(void); 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. + * + * This function returns 0 on success, or -1 on failure. If the function + * fails, the `out` structure is set to the failure error message and + * the normal system error message is not updated. */ -extern int git_error_state_capture(git_error_state *state, int error_code); +extern int git_error_save(git_error **out); /** - * Restore error state to a previous value, returning saved error code. + * Restore thread error state to the given value. The given value is + * freed and `git_error_free` need not be called on it. */ -extern int git_error_state_restore(git_error_state *state); +extern int git_error_restore(git_error *error); /** Free an error state. */ -extern void git_error_state_free(git_error_state *state); +extern void git_error_free(git_error *error); #endif diff --git a/src/util/fs_path.c b/src/util/fs_path.c index e03fcf7..9d5c99e 100644 --- a/src/util/fs_path.c +++ b/src/util/fs_path.c @@ -419,6 +419,16 @@ int git_fs_path_to_dir(git_str *path) return git_str_oom(path) ? -1 : 0; } +size_t git_fs_path_dirlen(const char *path) +{ + size_t len = strlen(path); + + while (len > 1 && path[len - 1] == '/') + len--; + + return len; +} + void git_fs_path_string_to_dir(char *path, size_t size) { size_t end = strlen(path); @@ -1938,12 +1948,13 @@ static int sudo_uid_lookup(uid_t *out) { git_str uid_str = GIT_STR_INIT; int64_t uid; - int error; + int error = -1; - 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)) { + if (git__getenv(&uid_str, "SUDO_UID") == 0 && + git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10) == 0 && + uid == (int64_t)((uid_t)uid)) { *out = (uid_t)uid; + error = 0; } git_str_dispose(&uid_str); diff --git a/src/util/fs_path.h b/src/util/fs_path.h index e5ca673..43f7951 100644 --- a/src/util/fs_path.h +++ b/src/util/fs_path.h @@ -87,6 +87,29 @@ extern int git_fs_path_to_dir(git_str *path); extern void git_fs_path_string_to_dir(char *path, size_t size); /** + * Provides the length of the given path string with no trailing + * slashes. + */ +size_t git_fs_path_dirlen(const char *path); + +/** + * Returns nonzero if the given path is a filesystem root; on Windows, this + * means a drive letter (eg `A:/`, `C:\`). On POSIX this is `/`. + */ +GIT_INLINE(int) git_fs_path_is_root(const char *name) +{ +#ifdef GIT_WIN32 + if (((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) && + name[1] == ':' && + (name[2] == '/' || name[2] == '\\') && + name[3] == '\0') + return 1; +#endif + + return (name[0] == '/' && name[1] == '\0'); +} + +/** * 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) diff --git a/src/util/futils.h b/src/util/futils.h index 3f207af..53bcc55 100644 --- a/src/util/futils.h +++ b/src/util/futils.h @@ -25,7 +25,7 @@ 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], + unsigned char checksum[GIT_HASH_SHA256_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); diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in index a84ea89..52b7328 100644 --- a/src/util/git2_features.h.in +++ b/src/util/git2_features.h.in @@ -30,7 +30,9 @@ #cmakedefine GIT_QSORT_MSC #cmakedefine GIT_SSH 1 -#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 +#cmakedefine GIT_SSH_EXEC 1 +#cmakedefine GIT_SSH_LIBSSH2 1 +#cmakedefine GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1 #cmakedefine GIT_NTLM 1 #cmakedefine GIT_GSSAPI 1 @@ -44,6 +46,10 @@ #cmakedefine GIT_MBEDTLS 1 #cmakedefine GIT_SCHANNEL 1 +#cmakedefine GIT_HTTPPARSER_HTTPPARSER 1 +#cmakedefine GIT_HTTPPARSER_LLHTTP 1 +#cmakedefine GIT_HTTPPARSER_BUILTIN 1 + #cmakedefine GIT_SHA1_COLLISIONDETECT 1 #cmakedefine GIT_SHA1_WIN32 1 #cmakedefine GIT_SHA1_COMMON_CRYPTO 1 diff --git a/src/util/git2_util.h b/src/util/git2_util.h index c62dc24..5bf0981 100644 --- a/src/util/git2_util.h +++ b/src/util/git2_util.h @@ -12,6 +12,7 @@ #endif #include "git2/common.h" +#include "git2/sys/errors.h" #include "cc-compat.h" typedef struct git_str git_str; @@ -164,5 +165,6 @@ typedef struct git_str git_str; if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } #include "util.h" +#include "ctype_compat.h" #endif diff --git a/src/util/integer.h b/src/util/integer.h index 6327717..a9e416c 100644 --- a/src/util/integer.h +++ b/src/util/integer.h @@ -89,7 +89,9 @@ GIT_INLINE(int) git__is_int(int64_t p) /* Use Microsoft's safe integer handling functions where available */ #elif defined(_MSC_VER) -# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# if !defined(ENABLE_INTSAFE_SIGNED_FUNCTIONS) +# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# endif # include <intsafe.h> # define git__add_sizet_overflow(out, one, two) \ diff --git a/src/util/net.c b/src/util/net.c index afd52ce..dede784 100644 --- a/src/util/net.c +++ b/src/util/net.c @@ -11,7 +11,6 @@ #include "posix.h" #include "str.h" -#include "http_parser.h" #include "runtime.h" #define DEFAULT_PORT_HTTP "80" @@ -22,7 +21,7 @@ #define GIT_NET_URL_PARSER_INIT { 0 } typedef struct { - int hierarchical : 1; + unsigned int hierarchical : 1; const char *scheme; const char *user; @@ -657,7 +656,7 @@ static bool has_at(const char *str) 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; + const char *c, *user, *host, *port = NULL, *path = NULL; size_t user_len = 0, host_len = 0, port_len = 0; unsigned short bracket = 0; diff --git a/src/util/process.h b/src/util/process.h new file mode 100644 index 0000000..3ada669 --- /dev/null +++ b/src/util/process.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_process_h__ +#define INCLUDE_process_h__ + +typedef struct git_process git_process; + +typedef struct { + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1, + exclude_env : 1; + + char *cwd; +} git_process_options; + +typedef enum { + GIT_PROCESS_STATUS_NONE, + GIT_PROCESS_STATUS_NORMAL, + GIT_PROCESS_STATUS_ERROR +} git_process_result_status; + +#define GIT_PROCESS_RESULT_INIT { GIT_PROCESS_STATUS_NONE } + +typedef struct { + git_process_result_status status; + int exitcode; + int signal; +} git_process_result; + +#define GIT_PROCESS_OPTIONS_INIT { 0 } + +#ifdef GIT_WIN32 +# define p_pid_t DWORD +#else +# define p_pid_t pid_t +#endif + +/** + * Create a new process. The command to run should be specified as the + * element of the `arg` array, execv-style. This should be the full path + * to the command to run, the PATH is not obeyed. + * + * This function will add the given environment variables (in `env`) + * to the current environment. Operations on environment variables + * are not thread safe, so you may not modify the environment during + * this call. You can avoid this by setting `exclude_env` in the + * options and providing the entire environment yourself. + * + * @param out location to store the process + * @param args the command (with arguments) to run + * @param args_len the length of the args array + * @param env environment variables to add (or NULL) + * @param env_len the length of the env len + * @param opts the options for creating the process + * @return 0 or an error code + */ +extern int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts); + +/** + * Create a new process. The command to run should be specified as the + * `cmdline` option - which is the full text of the command line as it + * would be specified or run by a user. The command to run will be + * looked up in the PATH. + * + * On Unix, this will be executed by the system's shell (`/bin/sh`) + * and may contain _Bourne-style_ shell quoting rules. On Windows, + * this will be passed to `CreateProcess`, and similarly, may + * contain _Windows-style_ shell quoting rules. + * + * This function will add the given environment variables (in `env`) + * to the current environment. Operations on environment variables + * are not thread safe, so you may not modify the environment during + * this call. You can avoid this by setting `exclude_env` in the + * options and providing the entire environment yourself. + */ +extern int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts); + +#ifdef GIT_WIN32 + +extern int git_process__appname( + git_str *out, + const char *cmdline); + +/* Windows path parsing is tricky; this helper function is for testing. */ +extern int git_process__cmdline( + git_str *out, + const char **in, + size_t in_len); + +#endif + +/* + * Whether the given string looks like a command line option (starts + * with a dash). This is useful for examining strings that will become + * cmdline arguments to ensure that they are not erroneously treated + * as an option. For example, arguments to `ssh`. + */ +GIT_INLINE(bool) git_process__is_cmdline_option(const char *str) +{ + return (str && str[0] == '-'); +} + +/** + * Start the process. + * + * @param process the process to start + * @return 0 or an error code + */ +extern int git_process_start(git_process *process); + +/** + * Returns the process id of the process. + * + * @param out pointer to a pid_t to store the process id + * @param process the process to query + * @return 0 or an error code + */ +extern int git_process_id(p_pid_t *out, git_process *process); + +/** + * Read from the process's stdout. The process must have been created with + * `capture_out` set to true. + * + * @param process the process to read from + * @param buf the buf to read into + * @param count maximum number of bytes to read + * @return number of bytes read or an error code + */ +extern ssize_t git_process_read(git_process *process, void *buf, size_t count); + +/** + * Read from the process's stderr. The process must have been created with + * `capture_err` set to true. + * + * @param process the process to read from + * @param buf the buf to read into + * @param count maximum number of bytes to read + * @return number of bytes read or an error code + */ +extern ssize_t git_process_read_err(git_process *process, void *buf, size_t count); + +/** + * Write to the process's stdin. The process must have been created with + * `capture_in` set to true. + * + * @param process the process to write to + * @param buf the buf to write + * @param count maximum number of bytes to write + * @return number of bytes written or an error code + */ +extern ssize_t git_process_write(git_process *process, const void *buf, size_t count); + +/** + * Wait for the process to finish. + * + * @param result the result of the process or NULL + * @param process the process to wait on + */ +extern int git_process_wait(git_process_result *result, git_process *process); + +/** + * Close the input pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_in(git_process *process); + +/** + * Close the output pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_out(git_process *process); + +/** + * Close the error pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_err(git_process *process); + +/** + * Close all resources that are used by the process. This does not + * wait for the process to complete. + * + * @parma process the process to close + */ +extern int git_process_close(git_process *process); + +/** + * Place a human-readable error message in the given git buffer. + * + * @param msg the buffer to store the message + * @param result the process result that produced an error + */ +extern int git_process_result_msg(git_str *msg, git_process_result *result); + +/** + * Free a process structure + * + * @param process the process to free + */ +extern void git_process_free(git_process *process); + +#endif diff --git a/src/util/rand.c b/src/util/rand.c index 2ed0605..2b137a5 100644 --- a/src/util/rand.c +++ b/src/util/rand.c @@ -10,10 +10,6 @@ See <http://creativecommons.org/publicdomain/zero/1.0/>. */ #include "rand.h" #include "runtime.h" -#if defined(GIT_RAND_GETENTROPY) -# include <sys/random.h> -#endif - #if defined(GIT_WIN32) # include <wincrypt.h> #endif @@ -80,10 +76,10 @@ GIT_INLINE(int) getseed(uint64_t *seed) GIT_INLINE(int) getseed(uint64_t *seed) { struct timeval tv; - double loadavg[3]; int fd; # if defined(GIT_RAND_GETLOADAVG) + double loadavg[3]; bits convert; # endif @@ -129,8 +125,6 @@ GIT_INLINE(int) getseed(uint64_t *seed) 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 *seed ^= git_time_monotonic(); diff --git a/src/util/regexp.c b/src/util/regexp.c index 0870088..eb45822 100644 --- a/src/util/regexp.c +++ b/src/util/regexp.c @@ -125,7 +125,7 @@ int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { git_error_set_oom(); - goto out; + return -1; } if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), diff --git a/src/util/str.c b/src/util/str.c index 0d405bf..0b07c81 100644 --- a/src/util/str.c +++ b/src/util/str.c @@ -485,8 +485,8 @@ int git_str_decode_percent( 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])) { + git__isxdigit(str[str_pos + 1]) && + git__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; diff --git a/src/util/strlist.c b/src/util/strlist.c new file mode 100644 index 0000000..df5640c --- /dev/null +++ b/src/util/strlist.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> + +#include "git2_util.h" +#include "vector.h" +#include "strlist.h" + +int git_strlist_copy(char ***out, const char **in, size_t len) +{ + char **dup; + size_t i; + + dup = git__calloc(len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +int git_strlist_copy_with_null(char ***out, const char **in, size_t len) +{ + char **dup; + size_t new_len, i; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_len, len, 1); + + dup = git__calloc(new_len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +bool git_strlist_contains_prefix( + const char **strings, + size_t len, + const char *str, + size_t n) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (strncmp(strings[i], str, n) == 0) + return true; + } + + return false; +} + +bool git_strlist_contains_key( + const char **strings, + size_t len, + const char *key, + char delimiter) +{ + const char *c; + + for (c = key; *c; c++) { + if (*c == delimiter) + break; + } + + return *c ? + git_strlist_contains_prefix(strings, len, key, (c - key)) : + false; +} + +void git_strlist_free(char **strings, size_t len) +{ + size_t i; + + if (!strings) + return; + + for (i = 0; i < len; i++) + git__free(strings[i]); + + git__free(strings); +} + +void git_strlist_free_with_null(char **strings) +{ + char **s; + + if (!strings) + return; + + for (s = strings; *s; s++) + git__free(*s); + + git__free(strings); +} diff --git a/src/util/strlist.h b/src/util/strlist.h new file mode 100644 index 0000000..68fbf8f --- /dev/null +++ b/src/util/strlist.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_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "git2_util.h" + +extern int git_strlist_copy(char ***out, const char **in, size_t len); + +extern int git_strlist_copy_with_null( + char ***out, + const char **in, + size_t len); + +extern bool git_strlist_contains_prefix( + const char **strings, + size_t len, + const char *str, + size_t n); + +extern bool git_strlist_contains_key( + const char **strings, + size_t len, + const char *key, + char delimiter); + +extern void git_strlist_free(char **strings, size_t len); + +extern void git_strlist_free_with_null(char **strings); + +#endif diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h index 778477e..60f27d3 100644 --- a/src/util/unix/posix.h +++ b/src/util/unix/posix.h @@ -54,8 +54,6 @@ GIT_INLINE(int) p_fsync(int fd) #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) diff --git a/src/util/unix/process.c b/src/util/unix/process.c new file mode 100644 index 0000000..68c0384 --- /dev/null +++ b/src/util/unix/process.c @@ -0,0 +1,629 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> +#include <sys/wait.h> +#include <signal.h> +#include <git2.h> + +#include "git2_util.h" +#include "vector.h" +#include "process.h" +#include "strlist.h" + +#ifdef __APPLE__ + #include <crt_externs.h> + #define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif + +struct git_process { + char **args; + char **env; + + char *cwd; + + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1; + + pid_t pid; + + int child_in; + int child_out; + int child_err; + git_process_result_status status; +}; + +GIT_INLINE(bool) is_delete_env(const char *env) +{ + char *c = strchr(env, '='); + + if (c == NULL) + return false; + + return *(c+1) == '\0'; +} + +static int merge_env( + char ***out, + const char **env, + size_t env_len, + bool exclude_env) +{ + git_vector merged = GIT_VECTOR_INIT; + char **kv, *dup; + size_t max, cnt; + int error = 0; + + for (max = env_len, kv = environ; !exclude_env && *kv; kv++) + max++; + + if ((error = git_vector_init(&merged, max, NULL)) < 0) + goto on_error; + + for (cnt = 0; env && cnt < env_len; cnt++) { + if (is_delete_env(env[cnt])) + continue; + + dup = git__strdup(env[cnt]); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(&merged, dup)) < 0) + goto on_error; + } + + if (!exclude_env) { + for (kv = environ; *kv; kv++) { + if (env && git_strlist_contains_key(env, env_len, *kv, '=')) + continue; + + dup = git__strdup(*kv); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(&merged, dup)) < 0) + goto on_error; + } + } + + if (merged.length == 0) { + *out = NULL; + error = 0; + goto on_error; + } + + git_vector_insert(&merged, NULL); + + *out = (char **)merged.contents; + + return 0; + +on_error: + git_vector_free_deep(&merged); + return error; +} + +int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process *process; + + GIT_ASSERT_ARG(out && args && args_len > 0); + + *out = NULL; + + process = git__calloc(sizeof(git_process), 1); + GIT_ERROR_CHECK_ALLOC(process); + + if (git_strlist_copy_with_null(&process->args, args, args_len) < 0 || + merge_env(&process->env, env, env_len, opts ? opts->exclude_env : false) < 0) { + git_process_free(process); + return -1; + } + + if (opts) { + process->capture_in = opts->capture_in; + process->capture_out = opts->capture_out; + process->capture_err = opts->capture_err; + + if (opts->cwd) { + process->cwd = git__strdup(opts->cwd); + GIT_ERROR_CHECK_ALLOC(process->cwd); + } + } + + process->child_in = -1; + process->child_out = -1; + process->child_err = -1; + process->status = -1; + + *out = process; + return 0; +} + +extern int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + const char *args[] = { "/bin/sh", "-c", cmdline }; + + return git_process_new(out, + args, ARRAY_SIZE(args), env, env_len, opts); +} + +#define CLOSE_FD(fd) \ + if (fd >= 0) { \ + close(fd); \ + fd = -1; \ + } + +static int try_read_status(size_t *out, int fd, void *buf, size_t len) +{ + size_t read_len = 0; + int ret = -1; + + while (ret && read_len < len) { + ret = read(fd, buf + read_len, len - read_len); + + if (ret < 0 && errno != EAGAIN && errno != EINTR) { + git_error_set(GIT_ERROR_OS, "could not read child status"); + return -1; + } + + read_len += ret; + } + + *out = read_len; + return 0; +} + + +static int read_status(int fd) +{ + size_t status_len = sizeof(int) * 3, read_len = 0; + char buffer[status_len], fn[128]; + int error, fn_error, os_error, fn_len = 0; + + if ((error = try_read_status(&read_len, fd, buffer, status_len)) < 0) + return error; + + /* Immediate EOF indicates the exec succeeded. */ + if (read_len == 0) + return 0; + + if (read_len < status_len) { + git_error_set(GIT_ERROR_INVALID, "child status truncated"); + return -1; + } + + memcpy(&fn_error, &buffer[0], sizeof(int)); + memcpy(&os_error, &buffer[sizeof(int)], sizeof(int)); + memcpy(&fn_len, &buffer[sizeof(int) * 2], sizeof(int)); + + if (fn_len > 0) { + fn_len = min(fn_len, (int)(ARRAY_SIZE(fn) - 1)); + + if ((error = try_read_status(&read_len, fd, fn, fn_len)) < 0) + return error; + + fn[fn_len] = '\0'; + } else { + fn[0] = '\0'; + } + + if (fn_error) { + errno = os_error; + git_error_set(GIT_ERROR_OS, "could not %s", fn[0] ? fn : "(unknown)"); + } + + return fn_error; +} + +static bool try_write_status(int fd, const void *buf, size_t len) +{ + size_t write_len; + int ret; + + for (write_len = 0; write_len < len; ) { + ret = write(fd, buf + write_len, len - write_len); + + if (ret <= 0) + break; + + write_len += ret; + } + + return (len == write_len); +} + +static void write_status(int fd, const char *fn, int error, int os_error) +{ + size_t status_len = sizeof(int) * 3, fn_len; + char buffer[status_len]; + + fn_len = strlen(fn); + + if (fn_len > INT_MAX) + fn_len = INT_MAX; + + memcpy(&buffer[0], &error, sizeof(int)); + memcpy(&buffer[sizeof(int)], &os_error, sizeof(int)); + memcpy(&buffer[sizeof(int) * 2], &fn_len, sizeof(int)); + + /* Do our best effort to write all the status. */ + if (!try_write_status(fd, buffer, status_len)) + return; + + if (fn_len) + try_write_status(fd, fn, fn_len); +} + +int git_process_start(git_process *process) +{ + int in[2] = { -1, -1 }, out[2] = { -1, -1 }, + err[2] = { -1, -1 }, status[2] = { -1, -1 }; + int fdflags, state, error; + pid_t pid; + + /* Set up the pipes to read from/write to the process */ + if ((process->capture_in && pipe(in) < 0) || + (process->capture_out && pipe(out) < 0) || + (process->capture_err && pipe(err) < 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + /* Set up a self-pipe for status from the forked process. */ + if (pipe(status) < 0 || + (fdflags = fcntl(status[1], F_GETFD)) < 0 || + fcntl(status[1], F_SETFD, fdflags | FD_CLOEXEC) < 0) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + switch (pid = fork()) { + case -1: + git_error_set(GIT_ERROR_OS, "could not fork"); + goto on_error; + + /* Child: start the process. */ + case 0: + /* Close the opposing side of the pipes */ + CLOSE_FD(status[0]); + + if (process->capture_in) { + CLOSE_FD(in[1]); + dup2(in[0], STDIN_FILENO); + } + + if (process->capture_out) { + CLOSE_FD(out[0]); + dup2(out[1], STDOUT_FILENO); + } + + if (process->capture_err) { + CLOSE_FD(err[0]); + dup2(err[1], STDERR_FILENO); + } + + if (process->cwd && (error = chdir(process->cwd)) < 0) { + write_status(status[1], "chdir", error, errno); + exit(0); + } + + /* + * Exec the process and write the results back if the + * call fails. If it succeeds, we'll close the status + * pipe (via CLOEXEC) and the parent will know. + */ + error = execve(process->args[0], + process->args, + process->env); + + write_status(status[1], "execve", error, errno); + exit(0); + + /* Parent: make sure the child process exec'd correctly. */ + default: + /* Close the opposing side of the pipes */ + CLOSE_FD(status[1]); + + if (process->capture_in) { + CLOSE_FD(in[0]); + process->child_in = in[1]; + } + + if (process->capture_out) { + CLOSE_FD(out[1]); + process->child_out = out[0]; + } + + if (process->capture_err) { + CLOSE_FD(err[1]); + process->child_err = err[0]; + } + + /* Try to read the status */ + process->status = status[0]; + if ((error = read_status(status[0])) < 0) { + waitpid(process->pid, &state, 0); + goto on_error; + } + + process->pid = pid; + return 0; + } + +on_error: + CLOSE_FD(in[0]); CLOSE_FD(in[1]); + CLOSE_FD(out[0]); CLOSE_FD(out[1]); + CLOSE_FD(err[0]); CLOSE_FD(err[1]); + CLOSE_FD(status[0]); CLOSE_FD(status[1]); + return -1; +} + +int git_process_id(p_pid_t *out, git_process *process) +{ + GIT_ASSERT(out && process); + + if (!process->pid) { + git_error_set(GIT_ERROR_INVALID, "process not running"); + return -1; + } + + *out = process->pid; + return 0; +} + +static ssize_t process_read(int fd, void *buf, size_t count) +{ + ssize_t ret; + + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if ((ret = read(fd, buf, count)) < 0) { + git_error_set(GIT_ERROR_OS, "could not read from child process"); + return -1; + } + + return ret; +} + +ssize_t git_process_read(git_process *process, void *buf, size_t count) +{ + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_out); + + return process_read(process->child_out, buf, count); +} + +ssize_t git_process_read_err(git_process *process, void *buf, size_t count) +{ + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_err); + + return process_read(process->child_err, buf, count); +} + +#ifdef GIT_THREADS + +# define signal_state sigset_t + +/* + * Since signal-handling is process-wide, we cannot simply use + * SIG_IGN to avoid SIGPIPE. Instead: http://www.microhowto.info:80/howto/ignore_sigpipe_without_affecting_other_threads_in_a_process.html + */ + +GIT_INLINE(int) disable_signals(sigset_t *saved_mask) +{ + sigset_t sigpipe_mask; + + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, saved_mask) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal mask"); + return -1; + } + + return 0; +} + +GIT_INLINE(int) restore_signals(sigset_t *saved_mask) +{ + sigset_t sigpipe_mask, pending; + int signal; + + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (sigpending(&pending) < 0) { + git_error_set(GIT_ERROR_OS, "could not examine pending signals"); + return -1; + } + + if (sigismember(&pending, SIGPIPE) == 1 && + sigwait(&sigpipe_mask, &signal) < 0) { + git_error_set(GIT_ERROR_OS, "could not wait for (blocking) signal delivery"); + return -1; + } + + if (pthread_sigmask(SIG_SETMASK, saved_mask, 0) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal mask"); + return -1; + } + + return 0; +} + +#else + +# define signal_state struct sigaction + +GIT_INLINE(int) disable_signals(struct sigaction *saved_handler) +{ + struct sigaction ign_handler = { 0 }; + + ign_handler.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &ign_handler, saved_handler) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal handler"); + return -1; + } + + return 0; +} + +GIT_INLINE(int) restore_signals(struct sigaction *saved_handler) +{ + if (sigaction(SIGPIPE, saved_handler, NULL) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal handler"); + return -1; + } + + return 0; +} + +#endif + +ssize_t git_process_write(git_process *process, const void *buf, size_t count) +{ + signal_state saved_signal; + ssize_t ret; + + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_in); + + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (disable_signals(&saved_signal) < 0) + return -1; + + if ((ret = write(process->child_in, buf, count)) < 0) + git_error_set(GIT_ERROR_OS, "could not write to child process"); + + if (restore_signals(&saved_signal) < 0) + return -1; + + return (ret < 0) ? -1 : ret; +} + +int git_process_close_in(git_process *process) +{ + if (!process->capture_in) { + git_error_set(GIT_ERROR_INVALID, "input is not open"); + return -1; + } + + CLOSE_FD(process->child_in); + return 0; +} + +int git_process_close_out(git_process *process) +{ + if (!process->capture_out) { + git_error_set(GIT_ERROR_INVALID, "output is not open"); + return -1; + } + + CLOSE_FD(process->child_out); + return 0; +} + +int git_process_close_err(git_process *process) +{ + if (!process->capture_err) { + git_error_set(GIT_ERROR_INVALID, "error is not open"); + return -1; + } + + CLOSE_FD(process->child_err); + return 0; +} + +int git_process_close(git_process *process) +{ + CLOSE_FD(process->child_in); + CLOSE_FD(process->child_out); + CLOSE_FD(process->child_err); + + return 0; +} + +int git_process_wait(git_process_result *result, git_process *process) +{ + int state; + + if (result) + memset(result, 0, sizeof(git_process_result)); + + if (!process->pid) { + git_error_set(GIT_ERROR_INVALID, "process is stopped"); + return -1; + } + + if (waitpid(process->pid, &state, 0) < 0) { + git_error_set(GIT_ERROR_OS, "could not wait for child"); + return -1; + } + + process->pid = 0; + + if (result) { + if (WIFEXITED(state)) { + result->status = GIT_PROCESS_STATUS_NORMAL; + result->exitcode = WEXITSTATUS(state); + } else if (WIFSIGNALED(state)) { + result->status = GIT_PROCESS_STATUS_ERROR; + result->signal = WTERMSIG(state); + } else { + result->status = GIT_PROCESS_STATUS_ERROR; + } + } + + return 0; +} + +int git_process_result_msg(git_str *out, git_process_result *result) +{ + if (result->status == GIT_PROCESS_STATUS_NONE) { + return git_str_puts(out, "process not started"); + } else if (result->status == GIT_PROCESS_STATUS_NORMAL) { + return git_str_printf(out, "process exited with code %d", + result->exitcode); + } else if (result->signal) { + return git_str_printf(out, "process exited on signal %d", + result->signal); + } + + return git_str_puts(out, "unknown error"); +} + +void git_process_free(git_process *process) +{ + if (!process) + return; + + if (process->pid) + git_process_close(process); + + git__free(process->cwd); + git_strlist_free_with_null(process->args); + git_strlist_free_with_null(process->env); + git__free(process); +} diff --git a/src/util/util.c b/src/util/util.c index c8e8303..e86bcee 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -623,12 +623,12 @@ int git__bsearch_r( */ int git__strcmp_cb(const void *a, const void *b) { - return strcmp((const char *)a, (const char *)b); + return git__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); + return git__strcasecmp((const char *)a, (const char *)b); } int git__parse_bool(int *out, const char *value) diff --git a/src/util/util.h b/src/util/util.h index 7f178b1..2ed0051 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -83,15 +83,6 @@ 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) @@ -249,26 +240,6 @@ 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'); @@ -279,11 +250,6 @@ 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. * diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c index 3fec469..ace2320 100644 --- a/src/util/win32/posix_w32.c +++ b/src/util/win32/posix_w32.c @@ -787,13 +787,19 @@ int p_rmdir(const char *path) char *p_realpath(const char *orig_path, char *buffer) { git_win32_path orig_path_w, buffer_w; + DWORD long_len; 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 + /* + * POSIX realpath performs two functions: first, it turns relative + * paths into absolute paths. For this, we need GetFullPathName. + * + * 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. */ + * 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; @@ -803,9 +809,26 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; } - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; + /* + * Then, the path is canonicalized. eg, on macOS, + * "/TMP" -> "/private/tmp". For this, we need GetLongPathName. + */ + if ((long_len = GetLongPathNameW(buffer_w, buffer_w, GIT_WIN_PATH_UTF16)) == 0) { + DWORD error = GetLastError(); + + if (error == ERROR_FILE_NOT_FOUND || + error == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (error == ERROR_ACCESS_DENIED) + errno = EPERM; + else + errno = EINVAL; + + return NULL; + } + + if (long_len > GIT_WIN_PATH_UTF16) { + errno = ENAMETOOLONG; return NULL; } @@ -821,7 +844,6 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; git_fs_path_mkposix(buffer); - return buffer; } diff --git a/src/util/win32/process.c b/src/util/win32/process.c new file mode 100644 index 0000000..bb52245 --- /dev/null +++ b/src/util/win32/process.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> +#include <git2.h> + +#include "git2_util.h" +#include "process.h" +#include "strlist.h" + +#ifndef DWORD_MAX +# define DWORD_MAX INT32_MAX +#endif + +#define ENV_MAX 32767 + +struct git_process { + wchar_t *appname; + wchar_t *cmdline; + wchar_t *env; + + wchar_t *cwd; + + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1; + + PROCESS_INFORMATION process_info; + + HANDLE child_in; + HANDLE child_out; + HANDLE child_err; + + git_process_result_status status; +}; + +/* + * Windows processes have a single command-line that is split by the + * invoked application into arguments (instead of an array of + * command-line arguments). This command-line is split by space or + * tab delimiters, unless that whitespace is within a double quote. + * Literal double-quotes themselves can be escaped by a backslash, + * but only when not within double quotes. Literal backslashes can + * be escaped by a backslash. + * + * Effectively, this means that instead of thinking about quoting + * individual strings, think about double quotes as an escaping + * mechanism for whitespace. + * + * In other words (using ` as a string boundary): + * [ `foo`, `bar` ] => `foo bar` + * [ `foo bar` ] => `foo" "bar` + * [ `foo bar`, `foo bar` ] => `foo" "bar foo" "bar` + * [ `foo "bar" foo` ] => `foo" "\"bar\"" "foo` + */ +int git_process__cmdline( + git_str *out, + const char **in, + size_t in_len) +{ + bool quoted = false; + const char *c; + size_t i; + + for (i = 0; i < in_len; i++) { + /* Arguments are delimited by an unquoted space */ + if (i) + git_str_putc(out, ' '); + + for (c = in[i]; *c; c++) { + /* Start or stop quoting spaces within an argument */ + if ((*c == ' ' || *c == '\t') && !quoted) { + git_str_putc(out, '"'); + quoted = true; + } else if (*c != ' ' && *c != '\t' && quoted) { + git_str_putc(out, '"'); + quoted = false; + } + + /* Escape double-quotes and backslashes */ + if (*c == '"' || *c == '\\') + git_str_putc(out, '\\'); + + git_str_putc(out, *c); + } + } + + return git_str_oom(out) ? -1 : 0; +} + +GIT_INLINE(bool) is_delete_env(const char *env) +{ + char *c = strchr(env, '='); + + if (c == NULL) + return false; + + return *(c+1) == '\0'; +} + +static int merge_env(wchar_t **out, const char **in, size_t in_len, bool exclude_env) +{ + git_str merged = GIT_STR_INIT; + wchar_t *in16 = NULL, *env = NULL, *e; + char *e8 = NULL; + size_t e_len; + int ret = 0; + size_t i; + + *out = NULL; + + in16 = git__malloc(ENV_MAX * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(in16); + + e8 = git__malloc(ENV_MAX); + GIT_ERROR_CHECK_ALLOC(e8); + + for (i = 0; in && i < in_len; i++) { + if (is_delete_env(in[i])) + continue; + + if ((ret = git_utf8_to_16(in16, ENV_MAX, in[i])) < 0) + goto done; + + git_str_put(&merged, (const char *)in16, ret * 2); + git_str_put(&merged, "\0\0", 2); + } + + if (!exclude_env) { + env = GetEnvironmentStringsW(); + + for (e = env; *e; e += (e_len + 1)) { + e_len = wcslen(e); + + if ((ret = git_utf8_from_16(e8, ENV_MAX, e)) < 0) + goto done; + + if (git_strlist_contains_key(in, in_len, e8, '=')) + continue; + + git_str_put(&merged, (const char *)e, e_len * 2); + git_str_put(&merged, "\0\0", 2); + } + } + + git_str_put(&merged, "\0\0", 2); + + *out = (wchar_t *)git_str_detach(&merged); + +done: + if (env) + FreeEnvironmentStringsW(env); + + git_str_dispose(&merged); + git__free(e8); + git__free(in16); + + return ret < 0 ? -1 : 0; +} + +static int process_new( + git_process **out, + const char *appname, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process *process; + int error = 0; + + *out = NULL; + + process = git__calloc(1, sizeof(git_process)); + GIT_ERROR_CHECK_ALLOC(process); + + if (appname && + git_utf8_to_16_alloc(&process->appname, appname) < 0) { + error = -1; + goto done; + } + + if (git_utf8_to_16_alloc(&process->cmdline, cmdline) < 0) { + error = -1; + goto done; + } + + if (opts && opts->cwd && + git_utf8_to_16_alloc(&process->cwd, opts->cwd) < 0) { + error = -1; + goto done; + } + + if (env && (error = merge_env(&process->env, env, env_len, opts && opts->exclude_env) < 0)) + goto done; + + if (opts) { + process->capture_in = opts->capture_in; + process->capture_out = opts->capture_out; + process->capture_err = opts->capture_err; + } + +done: + if (error) + git_process_free(process); + else + *out = process; + + return error; +} + +int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + GIT_ASSERT_ARG(out && cmdline); + + return process_new(out, NULL, cmdline, env, env_len, opts); +} + +int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_str cmdline = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out && args && args_len > 0); + + if ((error = git_process__cmdline(&cmdline, args, args_len)) < 0) + goto done; + + error = process_new(out, args[0], cmdline.ptr, env, env_len, opts); + +done: + git_str_dispose(&cmdline); + return error; +} + +#define CLOSE_HANDLE(h) do { if ((h) != NULL) CloseHandle(h); } while(0) + +int git_process_start(git_process *process) +{ + STARTUPINFOW startup_info; + SECURITY_ATTRIBUTES security_attrs; + DWORD flags = CREATE_UNICODE_ENVIRONMENT; + HANDLE in[2] = { NULL, NULL }, + out[2] = { NULL, NULL }, + err[2] = { NULL, NULL }; + + memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES)); + security_attrs.bInheritHandle = TRUE; + + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + if (process->capture_in) { + if (!CreatePipe(&in[0], &in[1], &security_attrs, 0) || + !SetHandleInformation(in[1], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdInput = in[0]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + if (process->capture_out) { + if (!CreatePipe(&out[0], &out[1], &security_attrs, 0) || + !SetHandleInformation(out[0], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdOutput = out[1]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + if (process->capture_err) { + if (!CreatePipe(&err[0], &err[1], &security_attrs, 0) || + !SetHandleInformation(err[0], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdError = err[1]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION)); + + if (!CreateProcessW(process->appname, process->cmdline, + NULL, NULL, TRUE, flags, process->env, + process->cwd, + &startup_info, + &process->process_info)) { + git_error_set(GIT_ERROR_OS, "could not create process"); + goto on_error; + } + + CLOSE_HANDLE(in[0]); process->child_in = in[1]; + CLOSE_HANDLE(out[1]); process->child_out = out[0]; + CLOSE_HANDLE(err[1]); process->child_err = err[0]; + + return 0; + +on_error: + CLOSE_HANDLE(in[0]); CLOSE_HANDLE(in[1]); + CLOSE_HANDLE(out[0]); CLOSE_HANDLE(out[1]); + CLOSE_HANDLE(err[0]); CLOSE_HANDLE(err[1]); + return -1; +} + +int git_process_id(p_pid_t *out, git_process *process) +{ + GIT_ASSERT(out && process); + + if (!process->process_info.dwProcessId) { + git_error_set(GIT_ERROR_INVALID, "process not running"); + return -1; + } + + *out = process->process_info.dwProcessId; + return 0; +} + +ssize_t git_process_read(git_process *process, void *buf, size_t count) +{ + DWORD ret; + + if (count > DWORD_MAX) + count = DWORD_MAX; + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (!ReadFile(process->child_out, buf, (DWORD)count, &ret, NULL)) { + if (GetLastError() == ERROR_BROKEN_PIPE) + return 0; + + git_error_set(GIT_ERROR_OS, "could not read"); + return -1; + } + + return ret; +} + +ssize_t git_process_write(git_process *process, const void *buf, size_t count) +{ + DWORD ret; + + if (count > DWORD_MAX) + count = DWORD_MAX; + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (!WriteFile(process->child_in, buf, (DWORD)count, &ret, NULL)) { + git_error_set(GIT_ERROR_OS, "could not write"); + return -1; + } + + return ret; +} + +int git_process_close_in(git_process *process) +{ + if (!process->capture_in) { + git_error_set(GIT_ERROR_INVALID, "input is not open"); + return -1; + } + + if (process->child_in) { + CloseHandle(process->child_in); + process->child_in = NULL; + } + + return 0; +} + +int git_process_close_out(git_process *process) +{ + if (!process->capture_out) { + git_error_set(GIT_ERROR_INVALID, "output is not open"); + return -1; + } + + if (process->child_out) { + CloseHandle(process->child_out); + process->child_out = NULL; + } + + return 0; +} + +int git_process_close_err(git_process *process) +{ + if (!process->capture_err) { + git_error_set(GIT_ERROR_INVALID, "error is not open"); + return -1; + } + + if (process->child_err) { + CloseHandle(process->child_err); + process->child_err = NULL; + } + + return 0; +} + +int git_process_close(git_process *process) +{ + if (process->child_in) { + CloseHandle(process->child_in); + process->child_in = NULL; + } + + if (process->child_out) { + CloseHandle(process->child_out); + process->child_out = NULL; + } + + if (process->child_err) { + CloseHandle(process->child_err); + process->child_err = NULL; + } + + CloseHandle(process->process_info.hProcess); + process->process_info.hProcess = NULL; + + CloseHandle(process->process_info.hThread); + process->process_info.hThread = NULL; + + return 0; +} + +int git_process_wait(git_process_result *result, git_process *process) +{ + DWORD exitcode; + + if (result) + memset(result, 0, sizeof(git_process_result)); + + if (!process->process_info.dwProcessId) { + git_error_set(GIT_ERROR_INVALID, "process is stopped"); + return -1; + } + + if (WaitForSingleObject(process->process_info.hProcess, INFINITE) == WAIT_FAILED) { + git_error_set(GIT_ERROR_OS, "could not wait for process"); + return -1; + } + + if (!GetExitCodeProcess(process->process_info.hProcess, &exitcode)) { + git_error_set(GIT_ERROR_OS, "could not get process exit code"); + return -1; + } + + result->status = GIT_PROCESS_STATUS_NORMAL; + result->exitcode = exitcode; + + memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION)); + return 0; +} + +int git_process_result_msg(git_str *out, git_process_result *result) +{ + if (result->status == GIT_PROCESS_STATUS_NONE) { + return git_str_puts(out, "process not started"); + } else if (result->status == GIT_PROCESS_STATUS_NORMAL) { + return git_str_printf(out, "process exited with code %d", + result->exitcode); + } else if (result->signal) { + return git_str_printf(out, "process exited on signal %d", + result->signal); + } + + return git_str_puts(out, "unknown error"); +} + +void git_process_free(git_process *process) +{ + if (!process) + return; + + if (process->process_info.hProcess) + git_process_close(process); + + git__free(process->env); + git__free(process->cwd); + git__free(process->cmdline); + git__free(process->appname); + git__free(process); +} diff --git a/tests/benchmarks/benchmark.sh b/tests/benchmarks/benchmark.sh index 4a89807..4cb2b2f 100755 --- a/tests/benchmarks/benchmark.sh +++ b/tests/benchmarks/benchmark.sh @@ -6,9 +6,10 @@ set -eo pipefail # parse the command line # -usage() { echo "usage: $(basename "$0") [--cli <path>] [--baseline-cli <path>] [--suite <suite>] [--json <path>] [--zip <path>] [--verbose] [--debug]"; } +usage() { echo "usage: $(basename "$0") [--cli <path>] [--name <cli-name>] [--baseline-cli <path>] [--suite <suite>] [--json <path>] [--zip <path>] [--verbose] [--debug]"; } TEST_CLI="git" +TEST_CLI_NAME= BASELINE_CLI= SUITE= JSON_RESULT= @@ -22,6 +23,9 @@ for a in "$@"; do if [ "${NEXT}" = "cli" ]; then TEST_CLI="${a}" NEXT= + elif [ "${NEXT}" = "name" ]; then + TEST_CLI_NAME="${a}" + NEXT= elif [ "${NEXT}" = "baseline-cli" ]; then BASELINE_CLI="${a}" NEXT= @@ -41,6 +45,10 @@ for a in "$@"; do NEXT="cli" elif [[ "${a}" == "-c"* ]]; then TEST_CLI="${a/-c/}" + elif [ "${a}" = "n" ] || [ "${a}" = "--name" ]; then + NEXT="name" + elif [[ "${a}" == "-n"* ]]; then + TEST_CLI_NAME="${a/-n/}" elif [ "${a}" = "b" ] || [ "${a}" = "--baseline-cli" ]; then NEXT="baseline-cli" elif [[ "${a}" == "-b"* ]]; then diff --git a/tests/clar/clar.c b/tests/clar/clar.c index c9c3fde..9695dc9 100644 --- a/tests/clar/clar.c +++ b/tests/clar/clar.c @@ -41,9 +41,6 @@ # ifndef strdup # define strdup(str) _strdup(str) # endif -# ifndef strcasecmp -# define strcasecmp(a,b) _stricmp(a,b) -# endif # ifndef __MINGW32__ # pragma comment(lib, "shell32") @@ -94,8 +91,10 @@ static void fs_rm(const char *_source); static void fs_copy(const char *_source, const char *dest); +#ifdef CLAR_FIXTURE_PATH static const char * fixture_path(const char *base, const char *fixture_name); +#endif struct clar_error { const char *file; diff --git a/tests/clar/clar/fixtures.h b/tests/clar/clar/fixtures.h index 77033d3..6ec6423 100644 --- a/tests/clar/clar/fixtures.h +++ b/tests/clar/clar/fixtures.h @@ -1,3 +1,4 @@ +#ifdef CLAR_FIXTURE_PATH static const char * fixture_path(const char *base, const char *fixture_name) { @@ -20,7 +21,6 @@ fixture_path(const char *base, const char *fixture_name) return _path; } -#ifdef CLAR_FIXTURE_PATH const char *cl_fixture(const char *fixture_name) { return fixture_path(CLAR_FIXTURE_PATH, fixture_name); diff --git a/tests/clar/clar/fs.h b/tests/clar/clar/fs.h index 44ede45..a6eda5e 100644 --- a/tests/clar/clar/fs.h +++ b/tests/clar/clar/fs.h @@ -295,7 +295,9 @@ fs_copy(const char *_source, const char *_dest) void cl_fs_cleanup(void) { +#ifdef CLAR_FIXTURE_PATH fs_rm(fixture_path(_clar_path, "*")); +#endif } #else diff --git a/tests/clar/clar/sandbox.h b/tests/clar/clar/sandbox.h index 0ba1479..0688374 100644 --- a/tests/clar/clar/sandbox.h +++ b/tests/clar/clar/sandbox.h @@ -2,7 +2,8 @@ #include <sys/syslimits.h> #endif -static char _clar_path[4096 + 1]; +#define CLAR_PATH_MAX 4096 +static char _clar_path[CLAR_PATH_MAX]; static int is_valid_tmp_path(const char *path) @@ -35,10 +36,9 @@ find_tmp_path(char *buffer, size_t length) continue; if (is_valid_tmp_path(env)) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath(env, buffer) != NULL) - return 0; -#endif + if (strlen(env) + 1 > CLAR_PATH_MAX) + return -1; + strncpy(buffer, env, length - 1); buffer[length - 1] = '\0'; return 0; @@ -47,10 +47,6 @@ find_tmp_path(char *buffer, size_t length) /* If the environment doesn't say anything, try to use /tmp */ if (is_valid_tmp_path("/tmp")) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) - return 0; -#endif strncpy(buffer, "/tmp", length - 1); buffer[length - 1] = '\0'; return 0; @@ -75,6 +71,34 @@ find_tmp_path(char *buffer, size_t length) return -1; } +static int canonicalize_tmp_path(char *buffer) +{ +#ifdef _WIN32 + char tmp[CLAR_PATH_MAX]; + DWORD ret; + + ret = GetFullPathName(buffer, CLAR_PATH_MAX, tmp, NULL); + + if (ret == 0 || ret > CLAR_PATH_MAX) + return -1; + + ret = GetLongPathName(tmp, buffer, CLAR_PATH_MAX); + + if (ret == 0 || ret > CLAR_PATH_MAX) + return -1; + + return 0; +#else + char tmp[CLAR_PATH_MAX]; + + if (realpath(buffer, tmp) == NULL) + return -1; + + strcpy(buffer, tmp); + return 0; +#endif +} + static void clar_unsandbox(void) { if (_clar_path[0] == '\0') @@ -95,7 +119,8 @@ static int build_sandbox_path(void) size_t len; - if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0 || + canonicalize_tmp_path(_clar_path) < 0) return -1; len = strlen(_clar_path); diff --git a/tests/clar/clar_libgit2.h b/tests/clar/clar_libgit2.h index c33b5d2..d8105c8 100644 --- a/tests/clar/clar_libgit2.h +++ b/tests/clar/clar_libgit2.h @@ -166,10 +166,27 @@ GIT_INLINE(void) clar__assert_equal_oid( } } +GIT_INLINE(void) clar__assert_equal_oidstr( + const char *file, const char *func, int line, const char *desc, + const char *one_str, const git_oid *two) +{ + git_oid one; + + if (git_oid__fromstr(&one, one_str, git_oid_type(two)) < 0) { + clar__fail(file, func, line, desc, "could not parse oid string", 1); + } else { + clar__assert_equal_oid(file, func, line, desc, &one, two); + } +} + #define cl_assert_equal_oid(one, two) \ clar__assert_equal_oid(__FILE__, __func__, __LINE__, \ "OID mismatch: " #one " != " #two, (one), (two)) +#define cl_assert_equal_oidstr(one_str, two) \ + clar__assert_equal_oidstr(__FILE__, __func__, __LINE__, \ + "OID mismatch: " #one_str " != " #two, (one_str), (two)) + /* * Some utility macros for building long strings */ diff --git a/tests/libgit2/attr/repo.c b/tests/libgit2/attr/repo.c index abd2381..747715b 100644 --- a/tests/libgit2/attr/repo.c +++ b/tests/libgit2/attr/repo.c @@ -309,6 +309,31 @@ void test_attr_repo__bare_repo_with_index(void) cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); } +void test_attr_repo__inmemory_repo_without_index(void) +{ + const char *names[1] = { "fake" }; + const char *values[1]; + git_repository *inmemory; + git_index *index = NULL; + + /* setup bare in-memory repo without index */ +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&inmemory, GIT_OID_SHA1)); +#else + cl_git_pass(git_repository_new(&inmemory)); +#endif + cl_assert(git_repository_is_bare(inmemory)); + + /* verify repo isn't given an index upfront in future */ + git_repository_index(&index, inmemory); + cl_assert(!index); + + /* check attributes can be queried without error due to missing index */ + cl_git_pass(git_attr_get_many(values, inmemory, 0, "fake.txt", 1, names)); + + git_repository_free(inmemory); +} + void test_attr_repo__sysdir(void) { git_str sysdir = GIT_STR_INIT; diff --git a/tests/libgit2/blame/buffer.c b/tests/libgit2/blame/buffer.c index 06d5042..456402c 100644 --- a/tests/libgit2/blame/buffer.c +++ b/tests/libgit2/blame/buffer.c @@ -17,15 +17,204 @@ void test_blame_buffer__cleanup(void) git_repository_free(g_repo); } + +void test_blame_buffer__4_edits(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +x\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 2, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 3, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 4, 1, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 5, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 6, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 7, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 8, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 7, 9, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 8, 10, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 9, 11, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__two_added_lines_and_one_modified(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +x\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +x\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 4, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 5, 1, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 6, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 7, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 8, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 9, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 7, 12, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__two_added_lines(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +abc\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +def\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 4, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 5, 1, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 6, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 7, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 12, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__added_blocks(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 4, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 13, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 16, 5, 0, "aa06ecca", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 21, 3, 0, "000000", "b.txt"); + + +} + +void test_blame_buffer__overlapping_blocks(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 4, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 7, 4, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 11, 5, 0, "aa06ecca", "b.txt"); + +} + +void test_blame_buffer__2_add_splits_hunk(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +abc\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abc\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 2, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 3, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 4, 2, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 6, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 7, 2, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 9, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 10, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 7, 13, 5, 0, "aa06ecca", "b.txt"); +} + void test_blame_buffer__index(void) { const git_blame_hunk *hunk; const char *buffer = "Hello\nWorld!"; - /* - * We need to open a different file from the ones used in other tests. Close - * the one opened in test_blame_buffer__initialize() to avoid a leak. - */ git_blame_free(g_fileblame); g_fileblame = NULL; cl_git_pass(git_blame_file(&g_fileblame, g_repo, "file.txt", NULL)); @@ -43,6 +232,8 @@ void test_blame_buffer__index(void) cl_assert(hunk->final_signature == NULL); } + + void test_blame_buffer__added_line(void) { const git_blame_hunk *hunk; @@ -73,6 +264,43 @@ CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC cl_assert_equal_s("Ben Straub", hunk->final_signature->name); } +void test_blame_buffer__added_lines(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +\n\ +\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +\n\ +\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +\n\ +\n\ +\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + cl_assert_equal_i(7, git_blame_get_hunk_count(g_bufferblame)); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 14, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 22, 3, 0, "000000", "b.txt"); + +} + void test_blame_buffer__deleted_line(void) { const char *buffer = "\ diff --git a/tests/libgit2/checkout/tree.c b/tests/libgit2/checkout/tree.c index 65df00c..97935aa 100644 --- a/tests/libgit2/checkout/tree.c +++ b/tests/libgit2/checkout/tree.c @@ -1235,7 +1235,7 @@ void test_checkout_tree__case_changing_rename(void) cl_git_pass(git_signature_new(&signature, "Renamer", "rename@contoso.com", time(NULL), 0)); - cl_git_pass(git_commit_create(&commit_id, g_repo, "refs/heads/dir", signature, signature, NULL, "case-changing rename", tree, 1, (const git_commit **)&dir_commit)); + cl_git_pass(git_commit_create(&commit_id, g_repo, "refs/heads/dir", signature, signature, NULL, "case-changing rename", tree, 1, &dir_commit)); cl_assert(git_fs_path_isfile("testrepo/readme")); if (case_sensitive) diff --git a/tests/libgit2/cherrypick/workdir.c b/tests/libgit2/cherrypick/workdir.c index c16b781..9d9a3f4 100644 --- a/tests/libgit2/cherrypick/workdir.c +++ b/tests/libgit2/cherrypick/workdir.c @@ -77,7 +77,7 @@ void test_cherrypick_workdir__automerge(void) cl_git_pass(git_index_write_tree(&cherrypicked_tree_oid, repo_index)); cl_git_pass(git_tree_lookup(&cherrypicked_tree, repo, &cherrypicked_tree_oid)); cl_git_pass(git_commit_create(&cherrypicked_oid, repo, "HEAD", signature, signature, NULL, - "Cherry picked!", cherrypicked_tree, 1, (const git_commit **)&head)); + "Cherry picked!", cherrypicked_tree, 1, &head)); cl_assert(merge_test_index(repo_index, merge_index_entries + i * 3, 3)); diff --git a/tests/libgit2/clone/local.c b/tests/libgit2/clone/local.c index e0bd74d..d35fe86 100644 --- a/tests/libgit2/clone/local.c +++ b/tests/libgit2/clone/local.c @@ -210,3 +210,13 @@ void test_clone_local__git_style_unc_paths(void) cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); #endif } + +void test_clone_local__shallow_fails(void) +{ + git_repository *repo; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + opts.fetch_opts.depth = 4; + + cl_git_fail_with(GIT_ENOTSUPPORTED, git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts)); +} diff --git a/tests/libgit2/commit/commit.c b/tests/libgit2/commit/commit.c index 140f87d..923740f 100644 --- a/tests/libgit2/commit/commit.c +++ b/tests/libgit2/commit/commit.c @@ -36,11 +36,11 @@ void test_commit_commit__create_unexisting_update_ref(void) cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, - NULL, "some msg", tree, 1, (const git_commit **) &commit)); + NULL, "some msg", tree, 1, &commit)); /* fail because the parent isn't the tip of the branch anymore */ cl_git_fail(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, - NULL, "some msg", tree, 1, (const git_commit **) &commit)); + NULL, "some msg", tree, 1, &commit)); cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); cl_assert_equal_oid(&oid, git_reference_target(ref)); diff --git a/tests/libgit2/commit/create.c b/tests/libgit2/commit/create.c new file mode 100644 index 0000000..9f627dc --- /dev/null +++ b/tests/libgit2/commit/create.c @@ -0,0 +1,112 @@ +#include "clar_libgit2.h" +#include "repository.h" + +/* Fixture setup */ +static git_repository *g_repo; +static git_signature *g_author, *g_committer; + +void test_commit_create__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo2"); + cl_git_pass(git_signature_new(&g_author, "Edward Thomson", "ethomson@edwardthomson.com", 123456789, 60)); + cl_git_pass(git_signature_new(&g_committer, "libgit2 user", "nobody@noreply.libgit2.org", 987654321, 90)); +} + +void test_commit_create__cleanup(void) +{ + git_signature_free(g_committer); + git_signature_free(g_author); + cl_git_sandbox_cleanup(); +} + + +void test_commit_create__from_stage_simple(void) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_index *index; + git_oid commit_id; + git_tree *tree; + + opts.author = g_author; + opts.committer = g_committer; + + cl_git_rewritefile("testrepo2/newfile.txt", "This is a new file.\n"); + cl_git_rewritefile("testrepo2/newfile2.txt", "This is a new file.\n"); + cl_git_rewritefile("testrepo2/README", "hello, world.\n"); + cl_git_rewritefile("testrepo2/new.txt", "hi there.\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "newfile2.txt")); + cl_git_pass(git_index_add_bypath(index, "README")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "This is the message.", &opts)); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + cl_assert_equal_oidstr("241b5b04e847bc38dd7b4b9f49f21e55da40f3a6", &commit_id); + cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(tree)); + + git_index_free(index); + git_tree_free(tree); +} + +void test_commit_create__from_stage_nochanges(void) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_oid commit_id; + git_tree *tree; + + opts.author = g_author; + opts.committer = g_committer; + + cl_git_fail_with(GIT_EUNCHANGED, git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts)); + + opts.allow_empty_commit = 1; + + cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts)); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + cl_assert_equal_oidstr("f776dc4c7fd8164b7127dc8e4f9b44421cb01b56", &commit_id); + cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree)); + + git_tree_free(tree); +} + +void test_commit_create__from_stage_newrepo(void) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_repository *newrepo; + git_index *index; + git_commit *commit; + git_tree *tree; + git_oid commit_id; + + opts.author = g_author; + opts.committer = g_committer; + + git_repository_init(&newrepo, "newrepo", false); + cl_git_pass(git_repository_index(&index, newrepo)); + + cl_git_rewritefile("newrepo/hello.txt", "hello, world.\n"); + cl_git_rewritefile("newrepo/hi.txt", "hi there.\n"); + cl_git_rewritefile("newrepo/foo.txt", "bar.\n"); + + cl_git_pass(git_index_add_bypath(index, "hello.txt")); + cl_git_pass(git_index_add_bypath(index, "foo.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_commit_create_from_stage(&commit_id, newrepo, "Initial commit.", &opts)); + cl_git_pass(git_repository_head_commit(&commit, newrepo)); + cl_git_pass(git_repository_head_tree(&tree, newrepo)); + + cl_assert_equal_oid(&commit_id, git_commit_id(commit)); + cl_assert_equal_oidstr("b2fa96a4f191c76eb172437281c66aa29609dcaa", git_commit_tree_id(commit)); + + git_tree_free(tree); + git_commit_free(commit); + git_index_free(index); + git_repository_free(newrepo); + cl_fixture_cleanup("newrepo"); +} diff --git a/tests/libgit2/commit/signature.c b/tests/libgit2/commit/signature.c index a915514..fddd507 100644 --- a/tests/libgit2/commit/signature.c +++ b/tests/libgit2/commit/signature.c @@ -36,10 +36,17 @@ void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void) assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " \t nulltoken \n", " \n emeric.fermas@gmail.com \n"); } +void test_commit_signature__leading_and_trailing_dots_are_supported(void) +{ + assert_name_and_email(".nulltoken", ".emeric.fermas@gmail.com", ".nulltoken", ".emeric.fermas@gmail.com"); + assert_name_and_email("nulltoken.", "emeric.fermas@gmail.com.", "nulltoken.", "emeric.fermas@gmail.com."); + assert_name_and_email(".nulltoken.", ".emeric.fermas@gmail.com.", ".nulltoken.", ".emeric.fermas@gmail.com."); +} + void test_commit_signature__leading_and_trailing_crud_is_trimmed(void) { assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", "\"nulltoken\"", "\"emeric.fermas@gmail.com\""); - assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w.", "emeric.fermas@gmail.com"); + assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w;", "emeric.fermas@gmail.com"); assert_name_and_email("nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com", "nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com"); } diff --git a/tests/libgit2/commit/write.c b/tests/libgit2/commit/write.c index 890f738..d38b54d 100644 --- a/tests/libgit2/commit/write.c +++ b/tests/libgit2/commit/write.c @@ -118,7 +118,7 @@ void test_commit_write__into_buf(void) cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); cl_git_pass(git_commit_create_buffer(&commit, g_repo, author, committer, - NULL, root_commit_message, tree, 1, (const git_commit **) &parent)); + NULL, root_commit_message, tree, 1, &parent)); cl_assert_equal_s(commit.ptr, "tree 1810dff58d8a660512d4832e740f692884338ccd\n\ diff --git a/tests/libgit2/config/configlevel.c b/tests/libgit2/config/configlevel.c index 8422d32..0e31268 100644 --- a/tests/libgit2/config/configlevel.c +++ b/tests/libgit2/config/configlevel.c @@ -71,3 +71,44 @@ void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_ret git_config_free(cfg); } + +void test_config_configlevel__can_fetch_highest_level(void) +{ + git_config *cfg; + git_config *single_level_cfg; + git_buf buf = {0}; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_HIGHEST_LEVEL)); + + git_config_free(cfg); + + cl_git_pass(git_config_get_string_buf(&buf, single_level_cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(single_level_cfg); +} + +void test_config_configlevel__can_override_local_with_worktree(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_WORKTREE, NULL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(cfg); +} diff --git a/tests/libgit2/config/include.c b/tests/libgit2/config/include.c index 1b55fdc..ba8bcad 100644 --- a/tests/libgit2/config/include.c +++ b/tests/libgit2/config/include.c @@ -111,7 +111,7 @@ void test_config_include__missing(void) git_error_clear(); cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_assert(git_error_last() == NULL); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", buf.ptr); @@ -126,7 +126,7 @@ void test_config_include__missing_homedir(void) git_error_clear(); cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_assert(git_error_last() == NULL); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", buf.ptr); diff --git a/tests/libgit2/config/memory.c b/tests/libgit2/config/memory.c index ae66189..9f533e2 100644 --- a/tests/libgit2/config/memory.c +++ b/tests/libgit2/config/memory.c @@ -34,8 +34,13 @@ static int contains_all_cb(const git_config_entry *entry, void *payload) int i; for (i = 0; entries[i].name; i++) { - if (strcmp(entries[i].name, entry->name) || - strcmp(entries[i].value , entry->value)) + if (strcmp(entries[i].name, entry->name)) + continue; + + if ((entries[i].value == NULL) ^ (entry->value == NULL)) + continue; + + if (entry->value && strcmp(entries[i].value , entry->value)) continue; if (entries[i].seen) @@ -61,7 +66,23 @@ static void assert_config_contains_all(git_config_backend *backend, static void setup_backend(const char *cfg) { - cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg), &opts)); + cl_git_pass(git_config_backend_open(backend, 0, NULL)); +} + +static void setup_values_backend(const char **values, size_t len) +{ + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_values(&backend, values, len, &opts)); cl_git_pass(git_config_backend_open(backend, 0, NULL)); } @@ -88,7 +109,13 @@ void test_config_memory__malformed_fails_to_open(void) const char *cfg = "[general\n" "foo=bar\n"; - cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); + + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg), &opts)); cl_git_fail(git_config_backend_open(backend, 0, NULL)); } @@ -137,3 +164,43 @@ void test_config_memory__foreach_sees_multivar(void) "foo=bar2\n"); assert_config_contains_all(backend, entries); } + +void test_config_memory__values(void) +{ + const char *values[] = { + "general.foo=bar1", + "general.foo=bar2", + "other.key=value", + "empty.value=", + "no.value", + }; + + struct expected_entry entries[] = { + { "general.foo", "bar1", 0 }, + { "general.foo", "bar2", 0 }, + { "other.key", "value", 0 }, + { "empty.value", "", 0 }, + { "no.value", NULL, 0 }, + { NULL, NULL, 0 } + }; + + setup_values_backend(values, 5); + assert_config_contains_all(backend, entries); +} + +void test_config_memory__valid_values(void) +{ + const char *values[] = { + "general.foo=bar1", + "=bar2", + "other.key=value" + }; + + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_values(&backend, values, 3, &opts)); + cl_git_fail(git_config_backend_open(backend, 0, NULL)); +} diff --git a/tests/libgit2/config/multivar.c b/tests/libgit2/config/multivar.c index 244e375..3ed8460 100644 --- a/tests/libgit2/config/multivar.c +++ b/tests/libgit2/config/multivar.c @@ -1,4 +1,6 @@ #include "clar_libgit2.h" +#include "config.h" +#include "config/config_helpers.h" static const char *_name = "remote.ab.url"; @@ -286,3 +288,32 @@ void test_config_multivar__delete_notfound(void) git_config_free(cfg); } + +void test_config_multivar__rename_section(void) +{ + git_repository *repo; + git_config *cfg; + int n; + + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_config(&cfg, repo)); + + cl_git_pass(git_config_set_multivar(cfg, "branch.foo.name", "^$", "bar")); + cl_git_pass(git_config_set_multivar(cfg, "branch.foo.name", "^$", "xyzzy")); + n = 0; + cl_git_pass(git_config_get_multivar_foreach( + cfg, "branch.foo.name", NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass( + git_config_rename_section(repo, "branch.foo", "branch.foobar")); + + assert_config_entry_existence(repo, "branch.foo.name", false); + n = 0; + cl_git_pass(git_config_get_multivar_foreach( + cfg, "branch.foobar.name", NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); + cl_git_sandbox_cleanup(); +} diff --git a/tests/libgit2/config/read.c b/tests/libgit2/config/read.c index ac6459b..25e7b96 100644 --- a/tests/libgit2/config/read.c +++ b/tests/libgit2/config/read.c @@ -495,6 +495,8 @@ void test_config_read__read_git_config_entry(void) cl_assert_equal_s("core.dummy2", entry->name); cl_assert_equal_s("42", entry->value); cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s(cl_fixture("config/config9"), entry->origin_path); git_config_entry_free(entry); git_config_free(cfg); diff --git a/tests/libgit2/config/readonly.c b/tests/libgit2/config/readonly.c index a8901e3..483f83a 100644 --- a/tests/libgit2/config/readonly.c +++ b/tests/libgit2/config/readonly.c @@ -24,7 +24,7 @@ void test_config_readonly__writing_to_readonly_fails(void) backend->readonly = 1; cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_fail_with(GIT_ENOTFOUND, git_config_set_string(cfg, "foo.bar", "baz")); + cl_git_fail_with(GIT_EREADONLY, git_config_set_string(cfg, "foo.bar", "baz")); cl_assert(!git_fs_path_exists("global")); } diff --git a/tests/libgit2/config/snapshot.c b/tests/libgit2/config/snapshot.c index 5cc08a7..cc87706 100644 --- a/tests/libgit2/config/snapshot.c +++ b/tests/libgit2/config/snapshot.c @@ -79,6 +79,7 @@ void test_config_snapshot__multivar(void) void test_config_snapshot__includes(void) { + git_config_entry *entry; int i; cl_git_mkfile("including", "[include]\npath = included"); @@ -99,6 +100,16 @@ void test_config_snapshot__includes(void) cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); cl_assert_equal_i(i, 1); + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s("./included", entry->origin_path); + + git_config_entry_free(entry); + cl_git_pass(p_unlink("including")); cl_git_pass(p_unlink("included")); } @@ -106,6 +117,7 @@ void test_config_snapshot__includes(void) void test_config_snapshot__snapshot(void) { git_config *snapshot_snapshot; + git_config_entry *entry; int i; cl_git_mkfile("configfile", "[section]\nkey = 1\n"); @@ -118,22 +130,49 @@ void test_config_snapshot__snapshot(void) cl_git_pass(git_config_get_int32(&i, snapshot_snapshot, "section.key")); cl_assert_equal_i(i, 1); + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot_snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s("configfile", entry->origin_path); + + git_config_entry_free(entry); + git_config_free(snapshot_snapshot); cl_git_pass(p_unlink("configfile")); } -void test_config_snapshot__snapshot_from_in_memony(void) +void test_config_snapshot__snapshot_from_in_memory(void) { const char *configuration = "[section]\nkey = 1\n"; git_config_backend *backend; + git_config_entry *entry; int i; + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + opts.origin_path = "hello"; + cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration))); + cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration), &opts)); cl_git_pass(git_config_add_backend(cfg, backend, 0, NULL, 0)); cl_git_pass(git_config_snapshot(&snapshot, cfg)); cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); cl_assert_equal_i(i, 1); + + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("test", entry->backend_type); + cl_assert_equal_s("hello", entry->origin_path); + + git_config_entry_free(entry); } diff --git a/tests/libgit2/config/write.c b/tests/libgit2/config/write.c index 9d8c3fe..c71d4f6 100644 --- a/tests/libgit2/config/write.c +++ b/tests/libgit2/config/write.c @@ -696,6 +696,36 @@ void test_config_write__locking(void) git_config_free(cfg); } +void test_config_write__abort_lock(void) +{ + git_config *cfg; + git_config_entry *entry; + git_transaction *tx; + const char *filename = "locked-file"; + + /* Open the config and lock it */ + cl_git_mkfile(filename, "[section]\n\tname = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_lock(&tx, cfg)); + + /* Change entries in the locked backend */ + cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); + cl_git_pass(git_config_set_string(cfg, "section2.name3", "more value")); + + git_transaction_free(tx); + + /* Now that we've unlocked it, we should see no changes */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); + + git_config_free(cfg); +} + void test_config_write__repeated(void) { const char *filename = "config-repeated"; diff --git a/tests/libgit2/core/opts.c b/tests/libgit2/core/opts.c index 1aa095a..cbef29f 100644 --- a/tests/libgit2/core/opts.c +++ b/tests/libgit2/core/opts.c @@ -34,9 +34,10 @@ void test_core_opts__extensions_query(void) cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 2); + cl_assert_equal_sz(out.count, 3); cl_assert_equal_s("noop", out.strings[0]); cl_assert_equal_s("objectformat", out.strings[1]); + cl_assert_equal_s("worktreeconfig", out.strings[2]); git_strarray_dispose(&out); } @@ -49,10 +50,11 @@ void test_core_opts__extensions_add(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 3); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("foo", out.strings[0]); cl_assert_equal_s("noop", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -65,10 +67,11 @@ void test_core_opts__extensions_remove(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 3); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("baz", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -81,11 +84,12 @@ void test_core_opts__extensions_uniq(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 4); + cl_assert_equal_sz(out.count, 5); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("foo", out.strings[1]); cl_assert_equal_s("noop", out.strings[2]); cl_assert_equal_s("objectformat", out.strings[3]); + cl_assert_equal_s("worktreeconfig", out.strings[4]); git_strarray_dispose(&out); } diff --git a/tests/libgit2/core/useragent.c b/tests/libgit2/core/useragent.c index a4ece90..2e119de 100644 --- a/tests/libgit2/core/useragent.c +++ b/tests/libgit2/core/useragent.c @@ -1,17 +1,52 @@ #include "clar_libgit2.h" #include "settings.h" -void test_core_useragent__get(void) +static git_buf default_ua = GIT_BUF_INIT; +static git_buf default_product = GIT_BUF_INIT; + +void test_core_useragent__initialize(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &default_ua)); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT_PRODUCT, &default_product)); +} + +void test_core_useragent__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL); + git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL); + + git_buf_dispose(&default_ua); + git_buf_dispose(&default_product); +} + +void test_core_useragent__get_default(void) +{ + cl_assert(default_ua.size); + cl_assert(default_ua.ptr); + cl_assert(git__prefixcmp(default_ua.ptr, "libgit2 ") == 0); + + cl_assert(default_product.size); + cl_assert(default_product.ptr); + cl_assert(git__prefixcmp(default_product.ptr, "git/") == 0); +} + +void test_core_useragent__set(void) { - const char *custom_name = "super duper git"; - git_str buf = GIT_STR_INIT; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "foo bar 4.24")); + cl_assert_equal_s("foo bar 4.24", git_settings__user_agent()); + cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product()); - cl_assert_equal_p(NULL, git_libgit2__user_agent()); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); - cl_assert_equal_s(custom_name, git_libgit2__user_agent()); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "baz/2.2.3")); + cl_assert_equal_s("foo bar 4.24", git_settings__user_agent()); + cl_assert_equal_s("baz/2.2.3", git_settings__user_agent_product()); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); - cl_assert_equal_s(custom_name, buf.ptr); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "")); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "")); + cl_assert_equal_s("", git_settings__user_agent()); + cl_assert_equal_s("", git_settings__user_agent_product()); - git_str_dispose(&buf); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL)); + cl_assert_equal_s(default_ua.ptr, git_settings__user_agent()); + cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product()); } diff --git a/tests/libgit2/diff/parse.c b/tests/libgit2/diff/parse.c index 79745b9..59fc028 100644 --- a/tests/libgit2/diff/parse.c +++ b/tests/libgit2/diff/parse.c @@ -279,6 +279,31 @@ static int file_cb(const git_diff_delta *delta, float progress, void *payload) return 0; } +void test_diff_parse__eof_nl_missing(void) +{ + const char patch[] = + "diff --git a/.env b/.env\n" + "index f89e4c0..7c56eb7 100644\n" + "--- a/.env\n" + "+++ b/.env\n" + "@@ -1 +1 @@\n" + "-hello=12345\n" + "+hello=123456\n" + "\\ No newline at end of file\n"; + git_diff *diff; + git_patch *ret_patch; + git_diff_line *line; + + cl_git_pass(diff_from_buffer(&diff, patch, strlen(patch))); + cl_git_pass(git_patch_from_diff(&ret_patch, diff, 0)); + + cl_assert((line = git_array_get(ret_patch->lines, 2)) != NULL); + cl_assert(line->origin == GIT_DIFF_LINE_DEL_EOFNL); + + git_diff_free(diff); + git_patch_free(ret_patch); +} + void test_diff_parse__foreach_works_with_parsed_patch(void) { const char patch[] = diff --git a/tests/libgit2/diff/rename.c b/tests/libgit2/diff/rename.c index 9d44394..15dee5c 100644 --- a/tests/libgit2/diff/rename.c +++ b/tests/libgit2/diff/rename.c @@ -424,7 +424,7 @@ void test_diff_rename__test_small_files(void) cl_git_pass(git_index_write_tree(&oid, index)); cl_git_pass(git_tree_lookup(&commit_tree, g_repo, &oid)); cl_git_pass(git_signature_new(&signature, "Rename", "rename@example.com", 1404157834, 0)); - cl_git_pass(git_commit_create(&oid, g_repo, "HEAD", signature, signature, NULL, "Test commit", commit_tree, 1, (const git_commit**)&head_commit)); + cl_git_pass(git_commit_create(&oid, g_repo, "HEAD", signature, signature, NULL, "Test commit", commit_tree, 1, &head_commit)); cl_git_mkfile("renames/copy.txt", "Hello World!\n"); cl_git_rmfile("renames/small.txt"); @@ -1441,6 +1441,52 @@ void test_diff_rename__can_delete_unmodified_deltas(void) git_str_dispose(&c1); } +void test_diff_rename__can_delete_unmodified_deltas_including_submodule(void) +{ + git_repository *repo; /* "submodules" */ + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_sandbox_cleanup(); /* Don't use "renames" for this test */ + repo = cl_git_sandbox_init("submodules"); + + cl_repo_set_bool(repo, "core.autocrlf", false); + + cl_git_pass( + git_revparse_single((git_object **)&tree, repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read_tree(index, tree)); + + diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_REMOVE_UNMODIFIED; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(0, exp.files); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + cl_git_sandbox_cleanup(); +} + void test_diff_rename__matches_config_behavior(void) { const char *sha0 = INITIAL_COMMIT; diff --git a/tests/libgit2/diff/workdir.c b/tests/libgit2/diff/workdir.c index c433b75..504ece6 100644 --- a/tests/libgit2/diff/workdir.c +++ b/tests/libgit2/diff/workdir.c @@ -2286,42 +2286,81 @@ void test_diff_workdir__to_index_reversed_content_loads(void) diff_expects exp; int use_iterator; char *pathspec = "new_file"; - + g_repo = cl_git_sandbox_init("status"); - + opts.context_lines = 3; opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT | GIT_DIFF_REVERSE; opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); - + if (use_iterator) cl_git_pass(diff_foreach_via_iterator( diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); else cl_git_pass(git_diff_foreach( diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - + cl_assert_equal_i(1, exp.files); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - + cl_assert_equal_i(1, exp.hunks); - + cl_assert_equal_i(1, exp.lines); cl_assert_equal_i(0, exp.line_ctxt); cl_assert_equal_i(0, exp.line_adds); cl_assert_equal_i(1, exp.line_dels); } - + + git_diff_free(diff); +} + +void test_diff_workdir__completely_ignored_shows_empty_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + char *pathspec = "subdir.txt"; + + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + g_repo = cl_git_sandbox_init("status"); + cl_git_rewritefile("status/subdir.txt", "Is it a bird?\n\nIs it a plane?\n"); + + /* Perform the diff normally */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s("diff --git a/subdir.txt b/subdir.txt\nindex e8ee89e..53c8db5 100644\n--- a/subdir.txt\n+++ b/subdir.txt\n@@ -1,2 +1,3 @@\n Is it a bird?\n+\n Is it a plane?\n", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + /* Perform the diff ignoring blank lines */ + opts.flags |= GIT_DIFF_IGNORE_BLANK_LINES; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s("", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); git_diff_free(diff); } diff --git a/tests/libgit2/grafts/shallow.c b/tests/libgit2/grafts/shallow.c index 5911a26..289d1b1 100644 --- a/tests/libgit2/grafts/shallow.c +++ b/tests/libgit2/grafts/shallow.c @@ -40,7 +40,7 @@ void test_grafts_shallow__clears_errors(void) { g_repo = cl_git_sandbox_init("testrepo.git"); cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); } void test_grafts_shallow__shallow_oids(void) diff --git a/tests/libgit2/iterator/workdir.c b/tests/libgit2/iterator/workdir.c index 7634997..af47d8b 100644 --- a/tests/libgit2/iterator/workdir.c +++ b/tests/libgit2/iterator/workdir.c @@ -585,9 +585,9 @@ void test_iterator_workdir__filesystem(void) expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); git_iterator_free(i); - git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); + git__tsort((void **)expect_base, 8, git__strcasecmp_cb); + git__tsort((void **)expect_trees, 18, git__strcasecmp_cb); + git__tsort((void **)expect_noauto, 5, git__strcasecmp_cb); i_opts.flags = GIT_ITERATOR_IGNORE_CASE; cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); diff --git a/tests/libgit2/merge/trees/renames.c b/tests/libgit2/merge/trees/renames.c index a27945e..9507b51 100644 --- a/tests/libgit2/merge/trees/renames.c +++ b/tests/libgit2/merge/trees/renames.c @@ -350,3 +350,22 @@ void test_merge_trees_renames__cache_recomputation(void) git_tree_free(our_tree); git__free(data); } + +void test_merge_trees_renames__emptyfile_renames(void) +{ + git_index *index; + git_merge_options *opts = NULL; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 1, "bar" }, + { 0100644, "60b12be2d2f57977ce83d8dfd32e2394ac1ba1a2", 3, "bar" }, + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 0, "boo" }, + { 0100644, "e50a49f9558d09d4d3bfc108363bb24c127ed263", 0, "foo" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, + "emptyfile_renames", "emptyfile_renames-branch", + opts)); + cl_assert(merge_test_index(index, merge_index_entries, 4)); + git_index_free(index); +} diff --git a/tests/libgit2/message/trailer.c b/tests/libgit2/message/trailer.c index 919e10a..09e8f61 100644 --- a/tests/libgit2/message/trailer.c +++ b/tests/libgit2/message/trailer.c @@ -3,19 +3,22 @@ static void assert_trailers(const char *message, git_message_trailer *trailers) { git_message_trailer_array arr; - size_t i; + size_t i, count; int rc = git_message_trailers(&arr, message); cl_assert_equal_i(0, rc); - for(i=0; i<arr.count; i++) { + for (i = 0, count = 0; trailers[i].key != NULL; i++, count++) + ; + + cl_assert_equal_sz(arr.count, count); + + for (i=0; i < count; i++) { cl_assert_equal_s(arr.trailers[i].key, trailers[i].key); cl_assert_equal_s(arr.trailers[i].value, trailers[i].value); } - cl_assert_equal_i(0, rc); - git_message_trailer_array_free(&arr); } @@ -162,3 +165,23 @@ void test_message_trailer__invalid(void) "Another: trailer\n" , trailers); } + +void test_message_trailer__ignores_dashes(void) +{ + git_message_trailer trailers[] = { + { "Signed-off-by", "some@one.com" }, + { "Another", "trailer" }, + { NULL, NULL }, + }; + + assert_trailers( + "Message\n" + "\n" + "Markdown header\n" + "---------------\n" + "Lorem ipsum\n" + "\n" + "Signed-off-by: some@one.com\n" + "Another: trailer\n" + , trailers); +} diff --git a/tests/libgit2/network/remote/local.c b/tests/libgit2/network/remote/local.c index 2007f37..d70d0eb 100644 --- a/tests/libgit2/network/remote/local.c +++ b/tests/libgit2/network/remote/local.c @@ -473,7 +473,11 @@ void test_network_remote_local__anonymous_remote_inmemory_repo(void) git_str_sets(&file_path_buf, cl_git_path_url(cl_fixture("testrepo.git"))); +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&inmemory, GIT_OID_SHA1)); +#else cl_git_pass(git_repository_new(&inmemory)); +#endif cl_git_pass(git_remote_create_anonymous(&remote, inmemory, git_str_cstr(&file_path_buf))); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_assert(git_remote_connected(remote)); diff --git a/tests/libgit2/network/remote/rename.c b/tests/libgit2/network/remote/rename.c index b071f9c..21ea216 100644 --- a/tests/libgit2/network/remote/rename.c +++ b/tests/libgit2/network/remote/rename.c @@ -216,7 +216,7 @@ void test_network_remote_rename__symref_head(void) cl_assert_equal_i(0, problems.count); git_strarray_dispose(&problems); - cl_git_pass(git_vector_init(&refs, 2, (git_vector_cmp) git_reference_cmp)); + cl_git_pass(git_vector_init(&refs, 2, git_reference__cmp_cb)); cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); while ((error = git_branch_next(&ref, &btype, iter)) == 0) { diff --git a/tests/libgit2/odb/backend/nobackend.c b/tests/libgit2/odb/backend/nobackend.c index 7d9394c..a81e585 100644 --- a/tests/libgit2/odb/backend/nobackend.c +++ b/tests/libgit2/odb/backend/nobackend.c @@ -11,7 +11,11 @@ void test_odb_backend_nobackend__initialize(void) git_odb *odb; git_refdb *refdb; +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&_repo, GIT_OID_SHA1)); +#else cl_git_pass(git_repository_new(&_repo)); +#endif cl_git_pass(git_config_new(&config)); cl_git_pass(git_odb__new(&odb, NULL)); cl_git_pass(git_refdb_new(&refdb, _repo)); diff --git a/tests/libgit2/odb/freshen.c b/tests/libgit2/odb/freshen.c index e337c82..d0e0e3c 100644 --- a/tests/libgit2/odb/freshen.c +++ b/tests/libgit2/odb/freshen.c @@ -125,7 +125,7 @@ void test_odb_freshen__tree_during_commit(void) cl_git_pass(git_commit_create(&commit_id, repo, NULL, signature, signature, NULL, "New commit pointing to old tree", - tree, 1, (const git_commit **)&parent)); + tree, 1, &parent)); /* make sure we freshen the tree the commit points to */ cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_TREE_FN, &after)); diff --git a/tests/libgit2/odb/sorting.c b/tests/libgit2/odb/sorting.c index ec4e369..3fe52e8 100644 --- a/tests/libgit2/odb/sorting.c +++ b/tests/libgit2/odb/sorting.c @@ -7,6 +7,11 @@ typedef struct { size_t position; } fake_backend; +static void odb_backend_free(git_odb_backend *odb) +{ + git__free(odb); +} + static git_odb_backend *new_backend(size_t position) { fake_backend *b; @@ -15,7 +20,7 @@ static git_odb_backend *new_backend(size_t position) if (b == NULL) return NULL; - b->base.free = (void (*)(git_odb_backend *)) git__free; + b->base.free = odb_backend_free; b->base.version = GIT_ODB_BACKEND_VERSION; b->position = position; return (git_odb_backend *)b; diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c index 5789e96..207dd83 100644 --- a/tests/libgit2/online/clone.c +++ b/tests/libgit2/online/clone.c @@ -7,6 +7,7 @@ #include "refs.h" #define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" +#define LIVE_REPO_AS_DIR "http:/github.com/libgit2/TestGitRepository" #define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository" #define BB_REPO_URL "https://libgit2-test@bitbucket.org/libgit2-test/testgitrepository.git" #define BB_REPO_URL_WITH_PASS "https://libgit2-test:YT77Ppm2nq8w4TYjGS8U@bitbucket.org/libgit2-test/testgitrepository.git" @@ -47,6 +48,9 @@ static char *_orig_http_proxy = NULL; static char *_orig_https_proxy = NULL; static char *_orig_no_proxy = NULL; +static char *_ssh_cmd = NULL; +static char *_orig_ssh_cmd = NULL; + static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload) { GIT_UNUSED(cert); @@ -102,8 +106,26 @@ void test_online_clone__initialize(void) _orig_https_proxy = cl_getenv("HTTPS_PROXY"); _orig_no_proxy = cl_getenv("NO_PROXY"); + _orig_ssh_cmd = cl_getenv("GIT_SSH"); + _ssh_cmd = cl_getenv("GITTEST_SSH_CMD"); + + if (_ssh_cmd) + cl_setenv("GIT_SSH", _ssh_cmd); + else + cl_setenv("GIT_SSH", NULL); + if (_remote_expectcontinue) git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); + +#if !defined(GIT_WIN32) + /* + * On system that allows ':' in filenames "http://path" can be misinterpreted + * as the local path "http:/path". + * Create a local non-repository path that looks like LIVE_REPO_URL to make + * sure we can handle cloning despite this directory being around. + */ + git_futils_mkdir_r(LIVE_REPO_AS_DIR, 0777); +#endif } void test_online_clone__cleanup(void) @@ -116,6 +138,10 @@ void test_online_clone__cleanup(void) cl_fixture_cleanup("./initial"); cl_fixture_cleanup("./subsequent"); +#if !defined(GIT_WIN32) + cl_fixture_cleanup("http:"); +#endif + git__free(_remote_url); git__free(_remote_user); git__free(_remote_pass); @@ -149,6 +175,11 @@ void test_online_clone__cleanup(void) git__free(_orig_https_proxy); git__free(_orig_no_proxy); + cl_setenv("GIT_SSH", _orig_ssh_cmd); + git__free(_orig_ssh_cmd); + + git__free(_ssh_cmd); + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL); git_libgit2_opts(GIT_OPT_SET_SERVER_TIMEOUT, 0); git_libgit2_opts(GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, 0); @@ -653,7 +684,7 @@ void test_online_clone__ssh_auth_methods(void) { int with_user; -#ifndef GIT_SSH +#ifndef GIT_SSH_LIBSSH2 clar__skip(); #endif g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods; @@ -675,7 +706,7 @@ void test_online_clone__ssh_auth_methods(void) */ void test_online_clone__ssh_certcheck_accepts_unknown(void) { -#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS) +#if !defined(GIT_SSH_LIBSSH2) || !defined(GIT_SSH_MEMORY_CREDENTIALS) clar__skip(); #endif @@ -793,9 +824,10 @@ static int cred_foo_bar(git_credential **cred, const char *url, const char *user void test_online_clone__ssh_cannot_change_username(void) { -#ifndef GIT_SSH +#ifndef GIT_SSH_LIBSSH2 clar__skip(); #endif + g_options.fetch_opts.callbacks.credentials = cred_foo_bar; cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); @@ -837,6 +869,10 @@ static int ssh_certificate_check(git_cert *cert, int valid, const char *host, vo void test_online_clone__ssh_cert(void) { +#ifndef GIT_SSH_LIBSSH2 + cl_skip(); +#endif + g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check; if (!_remote_ssh_fingerprint) @@ -908,12 +944,12 @@ void test_online_clone__certificate_invalid(void) { g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check; - cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options), - GIT_ECERTIFICATE); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); -#ifdef GIT_SSH - cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options), - GIT_ECERTIFICATE); +#ifdef GIT_SSH_LIBSSH2 + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options)); #endif } diff --git a/tests/libgit2/online/fetch.c b/tests/libgit2/online/fetch.c index a557bbf..08a14c6 100644 --- a/tests/libgit2/online/fetch.c +++ b/tests/libgit2/online/fetch.c @@ -110,6 +110,26 @@ void test_online_fetch__fetch_twice(void) git_remote_free(remote); } +void test_online_fetch__fetch_with_empty_http_proxy(void) +{ + git_remote *remote; + git_config *config; + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; + + opts.proxy_opts.type = GIT_PROXY_AUTO; + + cl_git_pass(git_repository_config(&config, _repo)); + cl_git_pass(git_config_set_string(config, "http.proxy", "")); + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_remote_fetch(remote, NULL, &opts, NULL)); + + git_remote_disconnect(remote); + git_remote_free(remote); + git_config_free(config); +} + static int transferProgressCallback(const git_indexer_progress *stats, void *payload) { bool *invoked = (bool *)payload; @@ -128,6 +148,8 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date git_clone_options opts = GIT_CLONE_OPTIONS_INIT; opts.bare = true; + counter = 0; + cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git", "./fetch/lg2", &opts)); git_repository_free(_repository); @@ -141,11 +163,52 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date options.callbacks.transfer_progress = &transferProgressCallback; options.callbacks.payload = &invoked; + options.callbacks.update_tips = update_tips; cl_git_pass(git_remote_download(remote, NULL, &options)); cl_assert_equal_i(false, invoked); - cl_git_pass(git_remote_update_tips(remote, &options.callbacks, 1, options.download_tags, NULL)); + cl_git_pass(git_remote_update_tips(remote, &options.callbacks, GIT_REMOTE_UPDATE_FETCHHEAD, options.download_tags, NULL)); + cl_assert_equal_i(0, counter); + + git_remote_disconnect(remote); + + git_remote_free(remote); + git_repository_free(_repository); +} + +void test_online_fetch__report_unchanged_tips(void) +{ + git_repository *_repository; + bool invoked = false; + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.bare = true; + + counter = 0; + + cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git", + "./fetch/lg2", &opts)); + git_repository_free(_repository); + + cl_git_pass(git_repository_open(&_repository, "./fetch/lg2")); + + cl_git_pass(git_remote_lookup(&remote, _repository, "origin")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + + cl_assert_equal_i(false, invoked); + + options.callbacks.transfer_progress = &transferProgressCallback; + options.callbacks.payload = &invoked; + options.callbacks.update_tips = update_tips; + cl_git_pass(git_remote_download(remote, NULL, &options)); + + cl_assert_equal_i(false, invoked); + + cl_git_pass(git_remote_update_tips(remote, &options.callbacks, GIT_REMOTE_UPDATE_REPORT_UNCHANGED, options.download_tags, NULL)); + cl_assert(counter > 0); + git_remote_disconnect(remote); git_remote_free(remote); diff --git a/tests/libgit2/online/push.c b/tests/libgit2/online/push.c index 204572c..e5693bf 100644 --- a/tests/libgit2/online/push.c +++ b/tests/libgit2/online/push.c @@ -5,6 +5,7 @@ #include "push_util.h" #include "refspec.h" #include "remote.h" +#include "futils.h" static git_repository *_repo; @@ -20,7 +21,12 @@ static char *_remote_ssh_passphrase = NULL; static char *_remote_default = NULL; static char *_remote_expectcontinue = NULL; -static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); +static char *_remote_push_options = NULL; + +static char *_orig_ssh_cmd = NULL; +static char *_ssh_cmd = NULL; + +static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); static git_remote *_remote; static record_callbacks_data _record_cbs_data = {{ 0 }}; @@ -367,8 +373,17 @@ void test_online_push__initialize(void) _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); + _remote_push_options = cl_getenv("GITTEST_PUSH_OPTIONS"); _remote = NULL; + _orig_ssh_cmd = cl_getenv("GIT_SSH"); + _ssh_cmd = cl_getenv("GITTEST_SSH_CMD"); + + if (_ssh_cmd) + cl_setenv("GIT_SSH", _ssh_cmd); + else + cl_setenv("GIT_SSH", NULL); + /* Skip the test if we're missing the remote URL */ if (!_remote_url) cl_skip(); @@ -422,6 +437,10 @@ void test_online_push__cleanup(void) git__free(_remote_ssh_passphrase); git__free(_remote_default); git__free(_remote_expectcontinue); + git__free(_remote_push_options); + + git__free(_orig_ssh_cmd); + git__free(_ssh_cmd); /* Freed by cl_git_sandbox_cleanup */ _repo = NULL; @@ -430,6 +449,7 @@ void test_online_push__cleanup(void) record_callbacks_data_clear(&_record_cbs_data); + cl_fixture_cleanup("push-options-result"); cl_fixture_cleanup("testrepo.git"); cl_git_sandbox_cleanup(); } @@ -472,7 +492,8 @@ static void do_push( const char *refspecs[], size_t refspecs_len, push_status expected_statuses[], size_t expected_statuses_len, expected_ref expected_refs[], size_t expected_refs_len, - int expected_ret, int check_progress_cb, int check_update_tips_cb) + int expected_ret, int check_progress_cb, int check_update_tips_cb, + git_strarray *remote_push_options) { git_push_options opts = GIT_PUSH_OPTIONS_INIT; size_t i; @@ -484,6 +505,9 @@ static void do_push( /* Auto-detect the number of threads to use */ opts.pb_parallelism = 0; + if (remote_push_options) + memcpy(&opts.remote_push_options, remote_push_options, sizeof(git_strarray)); + memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); data = opts.callbacks.payload; @@ -527,13 +551,12 @@ static void do_push( verify_update_tips_callback(_remote, expected_refs, expected_refs_len); } - } /* Call push_finish() without ever calling git_push_add_refspec() */ void test_online_push__noop(void) { - do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1); + do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1, NULL); } void test_online_push__b1(void) @@ -543,7 +566,9 @@ void test_online_push__b1(void) expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b2(void) @@ -553,7 +578,9 @@ void test_online_push__b2(void) expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b3(void) @@ -563,7 +590,9 @@ void test_online_push__b3(void) expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b4(void) @@ -573,7 +602,9 @@ void test_online_push__b4(void) expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b5(void) @@ -583,13 +614,15 @@ void test_online_push__b5(void) expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b5_cancel(void) { const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; - do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1); + do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1, NULL); } void test_online_push__multi(void) @@ -620,7 +653,9 @@ void test_online_push__multi(void) }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1")); entry = git_reflog_entry_byindex(log, 0); @@ -641,16 +676,21 @@ void test_online_push__implicit_tgt(void) const char *specs2[] = { "refs/heads/b2" }; push_status exp_stats2[] = { { "refs/heads/b2", 1 } }; expected_ref exp_refs2[] = { - { "refs/heads/b1", &_oid_b1 }, - { "refs/heads/b2", &_oid_b2 } + { "refs/heads/b1", &_oid_b1 }, + { "refs/heads/b2", &_oid_b2 } }; do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 1, 1, + NULL); + do_push(specs2, ARRAY_SIZE(specs2), exp_stats2, ARRAY_SIZE(exp_stats2), - exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0); + exp_refs2, ARRAY_SIZE(exp_refs2), + 0, 0, 0, + NULL); } void test_online_push__fast_fwd(void) @@ -672,19 +712,27 @@ void test_online_push__fast_fwd(void) do_push(specs_init, ARRAY_SIZE(specs_init), exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1); + exp_refs_init, ARRAY_SIZE(exp_refs_init), + 0, 1, 1, + NULL); do_push(specs_ff, ARRAY_SIZE(specs_ff), exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), + 0, 0, 0, + NULL); do_push(specs_reset, ARRAY_SIZE(specs_reset), exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0); + exp_refs_init, ARRAY_SIZE(exp_refs_init), + 0, 0, 0, + NULL); do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force), exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), + 0, 0, 0, + NULL); } void test_online_push__tag_commit(void) @@ -694,7 +742,9 @@ void test_online_push__tag_commit(void) expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_tree(void) @@ -704,7 +754,9 @@ void test_online_push__tag_tree(void) expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_blob(void) @@ -714,7 +766,9 @@ void test_online_push__tag_blob(void) expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_lightweight(void) @@ -724,7 +778,9 @@ void test_online_push__tag_lightweight(void) expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_to_tag(void) @@ -734,7 +790,9 @@ void test_online_push__tag_to_tag(void) expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 0, 0, + NULL); } void test_online_push__force(void) @@ -751,17 +809,80 @@ void test_online_push__force(void) do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 1, 1, + NULL); do_push(specs2, ARRAY_SIZE(specs2), NULL, 0, - exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + GIT_ENONFASTFORWARD, 0, 0, + NULL); /* Non-fast-forward update with force should pass. */ record_callbacks_data_clear(&_record_cbs_data); do_push(specs2_force, ARRAY_SIZE(specs2_force), exp_stats2_force, ARRAY_SIZE(exp_stats2_force), - exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1); + exp_refs2_force, ARRAY_SIZE(exp_refs2_force), + 0, 1, 1, + NULL); +} + +static void push_option_test(git_strarray given_options, const char *expected_option) +{ + const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; + push_status exp_stats[] = { { "refs/heads/b1", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; + git_str push_options_path = GIT_STR_INIT; + git_str push_options_result = GIT_STR_INIT; + char *options[16]; + git_strarray push_options = { options, given_options.count + 1 }; + size_t i; + + /* Skip the test if we're missing the push options result file */ + if (!_remote_push_options) + cl_skip(); + + cl_assert(given_options.count < 16); + + cl_git_pass(git_str_joinpath(&push_options_path, clar_sandbox_path(), "push-options-result")); + + options[0] = push_options_path.ptr; + for (i = 0; i < given_options.count; i++) + options[i + 1] = given_options.strings[i]; + + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + &push_options); + + cl_assert(git_fs_path_exists(push_options_path.ptr)); + cl_git_pass(git_futils_readbuffer(&push_options_result, push_options_path.ptr)); + + cl_assert_equal_s(expected_option, git_str_cstr(&push_options_result)); + git_str_dispose(&push_options_result); + git_str_dispose(&push_options_path); +} + +void test_online_push__options(void) +{ + char *push_options_string_args_test_1[1] = { "test_string" }; + git_strarray push_options_test_1 = { push_options_string_args_test_1, 1 }; + + char *push_options_string_args_test_2[2] = { "test_string", "another arg?" }; + git_strarray push_options_test_2 = { push_options_string_args_test_2, 2 }; + + char *push_options_string_args_test_3[1] = { "👨ðŸ¿â€ðŸ’» but can it do unicode? 🇺🇦" }; + git_strarray push_options_test_3 = { push_options_string_args_test_3, 1 }; + + char *push_options_string_args_test_4[3] = { "\0", "\0", "\0" }; + git_strarray push_options_test_4 = { push_options_string_args_test_4, 3 }; + + push_option_test(push_options_test_1, "test_string"); + push_option_test(push_options_test_2, "test_stringanother arg?"); + push_option_test(push_options_test_3, "👨ðŸ¿â€ðŸ’» but can it do unicode? 🇺🇦"); + push_option_test(push_options_test_4, "\0\0\0"); } void test_online_push__delete(void) @@ -792,7 +913,9 @@ void test_online_push__delete(void) do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 1, 1, + NULL); /* When deleting a non-existent branch, the git client sends zero for both * the old and new commit id. This should succeed on the server with the @@ -802,23 +925,35 @@ void test_online_push__delete(void) */ do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake), exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 0, 0, + NULL); + do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force), exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 0, 0, + NULL); /* Delete one of the pushed branches. */ do_push(specs_delete, ARRAY_SIZE(specs_delete), exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), + 0, 0, 0, + NULL); /* Re-push branches and retry delete with force. */ do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 0, 0, + NULL); + do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force), exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), + 0, 0, 0, + NULL); } void test_online_push__bad_refspecs(void) @@ -862,7 +997,9 @@ void test_online_push__expressions(void) do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__notes(void) @@ -884,13 +1021,17 @@ void test_online_push__notes(void) do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); /* And make sure to delete the note */ do_push(specs_del, ARRAY_SIZE(specs_del), exp_stats, 1, - NULL, 0, 0, 0, 0); + NULL, 0, + 0, 0, 0, + NULL); git_signature_free(signature); } @@ -920,13 +1061,17 @@ void test_online_push__configured(void) do_push(NULL, 0, exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); /* And make sure to delete the note */ do_push(specs_del, ARRAY_SIZE(specs_del), exp_stats, 1, - NULL, 0, 0, 0, 0); + NULL, 0, + 0, 0, 0, + NULL); git_signature_free(signature); } diff --git a/tests/libgit2/online/shallow.c b/tests/libgit2/online/shallow.c index 5c0e656..ee4aaf3 100644 --- a/tests/libgit2/online/shallow.c +++ b/tests/libgit2/online/shallow.c @@ -164,3 +164,102 @@ void test_online_shallow__unshallow(void) git_revwalk_free(walk); git_repository_free(repo); } + +void test_online_shallow__deepen_six(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_revwalk *walk; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_remote *origin = NULL; + git_oid oid; + git_oid *roots; + size_t roots_len; + size_t num_commits = 0; + int error = 0; + + clone_opts.fetch_opts.depth = 5; + clone_opts.remote_cb = remote_single_branch; + + git_str_joinpath(&path, clar_sandbox_path(), "deepen_6"); + cl_git_pass(git_clone(&repo, "https://github.com/libgit2/TestGitRepository", git_str_cstr(&path), &clone_opts)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + fetch_opts.depth = 6; + cl_git_pass(git_remote_lookup(&origin, repo, "origin")); + cl_git_pass(git_remote_fetch(origin, NULL, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(4, roots_len); + cl_assert_equal_s("58be4659bb571194ed4562d04b359d26216f526e", git_oid_tostr_s(&roots[0])); + cl_assert_equal_s("d31f5a60d406e831d056b8ac2538d515100c2df2", git_oid_tostr_s(&roots[1])); + cl_assert_equal_s("6462e7d8024396b14d7651e2ec11e2bbf07a05c4", git_oid_tostr_s(&roots[2])); + cl_assert_equal_s("2c349335b7f797072cf729c4f3bb0914ecb6dec9", git_oid_tostr_s(&roots[3])); + + git_revwalk_new(&walk, repo); + git_revwalk_push_head(walk); + + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + + cl_assert_equal_i(num_commits, 17); + cl_assert_equal_i(error, GIT_ITEROVER); + + git__free(roots); + git_remote_free(origin); + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} + +void test_online_shallow__shorten_four(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_revwalk *walk; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_remote *origin = NULL; + git_oid oid; + git_oid *roots; + size_t roots_len; + size_t num_commits = 0; + int error = 0; + + clone_opts.fetch_opts.depth = 5; + clone_opts.remote_cb = remote_single_branch; + + git_str_joinpath(&path, clar_sandbox_path(), "shorten_4"); + cl_git_pass(git_clone(&repo, "https://github.com/libgit2/TestGitRepository", git_str_cstr(&path), &clone_opts)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + fetch_opts.depth = 4; + cl_git_pass(git_remote_lookup(&origin, repo, "origin")); + cl_git_pass(git_remote_fetch(origin, NULL, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(3, roots_len); + cl_assert_equal_s("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864", git_oid_tostr_s(&roots[0])); + cl_assert_equal_s("59706a11bde2b9899a278838ef20a97e8f8795d2", git_oid_tostr_s(&roots[1])); + cl_assert_equal_s("bab66b48f836ed950c99134ef666436fb07a09a0", git_oid_tostr_s(&roots[2])); + + git_revwalk_new(&walk, repo); + git_revwalk_push_head(walk); + + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + + cl_assert_equal_i(num_commits, 10); + cl_assert_equal_i(error, GIT_ITEROVER); + + git__free(roots); + git_remote_free(origin); + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} diff --git a/tests/libgit2/pack/packbuilder.c b/tests/libgit2/pack/packbuilder.c index ff3dc1f..7da3877 100644 --- a/tests/libgit2/pack/packbuilder.c +++ b/tests/libgit2/pack/packbuilder.c @@ -98,9 +98,6 @@ void test_pack_packbuilder__create_pack(void) { git_indexer_progress stats; git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - git_hash_ctx ctx; - unsigned char hash[GIT_HASH_SHA1_SIZE]; - char hex[(GIT_HASH_SHA1_SIZE * 2) + 1]; seed_packbuilder(); @@ -114,33 +111,13 @@ void test_pack_packbuilder__create_pack(void) cl_git_pass(git_indexer_commit(_indexer, &stats)); git_str_printf(&path, "pack-%s.pack", git_indexer_name(_indexer)); - - /* - * By default, packfiles are created with only one thread. - * Therefore we can predict the object ordering and make sure - * we create exactly the same pack as git.git does when *not* - * reusing existing deltas (as libgit2). - * - * $ cd tests/resources/testrepo.git - * $ git rev-list --objects HEAD | \ - * git pack-objects -q --no-reuse-delta --threads=1 pack - * $ sha1sum pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack - * 5d410bdf97cf896f9007681b92868471d636954b - * - */ + cl_assert(git_fs_path_exists(path.ptr)); cl_git_pass(git_futils_readbuffer(&buf, git_str_cstr(&path))); - - cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); - cl_git_pass(git_hash_update(&ctx, buf.ptr, buf.size)); - cl_git_pass(git_hash_final(hash, &ctx)); - git_hash_ctx_cleanup(&ctx); + cl_assert(buf.size > 256); git_str_dispose(&path); git_str_dispose(&buf); - - git_hash_fmt(hex, hash, GIT_HASH_SHA1_SIZE); - cl_assert_equal_s(hex, "5d410bdf97cf896f9007681b92868471d636954b"); } void test_pack_packbuilder__get_name(void) @@ -148,22 +125,49 @@ void test_pack_packbuilder__get_name(void) seed_packbuilder(); cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL)); - cl_assert_equal_s("7f5fa362c664d68ba7221259be1cbd187434b2f0", git_packbuilder_name(_packbuilder)); + cl_assert(git_packbuilder_name(_packbuilder) != NULL); +} + +static void get_packfile_path(git_str *out, git_packbuilder *pb) +{ + git_str_puts(out, "pack-"); + git_str_puts(out, git_packbuilder_name(pb)); + git_str_puts(out, ".pack"); +} + +static void get_index_path(git_str *out, git_packbuilder *pb) +{ + git_str_puts(out, "pack-"); + git_str_puts(out, git_packbuilder_name(pb)); + git_str_puts(out, ".idx"); } void test_pack_packbuilder__write_default_path(void) { + git_str idx = GIT_STR_INIT, pack = GIT_STR_INIT; + seed_packbuilder(); cl_git_pass(git_packbuilder_write(_packbuilder, NULL, 0, NULL, NULL)); - cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx")); - cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack")); + + git_str_puts(&idx, "objects/pack/"); + get_index_path(&idx, _packbuilder); + + git_str_puts(&pack, "objects/pack/"); + get_packfile_path(&pack, _packbuilder); + + cl_assert(git_fs_path_exists(idx.ptr)); + cl_assert(git_fs_path_exists(pack.ptr)); + + git_str_dispose(&idx); + git_str_dispose(&pack); } static void test_write_pack_permission(mode_t given, mode_t expected) { struct stat statbuf; mode_t mask, os_mask; + git_str idx = GIT_STR_INIT, pack = GIT_STR_INIT; seed_packbuilder(); @@ -181,11 +185,17 @@ static void test_write_pack_permission(mode_t given, mode_t expected) mask = p_umask(0); p_umask(mask); - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx", &statbuf)); + get_index_path(&idx, _packbuilder); + get_packfile_path(&pack, _packbuilder); + + cl_git_pass(p_stat(idx.ptr, &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack", &statbuf)); + cl_git_pass(p_stat(pack.ptr, &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); + + git_str_dispose(&idx); + git_str_dispose(&pack); } void test_pack_packbuilder__permissions_standard(void) diff --git a/tests/libgit2/rebase/sign.c b/tests/libgit2/rebase/sign.c index 69bb1c6..45bac29 100644 --- a/tests/libgit2/rebase/sign.c +++ b/tests/libgit2/rebase/sign.c @@ -26,7 +26,7 @@ static int create_cb_passthrough( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[], + git_commit * const parents[], void *payload) { GIT_UNUSED(out); @@ -94,7 +94,7 @@ static int create_cb_signed_gpg( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[], + git_commit * const parents[], void *payload) { git_buf commit_content = GIT_BUF_INIT; @@ -202,7 +202,7 @@ static int create_cb_error( const char *message, const git_tree *tree, size_t parent_count, - const git_commit *parents[], + git_commit * const parents[], void *payload) { GIT_UNUSED(out); diff --git a/tests/libgit2/refs/branches/delete.c b/tests/libgit2/refs/branches/delete.c index 6b3d507..63f8c5d 100644 --- a/tests/libgit2/refs/branches/delete.c +++ b/tests/libgit2/refs/branches/delete.c @@ -92,6 +92,21 @@ void test_refs_branches_delete__can_delete_a_local_branch(void) git_reference_free(branch); } +void test_refs_branches_delete__can_delete_a_local_branch_with_multivar(void) +{ + git_reference *branch; + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_multivar( + cfg, "branch.br2.gitpublishto", "^$", "example1@example.com")); + cl_git_pass(git_config_set_multivar( + cfg, "branch.br2.gitpublishto", "^$", "example2@example.com")); + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + void test_refs_branches_delete__can_delete_a_remote_branch(void) { git_reference *branch; diff --git a/tests/libgit2/refs/reflog/messages.c b/tests/libgit2/refs/reflog/messages.c index 647c00d..4a2ecb0 100644 --- a/tests/libgit2/refs/reflog/messages.c +++ b/tests/libgit2/refs/reflog/messages.c @@ -233,7 +233,7 @@ void test_refs_reflog_messages__show_merge_for_merge_commits(void) cl_git_pass(git_commit_create(&merge_commit_oid, g_repo, "HEAD", s, s, NULL, "Merge commit", tree, - 2, (const struct git_commit **) parent_commits)); + 2, parent_commits)); cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, NULL, diff --git a/tests/libgit2/refs/revparse.c b/tests/libgit2/refs/revparse.c index d2f4648..9bc3bd1 100644 --- a/tests/libgit2/refs/revparse.c +++ b/tests/libgit2/refs/revparse.c @@ -747,6 +747,25 @@ void test_refs_revparse__try_to_retrieve_branch_before_abbrev_sha(void) cl_git_sandbox_cleanup(); } +void test_refs_revparse__at_at_end_of_refname(void) +{ + git_repository *repo; + git_reference *branch; + git_object *target; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_revparse_single(&target, repo, "HEAD")); + cl_git_pass(git_branch_create(&branch, repo, "master@", (git_commit *)target, 0)); + git_object_free(target); + + test_id_inrepo("master@", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE, repo); + + cl_git_fail_with(GIT_ENOTFOUND, git_revparse_single(&target, repo, "foo@")); + + git_reference_free(branch); + cl_git_sandbox_cleanup(); +} void test_refs_revparse__range(void) { diff --git a/tests/libgit2/remote/fetch.c b/tests/libgit2/remote/fetch.c index 85e9920..a5d3272 100644 --- a/tests/libgit2/remote/fetch.c +++ b/tests/libgit2/remote/fetch.c @@ -75,6 +75,7 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, /* create two commits in repo 1 and a reference to them */ { git_oid empty_tree_id; + git_commit *commit1; git_tree *empty_tree; git_signature *sig; git_treebuilder *tb; @@ -84,10 +85,12 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, cl_git_pass(git_signature_default(&sig, repo1)); cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, sig, NULL, "one", empty_tree, 0, NULL)); + cl_git_pass(git_commit_lookup(&commit1, repo1, commit1id)); cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, - sig, NULL, "two", empty_tree, 1, commit1id)); + sig, NULL, "two", empty_tree, 1, commit1)); git_tree_free(empty_tree); + git_commit_free(commit1); git_signature_free(sig); git_treebuilder_free(tb); } diff --git a/tests/libgit2/repo/getters.c b/tests/libgit2/repo/getters.c index d401bb8..8e21d35 100644 --- a/tests/libgit2/repo/getters.c +++ b/tests/libgit2/repo/getters.c @@ -51,3 +51,63 @@ void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) git_odb_free(odb); } + +void test_repo_getters__commit_parents(void) +{ + git_repository *repo; + git_commitarray parents; + git_oid first_parent; + git_oid merge_parents[4]; + + git_oid__fromstr(&first_parent, "099fabac3a9ea935598528c27f866e34089c2eff", GIT_OID_SHA1); + + /* A commit on a new repository has no parents */ + + cl_git_pass(git_repository_init(&repo, "new_repo", false)); + cl_git_pass(git_repository_commit_parents(&parents, repo)); + + cl_assert_equal_sz(0, parents.count); + cl_assert_equal_p(NULL, parents.commits); + + git_commitarray_dispose(&parents); + git_repository_free(repo); + + /* A standard commit has one parent */ + + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_commit_parents(&parents, repo)); + + cl_assert_equal_sz(1, parents.count); + cl_assert_equal_oid(&first_parent, git_commit_id(parents.commits[0])); + + git_commitarray_dispose(&parents); + + /* A merge commit has multiple parents */ + + cl_git_rewritefile("testrepo/.git/MERGE_HEAD", + "8496071c1b46c854b31185ea97743be6a8774479\n" + "5b5b025afb0b4c913b4c338a42934a3863bf3644\n" + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045\n" + "9fd738e8f7967c078dceed8190330fc8648ee56a\n"); + + cl_git_pass(git_repository_commit_parents(&parents, repo)); + + cl_assert_equal_sz(5, parents.count); + + cl_assert_equal_oid(&first_parent, git_commit_id(parents.commits[0])); + + git_oid__fromstr(&merge_parents[0], "8496071c1b46c854b31185ea97743be6a8774479", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[0], git_commit_id(parents.commits[1])); + git_oid__fromstr(&merge_parents[1], "5b5b025afb0b4c913b4c338a42934a3863bf3644", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[1], git_commit_id(parents.commits[2])); + git_oid__fromstr(&merge_parents[2], "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[2], git_commit_id(parents.commits[3])); + git_oid__fromstr(&merge_parents[3], "9fd738e8f7967c078dceed8190330fc8648ee56a", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[3], git_commit_id(parents.commits[4])); + + git_commitarray_dispose(&parents); + + git_repository_free(repo); + + cl_fixture_cleanup("testrepo"); +} diff --git a/tests/libgit2/repo/init.c b/tests/libgit2/repo/init.c index d78ec06..446ab73 100644 --- a/tests/libgit2/repo/init.c +++ b/tests/libgit2/repo/init.c @@ -755,3 +755,28 @@ void test_repo_init__longpath(void) git_str_dispose(&path); #endif } + +void test_repo_init__absolute_path_with_backslashes(void) +{ +#ifdef GIT_WIN32 + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + char *c; + + cl_set_cleanup(&cleanup_repository, "path"); + + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "path/to/newrepo")); + + for (c = path.ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } + + initopts.flags |= GIT_REPOSITORY_INIT_MKDIR | GIT_REPOSITORY_INIT_MKPATH; + + cl_git_pass(git_repository_init_ext(&g_repo, path.ptr, &initopts)); + git_str_dispose(&path); +#else + clar__skip(); +#endif +} diff --git a/tests/libgit2/repo/new.c b/tests/libgit2/repo/new.c index d77e903..aaa917a 100644 --- a/tests/libgit2/repo/new.c +++ b/tests/libgit2/repo/new.c @@ -5,7 +5,11 @@ void test_repo_new__has_nothing(void) { git_repository *repo; +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&repo, GIT_OID_SHA1)); +#else cl_git_pass(git_repository_new(&repo)); +#endif cl_assert_equal_b(true, git_repository_is_bare(repo)); cl_assert_equal_p(NULL, git_repository_path(repo)); cl_assert_equal_p(NULL, git_repository_workdir(repo)); @@ -16,7 +20,11 @@ void test_repo_new__is_bare_until_workdir_set(void) { git_repository *repo; +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&repo, GIT_OID_SHA1)); +#else cl_git_pass(git_repository_new(&repo)); +#endif cl_assert_equal_b(true, git_repository_is_bare(repo)); cl_git_pass(git_repository_set_workdir(repo, clar_sandbox_path(), 0)); @@ -25,3 +33,30 @@ void test_repo_new__is_bare_until_workdir_set(void) git_repository_free(repo); } +void test_repo_new__sha1(void) +{ + git_repository *repo; + +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&repo, GIT_OID_SHA1)); +#else + cl_git_pass(git_repository_new(&repo)); +#endif + cl_assert_equal_i(GIT_OID_SHA1, git_repository_oid_type(repo)); + + git_repository_free(repo); +} + +void test_repo_new__sha256(void) +{ +#ifndef GIT_EXPERIMENTAL_SHA256 + cl_skip(); +#else + git_repository *repo; + + cl_git_pass(git_repository_new(&repo, GIT_OID_SHA256)); + cl_assert_equal_i(GIT_OID_SHA256, git_repository_oid_type(repo)); + + git_repository_free(repo); +#endif +} diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 3d1a062..d585513 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -316,7 +316,7 @@ static void unposix_path(git_str *path) src = tgt = path->ptr; /* convert "/d/..." to "d:\..." */ - if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') { + if (src[0] == '/' && git__isalpha(src[1]) && src[2] == '/') { *tgt++ = src[1]; *tgt++ = ':'; *tgt++ = '\\'; @@ -533,15 +533,21 @@ void test_repo_open__validates_bare_repo_ownership(void) cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); } -void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) +static int test_safe_path(const char *path) { git_repository *repo; git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT, config_data = GIT_STR_INIT; + int error; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + /* + * Sandbox the fixture, and ensure that when we fake an owner + * of "other" that the repository cannot be opened (and fails + * with `GIT_EOWNER`). + */ cl_fixture_sandbox("empty_standard_repo"); cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); @@ -555,81 +561,179 @@ void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + git_str_clear(&config_data); git_str_printf(&config_data, "[foo]\n" \ "\tbar = Foobar\n" \ "\tbaz = Baz!\n" \ "[safe]\n" \ - "\tdirectory = /non/existent/path\n" \ - "\tdirectory = /\n" \ - "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s\n" \ - "\tdirectory = /tmp\n" \ + "\tdirectory = %s\n" \ "[bar]\n" \ "\tfoo = barfoo\n", - clar_sandbox_path(), "empty_standard_repo"); + path); cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + error = git_repository_open(&repo, "empty_standard_repo"); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); git_str_dispose(&config_data); + + return error; } -void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) +static int test_bare_safe_path(const char *path) { git_repository *repo; - git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT; + git_str config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; + int error; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_fixture_sandbox("testrepo.git"); git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with( - GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); /* Add safe.directory options to the global configuration */ git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - config_path.ptr); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - cl_git_rewritefile(config_filename.ptr, "[foo]\n" - "\tbar = Foobar\n" - "\tbaz = Baz!\n" - "[safe]\n" - "\tdirectory = *\n" - "[bar]\n" - "\tfoo = barfoo\n"); + git_str_printf(&config_data, + "[foo]\n" \ + "\tbar = Foobar\n" \ + "\tbaz = Baz!\n" \ + "[safe]\n" \ + "\tdirectory = /non/existent/path\n" \ + "\tdirectory = /\n" \ + "\tdirectory = c:\\\\temp\n" \ + "\tdirectory = %s\n" \ + "\tdirectory = /tmp\n" \ + "[bar]\n" \ + "\tfoo = barfoo\n", + path); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + error = git_repository_open(&repo, "testrepo.git"); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); + git_str_dispose(&config_data); + + return error; +} + +void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf(&path, "%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__safe_directory_fails_with_trailing_slash(void) +{ + git_str path = GIT_STR_INIT; + + /* + * "/tmp/foo/" is not permitted; safe path must be specified + * as "/tmp/foo" + */ + cl_git_pass(git_str_printf(&path, "%s/%s/", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) +{ + cl_git_pass(test_safe_path("*")); } void test_repo_open__can_allowlist_bare_gitdir(void) { + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf(&path, "%s/%s", + clar_sandbox_path(), "testrepo.git")); + cl_git_pass(test_bare_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) +{ + cl_git_pass(test_bare_safe_path("*")); +} + +void test_repo_open__can_handle_prefixed_safe_paths(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)/" becomes "%(prefix)//tmp/foo" - so + * "%(prefix)/" is stripped and means the literal path + * follows. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +#endif +} + +void test_repo_open__prefixed_safe_paths_must_have_two_slashes(void) +{ + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)" becomes "%(prefix)/tmp/foo" - so it's + * actually trying to look in the git prefix, for example, + * "/usr/local/tmp/foo", which we don't actually support. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_handle_win32_prefixed_safe_paths(void) +{ +#ifdef GIT_WIN32 git_repository *repo; - git_str config_path = GIT_STR_INIT, + git_str unc_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT, config_data = GIT_STR_INIT; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - cl_fixture_sandbox("testrepo.git"); + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + /* + * On Windows, we can generally map a local drive to a UNC path; + * for example C:\Foo\Bar becomes //localhost/C$/Foo/bar + */ + cl_git_pass(git_str_printf(&unc_path, "//localhost/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + + if (unc_path.ptr[13] != ':' || unc_path.ptr[14] != '/') + cl_skip(); + + unc_path.ptr[13] = '$'; git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, unc_path.ptr)); /* Add safe.directory options to the global configuration */ git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); @@ -638,64 +742,74 @@ void test_repo_open__can_allowlist_bare_gitdir(void) git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + /* The blank resets our sandbox directory and opening fails */ + git_str_printf(&config_data, - "[foo]\n" \ - "\tbar = Foobar\n" \ - "\tbaz = Baz!\n" \ - "[safe]\n" \ - "\tdirectory = /non/existent/path\n" \ - "\tdirectory = /\n" \ - "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s\n" \ - "\tdirectory = /tmp\n" \ - "[bar]\n" \ - "\tfoo = barfoo\n", - clar_sandbox_path(), "testrepo.git"); + "[safe]\n\tdirectory = %%(prefix)/%s\n", + unc_path.ptr); cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + cl_git_pass(git_repository_open(&repo, unc_path.ptr)); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); git_str_dispose(&config_data); + git_str_dispose(&unc_path); +#endif } -void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) +void test_repo_open__can_handle_win32_unc_safe_paths(void) { +#ifdef GIT_WIN32 git_repository *repo; - git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT; + git_str unc_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - cl_fixture_sandbox("testrepo.git"); + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + /* + * On Windows, we can generally map a local drive to a UNC path; + * for example C:\Foo\Bar becomes //localhost/C$/Foo/bar + */ + cl_git_pass(git_str_printf(&unc_path, "//localhost/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + + if (unc_path.ptr[13] != ':' || unc_path.ptr[14] != '/') + cl_skip(); + + unc_path.ptr[13] = '$'; git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with( - GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, unc_path.ptr)); /* Add safe.directory options to the global configuration */ git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - config_path.ptr); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - cl_git_rewritefile(config_filename.ptr, "[foo]\n" - "\tbar = Foobar\n" - "\tbaz = Baz!\n" - "[safe]\n" - "\tdirectory = *\n" - "[bar]\n" - "\tfoo = barfoo\n"); + /* The blank resets our sandbox directory and opening fails */ - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + git_str_printf(&config_data, + "[safe]\n\tdirectory = %s\n", + unc_path.ptr); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); + + cl_git_pass(git_repository_open(&repo, unc_path.ptr)); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); + git_str_dispose(&config_data); + git_str_dispose(&unc_path); +#endif } void test_repo_open__can_reset_safe_directory_list(void) diff --git a/tests/libgit2/repo/shallow.c b/tests/libgit2/repo/shallow.c index adb7a9e..a3e3036 100644 --- a/tests/libgit2/repo/shallow.c +++ b/tests/libgit2/repo/shallow.c @@ -35,5 +35,5 @@ void test_repo_shallow__clears_errors(void) { g_repo = cl_git_sandbox_init("testrepo.git"); cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); } diff --git a/tests/libgit2/revert/workdir.c b/tests/libgit2/revert/workdir.c index 3e790b7..6d74254 100644 --- a/tests/libgit2/revert/workdir.c +++ b/tests/libgit2/revert/workdir.c @@ -188,7 +188,7 @@ void test_revert_workdir__again(void) cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); - cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head)); + cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, &orig_head)); cl_git_pass(git_revert(repo, orig_head, NULL)); cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); @@ -238,7 +238,7 @@ void test_revert_workdir__again_after_automerge(void) cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); - cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&head)); + cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, &head)); cl_git_pass(git_revert(repo, commit, NULL)); cl_assert(merge_test_index(repo_index, second_revert_entries, 6)); @@ -287,7 +287,7 @@ void test_revert_workdir__again_after_edit(void) cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); - cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head)); + cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, &orig_head)); cl_git_pass(git_revert(repo, commit, NULL)); cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); diff --git a/tests/libgit2/revwalk/basic.c b/tests/libgit2/revwalk/basic.c index 41090a1..5c53405 100644 --- a/tests/libgit2/revwalk/basic.c +++ b/tests/libgit2/revwalk/basic.c @@ -550,8 +550,7 @@ void test_revwalk_basic__big_timestamp(void) cl_git_pass(git_signature_new(&sig, "Joe", "joe@example.com", INT64_C(2399662595), 0)); cl_git_pass(git_commit_tree(&tree, tip)); - cl_git_pass(git_commit_create(&id, _repo, "HEAD", sig, sig, NULL, "some message", tree, 1, - (const git_commit **)&tip)); + cl_git_pass(git_commit_create(&id, _repo, "HEAD", sig, sig, NULL, "some message", tree, 1, &tip)); cl_git_pass(git_revwalk_push_head(_walk)); diff --git a/tests/libgit2/status/worktree.c b/tests/libgit2/status/worktree.c index efbf597..8a2ea9c 100644 --- a/tests/libgit2/status/worktree.c +++ b/tests/libgit2/status/worktree.c @@ -1360,3 +1360,13 @@ void test_status_worktree__at_head_parent(void) git_tree_free(parent_tree); git_status_list_free(statuslist); } + +void test_status_worktree__skip_hash(void) +{ + git_repository *repo = cl_git_sandbox_init("status_skiphash"); + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read(index, true)); + git_index_free(index); +} diff --git a/tests/libgit2/submodule/lookup.c b/tests/libgit2/submodule/lookup.c index febb7df..14a624b 100644 --- a/tests/libgit2/submodule/lookup.c +++ b/tests/libgit2/submodule/lookup.c @@ -401,6 +401,24 @@ void test_submodule_lookup__prefix_name(void) git_submodule_free(sm); } +/* ".path" in name of submodule */ +void test_submodule_lookup__dotpath_in_name(void) +{ + sm_lookup_data data; + + cl_git_rewritefile( + "submod2/.gitmodules", "[submodule \"kwb.pathdict\"]\n" + " path = kwb.pathdict\n" + " url = ../Test_App\n" + "[submodule \"fakin.path.app\"]\n" + " path = fakin.path.app\n" + " url = ../Test_App\n"); + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(9, data.count); +} + void test_submodule_lookup__renamed(void) { const char *newpath = "sm_actually_changed"; diff --git a/tests/libgit2/trace/trace.c b/tests/libgit2/trace/trace.c index 097208b..9fea576 100644 --- a/tests/libgit2/trace/trace.c +++ b/tests/libgit2/trace/trace.c @@ -32,16 +32,11 @@ void test_trace_trace__cleanup(void) void test_trace_trace__sets(void) { -#ifdef GIT_TRACE cl_assert(git_trace_level() == GIT_TRACE_INFO); -#else - cl_skip(); -#endif } void test_trace_trace__can_reset(void) { -#ifdef GIT_TRACE cl_assert(git_trace_level() == GIT_TRACE_INFO); cl_git_pass(git_trace_set(GIT_TRACE_ERROR, trace_callback)); @@ -51,14 +46,10 @@ void test_trace_trace__can_reset(void) git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); cl_assert(written == 1); -#else - cl_skip(); -#endif } void test_trace_trace__can_unset(void) { -#ifdef GIT_TRACE cl_assert(git_trace_level() == GIT_TRACE_INFO); cl_git_pass(git_trace_set(GIT_TRACE_NONE, NULL)); @@ -67,40 +58,25 @@ void test_trace_trace__can_unset(void) cl_assert(written == 0); git_trace(GIT_TRACE_FATAL, "Hello %s!", "world"); cl_assert(written == 0); -#else - cl_skip(); -#endif } void test_trace_trace__skips_higher_level(void) { -#ifdef GIT_TRACE cl_assert(written == 0); git_trace(GIT_TRACE_DEBUG, "Hello %s!", "world"); cl_assert(written == 0); -#else - cl_skip(); -#endif } void test_trace_trace__writes(void) { -#ifdef GIT_TRACE cl_assert(written == 0); git_trace(GIT_TRACE_INFO, "Hello %s!", "world"); cl_assert(written == 1); -#else - cl_skip(); -#endif } void test_trace_trace__writes_lower_level(void) { -#ifdef GIT_TRACE cl_assert(written == 0); git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); cl_assert(written == 1); -#else - cl_skip(); -#endif } diff --git a/tests/libgit2/transport/ssh_exec.c b/tests/libgit2/transport/ssh_exec.c new file mode 100644 index 0000000..886c10a --- /dev/null +++ b/tests/libgit2/transport/ssh_exec.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" +#include "git2/sys/remote.h" +#include "git2/sys/transport.h" + + +void test_transport_ssh_exec__reject_injection_username(void) +{ +#ifndef GIT_SSH_EXEC + cl_skip(); +#else + git_remote *remote; + git_repository *repo; + git_transport *transport; + const char *url = "-oProxyCommand=git@somehost:somepath"; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + + cl_git_pass(git_repository_init(&repo, "./transport-username", 0)); + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_transport_new(&transport, remote, url)); + cl_git_fail_with(-1, transport->connect(transport, url, + GIT_SERVICE_UPLOADPACK_LS, &opts)); + + transport->free(transport); + git_remote_free(remote); + git_repository_free(repo); +#endif +} + +void test_transport_ssh_exec__reject_injection_hostname(void) +{ +#ifndef GIT_SSH_EXEC + cl_skip(); +#else + git_remote *remote; + git_repository *repo; + git_transport *transport; + const char *url = "-oProxyCommand=somehost:somepath-hostname"; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + + cl_git_pass(git_repository_init(&repo, "./transport-hostname", 0)); + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_transport_new(&transport, remote, url)); + cl_git_fail_with(-1, transport->connect(transport, url, + GIT_SERVICE_UPLOADPACK_LS, &opts)); + + transport->free(transport); + git_remote_free(remote); + git_repository_free(repo); +#endif +} + +void test_transport_ssh_exec__reject_injection_path(void) +{ +#ifndef GIT_SSH_EXEC + cl_skip(); +#else + git_remote *remote; + git_repository *repo; + git_transport *transport; + const char *url = "git@somehost:-somepath"; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + + cl_git_pass(git_repository_init(&repo, "./transport-path", 0)); + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_transport_new(&transport, remote, url)); + cl_git_fail_with(-1, transport->connect(transport, url, + GIT_SERVICE_UPLOADPACK_LS, &opts)); + + transport->free(transport); + git_remote_free(remote); + git_repository_free(repo); +#endif +} diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index 81dcfe1..1fd1f75 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -6,15 +6,19 @@ static worktree_fixture fixture = WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); +static worktree_fixture submodule = + WORKTREE_FIXTURE_INIT("submodules", "submodules-worktree-parent"); void test_worktree_config__initialize(void) { setup_fixture_worktree(&fixture); + setup_fixture_worktree(&submodule); } void test_worktree_config__cleanup(void) { cleanup_fixture_worktree(&fixture); + cleanup_fixture_worktree(&submodule); } void test_worktree_config__open(void) @@ -27,7 +31,7 @@ void test_worktree_config__open(void) git_config_free(cfg); } -void test_worktree_config__set(void) +void test_worktree_config__set_level_local(void) { git_config *cfg; int32_t val; @@ -45,3 +49,78 @@ void test_worktree_config__set(void) cl_assert_equal_i(val, 5); git_config_free(cfg); } + +void test_worktree_config__requires_extension(void) +{ + git_config *cfg; + git_config *wtcfg; + int extension = 0; + + /* + * the "submodules" repo does not have extensions.worktreeconfig + * set, the worktree configuration should not be available. + */ + cl_git_pass(git_repository_config(&cfg, submodule.repo)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&extension, cfg, "extensions.worktreeconfig")); + cl_assert_equal_i(0, extension); + cl_git_fail_with(GIT_ENOTFOUND, git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + git_config_free(cfg); + + /* the "testrepo" repo does have the configuration set. */ + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_bool(&extension, cfg, "extensions.worktreeconfig")); + cl_assert_equal_i(1, extension); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + git_config_free(wtcfg); + git_config_free(cfg); +} + +void test_worktree_config__exists(void) +{ + git_config *cfg, *wtcfg, *snap; + const char *str; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_repository_config(&wtcfg, fixture.worktree)); + + cl_git_pass(git_config_snapshot(&snap, cfg)); + cl_git_pass(git_config_get_string(&str, snap, "worktreetest.config")); + cl_assert_equal_s("mainrepo", str); + git_config_free(snap); + + cl_git_pass(git_config_snapshot(&snap, wtcfg)); + cl_git_pass(git_config_get_string(&str, snap, "worktreetest.config")); + cl_assert_equal_s("worktreerepo", str); + git_config_free(snap); + + git_config_free(cfg); + git_config_free(wtcfg); +} + +void test_worktree_config__set_level_worktree(void) +{ + git_config *cfg; + git_config *wtcfg; + int32_t val; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + cl_git_pass(git_config_set_int32(wtcfg, "worktree.specific", 42)); + + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + /* reopen to verify config has been set */ + git_config_free(cfg); + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + cl_git_fail_with(GIT_ENOTFOUND, git_config_delete_entry(cfg, "worktree.specific")); + + cl_git_pass(git_config_delete_entry(wtcfg, "worktree.specific")); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_int32(&val, cfg, "worktree.specific")); + + git_config_free(cfg); + git_config_free(wtcfg); +} diff --git a/tests/libgit2/worktree/refs.c b/tests/libgit2/worktree/refs.c index 557726a..51e7b2b 100644 --- a/tests/libgit2/worktree/refs.c +++ b/tests/libgit2/worktree/refs.c @@ -20,7 +20,7 @@ void test_worktree_refs__cleanup(void) cleanup_fixture_worktree(&fixture); } -void test_worktree_refs__list(void) +void test_worktree_refs__list_no_difference_in_worktree(void) { git_strarray refs, wtrefs; unsigned i, j; @@ -61,6 +61,66 @@ exit: cl_git_pass(error); } +void test_worktree_refs__list_worktree_specific(void) +{ + git_strarray refs, wtrefs; + git_reference *ref, *new_branch; + int error = 0; + git_oid oid; + + cl_git_pass(git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_create( + &new_branch, fixture.worktree, "refs/bisect/a-bisect-ref", &oid, + 0, "test")); + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_lookup(&ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + cl_assert_equal_sz(wtrefs.count, refs.count + 1); + + git_reference_free(ref); + git_reference_free(new_branch); + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + +void test_worktree_refs__list_worktree_specific_hidden_in_main_repo(void) +{ + git_strarray refs, wtrefs; + git_reference *ref, *new_branch; + int error = 0; + git_oid oid; + + cl_git_pass( + git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup( + &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_create( + &new_branch, fixture.repo, "refs/bisect/a-bisect-ref", &oid, + 0, "test")); + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup( + &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_lookup( + &ref, fixture.repo, "refs/bisect/a-bisect-ref")); + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + cl_assert_equal_sz(refs.count, wtrefs.count + 1); + + git_reference_free(ref); + git_reference_free(new_branch); + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + void test_worktree_refs__read_head(void) { git_reference *head; diff --git a/tests/libgit2/worktree/worktree.c b/tests/libgit2/worktree/worktree.c index fed5c92..00e3e3f 100644 --- a/tests/libgit2/worktree/worktree.c +++ b/tests/libgit2/worktree/worktree.c @@ -217,6 +217,50 @@ void test_worktree_worktree__init(void) git_repository_free(repo); } +void test_worktree_worktree__add_remove_add(void) +{ + git_worktree_add_options add_opts = GIT_WORKTREE_ADD_OPTIONS_INIT; + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_str path = GIT_BUF_INIT; + git_reference *branch; + git_repository *repo; + git_worktree *wt; + + /* Add the worktree */ + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-add-remove-add")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, NULL)); + + /* Open and verify created repo */ + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-add-remove-add/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_reference_free(branch); + git_repository_free(repo); + + /* Prune the worktree */ + opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_WORKING_TREE; + cl_git_pass(git_worktree_prune(wt, &opts)); + cl_assert(!git_fs_path_exists(wt->gitdir_path)); + cl_assert(!git_fs_path_exists(wt->gitlink_path)); + git_worktree_free(wt); + + /* Add the worktree back with default options should fail. */ + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, &add_opts)); + /* If allowing checkout of existing branches, it should succeed. */ + add_opts.checkout_existing = 1; + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, &add_opts)); + + /* Open and verify created repo */ + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-add-remove-add/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_reference_free(branch); + git_repository_free(repo); + + git_str_dispose(&path); + git_worktree_free(wt); +} + void test_worktree_worktree__add_locked(void) { git_worktree *wt; @@ -244,6 +288,7 @@ void test_worktree_worktree__add_locked(void) void test_worktree_worktree__init_existing_branch(void) { + git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; git_reference *head, *branch; git_commit *commit; git_worktree *wt; @@ -251,12 +296,18 @@ void test_worktree_worktree__init_existing_branch(void) cl_git_pass(git_repository_head(&head, fixture.repo)); cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); - cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false)); + cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new-exist", commit, false)); - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); - cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new-exist")); + + /* Add the worktree back with default options should fail. */ + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, NULL)); + /* If allowing checkout of existing branches, it should succeed. */ + opts.checkout_existing = 1; + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, &opts)); git_str_dispose(&path); + git_worktree_free(wt); git_commit_free(commit); git_reference_free(head); git_reference_free(branch); diff --git a/tests/resources/merge-resolve/.gitted/objects/29 b/tests/resources/merge-resolve/.gitted/objects/29 Binary files differnew file mode 100644 index 0000000..9661507 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/29 diff --git a/tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d b/tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d new file mode 100644 index 0000000..7e0555b --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d @@ -0,0 +1,3 @@ +x¥NI +1ôœWô]žì/þÀ´Žãa&#~ß(þÀ:ÕFQ\—åÖAÛ¸éMtrš8K0Y›àK&Ž¥Xk¢w—É™"è$pPwj²vÕ\RðhÑgI=ŘK*SDaMÚ*zö¹68åµç¹.ºÂ^†ûaGù?µãº` +q6YØâ€î8ÛåÏÅ3W^áBM½ùiQ†
\ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 b/tests/resources/merge-resolve/.gitted/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 Binary files differnew file mode 100644 index 0000000..cfc3920 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 diff --git a/tests/resources/merge-resolve/.gitted/objects/60/b12be2d2f57977ce83d8dfd32e2394ac1ba1a2 b/tests/resources/merge-resolve/.gitted/objects/60/b12be2d2f57977ce83d8dfd32e2394ac1ba1a2 Binary files differnew file mode 100644 index 0000000..f53f75e --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/60/b12be2d2f57977ce83d8dfd32e2394ac1ba1a2 diff --git a/tests/resources/merge-resolve/.gitted/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 b/tests/resources/merge-resolve/.gitted/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 Binary files differnew file mode 100644 index 0000000..dc6cf64 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 diff --git a/tests/resources/merge-resolve/.gitted/objects/ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 b/tests/resources/merge-resolve/.gitted/objects/ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 Binary files differnew file mode 100644 index 0000000..d743a38 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 diff --git a/tests/resources/merge-resolve/.gitted/objects/bc/114411903fd2afaa4bb9b85ed13f27e37ac375 b/tests/resources/merge-resolve/.gitted/objects/bc/114411903fd2afaa4bb9b85ed13f27e37ac375 Binary files differnew file mode 100644 index 0000000..08941ff --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/bc/114411903fd2afaa4bb9b85ed13f27e37ac375 diff --git a/tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 b/tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 new file mode 100644 index 0000000..011b5b3 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 @@ -0,0 +1,2 @@ +x¥A +B1C]÷³¤µÓö"n¼¨Ó)ÀþBÕë[ŘUò ÕR¤Ã^ÛMo̽3¨“c:dÚcö™òÄhCð·i2FÅGŸkƒKzÅ–à:ײ֎<èÇù[üÒŽj9 zBë0XØê!5è8ïü猒EºÄ;4~Ê*uQo—$DÝ
\ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/objects/de/06afe070b65f94d7d791c39a6d389c58dda60d b/tests/resources/merge-resolve/.gitted/objects/de/06afe070b65f94d7d791c39a6d389c58dda60d Binary files differnew file mode 100644 index 0000000..28567b6 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/de/06afe070b65f94d7d791c39a6d389c58dda60d diff --git a/tests/resources/merge-resolve/.gitted/objects/e5/0a49f9558d09d4d3bfc108363bb24c127ed263 b/tests/resources/merge-resolve/.gitted/objects/e5/0a49f9558d09d4d3bfc108363bb24c127ed263 Binary files differnew file mode 100644 index 0000000..251c5df --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/e5/0a49f9558d09d4d3bfc108363bb24c127ed263 diff --git a/tests/resources/merge-resolve/.gitted/objects/ea/789495e0a72efadcd0f86a48f4c9ed435bb8a3 b/tests/resources/merge-resolve/.gitted/objects/ea/789495e0a72efadcd0f86a48f4c9ed435bb8a3 new file mode 100644 index 0000000..ed98d70 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/ea/789495e0a72efadcd0f86a48f4c9ed435bb8a3 @@ -0,0 +1,3 @@ +x¥OIj1ÌY¯è»!´¶‘ÁøâäV‹ÉA£ Ëøû–M~:ÕE÷Ö~&o>æÌZ;§uB[‹¡JärN9z)ÚVÄb¼ú¥!Ç.Rj +:Ü +‡$ÈŘKª:¢°!c ÝçÞ\˃Fï½·[?àK–ûbyê“{;ƒõÞ œpA-wòϵ–S[_iÀì{WOG‰Rï
\ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames new file mode 100644 index 0000000..89b4eea --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames @@ -0,0 +1 @@ +ea789495e0a72efadcd0f86a48f4c9ed435bb8a3 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch new file mode 100644 index 0000000..1c6a9f4 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch @@ -0,0 +1 @@ +ab347abd8cda4a0e3b8bb42bb620c0c72c7df779 diff --git a/tests/resources/process/cat.bat b/tests/resources/process/cat.bat new file mode 100644 index 0000000..af9b573 --- /dev/null +++ b/tests/resources/process/cat.bat @@ -0,0 +1,2 @@ +@ECHO OFF
+FOR /F "tokens=*" %%a IN ('more') DO ECHO %%a
diff --git a/tests/resources/process/env.cmd b/tests/resources/process/env.cmd new file mode 100644 index 0000000..62675cf --- /dev/null +++ b/tests/resources/process/env.cmd @@ -0,0 +1,2 @@ +@ECHO OFF
+SET
diff --git a/tests/resources/process/helloworld.sh b/tests/resources/process/helloworld.sh new file mode 100755 index 0000000..0c4aefc --- /dev/null +++ b/tests/resources/process/helloworld.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Hello, world." diff --git a/tests/resources/process/pwd.bat b/tests/resources/process/pwd.bat new file mode 100644 index 0000000..82e4fb6 --- /dev/null +++ b/tests/resources/process/pwd.bat @@ -0,0 +1,2 @@ +@ECHO OFF
+ECHO %CD%
diff --git a/tests/resources/push.sh b/tests/resources/push.sh index 3e77fb5..648c2ad 100644 --- a/tests/resources/push.sh +++ b/tests/resources/push.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash #creates push_src repo for libgit2 push tests. set -eu diff --git a/tests/resources/pushoptions.git/HEAD b/tests/resources/pushoptions.git/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/tests/resources/pushoptions.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/resources/pushoptions.git/branches/.gitignore b/tests/resources/pushoptions.git/branches/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/resources/pushoptions.git/branches/.gitignore diff --git a/tests/resources/pushoptions.git/config b/tests/resources/pushoptions.git/config new file mode 100644 index 0000000..23d3978 --- /dev/null +++ b/tests/resources/pushoptions.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[receive] + advertisePushOptions = true diff --git a/tests/resources/pushoptions.git/description b/tests/resources/pushoptions.git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/tests/resources/pushoptions.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/resources/pushoptions.git/hooks/pre-receive b/tests/resources/pushoptions.git/hooks/pre-receive new file mode 100755 index 0000000..24f48d3 --- /dev/null +++ b/tests/resources/pushoptions.git/hooks/pre-receive @@ -0,0 +1,3 @@ +#!/bin/sh +printf "${GIT_PUSH_OPTION_1}${GIT_PUSH_OPTION_2}${GIT_PUSH_OPTION_3}" > "${GIT_PUSH_OPTION_0}" +exit 0 diff --git a/tests/resources/pushoptions.git/info/exclude b/tests/resources/pushoptions.git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/tests/resources/pushoptions.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/resources/pushoptions.git/objects/info/.gitignore b/tests/resources/pushoptions.git/objects/info/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/resources/pushoptions.git/objects/info/.gitignore diff --git a/tests/resources/pushoptions.git/objects/pack/.gitignore b/tests/resources/pushoptions.git/objects/pack/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/resources/pushoptions.git/objects/pack/.gitignore diff --git a/tests/resources/pushoptions.git/refs/heads/.gitignore b/tests/resources/pushoptions.git/refs/heads/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/resources/pushoptions.git/refs/heads/.gitignore diff --git a/tests/resources/pushoptions.git/refs/tags/.gitignore b/tests/resources/pushoptions.git/refs/tags/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/resources/pushoptions.git/refs/tags/.gitignore diff --git a/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG b/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG new file mode 100644 index 0000000..ea450f9 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +New file diff --git a/tests/resources/status_skiphash/.gitted/HEAD b/tests/resources/status_skiphash/.gitted/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/resources/status_skiphash/.gitted/MERGE_RR b/tests/resources/status_skiphash/.gitted/MERGE_RR new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/MERGE_RR diff --git a/tests/resources/status_skiphash/.gitted/config b/tests/resources/status_skiphash/.gitted/config new file mode 100644 index 0000000..16aebb6 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true +[index] + skipHash = true diff --git a/tests/resources/status_skiphash/.gitted/description b/tests/resources/status_skiphash/.gitted/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/resources/status_skiphash/.gitted/index b/tests/resources/status_skiphash/.gitted/index Binary files differnew file mode 100644 index 0000000..1963fe0 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/index diff --git a/tests/resources/status_skiphash/.gitted/info/exclude b/tests/resources/status_skiphash/.gitted/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/resources/status_skiphash/.gitted/logs/HEAD b/tests/resources/status_skiphash/.gitted/logs/HEAD new file mode 100644 index 0000000..35e1a74 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 34f4c90b237fcb4c677772a6093f3cba239c41a5 Parnic <github@parnic.com> 1708097798 -0600 commit (initial): New file diff --git a/tests/resources/status_skiphash/.gitted/logs/refs/heads/main b/tests/resources/status_skiphash/.gitted/logs/refs/heads/main new file mode 100644 index 0000000..35e1a74 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 34f4c90b237fcb4c677772a6093f3cba239c41a5 Parnic <github@parnic.com> 1708097798 -0600 commit (initial): New file diff --git a/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 b/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 Binary files differnew file mode 100644 index 0000000..0513158 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 diff --git a/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 b/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 Binary files differnew file mode 100644 index 0000000..7c48fa4 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 diff --git a/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c b/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c Binary files differnew file mode 100644 index 0000000..c685321 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c diff --git a/tests/resources/status_skiphash/.gitted/refs/heads/main b/tests/resources/status_skiphash/.gitted/refs/heads/main new file mode 100644 index 0000000..693a12b --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/refs/heads/main @@ -0,0 +1 @@ +34f4c90b237fcb4c677772a6093f3cba239c41a5 diff --git a/tests/resources/status_skiphash/new_file b/tests/resources/status_skiphash/new_file new file mode 100644 index 0000000..badcfca --- /dev/null +++ b/tests/resources/status_skiphash/new_file @@ -0,0 +1 @@ +new_file
diff --git a/tests/resources/testrepo/.gitted/config b/tests/resources/testrepo/.gitted/config index d011401..04d750a 100644 --- a/tests/resources/testrepo/.gitted/config +++ b/tests/resources/testrepo/.gitted/config @@ -3,6 +3,8 @@ filemode = true bare = false logallrefupdates = true +[extensions] + worktreeconfig = true [remote "test"] url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* diff --git a/tests/resources/testrepo/.gitted/config.worktree b/tests/resources/testrepo/.gitted/config.worktree new file mode 100644 index 0000000..df9f0ca --- /dev/null +++ b/tests/resources/testrepo/.gitted/config.worktree @@ -0,0 +1,2 @@ +[worktreetest] + config = mainrepo diff --git a/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree new file mode 100644 index 0000000..7a130a7 --- /dev/null +++ b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree @@ -0,0 +1,2 @@ +[worktreetest] + config = worktreerepo diff --git a/tests/util/assert.c b/tests/util/assert.c index 3babc47..4644f35 100644 --- a/tests/util/assert.c +++ b/tests/util/assert.c @@ -92,7 +92,8 @@ void test_assert__argument_with_void_return_type(void) git_error_clear(); cl_assert_equal_p(foo, fn_returns_string(foo)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); + cl_assert_equal_s("no error", git_error_last()->message); } void test_assert__internal(void) diff --git a/tests/util/errors.c b/tests/util/errors.c index 78654a7..f9b85a6 100644 --- a/tests/util/errors.c +++ b/tests/util/errors.c @@ -5,7 +5,10 @@ void test_errors__public_api(void) char *str_in_error; git_error_clear(); - cl_assert(git_error_last() == NULL); + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_set_oom(); @@ -23,7 +26,9 @@ void test_errors__public_api(void) cl_assert(str_in_error != NULL); git_error_clear(); - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); } #include "common.h" @@ -35,7 +40,9 @@ void test_errors__new_school(void) char *str_in_error; git_error_clear(); - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_set_oom(); /* internal fn */ @@ -53,7 +60,9 @@ void test_errors__new_school(void) cl_assert(str_in_error != NULL); git_error_clear(); - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); do { struct stat st; @@ -88,52 +97,32 @@ void test_errors__new_school(void) void test_errors__restore(void) { - git_error_state err_state = {0}; + git_error *last_error; git_error_clear(); - cl_assert(git_error_last() == NULL); - - cl_assert_equal_i(0, git_error_state_capture(&err_state, 0)); - - memset(&err_state, 0x0, sizeof(git_error_state)); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp("no error", git_error_last()->message) == 0); git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - - git_error_set(99, "Bar: %s", "foo"); - - git_error_state_restore(&err_state); - - cl_assert_equal_i(42, git_error_last()->klass); - cl_assert_equal_s("Foo: bar", git_error_last()->message); -} - -void test_errors__free_state(void) -{ - git_error_state err_state = {0}; + cl_assert(git_error_save(&last_error) == 0); git_error_clear(); - - git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp("no error", git_error_last()->message) == 0); git_error_set(99, "Bar: %s", "foo"); - git_error_state_free(&err_state); - - cl_assert_equal_i(99, git_error_last()->klass); - cl_assert_equal_s("Bar: foo", git_error_last()->message); + git_error_restore(last_error); - git_error_state_restore(&err_state); - - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last()->klass == 42); + cl_assert(strcmp("Foo: bar", git_error_last()->message) == 0); } void test_errors__restore_oom(void) { - git_error_state err_state = {0}; + git_error *last_error; const git_error *oom_error = NULL; git_error_clear(); @@ -141,15 +130,18 @@ void test_errors__restore_oom(void) git_error_set_oom(); /* internal fn */ oom_error = git_error_last(); cl_assert(oom_error); + cl_assert(oom_error->klass == GIT_ERROR_NOMEMORY); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass); - cl_assert_equal_s("Out of memory", err_state.error_msg.message); + cl_assert(git_error_save(&last_error) == 0); + cl_assert(last_error->klass == GIT_ERROR_NOMEMORY); + cl_assert(strcmp("Out of memory", last_error->message) == 0); - git_error_state_restore(&err_state); + git_error_clear(); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp("no error", git_error_last()->message) == 0); + git_error_restore(last_error); cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); cl_assert_(git_error_last() == oom_error, "static oom error not restored"); @@ -204,11 +196,15 @@ void test_errors__integer_overflow_sets_oom(void) git_error_clear(); cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_clear(); cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_clear(); cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); diff --git a/tests/util/path.c b/tests/util/path.c deleted file mode 100644 index 02ec42f..0000000 --- a/tests/util/path.c +++ /dev/null @@ -1,768 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "fs_path.h" - -#ifndef GIT_WIN32 -# include <unistd.h> -#endif - -static char *path_save; - -void test_path__initialize(void) -{ - path_save = cl_getenv("PATH"); -} - -void test_path__cleanup(void) -{ - cl_setenv("PATH", path_save); - git__free(path_save); - path_save = NULL; -} - -static void -check_dirname(const char *A, const char *B) -{ - git_str dir = GIT_STR_INIT; - char *dir2; - - cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); - cl_assert_equal_s(B, dir.ptr); - git_str_dispose(&dir); - - cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); - cl_assert_equal_s(B, dir2); - git__free(dir2); -} - -static void -check_basename(const char *A, const char *B) -{ - git_str base = GIT_STR_INIT; - char *base2; - - cl_assert(git_fs_path_basename_r(&base, A) >= 0); - cl_assert_equal_s(B, base.ptr); - git_str_dispose(&base); - - cl_assert((base2 = git_fs_path_basename(A)) != NULL); - cl_assert_equal_s(B, base2); - git__free(base2); -} - -static void -check_joinpath(const char *path_a, const char *path_b, const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void -check_joinpath_n( - const char *path_a, - const char *path_b, - const char *path_c, - const char *path_d, - const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&joined_path, '/', 4, - path_a, path_b, path_c, path_d)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void check_setenv(const char* name, const char* value) -{ - char* check; - - cl_git_pass(cl_setenv(name, value)); - check = cl_getenv(name); - - if (value) - cl_assert_equal_s(value, check); - else - cl_assert(check == NULL); - - git__free(check); -} - -/* get the dirname of a path */ -void test_path__00_dirname(void) -{ - check_dirname(NULL, "."); - check_dirname("", "."); - check_dirname("a", "."); - check_dirname("/", "/"); - check_dirname("/usr", "/"); - check_dirname("/usr/", "/"); - check_dirname("/usr/lib", "/usr"); - check_dirname("/usr/lib/", "/usr"); - check_dirname("/usr/lib//", "/usr"); - check_dirname("usr/lib", "usr"); - check_dirname("usr/lib/", "usr"); - check_dirname("usr/lib//", "usr"); - check_dirname(".git/", "."); - - check_dirname(REP16("/abc"), REP15("/abc")); - -#ifdef GIT_WIN32 - check_dirname("C:/", "C:/"); - check_dirname("C:", "C:/"); - check_dirname("C:/path/", "C:/"); - check_dirname("C:/path", "C:/"); - check_dirname("//computername/", "//computername/"); - check_dirname("//computername", "//computername/"); - check_dirname("//computername/path/", "//computername/"); - check_dirname("//computername/path", "//computername/"); - check_dirname("//computername/sub/path/", "//computername/sub"); - check_dirname("//computername/sub/path", "//computername/sub"); -#endif -} - -/* get the base name of a path */ -void test_path__01_basename(void) -{ - check_basename(NULL, "."); - check_basename("", "."); - check_basename("a", "a"); - check_basename("/", "/"); - check_basename("/usr", "usr"); - check_basename("/usr/", "usr"); - check_basename("/usr/lib", "lib"); - check_basename("/usr/lib//", "lib"); - check_basename("usr/lib", "lib"); - - check_basename(REP16("/abc"), "abc"); - check_basename(REP1024("/abc"), "abc"); -} - -/* properly join path components */ -void test_path__05_joins(void) -{ - check_joinpath("", "", ""); - check_joinpath("", "a", "a"); - check_joinpath("", "/a", "/a"); - check_joinpath("a", "", "a/"); - check_joinpath("a", "/", "a/"); - check_joinpath("a", "b", "a/b"); - check_joinpath("/", "a", "/a"); - check_joinpath("/", "", "/"); - check_joinpath("/a", "/b", "/a/b"); - check_joinpath("/a", "/b/", "/a/b/"); - check_joinpath("/a/", "b/", "/a/b/"); - check_joinpath("/a/", "/b/", "/a/b/"); - - check_joinpath("/abcd", "/defg", "/abcd/defg"); - check_joinpath("/abcd", "/defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); - check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); - check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); - - check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); - check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); - check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); - - check_joinpath(REP1024("aaaa"), REP1024("bbbb"), - REP1024("aaaa") "/" REP1024("bbbb")); - check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), - REP1024("/aaaa") REP1024("/bbbb")); -} - -/* properly join path components for more than one path */ -void test_path__06_long_joins(void) -{ - check_joinpath_n("", "", "", "", ""); - check_joinpath_n("", "a", "", "", "a/"); - check_joinpath_n("a", "", "", "", "a/"); - check_joinpath_n("", "", "", "a", "a"); - check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); - check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); - check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); - check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); - check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); - - check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), - REP1024("a") "/" REP1024("b") "/" - REP1024("c") "/" REP1024("d")); - check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), - REP1024("/a") REP1024("/b") - REP1024("/c") REP1024("/d")); -} - - -static void -check_path_to_dir( - const char* path, - const char* expected) -{ - git_str tgt = GIT_STR_INIT; - - git_str_sets(&tgt, path); - cl_git_pass(git_fs_path_to_dir(&tgt)); - cl_assert_equal_s(expected, tgt.ptr); - - git_str_dispose(&tgt); -} - -static void -check_string_to_dir( - const char* path, - size_t maxlen, - const char* expected) -{ - size_t len = strlen(path); - char *buf = git__malloc(len + 2); - cl_assert(buf); - - strncpy(buf, path, len + 2); - - git_fs_path_string_to_dir(buf, maxlen); - - cl_assert_equal_s(expected, buf); - - git__free(buf); -} - -/* convert paths to dirs */ -void test_path__07_path_to_dir(void) -{ - check_path_to_dir("", ""); - check_path_to_dir(".", "./"); - check_path_to_dir("./", "./"); - check_path_to_dir("a/", "a/"); - check_path_to_dir("ab", "ab/"); - /* make sure we try just under and just over an expansion that will - * require a realloc - */ - check_path_to_dir("abcdef", "abcdef/"); - check_path_to_dir("abcdefg", "abcdefg/"); - check_path_to_dir("abcdefgh", "abcdefgh/"); - check_path_to_dir("abcdefghi", "abcdefghi/"); - check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); - check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); - - check_string_to_dir("", 1, ""); - check_string_to_dir(".", 1, "."); - check_string_to_dir(".", 2, "./"); - check_string_to_dir(".", 3, "./"); - check_string_to_dir("abcd", 3, "abcd"); - check_string_to_dir("abcd", 4, "abcd"); - check_string_to_dir("abcd", 5, "abcd/"); - check_string_to_dir("abcd", 6, "abcd/"); -} - -/* join path to itself */ -void test_path__08_self_join(void) -{ - git_str path = GIT_STR_INIT; - size_t asize = 0; - - asize = path.asize; - cl_git_pass(git_str_sets(&path, "/foo")); - cl_assert_equal_s(path.ptr, "/foo"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); - cl_git_pass(git_str_sets(&path, "/foo/bar")); - - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); - cl_assert_equal_s(path.ptr, "/bar/baz"); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); - cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); -} - -static void check_percent_decoding(const char *expected_result, const char *input) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git__percent_decode(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -void test_path__09_percent_decode(void) -{ - check_percent_decoding("abcd", "abcd"); - check_percent_decoding("a2%", "a2%"); - check_percent_decoding("a2%3", "a2%3"); - check_percent_decoding("a2%%3", "a2%%3"); - check_percent_decoding("a2%3z", "a2%3z"); - check_percent_decoding("a,", "a%2c"); - check_percent_decoding("a21", "a2%31"); - check_percent_decoding("a2%1", "a2%%31"); - check_percent_decoding("a bc ", "a%20bc%20"); - check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); -} - -static void check_fromurl(const char *expected_result, const char *input, int should_fail) -{ - git_str buf = GIT_STR_INIT; - - assert(should_fail || expected_result); - - if (!should_fail) { - cl_git_pass(git_fs_path_fromurl(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - } else - cl_git_fail(git_fs_path_fromurl(&buf, input)); - - git_str_dispose(&buf); -} - -#ifdef GIT_WIN32 -#define ABS_PATH_MARKER "" -#else -#define ABS_PATH_MARKER "/" -#endif - -void test_path__10_fromurl(void) -{ - /* Failing cases */ - check_fromurl(NULL, "a", 1); - check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:///", 1); - check_fromurl(NULL, "file:////", 1); - check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); - - /* Passing cases */ - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); -} - -typedef struct { - int expect_idx; - int cancel_after; - char **expect; -} check_walkup_info; - -#define CANCEL_VALUE 1234 - -static int check_one_walkup_step(void *ref, const char *path) -{ - check_walkup_info *info = (check_walkup_info *)ref; - - if (!info->cancel_after) { - cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); - return CANCEL_VALUE; - } - info->cancel_after--; - - cl_assert(info->expect[info->expect_idx] != NULL); - cl_assert_equal_s(info->expect[info->expect_idx], path); - info->expect_idx++; - - return 0; -} - -void test_path__11_walkup(void) -{ - git_str p = GIT_STR_INIT; - - char *expect[] = { - /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 7 */ "this_is_a_path", "", NULL, - /* 8 */ "this_is_a_path/", "", NULL, - /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, - /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, - /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, - /* 12 */ "a/b/c/", "a/b/", "a/", NULL, - /* 13 */ "", NULL, - /* 14 */ "/", NULL, - /* 15 */ NULL - }; - - char *root[] = { - /* 1 */ NULL, - /* 2 */ NULL, - /* 3 */ "/", - /* 4 */ "", - /* 5 */ "/a/b", - /* 6 */ "/a/b/", - /* 7 */ NULL, - /* 8 */ NULL, - /* 9 */ NULL, - /* 10 */ NULL, - /* 11 */ NULL, - /* 12 */ "a/", - /* 13 */ NULL, - /* 14 */ NULL, - }; - - int i, j; - check_walkup_info info; - - info.expect = expect; - info.cancel_after = -1; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.expect_idx = i; - cl_git_pass( - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - cl_assert_equal_s(p.ptr, expect[i]); - cl_assert(expect[info.expect_idx] == NULL); - i = info.expect_idx; - } - - git_str_dispose(&p); -} - -void test_path__11a_walkup_cancel(void) -{ - git_str p = GIT_STR_INIT; - int cancel[] = { 3, 2, 1, 0 }; - char *expect[] = { - "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, - "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, - "/a/b/c/d/e", "[CANCEL]", NULL, - "[CANCEL]", NULL, - NULL - }; - char *root[] = { NULL, NULL, "/", "", NULL }; - int i, j; - check_walkup_info info; - - info.expect = expect; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.cancel_after = cancel[j]; - info.expect_idx = i; - - cl_assert_equal_i( - CANCEL_VALUE, - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - /* skip to next run of expectations */ - while (expect[i] != NULL) i++; - } - - git_str_dispose(&p); -} - -void test_path__12_offset_to_path_root(void) -{ - cl_assert(git_fs_path_root("non/rooted/path") == -1); - cl_assert(git_fs_path_root("/rooted/path") == 0); - -#ifdef GIT_WIN32 - /* Windows specific tests */ - cl_assert(git_fs_path_root("C:non/rooted/path") == -1); - cl_assert(git_fs_path_root("C:/rooted/path") == 2); - cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); - cl_assert(git_fs_path_root("//computername/sharefolder") == 14); - cl_assert(git_fs_path_root("//computername") == -1); -#endif -} - -#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" - -void test_path__13_cannot_prettify_a_non_existing_file(void) -{ - git_str p = GIT_STR_INIT; - - cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); - - git_str_dispose(&p); -} - -void test_path__14_apply_relative(void) -{ - git_str p = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&p, "/this/is/a/base")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../test")); - cl_assert_equal_s("/this/is/a/test", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); - cl_assert_equal_s("/this/is/the/end", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); - cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); - cl_assert_equal_s("/this/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../")); - cl_assert_equal_s("/", p.ptr); - - cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); - - - cl_git_pass(git_str_sets(&p, "d:/another/test")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../..")); - cl_assert_equal_s("d:/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); - cl_assert_equal_s("d:/from/here/and/back/", p.ptr); - - - cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); - cl_assert_equal_s("https://my.url.com/another.git", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); - cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "..")); - cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); - cl_assert_equal_s("https://", p.ptr); - - - cl_git_pass(git_str_sets(&p, "../../this/is/relative")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); - cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); - cl_assert_equal_s("../../that", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../there")); - cl_assert_equal_s("../../there", p.ptr); - git_str_dispose(&p); -} - -static void assert_resolve_relative( - git_str *buf, const char *expected, const char *path) -{ - cl_git_pass(git_str_sets(buf, path)); - cl_git_pass(git_fs_path_resolve_relative(buf, 0)); - cl_assert_equal_s(expected, buf->ptr); -} - -void test_path__15_resolve_relative(void) -{ - git_str buf = GIT_STR_INIT; - - assert_resolve_relative(&buf, "", ""); - assert_resolve_relative(&buf, "", "."); - assert_resolve_relative(&buf, "", "./"); - assert_resolve_relative(&buf, "..", ".."); - assert_resolve_relative(&buf, "../", "../"); - assert_resolve_relative(&buf, "..", "./.."); - assert_resolve_relative(&buf, "../", "./../"); - assert_resolve_relative(&buf, "../", "../."); - assert_resolve_relative(&buf, "../", ".././"); - assert_resolve_relative(&buf, "../..", "../.."); - assert_resolve_relative(&buf, "../../", "../../"); - - assert_resolve_relative(&buf, "/", "/"); - assert_resolve_relative(&buf, "/", "/."); - - assert_resolve_relative(&buf, "", "a/.."); - assert_resolve_relative(&buf, "", "a/../"); - assert_resolve_relative(&buf, "", "a/../."); - - assert_resolve_relative(&buf, "/a", "/a"); - assert_resolve_relative(&buf, "/a/", "/a/."); - assert_resolve_relative(&buf, "/", "/a/../"); - assert_resolve_relative(&buf, "/", "/a/../."); - assert_resolve_relative(&buf, "/", "/a/.././"); - - assert_resolve_relative(&buf, "a", "a"); - assert_resolve_relative(&buf, "a/", "a/"); - assert_resolve_relative(&buf, "a/", "a/."); - assert_resolve_relative(&buf, "a/", "a/./"); - - assert_resolve_relative(&buf, "a/b", "a//b"); - assert_resolve_relative(&buf, "a/b/c", "a/b/c"); - assert_resolve_relative(&buf, "b/c", "./b/c"); - assert_resolve_relative(&buf, "a/c", "a/./c"); - assert_resolve_relative(&buf, "a/b/", "a/b/."); - - assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); - assert_resolve_relative(&buf, "/", "////"); - assert_resolve_relative(&buf, "/a", "///a"); - assert_resolve_relative(&buf, "/", "///."); - assert_resolve_relative(&buf, "/", "///a/.."); - - assert_resolve_relative(&buf, "../../path", "../../test//../././path"); - assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); - - cl_git_pass(git_str_sets(&buf, "/..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/./..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/.//..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.././../a")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "////..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - /* things that start with Windows network paths */ -#ifdef GIT_WIN32 - assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "//a/", "//a/b/.."); - assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); - - cl_git_pass(git_str_sets(&buf, "//a/b/../..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); -#else - assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "/a/", "//a/b/.."); - assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); - assert_resolve_relative(&buf, "/", "//a/b/../.."); -#endif - - git_str_dispose(&buf); -} - -#define assert_common_dirlen(i, p, q) \ - cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); - -void test_path__16_resolve_relative(void) -{ - assert_common_dirlen(0, "", ""); - assert_common_dirlen(0, "", "bar.txt"); - assert_common_dirlen(0, "foo.txt", "bar.txt"); - assert_common_dirlen(0, "foo.txt", ""); - assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); - assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); - - assert_common_dirlen(1, "/one.txt", "/two.txt"); - assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); - assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); - - assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); - assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); -} - -static void fix_path(git_str *s) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(s); -#else - char* c; - - for (c = s->ptr; *c; c++) { - if (*c == '/') - *c = '\\'; - } -#endif -} - -void test_path__find_exe_in_path(void) -{ - char *orig_path; - git_str sandbox_path = GIT_STR_INIT; - git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, - dummy_path = GIT_STR_INIT; - -#ifdef GIT_WIN32 - static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; - static const char *bogus_path_2 = "e:\\non\\existent"; -#else - static const char *bogus_path_1 = "/this/path/does/not/exist/"; - static const char *bogus_path_2 = "/non/existent"; -#endif - - orig_path = cl_getenv("PATH"); - - git_str_puts(&sandbox_path, clar_sandbox_path()); - git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); - cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); - - fix_path(&sandbox_path); - fix_path(&dummy_path); - - cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", - bogus_path_1, GIT_PATH_LIST_SEPARATOR, - orig_path, GIT_PATH_LIST_SEPARATOR, - sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, - bogus_path_2)); - - check_setenv("PATH", new_path.ptr); - - cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); - cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); - - cl_assert_equal_s(full_path.ptr, dummy_path.ptr); - - git_str_dispose(&full_path); - git_str_dispose(&new_path); - git_str_dispose(&dummy_path); - git_str_dispose(&sandbox_path); - git__free(orig_path); -} - -void test_path__validate_current_user_ownership(void) -{ - bool is_cur; - - cl_must_pass(p_mkdir("testdir", 0777)); - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir")); - cl_assert_equal_i(is_cur, 1); - - cl_git_rewritefile("testfile", "This is a test file."); - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile")); - cl_assert_equal_i(is_cur, 1); - -#ifdef GIT_WIN32 - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\")); - cl_assert_equal_i(is_cur, 0); - - cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist")); -#else - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/")); - cl_assert_equal_i(is_cur, (geteuid() == 0)); - - cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); -#endif -} diff --git a/tests/util/path/core.c b/tests/util/path/core.c index f30f6c0..d1935a8 100644 --- a/tests/util/path/core.c +++ b/tests/util/path/core.c @@ -1,6 +1,784 @@ #include "clar_libgit2.h" +#include "futils.h" #include "fs_path.h" +#ifndef GIT_WIN32 +# include <unistd.h> +#endif + +static char *path_save; + +void test_path_core__initialize(void) +{ + path_save = cl_getenv("PATH"); +} + +void test_path_core__cleanup(void) +{ + cl_setenv("PATH", path_save); + git__free(path_save); + path_save = NULL; +} + +static void +check_dirname(const char *A, const char *B) +{ + git_str dir = GIT_STR_INIT; + char *dir2; + + cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); + cl_assert_equal_s(B, dir.ptr); + git_str_dispose(&dir); + + cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); + cl_assert_equal_s(B, dir2); + git__free(dir2); +} + +static void +check_basename(const char *A, const char *B) +{ + git_str base = GIT_STR_INIT; + char *base2; + + cl_assert(git_fs_path_basename_r(&base, A) >= 0); + cl_assert_equal_s(B, base.ptr); + git_str_dispose(&base); + + cl_assert((base2 = git_fs_path_basename(A)) != NULL); + cl_assert_equal_s(B, base2); + git__free(base2); +} + +static void +check_joinpath(const char *path_a, const char *path_b, const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void +check_joinpath_n( + const char *path_a, + const char *path_b, + const char *path_c, + const char *path_d, + const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&joined_path, '/', 4, + path_a, path_b, path_c, path_d)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void check_setenv(const char* name, const char* value) +{ + char* check; + + cl_git_pass(cl_setenv(name, value)); + check = cl_getenv(name); + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + + git__free(check); +} + +/* get the dirname of a path */ +void test_path_core__00_dirname(void) +{ + check_dirname(NULL, "."); + check_dirname("", "."); + check_dirname("a", "."); + check_dirname("/", "/"); + check_dirname("/usr", "/"); + check_dirname("/usr/", "/"); + check_dirname("/usr/lib", "/usr"); + check_dirname("/usr/lib/", "/usr"); + check_dirname("/usr/lib//", "/usr"); + check_dirname("usr/lib", "usr"); + check_dirname("usr/lib/", "usr"); + check_dirname("usr/lib//", "usr"); + check_dirname(".git/", "."); + + check_dirname(REP16("/abc"), REP15("/abc")); + +#ifdef GIT_WIN32 + check_dirname("C:/", "C:/"); + check_dirname("C:", "C:/"); + check_dirname("C:/path/", "C:/"); + check_dirname("C:/path", "C:/"); + check_dirname("//computername/", "//computername/"); + check_dirname("//computername", "//computername/"); + check_dirname("//computername/path/", "//computername/"); + check_dirname("//computername/path", "//computername/"); + check_dirname("//computername/sub/path/", "//computername/sub"); + check_dirname("//computername/sub/path", "//computername/sub"); +#endif +} + +/* get the base name of a path */ +void test_path_core__01_basename(void) +{ + check_basename(NULL, "."); + check_basename("", "."); + check_basename("a", "a"); + check_basename("/", "/"); + check_basename("/usr", "usr"); + check_basename("/usr/", "usr"); + check_basename("/usr/lib", "lib"); + check_basename("/usr/lib//", "lib"); + check_basename("usr/lib", "lib"); + + check_basename(REP16("/abc"), "abc"); + check_basename(REP1024("/abc"), "abc"); +} + +/* properly join path components */ +void test_path_core__05_joins(void) +{ + check_joinpath("", "", ""); + check_joinpath("", "a", "a"); + check_joinpath("", "/a", "/a"); + check_joinpath("a", "", "a/"); + check_joinpath("a", "/", "a/"); + check_joinpath("a", "b", "a/b"); + check_joinpath("/", "a", "/a"); + check_joinpath("/", "", "/"); + check_joinpath("/a", "/b", "/a/b"); + check_joinpath("/a", "/b/", "/a/b/"); + check_joinpath("/a/", "b/", "/a/b/"); + check_joinpath("/a/", "/b/", "/a/b/"); + + check_joinpath("/abcd", "/defg", "/abcd/defg"); + check_joinpath("/abcd", "/defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); + check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); + check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); + + check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); + check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); + check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); + + check_joinpath(REP1024("aaaa"), REP1024("bbbb"), + REP1024("aaaa") "/" REP1024("bbbb")); + check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), + REP1024("/aaaa") REP1024("/bbbb")); +} + +/* properly join path components for more than one path */ +void test_path_core__06_long_joins(void) +{ + check_joinpath_n("", "", "", "", ""); + check_joinpath_n("", "a", "", "", "a/"); + check_joinpath_n("a", "", "", "", "a/"); + check_joinpath_n("", "", "", "a", "a"); + check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); + check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); + check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); + check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); + check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); + + check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), + REP1024("a") "/" REP1024("b") "/" + REP1024("c") "/" REP1024("d")); + check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), + REP1024("/a") REP1024("/b") + REP1024("/c") REP1024("/d")); +} + + +static void +check_path_to_dir( + const char* path, + const char* expected) +{ + git_str tgt = GIT_STR_INIT; + + git_str_sets(&tgt, path); + cl_git_pass(git_fs_path_to_dir(&tgt)); + cl_assert_equal_s(expected, tgt.ptr); + + git_str_dispose(&tgt); +} + +static void +check_string_to_dir( + const char* path, + size_t maxlen, + const char* expected) +{ + size_t len = strlen(path); + char *buf = git__malloc(len + 2); + cl_assert(buf); + + strncpy(buf, path, len + 2); + + git_fs_path_string_to_dir(buf, maxlen); + + cl_assert_equal_s(expected, buf); + + git__free(buf); +} + +/* convert paths to dirs */ +void test_path_core__07_path_to_dir(void) +{ + check_path_to_dir("", ""); + check_path_to_dir(".", "./"); + check_path_to_dir("./", "./"); + check_path_to_dir("a/", "a/"); + check_path_to_dir("ab", "ab/"); + /* make sure we try just under and just over an expansion that will + * require a realloc + */ + check_path_to_dir("abcdef", "abcdef/"); + check_path_to_dir("abcdefg", "abcdefg/"); + check_path_to_dir("abcdefgh", "abcdefgh/"); + check_path_to_dir("abcdefghi", "abcdefghi/"); + check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); + check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); + + check_string_to_dir("", 1, ""); + check_string_to_dir(".", 1, "."); + check_string_to_dir(".", 2, "./"); + check_string_to_dir(".", 3, "./"); + check_string_to_dir("abcd", 3, "abcd"); + check_string_to_dir("abcd", 4, "abcd"); + check_string_to_dir("abcd", 5, "abcd/"); + check_string_to_dir("abcd", 6, "abcd/"); +} + +/* join path to itself */ +void test_path_core__08_self_join(void) +{ + git_str path = GIT_STR_INIT; + size_t asize = 0; + + asize = path.asize; + cl_git_pass(git_str_sets(&path, "/foo")); + cl_assert_equal_s(path.ptr, "/foo"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); + cl_git_pass(git_str_sets(&path, "/foo/bar")); + + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); + cl_assert_equal_s(path.ptr, "/bar/baz"); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); + cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); +} + +static void check_percent_decoding(const char *expected_result, const char *input) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git__percent_decode(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +void test_path_core__09_percent_decode(void) +{ + check_percent_decoding("abcd", "abcd"); + check_percent_decoding("a2%", "a2%"); + check_percent_decoding("a2%3", "a2%3"); + check_percent_decoding("a2%%3", "a2%%3"); + check_percent_decoding("a2%3z", "a2%3z"); + check_percent_decoding("a,", "a%2c"); + check_percent_decoding("a21", "a2%31"); + check_percent_decoding("a2%1", "a2%%31"); + check_percent_decoding("a bc ", "a%20bc%20"); + check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); +} + +static void check_fromurl(const char *expected_result, const char *input, int should_fail) +{ + git_str buf = GIT_STR_INIT; + + assert(should_fail || expected_result); + + if (!should_fail) { + cl_git_pass(git_fs_path_fromurl(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + } else + cl_git_fail(git_fs_path_fromurl(&buf, input)); + + git_str_dispose(&buf); +} + +#ifdef GIT_WIN32 +#define ABS_PATH_MARKER "" +#else +#define ABS_PATH_MARKER "/" +#endif + +void test_path_core__10_fromurl(void) +{ + /* Failing cases */ + check_fromurl(NULL, "a", 1); + check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:///", 1); + check_fromurl(NULL, "file:////", 1); + check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); + + /* Passing cases */ + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); +} + +typedef struct { + int expect_idx; + int cancel_after; + char **expect; +} check_walkup_info; + +#define CANCEL_VALUE 1234 + +static int check_one_walkup_step(void *ref, const char *path) +{ + check_walkup_info *info = (check_walkup_info *)ref; + + if (!info->cancel_after) { + cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); + return CANCEL_VALUE; + } + info->cancel_after--; + + cl_assert(info->expect[info->expect_idx] != NULL); + cl_assert_equal_s(info->expect[info->expect_idx], path); + info->expect_idx++; + + return 0; +} + +void test_path_core__11_walkup(void) +{ + git_str p = GIT_STR_INIT; + + char *expect[] = { + /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 7 */ "this_is_a_path", "", NULL, + /* 8 */ "this_is_a_path/", "", NULL, + /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, + /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, + /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, + /* 12 */ "a/b/c/", "a/b/", "a/", NULL, + /* 13 */ "", NULL, + /* 14 */ "/", NULL, + /* 15 */ NULL + }; + + char *root[] = { + /* 1 */ NULL, + /* 2 */ NULL, + /* 3 */ "/", + /* 4 */ "", + /* 5 */ "/a/b", + /* 6 */ "/a/b/", + /* 7 */ NULL, + /* 8 */ NULL, + /* 9 */ NULL, + /* 10 */ NULL, + /* 11 */ NULL, + /* 12 */ "a/", + /* 13 */ NULL, + /* 14 */ NULL, + }; + + int i, j; + check_walkup_info info; + + info.expect = expect; + info.cancel_after = -1; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.expect_idx = i; + cl_git_pass( + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + cl_assert_equal_s(p.ptr, expect[i]); + cl_assert(expect[info.expect_idx] == NULL); + i = info.expect_idx; + } + + git_str_dispose(&p); +} + +void test_path_core__11a_walkup_cancel(void) +{ + git_str p = GIT_STR_INIT; + int cancel[] = { 3, 2, 1, 0 }; + char *expect[] = { + "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, + "/a/b/c/d/e", "[CANCEL]", NULL, + "[CANCEL]", NULL, + NULL + }; + char *root[] = { NULL, NULL, "/", "", NULL }; + int i, j; + check_walkup_info info; + + info.expect = expect; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.cancel_after = cancel[j]; + info.expect_idx = i; + + cl_assert_equal_i( + CANCEL_VALUE, + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + /* skip to next run of expectations */ + while (expect[i] != NULL) i++; + } + + git_str_dispose(&p); +} + +void test_path_core__12_offset_to_path_root(void) +{ + cl_assert(git_fs_path_root("non/rooted/path") == -1); + cl_assert(git_fs_path_root("/rooted/path") == 0); + +#ifdef GIT_WIN32 + /* Windows specific tests */ + cl_assert(git_fs_path_root("C:non/rooted/path") == -1); + cl_assert(git_fs_path_root("C:/rooted/path") == 2); + cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); + cl_assert(git_fs_path_root("//computername/sharefolder") == 14); + cl_assert(git_fs_path_root("//computername") == -1); +#endif +} + +#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" + +void test_path_core__13_cannot_prettify_a_non_existing_file(void) +{ + git_str p = GIT_STR_INIT; + + cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); + + git_str_dispose(&p); +} + +void test_path_core__14_apply_relative(void) +{ + git_str p = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&p, "/this/is/a/base")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../test")); + cl_assert_equal_s("/this/is/a/test", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); + cl_assert_equal_s("/this/is/the/end", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); + cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); + cl_assert_equal_s("/this/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../")); + cl_assert_equal_s("/", p.ptr); + + cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); + + + cl_git_pass(git_str_sets(&p, "d:/another/test")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../..")); + cl_assert_equal_s("d:/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); + cl_assert_equal_s("d:/from/here/and/back/", p.ptr); + + + cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); + cl_assert_equal_s("https://my.url.com/another.git", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); + cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "..")); + cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); + cl_assert_equal_s("https://", p.ptr); + + + cl_git_pass(git_str_sets(&p, "../../this/is/relative")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); + cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); + cl_assert_equal_s("../../that", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../there")); + cl_assert_equal_s("../../there", p.ptr); + git_str_dispose(&p); +} + +static void assert_resolve_relative( + git_str *buf, const char *expected, const char *path) +{ + cl_git_pass(git_str_sets(buf, path)); + cl_git_pass(git_fs_path_resolve_relative(buf, 0)); + cl_assert_equal_s(expected, buf->ptr); +} + +void test_path_core__15_resolve_relative(void) +{ + git_str buf = GIT_STR_INIT; + + assert_resolve_relative(&buf, "", ""); + assert_resolve_relative(&buf, "", "."); + assert_resolve_relative(&buf, "", "./"); + assert_resolve_relative(&buf, "..", ".."); + assert_resolve_relative(&buf, "../", "../"); + assert_resolve_relative(&buf, "..", "./.."); + assert_resolve_relative(&buf, "../", "./../"); + assert_resolve_relative(&buf, "../", "../."); + assert_resolve_relative(&buf, "../", ".././"); + assert_resolve_relative(&buf, "../..", "../.."); + assert_resolve_relative(&buf, "../../", "../../"); + + assert_resolve_relative(&buf, "/", "/"); + assert_resolve_relative(&buf, "/", "/."); + + assert_resolve_relative(&buf, "", "a/.."); + assert_resolve_relative(&buf, "", "a/../"); + assert_resolve_relative(&buf, "", "a/../."); + + assert_resolve_relative(&buf, "/a", "/a"); + assert_resolve_relative(&buf, "/a/", "/a/."); + assert_resolve_relative(&buf, "/", "/a/../"); + assert_resolve_relative(&buf, "/", "/a/../."); + assert_resolve_relative(&buf, "/", "/a/.././"); + + assert_resolve_relative(&buf, "a", "a"); + assert_resolve_relative(&buf, "a/", "a/"); + assert_resolve_relative(&buf, "a/", "a/."); + assert_resolve_relative(&buf, "a/", "a/./"); + + assert_resolve_relative(&buf, "a/b", "a//b"); + assert_resolve_relative(&buf, "a/b/c", "a/b/c"); + assert_resolve_relative(&buf, "b/c", "./b/c"); + assert_resolve_relative(&buf, "a/c", "a/./c"); + assert_resolve_relative(&buf, "a/b/", "a/b/."); + + assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); + assert_resolve_relative(&buf, "/", "////"); + assert_resolve_relative(&buf, "/a", "///a"); + assert_resolve_relative(&buf, "/", "///."); + assert_resolve_relative(&buf, "/", "///a/.."); + + assert_resolve_relative(&buf, "../../path", "../../test//../././path"); + assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); + + cl_git_pass(git_str_sets(&buf, "/..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/./..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/.//..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.././../a")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "////..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + /* things that start with Windows network paths */ +#ifdef GIT_WIN32 + assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "//a/", "//a/b/.."); + assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); + + cl_git_pass(git_str_sets(&buf, "//a/b/../..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); +#else + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/a/", "//a/b/.."); + assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); + assert_resolve_relative(&buf, "/", "//a/b/../.."); +#endif + + git_str_dispose(&buf); +} + +#define assert_common_dirlen(i, p, q) \ + cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); + +void test_path_core__16_resolve_relative(void) +{ + assert_common_dirlen(0, "", ""); + assert_common_dirlen(0, "", "bar.txt"); + assert_common_dirlen(0, "foo.txt", "bar.txt"); + assert_common_dirlen(0, "foo.txt", ""); + assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); + assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); + + assert_common_dirlen(1, "/one.txt", "/two.txt"); + assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); + assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); + + assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); + assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); +} + +static void fix_path(git_str *s) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(s); +#else + char* c; + + for (c = s->ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } +#endif +} + +void test_path_core__find_exe_in_path(void) +{ + char *orig_path; + git_str sandbox_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, + dummy_path = GIT_STR_INIT; + +#ifdef GIT_WIN32 + static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; + static const char *bogus_path_2 = "e:\\non\\existent"; +#else + static const char *bogus_path_1 = "/this/path/does/not/exist/"; + static const char *bogus_path_2 = "/non/existent"; +#endif + + orig_path = cl_getenv("PATH"); + + git_str_puts(&sandbox_path, clar_sandbox_path()); + git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); + cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); + + fix_path(&sandbox_path); + fix_path(&dummy_path); + + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + + check_setenv("PATH", new_path.ptr); + + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); + + cl_assert_equal_s(full_path.ptr, dummy_path.ptr); + + git_str_dispose(&full_path); + git_str_dispose(&new_path); + git_str_dispose(&dummy_path); + git_str_dispose(&sandbox_path); + git__free(orig_path); +} + +void test_path_core__validate_current_user_ownership(void) +{ + bool is_cur; + + cl_must_pass(p_mkdir("testdir", 0777)); + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir")); + cl_assert_equal_i(is_cur, 1); + + cl_git_rewritefile("testfile", "This is a test file."); + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile")); + cl_assert_equal_i(is_cur, 1); + +#ifdef GIT_WIN32 + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\")); + cl_assert_equal_i(is_cur, 0); + + cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist")); +#else + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/")); + cl_assert_equal_i(is_cur, (geteuid() == 0)); + + cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); +#endif +} + +void test_path_core__dirlen(void) +{ + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf/")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf//")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo////")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo")); + cl_assert_equal_sz(1, git_fs_path_dirlen("/")); + cl_assert_equal_sz(1, git_fs_path_dirlen("////")); + cl_assert_equal_sz(0, git_fs_path_dirlen("")); +} + static void test_make_relative( const char *expected_path, const char *path, @@ -341,3 +1119,29 @@ void test_path_core__join_unrooted_respects_funny_windows_roots(void) test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); } + +void test_path_core__is_root(void) +{ + cl_assert_equal_b(true, git_fs_path_is_root("/")); + cl_assert_equal_b(false, git_fs_path_is_root("//")); + cl_assert_equal_b(false, git_fs_path_is_root("foo/")); + cl_assert_equal_b(false, git_fs_path_is_root("/foo/")); + cl_assert_equal_b(false, git_fs_path_is_root("/foo")); + cl_assert_equal_b(false, git_fs_path_is_root("\\")); + +#ifdef GIT_WIN32 + cl_assert_equal_b(true, git_fs_path_is_root("A:\\")); + cl_assert_equal_b(false, git_fs_path_is_root("B:\\foo")); + cl_assert_equal_b(false, git_fs_path_is_root("B:\\foo\\")); + cl_assert_equal_b(true, git_fs_path_is_root("C:\\")); + cl_assert_equal_b(true, git_fs_path_is_root("c:\\")); + cl_assert_equal_b(true, git_fs_path_is_root("z:\\")); + cl_assert_equal_b(false, git_fs_path_is_root("z:\\\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\Foo")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\Foo\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\Volume\\12345\\Foo\\Bar.txt")); +#endif +} diff --git a/tests/util/path/win32.c b/tests/util/path/win32.c index 1aaf686..6f90b44 100644 --- a/tests/util/path/win32.c +++ b/tests/util/path/win32.c @@ -278,5 +278,38 @@ void test_path_win32__8dot3_name(void) cl_must_pass(p_mkdir(".bar", 0777)); cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); git__free(shortname); + + p_rmdir(".foo"); + p_rmdir(".bar"); + p_unlink("bar~1"); +#endif +} + +void test_path_win32__realpath(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT; + char result[GIT_PATH_MAX]; + + /* Ensure relative paths become absolute */ + cl_must_pass(git_str_joinpath(&expected, clar_sandbox_path(), "abcdef")); + cl_must_pass(p_mkdir("abcdef", 0777)); + cl_assert(p_realpath("./abcdef", result) != NULL); + cl_assert_equal_s(expected.ptr, result); + + /* Ensure case is canonicalized */ + git_str_clear(&expected); + cl_must_pass(git_str_joinpath(&expected, clar_sandbox_path(), "FOO")); + cl_must_pass(p_mkdir("FOO", 0777)); + cl_assert(p_realpath("foo", result) != NULL); + cl_assert_equal_s(expected.ptr, result); + + cl_assert(p_realpath("nonexistent", result) == NULL); + cl_assert_equal_i(ENOENT, errno); + + git_str_dispose(&expected); + + p_rmdir("abcdef"); + p_rmdir("FOO"); #endif } diff --git a/tests/util/process/env.c b/tests/util/process/env.c new file mode 100644 index 0000000..bb7dbcd --- /dev/null +++ b/tests/util/process/env.c @@ -0,0 +1,111 @@ +#include "clar_libgit2.h" +#include "process.h" +#include "vector.h" + +static git_str env_cmd = GIT_STR_INIT; +static git_str accumulator = GIT_STR_INIT; +static git_vector env_result = GIT_VECTOR_INIT; + +void test_process_env__initialize(void) +{ +#ifdef GIT_WIN32 + git_str_printf(&env_cmd, "%s/env.cmd", cl_fixture("process")); +#else + git_str_puts(&env_cmd, "/usr/bin/env"); +#endif + + cl_git_pass(git_vector_init(&env_result, 32, git__strcmp_cb)); +} + +void test_process_env__cleanup(void) +{ + git_vector_free(&env_result); + git_str_dispose(&accumulator); + git_str_dispose(&env_cmd); +} + +static void run_env(const char **env_array, size_t env_len, bool exclude_env) +{ + const char *args_array[] = { env_cmd.ptr }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + char buf[1024], *tok; + ssize_t ret; + + opts.capture_out = 1; + opts.exclude_env = exclude_env; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), env_array, env_len, &opts)); + cl_git_pass(git_process_start(process)); + + while ((ret = git_process_read(process, buf, 1024)) > 0) + cl_git_pass(git_str_put(&accumulator, buf, (size_t)ret)); + + cl_assert_equal_i(0, ret); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + for (tok = strtok(accumulator.ptr, "\n"); tok; tok = strtok(NULL, "\n")) { +#ifdef GIT_WIN32 + if (strlen(tok) && tok[strlen(tok) - 1] == '\r') + tok[strlen(tok) - 1] = '\0'; +#endif + + cl_git_pass(git_vector_insert(&env_result, tok)); + } + + git_process_close(process); + git_process_free(process); +} + +void test_process_env__can_add_env(void) +{ + const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" }; + run_env(env_array, 2, false); + + cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=added")); + cl_git_pass(git_vector_search(NULL, &env_result, "TEST_OTHER_ENV=also_added")); +} + +void test_process_env__can_propagate_env(void) +{ + cl_setenv("TEST_NEW_ENV", "propagated"); + run_env(NULL, 0, false); + + cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=propagated")); +} + +void test_process_env__can_remove_env(void) +{ + const char *env_array[] = { "TEST_NEW_ENV=" }; + char *str; + size_t i; + + cl_setenv("TEST_NEW_ENV", "propagated"); + run_env(env_array, 1, false); + + git_vector_foreach(&env_result, i, str) + cl_assert(git__prefixcmp(str, "TEST_NEW_ENV=") != 0); +} + +void test_process_env__can_clear_env(void) +{ + const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" }; + + cl_setenv("SOME_EXISTING_ENV", "propagated"); + run_env(env_array, 2, true); + + /* + * We can't simply test that the environment is precisely what we + * provided. Some systems (eg win32) will add environment variables + * to all processes. + */ + cl_assert_equal_i(GIT_ENOTFOUND, git_vector_search(NULL, &env_result, "SOME_EXISTING_ENV=propagated")); +} diff --git a/tests/util/process/start.c b/tests/util/process/start.c new file mode 100644 index 0000000..19ae5e3 --- /dev/null +++ b/tests/util/process/start.c @@ -0,0 +1,247 @@ +#include "clar_libgit2.h" +#include "process.h" +#include "vector.h" + +#ifndef GIT_WIN32 +# include <signal.h> +#endif + +#ifndef SIGTERM +# define SIGTERM 42 +#endif + +#ifndef SIGPIPE +# define SIGPIPE 42 +#endif + +static git_str helloworld_cmd = GIT_STR_INIT; +static git_str cat_cmd = GIT_STR_INIT; +static git_str pwd_cmd = GIT_STR_INIT; + +void test_process_start__initialize(void) +{ +#ifdef GIT_WIN32 + git_str_printf(&helloworld_cmd, "%s/helloworld.bat", cl_fixture("process")); + git_str_printf(&cat_cmd, "%s/cat.bat", cl_fixture("process")); + git_str_printf(&pwd_cmd, "%s/pwd.bat", cl_fixture("process")); +#else + git_str_printf(&helloworld_cmd, "%s/helloworld.sh", cl_fixture("process")); +#endif +} + +void test_process_start__cleanup(void) +{ + git_str_dispose(&pwd_cmd); + git_str_dispose(&cat_cmd); + git_str_dispose(&helloworld_cmd); +} + +void test_process_start__returncode(void) +{ +#if defined(GIT_WIN32) + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", "exit", "1" }; +#elif defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__) || \ + defined(__MidnightBSD__) || defined(__DragonFly__) + const char *args_array[] = { "/usr/bin/false" }; +#else + const char *args_array[] = { "/bin/false" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(1, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_process_free(process); +} + +void test_process_start__not_found(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\a\\b\\z\\y\\not_found" }; +#else + const char *args_array[] = { "/a/b/z/y/not_found" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_fail(git_process_start(process)); + git_process_free(process); +} + +static void write_all(git_process *process, char *buf) +{ + size_t buf_len = strlen(buf); + ssize_t ret; + + while (buf_len) { + ret = git_process_write(process, buf, buf_len); + cl_git_pass(ret < 0 ? (int)ret : 0); + + buf += ret; + buf_len -= ret; + } +} + +static void read_all(git_str *out, git_process *process) +{ + char buf[32]; + size_t buf_len = 32; + ssize_t ret; + + while ((ret = git_process_read(process, buf, buf_len)) > 0) + cl_git_pass(git_str_put(out, buf, ret)); + + cl_git_pass(ret < 0 ? (int)ret : 0); +} + +void test_process_start__redirect_stdio(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", cat_cmd.ptr }; +#else + const char *args_array[] = { "/bin/cat" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + git_str buf = GIT_STR_INIT; + + opts.capture_in = 1; + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + + write_all(process, "Hello, world.\r\nHello!\r\n"); + cl_git_pass(git_process_close_in(process)); + + read_all(&buf, process); + cl_assert_equal_s("Hello, world.\r\nHello!\r\n", buf.ptr); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_str_dispose(&buf); + git_process_free(process); +} + +/* +void test_process_start__catch_sigterm(void) +{ + const char *args_array[] = { "/bin/cat" }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + p_pid_t pid; + + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_id(&pid, process)); + + cl_must_pass(kill(pid, SIGTERM)); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(SIGTERM, result.signal); + + git_process_free(process); +} + +void test_process_start__catch_sigpipe(void) +{ + const char *args_array[] = { helloworld_cmd.ptr }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_close(process)); + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(SIGPIPE, result.signal); + + git_process_free(process); +} +*/ + +void test_process_start__can_chdir(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr }; + char *startwd = "C:\\"; +#else + const char *args_array[] = { "/bin/pwd" }; + char *startwd = "/"; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + git_str buf = GIT_STR_INIT; + + opts.cwd = startwd; + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + + read_all(&buf, process); + git_str_rtrim(&buf); + + cl_assert_equal_s(startwd, buf.ptr); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_str_dispose(&buf); + git_process_free(process); +} + +void test_process_start__cannot_chdir_to_nonexistent_dir(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr }; + char *startwd = "C:\\a\\b\\z\\y\\not_found"; +#else + const char *args_array[] = { "/bin/pwd" }; + char *startwd = "/a/b/z/y/not_found"; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + + opts.cwd = startwd; + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_fail(git_process_start(process)); + git_process_free(process); +} diff --git a/tests/util/process/win32.c b/tests/util/process/win32.c new file mode 100644 index 0000000..c04a56e --- /dev/null +++ b/tests/util/process/win32.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" +#include "process.h" +#include "vector.h" + +#ifdef GIT_WIN32 +static git_str result; + +# define assert_cmdline(expected, given) do { \ + cl_git_pass(git_process__cmdline(&result, given, ARRAY_SIZE(given))); \ + cl_assert_equal_s(expected, result.ptr); \ + git_str_dispose(&result); \ + } while(0) + +#endif + + +void test_process_win32__cmdline_is_whitespace_delimited(void) +{ +#ifdef GIT_WIN32 + const char *one[] = { "one" }; + const char *two[] = { "one", "two" }; + const char *three[] = { "one", "two", "three" }; + const char *four[] = { "one", "two", "three", "four" }; + + assert_cmdline("one", one); + assert_cmdline("one two", two); + assert_cmdline("one two three", three); + assert_cmdline("one two three four", four); +#endif +} + +void test_process_win32__cmdline_escapes_whitespace(void) +{ +#ifdef GIT_WIN32 + const char *spaces[] = { "one with spaces" }; + const char *tabs[] = { "one\twith\ttabs" }; + const char *multiple[] = { "one with many spaces" }; + + assert_cmdline("one\" \"with\" \"spaces", spaces); + assert_cmdline("one\"\t\"with\"\t\"tabs", tabs); + assert_cmdline("one\" \"with\" \"many\" \"spaces", multiple); +#endif +} + +void test_process_win32__cmdline_escapes_quotes(void) +{ +#ifdef GIT_WIN32 + const char *one[] = { "echo", "\"hello world\"" }; + + assert_cmdline("echo \\\"hello\" \"world\\\"", one); +#endif +} + +void test_process_win32__cmdline_escapes_backslash(void) +{ +#ifdef GIT_WIN32 + const char *one[] = { "foo\\bar", "foo\\baz" }; + const char *two[] = { "c:\\program files\\foo bar\\foo bar.exe", "c:\\path\\to\\other\\", "/a", "/b" }; + + assert_cmdline("foo\\\\bar foo\\\\baz", one); + assert_cmdline("c:\\\\program\" \"files\\\\foo\" \"bar\\\\foo\" \"bar.exe c:\\\\path\\\\to\\\\other\\\\ /a /b", two); +#endif +} diff --git a/tests/util/string.c b/tests/util/string.c index de04dea..051f8c3 100644 --- a/tests/util/string.c +++ b/tests/util/string.c @@ -111,12 +111,6 @@ void test_string__strcasecmp(void) cl_assert(git__strcasecmp("foo", "FOO") == 0); cl_assert(git__strcasecmp("foo", "fOO") == 0); - cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0); - cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0); - cl_assert(strcasecmp("\303\215", "\303\255") < 0); - cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0); cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); |