From 6c7cee4230d27398964e038c111fed839867cf43 Mon Sep 17 00:00:00 2001 From: Raju Devidas Date: Thu, 27 Dec 2018 01:36:29 +0530 Subject: [PATCH] New upstream version 0.27.7+dfsg.1 --- .travis.yml | 92 ----- CHANGELOG.md | 140 ++++++++ CMakeLists.txt | 4 + README.md | 7 +- appveyor.yml | 60 ---- azure-pipelines.yml | 94 +++++ ci/bash.yml | 17 + ci/build.ps1 | 30 ++ ci/build.sh | 39 ++ {script => ci}/coverity.sh | 28 +- ci/docker.yml | 33 ++ ci/nightly.yml | 22 ++ ci/powershell.yml | 17 + ci/setup-linux.sh | 13 + ci/setup-mingw.ps1 | 25 ++ ci/setup-osx.sh | 8 + ci/test.ps1 | 72 ++++ ci/test.sh | 198 ++++++++++ cmake/Modules/CheckPrototypeDefinition.c.in | 29 ++ cmake/Modules/CheckPrototypeDefinition.cmake | 96 +++++ cmake/Modules/FindIconv.cmake | 17 +- include/git2/version.h | 4 +- libgit2_clar.supp | 25 ++ script/appveyor-mingw.sh | 23 -- script/cibuild.sh | 102 ------ script/install-deps-osx.sh | 9 - src/CMakeLists.txt | 17 +- src/commit.c | 2 +- src/commit_list.c | 4 +- src/config.c | 2 +- src/config_file.c | 15 +- src/config_parse.c | 74 ++-- src/diff_tform.c | 2 +- src/index.c | 7 +- src/odb.c | 4 +- src/odb_pack.c | 2 +- src/pack-objects.c | 2 +- src/parse.c | 11 +- src/patch_parse.c | 7 + src/rebase.c | 2 +- src/remote.c | 6 +- src/repository.c | 10 +- src/revparse.c | 5 +- src/revwalk.c | 14 +- src/signature.c | 8 +- src/streams/curl.c | 2 +- src/submodule.c | 31 +- src/tag.c | 10 +- src/transports/http.c | 3 + src/transports/smart.c | 5 + src/transports/smart.h | 32 +- src/transports/smart_pkt.c | 248 +++++++------ src/transports/smart_protocol.c | 45 ++- src/transports/winhttp.c | 35 +- src/tree-cache.c | 4 +- src/tree.c | 14 +- src/util.c | 85 +++-- src/util.h | 5 +- src/vector.c | 21 +- src/worktree.c | 4 +- tests/CMakeLists.txt | 14 +- tests/buf/oom.c | 18 +- tests/clar.c | 205 ++++++++--- tests/clar.h | 5 +- tests/clar/print.h | 9 +- tests/clar/summary.h | 134 +++++++ tests/config/include.c | 42 ++- tests/config/read.c | 33 ++ tests/core/memmem.c | 46 +++ tests/core/strtol.c | 105 ++++-- tests/core/vector.c | 19 + tests/diff/parse.c | 71 ++++ tests/object/tree/update.c | 16 + tests/online/clone.c | 6 + tests/online/push.c | 10 +- tests/resources/config/config-nosection | Bin 0 -> 12 bytes .../39/6c7f1adb7925f51ba13a75f48252f44c5a14a2 | Bin 0 -> 71 bytes tests/submodule/inject_option.c | 80 +++++ tests/transports/smart/packet.c | 340 ++++++++++++++++++ tests/worktree/worktree.c | 24 +- 80 files changed, 2396 insertions(+), 723 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml create mode 100644 azure-pipelines.yml create mode 100644 ci/bash.yml create mode 100644 ci/build.ps1 create mode 100755 ci/build.sh rename {script => ci}/coverity.sh (67%) create mode 100644 ci/docker.yml create mode 100644 ci/nightly.yml create mode 100644 ci/powershell.yml create mode 100755 ci/setup-linux.sh create mode 100644 ci/setup-mingw.ps1 create mode 100755 ci/setup-osx.sh create mode 100644 ci/test.ps1 create mode 100755 ci/test.sh create mode 100644 cmake/Modules/CheckPrototypeDefinition.c.in create mode 100644 cmake/Modules/CheckPrototypeDefinition.cmake delete mode 100755 script/appveyor-mingw.sh delete mode 100755 script/cibuild.sh delete mode 100755 script/install-deps-osx.sh create mode 100644 tests/clar/summary.h create mode 100644 tests/core/memmem.c create mode 100644 tests/resources/config/config-nosection create mode 100644 tests/resources/testrepo2/.gitted/objects/39/6c7f1adb7925f51ba13a75f48252f44c5a14a2 create mode 100644 tests/submodule/inject_option.c create mode 100644 tests/transports/smart/packet.c diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3a55e86b0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Travis-CI Build for libgit2 -# see travis-ci.org for details - -language: c - -os: - - linux - - osx - -compiler: - - gcc - - clang - -# Settings to try -env: - global: - - secure: "YnhS+8n6B+uoyaYfaJ3Lei7cSJqHDPiKJCKFIF2c87YDfmCvAJke8QtE7IzjYDs7UFkTCM4ox+ph2bERUrxZbSCyEkHdjIZpKuMJfYWja/jgMqTMxdyOH9y8JLFbZsSXDIXDwqBlC6vVyl1fP90M35wuWcNTs6tctfVWVofEFbs=" - - GITTEST_INVASIVE_FS_SIZE=1 - matrix: - - OPTIONS="-DTHREADSAFE=ON -DENABLE_TRACE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_WERROR=ON" - - OPTIONS="-DTHREADSAFE=OFF -DBUILD_EXAMPLES=ON -DENABLE_WERROR=ON" - -dist: trusty -osx_image: xcode8.3 -sudo: false - -addons: - apt: - sources: - - sourceline: 'deb https://dl.bintray.com/libgit2/ci-dependencies trusty libgit2deps' - key_url: 'https://bintray.com/user/downloadSubjectPublicKey?username=bintray' - packages: - cmake - curl - libcurl3 - libcurl3-gnutls - libcurl4-gnutls-dev - libssh2-1-dev - openssh-client - openssh-server - valgrind - -matrix: - fast_finish: true - exclude: - - os: osx - compiler: gcc - include: - - compiler: gcc - env: COVERITY=1 - os: linux - dist: trusty - - compiler: gcc - env: - - VALGRIND=1 - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug" - os: linux - dist: trusty - allow_failures: - - env: COVERITY=1 - -install: - - if [ -f ./script/install-deps-${TRAVIS_OS_NAME}.sh ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi - -# Run the Build script and tests -script: - - script/cibuild.sh - -# Run Tests -after_success: - - if [ "$TRAVIS_OS_NAME" = "linux" -a -n "$VALGRIND" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi - -# Only watch the development and master branches -branches: - only: - - master - - /^maint.*/ - -# Notify development list when needed -notifications: - irc: - channels: - - irc.freenode.net#libgit2 - on_success: change - on_failure: always - use_notice: true - skip_join: true - campfire: - on_success: always - on_failure: always - rooms: - - secure: "sH0dpPWMirbEe7AvLddZ2yOp8rzHalGmv0bYL/LIhVw3JDI589HCYckeLMSB\n3e/FeXw4bn0EqXWEXijVa4ijbilVY6d8oprdqMdWHEodng4KvY5vID3iZSGT\nxylhahO1XHmRynKQLOAvxlc93IlpVW38vQfby8giIY1nkpspb2w=" diff --git a/CHANGELOG.md b/CHANGELOG.md index be62aa63d..f7e72d343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,143 @@ +v0.27.7 +------- + +This is a bugfix release with the following changes or improvements: + +- Our continuous integration environment has switched from Travis and + AppVeyor to Azure Pipelines CI. + +- Fix adding worktrees for bare repositories. + +- Fix parsed patches not computing the old respectively new line + numbers correctly. + +- Fix parsing configuration variables which do not have a section. + +- Fix a zero-byte allocation when trying to detect file renames and + copies of a diff without any hunks. + +- Fix a zero-byte allocation when trying to resize or duplicate + vectors. + +- Fix return value when trying to unlock worktrees which aren't + locked. + +- Fix returning an unitialized error code when preparing a revision + walk without any pushed commits. + +- Fix return value of `git_remote_lookup` when lookup of + "remote.$remote.tagopt" fails. + +- Fix the revision walk always labelling commits as interesting due + to a mishandling of the commit date. + +- Fix the packbuilder inserting uninteresting blobs when adding a + tree containing references to such blobs. + +- Ignore unsupported authentication schemes in HTTP transport. + +- Improve performane of `git_remote_prune`. + +- Fix detection of whether `qsort_r` has a BSD or GNU function + signature. + +- Fix detection of iconv if it is provided by libc. + +v0.27.6 +------- + +This as a security release fixing the following list of issues: + +- The function family `git__strtol` is used to parse integers + from a buffer. As the functions do not take a buffer length as + argument, they will scan either until the end of the current + number or until a NUL byte is encountered. Many callers have + been misusing the function and called it on potentially + non-NUL-terminated buffers, resulting in possible out-of-bounds + reads. Callers have been fixed to use `git__strntol` functions + instead and `git__strtol` functions were removed. + +- The function `git__strntol64` relied on the undefined behavior + of signed integer overflows. While the code tried to detect + such overflows after they have happened, this is unspecified + behavior and may lead to weird behavior on uncommon platforms. + +- In the case where `git__strntol32` was unable to parse an + integer because it doesn't fit into an `int32_t`, it printed an + error message containing the string that is currently being + parsed. The code didn't truncate the string though, which + caused it to print the complete string until a NUL byte is + encountered and not only the currently parsed number. In case + where the string was not NUL terminated, this could have lead + to an out-of-bounds read. + +- When parsing tags, all unknown fields that appear before the + tag message are skipped. This skipping is done by using a plain + `strstr(buffer, "\n\n")` to search for the two newlines that + separate tag fields from tag message. As it is not possible to + supply a buffer length to `strstr`, this call may skip over the + buffer's end and thus result in an out of bounds read. As + `strstr` may return a pointer that is out of bounds, the + following computation of `buffer_end - buffer` will overflow + and result in an allocation of an invalid length. Note that + when reading objects from the object database, we make sure to + always NUL terminate them, making the use of `strstr` safe. + +- When parsing the "encoding" field of a commit, we may perform + an out of bounds read due to using `git__prefixcmp` instead of + `git__prefixncmp`. This can result in the parsed commit object + containing uninitialized data in both its message encoding and + message fields. Note that when reading objects from the object + database, we make sure to always NUL terminate them, making the + use of `strstr` safe. + +v0.27.5 +------- + +This is a security release fixing the following list of issues: + +- Submodule URLs and paths with a leading "-" are now ignored. + This is due to the recently discovered CVE-2018-17456, which + can lead to arbitrary code execution in upstream git. While + libgit2 itself is not vulnerable, it can be used to inject + options in an implementation which performs a recursive clone + by executing an external command. + +- When running repack while doing repo writes, + `packfile_load__cb()` could see some temporary files in the + directory that were bigger than the usual, and makes `memcmp` + overflow on the `p->pack_name` string. This issue was reported + and fixed by bisho. + +- The configuration file parser used unbounded recursion to parse + multiline variables, which could lead to a stack overflow. The + issue was reported by the oss-fuzz project, issue 10048 and + fixed by Nelson Elhage. + +- The fix to the unbounded recursion introduced a memory leak in + the config parser. While this leak was never in a public + release, the oss-fuzz project reported this as issue 10127. The + fix was implemented by Nelson Elhage and Patrick Steinhardt. + +- When parsing "ok" packets received via the smart protocol, our + parsing code did not correctly verify the bounds of the + packets, which could result in a heap-buffer overflow. The + issue was reported by the oss-fuzz project, issue 9749 and + fixed by Patrick Steinhardt. + +- The parsing code for the smart protocol has been tightened in + general, fixing heap-buffer overflows when parsing the packet + type as well as for "ACK" and "unpack" packets. The issue was + discovered and fixed by Patrick Steinhardt. + +- Fixed potential integer overflows on platforms with 16 bit + integers when parsing packets for the smart protocol. The issue + was discovered and fixed by Patrick Steinhardt. + +- Fixed potential NULL pointer dereference when parsing + configuration files which have "include.path" or + "includeIf..path" statements without a value. + v0.27.4 ------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c1590271..83898c570 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ INCLUDE(CheckLibraryExists) INCLUDE(CheckFunctionExists) INCLUDE(CheckSymbolExists) INCLUDE(CheckStructHasMember) +INCLUDE(CheckPrototypeDefinition) # Added in CMake 3.0 INCLUDE(AddCFlagIfSupported) INCLUDE(FindPkgLibraries) INCLUDE(FindThreads) @@ -224,6 +225,9 @@ ELSE () ENABLE_WARNINGS(shift-count-overflow) DISABLE_WARNINGS(unused-const-variable) DISABLE_WARNINGS(unused-function) + ENABLE_WARNINGS(format) + ENABLE_WARNINGS(format-security) + ENABLE_WARNINGS(int-conversion) IF (APPLE) # Apple deprecated OpenSSL DISABLE_WARNINGS(deprecated-declarations) diff --git a/README.md b/README.md index 9fe99d8d5..1ec0bd800 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ libgit2 - the Git linkable library ================================== -[![Travis Build Status](https://secure.travis-ci.org/libgit2/libgit2.svg?branch=master)](http://travis-ci.org/libgit2/libgit2) -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/xvof5b4t5480a2q3/branch/master?svg=true)](https://ci.appveyor.com/project/libgit2/libgit2/branch/master) +[![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7) [![Coverity Scan Build Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) `libgit2` is a portable, pure C implementation of the Git core methods @@ -18,7 +17,7 @@ in your favorite language. [GitKraken](https://gitkraken.com/) and [gmaster](https://gmaster.io/) and on Git hosting providers like [GitHub](https://github.com/), [GitLab](https://gitlab.com/) and -[Visual Studio Team Services](https://visualstudio.com/team-services/). +[Azure DevOps](https://azure.com/devops). We perform the merge every time you click "merge pull request". `libgit2` is licensed under a **very permissive license** (GPLv2 with a special @@ -87,7 +86,7 @@ What It Can Do libgit2 provides you with the ability to manage Git repositories in the programming language of your choice. It's used in production to power many -applications including GitHub.com, Plastic SCM and Visual Studio Team Services. +applications including GitHub.com, Plastic SCM and Azure DevOps. It does not aim to replace the git tool or its user-facing commands. Some APIs resemble the plumbing commands as those align closely with the concepts of the diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index f76830cb4..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,60 +0,0 @@ -version: '{build}' -branches: - only: - - master - - appveyor - - /^maint.*/ -environment: - GITTEST_INVASIVE_FS_STRUCTURE: 1 - GITTEST_INVASIVE_FS_SIZE: 1 - - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - GENERATOR: "Visual Studio 10 2010" - ARCH: 32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - GENERATOR: "Visual Studio 10 2010 Win64" - ARCH: 64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GENERATOR: "Visual Studio 14 2015" - ARCH: 32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GENERATOR: "Visual Studio 14 2015 Win64" - ARCH: 64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GENERATOR: "MSYS Makefiles" - ARCH: i686 # this is for 32-bit MinGW-w64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GENERATOR: "MSYS Makefiles" - ARCH: 64 -cache: -- i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z -- x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z - -build_script: -- ps: | - mkdir build - cd build - if ($env:GENERATOR -ne "MSYS Makefiles") { - cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D BUILD_EXAMPLES=ON -D MSVC_CRTDBG=ON .. -G"$env:GENERATOR" - cmake --build . --config Debug - } -- cmd: | - if "%GENERATOR%"=="MSYS Makefiles" (C:\MinGW\msys\1.0\bin\sh --login /c/projects/libgit2/script/appveyor-mingw.sh) -test_script: -- ps: | - # Disable DHE key exchange to fix intermittent build failures ("A buffer - # provided was too small") due to SChannel bug. See e.g. - # - https://github.com/aws/aws-sdk-cpp/issues/671 - # - https://github.com/dotnet/corefx/issues/7812 - New-Item HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\Diffie-Hellman -Force | New-ItemProperty -Name Enabled -Value 0 -Force - $ErrorActionPreference="Stop" - Start-FileDownload https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar -FileName poxyproxy.jar - # Run this early so we know it's ready by the time we need it - $proxyJob = Start-Job { java -jar $Env:APPVEYOR_BUILD_FOLDER\build\poxyproxy.jar -d --port 8080 --credentials foo:bar } - ctest -V -R libgit2_clar - Receive-Job -Job $proxyJob - $env:GITTEST_REMOTE_PROXY_URL = "localhost:8080" - $env:GITTEST_REMOTE_PROXY_USER = "foo" - $env:GITTEST_REMOTE_PROXY_PASS = "bar" - ctest -V -R libgit2_clar-proxy_credentials diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..64007d1a2 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,94 @@ +resources: +- repo: self + +trigger: +- master +- maint/* + +jobs: +- job: linux_trusty_gcc_openssl + displayName: 'Linux (Trusty; GCC; OpenSSL)' + pool: + vmImage: 'Ubuntu 16.04' + steps: + - template: ci/docker.yml + parameters: + imageName: 'libgit2/trusty-openssl:latest' + environmentVariables: | + CC=gcc + LEAK_CHECK=valgrind + +- job: linux_trusty_clang_openssl + displayName: 'Linux (Trusty; Clang; OpenSSL)' + pool: + vmImage: 'Ubuntu 16.04' + steps: + - template: ci/docker.yml + parameters: + imageName: 'libgit2/trusty-openssl:latest' + environmentVariables: | + CC=clang + LEAK_CHECK=valgrind + +- job: macos + displayName: 'macOS' + pool: + vmImage: 'macOS 10.13' + steps: + - bash: . '$(Build.SourcesDirectory)/ci/setup-osx.sh' + displayName: Setup + - template: ci/bash.yml + parameters: + environmentVariables: + TMPDIR: $(Agent.TempDirectory) + PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig + LEAK_CHECK: leaks + +- job: windows_vs_amd64 + displayName: 'Windows (Visual Studio; amd64)' + pool: Hosted + steps: + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013 Win64" + +- job: windows_vs_x86 + displayName: 'Windows (Visual Studio; x86)' + pool: Hosted + steps: + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013" + +- job: windows_mingw_amd64 + displayName: 'Windows (MinGW; amd64)' + pool: Hosted + steps: + - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + displayName: Setup + env: + TEMP: $(Agent.TempDirectory) + ARCH: amd64 + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -G"MinGW Makefiles" + PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin + +- job: windows_mingw_x86 + displayName: 'Windows (MinGW; x86)' + pool: Hosted + steps: + - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + displayName: Setup + workingDirectory: '$(Build.BinariesDirectory)' + env: + TEMP: $(Agent.TempDirectory) + ARCH: x86 + - template: ci/powershell.yml + parameters: + environmentVariables: + CMAKE_OPTIONS: -G"MinGW Makefiles" + PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin diff --git a/ci/bash.yml b/ci/bash.yml new file mode 100644 index 000000000..d776a3649 --- /dev/null +++ b/ci/bash.yml @@ -0,0 +1,17 @@ +# These are the steps used for building on machines with bash. +steps: +- bash: . '$(Build.SourcesDirectory)/ci/build.sh' + displayName: Build + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- bash: . '$(Build.SourcesDirectory)/ci/test.sh' + displayName: Test + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- task: PublishTestResults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFiles: 'results_*.xml' + searchFolder: '$(Build.BinariesDirectory)' + mergeTestResults: true diff --git a/ci/build.ps1 b/ci/build.ps1 new file mode 100644 index 000000000..159c1dd1b --- /dev/null +++ b/ci/build.ps1 @@ -0,0 +1,30 @@ +Set-StrictMode -Version Latest + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +if ($Env:SOURCE_DIR) { $SourceDirectory = $Env:SOURCE_DIR } else { $SourceDirectory = Split-Path (Split-Path $MyInvocation.MyCommand.Path -Parent) -Parent } +$BuildDirectory = $(Get-Location).Path + +Write-Host "Source directory: ${SourceDirectory}" +Write-Host "Build directory: ${BuildDirectory}" +Write-Host "" +Write-Host "Operating system version:" +Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, ServicePackMajorVersion, BuildNumber, OSArchitecture | Format-List +Write-Host "PATH: ${Env:PATH}" +Write-Host "" + +Write-Host "##############################################################################" +Write-Host "## Configuring build environment" +Write-Host "##############################################################################" + +Invoke-Expression "cmake ${SourceDirectory} -DBUILD_EXAMPLES=ON ${Env:CMAKE_OPTIONS}" +if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) } + +Write-Host "" +Write-Host "##############################################################################" +Write-Host "## Building libgit2" +Write-Host "##############################################################################" + +cmake --build . +if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) } diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 000000000..3757be48d --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# Environment variables: +# +# SOURCE_DIR: Set to the directory of the libgit2 source (optional) +# If not set, it will be derived relative to this script. + +set -e + +SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} +BUILD_DIR=$(pwd) +CC=${CC:-cc} + +indent() { sed "s/^/ /"; } + +echo "Source directory: ${SOURCE_DIR}" +echo "Build directory: ${BUILD_DIR}" +echo "" +echo "Operating system version:" +uname -a 2>&1 | indent +echo "CMake version:" +cmake --version 2>&1 | indent +echo "Compiler version:" +$CC --version 2>&1 | indent +echo "" + +echo "##############################################################################" +echo "## Configuring build environment" +echo "##############################################################################" + +echo cmake ${SOURCE_DIR} -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON ${CMAKE_OPTIONS} +cmake ${SOURCE_DIR} -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON ${CMAKE_OPTIONS} + +echo "" +echo "##############################################################################" +echo "## Building libgit2" +echo "##############################################################################" + +cmake --build . diff --git a/script/coverity.sh b/ci/coverity.sh similarity index 67% rename from script/coverity.sh rename to ci/coverity.sh index 5fe16c031..a97fae8c8 100755 --- a/script/coverity.sh +++ b/ci/coverity.sh @@ -1,17 +1,13 @@ #!/bin/bash -set -e -# Only run this on our branches -echo "Branch: $TRAVIS_BRANCH | Pull request: $TRAVIS_PULL_REQUEST | Slug: $TRAVIS_REPO_SLUG" -if [ "$TRAVIS_BRANCH" != "master" -o "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "libgit2/libgit2" ]; -then - echo "Only analyzing the 'master' brach of the main repository." - exit 0 -fi +set -e # Environment check [ -z "$COVERITY_TOKEN" ] && echo "Need to set a coverity token" && exit 1 +SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} +BUILD_DIR=$(pwd) + case $(uname -m) in i?86) BITS=32 ;; amd64|x86_64) BITS=64 ;; @@ -32,31 +28,29 @@ if [ ! -d "$TOOL_BASE" ]; then ln -s "$TOOL_DIR" "$TOOL_BASE"/cov-analysis fi -cp script/user_nodefs.h "$TOOL_BASE"/cov-analysis/config/user_nodefs.h +cp "${SOURCE_DIR}/script/user_nodefs.h" "$TOOL_BASE"/cov-analysis/config/user_nodefs.h COV_BUILD="$TOOL_BASE/cov-analysis/bin/cov-build" # Configure and build -rm -rf _build -mkdir _build -cd _build -cmake .. -DTHREADSAFE=ON +cmake ${SOURCE_DIR} + COVERITY_UNSUPPORTED=1 \ $COV_BUILD --dir cov-int \ cmake --build . # Upload results tar czf libgit2.tgz cov-int -SHA=$(git rev-parse --short HEAD) +SHA=$(cd ${SOURCE_DIR} && git rev-parse --short HEAD) HTML="$(curl \ --silent \ --write-out "\n%{http_code}" \ --form token="$COVERITY_TOKEN" \ - --form email=bs@github.com \ + --form email=libgit2@gmail.com \ --form file=@libgit2.tgz \ --form version="$SHA" \ - --form description="Travis build" \ + --form description="libgit2 build" \ https://scan.coverity.com/builds?project=libgit2)" # Body is everything up to the last line BODY="$(echo "$HTML" | head -n-1)" @@ -65,7 +59,7 @@ STATUS_CODE="$(echo "$HTML" | tail -n1)" echo "${BODY}" -if [ "${STATUS_CODE}" != "201" ]; then +if [ "${STATUS_CODE}" != "200" -a "${STATUS_CODE}" != "201" ]; then echo "Received error code ${STATUS_CODE} from Coverity" exit 1 fi diff --git a/ci/docker.yml b/ci/docker.yml new file mode 100644 index 000000000..e92510478 --- /dev/null +++ b/ci/docker.yml @@ -0,0 +1,33 @@ +# These are the steps used in a container-based build in VSTS. +steps: +- task: docker@0 + displayName: Build + inputs: + action: 'Run an image' + imageName: ${{ parameters.imageName }} + volumes: | + $(Build.SourcesDirectory):/src + $(Build.BinariesDirectory):/build + envVars: ${{ parameters.environmentVariables }} + workDir: '/build' + containerCommand: '/src/ci/build.sh' + detached: false +- task: docker@0 + displayName: Test + inputs: + action: 'Run an image' + imageName: ${{ parameters.imageName }} + volumes: | + $(Build.SourcesDirectory):/src + $(Build.BinariesDirectory):/build + envVars: ${{ parameters.environmentVariables }} + workDir: '/build' + containerCommand: '/src/ci/test.sh' + detached: false +- task: publishtestresults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFiles: 'results_*.xml' + searchFolder: '$(Build.BinariesDirectory)' + mergeTestResults: true diff --git a/ci/nightly.yml b/ci/nightly.yml new file mode 100644 index 000000000..4d6d8a3e8 --- /dev/null +++ b/ci/nightly.yml @@ -0,0 +1,22 @@ +resources: +- repo: self + +jobs: +- job: coverity + displayName: 'Coverity' + pool: + vmImage: 'Ubuntu 16.04' + steps: + - task: Docker@0 + displayName: Build + inputs: + action: 'Run an image' + imageName: 'libgit2/trusty-openssl:latest' + volumes: | + $(Build.SourcesDirectory):/src + $(Build.BinariesDirectory):/build + envVars: | + COVERITY_TOKEN=$(COVERITY_TOKEN) + workDir: '/build' + containerCommand: '/src/ci/coverity.sh' + detached: false diff --git a/ci/powershell.yml b/ci/powershell.yml new file mode 100644 index 000000000..a2eb175d5 --- /dev/null +++ b/ci/powershell.yml @@ -0,0 +1,17 @@ +# These are the steps used for building on machines with PowerShell. +steps: +- powershell: . '$(Build.SourcesDirectory)\ci\build.ps1' + displayName: Build + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- powershell: . '$(Build.SourcesDirectory)\ci\test.ps1' + displayName: Test + workingDirectory: '$(Build.BinariesDirectory)' + env: ${{ parameters.environmentVariables }} +- task: PublishTestResults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFiles: 'results_*.xml' + searchFolder: '$(Build.BinariesDirectory)' + mergeTestResults: true diff --git a/ci/setup-linux.sh b/ci/setup-linux.sh new file mode 100755 index 000000000..a0db14ee0 --- /dev/null +++ b/ci/setup-linux.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e +set -x + +TMPDIR=${TMPDIR:-/tmp} + +if [ -z "$SKIP_APT" ]; then + apt-get update + apt-get -y install build-essential pkg-config clang cmake openssl libssl-dev libssh2-1-dev libcurl4-gnutls-dev openssh-server +fi + +mkdir -p /var/run/sshd diff --git a/ci/setup-mingw.ps1 b/ci/setup-mingw.ps1 new file mode 100644 index 000000000..76ecd3987 --- /dev/null +++ b/ci/setup-mingw.ps1 @@ -0,0 +1,25 @@ +Set-StrictMode -Version Latest + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem"); + +Write-Host "##############################################################################" +Write-Host "## Downloading mingw" +Write-Host "##############################################################################" + +if ($env:ARCH -eq "amd64") { + $mingw_uri = "https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-x86_64-8.1.0-release-win32-seh-rt_v6-rev0.zip" + $platform = "x86_64" +} else { + $mingw_uri = "https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-i686-8.1.0-release-win32-sjlj-rt_v6-rev0.zip" + $platform = "x86" +} + +$wc = New-Object net.webclient +$wc.Downloadfile($mingw_uri, "${Env:TEMP}/mingw-${Env:ARCH}.zip") + +[System.IO.Compression.ZipFile]::ExtractToDirectory("${Env:TEMP}/mingw-${Env:ARCH}.zip", $Env:TEMP) diff --git a/ci/setup-osx.sh b/ci/setup-osx.sh new file mode 100755 index 000000000..564910e41 --- /dev/null +++ b/ci/setup-osx.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -x + +brew update +brew install pkgconfig zlib curl openssl libssh2 + +ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib diff --git a/ci/test.ps1 b/ci/test.ps1 new file mode 100644 index 000000000..1cf02118f --- /dev/null +++ b/ci/test.ps1 @@ -0,0 +1,72 @@ +Set-StrictMode -Version Latest + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +$SourceDir = Split-Path (Split-Path (Get-Variable MyInvocation).Value.MyCommand.Path) +$BuildDir = Get-Location +$global:Success = $true + +if ($Env:SKIP_TESTS) { exit } + +# Ask ctest what it would run if we were to invoke it directly. This lets +# us manage the test configuration in a single place (tests/CMakeLists.txt) +# instead of running clar here as well. But it allows us to wrap our test +# harness with a leak checker like valgrind. Append the option to write +# JUnit-style XML files. +function run_test { + $TestName = $args[0] + + $TestCommand = (ctest -N -V -R "^$TestName$") -join "`n" -replace "(?ms).*\n^[0-9]*: Test command: ","" -replace "\n.*","" + $TestCommand += " -r${BuildDir}\results_${TestName}.xml" + + Write-Host $TestCommand + Invoke-Expression $TestCommand + + if ($LastExitCode -ne 0) { $global:Success = $false } +} + +Write-Host "##############################################################################" +Write-Host "## Configuring test environment" +Write-Host "##############################################################################" + +if (-not $Env:SKIP_PROXY_TESTS) { + Write-Host "" + Write-Host "Starting HTTP proxy..." + Invoke-WebRequest -Method GET -Uri https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar -OutFile poxyproxy.jar + javaw -jar poxyproxy.jar -d --port 8080 --credentials foo:bar +} + +Write-Host "" +Write-Host "##############################################################################" +Write-Host "## Running (offline) tests" +Write-Host "##############################################################################" + +run_test offline + +if (-not $Env:SKIP_ONLINE_TESTS) { + Write-Host "" + Write-Host "##############################################################################" + Write-Host "## Running (online) tests" + Write-Host "##############################################################################" + + run_test online +} + +if (-not $Env:SKIP_PROXY_TESTS) { + Write-Host "" + Write-Host "Running proxy tests" + Write-Host "" + + $Env:GITTEST_REMOTE_PROXY_URL="localhost:8080" + $Env:GITTEST_REMOTE_PROXY_USER="foo" + $Env:GITTEST_REMOTE_PROXY_PASS="bar" + + run_test proxy + + taskkill /F /IM javaw.exe +} + +if (-Not $global:Success) { exit 1 } diff --git a/ci/test.sh b/ci/test.sh new file mode 100755 index 000000000..fea9d82d7 --- /dev/null +++ b/ci/test.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +set -e + +if [ -n "$SKIP_TESTS" ]; then + exit 0 +fi + +SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} +BUILD_DIR=$(pwd) +TMPDIR=${TMPDIR:-/tmp} +USER=${USER:-$(whoami)} + +SUCCESS=1 + +VALGRIND="valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions=\"$SOURCE_DIR/libgit2_clar.supp\"" +LEAKS="MallocStackLogging=1 MallocScribble=1 leaks -quiet -atExit -- nohup" + +cleanup() { + echo "Cleaning up..." + + if [ ! -z "$GITDAEMON_DIR" -a -f "${GITDAEMON_DIR}/pid" ]; then + echo "Stopping git daemon..." + kill $(cat "${GITDAEMON_DIR}/pid") + fi + + if [ ! -z "$SSHD_DIR" -a -f "${SSHD_DIR}/pid" ]; then + echo "Stopping SSH..." + kill $(cat "${SSHD_DIR}/pid") + fi + + echo "Done." +} + +failure() { + echo "Test exited with code: $1" + SUCCESS=0 +} + +# Ask ctest what it would run if we were to invoke it directly. This lets +# us manage the test configuration in a single place (tests/CMakeLists.txt) +# instead of running clar here as well. But it allows us to wrap our test +# harness with a leak checker like valgrind. Append the option to write +# JUnit-style XML files. +run_test() { + TEST_CMD=$(ctest -N -V -R "^${1}$" | sed -n 's/^[0-9]*: Test command: //p') + TEST_CMD="${TEST_CMD} -r${BUILD_DIR}/results_${1}.xml" + + if [ "$LEAK_CHECK" = "valgrind" ]; then + RUNNER="$VALGRIND $TEST_CMD" + elif [ "$LEAK_CHECK" = "leaks" ]; then + RUNNER="$LEAKS $TEST_CMD" + else + RUNNER="$TEST_CMD" + fi + + eval $RUNNER || failure +} + +# Configure the test environment; run them early so that we're certain +# that they're started by the time we need them. + +echo "##############################################################################" +echo "## Configuring test environment" +echo "##############################################################################" + +if [ -z "$SKIP_GITDAEMON_TESTS" ]; then + echo "Starting git daemon..." + GITDAEMON_DIR=`mktemp -d ${TMPDIR}/gitdaemon.XXXXXXXX` + git init --bare "${GITDAEMON_DIR}/test.git" + git daemon --listen=localhost --export-all --enable=receive-pack --pid-file="${GITDAEMON_DIR}/pid" --base-path="${GITDAEMON_DIR}" "${GITDAEMON_DIR}" 2>/dev/null & +fi + +if [ -z "$SKIP_PROXY_TESTS" ]; then + echo "Starting HTTP proxy..." + curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar >poxyproxy.jar + java -jar poxyproxy.jar -d --port 8080 --credentials foo:bar >/dev/null 2>&1 & +fi + +if [ -z "$SKIP_SSH_TESTS" ]; then + echo "Starting ssh daemon..." + HOME=`mktemp -d ${TMPDIR}/home.XXXXXXXX` + SSHD_DIR=`mktemp -d ${TMPDIR}/sshd.XXXXXXXX` + git init --bare "${SSHD_DIR}/test.git" + cat >"${SSHD_DIR}/sshd_config" <<-EOF + Port 2222 + ListenAddress 0.0.0.0 + Protocol 2 + HostKey ${SSHD_DIR}/id_rsa + PidFile ${SSHD_DIR}/pid + AuthorizedKeysFile ${HOME}/.ssh/authorized_keys + LogLevel DEBUG + RSAAuthentication yes + PasswordAuthentication yes + PubkeyAuthentication yes + ChallengeResponseAuthentication no + StrictModes no + # Required here as sshd will simply close connection otherwise + UsePAM no + EOF + ssh-keygen -t rsa -f "${SSHD_DIR}/id_rsa" -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" + while read algorithm key comment; do + echo "[localhost]:2222 $algorithm $key" >>"${HOME}/.ssh/known_hosts" + done <"${SSHD_DIR}/id_rsa.pub" + + # Get the fingerprint for localhost and remove the colons so we can + # parse it as a hex number. Older versions have a different output + # format. + if [[ $(ssh -V 2>&1) == OpenSSH_6* ]]; then + SSH_FINGERPRINT=$(ssh-keygen -F '[localhost]:2222' -f "${HOME}/.ssh/known_hosts" -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':') + else + SSH_FINGERPRINT=$(ssh-keygen -E md5 -F '[localhost]:2222' -f "${HOME}/.ssh/known_hosts" -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :) + fi +fi + +# Run the tests that do not require network connectivity. + +if [ -z "$SKIP_OFFLINE_TESTS" ]; then + echo "" + echo "##############################################################################" + echo "## Running (offline) tests" + echo "##############################################################################" + + run_test offline +fi + +if [ -z "$SKIP_ONLINE_TESTS" ]; then + # Run the various 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. + + echo "" + echo "##############################################################################" + echo "## Running (online) tests" + echo "##############################################################################" + + run_test online +fi + +if [ -z "$SKIP_GITDAEMON_TESTS" ]; then + echo "" + echo "Running gitdaemon tests" + echo "" + + export GITTEST_REMOTE_URL="git://localhost/test.git" + run_test gitdaemon + unset GITTEST_REMOTE_URL +fi + +if [ -z "$SKIP_PROXY_TESTS" ]; then + echo "" + echo "Running proxy tests" + echo "" + + export GITTEST_REMOTE_PROXY_URL="localhost:8080" + export GITTEST_REMOTE_PROXY_USER="foo" + export GITTEST_REMOTE_PROXY_PASS="bar" + run_test proxy + unset GITTEST_REMOTE_PROXY_URL + unset GITTEST_REMOTE_PROXY_USER + unset GITTEST_REMOTE_PROXY_PASS +fi + +if [ -z "$SKIP_SSH_TESTS" ]; then + echo "" + echo "Running ssh tests" + echo "" + + export GITTEST_REMOTE_URL="ssh://localhost:2222/$SSHD_DIR/test.git" + 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_PASSPHRASE="" + export GITTEST_REMOTE_SSH_FINGERPRINT="${SSH_FINGERPRINT}" + run_test ssh + unset GITTEST_REMOTE_URL + unset GITTEST_REMOTE_USER + unset GITTEST_REMOTE_SSH_KEY + unset GITTEST_REMOTE_SSH_PUBKEY + unset GITTEST_REMOTE_SSH_PASSPHRASE + unset GITTEST_REMOTE_SSH_FINGERPRINT +fi + +cleanup + +if [ "$SUCCESS" -ne "1" ]; then + echo "Some tests failed." + exit 1 +fi + +echo "Success." +exit 0 diff --git a/cmake/Modules/CheckPrototypeDefinition.c.in b/cmake/Modules/CheckPrototypeDefinition.c.in new file mode 100644 index 000000000..a97344ac3 --- /dev/null +++ b/cmake/Modules/CheckPrototypeDefinition.c.in @@ -0,0 +1,29 @@ +@CHECK_PROTOTYPE_DEFINITION_HEADER@ + +static void cmakeRequireSymbol(int dummy, ...) { + (void) dummy; +} + +static void checkSymbol(void) { +#ifndef @CHECK_PROTOTYPE_DEFINITION_SYMBOL@ + cmakeRequireSymbol(0, &@CHECK_PROTOTYPE_DEFINITION_SYMBOL@); +#endif +} + +@CHECK_PROTOTYPE_DEFINITION_PROTO@ { + return @CHECK_PROTOTYPE_DEFINITION_RETURN@; +} + +#ifdef __CLASSIC_C__ +int main() { + int ac; + char*av[]; +#else +int main(int ac, char *av[]) { +#endif + checkSymbol(); + if (ac > 1000) { + return *av[0]; + } + return 0; +} diff --git a/cmake/Modules/CheckPrototypeDefinition.cmake b/cmake/Modules/CheckPrototypeDefinition.cmake new file mode 100644 index 000000000..244b9b53b --- /dev/null +++ b/cmake/Modules/CheckPrototypeDefinition.cmake @@ -0,0 +1,96 @@ +# - Check if the protoype we expect is correct. +# check_prototype_definition(FUNCTION PROTOTYPE RETURN HEADER VARIABLE) +# FUNCTION - The name of the function (used to check if prototype exists) +# PROTOTYPE- The prototype to check. +# RETURN - The return value of the function. +# HEADER - The header files required. +# VARIABLE - The variable to store the result. +# Example: +# check_prototype_definition(getpwent_r +# "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" +# "NULL" +# "unistd.h;pwd.h" +# SOLARIS_GETPWENT_R) +# The following variables may be set before calling this macro to +# modify the way the check is run: +# +# CMAKE_REQUIRED_FLAGS = string of compile command line flags +# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) +# CMAKE_REQUIRED_INCLUDES = list of include directories +# CMAKE_REQUIRED_LIBRARIES = list of libraries to link + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# Copyright 2010-2011 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) +# + +get_filename_component(__check_proto_def_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) + +function(CHECK_PROTOTYPE_DEFINITION _FUNCTION _PROTOTYPE _RETURN _HEADER _VARIABLE) + + if ("${_VARIABLE}" MATCHES "^${_VARIABLE}$") + set(CHECK_PROTOTYPE_DEFINITION_CONTENT "/* */\n") + + set(CHECK_PROTOTYPE_DEFINITION_FLAGS ${CMAKE_REQUIRED_FLAGS}) + if (CMAKE_REQUIRED_LIBRARIES) + set(CHECK_PROTOTYPE_DEFINITION_LIBS + "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES}") + else(CMAKE_REQUIRED_LIBRARIES) + set(CHECK_PROTOTYPE_DEFINITION_LIBS) + endif(CMAKE_REQUIRED_LIBRARIES) + if (CMAKE_REQUIRED_INCLUDES) + set(CMAKE_SYMBOL_EXISTS_INCLUDES + "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") + else(CMAKE_REQUIRED_INCLUDES) + set(CMAKE_SYMBOL_EXISTS_INCLUDES) + endif(CMAKE_REQUIRED_INCLUDES) + + foreach(_FILE ${_HEADER}) + set(CHECK_PROTOTYPE_DEFINITION_HEADER + "${CHECK_PROTOTYPE_DEFINITION_HEADER}#include <${_FILE}>\n") + endforeach(_FILE) + + set(CHECK_PROTOTYPE_DEFINITION_SYMBOL ${_FUNCTION}) + set(CHECK_PROTOTYPE_DEFINITION_PROTO ${_PROTOTYPE}) + set(CHECK_PROTOTYPE_DEFINITION_RETURN ${_RETURN}) + + configure_file("${__check_proto_def_dir}/CheckPrototypeDefinition.c.in" + "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c" @ONLY) + + file(READ ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c _SOURCE) + + try_compile(${_VARIABLE} + ${CMAKE_BINARY_DIR} + ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c + COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} + CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CHECK_PROTOTYPE_DEFINITION_FLAGS} + "${CHECK_PROTOTYPE_DEFINITION_LIBS}" + "${CMAKE_SYMBOL_EXISTS_INCLUDES}" + OUTPUT_VARIABLE OUTPUT) + + if (${_VARIABLE}) + set(${_VARIABLE} 1 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") + message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - True") + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log + "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} passed with the following output:\n" + "${OUTPUT}\n\n") + else (${_VARIABLE}) + message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - False") + set(${_VARIABLE} 0 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} failed with the following output:\n" + "${OUTPUT}\n\n${_SOURCE}\n\n") + endif (${_VARIABLE}) + endif("${_VARIABLE}" MATCHES "^${_VARIABLE}$") + +endfunction(CHECK_PROTOTYPE_DEFINITION) diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake index 95414bda6..3c66cdad4 100644 --- a/cmake/Modules/FindIconv.cmake +++ b/cmake/Modules/FindIconv.cmake @@ -12,14 +12,19 @@ IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) ENDIF() FIND_PATH(ICONV_INCLUDE_DIR iconv.h) +CHECK_FUNCTION_EXISTS(iconv_open libc_has_iconv) FIND_LIBRARY(iconv_lib NAMES iconv libiconv libiconv-2 c) -IF(ICONV_INCLUDE_DIR AND iconv_lib) - SET(ICONV_FOUND TRUE) -ENDIF() - -IF(ICONV_FOUND) - # split iconv into -L and -l linker options, so we can set them for pkg-config +IF(ICONV_INCLUDE_DIR AND libc_has_iconv) + SET(ICONV_FOUND TRUE) + SET(ICONV_LIBRARIES "") + IF(NOT ICONV_FIND_QUIETLY) + MESSAGE(STATUS "Found Iconv: provided by libc") + ENDIF(NOT ICONV_FIND_QUIETLY) +ELSEIF(ICONV_INCLUDE_DIR AND iconv_lib) + SET(ICONV_FOUND TRUE) + # split iconv into -L and -l linker options, so we can + # set them for pkg-config GET_FILENAME_COMPONENT(iconv_path ${iconv_lib} PATH) GET_FILENAME_COMPONENT(iconv_name ${iconv_lib} NAME_WE) STRING(REGEX REPLACE "^lib" "" iconv_name ${iconv_name}) diff --git a/include/git2/version.h b/include/git2/version.h index 7c4242657..964e4a58b 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,10 +7,10 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.27.4" +#define LIBGIT2_VERSION "0.27.7" #define LIBGIT2_VER_MAJOR 0 #define LIBGIT2_VER_MINOR 27 -#define LIBGIT2_VER_REVISION 4 +#define LIBGIT2_VER_REVISION 7 #define LIBGIT2_VER_PATCH 0 #define LIBGIT2_SOVERSION 27 diff --git a/libgit2_clar.supp b/libgit2_clar.supp index bd22ada46..0cc89b57f 100644 --- a/libgit2_clar.supp +++ b/libgit2_clar.supp @@ -47,3 +47,28 @@ ... fun:__check_pf } + +{ + ignore-curl-global-init + Memcheck:Leak + ... + fun:curl_global_init +} + +{ + ignore-libssh2-gcrypt-leak + Memcheck:Leak + ... + fun:gcry_control + obj:*libssh2.so* +} + +{ + ignore-noai6ai_cached-double-free + Memcheck:Free + fun:free + fun:__libc_freeres + ... + fun:exit + ... +} diff --git a/script/appveyor-mingw.sh b/script/appveyor-mingw.sh deleted file mode 100755 index 6b2a9425e..000000000 --- a/script/appveyor-mingw.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -set -e -cd `dirname "$0"`/.. -if [ "$ARCH" = "i686" ]; then - f=i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z - if ! [ -e $f ]; then - curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/sjlj/$f - fi - 7z x $f > /dev/null - export PATH=`pwd`/mingw32/bin:$PATH -else - f=x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z - if ! [ -e $f ]; then - curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/$f - fi - 7z x $f > /dev/null - export PATH=`pwd`/mingw64/bin:$PATH -fi -cd build -gcc --version -cmake --version -cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D BUILD_EXAMPLES=ON .. -G"$GENERATOR" -cmake --build . --config RelWithDebInfo diff --git a/script/cibuild.sh b/script/cibuild.sh deleted file mode 100755 index 5d70e7506..000000000 --- a/script/cibuild.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh - -set -x - -if [ -n "$COVERITY" ]; -then - ./script/coverity.sh; - exit $?; -fi - -if [ "$TRAVIS_OS_NAME" = "osx" ]; then - export PKG_CONFIG_PATH=$(ls -d /usr/local/Cellar/{curl,zlib}/*/lib/pkgconfig | paste -s -d':' -) - - # Set up a ramdisk for us to put our test data on to speed up tests on macOS - export CLAR_TMP="$HOME"/_clar_tmp - mkdir -p $CLAR_TMP - - # 5*2M sectors aka ~5GB of space - device=$(hdiutil attach -nomount ram://$((5 * 2 * 1024 * 1024))) - newfs_hfs $device - mount -t hfs $device $CLAR_TMP -fi - -# Should we ask Travis to cache this file? -curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar >poxyproxy.jar || exit $? -# Run this early so we know it's ready by the time we need it -java -jar poxyproxy.jar -d --port 8080 --credentials foo:bar & - -mkdir _build -cd _build -# shellcheck disable=SC2086 -cmake .. -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? -make -j2 install || exit $? - -# If this platform doesn't support test execution, bail out now -if [ -n "$SKIP_TESTS" ]; -then - exit $?; -fi - -# Create a test repo which we can use for the online::push tests -mkdir "$HOME"/_temp -git init --bare "$HOME"/_temp/test.git -git daemon --listen=localhost --export-all --enable=receive-pack --base-path="$HOME"/_temp "$HOME"/_temp 2>/dev/null & -export GITTEST_REMOTE_URL="git://localhost/test.git" - -# Run the test suite -ctest -V -R libgit2_clar || exit $? - -# Now that we've tested the raw git protocol, let's set up ssh to we -# can do the push tests over it - -killall git-daemon - -# Set up sshd -mkdir ~/sshd/ -cat >~/sshd/sshd_config<<-EOF - Port 2222 - ListenAddress 0.0.0.0 - Protocol 2 - HostKey ${HOME}/sshd/id_rsa - PidFile ${HOME}/sshd/pid - RSAAuthentication yes - PasswordAuthentication yes - PubkeyAuthentication yes - ChallengeResponseAuthentication no - # Required here as sshd will simply close connection otherwise - UsePAM no -EOF -ssh-keygen -t rsa -f ~/sshd/id_rsa -N "" -q -/usr/sbin/sshd -f ~/sshd/sshd_config - -# Set up keys -ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q -cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys -while read algorithm key comment; do - echo "[localhost]:2222 $algorithm $key" >>~/.ssh/known_hosts -done <~/sshd/id_rsa.pub - -# Get the fingerprint for localhost and remove the colons so we can parse it as -# a hex number. The Mac version is newer so it has a different output format. -if [ "$TRAVIS_OS_NAME" = "osx" ]; then - export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -E md5 -F '[localhost]:2222' -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :) -else - export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -F '[localhost]:2222' -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':') -fi - -# Use the SSH server -export GITTEST_REMOTE_URL="ssh://localhost:2222/$HOME/_temp/test.git" -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_PASSPHRASE="" -ctest -V -R libgit2_clar-ssh || exit $? - -# Use the proxy we started at the beginning -export GITTEST_REMOTE_PROXY_URL="localhost:8080" -export GITTEST_REMOTE_PROXY_USER="foo" -export GITTEST_REMOTE_PROXY_PASS="bar" -ctest -V -R libgit2_clar-proxy_credentials || exit $? - -kill $(cat "$HOME/sshd/pid") diff --git a/script/install-deps-osx.sh b/script/install-deps-osx.sh deleted file mode 100755 index 94314dbaa..000000000 --- a/script/install-deps-osx.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -x - -brew update -brew install zlib -brew install curl -brew install openssl -brew install libssh2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4cfb2d311..2080933f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,10 +57,19 @@ IF (HAVE_FUTIMENS) SET(GIT_USE_FUTIMENS 1) ENDIF () -CHECK_FUNCTION_EXISTS(qsort_r HAVE_QSORT_R) -IF (HAVE_QSORT_R) - ADD_DEFINITIONS(-DHAVE_QSORT_R) -ENDIF () +CHECK_PROTOTYPE_DEFINITION(qsort_r + "void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))" + "" "stdlib.h" HAVE_QSORT_R_BSD) +IF (HAVE_QSORT_R_BSD) + ADD_DEFINITIONS(-DHAVE_QSORT_R_BSD) +ENDIF() + +CHECK_PROTOTYPE_DEFINITION(qsort_r + "void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg)" + "" "stdlib.h" HAVE_QSORT_R_GNU) +IF (HAVE_QSORT_R_GNU) + ADD_DEFINITIONS(-DHAVE_QSORT_R_GNU) +ENDIF() CHECK_FUNCTION_EXISTS(qsort_s HAVE_QSORT_S) IF (HAVE_QSORT_S) diff --git a/src/commit.c b/src/commit.c index 838688bb8..8972c077d 100644 --- a/src/commit.c +++ b/src/commit.c @@ -443,7 +443,7 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) while (eoln < buffer_end && *eoln != '\n') ++eoln; - if (git__prefixcmp(buffer, "encoding ") == 0) { + if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { buffer += strlen("encoding "); commit->message_encoding = git__strndup(buffer, eoln - buffer); diff --git a/src/commit_list.c b/src/commit_list.c index 96bd9dc15..b4313eed2 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -171,7 +171,9 @@ static int commit_quick_parse( buffer--; } - if ((buffer == committer_start) || (git__strtol64(&commit_time, (char *)(buffer + 1), NULL, 10) < 0)) + if ((buffer == committer_start) || + (git__strntol64(&commit_time, (char *)(buffer + 1), + buffer_end - buffer + 1, NULL, 10) < 0)) return commit_error(commit, "cannot parse commit time"); commit->time = commit_time; diff --git a/src/config.c b/src/config.c index 1bc11b99f..5d95c2458 100644 --- a/src/config.c +++ b/src/config.c @@ -1300,7 +1300,7 @@ int git_config_parse_int64(int64_t *out, const char *value) const char *num_end; int64_t num; - if (!value || git__strtol64(&num, value, &num_end, 0) < 0) + if (!value || git__strntol64(&num, value, strlen(value), &num_end, 0) < 0) goto fail_parse; switch (*num_end) { diff --git a/src/config_file.c b/src/config_file.c index aa9e83b9c..0c05fb515 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -928,6 +928,9 @@ static int parse_include(git_config_parser *reader, char *dir; int result; + if (!file) + return 0; + if ((result = git_path_dirname_r(&path, reader->file->path)) < 0) return result; @@ -1029,7 +1032,7 @@ static int parse_conditional_include(git_config_parser *reader, size_t i; int error = 0, matches; - if (!parse_data->repo) + if (!parse_data->repo || !file) return 0; condition = git__substrdup(section + strlen("includeIf."), @@ -1072,8 +1075,16 @@ static int read_on_variable( GIT_UNUSED(line); GIT_UNUSED(line_len); + if (current_section) { + /* TODO: Once warnings lang, we should likely warn + * here. Git appears to warn in most cases if it sees + * un-namespaced config options. + */ + git_buf_puts(&buf, current_section); + git_buf_putc(&buf, '.'); + } git__strtolower(var_name); - git_buf_printf(&buf, "%s.%s", current_section, var_name); + git_buf_puts(&buf, var_name); git__free(var_name); if (git_buf_oom(&buf)) { diff --git a/src/config_parse.c b/src/config_parse.c index 149550dcd..d4c1c2bbd 100644 --- a/src/config_parse.c +++ b/src/config_parse.c @@ -315,50 +315,52 @@ done: static int parse_multiline_variable(git_config_parser *reader, git_buf *value, int in_quotes) { - char *line = NULL, *proc_line = NULL; int quote_count; - bool multiline; + bool multiline = true; - /* Check that the next line exists */ - git_parse_advance_line(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - if (line == NULL) - return -1; + while (multiline) { + char *line = NULL, *proc_line = NULL; + int error; - /* We've reached the end of the file, there is no continuation. - * (this is not an error). - */ - if (line[0] == '\0') { + /* Check that the next line exists */ + git_parse_advance_line(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + GITERR_CHECK_ALLOC(line); + + /* + * We've reached the end of the file, there is no continuation. + * (this is not an error). + */ + if (line[0] == '\0') { + error = 0; + goto out; + } + + /* If it was just a comment, pretend it didn't exist */ + quote_count = strip_comments(line, !!in_quotes); + if (line[0] == '\0') + goto next; + + if ((error = unescape_line(&proc_line, &multiline, + line, in_quotes)) < 0) + goto out; + + /* Add this line to the multiline var */ + if ((error = git_buf_puts(value, proc_line)) < 0) + goto out; + +next: git__free(line); - return 0; - } + git__free(proc_line); + in_quotes = quote_count; + continue; - quote_count = strip_comments(line, !!in_quotes); - - /* If it was just a comment, pretend it didn't exist */ - if (line[0] == '\0') { +out: git__free(line); - return parse_multiline_variable(reader, value, quote_count); - /* TODO: unbounded recursion. This **could** be exploitable */ + git__free(proc_line); + return error; } - if (unescape_line(&proc_line, &multiline, line, in_quotes) < 0) { - git__free(line); - return -1; - } - /* add this line to the multiline var */ - - git_buf_puts(value, proc_line); - git__free(line); - git__free(proc_line); - - /* - * If we need to continue reading the next line, let's just - * keep putting stuff in the buffer - */ - if (multiline) - return parse_multiline_variable(reader, value, quote_count); - return 0; } diff --git a/src/diff_tform.c b/src/diff_tform.c index bc664dd05..a9706e002 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -822,7 +822,7 @@ int git_diff_find_similar( num_deltas = diff->deltas.length; /* TODO: maybe abort if deltas.length > rename_limit ??? */ - if (!git__is_uint32(num_deltas)) + if (!num_deltas || !git__is_uint32(num_deltas)) goto cleanup; /* No flags set; nothing to do */ diff --git a/src/index.c b/src/index.c index 09313ebed..2b47e4dc3 100644 --- a/src/index.c +++ b/src/index.c @@ -1774,7 +1774,8 @@ int git_index_conflict_add(git_index *index, if (entries[i] && !valid_filemode(entries[i]->mode)) { giterr_set(GITERR_INDEX, "invalid filemode for stage %d entry", i + 1); - return -1; + ret = -1; + goto on_error; } } @@ -2206,7 +2207,7 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) for (i = 0; i < 3; i++) { int64_t tmp; - if (git__strtol64(&tmp, buffer, &endptr, 8) < 0 || + if (git__strntol64(&tmp, buffer, size, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || tmp < 0 || tmp > UINT32_MAX) { index_entry_reuc_free(lost); @@ -3509,7 +3510,7 @@ int git_index_snapshot_new(git_vector *snap, git_index *index) error = git_vector_dup(snap, &index->entries, index->entries._cmp); if (error < 0) - git_index_free(index); + git_index_snapshot_release(snap, index); return error; } diff --git a/src/odb.c b/src/odb.c index ef9c87555..ede2aa57b 100644 --- a/src/odb.c +++ b/src/odb.c @@ -1384,8 +1384,8 @@ static int git_odb_stream__invalid_length( { giterr_set(GITERR_ODB, "cannot %s - " - "Invalid length. %"PRIdZ" was expected. The " - "total size of the received chunks amounts to %"PRIdZ".", + "Invalid length. %"PRId64" was expected. The " + "total size of the received chunks amounts to %"PRId64".", action, stream->declared_size, stream->received_bytes); return -1; diff --git a/src/odb_pack.c b/src/odb_pack.c index 20aff5386..7c977d5cd 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -210,7 +210,7 @@ static int packfile_load__cb(void *data, git_buf *path) for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p = git_vector_get(&backend->packs, i); - if (memcmp(p->pack_name, path_str, cmp_len) == 0) + if (strncmp(p->pack_name, path_str, cmp_len) == 0) return 0; } diff --git a/src/pack-objects.c b/src/pack-objects.c index e9245143c..355b9e1e0 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -1666,7 +1666,7 @@ int insert_tree(git_packbuilder *pb, git_tree *tree) break; case GIT_OBJ_BLOB: - if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) + if ((error = retrieve_object(&obj, pb, entry_id)) < 0) return error; if (obj->uninteresting) continue; diff --git a/src/parse.c b/src/parse.c index 6b8902c35..b04fda36b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -8,12 +8,14 @@ int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len) { - if (content_len) + if (content && content_len) { ctx->content = content; - else - ctx->content = NULL; + ctx->content_len = content_len; + } else { + ctx->content = ""; + ctx->content_len = 0; + } - ctx->content_len = content_len; ctx->remain = ctx->content; ctx->remain_len = ctx->content_len; ctx->line = ctx->remain; @@ -26,6 +28,7 @@ int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_l void git_parse_ctx_clear(git_parse_ctx *ctx) { memset(ctx, 0, sizeof(*ctx)); + ctx->content = ""; } void git_parse_advance_line(git_parse_ctx *ctx) diff --git a/src/patch_parse.c b/src/patch_parse.c index acdd45e82..e02c87cf3 100644 --- a/src/patch_parse.c +++ b/src/patch_parse.c @@ -563,6 +563,8 @@ static int parse_hunk_body( char c; int origin; int prefix = 1; + int old_lineno = hunk->hunk.old_start + (hunk->hunk.old_lines - oldlines); + int new_lineno = hunk->hunk.new_start + (hunk->hunk.new_lines - newlines); if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { error = git_parse_err("invalid patch instruction at line %"PRIuZ, @@ -586,11 +588,13 @@ static int parse_hunk_body( case '-': origin = GIT_DIFF_LINE_DELETION; oldlines--; + new_lineno = -1; break; case '+': origin = GIT_DIFF_LINE_ADDITION; newlines--; + old_lineno = -1; break; default: @@ -607,6 +611,9 @@ static int parse_hunk_body( line->content_len = ctx->parse_ctx.line_len - prefix; line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; line->origin = origin; + line->num_lines = 1; + line->old_lineno = old_lineno; + line->new_lineno = new_lineno; hunk->line_count++; } diff --git a/src/rebase.c b/src/rebase.c index 3be751254..52211ad11 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -152,7 +152,7 @@ GIT_INLINE(int) rebase_readint( if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) return error; - if (git__strtol32(&num, asc_out->ptr, &eol, 10) < 0 || num < 0 || *eol) { + if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { giterr_set(GITERR_REBASE, "the file '%s' contains an invalid numeric value", filename); return -1; } diff --git a/src/remote.c b/src/remote.c index d8a6b991d..2078dd3e7 100644 --- a/src/remote.c +++ b/src/remote.c @@ -428,7 +428,7 @@ static int get_optional_config( int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) { - git_remote *remote; + git_remote *remote = NULL; git_buf buf = GIT_BUF_INIT; const char *val; int error = 0; @@ -510,7 +510,7 @@ int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) goto cleanup; - if (download_tags_value(remote, config) < 0) + if ((error = download_tags_value(remote, config)) < 0) goto cleanup; if ((error = lookup_remote_prune_config(remote, config, name)) < 0) @@ -1234,7 +1234,7 @@ int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks) goto cleanup; key.name = (char *) git_buf_cstr(&buf); - error = git_vector_search(&pos, &remote_refs, &key); + error = git_vector_bsearch(&pos, &remote_refs, &key); git_buf_free(&buf); if (error < 0 && error != GIT_ENOTFOUND) diff --git a/src/repository.c b/src/repository.c index 90b778e03..3c8987990 100644 --- a/src/repository.c +++ b/src/repository.c @@ -268,11 +268,15 @@ static int load_config_data(git_repository *repo, const git_config *config) { int is_bare; + int err = git_config_get_bool(&is_bare, config, "core.bare"); + if (err < 0 && err != GIT_ENOTFOUND) + return err; + /* Try to figure out if it's bare, default to non-bare if it's not set */ - if (git_config_get_bool(&is_bare, config, "core.bare") < 0) - repo->is_bare = 0; + if (err != GIT_ENOTFOUND) + repo->is_bare = is_bare && !repo->is_worktree; else - repo->is_bare = is_bare; + repo->is_bare = 0; return 0; } diff --git a/src/revparse.c b/src/revparse.c index 7cb22f476..f96f9bada 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -128,7 +128,8 @@ static int try_parse_numeric(int *n, const char *curly_braces_content) int32_t content; const char *end_ptr; - if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0) + if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content), + &end_ptr, 10) < 0) return -1; if (*end_ptr != '\0') @@ -578,7 +579,7 @@ static int extract_how_many(int *n, const char *spec, size_t *pos) } while (spec[(*pos)] == kind && kind == '~'); if (git__isdigit(spec[*pos])) { - if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) + if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0) return GIT_EINVALIDSPEC; accumulated += (parsed - 1); diff --git a/src/revwalk.c b/src/revwalk.c index eb228a522..f11dc0630 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -384,10 +384,16 @@ static int still_interesting(git_commit_list *list, int64_t time, int slop) if (!list) return 0; + /* + * If the destination list has commits with an earlier date than our + * source, we want to reset the slop counter as we're not done. + */ + if (time <= list->item->time) + return SLOP; + for (; list; list = list->next) { /* - * If the destination list has commits with an earlier date than - * our source or if it still contains interesting commits we + * If the destination list still contains interesting commits we * want to continue looking. */ if (!list->item->uninteresting || list->item->time > time) @@ -401,7 +407,7 @@ static int still_interesting(git_commit_list *list, int64_t time, int slop) static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits) { int error, slop = SLOP; - int64_t time = ~0ll; + int64_t time = INT64_MAX; git_commit_list *list = commits; git_commit_list *newlist = NULL; git_commit_list **p = &newlist; @@ -522,7 +528,7 @@ cleanup: static int prepare_walk(git_revwalk *walk) { - int error; + int error = 0; git_commit_list *list, *commits = NULL; git_commit_list_node *next; diff --git a/src/signature.c b/src/signature.c index cd6852326..91864bb88 100644 --- a/src/signature.c +++ b/src/signature.c @@ -231,7 +231,8 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, const char *time_start = email_end + 2; const char *time_end; - if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0) { + if (git__strntol64(&sig->when.time, time_start, + buffer_end - time_start, &time_end, 10) < 0) { git__free(sig->name); git__free(sig->email); sig->name = sig->email = NULL; @@ -246,8 +247,9 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, tz_start = time_end + 1; if ((tz_start[0] != '-' && tz_start[0] != '+') || - git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0) { - //malformed timezone, just assume it's zero + git__strntol32(&offset, tz_start + 1, + buffer_end - tz_start + 1, &tz_end, 10) < 0) { + /* malformed timezone, just assume it's zero */ offset = 0; } diff --git a/src/streams/curl.c b/src/streams/curl.c index ee13be1dc..3c0af3b04 100644 --- a/src/streams/curl.c +++ b/src/streams/curl.c @@ -330,7 +330,7 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port) return -1; } - if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) { + if ((error = git__strntol32(&iport, port, strlen(port), NULL, 10)) < 0) { git__free(st); return error; } diff --git a/src/submodule.c b/src/submodule.c index 4bf74dcfb..06b7de585 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1813,6 +1813,14 @@ static int get_value(const char **out, git_config *cfg, git_buf *buf, const char return error; } +static bool looks_like_command_line_option(const char *s) +{ + if (s && s[0] == '-') + return true; + + return false; +} + static int submodule_read_config(git_submodule *sm, git_config *cfg) { git_buf key = GIT_BUF_INIT; @@ -1826,24 +1834,31 @@ static int submodule_read_config(git_submodule *sm, git_config *cfg) if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { in_config = 1; + /* We would warn here if we had that API */ + if (!looks_like_command_line_option(value)) { /* * TODO: if case insensitive filesystem, then the following strcmp * should be strcasecmp */ - if (strcmp(sm->name, value) != 0) { - if (sm->path != sm->name) - git__free(sm->path); - sm->path = git__strdup(value); - GITERR_CHECK_ALLOC(sm->path); + if (strcmp(sm->name, value) != 0) { + if (sm->path != sm->name) + git__free(sm->path); + sm->path = git__strdup(value); + GITERR_CHECK_ALLOC(sm->path); + } + } } else if (error != GIT_ENOTFOUND) { goto cleanup; } if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { - in_config = 1; - sm->url = git__strdup(value); - GITERR_CHECK_ALLOC(sm->url); + /* We would warn here if we had that API */ + if (!looks_like_command_line_option(value)) { + in_config = 1; + sm->url = git__strdup(value); + GITERR_CHECK_ALLOC(sm->url); + } } else if (error != GIT_ENOTFOUND) { goto cleanup; } diff --git a/src/tag.c b/src/tag.c index 445c3ff1d..1ec823731 100644 --- a/src/tag.c +++ b/src/tag.c @@ -70,10 +70,9 @@ static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) static const char *tag_types[] = { NULL, "commit\n", "tree\n", "blob\n", "tag\n" }; - - unsigned int i; size_t text_len, alloc_len; - char *search; + const char *search; + unsigned int i; if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) return tag_error("object field invalid"); @@ -138,8 +137,9 @@ static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) tag->message = NULL; if (buffer < buffer_end) { /* If we're not at the end of the header, search for it */ - if( *buffer != '\n' ) { - search = strstr(buffer, "\n\n"); + if(*buffer != '\n') { + search = git__memmem(buffer, buffer_end - buffer, + "\n\n", 2); if (search) buffer = search + 1; else diff --git a/src/transports/http.c b/src/transports/http.c index e051c8a35..22b8bb864 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -188,6 +188,9 @@ static int apply_credentials(git_buf *buf, http_subtransport *t) if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0) return -1; + if (!context) + return 0; + return context->next_token(buf, context, cred); } diff --git a/src/transports/smart.c b/src/transports/smart.c index 6d5d95fbf..5fedd394e 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -45,6 +45,11 @@ GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransp t->current_stream = NULL; } + if (t->url) { + git__free(t->url); + t->url = NULL; + } + if (close_subtransport && t->wrapped->close(t->wrapped) < 0) return -1; diff --git a/src/transports/smart.h b/src/transports/smart.h index e33a25402..e6020817e 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -33,14 +33,14 @@ extern bool git_smart__ofs_delta_enabled; -enum git_pkt_type { +typedef enum { GIT_PKT_CMD, GIT_PKT_FLUSH, GIT_PKT_REF, GIT_PKT_HAVE, GIT_PKT_ACK, GIT_PKT_NAK, - GIT_PKT_PACK, + GIT_PKT_PACK__UNUSED, GIT_PKT_COMMENT, GIT_PKT_ERR, GIT_PKT_DATA, @@ -48,7 +48,7 @@ enum git_pkt_type { GIT_PKT_OK, GIT_PKT_NG, GIT_PKT_UNPACK, -}; +} git_pkt_type; /* Used for multi_ack and mutli_ack_detailed */ enum git_ack_status { @@ -60,11 +60,11 @@ enum git_ack_status { /* This would be a flush pkt */ typedef struct { - enum git_pkt_type type; + git_pkt_type type; } git_pkt; struct git_pkt_cmd { - enum git_pkt_type type; + git_pkt_type type; char *cmd; char *path; char *host; @@ -72,50 +72,50 @@ struct git_pkt_cmd { /* This is a pkt-line with some info in it */ typedef struct { - enum git_pkt_type type; + git_pkt_type type; git_remote_head head; char *capabilities; } git_pkt_ref; /* Useful later */ typedef struct { - enum git_pkt_type type; + git_pkt_type type; git_oid oid; enum git_ack_status status; } git_pkt_ack; typedef struct { - enum git_pkt_type type; + git_pkt_type type; char comment[GIT_FLEX_ARRAY]; } git_pkt_comment; typedef struct { - enum git_pkt_type type; - int len; + git_pkt_type type; + size_t len; char data[GIT_FLEX_ARRAY]; } git_pkt_data; typedef git_pkt_data git_pkt_progress; typedef struct { - enum git_pkt_type type; - int len; + git_pkt_type type; + size_t len; char error[GIT_FLEX_ARRAY]; } git_pkt_err; typedef struct { - enum git_pkt_type type; + git_pkt_type type; char *ref; } git_pkt_ok; typedef struct { - enum git_pkt_type type; + git_pkt_type type; char *ref; char *msg; } git_pkt_ng; typedef struct { - enum git_pkt_type type; + git_pkt_type type; int unpack_ok; } git_pkt_unpack; @@ -189,7 +189,7 @@ int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream int git_smart__update_heads(transport_smart *t, git_vector *symrefs); /* smart_pkt.c */ -int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); +int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen); int git_pkt_buffer_flush(git_buf *buf); int git_pkt_send_flush(GIT_SOCKET s); int git_pkt_buffer_done(git_buf *buf); diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index d10d6c68f..0e05ff861 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -43,34 +43,43 @@ static int flush_pkt(git_pkt **out) static int ack_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_ack *pkt; - GIT_UNUSED(line); - GIT_UNUSED(len); pkt = git__calloc(1, sizeof(git_pkt_ack)); GITERR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_ACK; - line += 3; - len -= 3; - if (len >= GIT_OID_HEXSZ) { - git_oid_fromstr(&pkt->oid, line + 1); - line += GIT_OID_HEXSZ + 1; - len -= GIT_OID_HEXSZ + 1; - } + if (git__prefixncmp(line, len, "ACK ")) + goto out_err; + line += 4; + len -= 4; - if (len >= 7) { - if (!git__prefixcmp(line + 1, "continue")) + if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->oid, line) < 0) + goto out_err; + line += GIT_OID_HEXSZ; + len -= GIT_OID_HEXSZ; + + if (len && line[0] == ' ') { + line++; + len--; + + if (!git__prefixncmp(line, len, "continue")) pkt->status = GIT_ACK_CONTINUE; - if (!git__prefixcmp(line + 1, "common")) + else if (!git__prefixncmp(line, len, "common")) pkt->status = GIT_ACK_COMMON; - if (!git__prefixcmp(line + 1, "ready")) + else if (!git__prefixncmp(line, len, "ready")) pkt->status = GIT_ACK_READY; + else + goto out_err; } *out = (git_pkt *) pkt; return 0; + +out_err: + giterr_set(GITERR_NET, "error parsing ACK pkt-line"); + git__free(pkt); + return -1; } static int nak_pkt(git_pkt **out) @@ -86,19 +95,6 @@ static int nak_pkt(git_pkt **out) return 0; } -static int pack_pkt(git_pkt **out) -{ - git_pkt *pkt; - - pkt = git__malloc(sizeof(git_pkt)); - GITERR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_PACK; - *out = pkt; - - return 0; -} - static int comment_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_comment *pkt; @@ -120,10 +116,12 @@ static int comment_pkt(git_pkt **out, const char *line, size_t len) static int err_pkt(git_pkt **out, const char *line, size_t len) { - git_pkt_err *pkt; + git_pkt_err *pkt = NULL; size_t alloclen; /* Remove "ERR " from the line */ + if (git__prefixncmp(line, len, "ERR ")) + goto out_err; line += 4; len -= 4; @@ -131,15 +129,20 @@ static int err_pkt(git_pkt **out, const char *line, size_t len) GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); pkt = git__malloc(alloclen); GITERR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_ERR; - pkt->len = (int)len; + pkt->len = len; + memcpy(pkt->error, line, len); pkt->error[len] = '\0'; *out = (git_pkt *) pkt; return 0; + +out_err: + giterr_set(GITERR_NET, "error parsing ERR pkt-line"); + git__free(pkt); + return -1; } static int data_pkt(git_pkt **out, const char *line, size_t len) @@ -155,7 +158,7 @@ static int data_pkt(git_pkt **out, const char *line, size_t len) GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_DATA; - pkt->len = (int) len; + pkt->len = len; memcpy(pkt->data, line, len); *out = (git_pkt *) pkt; @@ -176,7 +179,7 @@ static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len) GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_PROGRESS; - pkt->len = (int) len; + pkt->len = len; memcpy(pkt->data, line, len); *out = (git_pkt *) pkt; @@ -212,28 +215,25 @@ static int sideband_error_pkt(git_pkt **out, const char *line, size_t len) */ static int ref_pkt(git_pkt **out, const char *line, size_t len) { - int error; git_pkt_ref *pkt; size_t alloclen; - pkt = git__malloc(sizeof(git_pkt_ref)); + pkt = git__calloc(1, sizeof(git_pkt_ref)); GITERR_CHECK_ALLOC(pkt); - - memset(pkt, 0x0, sizeof(git_pkt_ref)); pkt->type = GIT_PKT_REF; - if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0) - goto error_out; - /* Check for a bit of consistency */ - if (line[GIT_OID_HEXSZ] != ' ') { - giterr_set(GITERR_NET, "error parsing pkt-line"); - error = -1; - goto error_out; - } + if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->head.oid, line) < 0) + goto out_err; + line += GIT_OID_HEXSZ; + len -= GIT_OID_HEXSZ; - /* Jump from the name */ - line += GIT_OID_HEXSZ + 1; - len -= (GIT_OID_HEXSZ + 1); + if (git__prefixncmp(line, len, " ")) + goto out_err; + line++; + len--; + + if (!len) + goto out_err; if (line[len - 1] == '\n') --len; @@ -245,36 +245,36 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len) memcpy(pkt->head.name, line, len); pkt->head.name[len] = '\0'; - if (strlen(pkt->head.name) < len) { + if (strlen(pkt->head.name) < len) pkt->capabilities = strchr(pkt->head.name, '\0') + 1; - } *out = (git_pkt *)pkt; return 0; -error_out: +out_err: + giterr_set(GITERR_NET, "error parsing REF pkt-line"); + if (pkt) + git__free(pkt->head.name); git__free(pkt); - return error; + return -1; } static int ok_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_ok *pkt; - const char *ptr; size_t alloc_len; pkt = git__malloc(sizeof(*pkt)); GITERR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_OK; - line += 3; /* skip "ok " */ - if (!(ptr = strchr(line, '\n'))) { - giterr_set(GITERR_NET, "invalid packet line"); - git__free(pkt); - return -1; - } - len = ptr - line; + if (git__prefixncmp(line, len, "ok ")) + goto out_err; + line += 3; + len -= 3; + + if (line[len - 1] == '\n') + --len; GITERR_CHECK_ALLOC_ADD(&alloc_len, len, 1); pkt->ref = git__malloc(alloc_len); @@ -285,12 +285,17 @@ static int ok_pkt(git_pkt **out, const char *line, size_t len) *out = (git_pkt *)pkt; return 0; + +out_err: + giterr_set(GITERR_NET, "error parsing OK pkt-line"); + git__free(pkt); + return -1; } static int ng_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_ng *pkt; - const char *ptr; + const char *ptr, *eol; size_t alloclen; pkt = git__malloc(sizeof(*pkt)); @@ -299,11 +304,13 @@ static int ng_pkt(git_pkt **out, const char *line, size_t len) pkt->ref = NULL; pkt->type = GIT_PKT_NG; - if (len < 3) + eol = line + len; + + if (git__prefixncmp(line, len, "ng ")) goto out_err; - line += 3; /* skip "ng " */ - len -= 3; - if (!(ptr = memchr(line, ' ', len))) + line += 3; + + if (!(ptr = memchr(line, ' ', eol - line))) goto out_err; len = ptr - line; @@ -314,11 +321,11 @@ static int ng_pkt(git_pkt **out, const char *line, size_t len) memcpy(pkt->ref, line, len); pkt->ref[len] = '\0'; - if (len < 1) - goto out_err; line = ptr + 1; - len -= 1; - if (!(ptr = memchr(line, '\n', len))) + if (line >= eol) + goto out_err; + + if (!(ptr = memchr(line, '\n', eol - line))) goto out_err; len = ptr - line; @@ -343,13 +350,11 @@ static int unpack_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_unpack *pkt; - GIT_UNUSED(len); - pkt = git__malloc(sizeof(*pkt)); GITERR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_UNPACK; - if (!git__prefixcmp(line, "unpack ok")) + + if (!git__prefixncmp(line, len, "unpack ok")) pkt->unpack_ok = 1; else pkt->unpack_ok = 0; @@ -358,13 +363,17 @@ static int unpack_pkt(git_pkt **out, const char *line, size_t len) return 0; } -static int32_t parse_len(const char *line) +static int parse_len(size_t *out, const char *line, size_t linelen) { char num[PKT_LEN_SIZE + 1]; int i, k, error; int32_t len; const char *num_end; + /* Not even enough for the length */ + if (linelen < PKT_LEN_SIZE) + return GIT_EBUFS; + memcpy(num, line, PKT_LEN_SIZE); num[PKT_LEN_SIZE] = '\0'; @@ -376,16 +385,20 @@ static int32_t parse_len(const char *line) num[k] = '.'; } } - + giterr_set(GITERR_NET, "invalid hex digit in length: '%s'", num); return -1; } } - if ((error = git__strtol32(&len, num, &num_end, 16)) < 0) + if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0) return error; - return len; + if (len < 0) + return -1; + + *out = (size_t) len; + return 0; } /* @@ -402,35 +415,32 @@ static int32_t parse_len(const char *line) */ int git_pkt_parse_line( - git_pkt **head, const char *line, const char **out, size_t bufflen) + git_pkt **pkt, const char **endptr, const char *line, size_t linelen) { - int ret; - int32_t len; + int error; + size_t len; - /* Not even enough for the length */ - if (bufflen > 0 && bufflen < PKT_LEN_SIZE) - return GIT_EBUFS; - - len = parse_len(line); - if (len < 0) { + if ((error = parse_len(&len, line, linelen)) < 0) { /* - * If we fail to parse the length, it might be because the - * server is trying to send us the packfile already. + * If we fail to parse the length, it might be + * because the server is trying to send us the + * packfile already or because we do not yet have + * enough data. */ - if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) { - giterr_clear(); - *out = line; - return pack_pkt(head); - } - - return (int)len; + if (error == GIT_EBUFS) + ; + else if (!git__prefixncmp(line, linelen, "PACK")) + giterr_set(GITERR_NET, "unexpected pack file"); + else + giterr_set(GITERR_NET, "bad packet length"); + return error; } /* - * If we were given a buffer length, then make sure there is - * enough in the buffer to satisfy this line + * Make sure there is enough in the buffer to satisfy + * this line. */ - if (bufflen > 0 && bufflen < (size_t)len) + if (linelen < len) return GIT_EBUFS; /* @@ -453,38 +463,38 @@ int git_pkt_parse_line( } if (len == 0) { /* Flush pkt */ - *out = line; - return flush_pkt(head); + *endptr = line; + return flush_pkt(pkt); } len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ if (*line == GIT_SIDE_BAND_DATA) - ret = data_pkt(head, line, len); + error = data_pkt(pkt, line, len); else if (*line == GIT_SIDE_BAND_PROGRESS) - ret = sideband_progress_pkt(head, line, len); + error = sideband_progress_pkt(pkt, line, len); else if (*line == GIT_SIDE_BAND_ERROR) - ret = sideband_error_pkt(head, line, len); - else if (!git__prefixcmp(line, "ACK")) - ret = ack_pkt(head, line, len); - else if (!git__prefixcmp(line, "NAK")) - ret = nak_pkt(head); - else if (!git__prefixcmp(line, "ERR ")) - ret = err_pkt(head, line, len); + error = sideband_error_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ACK")) + error = ack_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "NAK")) + error = nak_pkt(pkt); + else if (!git__prefixncmp(line, len, "ERR")) + error = err_pkt(pkt, line, len); else if (*line == '#') - ret = comment_pkt(head, line, len); - else if (!git__prefixcmp(line, "ok")) - ret = ok_pkt(head, line, len); - else if (!git__prefixcmp(line, "ng")) - ret = ng_pkt(head, line, len); - else if (!git__prefixcmp(line, "unpack")) - ret = unpack_pkt(head, line, len); + error = comment_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ok")) + error = ok_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ng")) + error = ng_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "unpack")) + error = unpack_pkt(pkt, line, len); else - ret = ref_pkt(head, line, len); + error = ref_pkt(pkt, line, len); - *out = line + len; + *endptr = line + len; - return ret; + return error; } void git_pkt_free(git_pkt *pkt) diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index aecfece0a..893ac6e47 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -44,7 +44,7 @@ int git_smart__store_refs(transport_smart *t, int flushes) do { if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); + error = git_pkt_parse_line(&pkt, &line_end, buf->data, buf->offset); else error = GIT_EBUFS; @@ -209,15 +209,15 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vec return 0; } -static int recv_pkt(git_pkt **out, gitno_buffer *buf) +static int recv_pkt(git_pkt **out, git_pkt_type *pkt_type, gitno_buffer *buf) { const char *ptr = buf->data, *line_end = ptr; git_pkt *pkt = NULL; - int pkt_type, error = 0, ret; + int error = 0, ret; do { if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + error = git_pkt_parse_line(&pkt, &line_end, ptr, buf->offset); else error = GIT_EBUFS; @@ -236,13 +236,14 @@ static int recv_pkt(git_pkt **out, gitno_buffer *buf) } while (error); gitno_consume(buf, line_end); - pkt_type = pkt->type; + if (pkt_type) + *pkt_type = pkt->type; if (out != NULL) *out = pkt; else git__free(pkt); - return pkt_type; + return error; } static int store_common(transport_smart *t) @@ -252,7 +253,7 @@ static int store_common(transport_smart *t) int error; do { - if ((error = recv_pkt(&pkt, buf)) < 0) + if ((error = recv_pkt(&pkt, NULL, buf)) < 0) return error; if (pkt->type == GIT_PKT_ACK) { @@ -320,7 +321,7 @@ static int wait_while_ack(gitno_buffer *buf) while (1) { git__free(pkt); - if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0) + if ((error = recv_pkt((git_pkt **)&pkt, NULL, buf)) < 0) return error; if (pkt->type == GIT_PKT_NAK) @@ -345,7 +346,8 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c gitno_buffer *buf = &t->buffer; git_buf data = GIT_BUF_INIT; git_revwalk *walk = NULL; - int error = -1, pkt_type; + int error = -1; + git_pkt_type pkt_type; unsigned int i; git_oid oid; @@ -395,16 +397,13 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c if ((error = store_common(t)) < 0) goto on_error; } else { - pkt_type = recv_pkt(NULL, buf); - - if (pkt_type == GIT_PKT_ACK) { + error = recv_pkt(NULL, &pkt_type, buf); + if (error < 0) { + goto on_error; + } else if (pkt_type == GIT_PKT_ACK) { break; } else if (pkt_type == GIT_PKT_NAK) { continue; - } else if (pkt_type < 0) { - /* recv_pkt returned an error */ - error = pkt_type; - goto on_error; } else { giterr_set(GITERR_NET, "Unexpected pkt type"); error = -1; @@ -470,10 +469,10 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c /* Now let's eat up whatever the server gives us */ if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) { - pkt_type = recv_pkt(NULL, buf); + error = recv_pkt(NULL, &pkt_type, buf); - if (pkt_type < 0) { - return pkt_type; + if (error < 0) { + return error; } else if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { giterr_set(GITERR_NET, "Unexpected pkt type"); return -1; @@ -594,7 +593,7 @@ int git_smart__download_pack( goto done; } - if ((error = recv_pkt(&pkt, buf)) >= 0) { + if ((error = recv_pkt(&pkt, NULL, buf)) >= 0) { /* Check cancellation after network call */ if (t->cancelled.val) { giterr_clear(); @@ -752,7 +751,7 @@ static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, } while (line_len > 0) { - error = git_pkt_parse_line(&pkt, line, &line_end, line_len); + error = git_pkt_parse_line(&pkt, &line_end, line, line_len); if (error == GIT_EBUFS) { /* Buffer the data when the inner packet is split @@ -795,8 +794,8 @@ static int parse_report(transport_smart *transport, git_push *push) for (;;) { if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, buf->data, - &line_end, buf->offset); + error = git_pkt_parse_line(&pkt, &line_end, + buf->data, buf->offset); else error = GIT_EBUFS; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index e52d54b6d..c3c18a80a 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -761,7 +761,8 @@ static int winhttp_connect( t->connection = NULL; /* Prepare port */ - if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0) + if (git__strntol32(&port, t->connection_data.port, + strlen(t->connection_data.port), NULL, 10) < 0) return -1; /* Prepare host */ @@ -845,23 +846,27 @@ on_error: static int do_send_request(winhttp_stream *s, size_t len, int ignore_length) { - if (ignore_length) { - if (!WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) { - return -1; - } - } else { - if (!WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - len, 0)) { - return -1; + int attempts; + bool success; + + for (attempts = 0; attempts < 5; attempts++) { + if (ignore_length) { + success = WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0); + } else { + success = WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + len, 0); } + + if (success || GetLastError() != SEC_E_BUFFER_TOO_SMALL) + break; } - return 0; + return success ? 0 : -1; } static int send_request(winhttp_stream *s, size_t len, int ignore_length) diff --git a/src/tree-cache.c b/src/tree-cache.c index b331d22a2..c33e6af9e 100644 --- a/src/tree-cache.c +++ b/src/tree-cache.c @@ -91,7 +91,7 @@ static int read_tree_internal(git_tree_cache **out, return -1; /* Blank-terminated ASCII decimal number of entries in this tree */ - if (git__strtol32(&count, buffer, &buffer, 10) < 0) + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0) goto corrupted; tree->entry_count = count; @@ -100,7 +100,7 @@ static int read_tree_internal(git_tree_cache **out, goto corrupted; /* Number of children of the tree, newline-terminated */ - if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0) + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0 || count < 0) goto corrupted; tree->children_count = count; diff --git a/src/tree.c b/src/tree.c index 12622975a..a014ce807 100644 --- a/src/tree.c +++ b/src/tree.c @@ -490,15 +490,16 @@ static int append_entry( git_treebuilder *bld, const char *filename, const git_oid *id, - git_filemode_t filemode) + git_filemode_t filemode, + bool validate) { git_tree_entry *entry; int error = 0; - if (!valid_entry_name(bld->repo, filename)) + if (validate && !valid_entry_name(bld->repo, filename)) return tree_error("failed to insert entry: invalid name for a tree entry", filename); - if (git_oid_iszero(id)) + if (validate && git_oid_iszero(id)) return tree_error("failed to insert entry: invalid null OID for a tree entry", filename); entry = alloc_entry(filename, strlen(filename), id); @@ -596,12 +597,12 @@ static int write_tree( last_comp = subdir; } - error = append_entry(bld, last_comp, &sub_oid, S_IFDIR); + error = append_entry(bld, last_comp, &sub_oid, S_IFDIR, true); git__free(subdir); if (error < 0) goto on_error; } else { - error = append_entry(bld, filename, &entry->id, entry->mode); + error = append_entry(bld, filename, &entry->id, entry->mode, true); if (error < 0) goto on_error; } @@ -699,7 +700,8 @@ int git_treebuilder_new( if (append_entry( bld, entry_src->filename, entry_src->oid, - entry_src->attr) < 0) + entry_src->attr, + false) < 0) goto on_error; } } diff --git a/src/util.c b/src/util.c index 2955b7ca0..911921857 100644 --- a/src/util.c +++ b/src/util.c @@ -68,12 +68,6 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src) return 0; } -int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base) -{ - - return git__strntol64(result, nptr, (size_t)-1, endptr, base); -} - int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) { const char *p; @@ -132,10 +126,20 @@ int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const cha v = c - 'A' + 10; if (v >= base) break; - nn = n * base + (neg ? -v : v); - if ((!neg && nn < n) || (neg && nn > n)) + v = neg ? -v : v; + if (n > INT64_MAX / base || n < INT64_MIN / base) { ovfl = 1; - n = nn; + /* Keep on iterating until the end of this number */ + continue; + } + nn = n * base; + if ((v > 0 && nn > INT64_MAX - v) || + (v < 0 && nn < INT64_MIN - v)) { + ovfl = 1; + /* Keep on iterating until the end of this number */ + continue; + } + n = nn + v; } Return: @@ -156,28 +160,26 @@ Return: return 0; } -int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) -{ - - return git__strntol32(result, nptr, (size_t)-1, endptr, base); -} - int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) { - int error; + const char *tmp_endptr; int32_t tmp_int; int64_t tmp_long; + int error; - if ((error = git__strntol64(&tmp_long, nptr, nptr_len, endptr, base)) < 0) + if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) return error; tmp_int = tmp_long & 0xFFFFFFFF; if (tmp_int != tmp_long) { - giterr_set(GITERR_INVALID, "failed to convert: '%s' is too large", nptr); + int len = tmp_endptr - nptr; + giterr_set(GITERR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); return -1; } *result = tmp_int; + if (endptr) + *endptr = tmp_endptr; return error; } @@ -355,6 +357,47 @@ size_t git__linenlen(const char *buffer, size_t buffer_len) return nl ? (size_t)(nl - buffer) + 1 : buffer_len; } +/* + * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ + */ +const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) +{ + const char *h, *n; + size_t j, k, l; + + if (needlelen > haystacklen || !haystacklen || !needlelen) + return NULL; + + h = (const char *) haystack, + n = (const char *) needle; + + if (needlelen == 1) + return memchr(haystack, *n, haystacklen); + + if (n[0] == n[1]) { + k = 2; + l = 1; + } else { + k = 1; + l = 2; + } + + j = 0; + while (j <= haystacklen - needlelen) { + if (n[1] != h[j + 1]) { + j += k; + } else { + if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && + n[0] == h[j]) + return h + j; + j += l; + } + } + + return NULL; +} + void git__hexdump(const char *buffer, size_t len) { static const size_t LINE_WIDTH = 16; @@ -647,7 +690,7 @@ size_t git__unescape(char *str) return (pos - str); } -#if defined(HAVE_QSORT_S) || (defined(HAVE_QSORT_R) && defined(BSD)) +#if defined(HAVE_QSORT_S) || defined(HAVE_QSORT_R_BSD) typedef struct { git__sort_r_cmp cmp; void *payload; @@ -664,10 +707,10 @@ static int GIT_STDLIB_CALL git__qsort_r_glue_cmp( void git__qsort_r( void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) { -#if defined(HAVE_QSORT_R) && defined(BSD) +#if defined(HAVE_QSORT_R_BSD) git__qsort_r_glue glue = { cmp, payload }; qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); -#elif defined(HAVE_QSORT_R) && defined(__GLIBC__) +#elif defined(HAVE_QSORT_R_GNU) qsort_r(els, nel, elsize, cmp, payload); #elif defined(HAVE_QSORT_S) git__qsort_r_glue glue = { cmp, payload }; diff --git a/src/util.h b/src/util.h index f6d19cfde..f2ea22ebf 100644 --- a/src/util.h +++ b/src/util.h @@ -193,9 +193,7 @@ GIT_INLINE(int) git__signum(int val) return ((val > 0) - (val < 0)); } -extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); -extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); @@ -248,6 +246,9 @@ GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) return NULL; } +extern const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + typedef int (*git__tsort_cmp)(const void *a, const void *b); extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); diff --git a/src/vector.c b/src/vector.c index b12fa942d..98aa7bb2f 100644 --- a/src/vector.c +++ b/src/vector.c @@ -32,6 +32,9 @@ GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) { void *new_contents; + if (new_size == 0) + return 0; + new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); GITERR_CHECK_ALLOC(new_contents); @@ -50,22 +53,24 @@ int git_vector_size_hint(git_vector *v, size_t size_hint) int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) { - size_t bytes; - assert(v && src); - GITERR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); - - v->_alloc_size = src->length; + v->_alloc_size = 0; + v->contents = NULL; v->_cmp = cmp ? cmp : src->_cmp; v->length = src->length; v->flags = src->flags; if (cmp != src->_cmp) git_vector_set_sorted(v, 0); - v->contents = git__malloc(bytes); - GITERR_CHECK_ALLOC(v->contents); - memcpy(v->contents, src->contents, bytes); + if (src->length) { + size_t bytes; + GITERR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); + v->contents = git__malloc(bytes); + GITERR_CHECK_ALLOC(v->contents); + v->_alloc_size = src->length; + memcpy(v->contents, src->contents, bytes); + } return 0; } diff --git a/src/worktree.c b/src/worktree.c index 91527663e..4acf61b8b 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -139,7 +139,7 @@ static int open_worktree_dir(git_worktree **out, const char *parent, const char if ((wt->name = git__strdup(name)) == NULL || (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL || (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL - || (wt->parent_path = git__strdup(parent)) == NULL) { + || (parent && (wt->parent_path = git__strdup(parent)) == NULL)) { error = -1; goto out; } @@ -417,7 +417,7 @@ int git_worktree_unlock(git_worktree *wt) assert(wt); if (!git_worktree_is_locked(NULL, wt)) - return 0; + return 1; if (git_buf_joinpath(&path, wt->gitdir_path, "locked") < 0) return -1; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 82bf6d0d7..02042df6d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -52,12 +52,8 @@ IF (MSVC_IDE) SET_SOURCE_FILES_PROPERTIES("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") ENDIF () -IF (USE_HTTPS) - ADD_TEST(libgit2_clar "${libgit2_BINARY_DIR}/libgit2_clar" -ionline -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) -ELSE () - ADD_TEST(libgit2_clar "${libgit2_BINARY_DIR}/libgit2_clar" -v -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) -ENDIF () - -# Add additional test targets that require special setup -ADD_TEST(libgit2_clar-proxy_credentials "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request) -ADD_TEST(libgit2_clar-ssh "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths) +ADD_TEST(offline "${libgit2_BINARY_DIR}/libgit2_clar" -v -xonline) +ADD_TEST(online "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline) +ADD_TEST(gitdaemon "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push) +ADD_TEST(ssh "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths) +ADD_TEST(proxy "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request) diff --git a/tests/buf/oom.c b/tests/buf/oom.c index b9fd29cbb..16a03cc1a 100644 --- a/tests/buf/oom.c +++ b/tests/buf/oom.c @@ -1,10 +1,22 @@ #include "clar_libgit2.h" #include "buffer.h" -#if defined(GIT_ARCH_64) -#define TOOBIG 0xffffffffffffff00 +/* + * We want to use some ridiculous size that `malloc` will fail with + * but that does not otherwise interfere with testing. On Linux, choose + * a number that is large enough to fail immediately but small enough + * that valgrind doesn't believe it to erroneously be a negative number. + * On macOS, choose a number that is large enough to fail immediately + * without having libc print warnings to stderr. + */ +#if defined(GIT_ARCH_64) && defined(__linux__) +# define TOOBIG 0x0fffffffffffffff +#elif defined(__linux__) +# define TOOBIG 0x0fffffff +#elif defined(GIT_ARCH_64) +# define TOOBIG 0xffffffffffffff00 #else -#define TOOBIG 0xffffff00 +# define TOOBIG 0xffffff00 #endif /** diff --git a/tests/clar.c b/tests/clar.c index d5212d1ca..27d35e1c7 100644 --- a/tests/clar.c +++ b/tests/clar.c @@ -95,9 +95,6 @@ static const char * fixture_path(const char *base, const char *fixture_name); struct clar_error { - const char *test; - int test_number; - const char *suite; const char *file; int line_number; const char *error_msg; @@ -106,11 +103,34 @@ struct clar_error { struct clar_error *next; }; -static struct { - int argc; - char **argv; +struct clar_explicit { + size_t suite_idx; + const char *filter; + struct clar_explicit *next; +}; + +struct clar_report { + const char *test; + int test_number; + const char *suite; + + enum cl_test_status status; + + struct clar_error *errors; + struct clar_error *last_error; + + struct clar_report *next; +}; + +struct clar_summary { + const char *filename; + FILE *fp; +}; + +static struct { enum cl_test_status test_status; + const char *active_test; const char *active_suite; @@ -124,8 +144,15 @@ static struct { int exit_on_error; int report_suite_names; - struct clar_error *errors; - struct clar_error *last_error; + int write_summary; + const char *summary_filename; + struct clar_summary *summary; + + struct clar_explicit *explicit; + struct clar_explicit *last_explicit; + + struct clar_report *reports; + struct clar_report *last_report; void (*local_cleanup)(void *); void *local_cleanup_payload; @@ -155,7 +182,7 @@ struct clar_suite { /* From clar_print_*.c */ static void clar_print_init(int test_count, int suite_count, const char *suite_names); static void clar_print_shutdown(int test_count, int suite_count, int error_count); -static void clar_print_error(int num, const struct clar_error *error); +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); static void clar_print_onsuite(const char *suite_name, int suite_index); static void clar_print_onabort(const char *msg, ...); @@ -164,6 +191,10 @@ static void clar_print_onabort(const char *msg, ...); static void clar_unsandbox(void); static int clar_sandbox(void); +/* From summary.h */ +static struct clar_summary *clar_summary_init(const char *filename); +static int clar_summary_shutdown(struct clar_summary *fp); + /* Load the declarations for the test suite */ #include "clar.suite" @@ -186,21 +217,29 @@ void cl_trace_register(cl_trace_cb *cb, void *payload) /* Core test functions */ static void -clar_report_errors(void) +clar_report_errors(struct clar_report *report) { + struct clar_error *error; int i = 1; - struct clar_error *error, *next; - error = _clar.errors; - while (error != NULL) { - next = error->next; - clar_print_error(i++, error); - free(error->description); - free(error); - error = next; + for (error = report->errors; error; error = error->next) + clar_print_error(i++, _clar.last_report, error); +} + +static void +clar_report_all(void) +{ + struct clar_report *report; + struct clar_error *error; + int i = 1; + + for (report = _clar.reports; report; report = report->next) { + if (report->status != CL_TEST_FAILURE) + continue; + + for (error = report->errors; error; error = error->next) + clar_print_error(i++, report, error); } - - _clar.errors = _clar.last_error = NULL; } static void @@ -209,7 +248,6 @@ clar_run_test( const struct clar_func *initialize, const struct clar_func *cleanup) { - _clar.test_status = CL_TEST_OK; _clar.trampoline_enabled = 1; CL_TRACE(CL_TRACE__TEST__BEGIN); @@ -225,6 +263,9 @@ clar_run_test( _clar.trampoline_enabled = 0; + if (_clar.last_report->status == CL_TEST_NOTRUN) + _clar.last_report->status = CL_TEST_OK; + if (_clar.local_cleanup != NULL) _clar.local_cleanup(_clar.local_cleanup_payload); @@ -240,9 +281,9 @@ clar_run_test( _clar.local_cleanup_payload = NULL; if (_clar.report_errors_only) { - clar_report_errors(); + clar_report_errors(_clar.last_report); } else { - clar_print_ontest(test->name, _clar.tests_ran, _clar.test_status); + clar_print_ontest(test->name, _clar.tests_ran, _clar.last_report->status); } } @@ -251,6 +292,7 @@ clar_run_suite(const struct clar_suite *suite, const char *filter) { const struct clar_func *test = suite->tests; size_t i, matchlen; + struct clar_report *report; if (!suite->enabled) return; @@ -283,6 +325,21 @@ clar_run_suite(const struct clar_suite *suite, const char *filter) continue; _clar.active_test = test[i].name; + + report = calloc(1, sizeof(struct clar_report)); + report->suite = _clar.active_suite; + report->test = _clar.active_test; + report->test_number = _clar.tests_ran; + report->status = CL_TEST_NOTRUN; + + if (_clar.reports == NULL) + _clar.reports = report; + + if (_clar.last_report != NULL) + _clar.last_report->next = report; + + _clar.last_report = report; + clar_run_test(&test[i], &suite->initialize, &suite->cleanup); if (_clar.exit_on_error && _clar.total_errors) @@ -298,13 +355,14 @@ clar_usage(const char *arg) { printf("Usage: %s [options]\n\n", arg); printf("Options:\n"); - printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n"); - printf(" -iname\tInclude the suite with `name`\n"); - printf(" -xname\tExclude the suite with `name`\n"); - printf(" -v \tIncrease verbosity (show suite names)\n"); - printf(" -q \tOnly report tests that had an error\n"); - printf(" -Q \tQuit as soon as a test fails\n"); - printf(" -l \tPrint suite names\n"); + printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); + printf(" -iname Include the suite with `name`\n"); + printf(" -xname Exclude the suite with `name`\n"); + printf(" -v Increase verbosity (show suite names)\n"); + printf(" -q Only report tests that had an error\n"); + printf(" -Q Quit as soon as a test fails\n"); + printf(" -l Print suite names\n"); + printf(" -r[filename] Write summary file (to the optional filename)\n"); exit(-1); } @@ -318,7 +376,7 @@ clar_parse_args(int argc, char **argv) char *argument = argv[i]; if (argument[0] != '-' || argument[1] == '\0' - || strchr("sixvqQl", argument[1]) == NULL) { + || strchr("sixvqQlr", argument[1]) == NULL) { clar_usage(argv[0]); } } @@ -359,7 +417,24 @@ clar_parse_args(int argc, char **argv) _clar.report_suite_names = 1; switch (action) { - case 's': _clar_suites[j].enabled = 1; clar_run_suite(&_clar_suites[j], argument); break; + case 's': { + struct clar_explicit *explicit = + calloc(1, sizeof(struct clar_explicit)); + assert(explicit); + + explicit->suite_idx = j; + explicit->filter = argument; + + if (_clar.explicit == NULL) + _clar.explicit = explicit; + + if (_clar.last_explicit != NULL) + _clar.last_explicit->next = explicit; + + _clar_suites[j].enabled = 1; + _clar.last_explicit = explicit; + break; + } case 'i': _clar_suites[j].enabled = 1; break; case 'x': _clar_suites[j].enabled = 0; break; } @@ -397,6 +472,12 @@ clar_parse_args(int argc, char **argv) _clar.report_suite_names = 1; break; + case 'r': + _clar.write_summary = 1; + _clar.summary_filename = *(argument + 2) ? (argument + 2) : + "summary.xml"; + break; + default: assert(!"Unexpected commandline argument!"); } @@ -412,23 +493,31 @@ clar_test_init(int argc, char **argv) "" ); + if (argc > 1) + clar_parse_args(argc, argv); + + if (_clar.write_summary && + !(_clar.summary = clar_summary_init(_clar.summary_filename))) { + clar_print_onabort("Failed to open the summary file\n"); + exit(-1); + } + if (clar_sandbox() < 0) { clar_print_onabort("Failed to sandbox the test runner.\n"); exit(-1); } - - _clar.argc = argc; - _clar.argv = argv; } int clar_test_run(void) { - if (_clar.argc > 1) - clar_parse_args(_clar.argc, _clar.argv); + size_t i; + struct clar_explicit *explicit; - if (!_clar.suites_ran) { - size_t i; + if (_clar.explicit) { + for (explicit = _clar.explicit; explicit; explicit = explicit->next) + clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); + } else { for (i = 0; i < _clar_suite_count; ++i) clar_run_suite(&_clar_suites[i], NULL); } @@ -439,6 +528,9 @@ clar_test_run(void) void clar_test_shutdown(void) { + struct clar_explicit *explicit, *explicit_next; + struct clar_report *report, *report_next; + clar_print_shutdown( _clar.tests_ran, (int)_clar_suite_count, @@ -446,6 +538,21 @@ clar_test_shutdown(void) ); clar_unsandbox(); + + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { + clar_print_onabort("Failed to write the summary file\n"); + exit(-1); + } + + for (explicit = _clar.explicit; explicit; explicit = explicit_next) { + explicit_next = explicit->next; + free(explicit); + } + + for (report = _clar.reports; report; report = report_next) { + report_next = report->next; + free(report); + } } int @@ -465,7 +572,7 @@ static void abort_test(void) if (!_clar.trampoline_enabled) { clar_print_onabort( "Fatal error: a cleanup method raised an exception."); - clar_report_errors(); + clar_report_errors(_clar.last_report); exit(-1); } @@ -475,7 +582,7 @@ static void abort_test(void) void clar__skip(void) { - _clar.test_status = CL_TEST_SKIP; + _clar.last_report->status = CL_TEST_SKIP; _clar.total_skipped++; abort_test(); } @@ -489,17 +596,14 @@ void clar__fail( { struct clar_error *error = calloc(1, sizeof(struct clar_error)); - if (_clar.errors == NULL) - _clar.errors = error; + if (_clar.last_report->errors == NULL) + _clar.last_report->errors = error; - if (_clar.last_error != NULL) - _clar.last_error->next = error; + if (_clar.last_report->last_error != NULL) + _clar.last_report->last_error->next = error; - _clar.last_error = error; + _clar.last_report->last_error = error; - error->test = _clar.active_test; - error->test_number = _clar.tests_ran; - error->suite = _clar.active_suite; error->file = file; error->line_number = line; error->error_msg = error_msg; @@ -508,7 +612,7 @@ void clar__fail( error->description = strdup(description); _clar.total_errors++; - _clar.test_status = CL_TEST_FAILURE; + _clar.last_report->status = CL_TEST_FAILURE; if (should_abort) abort_test(); @@ -653,3 +757,4 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque) #include "clar/fixtures.h" #include "clar/fs.h" #include "clar/print.h" +#include "clar/summary.h" diff --git a/tests/clar.h b/tests/clar.h index 5c674d70f..bdaab09d7 100644 --- a/tests/clar.h +++ b/tests/clar.h @@ -12,13 +12,16 @@ enum cl_test_status { CL_TEST_OK, CL_TEST_FAILURE, - CL_TEST_SKIP + CL_TEST_SKIP, + CL_TEST_NOTRUN, }; +/** Setup clar environment */ void clar_test_init(int argc, char *argv[]); int clar_test_run(void); void clar_test_shutdown(void); +/** One shot setup & run */ int clar_test(int argc, char *argv[]); const char *clar_sandbox_path(void); diff --git a/tests/clar/print.h b/tests/clar/print.h index 916d807c1..73c4a8953 100644 --- a/tests/clar/print.h +++ b/tests/clar/print.h @@ -13,16 +13,16 @@ static void clar_print_shutdown(int test_count, int suite_count, int error_count (void)error_count; printf("\n\n"); - clar_report_errors(); + clar_report_all(); } -static void clar_print_error(int num, const struct clar_error *error) +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) { printf(" %d) Failure:\n", num); printf("%s::%s [%s:%d]\n", - error->suite, - error->test, + report->suite, + report->test, error->file, error->line_number); @@ -44,6 +44,7 @@ static void clar_print_ontest(const char *test_name, int test_number, enum cl_te case CL_TEST_OK: printf("."); break; case CL_TEST_FAILURE: printf("F"); break; case CL_TEST_SKIP: printf("S"); break; + case CL_TEST_NOTRUN: printf("N"); break; } fflush(stdout); diff --git a/tests/clar/summary.h b/tests/clar/summary.h new file mode 100644 index 000000000..1af110efa --- /dev/null +++ b/tests/clar/summary.h @@ -0,0 +1,134 @@ + +#include +#include + +int clar_summary_close_tag( + struct clar_summary *summary, const char *tag, int indent) +{ + const char *indt; + + if (indent == 0) indt = ""; + else if (indent == 1) indt = "\t"; + else indt = "\t\t"; + + return fprintf(summary->fp, "%s\n", indt, tag); +} + +int clar_summary_testsuites(struct clar_summary *summary) +{ + return fprintf(summary->fp, "\n"); +} + +int clar_summary_testsuite(struct clar_summary *summary, + int idn, const char *name, const char *pkg, time_t timestamp, + double elapsed, int test_count, int fail_count, int error_count) +{ + struct tm *tm = localtime(×tamp); + char iso_dt[20]; + + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) + return -1; + + return fprintf(summary->fp, "\t\n", + idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); +} + +int clar_summary_testcase(struct clar_summary *summary, + const char *name, const char *classname, double elapsed) +{ + return fprintf(summary->fp, + "\t\t\n", + name, classname, elapsed); +} + +int clar_summary_failure(struct clar_summary *summary, + const char *type, const char *message, const char *desc) +{ + return fprintf(summary->fp, + "\t\t\t\n", + type, message, desc); +} + +struct clar_summary *clar_summary_init(const char *filename) +{ + struct clar_summary *summary; + FILE *fp; + + if ((fp = fopen(filename, "w")) == NULL) + return NULL; + + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { + fclose(fp); + return NULL; + } + + summary->filename = filename; + summary->fp = fp; + + return summary; +} + +int clar_summary_shutdown(struct clar_summary *summary) +{ + struct clar_report *report; + const char *last_suite = NULL; + + if (clar_summary_testsuites(summary) < 0) + goto on_error; + + report = _clar.reports; + while (report != NULL) { + struct clar_error *error = report->errors; + + if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_testsuite(summary, 0, report->suite, "", + time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) + goto on_error; + } + + last_suite = report->suite; + + clar_summary_testcase(summary, report->test, "what", 0); + + while (error != NULL) { + if (clar_summary_failure(summary, "assert", + error->error_msg, error->description) < 0) + goto on_error; + + error = error->next; + } + + if (clar_summary_close_tag(summary, "testcase", 2) < 0) + goto on_error; + + report = report->next; + + if (!report || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_close_tag(summary, "testsuite", 1) < 0) + goto on_error; + } + } + + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || + fclose(summary->fp) != 0) + goto on_error; + + printf("written summary file to %s\n", summary->filename); + + free(summary); + return 0; + +on_error: + fclose(summary->fp); + free(summary); + return -1; +} diff --git a/tests/config/include.c b/tests/config/include.c index e440b9a78..87ef406e7 100644 --- a/tests/config/include.c +++ b/tests/config/include.c @@ -35,6 +35,8 @@ void test_config_include__absolute(void) cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); + + cl_git_pass(p_unlink("config-include-absolute")); } void test_config_include__homedir(void) @@ -48,6 +50,8 @@ void test_config_include__homedir(void) cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); cl_sandbox_set_search_path_defaults(); + + cl_git_pass(p_unlink("config-include-homedir")); } /* We need to pretend that the variables were defined where the file was included */ @@ -66,6 +70,9 @@ void test_config_include__ordering(void) git_buf_clear(&buf); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); + + cl_git_pass(p_unlink("included")); + cl_git_pass(p_unlink("including")); } /* We need to pretend that the variables were defined where the file was included */ @@ -76,8 +83,18 @@ void test_config_include__depth(void) cl_git_fail(git_config_open_ondisk(&cfg, "a")); - p_unlink("a"); - p_unlink("b"); + cl_git_pass(p_unlink("a")); + cl_git_pass(p_unlink("b")); +} + +void test_config_include__empty_path_sanely_handled(void) +{ + cl_git_mkfile("a", "[include]\npath"); + cl_git_pass(git_config_open_ondisk(&cfg, "a")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "include.path")); + cl_assert_equal_s("", git_buf_cstr(&buf)); + + cl_git_pass(p_unlink("a")); } void test_config_include__missing(void) @@ -89,6 +106,8 @@ void test_config_include__missing(void) cl_assert(giterr_last() == NULL); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", git_buf_cstr(&buf)); + + cl_git_pass(p_unlink("including")); } void test_config_include__missing_homedir(void) @@ -103,6 +122,7 @@ void test_config_include__missing_homedir(void) cl_assert_equal_s("baz", git_buf_cstr(&buf)); cl_sandbox_set_search_path_defaults(); + cl_git_pass(p_unlink("including")); } #define replicate10(s) s s s s s s s s s s @@ -122,6 +142,10 @@ void test_config_include__depth2(void) git_buf_clear(&buf); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar2")); cl_assert_equal_s("baz2", git_buf_cstr(&buf)); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("middle")); + cl_git_pass(p_unlink("bottom")); } void test_config_include__removing_include_removes_values(void) @@ -132,6 +156,9 @@ void test_config_include__removing_include_removes_values(void) cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); cl_git_mkfile("top-level", ""); cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); } void test_config_include__rewriting_include_refreshes_values(void) @@ -145,6 +172,10 @@ void test_config_include__rewriting_include_refreshes_values(void) cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_git_pass(git_config_get_string_buf(&buf, cfg, "first.other")); cl_assert_equal_s(buf.ptr, "value"); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("first")); + cl_git_pass(p_unlink("second")); } void test_config_include__included_variables_cannot_be_deleted(void) @@ -154,13 +185,20 @@ void test_config_include__included_variables_cannot_be_deleted(void) cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); cl_git_fail(git_config_delete_entry(cfg, "foo.bar")); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); } void test_config_include__included_variables_cannot_be_modified(void) { cl_git_mkfile("top-level", "[include]\npath = included\n"); + cl_git_mkfile("included", "[foo]\nbar = value"); cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); cl_git_fail(git_config_set_string(cfg, "foo.bar", "other-value")); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); } diff --git a/tests/config/read.c b/tests/config/read.c index a34455a0c..646567134 100644 --- a/tests/config/read.c +++ b/tests/config/read.c @@ -748,3 +748,36 @@ void test_config_read__bom(void) git_config_free(cfg); git_buf_free(&buf); } + +static int read_nosection_cb(const git_config_entry *entry, void *payload) { + int *seen = (int*)payload; + if (strcmp(entry->name, "key") == 0) { + (*seen)++; + } + return 0; +} + +/* This would ideally issue a warning, if we had a way to do so. */ +void test_config_read__nosection(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + int seen = 0; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-nosection"))); + + /* + * Given a key with no section, we do not allow reading it, + * but we do include it in an iteration over the config + * store. This appears to match how git's own APIs (and + * git-config(1)) behave. + */ + + cl_git_fail_with(git_config_get_string_buf(&buf, cfg, "key"), GIT_EINVALIDSPEC); + + cl_git_pass(git_config_foreach(cfg, read_nosection_cb, &seen)); + cl_assert_equal_i(seen, 1); + + git_buf_free(&buf); + git_config_free(cfg); +} diff --git a/tests/core/memmem.c b/tests/core/memmem.c new file mode 100644 index 000000000..fd9986d01 --- /dev/null +++ b/tests/core/memmem.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" + +static void assert_found(const char *haystack, const char *needle, size_t expected_pos) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + haystack + expected_pos); +} + +static void assert_absent(const char *haystack, const char *needle) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + NULL); +} + +void test_core_memmem__found(void) +{ + assert_found("a", "a", 0); + assert_found("ab", "a", 0); + assert_found("ba", "a", 1); + assert_found("aa", "a", 0); + assert_found("aab", "aa", 0); + assert_found("baa", "aa", 1); + assert_found("dabc", "abc", 1); + assert_found("abababc", "abc", 4); +} + +void test_core_memmem__absent(void) +{ + assert_absent("a", "b"); + assert_absent("a", "aa"); + assert_absent("ba", "ab"); + assert_absent("ba", "ab"); + assert_absent("abc", "abcd"); + assert_absent("abcabcabc", "bcac"); +} + +void test_core_memmem__edgecases(void) +{ + assert_absent(NULL, NULL); + assert_absent("a", NULL); + assert_absent(NULL, "a"); + assert_absent("", "a"); + assert_absent("a", ""); +} diff --git a/tests/core/strtol.c b/tests/core/strtol.c index 0d3b6a5e6..ba79fba51 100644 --- a/tests/core/strtol.c +++ b/tests/core/strtol.c @@ -1,45 +1,84 @@ #include "clar_libgit2.h" +static void assert_l32_parses(const char *string, int32_t expected, int base) +{ + int32_t i; + cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l32_fails(const char *string, int base) +{ + int32_t i; + cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base)); +} + +static void assert_l64_parses(const char *string, int64_t expected, int base) +{ + int64_t i; + cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l64_fails(const char *string, int base) +{ + int64_t i; + cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base)); +} + void test_core_strtol__int32(void) { - int32_t i; + assert_l32_parses("123", 123, 10); + assert_l32_parses(" +123 ", 123, 10); + assert_l32_parses(" +2147483647 ", 2147483647, 10); + assert_l32_parses(" -2147483648 ", -2147483648LL, 10); + assert_l32_parses("A", 10, 16); + assert_l32_parses("1x1", 1, 10); - cl_git_pass(git__strtol32(&i, "123", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol32(&i, " +123 ", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol32(&i, " +2147483647 ", NULL, 10)); - cl_assert(i == 2147483647); - cl_git_pass(git__strtol32(&i, " -2147483648 ", NULL, 10)); - cl_assert(i == -2147483648LL); - - cl_git_fail(git__strtol32(&i, " 2147483657 ", NULL, 10)); - cl_git_fail(git__strtol32(&i, " -2147483657 ", NULL, 10)); + assert_l32_fails("", 10); + assert_l32_fails("a", 10); + assert_l32_fails("x10x", 10); + assert_l32_fails(" 2147483657 ", 10); + assert_l32_fails(" -2147483657 ", 10); } void test_core_strtol__int64(void) { - int64_t i; + assert_l64_parses("123", 123, 10); + assert_l64_parses(" +123 ", 123, 10); + assert_l64_parses(" +2147483647 ", 2147483647, 10); + assert_l64_parses(" -2147483648 ", -2147483648LL, 10); + assert_l64_parses(" 2147483657 ", 2147483657LL, 10); + assert_l64_parses(" -2147483657 ", -2147483657LL, 10); + assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10); + assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10); + assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16); + assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16); + assert_l64_parses("1a", 26, 16); + assert_l64_parses("1A", 26, 16); - cl_git_pass(git__strtol64(&i, "123", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol64(&i, " +123 ", NULL, 10)); - cl_assert(i == 123); - cl_git_pass(git__strtol64(&i, " +2147483647 ", NULL, 10)); - cl_assert(i == 2147483647); - cl_git_pass(git__strtol64(&i, " -2147483648 ", NULL, 10)); - cl_assert(i == -2147483648LL); - cl_git_pass(git__strtol64(&i, " 2147483657 ", NULL, 10)); - cl_assert(i == 2147483657LL); - cl_git_pass(git__strtol64(&i, " -2147483657 ", NULL, 10)); - cl_assert(i == -2147483657LL); - cl_git_pass(git__strtol64(&i, " 9223372036854775807 ", NULL, 10)); - cl_assert(i == INT64_MAX); - cl_git_pass(git__strtol64(&i, " -9223372036854775808 ", NULL, 10)); - cl_assert(i == INT64_MIN); - cl_git_pass(git__strtol64(&i, " 0x7fffffffffffffff ", NULL, 16)); - cl_assert(i == INT64_MAX); - cl_git_pass(git__strtol64(&i, " -0x8000000000000000 ", NULL, 16)); - cl_assert(i == INT64_MIN); + assert_l64_fails("", 10); + assert_l64_fails("a", 10); + assert_l64_fails("x10x", 10); + assert_l64_fails("0x8000000000000000", 16); + assert_l64_fails("-0x8000000000000001", 16); } +void test_core_strtol__buffer_length_truncates(void) +{ + int32_t i32; + int64_t i64; + + cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10)); + cl_assert_equal_i(i32, 1); + + cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10)); + cl_assert_equal_i(i64, 1); +} + +void test_core_strtol__error_message_cuts_off(void) +{ + assert_l32_fails("2147483657foobar", 10); + cl_assert(strstr(giterr_last()->message, "2147483657") != NULL); + cl_assert(strstr(giterr_last()->message, "foobar") == NULL); +} diff --git a/tests/core/vector.c b/tests/core/vector.c index c2e5d3f34..91452404c 100644 --- a/tests/core/vector.c +++ b/tests/core/vector.c @@ -407,3 +407,22 @@ void test_core_vector__reverse(void) git_vector_free(&v); } + +void test_core_vector__dup_empty_vector(void) +{ + git_vector v = GIT_VECTOR_INIT; + git_vector dup = GIT_VECTOR_INIT; + int dummy; + + cl_assert_equal_i(0, v.length); + + cl_git_pass(git_vector_dup(&dup, &v, v._cmp)); + cl_assert_equal_i(0, dup._alloc_size); + cl_assert_equal_i(0, dup.length); + + cl_git_pass(git_vector_insert(&dup, &dummy)); + cl_assert_equal_i(8, dup._alloc_size); + cl_assert_equal_i(1, dup.length); + + git_vector_free(&dup); +} diff --git a/tests/diff/parse.c b/tests/diff/parse.c index dc2ceefec..74816f570 100644 --- a/tests/diff/parse.c +++ b/tests/diff/parse.c @@ -288,3 +288,74 @@ void test_diff_parse__patch_roundtrip_succeeds(void) git_buf_free(&patchbuf); git_buf_free(&diffbuf); } + +#define cl_assert_equal_i_src(i1,i2,file,line) clar__assert_equal(file,line,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) + +static void cl_git_assert_lineinfo_(int old_lineno, int new_lineno, int num_lines, git_patch *patch, size_t hunk_idx, size_t line_idx, const char *file, int lineno) +{ + const git_diff_line *line; + + cl_git_expect(git_patch_get_line_in_hunk(&line, patch, hunk_idx, line_idx), 0, file, lineno); + cl_assert_equal_i_src(old_lineno, line->old_lineno, file, lineno); + cl_assert_equal_i_src(new_lineno, line->new_lineno, file, lineno); + cl_assert_equal_i_src(num_lines, line->num_lines, file, lineno); +} + +#define cl_git_assert_lineinfo(old, new, num, p, h, l) \ + cl_git_assert_lineinfo_(old,new,num,p,h,l,__FILE__,__LINE__) + + +void test_diff_parse__issue4672(void) +{ + const char *text = "diff --git a/a b/a\n" + "index 7f129fd..af431f2 100644\n" + "--- a/a\n" + "+++ b/a\n" + "@@ -3 +3 @@\n" + "-a contents 2\n" + "+a contents\n"; + + git_diff *diff; + git_patch *patch; + const git_diff_hunk *hunk; + size_t n, l = 0; + + cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0)); + + cl_git_assert_lineinfo(3, -1, 1, patch, 0, l++); + cl_git_assert_lineinfo(-1, 3, 1, patch, 0, l++); + + cl_assert_equal_i(n, l); + + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_parse__lineinfo(void) +{ + const char *text = PATCH_ORIGINAL_TO_CHANGE_MIDDLE; + git_diff *diff; + git_patch *patch; + const git_diff_hunk *hunk; + size_t n, l = 0; + + cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0)); + + cl_git_assert_lineinfo(3, 3, 1, patch, 0, l++); + cl_git_assert_lineinfo(4, 4, 1, patch, 0, l++); + cl_git_assert_lineinfo(5, 5, 1, patch, 0, l++); + cl_git_assert_lineinfo(6, -1, 1, patch, 0, l++); + cl_git_assert_lineinfo(-1, 6, 1, patch, 0, l++); + cl_git_assert_lineinfo(7, 7, 1, patch, 0, l++); + cl_git_assert_lineinfo(8, 8, 1, patch, 0, l++); + cl_git_assert_lineinfo(9, 9, 1, patch, 0, l++); + + cl_assert_equal_i(n, l); + + git_patch_free(patch); + git_diff_free(diff); +} diff --git a/tests/object/tree/update.c b/tests/object/tree/update.c index b76e8612a..41b50f3e9 100644 --- a/tests/object/tree/update.c +++ b/tests/object/tree/update.c @@ -284,3 +284,19 @@ void test_object_tree_update__add_conflict2(void) cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); } + +void test_object_tree_update__remove_invalid_submodule(void) +{ + git_tree *baseline; + git_oid updated_tree_id, baseline_id; + git_tree_update updates[] = { + {GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB, "submodule"}, + }; + + /* This tree contains a submodule with an all-zero commit for a submodule named 'submodule' */ + cl_git_pass(git_oid_fromstr(&baseline_id, "396c7f1adb7925f51ba13a75f48252f44c5a14a2")); + cl_git_pass(git_tree_lookup(&baseline, g_repo, &baseline_id)); + cl_git_pass(git_tree_create_updated(&updated_tree_id, g_repo, baseline, 1, updates)); + + git_tree_free(baseline); +} diff --git a/tests/online/clone.c b/tests/online/clone.c index 27b7b9661..3fc6ddd03 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -263,6 +263,9 @@ static int cred_failure_cb( void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) { + git__free(_remote_url); + git__free(_remote_user); + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); _remote_user = git__strdup("libgit2test"); @@ -293,6 +296,9 @@ void test_online_clone__cred_callback_called_again_on_auth_failure(void) { size_t counter = 0; + git__free(_remote_url); + git__free(_remote_user); + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); _remote_user = git__strdup("libgit2test"); diff --git a/tests/online/push.c b/tests/online/push.c index f72b4f8cb..3b98278e0 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -152,8 +152,12 @@ static void do_verify_push_status(record_callbacks_data *data, const push_status git_buf_free(&msg); } - git_vector_foreach(actual, i, iter) - git__free(iter); + git_vector_foreach(actual, i, iter) { + push_status *s = (push_status *)iter; + git__free(s->ref); + git__free(s->msg); + git__free(s); + } git_vector_free(actual); } @@ -393,7 +397,7 @@ void test_online_push__initialize(void) } git_remote_disconnect(_remote); - git_vector_free(&delete_specs); + git_vector_free_deep(&delete_specs); /* Now that we've deleted everything, fetch from the remote */ memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); diff --git a/tests/resources/config/config-nosection b/tests/resources/config/config-nosection new file mode 100644 index 0000000000000000000000000000000000000000..dd2ee0857010cd49ba35f17cb83e42bcc34c8947 GIT binary patch literal 12 Tcmc~!tyHj8C`-&KP2~ar8kGa? literal 0 HcmV?d00001 diff --git a/tests/resources/testrepo2/.gitted/objects/39/6c7f1adb7925f51ba13a75f48252f44c5a14a2 b/tests/resources/testrepo2/.gitted/objects/39/6c7f1adb7925f51ba13a75f48252f44c5a14a2 new file mode 100644 index 0000000000000000000000000000000000000000..667704b32101c48fe3606383c5135f87b3f7704f GIT binary patch literal 71 zcmV-N0J#5n0V^p=O;s>AVK6i>Ff%bx2y%6F@paY9O<{O8_tET47q2;ccWbUIkGgT_ dNl)-ZLo)*)P$(`<%FRzH%}Hgz1^|d*7gxXN8type, GIT_PKT_FLUSH); + cl_assert_equal_strn(endptr, line + 4, linelen - 4); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_data_pkt_parses(const char *line, const char *expected_data, size_t expected_len) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_data *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_DATA); + cl_assert_equal_i(pkt->len, expected_len); + cl_assert_equal_strn(pkt->data, expected_data, expected_len); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_sideband_progress_parses(const char *line, const char *expected_data, size_t expected_len) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_progress *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_PROGRESS); + cl_assert_equal_i(pkt->len, expected_len); + cl_assert_equal_strn(pkt->data, expected_data, expected_len); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_error_parses(const char *line, const char *expected_error, size_t expected_len) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_err *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_ERR); + cl_assert_equal_i(pkt->len, expected_len); + cl_assert_equal_strn(pkt->error, expected_error, expected_len); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_ack_parses(const char *line, const char *expected_oid, enum git_ack_status expected_status) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_ack *pkt; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, expected_oid)); + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_ACK); + cl_assert_equal_oid(&pkt->oid, &oid); + cl_assert_equal_i(pkt->status, expected_status); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_nak_parses(const char *line) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_NAK); + cl_assert_equal_strn(endptr, line + 7, linelen - 7); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_comment_parses(const char *line, const char *expected_comment) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_comment *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_COMMENT); + cl_assert_equal_strn(pkt->comment, expected_comment, strlen(expected_comment)); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_ok_parses(const char *line, const char *expected_ref) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_ok *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_OK); + cl_assert_equal_strn(pkt->ref, expected_ref, strlen(expected_ref)); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_unpack_parses(const char *line, bool ok) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_unpack *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_UNPACK); + cl_assert_equal_i(pkt->unpack_ok, ok); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_ng_parses(const char *line, const char *expected_ref, const char *expected_msg) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_ng *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_NG); + cl_assert_equal_strn(pkt->ref, expected_ref, strlen(expected_ref)); + cl_assert_equal_strn(pkt->msg, expected_msg, strlen(expected_msg)); + + git_pkt_free((git_pkt *) pkt); +} + +#define assert_ref_parses(line, expected_oid, expected_ref, expected_capabilities) \ + assert_ref_parses_(line, sizeof(line), expected_oid, expected_ref, expected_capabilities) + +static void assert_ref_parses_(const char *line, size_t linelen, const char *expected_oid, + const char *expected_ref, const char *expected_capabilities) +{ + const char *endptr; + git_pkt_ref *pkt; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, expected_oid)); + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_REF); + cl_assert_equal_oid(&pkt->head.oid, &oid); + cl_assert_equal_strn(pkt->head.name, expected_ref, strlen(expected_ref)); + if (expected_capabilities) + cl_assert_equal_strn(pkt->capabilities, expected_capabilities, strlen(expected_capabilities)); + else + cl_assert_equal_p(NULL, pkt->capabilities); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_pkt_fails(const char *line) +{ + const char *endptr; + git_pkt *pkt; + cl_git_fail(git_pkt_parse_line(&pkt, &endptr, line, strlen(line) + 1)); +} + +void test_transports_smart_packet__parsing_garbage_fails(void) +{ + assert_pkt_fails("0foobar"); + assert_pkt_fails("00foobar"); + assert_pkt_fails("000foobar"); + assert_pkt_fails("0001"); + assert_pkt_fails(""); + assert_pkt_fails("0"); + assert_pkt_fails("0i00"); + assert_pkt_fails("f"); +} + +void test_transports_smart_packet__flush_parses(void) +{ + assert_flush_parses("0000"); + assert_flush_parses("0000foobar"); +} + +void test_transports_smart_packet__data_pkt(void) +{ + assert_pkt_fails("000foobar"); + assert_pkt_fails("0001o"); + assert_pkt_fails("0001\1"); + assert_data_pkt_parses("0005\1", "", 0); + assert_pkt_fails("0009\1o"); + assert_data_pkt_parses("0009\1data", "data", 4); + assert_data_pkt_parses("000a\1data", "data", 5); +} + +void test_transports_smart_packet__sideband_progress_pkt(void) +{ + assert_pkt_fails("0001\2"); + assert_sideband_progress_parses("0005\2", "", 0); + assert_pkt_fails("0009\2o"); + assert_sideband_progress_parses("0009\2data", "data", 4); + assert_sideband_progress_parses("000a\2data", "data", 5); +} + +void test_transports_smart_packet__sideband_err_pkt(void) +{ + assert_pkt_fails("0001\3"); + assert_error_parses("0005\3", "", 0); + assert_pkt_fails("0009\3o"); + assert_error_parses("0009\3data", "data", 4); + assert_error_parses("000a\3data", "data", 5); +} + +void test_transports_smart_packet__ack_pkt(void) +{ + assert_ack_parses("0030ACK 0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000", 0); + assert_ack_parses("0039ACK 0000000000000000000000000000000000000000 continue", + "0000000000000000000000000000000000000000", + GIT_ACK_CONTINUE); + assert_ack_parses("0037ACK 0000000000000000000000000000000000000000 common", + "0000000000000000000000000000000000000000", + GIT_ACK_COMMON); + assert_ack_parses("0037ACK 0000000000000000000000000000000000000000 ready", + "0000000000000000000000000000000000000000", + GIT_ACK_READY); + + /* these should fail as they don't have OIDs */ + assert_pkt_fails("0007ACK"); + assert_pkt_fails("0008ACK "); + + /* this one is missing a space and should thus fail */ + assert_pkt_fails("0036ACK00000000000000000x0000000000000000000000 ready"); + + /* the following ones have invalid OIDs and should thus fail */ + assert_pkt_fails("0037ACK 00000000000000000x0000000000000000000000 ready"); + assert_pkt_fails("0036ACK 000000000000000000000000000000000000000 ready"); + assert_pkt_fails("0036ACK 00000000000000000x0000000000000000000000ready"); + + /* this one has an invalid status and should thus fail */ + assert_pkt_fails("0036ACK 0000000000000000000000000000000000000000 read"); +} + +void test_transports_smart_packet__nak_pkt(void) +{ + assert_nak_parses("0007NAK"); + assert_pkt_fails("0007NaK"); + assert_pkt_fails("0007nak"); + assert_nak_parses("0007NAKfoobar"); + assert_pkt_fails("0007nakfoobar"); + assert_pkt_fails("0007 NAK"); +} + +void test_transports_smart_packet__error_pkt(void) +{ + assert_pkt_fails("0007ERR"); + assert_pkt_fails("0008ERRx"); + assert_error_parses("0008ERR ", "", 0); + assert_error_parses("000EERR ERRMSG", "ERRMSG", 6); +} + +void test_transports_smart_packet__comment_pkt(void) +{ + assert_comment_parses("0005#", ""); + assert_comment_parses("000B#foobar", "#fooba"); + assert_comment_parses("000C#foobar", "#foobar"); + assert_comment_parses("001A#this is a comment\nfoo", "#this is a comment\nfoo"); +} + +void test_transports_smart_packet__ok_pkt(void) +{ + assert_pkt_fails("0007ok\n"); + assert_ok_parses("0007ok ", ""); + assert_ok_parses("0008ok \n", ""); + assert_ok_parses("0008ok x", "x"); + assert_ok_parses("0009ok x\n", "x"); + assert_pkt_fails("001OK ref/foo/bar"); + assert_ok_parses("0012ok ref/foo/bar", "ref/foo/bar"); + assert_pkt_fails("0013OK ref/foo/bar\n"); + assert_ok_parses("0013ok ref/foo/bar\n", "ref/foo/bar"); +} + +void test_transports_smart_packet__ng_pkt(void) +{ + /* TODO: same as for ok pkt */ + assert_pkt_fails("0007ng\n"); + assert_pkt_fails("0008ng \n"); + assert_pkt_fails("000Bng ref\n"); + assert_pkt_fails("000Bng ref\n"); + /* TODO: is this a valid packet line? Probably not. */ + assert_ng_parses("000Ang x\n", "", "x"); + assert_ng_parses("000Fng ref msg\n", "ref", "msg"); + assert_ng_parses("000Fng ref msg\n", "ref", "msg"); +} + +void test_transports_smart_packet__unpack_pkt(void) +{ + assert_unpack_parses("000Dunpack ok", 1); + assert_unpack_parses("000Dunpack ng error-msg", 0); + /* TODO: the following tests should fail */ + assert_unpack_parses("000Aunpack", 0); + assert_unpack_parses("0011unpack foobar", 0); + assert_unpack_parses("0010unpack ng ok", 0); + assert_unpack_parses("0010unpack okfoo", 1); +} + +void test_transports_smart_packet__ref_pkt(void) +{ + assert_pkt_fails("002C0000000000000000000000000000000000000000"); + assert_pkt_fails("002D0000000000000000000000000000000000000000\n"); + assert_pkt_fails("00300000000000000000000000000000000000000000HEAD"); + assert_pkt_fails("004800000000x0000000000000000000000000000000 refs/heads/master\0multi_ack"); + assert_ref_parses( + "003F0000000000000000000000000000000000000000 refs/heads/master\0", + "0000000000000000000000000000000000000000", "refs/heads/master", ""); + assert_ref_parses( + "00480000000000000000000000000000000000000000 refs/heads/master\0multi_ack", + "0000000000000000000000000000000000000000", "refs/heads/master", "multi_ack"); + assert_ref_parses( + "00460000000000000000000000000000000000000000 refs/heads/master\0one two", + "0000000000000000000000000000000000000000", "refs/heads/master", "one two"); + assert_ref_parses( + "00310000000000000000000000000000000000000000 HEAD", + "0000000000000000000000000000000000000000", "HEAD", NULL); + assert_pkt_fails("0031000000000000000000000000000000000000000 HEAD"); + assert_ref_parses( + "00360000000000000000000000000000000000000000 HEAD HEAD", + "0000000000000000000000000000000000000000", "HEAD HEAD", NULL); +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 4ac3b8bba..9026a7f83 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -228,6 +228,26 @@ void test_worktree_worktree__init(void) git_repository_free(repo); } +void test_worktree_worktree__add_from_bare(void) +{ + git_worktree *wt; + git_repository *repo, *wtrepo; + + repo = cl_git_sandbox_init("short_tag.git"); + + cl_assert_equal_i(1, git_repository_is_bare(repo)); + cl_assert_equal_i(0, git_repository_is_worktree(repo)); + + cl_git_pass(git_worktree_add(&wt, repo, "worktree-frombare", "worktree-frombare", NULL)); + cl_git_pass(git_repository_open(&wtrepo, "worktree-frombare")); + cl_assert_equal_i(0, git_repository_is_bare(wtrepo)); + cl_assert_equal_i(1, git_repository_is_worktree(wtrepo)); + + git_worktree_free(wt); + git_repository_free(repo); + git_repository_free(wtrepo); +} + void test_worktree_worktree__add_locked(void) { git_worktree *wt; @@ -435,7 +455,7 @@ void test_worktree_worktree__unlock_unlocked_worktree(void) cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); cl_assert(!git_worktree_is_locked(NULL, wt)); - cl_assert(git_worktree_unlock(wt) == 0); + cl_assert_equal_i(1, git_worktree_unlock(wt)); cl_assert(!wt->locked); git_worktree_free(wt); @@ -448,7 +468,7 @@ void test_worktree_worktree__unlock_locked_worktree(void) cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); cl_git_pass(git_worktree_lock(wt, NULL)); cl_assert(git_worktree_is_locked(NULL, wt)); - cl_git_pass(git_worktree_unlock(wt)); + cl_assert_equal_i(0, git_worktree_unlock(wt)); cl_assert(!wt->locked); git_worktree_free(wt);