diff --git a/.gitattributes b/.gitattributes index 3d90b7d6..3788dc98 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ * text=auto +ci/**/*.sh text eol=lf +script/**/*.sh text eol=lf tests/resources/** linguist-vendored diff --git a/AUTHORS b/AUTHORS index 784bab3e..ee55e6ad 100644 --- a/AUTHORS +++ b/AUTHORS @@ -70,9 +70,11 @@ Shawn O. Pearce Shuhei Tanuma Steve Frécinaux Sven Strickroth +Talya "kivikakk" Connor Tim Branyen Tim Clem Tim Harder Torsten Bögershausen Trent Mick +Venus Xeon-Blonde Vicent Marti diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d27144..000870c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.5.1) -project(libgit2 VERSION "1.7.2" LANGUAGES C) +project(libgit2 VERSION "1.9.2" LANGUAGES C) # Add find modules to the path set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") @@ -30,14 +30,14 @@ option(USE_THREADS "Use threads for parallel processing when possibl option(USE_NSEC "Support nanosecond precision file mtimes and ctimes" ON) # Backend selection -option(USE_SSH "Link with libssh2 to enable SSH support" OFF) -option(USE_HTTPS "Enable HTTPS support. Can be set to a specific backend" ON) -option(USE_SHA1 "Enable SHA1. Can be set to CollisionDetection(ON)/HTTPS" ON) -option(USE_SHA256 "Enable SHA256. Can be set to HTTPS/Builtin" ON) -option(USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF) - set(USE_HTTP_PARSER "" CACHE STRING "Specifies the HTTP Parser implementation; either system or builtin.") + set(USE_SSH "" CACHE STRING "Enables SSH support and optionally selects provider. One of ON, OFF, or a specific provider: libssh2 or exec. (Defaults to OFF.)") + set(USE_HTTPS "" CACHE STRING "Enable HTTPS support and optionally selects the provider. One of ON, OFF, or a specific provider: OpenSSL, OpenSSL-FIPS, OpenSSL-Dynamic, mbedTLS, SecureTransport, Schannel, or WinHTTP. (Defaults to ON.)") + set(USE_SHA1 "" CACHE STRING "Selects SHA1 provider. One of CollisionDetection, HTTPS, or a specific provider. (Defaults to CollisionDetection.)") + set(USE_SHA256 "" CACHE STRING "Selects SHA256 provider. One of Builtin, HTTPS, or a specific provider. (Defaults to HTTPS.)") +option(USE_GSSAPI "Enable SPNEGO authentication using GSSAPI" OFF) + set(USE_HTTP_PARSER "" CACHE STRING "Selects HTTP Parser support: http-parser, llhttp, or builtin. (Defaults to builtin.)") # set(USE_XDIFF "" CACHE STRING "Specifies the xdiff implementation; either system or builtin.") - set(REGEX_BACKEND "" CACHE STRING "Regular expression implementation. One of regcomp_l, pcre2, pcre, regcomp, or builtin.") + set(REGEX_BACKEND "" CACHE STRING "Selects regex provider. One of regcomp_l, pcre2, pcre, regcomp, or builtin.") option(USE_BUNDLED_ZLIB "Use the bundled version of zlib. Can be set to one of Bundled(ON)/Chromium. The Chromium option requires a x86_64 processor with SSE4.2 and CLMUL" OFF) # Debugging options @@ -53,11 +53,18 @@ option(SONAME "Set the (SO)VERSION of the target" option(DEPRECATE_HARD "Do not include deprecated functions in the library" OFF) # Compilation options +# Default to c99 on Android Studio for compatibility; c90 everywhere else +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android") + set(CMAKE_C_STANDARD "99" CACHE STRING "The C standard to compile against") +else() + set(CMAKE_C_STANDARD "90" CACHE STRING "The C standard to compile against") +endif() +option(CMAKE_C_EXTENSIONS "Whether compiler extensions are supported" OFF) option(ENABLE_WERROR "Enable compilation with -Werror" OFF) if(UNIX) # NTLM client requires crypto libraries from the system HTTPS stack - if(NOT USE_HTTPS) + if(USE_HTTPS STREQUAL "OFF") option(USE_NTLMCLIENT "Enable NTLM support on Unix." OFF) else() option(USE_NTLMCLIENT "Enable NTLM support on Unix." ON) @@ -90,6 +97,7 @@ endif() # Modules +include(FeatureSummary) include(CheckLibraryExists) include(CheckFunctionExists) include(CheckSymbolExists) @@ -102,7 +110,6 @@ include(FindStatNsec) include(Findfutimens) include(GNUInstallDirs) include(IdeSplitSources) -include(FeatureSummary) include(EnableWarnings) include(DefaultCFlags) include(ExperimentalFeatures) @@ -143,3 +150,9 @@ endif() feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") + +# warn for not using sha1dc + +foreach(WARNING ${WARNINGS}) + message(WARNING ${WARNING}) +endforeach() diff --git a/COPYING b/COPYING index 25c8d8c6..701792e9 100644 --- a/COPYING +++ b/COPYING @@ -365,7 +365,7 @@ Public License instead of this License. The bundled ZLib code is licensed under the ZLib license: -Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + (C) 1995-2022 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1214,3 +1214,197 @@ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- + +The bundled ntlmclient code is licensed under the MIT license: + +Copyright (c) Edward Thomson. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +---------------------------------------------------------------------- + +Portions of this software derived from Team Explorer Everywhere: + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------------------------- + +Portions of this software derived from the LLVM Compiler Infrastructure: + +Copyright (c) 2003-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +--------------------------------------------------------------------------- + +Portions of this software derived from Unicode, Inc: + +Copyright 2001-2004 Unicode, Inc. + +Disclaimer + +This source code is provided as is by Unicode, Inc. No claims are +made as to fitness for any particular purpose. No warranties of any +kind are expressed or implied. The recipient agrees to determine +applicability of information provided. If this file has been +purchased on magnetic or optical media from Unicode, Inc., the +sole remedy for any claim will be exchange of defective media +within 90 days of receipt. + +Limitations on Rights to Redistribute This Code + +Unicode, Inc. hereby grants the right to freely use the information +supplied in this file in the creation of products supporting the +Unicode Standard, and to make copies of this file in any form +for internal or external distribution as long as this notice +remains attached. + +--------------------------------------------------------------------------- + +Portions of this software derived from sheredom/utf8.h: + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +--------------------------------------------------------------------------- + +Portions of this software derived from RFC 1320: + +Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +---------------------------------------------------------------------- + +The bundled llhttp dependency is licensed under the MIT license: + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 00000000..eb2f6310 --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x939121dD13f796C69d0Ac4185787285518081f8D" + } + } +} diff --git a/README.md b/README.md index 711e848e..7e0f1571 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,33 @@ libgit2 - the Git linkable library ================================== +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9609/badge)](https://www.bestpractices.dev/projects/9609) | Build Status | | | ------------ | - | -| **main** branch CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush) | -| **v1.7 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.7&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.7) | -| **v1.6 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.6&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.6) | -| **Nightly** builds | [![Nightly Build](https://github.com/libgit2/libgit2/workflows/Nightly%20Build/badge.svg)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22Nightly+Build%22) [![Coverity Scan Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) | +| **main** branch builds | [![CI Build](https://github.com/libgit2/libgit2/actions/workflows/main.yml/badge.svg?branch=main&event=push)](https://github.com/libgit2/libgit2/actions/workflows/main.yml?query=event%3Apush+branch%3Amain) [![Experimental Features](https://github.com/libgit2/libgit2/actions/workflows/experimental.yml/badge.svg?branch=main)](https://github.com/libgit2/libgit2/actions/workflows/experimental.yml?query=event%3Apush+branch%3Amain) | +| **v1.9 branch** builds | [![CI Build](https://github.com/libgit2/libgit2/actions/workflows/main.yml/badge.svg?branch=maint%2Fv1.9&event=push)](https://github.com/libgit2/libgit2/actions/workflows/main.yml?query=event%3Apush+branch%3Amaint%2Fv1.9) [![Experimental Features](https://github.com/libgit2/libgit2/actions/workflows/experimental.yml/badge.svg?branch=maint%2Fv1.9)](https://github.com/libgit2/libgit2/actions/workflows/experimental.yml?query=event%3Apush+branch%3Amaint%2Fv1.9) | +| **v1.8 branch** builds | [![CI Build](https://github.com/libgit2/libgit2/actions/workflows/main.yml/badge.svg?branch=maint%2Fv1.8&event=push)](https://github.com/libgit2/libgit2/actions/workflows/main.yml?query=event%3Apush+branch%3Amaint%2Fv1.8) [![Experimental Features](https://github.com/libgit2/libgit2/actions/workflows/experimental.yml/badge.svg?branch=maint%2Fv1.8)](https://github.com/libgit2/libgit2/actions/workflows/experimental.yml?query=event%3Apush+branch%3Amaint%2Fv1.8) | +| **Nightly** builds | [![Nightly Build](https://github.com/libgit2/libgit2/actions/workflows/nightly.yml/badge.svg?branch=main&event=schedule)](https://github.com/libgit2/libgit2/actions/workflows/nightly.yml) [![Coverity Scan Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) | `libgit2` is a portable, pure C implementation of the Git core methods provided as a linkable library with a solid API, allowing to build Git -functionality into your application. Language bindings like -[Rugged](https://github.com/libgit2/rugged) (Ruby), -[LibGit2Sharp](https://github.com/libgit2/libgit2sharp) (.NET), -[pygit2](http://www.pygit2.org/) (Python) and -[NodeGit](http://nodegit.org) (Node) allow you to build Git tooling -in your favorite language. - -`libgit2` is used to power Git GUI clients like -[GitKraken](https://gitkraken.com/) and [gmaster](https://gmaster.io/) -and on Git hosting providers like [GitHub](https://github.com/), -[GitLab](https://gitlab.com/) and -[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 -Linking Exception). This means that you can link against the library with any -kind of software without making that software fall under the GPL. -Changes to libgit2 would still be covered under its GPL license. -Additionally, the example code has been released to the public domain (see the -[separate license](examples/COPYING) for more information). +functionality into your application. + +`libgit2` is used in a variety of places, from GUI clients to hosting +providers ("forges") and countless utilities and applications in +between. Because it's written in C, it can be made available to any +other programming language through "bindings", so you can use it in +[Ruby](https://github.com/libgit2/rugged), +[.NET](https://github.com/libgit2/libgit2sharp), +[Python](http://www.pygit2.org/), +[Node.js](http://nodegit.org), +[Rust](https://github.com/rust-lang/git2-rs), and more. + +`libgit2` is licensed under a **very permissive license** (GPLv2 with +a special Linking Exception). This means that you can link against +the library with any kind of software without making that software +fall under the GPL. Changes to libgit2 would still be covered under +its GPL license. Table of Contents ================= @@ -47,7 +45,8 @@ Table of Contents * [Installation](#installation) * [Advanced Usage](#advanced-usage) * [Compiler and linker options](#compiler-and-linker-options) - * [MacOS X](#macos-x) + * [macOS](#macos) + * [iOS](#ios) * [Android](#android) * [MinGW](#mingw) * [Language Bindings](#language-bindings) @@ -68,7 +67,7 @@ But if you _do_ want to use libgit2 directly - because you're building an application in C - then you may be able use an existing binary. There are packages for the [vcpkg](https://github.com/Microsoft/vcpkg) and -[conan](https://conan.io/center/libgit2) +[conan](https://conan.io/center/recipes/libgit2) package managers. And libgit2 is available in [Homebrew](https://formulae.brew.sh/formula/libgit2) and most Linux distributions. @@ -92,8 +91,8 @@ Quick Start **Build** -1. Create a build directory beneath the libgit2 source directory, and change - into it: `mkdir build && cd build` +1. Create a build directory beneath the libgit2 source directory, + and change into it: `mkdir build && cd build` 2. Create the cmake build environment: `cmake ..` 3. Build libgit2: `cmake --build .` @@ -107,22 +106,24 @@ Getting Help - via IRC: join [#libgit2](https://web.libera.chat/#libgit2) on [libera](https://libera.chat). -- via Slack: visit [slack.libgit2.org](http://slack.libgit2.org/) to sign up, - then join us in `#libgit2` +- via Slack: visit [slack.libgit2.org](http://slack.libgit2.org/) + to sign up, then join us in `#libgit2` **Getting Help** If you have questions about the library, please be sure to check out the -[API documentation](http://libgit2.github.com/libgit2/). If you still have +[API documentation](https://libgit2.org/libgit2/). If you still have questions, reach out to us on Slack or post a question on -[StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) (with the `libgit2` tag). +[StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) +(with the `libgit2` tag). **Reporting Bugs** -Please open a [GitHub Issue](https://github.com/libgit2/libgit2/issues) and -include as much information as possible. If possible, provide sample code -that illustrates the problem you're seeing. If you're seeing a bug only -on a specific repository, please provide a link to it if possible. +Please open a [GitHub Issue](https://github.com/libgit2/libgit2/issues) +and include as much information as possible. If possible, provide +sample code that illustrates the problem you're seeing. If you're +seeing a bug only on a specific repository, please provide a link to +it if possible. We ask that you not open a GitHub Issue for help, only for bug reports. @@ -137,10 +138,10 @@ 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 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 -Git system, but most commands a user would type are out of scope for this -library to implement directly. +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 Git system, but most commands a user would type are out +of scope for this library to implement directly. The library provides: @@ -160,23 +161,31 @@ The library provides: As libgit2 is purely a consumer of the Git system, we have to adjust to changes made upstream. This has two major consequences: -* Some changes may require us to change provided interfaces. While we try to - implement functions in a generic way so that no future changes are required, - we cannot promise a completely stable API. -* As we have to keep up with changes in behavior made upstream, we may lag - behind in some areas. We usually to document these incompatibilities in our - issue tracker with the label "git change". +* Some changes may require us to change provided interfaces. While + we try to implement functions in a generic way so that no future + changes are required, we cannot promise a completely stable API. +* As we have to keep up with changes in behavior made upstream, we + may lag behind in some areas. We usually to document these + incompatibilities in our issue tracker with the label "git change". Optional dependencies ===================== -While the library provides git functionality without the need for -dependencies, it can make use of a few libraries to add to it: - -- pthreads (non-Windows) to enable threadsafe access as well as multi-threaded pack generation -- OpenSSL (non-Windows) to talk over HTTPS and provide the SHA-1 functions -- LibSSH2 to enable the SSH transport -- iconv (OSX) to handle the HFS+ path encoding peculiarities +While the library provides git functionality with very few +dependencies, some recommended dependencies are used for performance +or complete functionality. + +- Hash generation: Git uses SHA1DC (collision detecting SHA1) for + its default hash generation. SHA256 support is experimental, and + optimized support is provided by system libraries on macOS and + Windows, or by the HTTPS library on Unix systems when available. +- Threading: is provided by the system libraries on Windows, and + pthreads on Unix systems. +- HTTPS: is provided by the system libraries on macOS and Windows, + or by OpenSSL or mbedTLS on other Unix systems. +- SSH: is provided by [libssh2](https://libssh2.org/) or by invoking + [OpenSSH](https://www.openssh.com). +- Unicode: is provided by the system libraries on Windows and macOS. Initialization =============== @@ -211,12 +220,9 @@ Building libgit2 - Using CMake Building -------- -`libgit2` builds cleanly on most platforms without any external dependencies. -Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthreads` to be available; -they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API -for threading. - -The `libgit2` library is built using [CMake]() (version 2.8 or newer) on all platforms. +`libgit2` builds cleanly on most platforms without any external +dependencies as a requirement. `libgit2` is built using +[CMake]() (version 2.8 or newer) on all platforms. On most systems you can build the library using the following commands @@ -224,10 +230,90 @@ On most systems you can build the library using the following commands $ cmake .. $ cmake --build . +To include the examples in the build, use `cmake -DBUILD_EXAMPLES=ON ..` +instead of `cmake ..`. The built executable for the examples can then +be found in `build/examples`, relative to the toplevel directory. + Alternatively you can point the CMake GUI tool to the CMakeLists.txt file and generate platform specific build project or IDE workspace. If you're not familiar with CMake, [a more detailed explanation](https://preshing.com/20170511/how-to-build-a-cmake-based-project/) may be helpful. +Advanced Options +---------------- + +You can specify a number of options to `cmake` that will change the +way `libgit2` is built. To use this, specify `-Doption=value` during +the initial `cmake` configuration. For example, to enable SHA256 +compatibility: + + $ mkdir build && cd build + $ cmake -DEXPERIMENTAL_SHA256=ON .. + $ cmake --build . + +libgit2 options: + +* `EXPERIMENTAL_SHA256=ON`: turns on SHA256 compatibility; note that + this is an API-incompatible change, hence why it is labeled + "experimental" + +Build options: + +* `BUILD_EXAMPLES=ON`: builds the suite of example code +* `BUILD_FUZZERS=ON`: builds the fuzzing suite +* `ENABLE_WERROR=ON`: build with `-Werror` or the equivalent, which turns + compiler warnings into errors in the libgit2 codebase (but not its + dependencies) + +Dependency options: + +* `USE_SSH=type`: enables SSH support and optionally selects the provider; + `type` can be set to `libssh2` or `exec` (which will execute an external + OpenSSH command). `ON` implies `libssh2`; defaults to `OFF`. +* `USE_HTTPS=type`: enables HTTPS support and optionally selects the + provider; `type` can be set to `OpenSSL`, `OpenSSL-Dynamic` (to not + link against OpenSSL, but load it dynamically), `SecureTransport`, + `Schannel` or `WinHTTP`; the default is `SecureTransport` on macOS, + `WinHTTP` on Windows, and whichever of `OpenSSL` or `mbedTLS` is + detected on other platforms. Defaults to `ON`. +* `USE_SHA1=type`: selects the SHA1 mechanism to use; `type` can be set + to `CollisionDetection`, `HTTPS` to use the system or HTTPS provider, + or one of `OpenSSL`, `OpenSSL-Dynamic`, `OpenSSL-FIPS` (to use FIPS + compliant routines in OpenSSL), `CommonCrypto`, or `Schannel`. + Defaults to `CollisionDetection`. This option is retained for + backward compatibility and should not be changed. +* `USE_SHA256=type`: selects the SHA256 mechanism to use; `type` can be + set to `HTTPS` to use the system or HTTPS driver, `builtin`, or one of + `OpenSSL`, `OpenSSL-Dynamic`, `OpenSSL-FIPS` (to use FIPS compliant + routines in OpenSSL), `CommonCrypto`, or `Schannel`. Defaults to `HTTPS`. +* `USE_GSSAPI=`: enables GSSAPI for SPNEGO authentication on + Unix. Defaults to `OFF`. +* `USE_HTTP_PARSER=type`: selects the HTTP Parser; either `http-parser` + for an external + [`http-parser`](https://github.com/nodejs/http-parser) dependency, + `llhttp` for an external [`llhttp`](https://github.com/nodejs/llhttp) + dependency, or `builtin`. Defaults to `builtin`. +* `REGEX_BACKEND=type`: selects the regular expression backend to use; + one of `regcomp_l`, `pcre2`, `pcre`, `regcomp`, or `builtin`. The + default is to use `regcomp_l` where available, PCRE if found, otherwise, + to use the builtin. +* `USE_BUNDLED_ZLIB=type`: selects the bundled zlib; either `ON` or `OFF`. + Defaults to using the system zlib if available, falling back to the + bundled zlib. + +Locating Dependencies +--------------------- + +The `libgit2` project uses `cmake` since it helps with cross-platform +projects, especially those with many dependencies. If your dependencies +are in non-standard places, you may want to use the `_ROOT_DIR` options +to specify their location. For example, to specify an OpenSSL location: + + $ cmake -DOPENSSL_ROOT_DIR=/tmp/openssl-3.3.2 .. + +Since these options are general to CMake, their +[documentation](https://cmake.org/documentation/) may be helpful. If +you have questions about dependencies, please [contact us](#getting-help). + Running Tests ------------- @@ -243,12 +329,13 @@ Invoking the test suite directly is useful because it allows you to execute individual tests, or groups of tests using the `-s` flag. For example, to run the index tests: - $ ./libgit2_tests -sindex + $ ./libgit2_tests -sindex -To run a single test named `index::racy::diff`, which corresponds to the test -function [`test_index_racy__diff`](https://github.com/libgit2/libgit2/blob/main/tests/index/racy.c#L23): +To run a single test named `index::racy::diff`, which corresponds to +the test function +[`test_index_racy__diff`](https://github.com/libgit2/libgit2/blob/main/tests/index/racy.c#L23): - $ ./libgit2_tests -sindex::racy::diff + $ ./libgit2_tests -sindex::racy::diff The test suite will print a `.` for every passing test, and an `F` for any failing test. An `S` indicates that a test was skipped because it is not @@ -257,7 +344,8 @@ applicable to your platform or is particularly expensive. **Note:** There should be _no_ failing tests when you build an unmodified source tree from a [release](https://github.com/libgit2/libgit2/releases), or from the [main branch](https://github.com/libgit2/libgit2/tree/main). -Please contact us or [open an issue](https://github.com/libgit2/libgit2/issues) +Please contact us or +[open an issue](https://github.com/libgit2/libgit2/issues) if you see test failures. Installation @@ -271,7 +359,8 @@ To install the library you can specify the install prefix by setting: Advanced Usage -------------- -For more advanced use or questions about CMake please read . +For more advanced use or questions about CMake please read the +[CMake FAQ](https://cmake.org/Wiki/CMake_FAQ). The following CMake variables are declared: @@ -286,31 +375,71 @@ To list all build options and their current value, you can do the following: # Create and set up a build directory - $ mkdir build + $ mkdir build && cd build $ cmake .. + # List all build options and their values $ cmake -L Compiler and linker options --------------------------- -CMake lets you specify a few variables to control the behavior of the -compiler and linker. These flags are rarely used but can be useful for -64-bit to 32-bit cross-compilation. +There are several options that control the behavior of the compiler and +linker. These flags may be useful for cross-compilation or specialized +setups. - `CMAKE_C_FLAGS`: Set your own compiler flags +- `CMAKE_C_STANDARD`: the C standard to compile against; defaults to `C90` +- `CMAKE_C_EXTENSIONS`: whether compiler extensions are supported; defaults to `OFF` - `CMAKE_FIND_ROOT_PATH`: Override the search path for libraries - `ZLIB_LIBRARY`, `OPENSSL_SSL_LIBRARY` AND `OPENSSL_CRYPTO_LIBRARY`: Tell CMake where to find those specific libraries - `LINK_WITH_STATIC_LIBRARIES`: Link only with static versions of system libraries -MacOS X +macOS ------- -If you want to build a universal binary for Mac OS X, CMake sets it -all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"` -when configuring. +If you'd like to work with Xcode, you can generate an Xcode project with "-G Xcode". + + # Create and set up a build directory + $ mkdir build && cd build + $ cmake -G Xcode .. + +> [!TIP] +> Universal binary support: +> +> If you want to build a universal binary for macOS 11.0+, CMake sets it +> all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"` +> when configuring. +> +> [Deprecated] If you want to build a universal binary for Mac OS X +> (10.4.4 ~ 10.6), CMake sets it all up for you if you use +> `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"` when configuring. + +iOS +------- + +1. Get an iOS cmake toolchain File: + +You can use a pre-existing toolchain file like [ios-cmake](https://github.com/leetal/ios-cmake) or write your own. + +2. Specify the toolchain and system Name: + +- The CMAKE_TOOLCHAIN_FILE variable points to the toolchain file for iOS. +- The CMAKE_SYSTEM_NAME should be set to iOS. + +3. Example Command: + +Assuming you're using the ios-cmake toolchain, the command might look like this: + +``` +cmake -G Xcode -DCMAKE_TOOLCHAIN_FILE=path/to/ios.toolchain.cmake -DCMAKE_SYSTEM_NAME=iOS -DPLATFORM=OS64 .. +``` + +4. Build the Project: + +After generating the project, open the .xcodeproj file in Xcode, select your iOS device or simulator as the target, and build your project. Android ------- @@ -337,16 +466,19 @@ when configuring. MinGW ----- -If you want to build the library in MinGW environment with SSH support enabled, -you may need to pass `-DCMAKE_LIBRARY_PATH="${MINGW_PREFIX}/${MINGW_CHOST}/lib/"` flag -to CMake when configuring. This is because CMake cannot find the Win32 libraries in -MinGW folders by default and you might see an error message stating that CMake -could not resolve `ws2_32` library during configuration. +If you want to build the library in MinGW environment with SSH support +enabled, you may need to pass +`-DCMAKE_LIBRARY_PATH="${MINGW_PREFIX}/${MINGW_CHOST}/lib/"` flag +to CMake when configuring. This is because CMake cannot find the +Win32 libraries in MinGW folders by default and you might see an +error message stating that CMake could not resolve `ws2_32` library +during configuration. -Another option would be to install `msys2-w32api-runtime` package before configuring. -This package installs the Win32 libraries into `/usr/lib` folder which is by default -recognized as the library path by CMake. Please note though that this package is meant -for MSYS subsystem which is different from MinGW. +Another option would be to install `msys2-w32api-runtime` package before +configuring. This package installs the Win32 libraries into `/usr/lib` +folder which is by default recognized as the library path by CMake. +Please note though that this package is meant for MSYS subsystem which +is different from MinGW. Language Bindings ================================== @@ -426,15 +558,16 @@ and that are good places to jump in and get started. There's much more detailed information in our list of [outstanding projects](docs/projects.md). -Please be sure to check the [contribution guidelines](docs/contributing.md) to -understand our workflow, and the libgit2 [coding conventions](docs/conventions.md). +Please be sure to check the [contribution guidelines](docs/contributing.md) +to understand our workflow, and the libgit2 +[coding conventions](docs/conventions.md). License ================================== -`libgit2` is under GPL2 **with linking exception**. This means you can link to -and use the library from any program, proprietary or open source; paid or -gratis. However, if you modify libgit2 itself, you must distribute the -source to your modified version of libgit2. +`libgit2` is under GPL2 **with linking exception**. This means you can +link to and use the library from any program, proprietary or open source; +paid or gratis. However, if you modify libgit2 itself, you must distribute +the source to your modified version of libgit2. See the [COPYING file](COPYING) for the full license text. diff --git a/SECURITY.md b/SECURITY.md index f98eebf5..914e660b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ This project will always provide security fixes for the latest two released versions. E.g. if the latest version is v0.28.x, then we will provide security -fixes for both v0.28.x and v0.27.y, but no later versions. +fixes for both v0.28.x and v0.27.y, but no earlier versions. ## Reporting a Vulnerability diff --git a/ci/build.sh b/ci/build.sh index 80e7a61a..a9b66f66 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -61,6 +61,8 @@ if test -n "${CC}"; then "${CC}" --version 2>&1 | indent fi echo "Environment:" +echo "PATH=${BUILD_PATH}" | indent + if test -n "${CC}"; then echo "CC=${CC}" | indent fi diff --git a/ci/docker/bionic b/ci/docker/bionic index f1b69ede..f42c6d2a 100644 --- a/ci/docker/bionic +++ b/ci/docker/bionic @@ -12,7 +12,6 @@ RUN apt-get update && \ libcurl4-openssl-dev \ libkrb5-dev \ libpcre3-dev \ - libssh2-1-dev \ libssl-dev \ libz-dev \ ninja-build \ @@ -37,7 +36,16 @@ RUN cd /tmp && \ cd .. && \ rm -rf mbedtls-mbedtls-2.16.2 -FROM mbedtls AS adduser +FROM mbedtls AS libssh2 +RUN cd /tmp && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON . && \ + ninja install && \ + cd .. && \ + rm -rf libssh2-1.11.0 + +FROM libssh2 AS adduser ARG UID="" ARG GID="" RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ diff --git a/ci/docker/centos7 b/ci/docker/centos7 index 28ed6508..45c299d1 100644 --- a/ci/docker/centos7 +++ b/ci/docker/centos7 @@ -18,13 +18,13 @@ RUN yum install -y \ FROM yum AS libssh2 RUN cd /tmp && \ - curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.0.tar.gz | tar -xz && \ - cd libssh2-1.8.0 && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ ./configure && \ make && \ make install && \ cd .. && \ - rm -rf libssh-1.8.0 + rm -rf libssh-1.11.0 FROM libssh2 AS valgrind RUN cd /tmp && \ diff --git a/ci/docker/centos8 b/ci/docker/centos8 index 81f0c3c7..c2ac5f07 100644 --- a/ci/docker/centos8 +++ b/ci/docker/centos8 @@ -24,13 +24,13 @@ RUN yum install -y \ FROM yum AS libssh2 RUN cd /tmp && \ - curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.0.tar.gz | tar -xz && \ - cd libssh2-1.8.0 && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ ./configure && \ make && \ make install && \ cd .. && \ - rm -rf libssh2-1.8.0 + rm -rf libssh2-1.11.0 FROM libssh2 AS valgrind RUN cd /tmp && \ diff --git a/ci/docker/fedora b/ci/docker/fedora new file mode 100644 index 00000000..1db3ceb7 --- /dev/null +++ b/ci/docker/fedora @@ -0,0 +1,52 @@ +ARG BASE=fedora:rawhide + +FROM ${BASE} AS stream +RUN dnf -y distro-sync + +FROM stream AS yum +RUN yum install -y \ + which \ + bzip2 \ + git \ + libarchive \ + cmake \ + gcc \ + make \ + openssl-devel \ + openssh-server \ + git-daemon \ + java-1.8.0-openjdk-headless \ + sudo \ + python3 \ + valgrind \ + krb5-workstation \ + krb5-libs \ + krb5-devel \ + pcre2-devel \ + zlib-devel \ + ninja-build \ + llhttp-devel + +FROM yum AS libssh2 +RUN cd /tmp && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + ./configure && \ + make && \ + make install && \ + cd .. && \ + rm -rf libssh2-1.11.0 + +FROM libssh2 AS adduser +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 + +FROM adduser AS configure +ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig +RUN mkdir /var/run/sshd +RUN echo "/usr/local/lib" > /etc/ld.so.conf.d/local && \ + ldconfig diff --git a/ci/docker/focal b/ci/docker/focal index b3a402cb..62f5b630 100644 --- a/ci/docker/focal +++ b/ci/docker/focal @@ -53,7 +53,7 @@ RUN cd /tmp && \ cd libssh2-1.9.0 && \ mkdir build build-msan && \ cd build && \ - CC=clang-10 CFLAGS="-fPIC" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=Libgcrypt -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ + CC=clang-10 CFLAGS="-fPIC" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ ninja install && \ cd ../build-msan && \ CC=clang-10 CFLAGS="-fPIC -fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" LDFLAGS="-fsanitize=memory" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=mbedTLS -DCMAKE_PREFIX_PATH=/usr/local/msan -DCMAKE_INSTALL_PREFIX=/usr/local/msan .. && \ diff --git a/ci/docker/noble b/ci/docker/noble new file mode 100644 index 00000000..e7330277 --- /dev/null +++ b/ci/docker/noble @@ -0,0 +1,90 @@ +ARG BASE=ubuntu:noble + +FROM ${BASE} AS apt +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + bzip2 \ + clang \ + clang-18 \ + cmake \ + curl \ + gcc \ + git \ + krb5-user \ + libclang-rt-18-dev \ + libcurl4-gnutls-dev \ + libgcrypt20-dev \ + libhttp-parser-dev \ + libkrb5-dev \ + libpcre3-dev \ + libssl-dev \ + libz-dev \ + llvm-18 \ + make \ + ninja-build \ + openjdk-8-jre-headless \ + openssh-server \ + openssl \ + pkgconf \ + python3 \ + sudo \ + valgrind \ + && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir /usr/local/msan + +FROM apt AS mbedtls +RUN cd /tmp && \ + curl --location --silent --show-error https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/mbedtls-2.28.6.tar.gz | \ + tar -xz && \ + cd mbedtls-mbedtls-2.28.6 && \ + scripts/config.pl unset MBEDTLS_AESNI_C && \ + scripts/config.pl set MBEDTLS_MD4_C 1 && \ + mkdir build build-msan && \ + cd build && \ + CC=clang-18 CFLAGS="-fPIC" cmake -G Ninja -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=ON -DUSE_STATIC_MBEDTLS_LIBRARY=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ + ninja install && \ + cd ../build-msan && \ + CC=clang-18 CFLAGS="-fPIC" cmake -G Ninja -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=ON -DUSE_STATIC_MBEDTLS_LIBRARY=OFF -DCMAKE_BUILD_TYPE=MemSanDbg -DCMAKE_INSTALL_PREFIX=/usr/local/msan .. && \ + ninja install && \ + cd .. && \ + rm -rf mbedtls-mbedtls-2.28.6 + +FROM mbedtls AS libssh2 +RUN cd /tmp && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + mkdir build build-msan && \ + cd build && \ + CC=clang-18 CFLAGS="-fPIC" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_PREFIX_PATH=/usr/local -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ + ninja install && \ + cd ../build-msan && \ + CC=clang-18 CFLAGS="-fPIC -fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" LDFLAGS="-fsanitize=memory" cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=mbedTLS -DCMAKE_PREFIX_PATH=/usr/local/msan -DCMAKE_INSTALL_PREFIX=/usr/local/msan .. && \ + ninja install && \ + cd .. && \ + rm -rf libssh2-1.11.0 + +FROM libssh2 AS valgrind +RUN cd /tmp && \ + curl --insecure --location --silent --show-error https://sourceware.org/pub/valgrind/valgrind-3.23.0.tar.bz2 | \ + tar -xj && \ + cd valgrind-3.23.0 && \ + CC=clang-18 ./configure && \ + make MAKEFLAGS="-j -l$(grep -c ^processor /proc/cpuinfo)" && \ + make install && \ + cd .. && \ + rm -rf valgrind-3.23.0 + +FROM valgrind AS adduser +ARG UID="" +ARG GID="" +RUN if [ "${UID}" != "" ]; then USER_ARG="--uid ${UID}"; fi && \ + if [ "${GID}" != "" ]; then GROUP_ARG="--gid ${GID}"; fi && \ + groupadd ${GROUP_ARG} libgit2 && \ + useradd ${USER_ARG} --gid libgit2 --shell /bin/bash --create-home libgit2 + +FROM adduser AS ldconfig +RUN ldconfig + +FROM ldconfig AS configure +RUN mkdir /var/run/sshd diff --git a/ci/docker/xenial b/ci/docker/xenial index 578f0a96..c84db419 100644 --- a/ci/docker/xenial +++ b/ci/docker/xenial @@ -7,13 +7,13 @@ RUN apt-get update && \ clang \ cmake \ curl \ - gettext \ + gettext \ gcc \ krb5-user \ libcurl4-gnutls-dev \ - libexpat1-dev \ + libexpat1-dev \ libgcrypt20-dev \ - libintl-perl \ + libintl-perl \ libkrb5-dev \ libpcre3-dev \ libssl-dev \ @@ -53,12 +53,12 @@ RUN cd /tmp && \ FROM mbedtls AS libssh2 RUN cd /tmp && \ - curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.2.tar.gz | tar -xz && \ - cd libssh2-1.8.2 && \ - CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=Libgcrypt . && \ + curl --location --silent --show-error https://www.libssh2.org/download/libssh2-1.11.0.tar.gz | tar -xz && \ + cd libssh2-1.11.0 && \ + CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON . && \ ninja install && \ cd .. && \ - rm -rf libssh2-1.8.2 + rm -rf libssh2-1.11.0 FROM libssh2 AS valgrind RUN cd /tmp && \ diff --git a/ci/getcontainer.sh b/ci/getcontainer.sh deleted file mode 100755 index 81d0c1d9..00000000 --- a/ci/getcontainer.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -set -e - -IMAGE_NAME=$1 -DOCKERFILE_PATH=$2 - -if [ "${IMAGE_NAME}" = "" ]; then - echo "usage: $0 image_name [dockerfile]" - exit 1 -fi - -if [ "${DOCKERFILE_PATH}" = "" ]; then - DOCKERFILE_PATH="${IMAGE_NAME}" -fi - -if [ "${DOCKER_REGISTRY}" = "" ]; then - echo "DOCKER_REGISTRY environment variable is unset." - echo "Not running inside GitHub Actions or misconfigured?" - exit 1 -fi - -DOCKER_CONTAINER="${GITHUB_REPOSITORY}/${IMAGE_NAME}" -DOCKER_REGISTRY_CONTAINER="${DOCKER_REGISTRY}/${DOCKER_CONTAINER}" - -echo "dockerfile=${DOCKERFILE_PATH}" >> $GITHUB_ENV -echo "docker-container=${DOCKER_CONTAINER}" >> $GITHUB_ENV -echo "docker-registry-container=${DOCKER_REGISTRY_CONTAINER}" >> $GITHUB_ENV - -# Identify the last git commit that touched the Dockerfiles -# Use this as a hash to identify the resulting docker containers -DOCKER_SHA=$(git log -1 --pretty=format:"%h" -- "${DOCKERFILE_PATH}") -echo "docker-sha=${DOCKER_SHA}" >> $GITHUB_ENV - -DOCKER_REGISTRY_CONTAINER_SHA="${DOCKER_REGISTRY_CONTAINER}:${DOCKER_SHA}" - -echo "docker-registry-container-sha=${DOCKER_REGISTRY_CONTAINER_SHA}" >> $GITHUB_ENV -echo "docker-registry-container-latest=${DOCKER_REGISTRY_CONTAINER}:latest" >> $GITHUB_ENV - -echo "::: logging in to ${DOCKER_REGISTRY} as ${GITHUB_ACTOR}" - -exists="true" -docker login https://${DOCKER_REGISTRY} -u ${GITHUB_ACTOR} -p ${GITHUB_TOKEN} || exists="false" - -echo "::: pulling ${DOCKER_REGISTRY_CONTAINER_SHA}" - -if [ "${exists}" != "false" ]; then - docker pull ${DOCKER_REGISTRY_CONTAINER_SHA} || exists="false" -fi - -if [ "${exists}" = "true" ]; then - echo "docker-container-exists=true" >> $GITHUB_ENV -else - echo "docker-container-exists=false" >> $GITHUB_ENV -fi diff --git a/ci/setup-ios-build.sh b/ci/setup-ios-build.sh new file mode 100755 index 00000000..623b135c --- /dev/null +++ b/ci/setup-ios-build.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -ex + +brew update +brew install ninja + +sudo mkdir /usr/local/lib || true +sudo chmod 0755 /usr/local/lib +sudo ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib + +curl -s -L https://raw.githubusercontent.com/leetal/ios-cmake/master/ios.toolchain.cmake -o ios.toolchain.cmake diff --git a/ci/setup-osx-build.sh b/ci/setup-osx-build.sh index 0b95e762..55989025 100755 --- a/ci/setup-osx-build.sh +++ b/ci/setup-osx-build.sh @@ -3,6 +3,8 @@ set -ex brew update -brew install pkgconfig zlib curl openssl libssh2 ninja +brew install ninja -ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib +sudo mkdir /usr/local/lib || true +sudo chmod 0755 /usr/local/lib +sudo ln -s /Applications/Xcode.app/Contents/Developer/usr/lib/libLeaksAtExit.dylib /usr/local/lib diff --git a/ci/setup-sanitizer-build.sh b/ci/setup-sanitizer-build.sh new file mode 100755 index 00000000..e4591f85 --- /dev/null +++ b/ci/setup-sanitizer-build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -ex + +# Linux updated its ASLR randomization in a way that is incompatible with +# TSAN. See https://github.com/google/sanitizers/issues/1716 +sudo sysctl vm.mmap_rnd_bits=28 diff --git a/ci/test.sh b/ci/test.sh index ee6801a7..98093c6e 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -3,7 +3,14 @@ set -e if [ -n "$SKIP_TESTS" ]; then - exit 0 + if [ -z "$SKIP_OFFLINE_TESTS" ]; then SKIP_OFFLINE_TESTS=1; fi + if [ -z "$SKIP_ONLINE_TESTS" ]; then SKIP_ONLINE_TESTS=1; fi + if [ -z "$SKIP_GITDAEMON_TESTS" ]; then SKIP_GITDAEMON_TESTS=1; fi + if [ -z "$SKIP_PROXY_TESTS" ]; then SKIP_PROXY_TESTS=1; fi + if [ -z "$SKIP_NTLM_TESTS" ]; then SKIP_NTLM_TESTS=1; fi + if [ -z "$SKIP_NEGOTIATE_TESTS" ]; then SKIP_NEGOTIATE_TESTS=1; fi + if [ -z "$SKIP_SSH_TESTS" ]; then SKIP_SSH_TESTS=1; fi + if [ -z "$SKIP_FUZZERS" ]; then SKIP_FUZZERS=1; fi fi # Windows doesn't run the NTLM tests properly (yet) @@ -11,6 +18,11 @@ if [[ "$(uname -s)" == MINGW* ]]; then SKIP_NTLM_TESTS=1 fi +# older versions of git don't support push options +if [ -z "$SKIP_PUSHOPTIONS_TESTS" ]; then + export GITTEST_PUSH_OPTIONS=true +fi + SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} BUILD_DIR=$(pwd) BUILD_PATH=${BUILD_PATH:=$PATH} @@ -18,12 +30,24 @@ CTEST=$(which ctest) TMPDIR=${TMPDIR:-/tmp} USER=${USER:-$(whoami)} +GITTEST_SSH_KEYTYPE=${GITTEST_SSH_KEYTYPE:="ecdsa"} + HOME=`mktemp -d ${TMPDIR}/home.XXXXXXXX` export CLAR_HOMEDIR=${HOME} SUCCESS=1 CONTINUE_ON_FAILURE=0 +should_run() { + eval "skip=\${SKIP_${1}}" + [ -z "$skip" \ + -o "$skip" == "no" -o "$skip" == "NO" \ + -o "$skip" == "n" -o "$skip" == "N" \ + -o "$skip" == "false" -o "$skip" == "FALSE" \ + -o "$skip" == "f" -o "$skip" == "F" \ + -o "$skip" == "0" ] +} + cleanup() { echo "Cleaning up..." @@ -78,6 +102,8 @@ run_test() { echo "" echo "Re-running flaky ${1} tests..." echo "" + + sleep 2 fi RETURN_CODE=0 @@ -138,11 +164,12 @@ echo "########################################################################## echo "" -if [ -z "$SKIP_GITDAEMON_TESTS" ]; then +if should_run "GITDAEMON_TESTS"; then echo "Starting git daemon (standard)..." GIT_STANDARD_DIR=`mktemp -d ${TMPDIR}/git_standard.XXXXXXXX` - git init --bare "${GIT_STANDARD_DIR}/test.git" >/dev/null + cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${GIT_STANDARD_DIR}/test.git" git daemon --listen=localhost --export-all --enable=receive-pack --base-path="${GIT_STANDARD_DIR}" "${GIT_STANDARD_DIR}" 2>/dev/null & + GIT_STANDARD_PID=$! echo "Starting git daemon (namespace)..." @@ -158,7 +185,7 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then GIT_SHA256_PID=$! fi -if [ -z "$SKIP_PROXY_TESTS" ]; then +if should_run "PROXY_TESTS"; then curl --location --silent --show-error https://github.com/ethomson/poxyproxy/releases/download/v0.7.0/poxyproxy-0.7.0.jar >poxyproxy.jar echo "Starting HTTP proxy (Basic)..." @@ -170,25 +197,27 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then PROXY_NTLM_PID=$! fi -if [ -z "$SKIP_NTLM_TESTS" -o -z "$SKIP_ONLINE_TESTS" ]; then +if should_run "NTLM_TESTS" || should_run "ONLINE_TESTS"; then curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.6.0/poxygit-0.6.0.jar >poxygit.jar echo "Starting HTTP server..." HTTP_DIR=`mktemp -d ${TMPDIR}/http.XXXXXXXX` - git init --bare "${HTTP_DIR}/test.git" + cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${HTTP_DIR}/test.git" + java -jar poxygit.jar --address 127.0.0.1 --port 9000 --credentials foo:baz --quiet "${HTTP_DIR}" & HTTP_PID=$! fi -if [ -z "$SKIP_SSH_TESTS" ]; then +if should_run "SSH_TESTS"; then echo "Starting SSH server..." SSHD_DIR=`mktemp -d ${TMPDIR}/sshd.XXXXXXXX` - git init --bare "${SSHD_DIR}/test.git" >/dev/null + cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${SSHD_DIR}/test.git" + cat >"${SSHD_DIR}/sshd_config" <<-EOF Port 2222 ListenAddress 0.0.0.0 Protocol 2 - HostKey ${SSHD_DIR}/id_rsa + HostKey ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE} PidFile ${SSHD_DIR}/pid AuthorizedKeysFile ${HOME}/.ssh/authorized_keys LogLevel DEBUG @@ -197,19 +226,21 @@ if [ -z "$SKIP_SSH_TESTS" ]; then PubkeyAuthentication yes ChallengeResponseAuthentication no StrictModes no + HostCertificate ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}.pub + HostKey ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE} # Required here as sshd will simply close connection otherwise UsePAM no EOF - ssh-keygen -t rsa -f "${SSHD_DIR}/id_rsa" -N "" -q + ssh-keygen -t "${GITTEST_SSH_KEYTYPE}" -f "${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}" -N "" -q /usr/sbin/sshd -f "${SSHD_DIR}/sshd_config" -E "${SSHD_DIR}/log" # Set up keys mkdir "${HOME}/.ssh" - ssh-keygen -t rsa -f "${HOME}/.ssh/id_rsa" -N "" -q - cat "${HOME}/.ssh/id_rsa.pub" >>"${HOME}/.ssh/authorized_keys" + ssh-keygen -t "${GITTEST_SSH_KEYTYPE}" -f "${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}" -N "" -q + cat "${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}.pub" >>"${HOME}/.ssh/authorized_keys" while read algorithm key comment; do echo "[localhost]:2222 $algorithm $key" >>"${HOME}/.ssh/known_hosts" - done <"${SSHD_DIR}/id_rsa.pub" + done <"${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}.pub" # Append the github.com keys for the tests that don't override checks. # We ask for ssh-rsa to test that the selection based off of known_hosts @@ -228,7 +259,7 @@ fi # Run the tests that do not require network connectivity. -if [ -z "$SKIP_OFFLINE_TESTS" ]; then +if should_run "OFFLINE_TESTS"; then echo "" echo "##############################################################################" echo "## Running core tests" @@ -259,7 +290,11 @@ if [ -n "$RUN_INVASIVE_TESTS" ]; then unset GITTEST_INVASIVE_SPEED fi -if [ -z "$SKIP_ONLINE_TESTS" ]; then +# the various network tests can fail due to network connectivity problems; +# allow them to retry up to 5 times +export GITTEST_FLAKY_RETRY=5 + +if should_run "ONLINE_TESTS"; then # Run the online tests. The "online" test suite only includes the # default online tests that do not require additional configuration. # The "proxy" and "ssh" test suites require further setup. @@ -288,7 +323,7 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then run_test online_customcert fi -if [ -z "$SKIP_GITDAEMON_TESTS" ]; then +if should_run "GITDAEMON_TESTS"; then echo "" echo "Running gitdaemon (standard) tests" echo "" @@ -316,7 +351,7 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then unset GITTEST_REMOTE_URL fi -if [ -z "$SKIP_PROXY_TESTS" ]; then +if should_run "PROXY_TESTS"; then echo "" echo "Running proxy tests (Basic authentication)" echo "" @@ -342,7 +377,7 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then unset GITTEST_REMOTE_PROXY_PASS fi -if [ -z "$SKIP_NTLM_TESTS" ]; then +if should_run "NTLM_TESTS"; then echo "" echo "Running NTLM tests (IIS emulation)" echo "" @@ -368,7 +403,7 @@ if [ -z "$SKIP_NTLM_TESTS" ]; then unset GITTEST_REMOTE_PASS fi -if [ -z "$SKIP_NEGOTIATE_TESTS" -a -n "$GITTEST_NEGOTIATE_PASSWORD" ]; then +if should_run "NEGOTIATE_TESTS" && -n "$GITTEST_NEGOTIATE_PASSWORD" ; then echo "" echo "Running SPNEGO tests" echo "" @@ -401,13 +436,15 @@ if [ -z "$SKIP_NEGOTIATE_TESTS" -a -n "$GITTEST_NEGOTIATE_PASSWORD" ]; then kdestroy -A fi -if [ -z "$SKIP_SSH_TESTS" ]; then +if should_run "SSH_TESTS"; then export GITTEST_REMOTE_USER=$USER - export GITTEST_REMOTE_SSH_KEY="${HOME}/.ssh/id_rsa" - export GITTEST_REMOTE_SSH_PUBKEY="${HOME}/.ssh/id_rsa.pub" + export GITTEST_REMOTE_SSH_KEY="${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}" + export GITTEST_REMOTE_SSH_PUBKEY="${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE}.pub" export GITTEST_REMOTE_SSH_PASSPHRASE="" export GITTEST_REMOTE_SSH_FINGERPRINT="${SSH_FINGERPRINT}" + export GITTEST_SSH_CMD="ssh -i ${HOME}/.ssh/id_${GITTEST_SSH_KEYTYPE} -o UserKnownHostsFile=${HOME}/.ssh/known_hosts" + echo "" echo "Running ssh tests" echo "" @@ -424,6 +461,8 @@ if [ -z "$SKIP_SSH_TESTS" ]; then run_test ssh unset GITTEST_REMOTE_URL + unset GITTEST_SSH_CMD + unset GITTEST_REMOTE_USER unset GITTEST_REMOTE_SSH_KEY unset GITTEST_REMOTE_SSH_PUBKEY @@ -431,7 +470,9 @@ if [ -z "$SKIP_SSH_TESTS" ]; then unset GITTEST_REMOTE_SSH_FINGERPRINT fi -if [ -z "$SKIP_FUZZERS" ]; then +unset GITTEST_FLAKY_RETRY + +if should_run "FUZZERS"; then echo "" echo "##############################################################################" echo "## Running fuzzers" diff --git a/cmake/DefaultCFlags.cmake b/cmake/DefaultCFlags.cmake index a9c9ab97..2c407b05 100644 --- a/cmake/DefaultCFlags.cmake +++ b/cmake/DefaultCFlags.cmake @@ -13,6 +13,9 @@ if(MSVC) # /Gd - explicitly set cdecl calling convention set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gd") + # Remove warnings about operands that use different enum types. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd5287") + if(NOT (MSVC_VERSION LESS 1900)) # /guard:cf - Enable Control Flow Guard set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /guard:cf") @@ -56,7 +59,7 @@ if(MSVC) set(CMAKE_C_FLAGS_MINSIZEREL "/DNDEBUG /O1 /Oy /GL /Gy ${CRT_FLAG_RELEASE}") # /IGNORE:4221 - Ignore empty compilation units - set(CMAKE_STATIC_LINKER_FLAGS "/IGNORE:4221") + set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /IGNORE:4221") # /DYNAMICBASE - Address space load randomization (ASLR) # /NXCOMPAT - Data execution prevention (DEP) diff --git a/cmake/FindHTTPParser.cmake b/cmake/FindHTTP_Parser.cmake similarity index 100% rename from cmake/FindHTTPParser.cmake rename to cmake/FindHTTP_Parser.cmake diff --git a/cmake/FindIconv.cmake b/cmake/FindIntlIconv.cmake similarity index 86% rename from cmake/FindIconv.cmake rename to cmake/FindIntlIconv.cmake index 9e6ded99..07959ca1 100644 --- a/cmake/FindIconv.cmake +++ b/cmake/FindIntlIconv.cmake @@ -15,6 +15,12 @@ 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) +# workaround the iOS issue where iconv is provided by libc +# We set it to false to force it add -liconv to the linker flags +if(CMAKE_SYSTEM_NAME MATCHES "iOS") + set(libc_has_iconv FALSE) +endif() + if(ICONV_INCLUDE_DIR AND libc_has_iconv) set(ICONV_FOUND TRUE) set(ICONV_LIBRARIES "") diff --git a/cmake/FindLLHTTP.cmake b/cmake/FindLLHTTP.cmake new file mode 100644 index 00000000..a87d335d --- /dev/null +++ b/cmake/FindLLHTTP.cmake @@ -0,0 +1,39 @@ +# - Try to find llhttp +# +# Defines the following variables: +# +# LLHTTP_FOUND - system has llhttp +# LLHTTP_INCLUDE_DIR - the llhttp include directory +# LLHTTP_LIBRARIES - Link these to use llhttp +# LLHTTP_VERSION_MAJOR - major version +# LLHTTP_VERSION_MINOR - minor version +# LLHTTP_VERSION_STRING - the version of llhttp found + +# Find the header and library +find_path(LLHTTP_INCLUDE_DIR NAMES llhttp.h) +find_library(LLHTTP_LIBRARY NAMES llhttp libllhttp) + +# Found the header, read version +if(LLHTTP_INCLUDE_DIR AND EXISTS "${LLHTTP_INCLUDE_DIR}/llhttp.h") + file(READ "${LLHTTP_INCLUDE_DIR}/llhttp.h" LLHTTP_H) + if(LLHTTP_H) + string(REGEX REPLACE ".*#define[\t ]+LLHTTP_VERSION_MAJOR[\t ]+([0-9]+).*" "\\1" LLHTTP_VERSION_MAJOR "${LLHTTP_H}") + string(REGEX REPLACE ".*#define[\t ]+LLHTTP_VERSION_MINOR[\t ]+([0-9]+).*" "\\1" LLHTTP_VERSION_MINOR "${LLHTTP_H}") + set(LLHTTP_VERSION_STRING "${LLHTTP_VERSION_MAJOR}.${LLHTTP_VERSION_MINOR}") + endif() + unset(LLHTTP_H) +endif() + +# Handle the QUIETLY and REQUIRED arguments and set LLHTTP_FOUND +# to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LLHTTP REQUIRED_VARS LLHTTP_INCLUDE_DIR LLHTTP_LIBRARY) + +# Hide advanced variables +mark_as_advanced(LLHTTP_INCLUDE_DIR LLHTTP_LIBRARY) + +# Set standard variables +if(LLHTTP_FOUND) + set(LLHTTP_LIBRARIES ${LLHTTP_LIBRARY}) + set(LLHTTP_INCLUDE_DIRS ${LLHTTP_INCLUDE_DIR}) +endif() diff --git a/cmake/SelectGSSAPI.cmake b/cmake/SelectGSSAPI.cmake index 5bde1169..829850a4 100644 --- a/cmake/SelectGSSAPI.cmake +++ b/cmake/SelectGSSAPI.cmake @@ -2,7 +2,7 @@ include(SanitizeBool) # We try to find any packages our backends might use find_package(GSSAPI) -if(CMAKE_SYSTEM_NAME MATCHES "Darwin") +if(CMAKE_SYSTEM_NAME MATCHES "Darwin" OR CMAKE_SYSTEM_NAME MATCHES "iOS") include(FindGSSFramework) endif() diff --git a/cmake/SelectHTTPParser.cmake b/cmake/SelectHTTPParser.cmake index 955aea33..e547e2d0 100644 --- a/cmake/SelectHTTPParser.cmake +++ b/cmake/SelectHTTPParser.cmake @@ -1,19 +1,34 @@ # Optional external dependency: http-parser -if(USE_HTTP_PARSER STREQUAL "system") - find_package(HTTPParser) +if(USE_HTTP_PARSER STREQUAL "http-parser" OR USE_HTTP_PARSER STREQUAL "system") + find_package(HTTP_Parser) if(HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) list(APPEND LIBGIT2_SYSTEM_LIBS ${HTTP_PARSER_LIBRARIES}) list(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") - add_feature_info(http-parser ON "http-parser support (system)") + set(GIT_HTTPPARSER_HTTPPARSER 1) + add_feature_info(http-parser ON "using http-parser (system)") else() message(FATAL_ERROR "http-parser support was requested but not found") endif() +elseif(USE_HTTP_PARSER STREQUAL "llhttp") + find_package(LLHTTP) + + if(LLHTTP_FOUND AND LLHTTP_VERSION_MAJOR EQUAL 9) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${LLHTTP_INCLUDE_DIRS}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${LLHTTP_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS "-lllhttp") + set(GIT_HTTPPARSER_LLHTTP 1) + add_feature_info(http-parser ON "using llhttp (system)") + else() + message(FATAL_ERROR "llhttp support was requested but not found") + endif() +elseif(USE_HTTP_PARSER STREQUAL "" OR USE_HTTP_PARSER STREQUAL "builtin") + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/llhttp" "${PROJECT_BINARY_DIR}/deps/llhttp") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/llhttp") + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") + set(GIT_HTTPPARSER_BUILTIN 1) + add_feature_info(http-parser ON "using bundled parser") else() - message(STATUS "http-parser version 2 was not found or disabled; using bundled 3rd-party sources.") - add_subdirectory("${PROJECT_SOURCE_DIR}/deps/http-parser" "${PROJECT_BINARY_DIR}/deps/http-parser") - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/http-parser") - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") - add_feature_info(http-parser ON "http-parser support (bundled)") + message(FATAL_ERROR "unknown http-parser: ${USE_HTTP_PARSER}") endif() diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index d1494164..0316b3a1 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -3,14 +3,19 @@ include(SanitizeBool) # We try to find any packages our backends might use find_package(OpenSSL) find_package(mbedTLS) -if(CMAKE_SYSTEM_NAME MATCHES "Darwin") +if(CMAKE_SYSTEM_NAME MATCHES "Darwin" OR CMAKE_SYSTEM_NAME MATCHES "iOS") find_package(Security) find_package(CoreFoundation) endif() +if(USE_HTTPS STREQUAL "") + set(USE_HTTPS ON) +endif() + +sanitizebool(USE_HTTPS) + if(USE_HTTPS) # Auto-select TLS backend - sanitizebool(USE_HTTPS) if(USE_HTTPS STREQUAL ON) if(SECURITY_FOUND) if(SECURITY_HAS_SSLCREATECONTEXT) @@ -55,6 +60,10 @@ if(USE_HTTPS) set(GIT_OPENSSL 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES}) + # Static OpenSSL (lib crypto.a) requires libdl, include it explicitly + if(LINK_WITH_STATIC_LIBRARIES STREQUAL ON) + list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_DL_LIBS}) + endif() list(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) list(APPEND LIBGIT2_PC_REQUIRES "openssl") elseif(USE_HTTPS STREQUAL "mbedTLS") @@ -109,8 +118,8 @@ if(USE_HTTPS) elseif(USE_HTTPS STREQUAL "Schannel") set(GIT_SCHANNEL 1) - list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") - list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") elseif(USE_HTTPS STREQUAL "WinHTTP") set(GIT_WINHTTP 1) @@ -125,19 +134,19 @@ if(USE_HTTPS) list(APPEND LIBGIT2_PC_LIBS "-lwinhttp") endif() - list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") - list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic") set(GIT_OPENSSL 1) set(GIT_OPENSSL_DYNAMIC 1) list(APPEND LIBGIT2_SYSTEM_LIBS dl) else() - message(FATAL_ERROR "Asked for backend ${USE_HTTPS} but it wasn't found") + message(FATAL_ERROR "unknown HTTPS backend: ${USE_HTTPS}") endif() set(GIT_HTTPS 1) add_feature_info(HTTPS GIT_HTTPS "using ${USE_HTTPS}") else() set(GIT_HTTPS 0) - add_feature_info(HTTPS NO "") + add_feature_info(HTTPS NO "HTTPS support is disabled") endif() diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake index 5c007e58..35fea3ec 100644 --- a/cmake/SelectHashes.cmake +++ b/cmake/SelectHashes.cmake @@ -2,13 +2,12 @@ include(SanitizeBool) -# USE_SHA1=CollisionDetection(ON)/HTTPS/Generic/OFF sanitizebool(USE_SHA1) sanitizebool(USE_SHA256) # sha1 -if(USE_SHA1 STREQUAL ON) +if(USE_SHA1 STREQUAL "" OR USE_SHA1 STREQUAL ON) SET(USE_SHA1 "CollisionDetection") elseif(USE_SHA1 STREQUAL "HTTPS") if(USE_HTTPS STREQUAL "SecureTransport") @@ -20,7 +19,7 @@ elseif(USE_SHA1 STREQUAL "HTTPS") elseif(USE_HTTPS) set(USE_SHA1 ${USE_HTTPS}) else() - set(USE_SHA1 "CollisionDetection") + message(FATAL_ERROR "asked for HTTPS SHA1 backend but HTTPS is not enabled") endif() endif() @@ -28,6 +27,8 @@ if(USE_SHA1 STREQUAL "CollisionDetection") set(GIT_SHA1_COLLISIONDETECT 1) elseif(USE_SHA1 STREQUAL "OpenSSL") set(GIT_SHA1_OPENSSL 1) +elseif(USE_SHA1 STREQUAL "OpenSSL-FIPS") + set(GIT_SHA1_OPENSSL_FIPS 1) elseif(USE_SHA1 STREQUAL "OpenSSL-Dynamic") set(GIT_SHA1_OPENSSL 1) set(GIT_SHA1_OPENSSL_DYNAMIC 1) @@ -39,15 +40,21 @@ elseif(USE_SHA1 STREQUAL "mbedTLS") elseif(USE_SHA1 STREQUAL "Win32") set(GIT_SHA1_WIN32 1) else() - message(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}") + message(FATAL_ERROR "asked for unknown SHA1 backend: ${USE_SHA1}") endif() # sha256 -if(USE_SHA256 STREQUAL ON AND USE_HTTPS) - SET(USE_SHA256 "HTTPS") -elseif(USE_SHA256 STREQUAL ON) - SET(USE_SHA256 "Builtin") +if(USE_SHA256 STREQUAL "" OR USE_SHA256 STREQUAL ON) + if(USE_HTTPS) + SET(USE_SHA256 "HTTPS") + else() + SET(USE_SHA256 "builtin") + endif() +endif() + +if(USE_SHA256 STREQUAL "Builtin") + set(USE_SHA256 "builtin") endif() if(USE_SHA256 STREQUAL "HTTPS") @@ -62,10 +69,12 @@ if(USE_SHA256 STREQUAL "HTTPS") endif() endif() -if(USE_SHA256 STREQUAL "Builtin") +if(USE_SHA256 STREQUAL "builtin") set(GIT_SHA256_BUILTIN 1) elseif(USE_SHA256 STREQUAL "OpenSSL") set(GIT_SHA256_OPENSSL 1) +elseif(USE_SHA256 STREQUAL "OpenSSL-FIPS") + set(GIT_SHA256_OPENSSL_FIPS 1) elseif(USE_SHA256 STREQUAL "OpenSSL-Dynamic") set(GIT_SHA256_OPENSSL 1) set(GIT_SHA256_OPENSSL_DYNAMIC 1) @@ -77,11 +86,12 @@ elseif(USE_SHA256 STREQUAL "mbedTLS") elseif(USE_SHA256 STREQUAL "Win32") set(GIT_SHA256_WIN32 1) else() - message(FATAL_ERROR "Asked for unknown SHA256 backend: ${USE_SHA256}") + message(FATAL_ERROR "asked for unknown SHA256 backend: ${USE_SHA256}") endif() # add library requirements -if(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL") +if(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL" OR + USE_SHA1 STREQUAL "OpenSSL-FIPS" OR USE_SHA256 STREQUAL "OpenSSL-FIPS") if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") list(APPEND LIBGIT2_PC_LIBS "-lssl") else() @@ -102,3 +112,10 @@ endif() add_feature_info(SHA1 ON "using ${USE_SHA1}") add_feature_info(SHA256 ON "using ${USE_SHA256}") + +# warn for users who do not use sha1dc + +if(NOT "${USE_SHA1}" STREQUAL "CollisionDetection") + list(APPEND WARNINGS "SHA1 support is set to ${USE_SHA1} which is not recommended - git's hash algorithm is sha1dc, it is *not* SHA1. Using SHA1 may leave you and your users susceptible to SHAttered-style attacks.") + set(WARNINGS ${WARNINGS} PARENT_SCOPE) +endif() diff --git a/cmake/SelectRegex.cmake b/cmake/SelectRegex.cmake index 2a3a91b8..840ec7f3 100644 --- a/cmake/SelectRegex.cmake +++ b/cmake/SelectRegex.cmake @@ -5,7 +5,12 @@ if(REGEX_BACKEND STREQUAL "") check_symbol_exists(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L) if(HAVE_REGCOMP_L) - set(REGEX_BACKEND "regcomp_l") + # 'regcomp_l' has been explicitly marked unavailable on iOS_SDK + if(CMAKE_SYSTEM_NAME MATCHES "iOS") + set(REGEX_BACKEND "regcomp") + else() + set(REGEX_BACKEND "regcomp_l") + endif() elseif(PCRE_FOUND) set(REGEX_BACKEND "pcre") else() diff --git a/cmake/SelectSSH.cmake b/cmake/SelectSSH.cmake index 23dfc978..1a4032b8 100644 --- a/cmake/SelectSSH.cmake +++ b/cmake/SelectSSH.cmake @@ -1,6 +1,11 @@ -# Optional external dependency: libssh2 -if(USE_SSH) +if(USE_SSH STREQUAL "exec") + set(GIT_SSH 1) + set(GIT_SSH_EXEC 1) + + add_feature_info(SSH ON "using OpenSSH exec support") +elseif(USE_SSH STREQUAL ON OR USE_SSH STREQUAL "libssh2") find_pkglibraries(LIBSSH2 libssh2) + if(NOT LIBSSH2_FOUND) find_package(LibSSH2) set(LIBSSH2_INCLUDE_DIRS ${LIBSSH2_INCLUDE_DIR}) @@ -12,30 +17,30 @@ if(USE_SSH) if(NOT LIBSSH2_FOUND) message(FATAL_ERROR "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") endif() -endif() -if(LIBSSH2_FOUND) - set(GIT_SSH 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${LIBSSH2_INCLUDE_DIRS}) list(APPEND LIBGIT2_SYSTEM_LIBS ${LIBSSH2_LIBRARIES}) list(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS}) check_library_exists("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS) if(HAVE_LIBSSH2_MEMORY_CREDENTIALS) - set(GIT_SSH_MEMORY_CREDENTIALS 1) + set(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1) endif() -else() - message(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") -endif() -if(WIN32 AND EMBED_SSH_PATH) - file(GLOB SSH_SRC "${EMBED_SSH_PATH}/src/*.c") - list(SORT SSH_SRC) - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS ${SSH_SRC}) + if(WIN32 AND EMBED_SSH_PATH) + file(GLOB SSH_SRC "${EMBED_SSH_PATH}/src/*.c") + list(SORT SSH_SRC) + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS ${SSH_SRC}) + + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${EMBED_SSH_PATH}/include") + file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") + endif() - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${EMBED_SSH_PATH}/include") - file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") set(GIT_SSH 1) + set(GIT_SSH_LIBSSH2 1) + add_feature_info(SSH ON "using libssh2") +elseif(USE_SSH STREQUAL OFF OR USE_SSH STREQUAL "") + add_feature_info(SSH OFF "SSH transport support") +else() + message(FATAL_ERROR "unknown SSH option: ${USE_SSH}") endif() - -add_feature_info(SSH GIT_SSH "SSH transport support") diff --git a/cmake/SelectZlib.cmake b/cmake/SelectZlib.cmake index fb4361ab..25c7b2f9 100644 --- a/cmake/SelectZlib.cmake +++ b/cmake/SelectZlib.cmake @@ -4,6 +4,7 @@ include(SanitizeBool) SanitizeBool(USE_BUNDLED_ZLIB) if(USE_BUNDLED_ZLIB STREQUAL ON) set(USE_BUNDLED_ZLIB "Bundled") + set(GIT_COMPRESSION_BUILTIN) endif() if(USE_BUNDLED_ZLIB STREQUAL "OFF") @@ -17,6 +18,7 @@ if(USE_BUNDLED_ZLIB STREQUAL "OFF") list(APPEND LIBGIT2_PC_REQUIRES "zlib") endif() add_feature_info(zlib ON "using system zlib") + set(GIT_COMPRESSION_ZLIB 1) else() message(STATUS "zlib was not found; using bundled 3rd-party sources." ) endif() @@ -26,9 +28,11 @@ if(USE_BUNDLED_ZLIB STREQUAL "Chromium") list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/chromium-zlib") list(APPEND LIBGIT2_DEPENDENCY_OBJECTS $) add_feature_info(zlib ON "using (Chromium) bundled zlib") + set(GIT_COMPRESSION_BUILTIN 1) elseif(USE_BUNDLED_ZLIB OR NOT ZLIB_FOUND) add_subdirectory("${PROJECT_SOURCE_DIR}/deps/zlib" "${PROJECT_BINARY_DIR}/deps/zlib") list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/zlib") list(APPEND LIBGIT2_DEPENDENCY_OBJECTS $) add_feature_info(zlib ON "using bundled zlib") + set(GIT_COMPRESSION_BUILTIN 1) endif() diff --git a/debian/changelog b/debian/changelog index 24174a95..1d8e4d1f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,138 @@ +libgit2 (1.9.2+ds-6) unstable; urgency=medium + + * Use versioned symbols for libgit2-experimental + + -- Timo Röhling Thu, 12 Feb 2026 15:43:54 +0100 + +libgit2 (1.9.2+ds-5) unstable; urgency=medium + + * Fix FTBFS for binary:all builds (Closes: #1126146) + + -- Timo Röhling Thu, 22 Jan 2026 13:15:54 +0100 + +libgit2 (1.9.2+ds-4) unstable; urgency=medium + + * Switch to llhttp as HTTP parser backend (Closes: #1125517) + + -- Timo Röhling Wed, 21 Jan 2026 18:33:15 +0100 + +libgit2 (1.9.2+ds-3) experimental; urgency=medium + + * Enable experimental SHA-256 support (Closes: #1120588) + * Mark autopkgtest as superficial + + -- Timo Röhling Fri, 02 Jan 2026 19:57:09 +0100 + +libgit2 (1.9.2+ds-2) unstable; urgency=medium + + * Source only upload. + + -- Timo Röhling Tue, 30 Dec 2025 11:35:58 +0100 + +libgit2 (1.9.2+ds-1) unstable; urgency=medium + + * Migrate debian/watch to version 5 format + * New upstream version 1.9.2+ds + - Fix repository name quoting to prevent SSH arbitrary command + execution by rogue Git server + - Fix potential buffer overflow in SSH credential creation that + could be triggered if the public key is not NUL-terminated. + * Bump Standards-Version to 4.7.3 + + -- Timo Röhling Tue, 30 Dec 2025 11:17:24 +0100 + +libgit2 (1.9.1+ds-1) unstable; urgency=medium + + * New upstream version 1.9.1+ds + * Update patches. + Merged: Use-Multi-Arch-destination-for-CMake-config.patch + + -- Timo Röhling Wed, 13 Aug 2025 14:27:15 +0200 + +libgit2 (1.9.0+ds-2) unstable; urgency=medium + + * Upload to unstable. + * Bump Standards-Version to 4.7.2 + * Replace old FSF address by weblink in d/copyright + + -- Timo Röhling Thu, 03 Apr 2025 17:28:22 +0200 + +libgit2 (1.9.0+ds-1) experimental; urgency=medium + + * New upstream version 1.9.0+ds + (Closes: #1092215) + * Drop Debian patch for mbedTLS default cipher list (fixed upstream) + * Wrap and sort Debian package files + * Bump SONAME to 1.9 + * Install new CMake config + * Update d/copyright + + -- Timo Röhling Sun, 12 Jan 2025 00:39:53 +0100 + +libgit2 (1.8.4+ds-3) unstable; urgency=medium + + * Use mbedTLS default cipher list for TLS handshake (Closes: #1088281) + + -- Timo Röhling Wed, 27 Nov 2024 23:26:26 +0100 + +libgit2 (1.8.4+ds-2) unstable; urgency=medium + + * Bump cmake_minimum_required() for autopkgtest (Closes: #1087860) + + -- Timo Röhling Thu, 21 Nov 2024 11:46:53 +0100 + +libgit2 (1.8.4+ds-1) unstable; urgency=medium + + * Unvendor llhttp again + * New upstream version 1.8.4+ds + * Print the file header on GIT_DIFF_FORMAT_PATCH_HEADER (Closes: #1086494) + * Move back to http-parser again + + -- Timo Röhling Sun, 03 Nov 2024 01:47:58 +0100 + +libgit2 (1.8.2~rc1+ds2-1) unstable; urgency=medium + + * Keep vendored llhttp for ports architectures + * New upstream version 1.8.2~rc1+ds2 + * Update d/copyright + * Use vendored llhttp on architectures without nodejs (Closes: #1084223) + + -- Timo Röhling Tue, 08 Oct 2024 22:45:48 +0200 + +libgit2 (1.8.2~rc1+ds-2) unstable; urgency=medium + + * Upload to unstable. + + -- Timo Röhling Sat, 05 Oct 2024 01:02:23 +0200 + +libgit2 (1.8.2~rc1+ds-1) experimental; urgency=medium + + * New upstream version 1.8.2~rc1+ds + - Revert premature const correctness change to + git_commit_create() and git_commit_create_buffer() + + -- Timo Röhling Sun, 15 Sep 2024 09:52:48 +0200 + +libgit2 (1.8.1+ds-2) experimental; urgency=medium + + * Fix version ordering for -rc releases in d/watch + * Wrap and sort Debian package files + * Bump Standards-Version to 4.7.0 + * Move back to http-parser from llhttp (Closes: #1081534) + + -- Timo Röhling Thu, 12 Sep 2024 16:04:48 +0200 + +libgit2 (1.8.1+ds-1) experimental; urgency=medium + + * Update Files-Excluded for newly vendored HTTP parser + * New upstream version 1.8.1+ds + * Update patches + - Drop handle-bashim.patch (merged upstream) + * Bump SONAME to 1.8 + * Migrate from http-parser to llhttp + + -- Timo Röhling Mon, 20 May 2024 11:21:07 +0200 + libgit2 (1.7.2+ds-1) unstable; urgency=medium * New upstream version 1.7.2+ds diff --git a/debian/clean b/debian/clean index c30105cb..69a4b14c 100644 --- a/debian/clean +++ b/debian/clean @@ -1,2 +1,5 @@ -build-debian-devel/ -build-debian-release/ +build-debian-shared-experimental/ +build-debian-shared/ +build-debian-static-experimental/ +build-debian-static/ +debian/tmp-experimental/ diff --git a/debian/control b/debian/control index bf48304b..74541b2e 100644 --- a/debian/control +++ b/debian/control @@ -2,65 +2,103 @@ Source: libgit2 Section: libs Priority: optional Maintainer: Utkarsh Gupta -Uploaders: Pirate Praveen , - Mohammed Bilal , - Timo Röhling , -Build-Depends: debhelper-compat (= 13), - python3-minimal:any, - pkgconf, - ca-certificates, - cmake, - zlib1g-dev, - libmbedtls-dev, - libssh2-1-dev, - libhttp-parser-dev, - libpcre2-dev, - libkrb5-dev, - ca-certificates -Standards-Version: 4.6.2 +Uploaders: + Pirate Praveen , + Mohammed Bilal , + Timo Röhling , +Build-Depends: + debhelper-compat (= 13), + ca-certificates, + cmake, + libllhttp-dev, + libkrb5-dev, + libmbedtls-dev, + libpcre2-dev, + libssh2-1-dev, + pkgconf, + python3-minimal:any, + zlib1g-dev, +Standards-Version: 4.7.3 Homepage: https://libgit2.github.com/ Vcs-Git: https://salsa.debian.org/debian/libgit2.git Vcs-Browser: https://salsa.debian.org/debian/libgit2 Rules-Requires-Root: no +Description: low-level Git library + libgit2 is a portable, pure C implementation of the Git + distributed version control system core methods provided as a + re-entrant link-able library with a solid API. Package: libgit2-dev Architecture: any Section: libdevel Multi-Arch: same -Depends: libgit2-1.7 (= ${binary:Version}), - zlib1g-dev, - libmbedtls-dev, - libssh2-1-dev, - libhttp-parser-dev, - libpcre2-dev, - ${misc:Depends}, -Description: low-level Git library (development files) - libgit2 is a portable, pure C implementation of the Git - distributed version control system core methods provided as a - re-entrant link-able library with a solid API. +Depends: + libgit2-1.9 (= ${binary:Version}), + libhttp-parser-dev, + libmbedtls-dev, + libpcre2-dev, + libssh2-1-dev, + zlib1g-dev, + ${misc:Depends}, +Description: ${source:Synopsis} (development files) + ${source:Extended-Description} . This package contains the development files for libgit2. -Package: libgit2-1.7 +Package: libgit2-1.9 Architecture: any Multi-Arch: same -Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, - ${misc:Depends}, -Description: low-level Git library - libgit2 is a portable, pure C implementation of the Git - distributed version control system core methods provided as a - re-entrant link-able library with a solid API. +Pre-Depends: + ${misc:Pre-Depends}, +Depends: + ${misc:Depends}, + ${shlibs:Depends}, +Description: ${source:Synopsis} + ${source:Extended-Description} + +Package: libgit2-experimental-dev +Architecture: any +Section: libdevel +Multi-Arch: same +Conflicts: + libgit2-dev, +Depends: + libgit2-experimental1.9 (= ${binary:Version}), + libhttp-parser-dev, + libmbedtls-dev, + libpcre2-dev, + libssh2-1-dev, + zlib1g-dev, + ${misc:Depends}, +Description: ${source:Synopsis} with SHA-256 support (development files) + ${source:Extended-Description} + . + This package contains the development files for libgit2 with + experimental SHA-256 support enabled. + +Package: libgit2-experimental1.9 +Architecture: any +Multi-Arch: same +Pre-Depends: + ${misc:Pre-Depends}, +Depends: + ${misc:Depends}, + ${shlibs:Depends}, +Description: ${source:Synopsis} with SHA-256 support + ${source:Extended-Description} + . + This package contains the shared library version with + experimental SHA-256 support enabled. Package: libgit2-fixtures Architecture: all Multi-Arch: foreign -Pre-Depends: ${misc:Pre-Depends} -Depends: ${misc:Depends}, -Description: low-level Git library - test suite examples - libgit2 is a portable, pure C implementation of the Git - distributed version control system core methods provided as a - re-entrant link-able library with a solid API. +Pre-Depends: + ${misc:Pre-Depends}, +Depends: + ${misc:Depends}, +Description: ${source:Synopsis} (test suite examples) + ${source:Extended-Description} . This package provides the test examples of the library, which can be useful for other pieces of software relying on libgit2, diff --git a/debian/copyright b/debian/copyright index 34227750..86f6ca4e 100644 --- a/debian/copyright +++ b/debian/copyright @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: libgit2 Source: https://libgit2.github.com/ Files-Excluded: deps/chromium-zlib - deps/http-parser + deps/llhttp deps/ntlmclient deps/pcre deps/winhttp @@ -13,7 +13,7 @@ Comment: Remove unneeded bundled dependencies is released (and Debian packaged) separately. Files: * -Copyright: 2009-2023, the libgit2 contributors +Copyright: 2009-2025, the libgit2 contributors License: GPL-2 with linking exception, origin admission Files: examples/* @@ -48,10 +48,8 @@ Copyright: 2008, The Android Open Source Project the libgit2 contributors License: BSD-2-clause -Files: src/util/khash.h - src/util/utf8.c -Copyright: 2008-2011, Attractive Chaos - 2009, Public Software Group e. V., Berlin, Germany +Files: src/util/utf8.c +Copyright: 2009, Public Software Group e. V., Berlin, Germany the libgit2 contributors License: MIT @@ -225,9 +223,8 @@ License: LGPL-2.1+ Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301 USA. + License along with this library; if not, see + . License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/debian/libgit2-1.7.install b/debian/libgit2-1.7.install deleted file mode 100644 index 7993f874..00000000 --- a/debian/libgit2-1.7.install +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/*/lib*so.* diff --git a/debian/libgit2-1.7.symbols b/debian/libgit2-1.7.symbols deleted file mode 100644 index 45c15f11..00000000 --- a/debian/libgit2-1.7.symbols +++ /dev/null @@ -1,942 +0,0 @@ -libgit2.so.1.7 libgit2-1.7 #MINVER# -* Build-Depends-Package: libgit2-dev - git_annotated_commit_free@Base 1.7.0 - git_annotated_commit_from_fetchhead@Base 1.7.0 - git_annotated_commit_from_ref@Base 1.7.0 - git_annotated_commit_from_revspec@Base 1.7.0 - git_annotated_commit_id@Base 1.7.0 - git_annotated_commit_lookup@Base 1.7.0 - git_annotated_commit_ref@Base 1.7.0 - git_apply@Base 1.7.0 - git_apply_options_init@Base 1.7.0 - git_apply_to_tree@Base 1.7.0 - git_attr_add_macro@Base 1.7.0 - git_attr_cache_flush@Base 1.7.0 - git_attr_foreach@Base 1.7.0 - git_attr_foreach_ext@Base 1.7.0 - git_attr_get@Base 1.7.0 - git_attr_get_ext@Base 1.7.0 - git_attr_get_many@Base 1.7.0 - git_attr_get_many_ext@Base 1.7.0 - git_attr_value@Base 1.7.0 - git_blame_buffer@Base 1.7.0 - git_blame_file@Base 1.7.0 - git_blame_free@Base 1.7.0 - git_blame_get_hunk_byindex@Base 1.7.0 - git_blame_get_hunk_byline@Base 1.7.0 - git_blame_get_hunk_count@Base 1.7.0 - git_blame_init_options@Base 1.7.0 - git_blame_options_init@Base 1.7.0 - git_blob_create_from_buffer@Base 1.7.0 - git_blob_create_from_disk@Base 1.7.0 - git_blob_create_from_stream@Base 1.7.0 - git_blob_create_from_stream_commit@Base 1.7.0 - git_blob_create_from_workdir@Base 1.7.0 - git_blob_create_frombuffer@Base 1.7.0 - git_blob_create_fromdisk@Base 1.7.0 - git_blob_create_fromstream@Base 1.7.0 - git_blob_create_fromstream_commit@Base 1.7.0 - git_blob_create_fromworkdir@Base 1.7.0 - git_blob_data_is_binary@Base 1.7.0 - git_blob_dup@Base 1.7.0 - git_blob_filter@Base 1.7.0 - git_blob_filter_options_init@Base 1.7.0 - git_blob_filtered_content@Base 1.7.0 - git_blob_free@Base 1.7.0 - git_blob_id@Base 1.7.0 - git_blob_is_binary@Base 1.7.0 - git_blob_lookup@Base 1.7.0 - git_blob_lookup_prefix@Base 1.7.0 - git_blob_owner@Base 1.7.0 - git_blob_rawcontent@Base 1.7.0 - git_blob_rawsize@Base 1.7.0 - git_branch_create@Base 1.7.0 - git_branch_create_from_annotated@Base 1.7.0 - git_branch_delete@Base 1.7.0 - git_branch_is_checked_out@Base 1.7.0 - git_branch_is_head@Base 1.7.0 - git_branch_iterator_free@Base 1.7.0 - git_branch_iterator_new@Base 1.7.0 - git_branch_lookup@Base 1.7.0 - git_branch_move@Base 1.7.0 - git_branch_name@Base 1.7.0 - git_branch_name_is_valid@Base 1.7.0 - git_branch_next@Base 1.7.0 - git_branch_remote_name@Base 1.7.0 - git_branch_set_upstream@Base 1.7.0 - git_branch_upstream@Base 1.7.0 - git_branch_upstream_merge@Base 1.7.0 - git_branch_upstream_name@Base 1.7.0 - git_branch_upstream_remote@Base 1.7.0 - git_buf_contains_nul@Base 1.7.0 - git_buf_dispose@Base 1.7.0 - git_buf_free@Base 1.7.0 - git_buf_grow@Base 1.7.0 - git_buf_is_binary@Base 1.7.0 - git_buf_set@Base 1.7.0 - git_checkout_head@Base 1.7.0 - git_checkout_index@Base 1.7.0 - git_checkout_init_options@Base 1.7.0 - git_checkout_options_init@Base 1.7.0 - git_checkout_tree@Base 1.7.0 - git_cherrypick@Base 1.7.0 - git_cherrypick_commit@Base 1.7.0 - git_cherrypick_init_options@Base 1.7.0 - git_cherrypick_options_init@Base 1.7.0 - git_clone@Base 1.7.0 - git_clone_init_options@Base 1.7.0 - git_clone_options_init@Base 1.7.0 - git_commit_amend@Base 1.7.0 - git_commit_author@Base 1.7.0 - git_commit_author_with_mailmap@Base 1.7.0 - git_commit_body@Base 1.7.0 - git_commit_committer@Base 1.7.0 - git_commit_committer_with_mailmap@Base 1.7.0 - git_commit_create@Base 1.7.0 - git_commit_create_buffer@Base 1.7.0 - git_commit_create_from_callback@Base 1.7.0 - git_commit_create_from_ids@Base 1.7.0 - git_commit_create_v@Base 1.7.0 - git_commit_create_with_signature@Base 1.7.0 - git_commit_dup@Base 1.7.0 - git_commit_extract_signature@Base 1.7.0 - git_commit_free@Base 1.7.0 - git_commit_graph_free@Base 1.7.0 - git_commit_graph_open@Base 1.7.0 - git_commit_graph_writer_add_index_file@Base 1.7.0 - git_commit_graph_writer_add_revwalk@Base 1.7.0 - git_commit_graph_writer_commit@Base 1.7.0 - git_commit_graph_writer_dump@Base 1.7.0 - git_commit_graph_writer_free@Base 1.7.0 - git_commit_graph_writer_new@Base 1.7.0 - git_commit_graph_writer_options_init@Base 1.7.0 - git_commit_header_field@Base 1.7.0 - git_commit_id@Base 1.7.0 - git_commit_lookup@Base 1.7.0 - git_commit_lookup_prefix@Base 1.7.0 - git_commit_message@Base 1.7.0 - git_commit_message_encoding@Base 1.7.0 - git_commit_message_raw@Base 1.7.0 - git_commit_nth_gen_ancestor@Base 1.7.0 - git_commit_owner@Base 1.7.0 - git_commit_parent@Base 1.7.0 - git_commit_parent_id@Base 1.7.0 - git_commit_parentcount@Base 1.7.0 - git_commit_raw_header@Base 1.7.0 - git_commit_summary@Base 1.7.0 - git_commit_time@Base 1.7.0 - git_commit_time_offset@Base 1.7.0 - git_commit_tree@Base 1.7.0 - git_commit_tree_id@Base 1.7.0 - git_config_add_backend@Base 1.7.0 - git_config_add_file_ondisk@Base 1.7.0 - git_config_backend_foreach_match@Base 1.7.0 - git_config_delete_entry@Base 1.7.0 - git_config_delete_multivar@Base 1.7.0 - git_config_entry_free@Base 1.7.0 - git_config_find_global@Base 1.7.0 - git_config_find_programdata@Base 1.7.0 - git_config_find_system@Base 1.7.0 - git_config_find_xdg@Base 1.7.0 - git_config_foreach@Base 1.7.0 - git_config_foreach_match@Base 1.7.0 - git_config_free@Base 1.7.0 - git_config_get_bool@Base 1.7.0 - git_config_get_entry@Base 1.7.0 - git_config_get_int32@Base 1.7.0 - git_config_get_int64@Base 1.7.0 - git_config_get_mapped@Base 1.7.0 - git_config_get_multivar_foreach@Base 1.7.0 - git_config_get_path@Base 1.7.0 - git_config_get_string@Base 1.7.0 - git_config_get_string_buf@Base 1.7.0 - git_config_init_backend@Base 1.7.0 - git_config_iterator_free@Base 1.7.0 - git_config_iterator_glob_new@Base 1.7.0 - git_config_iterator_new@Base 1.7.0 - git_config_lock@Base 1.7.0 - git_config_lookup_map_value@Base 1.7.0 - git_config_multivar_iterator_new@Base 1.7.0 - git_config_new@Base 1.7.0 - git_config_next@Base 1.7.0 - git_config_open_default@Base 1.7.0 - git_config_open_global@Base 1.7.0 - git_config_open_level@Base 1.7.0 - git_config_open_ondisk@Base 1.7.0 - git_config_parse_bool@Base 1.7.0 - git_config_parse_int32@Base 1.7.0 - git_config_parse_int64@Base 1.7.0 - git_config_parse_path@Base 1.7.0 - git_config_set_bool@Base 1.7.0 - git_config_set_int32@Base 1.7.0 - git_config_set_int64@Base 1.7.0 - git_config_set_multivar@Base 1.7.0 - git_config_set_string@Base 1.7.0 - git_config_snapshot@Base 1.7.0 - git_config_unlock@Base 1.7.0 - git_cred_default_new@Base 1.7.0 - git_cred_free@Base 1.7.0 - git_cred_get_username@Base 1.7.0 - git_cred_has_username@Base 1.7.0 - git_cred_ssh_custom_new@Base 1.7.0 - git_cred_ssh_interactive_new@Base 1.7.0 - git_cred_ssh_key_from_agent@Base 1.7.0 - git_cred_ssh_key_memory_new@Base 1.7.0 - git_cred_ssh_key_new@Base 1.7.0 - git_cred_username_new@Base 1.7.0 - git_cred_userpass@Base 1.7.0 - git_cred_userpass_plaintext_new@Base 1.7.0 - git_credential_default_new@Base 1.7.0 - git_credential_free@Base 1.7.0 - git_credential_get_username@Base 1.7.0 - git_credential_has_username@Base 1.7.0 - git_credential_ssh_custom_new@Base 1.7.0 - git_credential_ssh_interactive_new@Base 1.7.0 - git_credential_ssh_key_from_agent@Base 1.7.0 - git_credential_ssh_key_memory_new@Base 1.7.0 - git_credential_ssh_key_new@Base 1.7.0 - git_credential_username_new@Base 1.7.0 - git_credential_userpass@Base 1.7.0 - git_credential_userpass_plaintext_new@Base 1.7.0 - git_describe_commit@Base 1.7.0 - git_describe_format@Base 1.7.0 - git_describe_format_options_init@Base 1.7.0 - git_describe_init_format_options@Base 1.7.0 - git_describe_init_options@Base 1.7.0 - git_describe_options_init@Base 1.7.0 - git_describe_result_free@Base 1.7.0 - git_describe_workdir@Base 1.7.0 - git_diff_blob_to_buffer@Base 1.7.0 - git_diff_blobs@Base 1.7.0 - git_diff_buffers@Base 1.7.0 - git_diff_commit_as_email@Base 1.7.0 - git_diff_find_init_options@Base 1.7.0 - git_diff_find_options_init@Base 1.7.0 - git_diff_find_similar@Base 1.7.0 - git_diff_foreach@Base 1.7.0 - git_diff_format_email@Base 1.7.0 - git_diff_format_email_init_options@Base 1.7.0 - git_diff_format_email_options_init@Base 1.7.0 - git_diff_free@Base 1.7.0 - git_diff_from_buffer@Base 1.7.0 - git_diff_get_delta@Base 1.7.0 - git_diff_get_perfdata@Base 1.7.0 - git_diff_get_stats@Base 1.7.0 - git_diff_index_to_index@Base 1.7.0 - git_diff_index_to_workdir@Base 1.7.0 - git_diff_init_options@Base 1.7.0 - git_diff_is_sorted_icase@Base 1.7.0 - git_diff_merge@Base 1.7.0 - git_diff_num_deltas@Base 1.7.0 - git_diff_num_deltas_of_type@Base 1.7.0 - git_diff_options_init@Base 1.7.0 - git_diff_patchid@Base 1.7.0 - git_diff_patchid_options_init@Base 1.7.0 - git_diff_print@Base 1.7.0 - git_diff_print_callback__to_buf@Base 1.7.0 - git_diff_print_callback__to_file_handle@Base 1.7.0 - git_diff_stats_deletions@Base 1.7.0 - git_diff_stats_files_changed@Base 1.7.0 - git_diff_stats_free@Base 1.7.0 - git_diff_stats_insertions@Base 1.7.0 - git_diff_stats_to_buf@Base 1.7.0 - git_diff_status_char@Base 1.7.0 - git_diff_to_buf@Base 1.7.0 - git_diff_tree_to_index@Base 1.7.0 - git_diff_tree_to_tree@Base 1.7.0 - git_diff_tree_to_workdir@Base 1.7.0 - git_diff_tree_to_workdir_with_index@Base 1.7.0 - git_email_create_from_commit@Base 1.7.0 - git_email_create_from_diff@Base 1.7.0 - git_error_clear@Base 1.7.0 - git_error_last@Base 1.7.0 - git_error_set@Base 1.7.0 - git_error_set_oom@Base 1.7.0 - git_error_set_str@Base 1.7.0 - git_fetch_init_options@Base 1.7.0 - git_fetch_options_init@Base 1.7.0 - git_filter_init@Base 1.7.0 - git_filter_list_apply_to_blob@Base 1.7.0 - git_filter_list_apply_to_buffer@Base 1.7.0 - git_filter_list_apply_to_data@Base 1.7.0 - git_filter_list_apply_to_file@Base 1.7.0 - git_filter_list_contains@Base 1.7.0 - git_filter_list_free@Base 1.7.0 - git_filter_list_length@Base 1.7.0 - git_filter_list_load@Base 1.7.0 - git_filter_list_load_ext@Base 1.7.0 - git_filter_list_new@Base 1.7.0 - git_filter_list_push@Base 1.7.0 - git_filter_list_stream_blob@Base 1.7.0 - git_filter_list_stream_buffer@Base 1.7.0 - git_filter_list_stream_data@Base 1.7.0 - git_filter_list_stream_file@Base 1.7.0 - git_filter_lookup@Base 1.7.0 - git_filter_register@Base 1.7.0 - git_filter_source_filemode@Base 1.7.0 - git_filter_source_flags@Base 1.7.0 - git_filter_source_id@Base 1.7.0 - git_filter_source_mode@Base 1.7.0 - git_filter_source_path@Base 1.7.0 - git_filter_source_repo@Base 1.7.0 - git_filter_unregister@Base 1.7.0 - git_graph_ahead_behind@Base 1.7.0 - git_graph_descendant_of@Base 1.7.0 - git_graph_reachable_from_any@Base 1.7.0 - git_hashsig_compare@Base 1.7.0 - git_hashsig_create@Base 1.7.0 - git_hashsig_create_fromfile@Base 1.7.0 - git_hashsig_free@Base 1.7.0 - git_ignore_add_rule@Base 1.7.0 - git_ignore_clear_internal_rules@Base 1.7.0 - git_ignore_path_is_ignored@Base 1.7.0 - git_index_add@Base 1.7.0 - git_index_add_all@Base 1.7.0 - git_index_add_bypath@Base 1.7.0 - git_index_add_from_buffer@Base 1.7.0 - git_index_add_frombuffer@Base 1.7.0 - git_index_caps@Base 1.7.0 - git_index_checksum@Base 1.7.0 - git_index_clear@Base 1.7.0 - git_index_conflict_add@Base 1.7.0 - git_index_conflict_cleanup@Base 1.7.0 - git_index_conflict_get@Base 1.7.0 - git_index_conflict_iterator_free@Base 1.7.0 - git_index_conflict_iterator_new@Base 1.7.0 - git_index_conflict_next@Base 1.7.0 - git_index_conflict_remove@Base 1.7.0 - git_index_entry_is_conflict@Base 1.7.0 - git_index_entry_stage@Base 1.7.0 - git_index_entrycount@Base 1.7.0 - git_index_find@Base 1.7.0 - git_index_find_prefix@Base 1.7.0 - git_index_free@Base 1.7.0 - git_index_get_byindex@Base 1.7.0 - git_index_get_bypath@Base 1.7.0 - git_index_has_conflicts@Base 1.7.0 - git_index_iterator_free@Base 1.7.0 - git_index_iterator_new@Base 1.7.0 - git_index_iterator_next@Base 1.7.0 - git_index_name_add@Base 1.7.0 - git_index_name_clear@Base 1.7.0 - git_index_name_entrycount@Base 1.7.0 - git_index_name_get_byindex@Base 1.7.0 - git_index_new@Base 1.7.0 - git_index_open@Base 1.7.0 - git_index_owner@Base 1.7.0 - git_index_path@Base 1.7.0 - git_index_read@Base 1.7.0 - git_index_read_tree@Base 1.7.0 - git_index_remove@Base 1.7.0 - git_index_remove_all@Base 1.7.0 - git_index_remove_bypath@Base 1.7.0 - git_index_remove_directory@Base 1.7.0 - git_index_reuc_add@Base 1.7.0 - git_index_reuc_clear@Base 1.7.0 - git_index_reuc_entrycount@Base 1.7.0 - git_index_reuc_find@Base 1.7.0 - git_index_reuc_get_byindex@Base 1.7.0 - git_index_reuc_get_bypath@Base 1.7.0 - git_index_reuc_remove@Base 1.7.0 - git_index_set_caps@Base 1.7.0 - git_index_set_version@Base 1.7.0 - git_index_update_all@Base 1.7.0 - git_index_version@Base 1.7.0 - git_index_write@Base 1.7.0 - git_index_write_tree@Base 1.7.0 - git_index_write_tree_to@Base 1.7.0 - git_indexer_append@Base 1.7.0 - git_indexer_commit@Base 1.7.0 - git_indexer_free@Base 1.7.0 - git_indexer_hash@Base 1.7.0 - git_indexer_init_options@Base 1.7.0 - git_indexer_name@Base 1.7.0 - git_indexer_new@Base 1.7.0 - git_indexer_options_init@Base 1.7.0 - git_libgit2_features@Base 1.7.0 - git_libgit2_init@Base 1.7.0 - git_libgit2_opts@Base 1.7.0 - git_libgit2_prerelease@Base 1.7.0 - git_libgit2_shutdown@Base 1.7.0 - git_libgit2_version@Base 1.7.0 - git_mailmap_add_entry@Base 1.7.0 - git_mailmap_free@Base 1.7.0 - git_mailmap_from_buffer@Base 1.7.0 - git_mailmap_from_repository@Base 1.7.0 - git_mailmap_new@Base 1.7.0 - git_mailmap_resolve@Base 1.7.0 - git_mailmap_resolve_signature@Base 1.7.0 - git_mempack_dump@Base 1.7.0 - git_mempack_new@Base 1.7.0 - git_mempack_reset@Base 1.7.0 - git_merge@Base 1.7.0 - git_merge_analysis@Base 1.7.0 - git_merge_analysis_for_ref@Base 1.7.0 - git_merge_base@Base 1.7.0 - git_merge_base_many@Base 1.7.0 - git_merge_base_octopus@Base 1.7.0 - git_merge_bases@Base 1.7.0 - git_merge_bases_many@Base 1.7.0 - git_merge_commits@Base 1.7.0 - git_merge_driver_lookup@Base 1.7.0 - git_merge_driver_register@Base 1.7.0 - git_merge_driver_source_ancestor@Base 1.7.0 - git_merge_driver_source_file_options@Base 1.7.0 - git_merge_driver_source_ours@Base 1.7.0 - git_merge_driver_source_repo@Base 1.7.0 - git_merge_driver_source_theirs@Base 1.7.0 - git_merge_driver_unregister@Base 1.7.0 - git_merge_file@Base 1.7.0 - git_merge_file_from_index@Base 1.7.0 - git_merge_file_init_input@Base 1.7.0 - git_merge_file_init_options@Base 1.7.0 - git_merge_file_input_init@Base 1.7.0 - git_merge_file_options_init@Base 1.7.0 - git_merge_file_result_free@Base 1.7.0 - git_merge_init_options@Base 1.7.0 - git_merge_options_init@Base 1.7.0 - git_merge_trees@Base 1.7.0 - git_message_prettify@Base 1.7.0 - git_message_trailer_array_free@Base 1.7.0 - git_message_trailers@Base 1.7.0 - git_midx_writer_add@Base 1.7.0 - git_midx_writer_commit@Base 1.7.0 - git_midx_writer_dump@Base 1.7.0 - git_midx_writer_free@Base 1.7.0 - git_midx_writer_new@Base 1.7.0 - git_note_author@Base 1.7.0 - git_note_commit_create@Base 1.7.0 - git_note_commit_iterator_new@Base 1.7.0 - git_note_commit_read@Base 1.7.0 - git_note_commit_remove@Base 1.7.0 - git_note_committer@Base 1.7.0 - git_note_create@Base 1.7.0 - git_note_default_ref@Base 1.7.0 - git_note_foreach@Base 1.7.0 - git_note_free@Base 1.7.0 - git_note_id@Base 1.7.0 - git_note_iterator_free@Base 1.7.0 - git_note_iterator_new@Base 1.7.0 - git_note_message@Base 1.7.0 - git_note_next@Base 1.7.0 - git_note_read@Base 1.7.0 - git_note_remove@Base 1.7.0 - git_object__size@Base 1.7.0 - git_object_dup@Base 1.7.0 - git_object_free@Base 1.7.0 - git_object_id@Base 1.7.0 - git_object_lookup@Base 1.7.0 - git_object_lookup_bypath@Base 1.7.0 - git_object_lookup_prefix@Base 1.7.0 - git_object_owner@Base 1.7.0 - git_object_peel@Base 1.7.0 - git_object_rawcontent_is_valid@Base 1.7.0 - git_object_short_id@Base 1.7.0 - git_object_string2type@Base 1.7.0 - git_object_type2string@Base 1.7.0 - git_object_type@Base 1.7.0 - git_object_typeisloose@Base 1.7.0 - git_odb__backend_loose@Base 1.7.0 - git_odb_add_alternate@Base 1.7.0 - git_odb_add_backend@Base 1.7.0 - git_odb_add_disk_alternate@Base 1.7.0 - git_odb_backend_data_alloc@Base 1.7.0 - git_odb_backend_data_free@Base 1.7.0 - git_odb_backend_loose@Base 1.7.0 - git_odb_backend_malloc@Base 1.7.0 - git_odb_backend_one_pack@Base 1.7.0 - git_odb_backend_pack@Base 1.7.0 - git_odb_exists@Base 1.7.0 - git_odb_exists_ext@Base 1.7.0 - git_odb_exists_prefix@Base 1.7.0 - git_odb_expand_ids@Base 1.7.0 - git_odb_foreach@Base 1.7.0 - git_odb_free@Base 1.7.0 - git_odb_get_backend@Base 1.7.0 - git_odb_hash@Base 1.7.0 - git_odb_hashfile@Base 1.7.0 - git_odb_init_backend@Base 1.7.0 - git_odb_new@Base 1.7.0 - git_odb_num_backends@Base 1.7.0 - git_odb_object_data@Base 1.7.0 - git_odb_object_dup@Base 1.7.0 - git_odb_object_free@Base 1.7.0 - git_odb_object_id@Base 1.7.0 - git_odb_object_size@Base 1.7.0 - git_odb_object_type@Base 1.7.0 - git_odb_open@Base 1.7.0 - git_odb_open_rstream@Base 1.7.0 - git_odb_open_wstream@Base 1.7.0 - git_odb_read@Base 1.7.0 - git_odb_read_header@Base 1.7.0 - git_odb_read_prefix@Base 1.7.0 - git_odb_refresh@Base 1.7.0 - git_odb_set_commit_graph@Base 1.7.0 - git_odb_stream_finalize_write@Base 1.7.0 - git_odb_stream_free@Base 1.7.0 - git_odb_stream_read@Base 1.7.0 - git_odb_stream_write@Base 1.7.0 - git_odb_write@Base 1.7.0 - git_odb_write_multi_pack_index@Base 1.7.0 - git_odb_write_pack@Base 1.7.0 - git_oid_cmp@Base 1.7.0 - git_oid_cpy@Base 1.7.0 - git_oid_equal@Base 1.7.0 - git_oid_fmt@Base 1.7.0 - git_oid_fromraw@Base 1.7.0 - git_oid_fromstr@Base 1.7.0 - git_oid_fromstrn@Base 1.7.0 - git_oid_fromstrp@Base 1.7.0 - git_oid_is_zero@Base 1.7.0 - git_oid_iszero@Base 1.7.0 - git_oid_ncmp@Base 1.7.0 - git_oid_nfmt@Base 1.7.0 - git_oid_pathfmt@Base 1.7.0 - git_oid_shorten_add@Base 1.7.0 - git_oid_shorten_free@Base 1.7.0 - git_oid_shorten_new@Base 1.7.0 - git_oid_strcmp@Base 1.7.0 - git_oid_streq@Base 1.7.0 - git_oid_tostr@Base 1.7.0 - git_oid_tostr_s@Base 1.7.0 - git_oidarray_dispose@Base 1.7.0 - git_oidarray_free@Base 1.7.0 - git_openssl_set_locking@Base 1.7.0 - git_packbuilder_foreach@Base 1.7.0 - git_packbuilder_free@Base 1.7.0 - git_packbuilder_hash@Base 1.7.0 - git_packbuilder_insert@Base 1.7.0 - git_packbuilder_insert_commit@Base 1.7.0 - git_packbuilder_insert_recur@Base 1.7.0 - git_packbuilder_insert_tree@Base 1.7.0 - git_packbuilder_insert_walk@Base 1.7.0 - git_packbuilder_name@Base 1.7.0 - git_packbuilder_new@Base 1.7.0 - git_packbuilder_object_count@Base 1.7.0 - git_packbuilder_set_callbacks@Base 1.7.0 - git_packbuilder_set_threads@Base 1.7.0 - git_packbuilder_write@Base 1.7.0 - git_packbuilder_write_buf@Base 1.7.0 - git_packbuilder_written@Base 1.7.0 - git_patch_free@Base 1.7.0 - git_patch_from_blob_and_buffer@Base 1.7.0 - git_patch_from_blobs@Base 1.7.0 - git_patch_from_buffers@Base 1.7.0 - git_patch_from_diff@Base 1.7.0 - git_patch_get_delta@Base 1.7.0 - git_patch_get_hunk@Base 1.7.0 - git_patch_get_line_in_hunk@Base 1.7.0 - git_patch_line_stats@Base 1.7.0 - git_patch_num_hunks@Base 1.7.0 - git_patch_num_lines_in_hunk@Base 1.7.0 - git_patch_owner@Base 1.7.0 - git_patch_print@Base 1.7.0 - git_patch_size@Base 1.7.0 - git_patch_to_buf@Base 1.7.0 - git_path_is_gitfile@Base 1.7.0 - git_pathspec_free@Base 1.7.0 - git_pathspec_match_diff@Base 1.7.0 - git_pathspec_match_index@Base 1.7.0 - git_pathspec_match_list_diff_entry@Base 1.7.0 - git_pathspec_match_list_entry@Base 1.7.0 - git_pathspec_match_list_entrycount@Base 1.7.0 - git_pathspec_match_list_failed_entry@Base 1.7.0 - git_pathspec_match_list_failed_entrycount@Base 1.7.0 - git_pathspec_match_list_free@Base 1.7.0 - git_pathspec_match_tree@Base 1.7.0 - git_pathspec_match_workdir@Base 1.7.0 - git_pathspec_matches_path@Base 1.7.0 - git_pathspec_new@Base 1.7.0 - git_proxy_init_options@Base 1.7.0 - git_proxy_options_init@Base 1.7.0 - git_push_init_options@Base 1.7.0 - git_push_options_init@Base 1.7.0 - git_rebase_abort@Base 1.7.0 - git_rebase_commit@Base 1.7.0 - git_rebase_finish@Base 1.7.0 - git_rebase_free@Base 1.7.0 - git_rebase_init@Base 1.7.0 - git_rebase_init_options@Base 1.7.0 - git_rebase_inmemory_index@Base 1.7.0 - git_rebase_next@Base 1.7.0 - git_rebase_onto_id@Base 1.7.0 - git_rebase_onto_name@Base 1.7.0 - git_rebase_open@Base 1.7.0 - git_rebase_operation_byindex@Base 1.7.0 - git_rebase_operation_current@Base 1.7.0 - git_rebase_operation_entrycount@Base 1.7.0 - git_rebase_options_init@Base 1.7.0 - git_rebase_orig_head_id@Base 1.7.0 - git_rebase_orig_head_name@Base 1.7.0 - git_refdb_backend_fs@Base 1.7.0 - git_refdb_compress@Base 1.7.0 - git_refdb_free@Base 1.7.0 - git_refdb_init_backend@Base 1.7.0 - git_refdb_new@Base 1.7.0 - git_refdb_open@Base 1.7.0 - git_refdb_set_backend@Base 1.7.0 - git_reference__alloc@Base 1.7.0 - git_reference__alloc_symbolic@Base 1.7.0 - git_reference_cmp@Base 1.7.0 - git_reference_create@Base 1.7.0 - git_reference_create_matching@Base 1.7.0 - git_reference_delete@Base 1.7.0 - git_reference_dup@Base 1.7.0 - git_reference_dwim@Base 1.7.0 - git_reference_ensure_log@Base 1.7.0 - git_reference_foreach@Base 1.7.0 - git_reference_foreach_glob@Base 1.7.0 - git_reference_foreach_name@Base 1.7.0 - git_reference_free@Base 1.7.0 - git_reference_has_log@Base 1.7.0 - git_reference_is_branch@Base 1.7.0 - git_reference_is_note@Base 1.7.0 - git_reference_is_remote@Base 1.7.0 - git_reference_is_tag@Base 1.7.0 - git_reference_is_valid_name@Base 1.7.0 - git_reference_iterator_free@Base 1.7.0 - git_reference_iterator_glob_new@Base 1.7.0 - git_reference_iterator_new@Base 1.7.0 - git_reference_list@Base 1.7.0 - git_reference_lookup@Base 1.7.0 - git_reference_name@Base 1.7.0 - git_reference_name_is_valid@Base 1.7.0 - git_reference_name_to_id@Base 1.7.0 - git_reference_next@Base 1.7.0 - git_reference_next_name@Base 1.7.0 - git_reference_normalize_name@Base 1.7.0 - git_reference_owner@Base 1.7.0 - git_reference_peel@Base 1.7.0 - git_reference_remove@Base 1.7.0 - git_reference_rename@Base 1.7.0 - git_reference_resolve@Base 1.7.0 - git_reference_set_target@Base 1.7.0 - git_reference_shorthand@Base 1.7.0 - git_reference_symbolic_create@Base 1.7.0 - git_reference_symbolic_create_matching@Base 1.7.0 - git_reference_symbolic_set_target@Base 1.7.0 - git_reference_symbolic_target@Base 1.7.0 - git_reference_target@Base 1.7.0 - git_reference_target_peel@Base 1.7.0 - git_reference_type@Base 1.7.0 - git_reflog_append@Base 1.7.0 - git_reflog_delete@Base 1.7.0 - git_reflog_drop@Base 1.7.0 - git_reflog_entry__free@Base 1.7.0 - git_reflog_entry_byindex@Base 1.7.0 - git_reflog_entry_committer@Base 1.7.0 - git_reflog_entry_id_new@Base 1.7.0 - git_reflog_entry_id_old@Base 1.7.0 - git_reflog_entry_message@Base 1.7.0 - git_reflog_entrycount@Base 1.7.0 - git_reflog_free@Base 1.7.0 - git_reflog_read@Base 1.7.0 - git_reflog_rename@Base 1.7.0 - git_reflog_write@Base 1.7.0 - git_refspec_direction@Base 1.7.0 - git_refspec_dst@Base 1.7.0 - git_refspec_dst_matches@Base 1.7.0 - git_refspec_force@Base 1.7.0 - git_refspec_free@Base 1.7.0 - git_refspec_parse@Base 1.7.0 - git_refspec_rtransform@Base 1.7.0 - git_refspec_src@Base 1.7.0 - git_refspec_src_matches@Base 1.7.0 - git_refspec_string@Base 1.7.0 - git_refspec_transform@Base 1.7.0 - git_remote_add_fetch@Base 1.7.0 - git_remote_add_push@Base 1.7.0 - git_remote_autotag@Base 1.7.0 - git_remote_connect@Base 1.7.0 - git_remote_connect_ext@Base 1.7.0 - git_remote_connect_options_dispose@Base 1.7.0 - git_remote_connect_options_init@Base 1.7.0 - git_remote_connected@Base 1.7.0 - git_remote_create@Base 1.7.0 - git_remote_create_anonymous@Base 1.7.0 - git_remote_create_detached@Base 1.7.0 - git_remote_create_init_options@Base 1.7.0 - git_remote_create_options_init@Base 1.7.0 - git_remote_create_with_fetchspec@Base 1.7.0 - git_remote_create_with_opts@Base 1.7.0 - git_remote_default_branch@Base 1.7.0 - git_remote_delete@Base 1.7.0 - git_remote_disconnect@Base 1.7.0 - git_remote_download@Base 1.7.0 - git_remote_dup@Base 1.7.0 - git_remote_fetch@Base 1.7.0 - git_remote_free@Base 1.7.0 - git_remote_get_fetch_refspecs@Base 1.7.0 - git_remote_get_push_refspecs@Base 1.7.0 - git_remote_get_refspec@Base 1.7.0 - git_remote_init_callbacks@Base 1.7.0 - git_remote_is_valid_name@Base 1.7.0 - git_remote_list@Base 1.7.0 - git_remote_lookup@Base 1.7.0 - git_remote_ls@Base 1.7.0 - git_remote_name@Base 1.7.0 - git_remote_name_is_valid@Base 1.7.0 - git_remote_owner@Base 1.7.0 - git_remote_prune@Base 1.7.0 - git_remote_prune_refs@Base 1.7.0 - git_remote_push@Base 1.7.0 - git_remote_pushurl@Base 1.7.0 - git_remote_refspec_count@Base 1.7.0 - git_remote_rename@Base 1.7.0 - git_remote_set_autotag@Base 1.7.0 - git_remote_set_instance_pushurl@Base 1.7.0 - git_remote_set_instance_url@Base 1.7.0 - git_remote_set_pushurl@Base 1.7.0 - git_remote_set_url@Base 1.7.0 - git_remote_stats@Base 1.7.0 - git_remote_stop@Base 1.7.0 - git_remote_update_tips@Base 1.7.0 - git_remote_upload@Base 1.7.0 - git_remote_url@Base 1.7.0 - git_repository__cleanup@Base 1.7.0 - git_repository_commondir@Base 1.7.0 - git_repository_config@Base 1.7.0 - git_repository_config_snapshot@Base 1.7.0 - git_repository_detach_head@Base 1.7.0 - git_repository_discover@Base 1.7.0 - git_repository_fetchhead_foreach@Base 1.7.0 - git_repository_free@Base 1.7.0 - git_repository_get_namespace@Base 1.7.0 - git_repository_hashfile@Base 1.7.0 - git_repository_head@Base 1.7.0 - git_repository_head_detached@Base 1.7.0 - git_repository_head_detached_for_worktree@Base 1.7.0 - git_repository_head_for_worktree@Base 1.7.0 - git_repository_head_unborn@Base 1.7.0 - git_repository_ident@Base 1.7.0 - git_repository_index@Base 1.7.0 - git_repository_init@Base 1.7.0 - git_repository_init_ext@Base 1.7.0 - git_repository_init_init_options@Base 1.7.0 - git_repository_init_options_init@Base 1.7.0 - git_repository_is_bare@Base 1.7.0 - git_repository_is_empty@Base 1.7.0 - git_repository_is_shallow@Base 1.7.0 - git_repository_is_worktree@Base 1.7.0 - git_repository_item_path@Base 1.7.0 - git_repository_mergehead_foreach@Base 1.7.0 - git_repository_message@Base 1.7.0 - git_repository_message_remove@Base 1.7.0 - git_repository_new@Base 1.7.0 - git_repository_odb@Base 1.7.0 - git_repository_oid_type@Base 1.7.0 - git_repository_open@Base 1.7.0 - git_repository_open_bare@Base 1.7.0 - git_repository_open_ext@Base 1.7.0 - git_repository_open_from_worktree@Base 1.7.0 - git_repository_path@Base 1.7.0 - git_repository_refdb@Base 1.7.0 - git_repository_reinit_filesystem@Base 1.7.0 - git_repository_set_bare@Base 1.7.0 - git_repository_set_config@Base 1.7.0 - git_repository_set_head@Base 1.7.0 - git_repository_set_head_detached@Base 1.7.0 - git_repository_set_head_detached_from_annotated@Base 1.7.0 - git_repository_set_ident@Base 1.7.0 - git_repository_set_index@Base 1.7.0 - git_repository_set_namespace@Base 1.7.0 - git_repository_set_odb@Base 1.7.0 - git_repository_set_refdb@Base 1.7.0 - git_repository_set_workdir@Base 1.7.0 - git_repository_state@Base 1.7.0 - git_repository_state_cleanup@Base 1.7.0 - git_repository_submodule_cache_all@Base 1.7.0 - git_repository_submodule_cache_clear@Base 1.7.0 - git_repository_workdir@Base 1.7.0 - git_repository_wrap_odb@Base 1.7.0 - git_reset@Base 1.7.0 - git_reset_default@Base 1.7.0 - git_reset_from_annotated@Base 1.7.0 - git_revert@Base 1.7.0 - git_revert_commit@Base 1.7.0 - git_revert_init_options@Base 1.7.0 - git_revert_options_init@Base 1.7.0 - git_revparse@Base 1.7.0 - git_revparse_ext@Base 1.7.0 - git_revparse_single@Base 1.7.0 - git_revwalk_add_hide_cb@Base 1.7.0 - git_revwalk_free@Base 1.7.0 - git_revwalk_hide@Base 1.7.0 - git_revwalk_hide_glob@Base 1.7.0 - git_revwalk_hide_head@Base 1.7.0 - git_revwalk_hide_ref@Base 1.7.0 - git_revwalk_new@Base 1.7.0 - git_revwalk_next@Base 1.7.0 - git_revwalk_push@Base 1.7.0 - git_revwalk_push_glob@Base 1.7.0 - git_revwalk_push_head@Base 1.7.0 - git_revwalk_push_range@Base 1.7.0 - git_revwalk_push_ref@Base 1.7.0 - git_revwalk_repository@Base 1.7.0 - git_revwalk_reset@Base 1.7.0 - git_revwalk_simplify_first_parent@Base 1.7.0 - git_revwalk_sorting@Base 1.7.0 - git_signature_default@Base 1.7.0 - git_signature_dup@Base 1.7.0 - git_signature_free@Base 1.7.0 - git_signature_from_buffer@Base 1.7.0 - git_signature_new@Base 1.7.0 - git_signature_now@Base 1.7.0 - git_smart_subtransport_git@Base 1.7.0 - git_smart_subtransport_http@Base 1.7.0 - git_smart_subtransport_ssh@Base 1.7.0 - git_stash_apply@Base 1.7.0 - git_stash_apply_init_options@Base 1.7.0 - git_stash_apply_options_init@Base 1.7.0 - git_stash_drop@Base 1.7.0 - git_stash_foreach@Base 1.7.0 - git_stash_pop@Base 1.7.0 - git_stash_save@Base 1.7.0 - git_stash_save_options_init@Base 1.7.0 - git_stash_save_with_opts@Base 1.7.0 - git_status_byindex@Base 1.7.0 - git_status_file@Base 1.7.0 - git_status_foreach@Base 1.7.0 - git_status_foreach_ext@Base 1.7.0 - git_status_init_options@Base 1.7.0 - git_status_list_entrycount@Base 1.7.0 - git_status_list_free@Base 1.7.0 - git_status_list_get_perfdata@Base 1.7.0 - git_status_list_new@Base 1.7.0 - git_status_options_init@Base 1.7.0 - git_status_should_ignore@Base 1.7.0 - git_strarray_copy@Base 1.7.0 - git_strarray_dispose@Base 1.7.0 - git_strarray_free@Base 1.7.0 - git_stream_register@Base 1.7.0 - git_stream_register_tls@Base 1.7.0 - git_submodule_add_finalize@Base 1.7.0 - git_submodule_add_setup@Base 1.7.0 - git_submodule_add_to_index@Base 1.7.0 - git_submodule_branch@Base 1.7.0 - git_submodule_clone@Base 1.7.0 - git_submodule_dup@Base 1.7.0 - git_submodule_fetch_recurse_submodules@Base 1.7.0 - git_submodule_foreach@Base 1.7.0 - git_submodule_free@Base 1.7.0 - git_submodule_head_id@Base 1.7.0 - git_submodule_ignore@Base 1.7.0 - git_submodule_index_id@Base 1.7.0 - git_submodule_init@Base 1.7.0 - git_submodule_location@Base 1.7.0 - git_submodule_lookup@Base 1.7.0 - git_submodule_name@Base 1.7.0 - git_submodule_open@Base 1.7.0 - git_submodule_owner@Base 1.7.0 - git_submodule_path@Base 1.7.0 - git_submodule_reload@Base 1.7.0 - git_submodule_repo_init@Base 1.7.0 - git_submodule_resolve_url@Base 1.7.0 - git_submodule_set_branch@Base 1.7.0 - git_submodule_set_fetch_recurse_submodules@Base 1.7.0 - git_submodule_set_ignore@Base 1.7.0 - git_submodule_set_update@Base 1.7.0 - git_submodule_set_url@Base 1.7.0 - git_submodule_status@Base 1.7.0 - git_submodule_sync@Base 1.7.0 - git_submodule_update@Base 1.7.0 - git_submodule_update_init_options@Base 1.7.0 - git_submodule_update_options_init@Base 1.7.0 - git_submodule_update_strategy@Base 1.7.0 - git_submodule_url@Base 1.7.0 - git_submodule_wd_id@Base 1.7.0 - git_tag_annotation_create@Base 1.7.0 - git_tag_create@Base 1.7.0 - git_tag_create_from_buffer@Base 1.7.0 - git_tag_create_frombuffer@Base 1.7.0 - git_tag_create_lightweight@Base 1.7.0 - git_tag_delete@Base 1.7.0 - git_tag_dup@Base 1.7.0 - git_tag_foreach@Base 1.7.0 - git_tag_free@Base 1.7.0 - git_tag_id@Base 1.7.0 - git_tag_list@Base 1.7.0 - git_tag_list_match@Base 1.7.0 - git_tag_lookup@Base 1.7.0 - git_tag_lookup_prefix@Base 1.7.0 - git_tag_message@Base 1.7.0 - git_tag_name@Base 1.7.0 - git_tag_name_is_valid@Base 1.7.0 - git_tag_owner@Base 1.7.0 - git_tag_peel@Base 1.7.0 - git_tag_tagger@Base 1.7.0 - git_tag_target@Base 1.7.0 - git_tag_target_id@Base 1.7.0 - git_tag_target_type@Base 1.7.0 - git_trace_set@Base 1.7.0 - git_transaction_commit@Base 1.7.0 - git_transaction_free@Base 1.7.0 - git_transaction_lock_ref@Base 1.7.0 - git_transaction_new@Base 1.7.0 - git_transaction_remove@Base 1.7.0 - git_transaction_set_reflog@Base 1.7.0 - git_transaction_set_symbolic_target@Base 1.7.0 - git_transaction_set_target@Base 1.7.0 - git_transport_init@Base 1.7.0 - git_transport_local@Base 1.7.0 - git_transport_new@Base 1.7.0 - git_transport_register@Base 1.7.0 - git_transport_remote_connect_options@Base 1.7.0 - git_transport_smart@Base 1.7.0 - git_transport_smart_certificate_check@Base 1.7.0 - git_transport_smart_credentials@Base 1.7.0 - git_transport_ssh_with_paths@Base 1.7.0 - git_transport_unregister@Base 1.7.0 - git_tree_create_updated@Base 1.7.0 - git_tree_dup@Base 1.7.0 - git_tree_entry_byid@Base 1.7.0 - git_tree_entry_byindex@Base 1.7.0 - git_tree_entry_byname@Base 1.7.0 - git_tree_entry_bypath@Base 1.7.0 - git_tree_entry_cmp@Base 1.7.0 - git_tree_entry_dup@Base 1.7.0 - git_tree_entry_filemode@Base 1.7.0 - git_tree_entry_filemode_raw@Base 1.7.0 - git_tree_entry_free@Base 1.7.0 - git_tree_entry_id@Base 1.7.0 - git_tree_entry_name@Base 1.7.0 - git_tree_entry_to_object@Base 1.7.0 - git_tree_entry_type@Base 1.7.0 - git_tree_entrycount@Base 1.7.0 - git_tree_free@Base 1.7.0 - git_tree_id@Base 1.7.0 - git_tree_lookup@Base 1.7.0 - git_tree_lookup_prefix@Base 1.7.0 - git_tree_owner@Base 1.7.0 - git_tree_walk@Base 1.7.0 - git_treebuilder_clear@Base 1.7.0 - git_treebuilder_entrycount@Base 1.7.0 - git_treebuilder_filter@Base 1.7.0 - git_treebuilder_free@Base 1.7.0 - git_treebuilder_get@Base 1.7.0 - git_treebuilder_insert@Base 1.7.0 - git_treebuilder_new@Base 1.7.0 - git_treebuilder_remove@Base 1.7.0 - git_treebuilder_write@Base 1.7.0 - git_treebuilder_write_with_buffer@Base 1.7.0 - git_worktree_add@Base 1.7.0 - git_worktree_add_init_options@Base 1.7.0 - git_worktree_add_options_init@Base 1.7.0 - git_worktree_free@Base 1.7.0 - git_worktree_is_locked@Base 1.7.0 - git_worktree_is_prunable@Base 1.7.0 - git_worktree_list@Base 1.7.0 - git_worktree_lock@Base 1.7.0 - git_worktree_lookup@Base 1.7.0 - git_worktree_name@Base 1.7.0 - git_worktree_open_from_repository@Base 1.7.0 - git_worktree_path@Base 1.7.0 - git_worktree_prune@Base 1.7.0 - git_worktree_prune_init_options@Base 1.7.0 - git_worktree_prune_options_init@Base 1.7.0 - git_worktree_unlock@Base 1.7.0 - git_worktree_validate@Base 1.7.0 - giterr_clear@Base 1.7.0 - giterr_last@Base 1.7.0 - giterr_set_oom@Base 1.7.0 - giterr_set_str@Base 1.7.0 diff --git a/debian/libgit2-1.9.install b/debian/libgit2-1.9.install new file mode 100644 index 00000000..3ddde584 --- /dev/null +++ b/debian/libgit2-1.9.install @@ -0,0 +1 @@ +usr/lib/*/lib*.so.* diff --git a/debian/libgit2-1.9.symbols b/debian/libgit2-1.9.symbols new file mode 100644 index 00000000..d1f2fb04 --- /dev/null +++ b/debian/libgit2-1.9.symbols @@ -0,0 +1,955 @@ +libgit2.so.1.9 libgit2-1.9 #MINVER# +* Build-Depends-Package: libgit2-dev + git_annotated_commit_free@Base 1.9.0 + git_annotated_commit_from_fetchhead@Base 1.9.0 + git_annotated_commit_from_ref@Base 1.9.0 + git_annotated_commit_from_revspec@Base 1.9.0 + git_annotated_commit_id@Base 1.9.0 + git_annotated_commit_lookup@Base 1.9.0 + git_annotated_commit_ref@Base 1.9.0 + git_apply@Base 1.9.0 + git_apply_options_init@Base 1.9.0 + git_apply_to_tree@Base 1.9.0 + git_attr_add_macro@Base 1.9.0 + git_attr_cache_flush@Base 1.9.0 + git_attr_foreach@Base 1.9.0 + git_attr_foreach_ext@Base 1.9.0 + git_attr_get@Base 1.9.0 + git_attr_get_ext@Base 1.9.0 + git_attr_get_many@Base 1.9.0 + git_attr_get_many_ext@Base 1.9.0 + git_attr_value@Base 1.9.0 + git_blame_buffer@Base 1.9.0 + git_blame_file@Base 1.9.0 + git_blame_free@Base 1.9.0 + git_blame_get_hunk_byindex@Base 1.9.0 + git_blame_get_hunk_byline@Base 1.9.0 + git_blame_get_hunk_count@Base 1.9.0 + git_blame_hunk_byindex@Base 1.9.0 + git_blame_hunk_byline@Base 1.9.0 + git_blame_hunkcount@Base 1.9.0 + git_blame_init_options@Base 1.9.0 + git_blame_line_byindex@Base 1.9.0 + git_blame_linecount@Base 1.9.0 + git_blame_options_init@Base 1.9.0 + git_blob_create_from_buffer@Base 1.9.0 + git_blob_create_from_disk@Base 1.9.0 + git_blob_create_from_stream@Base 1.9.0 + git_blob_create_from_stream_commit@Base 1.9.0 + git_blob_create_from_workdir@Base 1.9.0 + git_blob_create_frombuffer@Base 1.9.0 + git_blob_create_fromdisk@Base 1.9.0 + git_blob_create_fromstream@Base 1.9.0 + git_blob_create_fromstream_commit@Base 1.9.0 + git_blob_create_fromworkdir@Base 1.9.0 + git_blob_data_is_binary@Base 1.9.0 + git_blob_dup@Base 1.9.0 + git_blob_filter@Base 1.9.0 + git_blob_filter_options_init@Base 1.9.0 + git_blob_filtered_content@Base 1.9.0 + git_blob_free@Base 1.9.0 + git_blob_id@Base 1.9.0 + git_blob_is_binary@Base 1.9.0 + git_blob_lookup@Base 1.9.0 + git_blob_lookup_prefix@Base 1.9.0 + git_blob_owner@Base 1.9.0 + git_blob_rawcontent@Base 1.9.0 + git_blob_rawsize@Base 1.9.0 + git_branch_create@Base 1.9.0 + git_branch_create_from_annotated@Base 1.9.0 + git_branch_delete@Base 1.9.0 + git_branch_is_checked_out@Base 1.9.0 + git_branch_is_head@Base 1.9.0 + git_branch_iterator_free@Base 1.9.0 + git_branch_iterator_new@Base 1.9.0 + git_branch_lookup@Base 1.9.0 + git_branch_move@Base 1.9.0 + git_branch_name@Base 1.9.0 + git_branch_name_is_valid@Base 1.9.0 + git_branch_next@Base 1.9.0 + git_branch_remote_name@Base 1.9.0 + git_branch_set_upstream@Base 1.9.0 + git_branch_upstream@Base 1.9.0 + git_branch_upstream_merge@Base 1.9.0 + git_branch_upstream_name@Base 1.9.0 + git_branch_upstream_remote@Base 1.9.0 + git_buf_contains_nul@Base 1.9.0 + git_buf_dispose@Base 1.9.0 + git_buf_free@Base 1.9.0 + git_buf_grow@Base 1.9.0 + git_buf_is_binary@Base 1.9.0 + git_buf_set@Base 1.9.0 + git_checkout_head@Base 1.9.0 + git_checkout_index@Base 1.9.0 + git_checkout_init_options@Base 1.9.0 + git_checkout_options_init@Base 1.9.0 + git_checkout_tree@Base 1.9.0 + git_cherrypick@Base 1.9.0 + git_cherrypick_commit@Base 1.9.0 + git_cherrypick_init_options@Base 1.9.0 + git_cherrypick_options_init@Base 1.9.0 + git_clone@Base 1.9.0 + git_clone_init_options@Base 1.9.0 + git_clone_options_init@Base 1.9.0 + git_commit_amend@Base 1.9.0 + git_commit_author@Base 1.9.0 + git_commit_author_with_mailmap@Base 1.9.0 + git_commit_body@Base 1.9.0 + git_commit_committer@Base 1.9.0 + git_commit_committer_with_mailmap@Base 1.9.0 + git_commit_create@Base 1.9.0 + git_commit_create_buffer@Base 1.9.0 + git_commit_create_from_callback@Base 1.9.0 + git_commit_create_from_ids@Base 1.9.0 + git_commit_create_from_stage@Base 1.9.0 + git_commit_create_v@Base 1.9.0 + git_commit_create_with_signature@Base 1.9.0 + git_commit_dup@Base 1.9.0 + git_commit_extract_signature@Base 1.9.0 + git_commit_free@Base 1.9.0 + git_commit_graph_free@Base 1.9.0 + git_commit_graph_open@Base 1.9.0 + git_commit_graph_writer_add_index_file@Base 1.9.0 + git_commit_graph_writer_add_revwalk@Base 1.9.0 + git_commit_graph_writer_commit@Base 1.9.0 + git_commit_graph_writer_dump@Base 1.9.0 + git_commit_graph_writer_free@Base 1.9.0 + git_commit_graph_writer_new@Base 1.9.0 + git_commit_graph_writer_options_init@Base 1.9.0 + git_commit_header_field@Base 1.9.0 + git_commit_id@Base 1.9.0 + git_commit_lookup@Base 1.9.0 + git_commit_lookup_prefix@Base 1.9.0 + git_commit_message@Base 1.9.0 + git_commit_message_encoding@Base 1.9.0 + git_commit_message_raw@Base 1.9.0 + git_commit_nth_gen_ancestor@Base 1.9.0 + git_commit_owner@Base 1.9.0 + git_commit_parent@Base 1.9.0 + git_commit_parent_id@Base 1.9.0 + git_commit_parentcount@Base 1.9.0 + git_commit_raw_header@Base 1.9.0 + git_commit_summary@Base 1.9.0 + git_commit_time@Base 1.9.0 + git_commit_time_offset@Base 1.9.0 + git_commit_tree@Base 1.9.0 + git_commit_tree_id@Base 1.9.0 + git_commitarray_dispose@Base 1.9.0 + git_config_add_backend@Base 1.9.0 + git_config_add_file_ondisk@Base 1.9.0 + git_config_backend_foreach_match@Base 1.9.0 + git_config_delete_entry@Base 1.9.0 + git_config_delete_multivar@Base 1.9.0 + git_config_entry_free@Base 1.9.0 + git_config_find_global@Base 1.9.0 + git_config_find_programdata@Base 1.9.0 + git_config_find_system@Base 1.9.0 + git_config_find_xdg@Base 1.9.0 + git_config_foreach@Base 1.9.0 + git_config_foreach_match@Base 1.9.0 + git_config_free@Base 1.9.0 + git_config_get_bool@Base 1.9.0 + git_config_get_entry@Base 1.9.0 + git_config_get_int32@Base 1.9.0 + git_config_get_int64@Base 1.9.0 + git_config_get_mapped@Base 1.9.0 + git_config_get_multivar_foreach@Base 1.9.0 + git_config_get_path@Base 1.9.0 + git_config_get_string@Base 1.9.0 + git_config_get_string_buf@Base 1.9.0 + git_config_init_backend@Base 1.9.0 + git_config_iterator_free@Base 1.9.0 + git_config_iterator_glob_new@Base 1.9.0 + git_config_iterator_new@Base 1.9.0 + git_config_lock@Base 1.9.0 + git_config_lookup_map_value@Base 1.9.0 + git_config_multivar_iterator_new@Base 1.9.0 + git_config_new@Base 1.9.0 + git_config_next@Base 1.9.0 + git_config_open_default@Base 1.9.0 + git_config_open_global@Base 1.9.0 + git_config_open_level@Base 1.9.0 + git_config_open_ondisk@Base 1.9.0 + git_config_parse_bool@Base 1.9.0 + git_config_parse_int32@Base 1.9.0 + git_config_parse_int64@Base 1.9.0 + git_config_parse_path@Base 1.9.0 + git_config_set_bool@Base 1.9.0 + git_config_set_int32@Base 1.9.0 + git_config_set_int64@Base 1.9.0 + git_config_set_multivar@Base 1.9.0 + git_config_set_string@Base 1.9.0 + git_config_set_writeorder@Base 1.9.0 + git_config_snapshot@Base 1.9.0 + git_config_unlock@Base 1.9.0 + git_cred_default_new@Base 1.9.0 + git_cred_free@Base 1.9.0 + git_cred_get_username@Base 1.9.0 + git_cred_has_username@Base 1.9.0 + git_cred_ssh_custom_new@Base 1.9.0 + git_cred_ssh_interactive_new@Base 1.9.0 + git_cred_ssh_key_from_agent@Base 1.9.0 + git_cred_ssh_key_memory_new@Base 1.9.0 + git_cred_ssh_key_new@Base 1.9.0 + git_cred_username_new@Base 1.9.0 + git_cred_userpass@Base 1.9.0 + git_cred_userpass_plaintext_new@Base 1.9.0 + git_credential_default_new@Base 1.9.0 + git_credential_free@Base 1.9.0 + git_credential_get_username@Base 1.9.0 + git_credential_has_username@Base 1.9.0 + git_credential_ssh_custom_new@Base 1.9.0 + git_credential_ssh_interactive_new@Base 1.9.0 + git_credential_ssh_key_from_agent@Base 1.9.0 + git_credential_ssh_key_memory_new@Base 1.9.0 + git_credential_ssh_key_new@Base 1.9.0 + git_credential_username_new@Base 1.9.0 + git_credential_userpass@Base 1.9.0 + git_credential_userpass_plaintext_new@Base 1.9.0 + git_describe_commit@Base 1.9.0 + git_describe_format@Base 1.9.0 + git_describe_format_options_init@Base 1.9.0 + git_describe_init_format_options@Base 1.9.0 + git_describe_init_options@Base 1.9.0 + git_describe_options_init@Base 1.9.0 + git_describe_result_free@Base 1.9.0 + git_describe_workdir@Base 1.9.0 + git_diff_blob_to_buffer@Base 1.9.0 + git_diff_blobs@Base 1.9.0 + git_diff_buffers@Base 1.9.0 + git_diff_commit_as_email@Base 1.9.0 + git_diff_find_init_options@Base 1.9.0 + git_diff_find_options_init@Base 1.9.0 + git_diff_find_similar@Base 1.9.0 + git_diff_foreach@Base 1.9.0 + git_diff_format_email@Base 1.9.0 + git_diff_format_email_init_options@Base 1.9.0 + git_diff_format_email_options_init@Base 1.9.0 + git_diff_free@Base 1.9.0 + git_diff_from_buffer@Base 1.9.0 + git_diff_get_delta@Base 1.9.0 + git_diff_get_perfdata@Base 1.9.0 + git_diff_get_stats@Base 1.9.0 + git_diff_index_to_index@Base 1.9.0 + git_diff_index_to_workdir@Base 1.9.0 + git_diff_init_options@Base 1.9.0 + git_diff_is_sorted_icase@Base 1.9.0 + git_diff_merge@Base 1.9.0 + git_diff_num_deltas@Base 1.9.0 + git_diff_num_deltas_of_type@Base 1.9.0 + git_diff_options_init@Base 1.9.0 + git_diff_patchid@Base 1.9.0 + git_diff_patchid_options_init@Base 1.9.0 + git_diff_print@Base 1.9.0 + git_diff_print_callback__to_buf@Base 1.9.0 + git_diff_print_callback__to_file_handle@Base 1.9.0 + git_diff_stats_deletions@Base 1.9.0 + git_diff_stats_files_changed@Base 1.9.0 + git_diff_stats_free@Base 1.9.0 + git_diff_stats_insertions@Base 1.9.0 + git_diff_stats_to_buf@Base 1.9.0 + git_diff_status_char@Base 1.9.0 + git_diff_to_buf@Base 1.9.0 + git_diff_tree_to_index@Base 1.9.0 + git_diff_tree_to_tree@Base 1.9.0 + git_diff_tree_to_workdir@Base 1.9.0 + git_diff_tree_to_workdir_with_index@Base 1.9.0 + git_email_create_from_commit@Base 1.9.0 + git_email_create_from_diff@Base 1.9.0 + git_error_clear@Base 1.9.0 + git_error_last@Base 1.9.0 + git_error_set@Base 1.9.0 + git_error_set_oom@Base 1.9.0 + git_error_set_str@Base 1.9.0 + git_fetch_init_options@Base 1.9.0 + git_fetch_options_init@Base 1.9.0 + git_filter_init@Base 1.9.0 + git_filter_list_apply_to_blob@Base 1.9.0 + git_filter_list_apply_to_buffer@Base 1.9.0 + git_filter_list_apply_to_data@Base 1.9.0 + git_filter_list_apply_to_file@Base 1.9.0 + git_filter_list_contains@Base 1.9.0 + git_filter_list_free@Base 1.9.0 + git_filter_list_length@Base 1.9.0 + git_filter_list_load@Base 1.9.0 + git_filter_list_load_ext@Base 1.9.0 + git_filter_list_new@Base 1.9.0 + git_filter_list_push@Base 1.9.0 + git_filter_list_stream_blob@Base 1.9.0 + git_filter_list_stream_buffer@Base 1.9.0 + git_filter_list_stream_data@Base 1.9.0 + git_filter_list_stream_file@Base 1.9.0 + git_filter_lookup@Base 1.9.0 + git_filter_register@Base 1.9.0 + git_filter_source_filemode@Base 1.9.0 + git_filter_source_flags@Base 1.9.0 + git_filter_source_id@Base 1.9.0 + git_filter_source_mode@Base 1.9.0 + git_filter_source_path@Base 1.9.0 + git_filter_source_repo@Base 1.9.0 + git_filter_unregister@Base 1.9.0 + git_graph_ahead_behind@Base 1.9.0 + git_graph_descendant_of@Base 1.9.0 + git_graph_reachable_from_any@Base 1.9.0 + git_hashsig_compare@Base 1.9.0 + git_hashsig_create@Base 1.9.0 + git_hashsig_create_fromfile@Base 1.9.0 + git_hashsig_free@Base 1.9.0 + git_ignore_add_rule@Base 1.9.0 + git_ignore_clear_internal_rules@Base 1.9.0 + git_ignore_path_is_ignored@Base 1.9.0 + git_index_add@Base 1.9.0 + git_index_add_all@Base 1.9.0 + git_index_add_bypath@Base 1.9.0 + git_index_add_from_buffer@Base 1.9.0 + git_index_add_frombuffer@Base 1.9.0 + git_index_caps@Base 1.9.0 + git_index_checksum@Base 1.9.0 + git_index_clear@Base 1.9.0 + git_index_conflict_add@Base 1.9.0 + git_index_conflict_cleanup@Base 1.9.0 + git_index_conflict_get@Base 1.9.0 + git_index_conflict_iterator_free@Base 1.9.0 + git_index_conflict_iterator_new@Base 1.9.0 + git_index_conflict_next@Base 1.9.0 + git_index_conflict_remove@Base 1.9.0 + git_index_entry_is_conflict@Base 1.9.0 + git_index_entry_stage@Base 1.9.0 + git_index_entrycount@Base 1.9.0 + git_index_find@Base 1.9.0 + git_index_find_prefix@Base 1.9.0 + git_index_free@Base 1.9.0 + git_index_get_byindex@Base 1.9.0 + git_index_get_bypath@Base 1.9.0 + git_index_has_conflicts@Base 1.9.0 + git_index_iterator_free@Base 1.9.0 + git_index_iterator_new@Base 1.9.0 + git_index_iterator_next@Base 1.9.0 + git_index_name_add@Base 1.9.0 + git_index_name_clear@Base 1.9.0 + git_index_name_entrycount@Base 1.9.0 + git_index_name_get_byindex@Base 1.9.0 + git_index_new@Base 1.9.0 + git_index_open@Base 1.9.0 + git_index_owner@Base 1.9.0 + git_index_path@Base 1.9.0 + git_index_read@Base 1.9.0 + git_index_read_tree@Base 1.9.0 + git_index_remove@Base 1.9.0 + git_index_remove_all@Base 1.9.0 + git_index_remove_bypath@Base 1.9.0 + git_index_remove_directory@Base 1.9.0 + git_index_reuc_add@Base 1.9.0 + git_index_reuc_clear@Base 1.9.0 + git_index_reuc_entrycount@Base 1.9.0 + git_index_reuc_find@Base 1.9.0 + git_index_reuc_get_byindex@Base 1.9.0 + git_index_reuc_get_bypath@Base 1.9.0 + git_index_reuc_remove@Base 1.9.0 + git_index_set_caps@Base 1.9.0 + git_index_set_version@Base 1.9.0 + git_index_update_all@Base 1.9.0 + git_index_version@Base 1.9.0 + git_index_write@Base 1.9.0 + git_index_write_tree@Base 1.9.0 + git_index_write_tree_to@Base 1.9.0 + git_indexer_append@Base 1.9.0 + git_indexer_commit@Base 1.9.0 + git_indexer_free@Base 1.9.0 + git_indexer_hash@Base 1.9.0 + git_indexer_init_options@Base 1.9.0 + git_indexer_name@Base 1.9.0 + git_indexer_new@Base 1.9.0 + git_indexer_options_init@Base 1.9.0 + git_libgit2_feature_backend@Base 1.9.0 + git_libgit2_features@Base 1.9.0 + git_libgit2_init@Base 1.9.0 + git_libgit2_opts@Base 1.9.0 + git_libgit2_prerelease@Base 1.9.0 + git_libgit2_shutdown@Base 1.9.0 + git_libgit2_version@Base 1.9.0 + git_mailmap_add_entry@Base 1.9.0 + git_mailmap_free@Base 1.9.0 + git_mailmap_from_buffer@Base 1.9.0 + git_mailmap_from_repository@Base 1.9.0 + git_mailmap_new@Base 1.9.0 + git_mailmap_resolve@Base 1.9.0 + git_mailmap_resolve_signature@Base 1.9.0 + git_mempack_dump@Base 1.9.0 + git_mempack_new@Base 1.9.0 + git_mempack_object_count@Base 1.9.0 + git_mempack_reset@Base 1.9.0 + git_mempack_write_thin_pack@Base 1.9.0 + git_merge@Base 1.9.0 + git_merge_analysis@Base 1.9.0 + git_merge_analysis_for_ref@Base 1.9.0 + git_merge_base@Base 1.9.0 + git_merge_base_many@Base 1.9.0 + git_merge_base_octopus@Base 1.9.0 + git_merge_bases@Base 1.9.0 + git_merge_bases_many@Base 1.9.0 + git_merge_commits@Base 1.9.0 + git_merge_driver_lookup@Base 1.9.0 + git_merge_driver_register@Base 1.9.0 + git_merge_driver_source_ancestor@Base 1.9.0 + git_merge_driver_source_file_options@Base 1.9.0 + git_merge_driver_source_ours@Base 1.9.0 + git_merge_driver_source_repo@Base 1.9.0 + git_merge_driver_source_theirs@Base 1.9.0 + git_merge_driver_unregister@Base 1.9.0 + git_merge_file@Base 1.9.0 + git_merge_file_from_index@Base 1.9.0 + git_merge_file_init_input@Base 1.9.0 + git_merge_file_init_options@Base 1.9.0 + git_merge_file_input_init@Base 1.9.0 + git_merge_file_options_init@Base 1.9.0 + git_merge_file_result_free@Base 1.9.0 + git_merge_init_options@Base 1.9.0 + git_merge_options_init@Base 1.9.0 + git_merge_trees@Base 1.9.0 + git_message_prettify@Base 1.9.0 + git_message_trailer_array_free@Base 1.9.0 + git_message_trailers@Base 1.9.0 + git_midx_writer_add@Base 1.9.0 + git_midx_writer_commit@Base 1.9.0 + git_midx_writer_dump@Base 1.9.0 + git_midx_writer_free@Base 1.9.0 + git_midx_writer_new@Base 1.9.0 + git_note_author@Base 1.9.0 + git_note_commit_create@Base 1.9.0 + git_note_commit_iterator_new@Base 1.9.0 + git_note_commit_read@Base 1.9.0 + git_note_commit_remove@Base 1.9.0 + git_note_committer@Base 1.9.0 + git_note_create@Base 1.9.0 + git_note_default_ref@Base 1.9.0 + git_note_foreach@Base 1.9.0 + git_note_free@Base 1.9.0 + git_note_id@Base 1.9.0 + git_note_iterator_free@Base 1.9.0 + git_note_iterator_new@Base 1.9.0 + git_note_message@Base 1.9.0 + git_note_next@Base 1.9.0 + git_note_read@Base 1.9.0 + git_note_remove@Base 1.9.0 + git_object__size@Base 1.9.0 + git_object_dup@Base 1.9.0 + git_object_free@Base 1.9.0 + git_object_id@Base 1.9.0 + git_object_lookup@Base 1.9.0 + git_object_lookup_bypath@Base 1.9.0 + git_object_lookup_prefix@Base 1.9.0 + git_object_owner@Base 1.9.0 + git_object_peel@Base 1.9.0 + git_object_rawcontent_is_valid@Base 1.9.0 + git_object_short_id@Base 1.9.0 + git_object_string2type@Base 1.9.0 + git_object_type2string@Base 1.9.0 + git_object_type@Base 1.9.0 + git_object_typeisloose@Base 1.9.0 + git_odb__backend_loose@Base 1.9.0 + git_odb_add_alternate@Base 1.9.0 + git_odb_add_backend@Base 1.9.0 + git_odb_add_disk_alternate@Base 1.9.0 + git_odb_backend_data_alloc@Base 1.9.0 + git_odb_backend_data_free@Base 1.9.0 + git_odb_backend_loose@Base 1.9.0 + git_odb_backend_malloc@Base 1.9.0 + git_odb_backend_one_pack@Base 1.9.0 + git_odb_backend_pack@Base 1.9.0 + git_odb_exists@Base 1.9.0 + git_odb_exists_ext@Base 1.9.0 + git_odb_exists_prefix@Base 1.9.0 + git_odb_expand_ids@Base 1.9.0 + git_odb_foreach@Base 1.9.0 + git_odb_free@Base 1.9.0 + git_odb_get_backend@Base 1.9.0 + git_odb_hash@Base 1.9.0 + git_odb_hashfile@Base 1.9.0 + git_odb_init_backend@Base 1.9.0 + git_odb_new@Base 1.9.0 + git_odb_num_backends@Base 1.9.0 + git_odb_object_data@Base 1.9.0 + git_odb_object_dup@Base 1.9.0 + git_odb_object_free@Base 1.9.0 + git_odb_object_id@Base 1.9.0 + git_odb_object_size@Base 1.9.0 + git_odb_object_type@Base 1.9.0 + git_odb_open@Base 1.9.0 + git_odb_open_rstream@Base 1.9.0 + git_odb_open_wstream@Base 1.9.0 + git_odb_read@Base 1.9.0 + git_odb_read_header@Base 1.9.0 + git_odb_read_prefix@Base 1.9.0 + git_odb_refresh@Base 1.9.0 + git_odb_set_commit_graph@Base 1.9.0 + git_odb_stream_finalize_write@Base 1.9.0 + git_odb_stream_free@Base 1.9.0 + git_odb_stream_read@Base 1.9.0 + git_odb_stream_write@Base 1.9.0 + git_odb_write@Base 1.9.0 + git_odb_write_multi_pack_index@Base 1.9.0 + git_odb_write_pack@Base 1.9.0 + git_oid_cmp@Base 1.9.0 + git_oid_cpy@Base 1.9.0 + git_oid_equal@Base 1.9.0 + git_oid_fmt@Base 1.9.0 + git_oid_fromraw@Base 1.9.0 + git_oid_fromstr@Base 1.9.0 + git_oid_fromstrn@Base 1.9.0 + git_oid_fromstrp@Base 1.9.0 + git_oid_is_zero@Base 1.9.0 + git_oid_iszero@Base 1.9.0 + git_oid_ncmp@Base 1.9.0 + git_oid_nfmt@Base 1.9.0 + git_oid_pathfmt@Base 1.9.0 + git_oid_shorten_add@Base 1.9.0 + git_oid_shorten_free@Base 1.9.0 + git_oid_shorten_new@Base 1.9.0 + git_oid_strcmp@Base 1.9.0 + git_oid_streq@Base 1.9.0 + git_oid_tostr@Base 1.9.0 + git_oid_tostr_s@Base 1.9.0 + git_oidarray_dispose@Base 1.9.0 + git_oidarray_free@Base 1.9.0 + git_openssl_set_locking@Base 1.9.0 + git_packbuilder_foreach@Base 1.9.0 + git_packbuilder_free@Base 1.9.0 + git_packbuilder_hash@Base 1.9.0 + git_packbuilder_insert@Base 1.9.0 + git_packbuilder_insert_commit@Base 1.9.0 + git_packbuilder_insert_recur@Base 1.9.0 + git_packbuilder_insert_tree@Base 1.9.0 + git_packbuilder_insert_walk@Base 1.9.0 + git_packbuilder_name@Base 1.9.0 + git_packbuilder_new@Base 1.9.0 + git_packbuilder_object_count@Base 1.9.0 + git_packbuilder_set_callbacks@Base 1.9.0 + git_packbuilder_set_threads@Base 1.9.0 + git_packbuilder_write@Base 1.9.0 + git_packbuilder_write_buf@Base 1.9.0 + git_packbuilder_written@Base 1.9.0 + git_patch_free@Base 1.9.0 + git_patch_from_blob_and_buffer@Base 1.9.0 + git_patch_from_blobs@Base 1.9.0 + git_patch_from_buffers@Base 1.9.0 + git_patch_from_diff@Base 1.9.0 + git_patch_get_delta@Base 1.9.0 + git_patch_get_hunk@Base 1.9.0 + git_patch_get_line_in_hunk@Base 1.9.0 + git_patch_line_stats@Base 1.9.0 + git_patch_num_hunks@Base 1.9.0 + git_patch_num_lines_in_hunk@Base 1.9.0 + git_patch_owner@Base 1.9.0 + git_patch_print@Base 1.9.0 + git_patch_size@Base 1.9.0 + git_patch_to_buf@Base 1.9.0 + git_path_is_gitfile@Base 1.9.0 + git_pathspec_free@Base 1.9.0 + git_pathspec_match_diff@Base 1.9.0 + git_pathspec_match_index@Base 1.9.0 + git_pathspec_match_list_diff_entry@Base 1.9.0 + git_pathspec_match_list_entry@Base 1.9.0 + git_pathspec_match_list_entrycount@Base 1.9.0 + git_pathspec_match_list_failed_entry@Base 1.9.0 + git_pathspec_match_list_failed_entrycount@Base 1.9.0 + git_pathspec_match_list_free@Base 1.9.0 + git_pathspec_match_tree@Base 1.9.0 + git_pathspec_match_workdir@Base 1.9.0 + git_pathspec_matches_path@Base 1.9.0 + git_pathspec_new@Base 1.9.0 + git_proxy_init_options@Base 1.9.0 + git_proxy_options_init@Base 1.9.0 + git_push_init_options@Base 1.9.0 + git_push_options_init@Base 1.9.0 + git_rebase_abort@Base 1.9.0 + git_rebase_commit@Base 1.9.0 + git_rebase_finish@Base 1.9.0 + git_rebase_free@Base 1.9.0 + git_rebase_init@Base 1.9.0 + git_rebase_init_options@Base 1.9.0 + git_rebase_inmemory_index@Base 1.9.0 + git_rebase_next@Base 1.9.0 + git_rebase_onto_id@Base 1.9.0 + git_rebase_onto_name@Base 1.9.0 + git_rebase_open@Base 1.9.0 + git_rebase_operation_byindex@Base 1.9.0 + git_rebase_operation_current@Base 1.9.0 + git_rebase_operation_entrycount@Base 1.9.0 + git_rebase_options_init@Base 1.9.0 + git_rebase_orig_head_id@Base 1.9.0 + git_rebase_orig_head_name@Base 1.9.0 + git_refdb_backend_fs@Base 1.9.0 + git_refdb_compress@Base 1.9.0 + git_refdb_free@Base 1.9.0 + git_refdb_init_backend@Base 1.9.0 + git_refdb_new@Base 1.9.0 + git_refdb_open@Base 1.9.0 + git_refdb_set_backend@Base 1.9.0 + git_reference__alloc@Base 1.9.0 + git_reference__alloc_symbolic@Base 1.9.0 + git_reference_cmp@Base 1.9.0 + git_reference_create@Base 1.9.0 + git_reference_create_matching@Base 1.9.0 + git_reference_delete@Base 1.9.0 + git_reference_dup@Base 1.9.0 + git_reference_dwim@Base 1.9.0 + git_reference_ensure_log@Base 1.9.0 + git_reference_foreach@Base 1.9.0 + git_reference_foreach_glob@Base 1.9.0 + git_reference_foreach_name@Base 1.9.0 + git_reference_free@Base 1.9.0 + git_reference_has_log@Base 1.9.0 + git_reference_is_branch@Base 1.9.0 + git_reference_is_note@Base 1.9.0 + git_reference_is_remote@Base 1.9.0 + git_reference_is_tag@Base 1.9.0 + git_reference_is_valid_name@Base 1.9.0 + git_reference_iterator_free@Base 1.9.0 + git_reference_iterator_glob_new@Base 1.9.0 + git_reference_iterator_new@Base 1.9.0 + git_reference_list@Base 1.9.0 + git_reference_lookup@Base 1.9.0 + git_reference_name@Base 1.9.0 + git_reference_name_is_valid@Base 1.9.0 + git_reference_name_to_id@Base 1.9.0 + git_reference_next@Base 1.9.0 + git_reference_next_name@Base 1.9.0 + git_reference_normalize_name@Base 1.9.0 + git_reference_owner@Base 1.9.0 + git_reference_peel@Base 1.9.0 + git_reference_remove@Base 1.9.0 + git_reference_rename@Base 1.9.0 + git_reference_resolve@Base 1.9.0 + git_reference_set_target@Base 1.9.0 + git_reference_shorthand@Base 1.9.0 + git_reference_symbolic_create@Base 1.9.0 + git_reference_symbolic_create_matching@Base 1.9.0 + git_reference_symbolic_set_target@Base 1.9.0 + git_reference_symbolic_target@Base 1.9.0 + git_reference_target@Base 1.9.0 + git_reference_target_peel@Base 1.9.0 + git_reference_type@Base 1.9.0 + git_reflog_append@Base 1.9.0 + git_reflog_delete@Base 1.9.0 + git_reflog_drop@Base 1.9.0 + git_reflog_entry_byindex@Base 1.9.0 + git_reflog_entry_committer@Base 1.9.0 + git_reflog_entry_id_new@Base 1.9.0 + git_reflog_entry_id_old@Base 1.9.0 + git_reflog_entry_message@Base 1.9.0 + git_reflog_entrycount@Base 1.9.0 + git_reflog_free@Base 1.9.0 + git_reflog_read@Base 1.9.0 + git_reflog_rename@Base 1.9.0 + git_reflog_write@Base 1.9.0 + git_refspec_direction@Base 1.9.0 + git_refspec_dst@Base 1.9.0 + git_refspec_dst_matches@Base 1.9.0 + git_refspec_force@Base 1.9.0 + git_refspec_free@Base 1.9.0 + git_refspec_parse@Base 1.9.0 + git_refspec_rtransform@Base 1.9.0 + git_refspec_src@Base 1.9.0 + git_refspec_src_matches@Base 1.9.0 + git_refspec_src_matches_negative@Base 1.9.0 + git_refspec_string@Base 1.9.0 + git_refspec_transform@Base 1.9.0 + git_remote_add_fetch@Base 1.9.0 + git_remote_add_push@Base 1.9.0 + git_remote_autotag@Base 1.9.0 + git_remote_connect@Base 1.9.0 + git_remote_connect_ext@Base 1.9.0 + git_remote_connect_options_dispose@Base 1.9.0 + git_remote_connect_options_init@Base 1.9.0 + git_remote_connected@Base 1.9.0 + git_remote_create@Base 1.9.0 + git_remote_create_anonymous@Base 1.9.0 + git_remote_create_detached@Base 1.9.0 + git_remote_create_init_options@Base 1.9.0 + git_remote_create_options_init@Base 1.9.0 + git_remote_create_with_fetchspec@Base 1.9.0 + git_remote_create_with_opts@Base 1.9.0 + git_remote_default_branch@Base 1.9.0 + git_remote_delete@Base 1.9.0 + git_remote_disconnect@Base 1.9.0 + git_remote_download@Base 1.9.0 + git_remote_dup@Base 1.9.0 + git_remote_fetch@Base 1.9.0 + git_remote_free@Base 1.9.0 + git_remote_get_fetch_refspecs@Base 1.9.0 + git_remote_get_push_refspecs@Base 1.9.0 + git_remote_get_refspec@Base 1.9.0 + git_remote_init_callbacks@Base 1.9.0 + git_remote_is_valid_name@Base 1.9.0 + git_remote_list@Base 1.9.0 + git_remote_lookup@Base 1.9.0 + git_remote_ls@Base 1.9.0 + git_remote_name@Base 1.9.0 + git_remote_name_is_valid@Base 1.9.0 + git_remote_owner@Base 1.9.0 + git_remote_prune@Base 1.9.0 + git_remote_prune_refs@Base 1.9.0 + git_remote_push@Base 1.9.0 + git_remote_pushurl@Base 1.9.0 + git_remote_refspec_count@Base 1.9.0 + git_remote_rename@Base 1.9.0 + git_remote_set_autotag@Base 1.9.0 + git_remote_set_instance_pushurl@Base 1.9.0 + git_remote_set_instance_url@Base 1.9.0 + git_remote_set_pushurl@Base 1.9.0 + git_remote_set_url@Base 1.9.0 + git_remote_stats@Base 1.9.0 + git_remote_stop@Base 1.9.0 + git_remote_update_tips@Base 1.9.0 + git_remote_upload@Base 1.9.0 + git_remote_url@Base 1.9.0 + git_repository__cleanup@Base 1.9.0 + git_repository_commit_parents@Base 1.9.0 + git_repository_commondir@Base 1.9.0 + git_repository_config@Base 1.9.0 + git_repository_config_snapshot@Base 1.9.0 + git_repository_detach_head@Base 1.9.0 + git_repository_discover@Base 1.9.0 + git_repository_fetchhead_foreach@Base 1.9.0 + git_repository_free@Base 1.9.0 + git_repository_get_namespace@Base 1.9.0 + git_repository_hashfile@Base 1.9.0 + git_repository_head@Base 1.9.0 + git_repository_head_detached@Base 1.9.0 + git_repository_head_detached_for_worktree@Base 1.9.0 + git_repository_head_for_worktree@Base 1.9.0 + git_repository_head_unborn@Base 1.9.0 + git_repository_ident@Base 1.9.0 + git_repository_index@Base 1.9.0 + git_repository_init@Base 1.9.0 + git_repository_init_ext@Base 1.9.0 + git_repository_init_init_options@Base 1.9.0 + git_repository_init_options_init@Base 1.9.0 + git_repository_is_bare@Base 1.9.0 + git_repository_is_empty@Base 1.9.0 + git_repository_is_shallow@Base 1.9.0 + git_repository_is_worktree@Base 1.9.0 + git_repository_item_path@Base 1.9.0 + git_repository_mergehead_foreach@Base 1.9.0 + git_repository_message@Base 1.9.0 + git_repository_message_remove@Base 1.9.0 + git_repository_new@Base 1.9.0 + git_repository_odb@Base 1.9.0 + git_repository_oid_type@Base 1.9.0 + git_repository_open@Base 1.9.0 + git_repository_open_bare@Base 1.9.0 + git_repository_open_ext@Base 1.9.0 + git_repository_open_from_worktree@Base 1.9.0 + git_repository_path@Base 1.9.0 + git_repository_refdb@Base 1.9.0 + git_repository_reinit_filesystem@Base 1.9.0 + git_repository_set_bare@Base 1.9.0 + git_repository_set_config@Base 1.9.0 + git_repository_set_head@Base 1.9.0 + git_repository_set_head_detached@Base 1.9.0 + git_repository_set_head_detached_from_annotated@Base 1.9.0 + git_repository_set_ident@Base 1.9.0 + git_repository_set_index@Base 1.9.0 + git_repository_set_namespace@Base 1.9.0 + git_repository_set_odb@Base 1.9.0 + git_repository_set_refdb@Base 1.9.0 + git_repository_set_workdir@Base 1.9.0 + git_repository_state@Base 1.9.0 + git_repository_state_cleanup@Base 1.9.0 + git_repository_submodule_cache_all@Base 1.9.0 + git_repository_submodule_cache_clear@Base 1.9.0 + git_repository_workdir@Base 1.9.0 + git_repository_wrap_odb@Base 1.9.0 + git_reset@Base 1.9.0 + git_reset_default@Base 1.9.0 + git_reset_from_annotated@Base 1.9.0 + git_revert@Base 1.9.0 + git_revert_commit@Base 1.9.0 + git_revert_init_options@Base 1.9.0 + git_revert_options_init@Base 1.9.0 + git_revparse@Base 1.9.0 + git_revparse_ext@Base 1.9.0 + git_revparse_single@Base 1.9.0 + git_revwalk_add_hide_cb@Base 1.9.0 + git_revwalk_free@Base 1.9.0 + git_revwalk_hide@Base 1.9.0 + git_revwalk_hide_glob@Base 1.9.0 + git_revwalk_hide_head@Base 1.9.0 + git_revwalk_hide_ref@Base 1.9.0 + git_revwalk_new@Base 1.9.0 + git_revwalk_next@Base 1.9.0 + git_revwalk_push@Base 1.9.0 + git_revwalk_push_glob@Base 1.9.0 + git_revwalk_push_head@Base 1.9.0 + git_revwalk_push_range@Base 1.9.0 + git_revwalk_push_ref@Base 1.9.0 + git_revwalk_repository@Base 1.9.0 + git_revwalk_reset@Base 1.9.0 + git_revwalk_simplify_first_parent@Base 1.9.0 + git_revwalk_sorting@Base 1.9.0 + git_signature_default@Base 1.9.0 + git_signature_default_from_env@Base 1.9.0 + git_signature_dup@Base 1.9.0 + git_signature_free@Base 1.9.0 + git_signature_from_buffer@Base 1.9.0 + git_signature_new@Base 1.9.0 + git_signature_now@Base 1.9.0 + git_smart_subtransport_git@Base 1.9.0 + git_smart_subtransport_http@Base 1.9.0 + git_smart_subtransport_ssh@Base 1.9.0 + git_stash_apply@Base 1.9.0 + git_stash_apply_init_options@Base 1.9.0 + git_stash_apply_options_init@Base 1.9.0 + git_stash_drop@Base 1.9.0 + git_stash_foreach@Base 1.9.0 + git_stash_pop@Base 1.9.0 + git_stash_save@Base 1.9.0 + git_stash_save_options_init@Base 1.9.0 + git_stash_save_with_opts@Base 1.9.0 + git_status_byindex@Base 1.9.0 + git_status_file@Base 1.9.0 + git_status_foreach@Base 1.9.0 + git_status_foreach_ext@Base 1.9.0 + git_status_init_options@Base 1.9.0 + git_status_list_entrycount@Base 1.9.0 + git_status_list_free@Base 1.9.0 + git_status_list_get_perfdata@Base 1.9.0 + git_status_list_new@Base 1.9.0 + git_status_options_init@Base 1.9.0 + git_status_should_ignore@Base 1.9.0 + git_strarray_copy@Base 1.9.0 + git_strarray_dispose@Base 1.9.0 + git_strarray_free@Base 1.9.0 + git_stream_register@Base 1.9.0 + git_stream_register_tls@Base 1.9.0 + git_submodule_add_finalize@Base 1.9.0 + git_submodule_add_setup@Base 1.9.0 + git_submodule_add_to_index@Base 1.9.0 + git_submodule_branch@Base 1.9.0 + git_submodule_clone@Base 1.9.0 + git_submodule_dup@Base 1.9.0 + git_submodule_fetch_recurse_submodules@Base 1.9.0 + git_submodule_foreach@Base 1.9.0 + git_submodule_free@Base 1.9.0 + git_submodule_head_id@Base 1.9.0 + git_submodule_ignore@Base 1.9.0 + git_submodule_index_id@Base 1.9.0 + git_submodule_init@Base 1.9.0 + git_submodule_location@Base 1.9.0 + git_submodule_lookup@Base 1.9.0 + git_submodule_name@Base 1.9.0 + git_submodule_open@Base 1.9.0 + git_submodule_owner@Base 1.9.0 + git_submodule_path@Base 1.9.0 + git_submodule_reload@Base 1.9.0 + git_submodule_repo_init@Base 1.9.0 + git_submodule_resolve_url@Base 1.9.0 + git_submodule_set_branch@Base 1.9.0 + git_submodule_set_fetch_recurse_submodules@Base 1.9.0 + git_submodule_set_ignore@Base 1.9.0 + git_submodule_set_update@Base 1.9.0 + git_submodule_set_url@Base 1.9.0 + git_submodule_status@Base 1.9.0 + git_submodule_sync@Base 1.9.0 + git_submodule_update@Base 1.9.0 + git_submodule_update_init_options@Base 1.9.0 + git_submodule_update_options_init@Base 1.9.0 + git_submodule_update_strategy@Base 1.9.0 + git_submodule_url@Base 1.9.0 + git_submodule_wd_id@Base 1.9.0 + git_tag_annotation_create@Base 1.9.0 + git_tag_create@Base 1.9.0 + git_tag_create_from_buffer@Base 1.9.0 + git_tag_create_frombuffer@Base 1.9.0 + git_tag_create_lightweight@Base 1.9.0 + git_tag_delete@Base 1.9.0 + git_tag_dup@Base 1.9.0 + git_tag_foreach@Base 1.9.0 + git_tag_free@Base 1.9.0 + git_tag_id@Base 1.9.0 + git_tag_list@Base 1.9.0 + git_tag_list_match@Base 1.9.0 + git_tag_lookup@Base 1.9.0 + git_tag_lookup_prefix@Base 1.9.0 + git_tag_message@Base 1.9.0 + git_tag_name@Base 1.9.0 + git_tag_name_is_valid@Base 1.9.0 + git_tag_owner@Base 1.9.0 + git_tag_peel@Base 1.9.0 + git_tag_tagger@Base 1.9.0 + git_tag_target@Base 1.9.0 + git_tag_target_id@Base 1.9.0 + git_tag_target_type@Base 1.9.0 + git_trace_set@Base 1.9.0 + git_transaction_commit@Base 1.9.0 + git_transaction_free@Base 1.9.0 + git_transaction_lock_ref@Base 1.9.0 + git_transaction_new@Base 1.9.0 + git_transaction_remove@Base 1.9.0 + git_transaction_set_reflog@Base 1.9.0 + git_transaction_set_symbolic_target@Base 1.9.0 + git_transaction_set_target@Base 1.9.0 + git_transport_init@Base 1.9.0 + git_transport_local@Base 1.9.0 + git_transport_new@Base 1.9.0 + git_transport_register@Base 1.9.0 + git_transport_remote_connect_options@Base 1.9.0 + git_transport_smart@Base 1.9.0 + git_transport_smart_certificate_check@Base 1.9.0 + git_transport_smart_credentials@Base 1.9.0 + git_transport_ssh_with_paths@Base 1.9.0 + git_transport_unregister@Base 1.9.0 + git_tree_create_updated@Base 1.9.0 + git_tree_dup@Base 1.9.0 + git_tree_entry_byid@Base 1.9.0 + git_tree_entry_byindex@Base 1.9.0 + git_tree_entry_byname@Base 1.9.0 + git_tree_entry_bypath@Base 1.9.0 + git_tree_entry_cmp@Base 1.9.0 + git_tree_entry_dup@Base 1.9.0 + git_tree_entry_filemode@Base 1.9.0 + git_tree_entry_filemode_raw@Base 1.9.0 + git_tree_entry_free@Base 1.9.0 + git_tree_entry_id@Base 1.9.0 + git_tree_entry_name@Base 1.9.0 + git_tree_entry_to_object@Base 1.9.0 + git_tree_entry_type@Base 1.9.0 + git_tree_entrycount@Base 1.9.0 + git_tree_free@Base 1.9.0 + git_tree_id@Base 1.9.0 + git_tree_lookup@Base 1.9.0 + git_tree_lookup_prefix@Base 1.9.0 + git_tree_owner@Base 1.9.0 + git_tree_walk@Base 1.9.0 + git_treebuilder_clear@Base 1.9.0 + git_treebuilder_entrycount@Base 1.9.0 + git_treebuilder_filter@Base 1.9.0 + git_treebuilder_free@Base 1.9.0 + git_treebuilder_get@Base 1.9.0 + git_treebuilder_insert@Base 1.9.0 + git_treebuilder_new@Base 1.9.0 + git_treebuilder_remove@Base 1.9.0 + git_treebuilder_write@Base 1.9.0 + git_treebuilder_write_with_buffer@Base 1.9.0 + git_worktree_add@Base 1.9.0 + git_worktree_add_init_options@Base 1.9.0 + git_worktree_add_options_init@Base 1.9.0 + git_worktree_free@Base 1.9.0 + git_worktree_is_locked@Base 1.9.0 + git_worktree_is_prunable@Base 1.9.0 + git_worktree_list@Base 1.9.0 + git_worktree_lock@Base 1.9.0 + git_worktree_lookup@Base 1.9.0 + git_worktree_name@Base 1.9.0 + git_worktree_open_from_repository@Base 1.9.0 + git_worktree_path@Base 1.9.0 + git_worktree_prune@Base 1.9.0 + git_worktree_prune_init_options@Base 1.9.0 + git_worktree_prune_options_init@Base 1.9.0 + git_worktree_unlock@Base 1.9.0 + git_worktree_validate@Base 1.9.0 + giterr_clear@Base 1.9.0 + giterr_last@Base 1.9.0 + giterr_set_oom@Base 1.9.0 + giterr_set_str@Base 1.9.0 diff --git a/debian/libgit2-dev.install b/debian/libgit2-dev.install index afb1d67c..c796dce7 100644 --- a/debian/libgit2-dev.install +++ b/debian/libgit2-dev.install @@ -1,4 +1,5 @@ -/usr/include -/usr/lib/*/lib*.a -/usr/lib/*/lib*.so -/usr/lib/*/pkgconfig +usr/include +usr/lib/*/cmake +usr/lib/*/lib*.a +usr/lib/*/lib*.so +usr/lib/*/pkgconfig diff --git a/debian/libgit2-experimental-dev.install b/debian/libgit2-experimental-dev.install new file mode 100644 index 00000000..c796dce7 --- /dev/null +++ b/debian/libgit2-experimental-dev.install @@ -0,0 +1,5 @@ +usr/include +usr/lib/*/cmake +usr/lib/*/lib*.a +usr/lib/*/lib*.so +usr/lib/*/pkgconfig diff --git a/debian/libgit2-experimental-dev.links b/debian/libgit2-experimental-dev.links new file mode 100644 index 00000000..de479c6f --- /dev/null +++ b/debian/libgit2-experimental-dev.links @@ -0,0 +1,2 @@ +usr/include/git2-experimental.h usr/include/git2.h +usr/include/git2-experimental usr/include/git2 diff --git a/debian/libgit2-experimental.map b/debian/libgit2-experimental.map new file mode 100644 index 00000000..933bd9a1 --- /dev/null +++ b/debian/libgit2-experimental.map @@ -0,0 +1,4 @@ +SHA256 { + *; +}; + diff --git a/debian/libgit2-experimental1.9.install b/debian/libgit2-experimental1.9.install new file mode 100644 index 00000000..3ddde584 --- /dev/null +++ b/debian/libgit2-experimental1.9.install @@ -0,0 +1 @@ +usr/lib/*/lib*.so.* diff --git a/debian/libgit2-experimental1.9.symbols b/debian/libgit2-experimental1.9.symbols new file mode 100644 index 00000000..42491e74 --- /dev/null +++ b/debian/libgit2-experimental1.9.symbols @@ -0,0 +1,956 @@ +libgit2-experimental.so.1.9 libgit2-experimental1.9 #MINVER# +* Build-Depends-Package: libgit2-experimental-dev + SHA256@SHA256 1.9.2+ds-6~ + git_annotated_commit_free@SHA256 1.9.2+ds-6~ + git_annotated_commit_from_fetchhead@SHA256 1.9.2+ds-6~ + git_annotated_commit_from_ref@SHA256 1.9.2+ds-6~ + git_annotated_commit_from_revspec@SHA256 1.9.2+ds-6~ + git_annotated_commit_id@SHA256 1.9.2+ds-6~ + git_annotated_commit_lookup@SHA256 1.9.2+ds-6~ + git_annotated_commit_ref@SHA256 1.9.2+ds-6~ + git_apply@SHA256 1.9.2+ds-6~ + git_apply_options_init@SHA256 1.9.2+ds-6~ + git_apply_to_tree@SHA256 1.9.2+ds-6~ + git_attr_add_macro@SHA256 1.9.2+ds-6~ + git_attr_cache_flush@SHA256 1.9.2+ds-6~ + git_attr_foreach@SHA256 1.9.2+ds-6~ + git_attr_foreach_ext@SHA256 1.9.2+ds-6~ + git_attr_get@SHA256 1.9.2+ds-6~ + git_attr_get_ext@SHA256 1.9.2+ds-6~ + git_attr_get_many@SHA256 1.9.2+ds-6~ + git_attr_get_many_ext@SHA256 1.9.2+ds-6~ + git_attr_value@SHA256 1.9.2+ds-6~ + git_blame_buffer@SHA256 1.9.2+ds-6~ + git_blame_file@SHA256 1.9.2+ds-6~ + git_blame_free@SHA256 1.9.2+ds-6~ + git_blame_get_hunk_byindex@SHA256 1.9.2+ds-6~ + git_blame_get_hunk_byline@SHA256 1.9.2+ds-6~ + git_blame_get_hunk_count@SHA256 1.9.2+ds-6~ + git_blame_hunk_byindex@SHA256 1.9.2+ds-6~ + git_blame_hunk_byline@SHA256 1.9.2+ds-6~ + git_blame_hunkcount@SHA256 1.9.2+ds-6~ + git_blame_init_options@SHA256 1.9.2+ds-6~ + git_blame_line_byindex@SHA256 1.9.2+ds-6~ + git_blame_linecount@SHA256 1.9.2+ds-6~ + git_blame_options_init@SHA256 1.9.2+ds-6~ + git_blob_create_from_buffer@SHA256 1.9.2+ds-6~ + git_blob_create_from_disk@SHA256 1.9.2+ds-6~ + git_blob_create_from_stream@SHA256 1.9.2+ds-6~ + git_blob_create_from_stream_commit@SHA256 1.9.2+ds-6~ + git_blob_create_from_workdir@SHA256 1.9.2+ds-6~ + git_blob_create_frombuffer@SHA256 1.9.2+ds-6~ + git_blob_create_fromdisk@SHA256 1.9.2+ds-6~ + git_blob_create_fromstream@SHA256 1.9.2+ds-6~ + git_blob_create_fromstream_commit@SHA256 1.9.2+ds-6~ + git_blob_create_fromworkdir@SHA256 1.9.2+ds-6~ + git_blob_data_is_binary@SHA256 1.9.2+ds-6~ + git_blob_dup@SHA256 1.9.2+ds-6~ + git_blob_filter@SHA256 1.9.2+ds-6~ + git_blob_filter_options_init@SHA256 1.9.2+ds-6~ + git_blob_filtered_content@SHA256 1.9.2+ds-6~ + git_blob_free@SHA256 1.9.2+ds-6~ + git_blob_id@SHA256 1.9.2+ds-6~ + git_blob_is_binary@SHA256 1.9.2+ds-6~ + git_blob_lookup@SHA256 1.9.2+ds-6~ + git_blob_lookup_prefix@SHA256 1.9.2+ds-6~ + git_blob_owner@SHA256 1.9.2+ds-6~ + git_blob_rawcontent@SHA256 1.9.2+ds-6~ + git_blob_rawsize@SHA256 1.9.2+ds-6~ + git_branch_create@SHA256 1.9.2+ds-6~ + git_branch_create_from_annotated@SHA256 1.9.2+ds-6~ + git_branch_delete@SHA256 1.9.2+ds-6~ + git_branch_is_checked_out@SHA256 1.9.2+ds-6~ + git_branch_is_head@SHA256 1.9.2+ds-6~ + git_branch_iterator_free@SHA256 1.9.2+ds-6~ + git_branch_iterator_new@SHA256 1.9.2+ds-6~ + git_branch_lookup@SHA256 1.9.2+ds-6~ + git_branch_move@SHA256 1.9.2+ds-6~ + git_branch_name@SHA256 1.9.2+ds-6~ + git_branch_name_is_valid@SHA256 1.9.2+ds-6~ + git_branch_next@SHA256 1.9.2+ds-6~ + git_branch_remote_name@SHA256 1.9.2+ds-6~ + git_branch_set_upstream@SHA256 1.9.2+ds-6~ + git_branch_upstream@SHA256 1.9.2+ds-6~ + git_branch_upstream_merge@SHA256 1.9.2+ds-6~ + git_branch_upstream_name@SHA256 1.9.2+ds-6~ + git_branch_upstream_remote@SHA256 1.9.2+ds-6~ + git_buf_contains_nul@SHA256 1.9.2+ds-6~ + git_buf_dispose@SHA256 1.9.2+ds-6~ + git_buf_free@SHA256 1.9.2+ds-6~ + git_buf_grow@SHA256 1.9.2+ds-6~ + git_buf_is_binary@SHA256 1.9.2+ds-6~ + git_buf_set@SHA256 1.9.2+ds-6~ + git_checkout_head@SHA256 1.9.2+ds-6~ + git_checkout_index@SHA256 1.9.2+ds-6~ + git_checkout_init_options@SHA256 1.9.2+ds-6~ + git_checkout_options_init@SHA256 1.9.2+ds-6~ + git_checkout_tree@SHA256 1.9.2+ds-6~ + git_cherrypick@SHA256 1.9.2+ds-6~ + git_cherrypick_commit@SHA256 1.9.2+ds-6~ + git_cherrypick_init_options@SHA256 1.9.2+ds-6~ + git_cherrypick_options_init@SHA256 1.9.2+ds-6~ + git_clone@SHA256 1.9.2+ds-6~ + git_clone_init_options@SHA256 1.9.2+ds-6~ + git_clone_options_init@SHA256 1.9.2+ds-6~ + git_commit_amend@SHA256 1.9.2+ds-6~ + git_commit_author@SHA256 1.9.2+ds-6~ + git_commit_author_with_mailmap@SHA256 1.9.2+ds-6~ + git_commit_body@SHA256 1.9.2+ds-6~ + git_commit_committer@SHA256 1.9.2+ds-6~ + git_commit_committer_with_mailmap@SHA256 1.9.2+ds-6~ + git_commit_create@SHA256 1.9.2+ds-6~ + git_commit_create_buffer@SHA256 1.9.2+ds-6~ + git_commit_create_from_callback@SHA256 1.9.2+ds-6~ + git_commit_create_from_ids@SHA256 1.9.2+ds-6~ + git_commit_create_from_stage@SHA256 1.9.2+ds-6~ + git_commit_create_v@SHA256 1.9.2+ds-6~ + git_commit_create_with_signature@SHA256 1.9.2+ds-6~ + git_commit_dup@SHA256 1.9.2+ds-6~ + git_commit_extract_signature@SHA256 1.9.2+ds-6~ + git_commit_free@SHA256 1.9.2+ds-6~ + git_commit_graph_free@SHA256 1.9.2+ds-6~ + git_commit_graph_open@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_add_index_file@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_add_revwalk@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_commit@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_dump@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_free@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_new@SHA256 1.9.2+ds-6~ + git_commit_graph_writer_options_init@SHA256 1.9.2+ds-6~ + git_commit_header_field@SHA256 1.9.2+ds-6~ + git_commit_id@SHA256 1.9.2+ds-6~ + git_commit_lookup@SHA256 1.9.2+ds-6~ + git_commit_lookup_prefix@SHA256 1.9.2+ds-6~ + git_commit_message@SHA256 1.9.2+ds-6~ + git_commit_message_encoding@SHA256 1.9.2+ds-6~ + git_commit_message_raw@SHA256 1.9.2+ds-6~ + git_commit_nth_gen_ancestor@SHA256 1.9.2+ds-6~ + git_commit_owner@SHA256 1.9.2+ds-6~ + git_commit_parent@SHA256 1.9.2+ds-6~ + git_commit_parent_id@SHA256 1.9.2+ds-6~ + git_commit_parentcount@SHA256 1.9.2+ds-6~ + git_commit_raw_header@SHA256 1.9.2+ds-6~ + git_commit_summary@SHA256 1.9.2+ds-6~ + git_commit_time@SHA256 1.9.2+ds-6~ + git_commit_time_offset@SHA256 1.9.2+ds-6~ + git_commit_tree@SHA256 1.9.2+ds-6~ + git_commit_tree_id@SHA256 1.9.2+ds-6~ + git_commitarray_dispose@SHA256 1.9.2+ds-6~ + git_config_add_backend@SHA256 1.9.2+ds-6~ + git_config_add_file_ondisk@SHA256 1.9.2+ds-6~ + git_config_backend_foreach_match@SHA256 1.9.2+ds-6~ + git_config_delete_entry@SHA256 1.9.2+ds-6~ + git_config_delete_multivar@SHA256 1.9.2+ds-6~ + git_config_entry_free@SHA256 1.9.2+ds-6~ + git_config_find_global@SHA256 1.9.2+ds-6~ + git_config_find_programdata@SHA256 1.9.2+ds-6~ + git_config_find_system@SHA256 1.9.2+ds-6~ + git_config_find_xdg@SHA256 1.9.2+ds-6~ + git_config_foreach@SHA256 1.9.2+ds-6~ + git_config_foreach_match@SHA256 1.9.2+ds-6~ + git_config_free@SHA256 1.9.2+ds-6~ + git_config_get_bool@SHA256 1.9.2+ds-6~ + git_config_get_entry@SHA256 1.9.2+ds-6~ + git_config_get_int32@SHA256 1.9.2+ds-6~ + git_config_get_int64@SHA256 1.9.2+ds-6~ + git_config_get_mapped@SHA256 1.9.2+ds-6~ + git_config_get_multivar_foreach@SHA256 1.9.2+ds-6~ + git_config_get_path@SHA256 1.9.2+ds-6~ + git_config_get_string@SHA256 1.9.2+ds-6~ + git_config_get_string_buf@SHA256 1.9.2+ds-6~ + git_config_init_backend@SHA256 1.9.2+ds-6~ + git_config_iterator_free@SHA256 1.9.2+ds-6~ + git_config_iterator_glob_new@SHA256 1.9.2+ds-6~ + git_config_iterator_new@SHA256 1.9.2+ds-6~ + git_config_lock@SHA256 1.9.2+ds-6~ + git_config_lookup_map_value@SHA256 1.9.2+ds-6~ + git_config_multivar_iterator_new@SHA256 1.9.2+ds-6~ + git_config_new@SHA256 1.9.2+ds-6~ + git_config_next@SHA256 1.9.2+ds-6~ + git_config_open_default@SHA256 1.9.2+ds-6~ + git_config_open_global@SHA256 1.9.2+ds-6~ + git_config_open_level@SHA256 1.9.2+ds-6~ + git_config_open_ondisk@SHA256 1.9.2+ds-6~ + git_config_parse_bool@SHA256 1.9.2+ds-6~ + git_config_parse_int32@SHA256 1.9.2+ds-6~ + git_config_parse_int64@SHA256 1.9.2+ds-6~ + git_config_parse_path@SHA256 1.9.2+ds-6~ + git_config_set_bool@SHA256 1.9.2+ds-6~ + git_config_set_int32@SHA256 1.9.2+ds-6~ + git_config_set_int64@SHA256 1.9.2+ds-6~ + git_config_set_multivar@SHA256 1.9.2+ds-6~ + git_config_set_string@SHA256 1.9.2+ds-6~ + git_config_set_writeorder@SHA256 1.9.2+ds-6~ + git_config_snapshot@SHA256 1.9.2+ds-6~ + git_config_unlock@SHA256 1.9.2+ds-6~ + git_cred_default_new@SHA256 1.9.2+ds-6~ + git_cred_free@SHA256 1.9.2+ds-6~ + git_cred_get_username@SHA256 1.9.2+ds-6~ + git_cred_has_username@SHA256 1.9.2+ds-6~ + git_cred_ssh_custom_new@SHA256 1.9.2+ds-6~ + git_cred_ssh_interactive_new@SHA256 1.9.2+ds-6~ + git_cred_ssh_key_from_agent@SHA256 1.9.2+ds-6~ + git_cred_ssh_key_memory_new@SHA256 1.9.2+ds-6~ + git_cred_ssh_key_new@SHA256 1.9.2+ds-6~ + git_cred_username_new@SHA256 1.9.2+ds-6~ + git_cred_userpass@SHA256 1.9.2+ds-6~ + git_cred_userpass_plaintext_new@SHA256 1.9.2+ds-6~ + git_credential_default_new@SHA256 1.9.2+ds-6~ + git_credential_free@SHA256 1.9.2+ds-6~ + git_credential_get_username@SHA256 1.9.2+ds-6~ + git_credential_has_username@SHA256 1.9.2+ds-6~ + git_credential_ssh_custom_new@SHA256 1.9.2+ds-6~ + git_credential_ssh_interactive_new@SHA256 1.9.2+ds-6~ + git_credential_ssh_key_from_agent@SHA256 1.9.2+ds-6~ + git_credential_ssh_key_memory_new@SHA256 1.9.2+ds-6~ + git_credential_ssh_key_new@SHA256 1.9.2+ds-6~ + git_credential_username_new@SHA256 1.9.2+ds-6~ + git_credential_userpass@SHA256 1.9.2+ds-6~ + git_credential_userpass_plaintext_new@SHA256 1.9.2+ds-6~ + git_describe_commit@SHA256 1.9.2+ds-6~ + git_describe_format@SHA256 1.9.2+ds-6~ + git_describe_format_options_init@SHA256 1.9.2+ds-6~ + git_describe_init_format_options@SHA256 1.9.2+ds-6~ + git_describe_init_options@SHA256 1.9.2+ds-6~ + git_describe_options_init@SHA256 1.9.2+ds-6~ + git_describe_result_free@SHA256 1.9.2+ds-6~ + git_describe_workdir@SHA256 1.9.2+ds-6~ + git_diff_blob_to_buffer@SHA256 1.9.2+ds-6~ + git_diff_blobs@SHA256 1.9.2+ds-6~ + git_diff_buffers@SHA256 1.9.2+ds-6~ + git_diff_commit_as_email@SHA256 1.9.2+ds-6~ + git_diff_find_init_options@SHA256 1.9.2+ds-6~ + git_diff_find_options_init@SHA256 1.9.2+ds-6~ + git_diff_find_similar@SHA256 1.9.2+ds-6~ + git_diff_foreach@SHA256 1.9.2+ds-6~ + git_diff_format_email@SHA256 1.9.2+ds-6~ + git_diff_format_email_init_options@SHA256 1.9.2+ds-6~ + git_diff_format_email_options_init@SHA256 1.9.2+ds-6~ + git_diff_free@SHA256 1.9.2+ds-6~ + git_diff_from_buffer@SHA256 1.9.2+ds-6~ + git_diff_get_delta@SHA256 1.9.2+ds-6~ + git_diff_get_perfdata@SHA256 1.9.2+ds-6~ + git_diff_get_stats@SHA256 1.9.2+ds-6~ + git_diff_index_to_index@SHA256 1.9.2+ds-6~ + git_diff_index_to_workdir@SHA256 1.9.2+ds-6~ + git_diff_init_options@SHA256 1.9.2+ds-6~ + git_diff_is_sorted_icase@SHA256 1.9.2+ds-6~ + git_diff_merge@SHA256 1.9.2+ds-6~ + git_diff_num_deltas@SHA256 1.9.2+ds-6~ + git_diff_num_deltas_of_type@SHA256 1.9.2+ds-6~ + git_diff_options_init@SHA256 1.9.2+ds-6~ + git_diff_patchid@SHA256 1.9.2+ds-6~ + git_diff_patchid_options_init@SHA256 1.9.2+ds-6~ + git_diff_print@SHA256 1.9.2+ds-6~ + git_diff_print_callback__to_buf@SHA256 1.9.2+ds-6~ + git_diff_print_callback__to_file_handle@SHA256 1.9.2+ds-6~ + git_diff_stats_deletions@SHA256 1.9.2+ds-6~ + git_diff_stats_files_changed@SHA256 1.9.2+ds-6~ + git_diff_stats_free@SHA256 1.9.2+ds-6~ + git_diff_stats_insertions@SHA256 1.9.2+ds-6~ + git_diff_stats_to_buf@SHA256 1.9.2+ds-6~ + git_diff_status_char@SHA256 1.9.2+ds-6~ + git_diff_to_buf@SHA256 1.9.2+ds-6~ + git_diff_tree_to_index@SHA256 1.9.2+ds-6~ + git_diff_tree_to_tree@SHA256 1.9.2+ds-6~ + git_diff_tree_to_workdir@SHA256 1.9.2+ds-6~ + git_diff_tree_to_workdir_with_index@SHA256 1.9.2+ds-6~ + git_email_create_from_commit@SHA256 1.9.2+ds-6~ + git_email_create_from_diff@SHA256 1.9.2+ds-6~ + git_error_clear@SHA256 1.9.2+ds-6~ + git_error_last@SHA256 1.9.2+ds-6~ + git_error_set@SHA256 1.9.2+ds-6~ + git_error_set_oom@SHA256 1.9.2+ds-6~ + git_error_set_str@SHA256 1.9.2+ds-6~ + git_fetch_init_options@SHA256 1.9.2+ds-6~ + git_fetch_options_init@SHA256 1.9.2+ds-6~ + git_filter_init@SHA256 1.9.2+ds-6~ + git_filter_list_apply_to_blob@SHA256 1.9.2+ds-6~ + git_filter_list_apply_to_buffer@SHA256 1.9.2+ds-6~ + git_filter_list_apply_to_data@SHA256 1.9.2+ds-6~ + git_filter_list_apply_to_file@SHA256 1.9.2+ds-6~ + git_filter_list_contains@SHA256 1.9.2+ds-6~ + git_filter_list_free@SHA256 1.9.2+ds-6~ + git_filter_list_length@SHA256 1.9.2+ds-6~ + git_filter_list_load@SHA256 1.9.2+ds-6~ + git_filter_list_load_ext@SHA256 1.9.2+ds-6~ + git_filter_list_new@SHA256 1.9.2+ds-6~ + git_filter_list_push@SHA256 1.9.2+ds-6~ + git_filter_list_stream_blob@SHA256 1.9.2+ds-6~ + git_filter_list_stream_buffer@SHA256 1.9.2+ds-6~ + git_filter_list_stream_data@SHA256 1.9.2+ds-6~ + git_filter_list_stream_file@SHA256 1.9.2+ds-6~ + git_filter_lookup@SHA256 1.9.2+ds-6~ + git_filter_register@SHA256 1.9.2+ds-6~ + git_filter_source_filemode@SHA256 1.9.2+ds-6~ + git_filter_source_flags@SHA256 1.9.2+ds-6~ + git_filter_source_id@SHA256 1.9.2+ds-6~ + git_filter_source_mode@SHA256 1.9.2+ds-6~ + git_filter_source_path@SHA256 1.9.2+ds-6~ + git_filter_source_repo@SHA256 1.9.2+ds-6~ + git_filter_unregister@SHA256 1.9.2+ds-6~ + git_graph_ahead_behind@SHA256 1.9.2+ds-6~ + git_graph_descendant_of@SHA256 1.9.2+ds-6~ + git_graph_reachable_from_any@SHA256 1.9.2+ds-6~ + git_hashsig_compare@SHA256 1.9.2+ds-6~ + git_hashsig_create@SHA256 1.9.2+ds-6~ + git_hashsig_create_fromfile@SHA256 1.9.2+ds-6~ + git_hashsig_free@SHA256 1.9.2+ds-6~ + git_ignore_add_rule@SHA256 1.9.2+ds-6~ + git_ignore_clear_internal_rules@SHA256 1.9.2+ds-6~ + git_ignore_path_is_ignored@SHA256 1.9.2+ds-6~ + git_index_add@SHA256 1.9.2+ds-6~ + git_index_add_all@SHA256 1.9.2+ds-6~ + git_index_add_bypath@SHA256 1.9.2+ds-6~ + git_index_add_from_buffer@SHA256 1.9.2+ds-6~ + git_index_add_frombuffer@SHA256 1.9.2+ds-6~ + git_index_caps@SHA256 1.9.2+ds-6~ + git_index_checksum@SHA256 1.9.2+ds-6~ + git_index_clear@SHA256 1.9.2+ds-6~ + git_index_conflict_add@SHA256 1.9.2+ds-6~ + git_index_conflict_cleanup@SHA256 1.9.2+ds-6~ + git_index_conflict_get@SHA256 1.9.2+ds-6~ + git_index_conflict_iterator_free@SHA256 1.9.2+ds-6~ + git_index_conflict_iterator_new@SHA256 1.9.2+ds-6~ + git_index_conflict_next@SHA256 1.9.2+ds-6~ + git_index_conflict_remove@SHA256 1.9.2+ds-6~ + git_index_entry_is_conflict@SHA256 1.9.2+ds-6~ + git_index_entry_stage@SHA256 1.9.2+ds-6~ + git_index_entrycount@SHA256 1.9.2+ds-6~ + git_index_find@SHA256 1.9.2+ds-6~ + git_index_find_prefix@SHA256 1.9.2+ds-6~ + git_index_free@SHA256 1.9.2+ds-6~ + git_index_get_byindex@SHA256 1.9.2+ds-6~ + git_index_get_bypath@SHA256 1.9.2+ds-6~ + git_index_has_conflicts@SHA256 1.9.2+ds-6~ + git_index_iterator_free@SHA256 1.9.2+ds-6~ + git_index_iterator_new@SHA256 1.9.2+ds-6~ + git_index_iterator_next@SHA256 1.9.2+ds-6~ + git_index_name_add@SHA256 1.9.2+ds-6~ + git_index_name_clear@SHA256 1.9.2+ds-6~ + git_index_name_entrycount@SHA256 1.9.2+ds-6~ + git_index_name_get_byindex@SHA256 1.9.2+ds-6~ + git_index_new@SHA256 1.9.2+ds-6~ + git_index_open@SHA256 1.9.2+ds-6~ + git_index_owner@SHA256 1.9.2+ds-6~ + git_index_path@SHA256 1.9.2+ds-6~ + git_index_read@SHA256 1.9.2+ds-6~ + git_index_read_tree@SHA256 1.9.2+ds-6~ + git_index_remove@SHA256 1.9.2+ds-6~ + git_index_remove_all@SHA256 1.9.2+ds-6~ + git_index_remove_bypath@SHA256 1.9.2+ds-6~ + git_index_remove_directory@SHA256 1.9.2+ds-6~ + git_index_reuc_add@SHA256 1.9.2+ds-6~ + git_index_reuc_clear@SHA256 1.9.2+ds-6~ + git_index_reuc_entrycount@SHA256 1.9.2+ds-6~ + git_index_reuc_find@SHA256 1.9.2+ds-6~ + git_index_reuc_get_byindex@SHA256 1.9.2+ds-6~ + git_index_reuc_get_bypath@SHA256 1.9.2+ds-6~ + git_index_reuc_remove@SHA256 1.9.2+ds-6~ + git_index_set_caps@SHA256 1.9.2+ds-6~ + git_index_set_version@SHA256 1.9.2+ds-6~ + git_index_update_all@SHA256 1.9.2+ds-6~ + git_index_version@SHA256 1.9.2+ds-6~ + git_index_write@SHA256 1.9.2+ds-6~ + git_index_write_tree@SHA256 1.9.2+ds-6~ + git_index_write_tree_to@SHA256 1.9.2+ds-6~ + git_indexer_append@SHA256 1.9.2+ds-6~ + git_indexer_commit@SHA256 1.9.2+ds-6~ + git_indexer_free@SHA256 1.9.2+ds-6~ + git_indexer_hash@SHA256 1.9.2+ds-6~ + git_indexer_init_options@SHA256 1.9.2+ds-6~ + git_indexer_name@SHA256 1.9.2+ds-6~ + git_indexer_new@SHA256 1.9.2+ds-6~ + git_indexer_options_init@SHA256 1.9.2+ds-6~ + git_libgit2_feature_backend@SHA256 1.9.2+ds-6~ + git_libgit2_features@SHA256 1.9.2+ds-6~ + git_libgit2_init@SHA256 1.9.2+ds-6~ + git_libgit2_opts@SHA256 1.9.2+ds-6~ + git_libgit2_prerelease@SHA256 1.9.2+ds-6~ + git_libgit2_shutdown@SHA256 1.9.2+ds-6~ + git_libgit2_version@SHA256 1.9.2+ds-6~ + git_mailmap_add_entry@SHA256 1.9.2+ds-6~ + git_mailmap_free@SHA256 1.9.2+ds-6~ + git_mailmap_from_buffer@SHA256 1.9.2+ds-6~ + git_mailmap_from_repository@SHA256 1.9.2+ds-6~ + git_mailmap_new@SHA256 1.9.2+ds-6~ + git_mailmap_resolve@SHA256 1.9.2+ds-6~ + git_mailmap_resolve_signature@SHA256 1.9.2+ds-6~ + git_mempack_dump@SHA256 1.9.2+ds-6~ + git_mempack_new@SHA256 1.9.2+ds-6~ + git_mempack_object_count@SHA256 1.9.2+ds-6~ + git_mempack_reset@SHA256 1.9.2+ds-6~ + git_mempack_write_thin_pack@SHA256 1.9.2+ds-6~ + git_merge@SHA256 1.9.2+ds-6~ + git_merge_analysis@SHA256 1.9.2+ds-6~ + git_merge_analysis_for_ref@SHA256 1.9.2+ds-6~ + git_merge_base@SHA256 1.9.2+ds-6~ + git_merge_base_many@SHA256 1.9.2+ds-6~ + git_merge_base_octopus@SHA256 1.9.2+ds-6~ + git_merge_bases@SHA256 1.9.2+ds-6~ + git_merge_bases_many@SHA256 1.9.2+ds-6~ + git_merge_commits@SHA256 1.9.2+ds-6~ + git_merge_driver_lookup@SHA256 1.9.2+ds-6~ + git_merge_driver_register@SHA256 1.9.2+ds-6~ + git_merge_driver_source_ancestor@SHA256 1.9.2+ds-6~ + git_merge_driver_source_file_options@SHA256 1.9.2+ds-6~ + git_merge_driver_source_ours@SHA256 1.9.2+ds-6~ + git_merge_driver_source_repo@SHA256 1.9.2+ds-6~ + git_merge_driver_source_theirs@SHA256 1.9.2+ds-6~ + git_merge_driver_unregister@SHA256 1.9.2+ds-6~ + git_merge_file@SHA256 1.9.2+ds-6~ + git_merge_file_from_index@SHA256 1.9.2+ds-6~ + git_merge_file_init_input@SHA256 1.9.2+ds-6~ + git_merge_file_init_options@SHA256 1.9.2+ds-6~ + git_merge_file_input_init@SHA256 1.9.2+ds-6~ + git_merge_file_options_init@SHA256 1.9.2+ds-6~ + git_merge_file_result_free@SHA256 1.9.2+ds-6~ + git_merge_init_options@SHA256 1.9.2+ds-6~ + git_merge_options_init@SHA256 1.9.2+ds-6~ + git_merge_trees@SHA256 1.9.2+ds-6~ + git_message_prettify@SHA256 1.9.2+ds-6~ + git_message_trailer_array_free@SHA256 1.9.2+ds-6~ + git_message_trailers@SHA256 1.9.2+ds-6~ + git_midx_writer_add@SHA256 1.9.2+ds-6~ + git_midx_writer_commit@SHA256 1.9.2+ds-6~ + git_midx_writer_dump@SHA256 1.9.2+ds-6~ + git_midx_writer_free@SHA256 1.9.2+ds-6~ + git_midx_writer_new@SHA256 1.9.2+ds-6~ + git_note_author@SHA256 1.9.2+ds-6~ + git_note_commit_create@SHA256 1.9.2+ds-6~ + git_note_commit_iterator_new@SHA256 1.9.2+ds-6~ + git_note_commit_read@SHA256 1.9.2+ds-6~ + git_note_commit_remove@SHA256 1.9.2+ds-6~ + git_note_committer@SHA256 1.9.2+ds-6~ + git_note_create@SHA256 1.9.2+ds-6~ + git_note_default_ref@SHA256 1.9.2+ds-6~ + git_note_foreach@SHA256 1.9.2+ds-6~ + git_note_free@SHA256 1.9.2+ds-6~ + git_note_id@SHA256 1.9.2+ds-6~ + git_note_iterator_free@SHA256 1.9.2+ds-6~ + git_note_iterator_new@SHA256 1.9.2+ds-6~ + git_note_message@SHA256 1.9.2+ds-6~ + git_note_next@SHA256 1.9.2+ds-6~ + git_note_read@SHA256 1.9.2+ds-6~ + git_note_remove@SHA256 1.9.2+ds-6~ + git_object__size@SHA256 1.9.2+ds-6~ + git_object_dup@SHA256 1.9.2+ds-6~ + git_object_free@SHA256 1.9.2+ds-6~ + git_object_id@SHA256 1.9.2+ds-6~ + git_object_lookup@SHA256 1.9.2+ds-6~ + git_object_lookup_bypath@SHA256 1.9.2+ds-6~ + git_object_lookup_prefix@SHA256 1.9.2+ds-6~ + git_object_owner@SHA256 1.9.2+ds-6~ + git_object_peel@SHA256 1.9.2+ds-6~ + git_object_rawcontent_is_valid@SHA256 1.9.2+ds-6~ + git_object_short_id@SHA256 1.9.2+ds-6~ + git_object_string2type@SHA256 1.9.2+ds-6~ + git_object_type2string@SHA256 1.9.2+ds-6~ + git_object_type@SHA256 1.9.2+ds-6~ + git_object_typeisloose@SHA256 1.9.2+ds-6~ + git_odb__backend_loose@SHA256 1.9.2+ds-6~ + git_odb_add_alternate@SHA256 1.9.2+ds-6~ + git_odb_add_backend@SHA256 1.9.2+ds-6~ + git_odb_add_disk_alternate@SHA256 1.9.2+ds-6~ + git_odb_backend_data_alloc@SHA256 1.9.2+ds-6~ + git_odb_backend_data_free@SHA256 1.9.2+ds-6~ + git_odb_backend_loose@SHA256 1.9.2+ds-6~ + git_odb_backend_malloc@SHA256 1.9.2+ds-6~ + git_odb_backend_one_pack@SHA256 1.9.2+ds-6~ + git_odb_backend_pack@SHA256 1.9.2+ds-6~ + git_odb_exists@SHA256 1.9.2+ds-6~ + git_odb_exists_ext@SHA256 1.9.2+ds-6~ + git_odb_exists_prefix@SHA256 1.9.2+ds-6~ + git_odb_expand_ids@SHA256 1.9.2+ds-6~ + git_odb_foreach@SHA256 1.9.2+ds-6~ + git_odb_free@SHA256 1.9.2+ds-6~ + git_odb_get_backend@SHA256 1.9.2+ds-6~ + git_odb_hash@SHA256 1.9.2+ds-6~ + git_odb_hashfile@SHA256 1.9.2+ds-6~ + git_odb_init_backend@SHA256 1.9.2+ds-6~ + git_odb_new@SHA256 1.9.2+ds-6~ + git_odb_num_backends@SHA256 1.9.2+ds-6~ + git_odb_object_data@SHA256 1.9.2+ds-6~ + git_odb_object_dup@SHA256 1.9.2+ds-6~ + git_odb_object_free@SHA256 1.9.2+ds-6~ + git_odb_object_id@SHA256 1.9.2+ds-6~ + git_odb_object_size@SHA256 1.9.2+ds-6~ + git_odb_object_type@SHA256 1.9.2+ds-6~ + git_odb_open@SHA256 1.9.2+ds-6~ + git_odb_open_rstream@SHA256 1.9.2+ds-6~ + git_odb_open_wstream@SHA256 1.9.2+ds-6~ + git_odb_read@SHA256 1.9.2+ds-6~ + git_odb_read_header@SHA256 1.9.2+ds-6~ + git_odb_read_prefix@SHA256 1.9.2+ds-6~ + git_odb_refresh@SHA256 1.9.2+ds-6~ + git_odb_set_commit_graph@SHA256 1.9.2+ds-6~ + git_odb_stream_finalize_write@SHA256 1.9.2+ds-6~ + git_odb_stream_free@SHA256 1.9.2+ds-6~ + git_odb_stream_read@SHA256 1.9.2+ds-6~ + git_odb_stream_write@SHA256 1.9.2+ds-6~ + git_odb_write@SHA256 1.9.2+ds-6~ + git_odb_write_multi_pack_index@SHA256 1.9.2+ds-6~ + git_odb_write_pack@SHA256 1.9.2+ds-6~ + git_oid_cmp@SHA256 1.9.2+ds-6~ + git_oid_cpy@SHA256 1.9.2+ds-6~ + git_oid_equal@SHA256 1.9.2+ds-6~ + git_oid_fmt@SHA256 1.9.2+ds-6~ + git_oid_fromraw@SHA256 1.9.2+ds-6~ + git_oid_fromstr@SHA256 1.9.2+ds-6~ + git_oid_fromstrn@SHA256 1.9.2+ds-6~ + git_oid_fromstrp@SHA256 1.9.2+ds-6~ + git_oid_is_zero@SHA256 1.9.2+ds-6~ + git_oid_iszero@SHA256 1.9.2+ds-6~ + git_oid_ncmp@SHA256 1.9.2+ds-6~ + git_oid_nfmt@SHA256 1.9.2+ds-6~ + git_oid_pathfmt@SHA256 1.9.2+ds-6~ + git_oid_shorten_add@SHA256 1.9.2+ds-6~ + git_oid_shorten_free@SHA256 1.9.2+ds-6~ + git_oid_shorten_new@SHA256 1.9.2+ds-6~ + git_oid_strcmp@SHA256 1.9.2+ds-6~ + git_oid_streq@SHA256 1.9.2+ds-6~ + git_oid_tostr@SHA256 1.9.2+ds-6~ + git_oid_tostr_s@SHA256 1.9.2+ds-6~ + git_oidarray_dispose@SHA256 1.9.2+ds-6~ + git_oidarray_free@SHA256 1.9.2+ds-6~ + git_openssl_set_locking@SHA256 1.9.2+ds-6~ + git_packbuilder_foreach@SHA256 1.9.2+ds-6~ + git_packbuilder_free@SHA256 1.9.2+ds-6~ + git_packbuilder_hash@SHA256 1.9.2+ds-6~ + git_packbuilder_insert@SHA256 1.9.2+ds-6~ + git_packbuilder_insert_commit@SHA256 1.9.2+ds-6~ + git_packbuilder_insert_recur@SHA256 1.9.2+ds-6~ + git_packbuilder_insert_tree@SHA256 1.9.2+ds-6~ + git_packbuilder_insert_walk@SHA256 1.9.2+ds-6~ + git_packbuilder_name@SHA256 1.9.2+ds-6~ + git_packbuilder_new@SHA256 1.9.2+ds-6~ + git_packbuilder_object_count@SHA256 1.9.2+ds-6~ + git_packbuilder_set_callbacks@SHA256 1.9.2+ds-6~ + git_packbuilder_set_threads@SHA256 1.9.2+ds-6~ + git_packbuilder_write@SHA256 1.9.2+ds-6~ + git_packbuilder_write_buf@SHA256 1.9.2+ds-6~ + git_packbuilder_written@SHA256 1.9.2+ds-6~ + git_patch_free@SHA256 1.9.2+ds-6~ + git_patch_from_blob_and_buffer@SHA256 1.9.2+ds-6~ + git_patch_from_blobs@SHA256 1.9.2+ds-6~ + git_patch_from_buffers@SHA256 1.9.2+ds-6~ + git_patch_from_diff@SHA256 1.9.2+ds-6~ + git_patch_get_delta@SHA256 1.9.2+ds-6~ + git_patch_get_hunk@SHA256 1.9.2+ds-6~ + git_patch_get_line_in_hunk@SHA256 1.9.2+ds-6~ + git_patch_line_stats@SHA256 1.9.2+ds-6~ + git_patch_num_hunks@SHA256 1.9.2+ds-6~ + git_patch_num_lines_in_hunk@SHA256 1.9.2+ds-6~ + git_patch_owner@SHA256 1.9.2+ds-6~ + git_patch_print@SHA256 1.9.2+ds-6~ + git_patch_size@SHA256 1.9.2+ds-6~ + git_patch_to_buf@SHA256 1.9.2+ds-6~ + git_path_is_gitfile@SHA256 1.9.2+ds-6~ + git_pathspec_free@SHA256 1.9.2+ds-6~ + git_pathspec_match_diff@SHA256 1.9.2+ds-6~ + git_pathspec_match_index@SHA256 1.9.2+ds-6~ + git_pathspec_match_list_diff_entry@SHA256 1.9.2+ds-6~ + git_pathspec_match_list_entry@SHA256 1.9.2+ds-6~ + git_pathspec_match_list_entrycount@SHA256 1.9.2+ds-6~ + git_pathspec_match_list_failed_entry@SHA256 1.9.2+ds-6~ + git_pathspec_match_list_failed_entrycount@SHA256 1.9.2+ds-6~ + git_pathspec_match_list_free@SHA256 1.9.2+ds-6~ + git_pathspec_match_tree@SHA256 1.9.2+ds-6~ + git_pathspec_match_workdir@SHA256 1.9.2+ds-6~ + git_pathspec_matches_path@SHA256 1.9.2+ds-6~ + git_pathspec_new@SHA256 1.9.2+ds-6~ + git_proxy_init_options@SHA256 1.9.2+ds-6~ + git_proxy_options_init@SHA256 1.9.2+ds-6~ + git_push_init_options@SHA256 1.9.2+ds-6~ + git_push_options_init@SHA256 1.9.2+ds-6~ + git_rebase_abort@SHA256 1.9.2+ds-6~ + git_rebase_commit@SHA256 1.9.2+ds-6~ + git_rebase_finish@SHA256 1.9.2+ds-6~ + git_rebase_free@SHA256 1.9.2+ds-6~ + git_rebase_init@SHA256 1.9.2+ds-6~ + git_rebase_init_options@SHA256 1.9.2+ds-6~ + git_rebase_inmemory_index@SHA256 1.9.2+ds-6~ + git_rebase_next@SHA256 1.9.2+ds-6~ + git_rebase_onto_id@SHA256 1.9.2+ds-6~ + git_rebase_onto_name@SHA256 1.9.2+ds-6~ + git_rebase_open@SHA256 1.9.2+ds-6~ + git_rebase_operation_byindex@SHA256 1.9.2+ds-6~ + git_rebase_operation_current@SHA256 1.9.2+ds-6~ + git_rebase_operation_entrycount@SHA256 1.9.2+ds-6~ + git_rebase_options_init@SHA256 1.9.2+ds-6~ + git_rebase_orig_head_id@SHA256 1.9.2+ds-6~ + git_rebase_orig_head_name@SHA256 1.9.2+ds-6~ + git_refdb_backend_fs@SHA256 1.9.2+ds-6~ + git_refdb_compress@SHA256 1.9.2+ds-6~ + git_refdb_free@SHA256 1.9.2+ds-6~ + git_refdb_init_backend@SHA256 1.9.2+ds-6~ + git_refdb_new@SHA256 1.9.2+ds-6~ + git_refdb_open@SHA256 1.9.2+ds-6~ + git_refdb_set_backend@SHA256 1.9.2+ds-6~ + git_reference__alloc@SHA256 1.9.2+ds-6~ + git_reference__alloc_symbolic@SHA256 1.9.2+ds-6~ + git_reference_cmp@SHA256 1.9.2+ds-6~ + git_reference_create@SHA256 1.9.2+ds-6~ + git_reference_create_matching@SHA256 1.9.2+ds-6~ + git_reference_delete@SHA256 1.9.2+ds-6~ + git_reference_dup@SHA256 1.9.2+ds-6~ + git_reference_dwim@SHA256 1.9.2+ds-6~ + git_reference_ensure_log@SHA256 1.9.2+ds-6~ + git_reference_foreach@SHA256 1.9.2+ds-6~ + git_reference_foreach_glob@SHA256 1.9.2+ds-6~ + git_reference_foreach_name@SHA256 1.9.2+ds-6~ + git_reference_free@SHA256 1.9.2+ds-6~ + git_reference_has_log@SHA256 1.9.2+ds-6~ + git_reference_is_branch@SHA256 1.9.2+ds-6~ + git_reference_is_note@SHA256 1.9.2+ds-6~ + git_reference_is_remote@SHA256 1.9.2+ds-6~ + git_reference_is_tag@SHA256 1.9.2+ds-6~ + git_reference_is_valid_name@SHA256 1.9.2+ds-6~ + git_reference_iterator_free@SHA256 1.9.2+ds-6~ + git_reference_iterator_glob_new@SHA256 1.9.2+ds-6~ + git_reference_iterator_new@SHA256 1.9.2+ds-6~ + git_reference_list@SHA256 1.9.2+ds-6~ + git_reference_lookup@SHA256 1.9.2+ds-6~ + git_reference_name@SHA256 1.9.2+ds-6~ + git_reference_name_is_valid@SHA256 1.9.2+ds-6~ + git_reference_name_to_id@SHA256 1.9.2+ds-6~ + git_reference_next@SHA256 1.9.2+ds-6~ + git_reference_next_name@SHA256 1.9.2+ds-6~ + git_reference_normalize_name@SHA256 1.9.2+ds-6~ + git_reference_owner@SHA256 1.9.2+ds-6~ + git_reference_peel@SHA256 1.9.2+ds-6~ + git_reference_remove@SHA256 1.9.2+ds-6~ + git_reference_rename@SHA256 1.9.2+ds-6~ + git_reference_resolve@SHA256 1.9.2+ds-6~ + git_reference_set_target@SHA256 1.9.2+ds-6~ + git_reference_shorthand@SHA256 1.9.2+ds-6~ + git_reference_symbolic_create@SHA256 1.9.2+ds-6~ + git_reference_symbolic_create_matching@SHA256 1.9.2+ds-6~ + git_reference_symbolic_set_target@SHA256 1.9.2+ds-6~ + git_reference_symbolic_target@SHA256 1.9.2+ds-6~ + git_reference_target@SHA256 1.9.2+ds-6~ + git_reference_target_peel@SHA256 1.9.2+ds-6~ + git_reference_type@SHA256 1.9.2+ds-6~ + git_reflog_append@SHA256 1.9.2+ds-6~ + git_reflog_delete@SHA256 1.9.2+ds-6~ + git_reflog_drop@SHA256 1.9.2+ds-6~ + git_reflog_entry_byindex@SHA256 1.9.2+ds-6~ + git_reflog_entry_committer@SHA256 1.9.2+ds-6~ + git_reflog_entry_id_new@SHA256 1.9.2+ds-6~ + git_reflog_entry_id_old@SHA256 1.9.2+ds-6~ + git_reflog_entry_message@SHA256 1.9.2+ds-6~ + git_reflog_entrycount@SHA256 1.9.2+ds-6~ + git_reflog_free@SHA256 1.9.2+ds-6~ + git_reflog_read@SHA256 1.9.2+ds-6~ + git_reflog_rename@SHA256 1.9.2+ds-6~ + git_reflog_write@SHA256 1.9.2+ds-6~ + git_refspec_direction@SHA256 1.9.2+ds-6~ + git_refspec_dst@SHA256 1.9.2+ds-6~ + git_refspec_dst_matches@SHA256 1.9.2+ds-6~ + git_refspec_force@SHA256 1.9.2+ds-6~ + git_refspec_free@SHA256 1.9.2+ds-6~ + git_refspec_parse@SHA256 1.9.2+ds-6~ + git_refspec_rtransform@SHA256 1.9.2+ds-6~ + git_refspec_src@SHA256 1.9.2+ds-6~ + git_refspec_src_matches@SHA256 1.9.2+ds-6~ + git_refspec_src_matches_negative@SHA256 1.9.2+ds-6~ + git_refspec_string@SHA256 1.9.2+ds-6~ + git_refspec_transform@SHA256 1.9.2+ds-6~ + git_remote_add_fetch@SHA256 1.9.2+ds-6~ + git_remote_add_push@SHA256 1.9.2+ds-6~ + git_remote_autotag@SHA256 1.9.2+ds-6~ + git_remote_connect@SHA256 1.9.2+ds-6~ + git_remote_connect_ext@SHA256 1.9.2+ds-6~ + git_remote_connect_options_dispose@SHA256 1.9.2+ds-6~ + git_remote_connect_options_init@SHA256 1.9.2+ds-6~ + git_remote_connected@SHA256 1.9.2+ds-6~ + git_remote_create@SHA256 1.9.2+ds-6~ + git_remote_create_anonymous@SHA256 1.9.2+ds-6~ + git_remote_create_detached@SHA256 1.9.2+ds-6~ + git_remote_create_init_options@SHA256 1.9.2+ds-6~ + git_remote_create_options_init@SHA256 1.9.2+ds-6~ + git_remote_create_with_fetchspec@SHA256 1.9.2+ds-6~ + git_remote_create_with_opts@SHA256 1.9.2+ds-6~ + git_remote_default_branch@SHA256 1.9.2+ds-6~ + git_remote_delete@SHA256 1.9.2+ds-6~ + git_remote_disconnect@SHA256 1.9.2+ds-6~ + git_remote_download@SHA256 1.9.2+ds-6~ + git_remote_dup@SHA256 1.9.2+ds-6~ + git_remote_fetch@SHA256 1.9.2+ds-6~ + git_remote_free@SHA256 1.9.2+ds-6~ + git_remote_get_fetch_refspecs@SHA256 1.9.2+ds-6~ + git_remote_get_push_refspecs@SHA256 1.9.2+ds-6~ + git_remote_get_refspec@SHA256 1.9.2+ds-6~ + git_remote_init_callbacks@SHA256 1.9.2+ds-6~ + git_remote_is_valid_name@SHA256 1.9.2+ds-6~ + git_remote_list@SHA256 1.9.2+ds-6~ + git_remote_lookup@SHA256 1.9.2+ds-6~ + git_remote_ls@SHA256 1.9.2+ds-6~ + git_remote_name@SHA256 1.9.2+ds-6~ + git_remote_name_is_valid@SHA256 1.9.2+ds-6~ + git_remote_owner@SHA256 1.9.2+ds-6~ + git_remote_prune@SHA256 1.9.2+ds-6~ + git_remote_prune_refs@SHA256 1.9.2+ds-6~ + git_remote_push@SHA256 1.9.2+ds-6~ + git_remote_pushurl@SHA256 1.9.2+ds-6~ + git_remote_refspec_count@SHA256 1.9.2+ds-6~ + git_remote_rename@SHA256 1.9.2+ds-6~ + git_remote_set_autotag@SHA256 1.9.2+ds-6~ + git_remote_set_instance_pushurl@SHA256 1.9.2+ds-6~ + git_remote_set_instance_url@SHA256 1.9.2+ds-6~ + git_remote_set_pushurl@SHA256 1.9.2+ds-6~ + git_remote_set_url@SHA256 1.9.2+ds-6~ + git_remote_stats@SHA256 1.9.2+ds-6~ + git_remote_stop@SHA256 1.9.2+ds-6~ + git_remote_update_tips@SHA256 1.9.2+ds-6~ + git_remote_upload@SHA256 1.9.2+ds-6~ + git_remote_url@SHA256 1.9.2+ds-6~ + git_repository__cleanup@SHA256 1.9.2+ds-6~ + git_repository_commit_parents@SHA256 1.9.2+ds-6~ + git_repository_commondir@SHA256 1.9.2+ds-6~ + git_repository_config@SHA256 1.9.2+ds-6~ + git_repository_config_snapshot@SHA256 1.9.2+ds-6~ + git_repository_detach_head@SHA256 1.9.2+ds-6~ + git_repository_discover@SHA256 1.9.2+ds-6~ + git_repository_fetchhead_foreach@SHA256 1.9.2+ds-6~ + git_repository_free@SHA256 1.9.2+ds-6~ + git_repository_get_namespace@SHA256 1.9.2+ds-6~ + git_repository_hashfile@SHA256 1.9.2+ds-6~ + git_repository_head@SHA256 1.9.2+ds-6~ + git_repository_head_detached@SHA256 1.9.2+ds-6~ + git_repository_head_detached_for_worktree@SHA256 1.9.2+ds-6~ + git_repository_head_for_worktree@SHA256 1.9.2+ds-6~ + git_repository_head_unborn@SHA256 1.9.2+ds-6~ + git_repository_ident@SHA256 1.9.2+ds-6~ + git_repository_index@SHA256 1.9.2+ds-6~ + git_repository_init@SHA256 1.9.2+ds-6~ + git_repository_init_ext@SHA256 1.9.2+ds-6~ + git_repository_init_init_options@SHA256 1.9.2+ds-6~ + git_repository_init_options_init@SHA256 1.9.2+ds-6~ + git_repository_is_bare@SHA256 1.9.2+ds-6~ + git_repository_is_empty@SHA256 1.9.2+ds-6~ + git_repository_is_shallow@SHA256 1.9.2+ds-6~ + git_repository_is_worktree@SHA256 1.9.2+ds-6~ + git_repository_item_path@SHA256 1.9.2+ds-6~ + git_repository_mergehead_foreach@SHA256 1.9.2+ds-6~ + git_repository_message@SHA256 1.9.2+ds-6~ + git_repository_message_remove@SHA256 1.9.2+ds-6~ + git_repository_new@SHA256 1.9.2+ds-6~ + git_repository_odb@SHA256 1.9.2+ds-6~ + git_repository_oid_type@SHA256 1.9.2+ds-6~ + git_repository_open@SHA256 1.9.2+ds-6~ + git_repository_open_bare@SHA256 1.9.2+ds-6~ + git_repository_open_ext@SHA256 1.9.2+ds-6~ + git_repository_open_from_worktree@SHA256 1.9.2+ds-6~ + git_repository_path@SHA256 1.9.2+ds-6~ + git_repository_refdb@SHA256 1.9.2+ds-6~ + git_repository_reinit_filesystem@SHA256 1.9.2+ds-6~ + git_repository_set_bare@SHA256 1.9.2+ds-6~ + git_repository_set_config@SHA256 1.9.2+ds-6~ + git_repository_set_head@SHA256 1.9.2+ds-6~ + git_repository_set_head_detached@SHA256 1.9.2+ds-6~ + git_repository_set_head_detached_from_annotated@SHA256 1.9.2+ds-6~ + git_repository_set_ident@SHA256 1.9.2+ds-6~ + git_repository_set_index@SHA256 1.9.2+ds-6~ + git_repository_set_namespace@SHA256 1.9.2+ds-6~ + git_repository_set_odb@SHA256 1.9.2+ds-6~ + git_repository_set_refdb@SHA256 1.9.2+ds-6~ + git_repository_set_workdir@SHA256 1.9.2+ds-6~ + git_repository_state@SHA256 1.9.2+ds-6~ + git_repository_state_cleanup@SHA256 1.9.2+ds-6~ + git_repository_submodule_cache_all@SHA256 1.9.2+ds-6~ + git_repository_submodule_cache_clear@SHA256 1.9.2+ds-6~ + git_repository_workdir@SHA256 1.9.2+ds-6~ + git_repository_wrap_odb@SHA256 1.9.2+ds-6~ + git_reset@SHA256 1.9.2+ds-6~ + git_reset_default@SHA256 1.9.2+ds-6~ + git_reset_from_annotated@SHA256 1.9.2+ds-6~ + git_revert@SHA256 1.9.2+ds-6~ + git_revert_commit@SHA256 1.9.2+ds-6~ + git_revert_init_options@SHA256 1.9.2+ds-6~ + git_revert_options_init@SHA256 1.9.2+ds-6~ + git_revparse@SHA256 1.9.2+ds-6~ + git_revparse_ext@SHA256 1.9.2+ds-6~ + git_revparse_single@SHA256 1.9.2+ds-6~ + git_revwalk_add_hide_cb@SHA256 1.9.2+ds-6~ + git_revwalk_free@SHA256 1.9.2+ds-6~ + git_revwalk_hide@SHA256 1.9.2+ds-6~ + git_revwalk_hide_glob@SHA256 1.9.2+ds-6~ + git_revwalk_hide_head@SHA256 1.9.2+ds-6~ + git_revwalk_hide_ref@SHA256 1.9.2+ds-6~ + git_revwalk_new@SHA256 1.9.2+ds-6~ + git_revwalk_next@SHA256 1.9.2+ds-6~ + git_revwalk_push@SHA256 1.9.2+ds-6~ + git_revwalk_push_glob@SHA256 1.9.2+ds-6~ + git_revwalk_push_head@SHA256 1.9.2+ds-6~ + git_revwalk_push_range@SHA256 1.9.2+ds-6~ + git_revwalk_push_ref@SHA256 1.9.2+ds-6~ + git_revwalk_repository@SHA256 1.9.2+ds-6~ + git_revwalk_reset@SHA256 1.9.2+ds-6~ + git_revwalk_simplify_first_parent@SHA256 1.9.2+ds-6~ + git_revwalk_sorting@SHA256 1.9.2+ds-6~ + git_signature_default@SHA256 1.9.2+ds-6~ + git_signature_default_from_env@SHA256 1.9.2+ds-6~ + git_signature_dup@SHA256 1.9.2+ds-6~ + git_signature_free@SHA256 1.9.2+ds-6~ + git_signature_from_buffer@SHA256 1.9.2+ds-6~ + git_signature_new@SHA256 1.9.2+ds-6~ + git_signature_now@SHA256 1.9.2+ds-6~ + git_smart_subtransport_git@SHA256 1.9.2+ds-6~ + git_smart_subtransport_http@SHA256 1.9.2+ds-6~ + git_smart_subtransport_ssh@SHA256 1.9.2+ds-6~ + git_stash_apply@SHA256 1.9.2+ds-6~ + git_stash_apply_init_options@SHA256 1.9.2+ds-6~ + git_stash_apply_options_init@SHA256 1.9.2+ds-6~ + git_stash_drop@SHA256 1.9.2+ds-6~ + git_stash_foreach@SHA256 1.9.2+ds-6~ + git_stash_pop@SHA256 1.9.2+ds-6~ + git_stash_save@SHA256 1.9.2+ds-6~ + git_stash_save_options_init@SHA256 1.9.2+ds-6~ + git_stash_save_with_opts@SHA256 1.9.2+ds-6~ + git_status_byindex@SHA256 1.9.2+ds-6~ + git_status_file@SHA256 1.9.2+ds-6~ + git_status_foreach@SHA256 1.9.2+ds-6~ + git_status_foreach_ext@SHA256 1.9.2+ds-6~ + git_status_init_options@SHA256 1.9.2+ds-6~ + git_status_list_entrycount@SHA256 1.9.2+ds-6~ + git_status_list_free@SHA256 1.9.2+ds-6~ + git_status_list_get_perfdata@SHA256 1.9.2+ds-6~ + git_status_list_new@SHA256 1.9.2+ds-6~ + git_status_options_init@SHA256 1.9.2+ds-6~ + git_status_should_ignore@SHA256 1.9.2+ds-6~ + git_strarray_copy@SHA256 1.9.2+ds-6~ + git_strarray_dispose@SHA256 1.9.2+ds-6~ + git_strarray_free@SHA256 1.9.2+ds-6~ + git_stream_register@SHA256 1.9.2+ds-6~ + git_stream_register_tls@SHA256 1.9.2+ds-6~ + git_submodule_add_finalize@SHA256 1.9.2+ds-6~ + git_submodule_add_setup@SHA256 1.9.2+ds-6~ + git_submodule_add_to_index@SHA256 1.9.2+ds-6~ + git_submodule_branch@SHA256 1.9.2+ds-6~ + git_submodule_clone@SHA256 1.9.2+ds-6~ + git_submodule_dup@SHA256 1.9.2+ds-6~ + git_submodule_fetch_recurse_submodules@SHA256 1.9.2+ds-6~ + git_submodule_foreach@SHA256 1.9.2+ds-6~ + git_submodule_free@SHA256 1.9.2+ds-6~ + git_submodule_head_id@SHA256 1.9.2+ds-6~ + git_submodule_ignore@SHA256 1.9.2+ds-6~ + git_submodule_index_id@SHA256 1.9.2+ds-6~ + git_submodule_init@SHA256 1.9.2+ds-6~ + git_submodule_location@SHA256 1.9.2+ds-6~ + git_submodule_lookup@SHA256 1.9.2+ds-6~ + git_submodule_name@SHA256 1.9.2+ds-6~ + git_submodule_open@SHA256 1.9.2+ds-6~ + git_submodule_owner@SHA256 1.9.2+ds-6~ + git_submodule_path@SHA256 1.9.2+ds-6~ + git_submodule_reload@SHA256 1.9.2+ds-6~ + git_submodule_repo_init@SHA256 1.9.2+ds-6~ + git_submodule_resolve_url@SHA256 1.9.2+ds-6~ + git_submodule_set_branch@SHA256 1.9.2+ds-6~ + git_submodule_set_fetch_recurse_submodules@SHA256 1.9.2+ds-6~ + git_submodule_set_ignore@SHA256 1.9.2+ds-6~ + git_submodule_set_update@SHA256 1.9.2+ds-6~ + git_submodule_set_url@SHA256 1.9.2+ds-6~ + git_submodule_status@SHA256 1.9.2+ds-6~ + git_submodule_sync@SHA256 1.9.2+ds-6~ + git_submodule_update@SHA256 1.9.2+ds-6~ + git_submodule_update_init_options@SHA256 1.9.2+ds-6~ + git_submodule_update_options_init@SHA256 1.9.2+ds-6~ + git_submodule_update_strategy@SHA256 1.9.2+ds-6~ + git_submodule_url@SHA256 1.9.2+ds-6~ + git_submodule_wd_id@SHA256 1.9.2+ds-6~ + git_tag_annotation_create@SHA256 1.9.2+ds-6~ + git_tag_create@SHA256 1.9.2+ds-6~ + git_tag_create_from_buffer@SHA256 1.9.2+ds-6~ + git_tag_create_frombuffer@SHA256 1.9.2+ds-6~ + git_tag_create_lightweight@SHA256 1.9.2+ds-6~ + git_tag_delete@SHA256 1.9.2+ds-6~ + git_tag_dup@SHA256 1.9.2+ds-6~ + git_tag_foreach@SHA256 1.9.2+ds-6~ + git_tag_free@SHA256 1.9.2+ds-6~ + git_tag_id@SHA256 1.9.2+ds-6~ + git_tag_list@SHA256 1.9.2+ds-6~ + git_tag_list_match@SHA256 1.9.2+ds-6~ + git_tag_lookup@SHA256 1.9.2+ds-6~ + git_tag_lookup_prefix@SHA256 1.9.2+ds-6~ + git_tag_message@SHA256 1.9.2+ds-6~ + git_tag_name@SHA256 1.9.2+ds-6~ + git_tag_name_is_valid@SHA256 1.9.2+ds-6~ + git_tag_owner@SHA256 1.9.2+ds-6~ + git_tag_peel@SHA256 1.9.2+ds-6~ + git_tag_tagger@SHA256 1.9.2+ds-6~ + git_tag_target@SHA256 1.9.2+ds-6~ + git_tag_target_id@SHA256 1.9.2+ds-6~ + git_tag_target_type@SHA256 1.9.2+ds-6~ + git_trace_set@SHA256 1.9.2+ds-6~ + git_transaction_commit@SHA256 1.9.2+ds-6~ + git_transaction_free@SHA256 1.9.2+ds-6~ + git_transaction_lock_ref@SHA256 1.9.2+ds-6~ + git_transaction_new@SHA256 1.9.2+ds-6~ + git_transaction_remove@SHA256 1.9.2+ds-6~ + git_transaction_set_reflog@SHA256 1.9.2+ds-6~ + git_transaction_set_symbolic_target@SHA256 1.9.2+ds-6~ + git_transaction_set_target@SHA256 1.9.2+ds-6~ + git_transport_init@SHA256 1.9.2+ds-6~ + git_transport_local@SHA256 1.9.2+ds-6~ + git_transport_new@SHA256 1.9.2+ds-6~ + git_transport_register@SHA256 1.9.2+ds-6~ + git_transport_remote_connect_options@SHA256 1.9.2+ds-6~ + git_transport_smart@SHA256 1.9.2+ds-6~ + git_transport_smart_certificate_check@SHA256 1.9.2+ds-6~ + git_transport_smart_credentials@SHA256 1.9.2+ds-6~ + git_transport_ssh_with_paths@SHA256 1.9.2+ds-6~ + git_transport_unregister@SHA256 1.9.2+ds-6~ + git_tree_create_updated@SHA256 1.9.2+ds-6~ + git_tree_dup@SHA256 1.9.2+ds-6~ + git_tree_entry_byid@SHA256 1.9.2+ds-6~ + git_tree_entry_byindex@SHA256 1.9.2+ds-6~ + git_tree_entry_byname@SHA256 1.9.2+ds-6~ + git_tree_entry_bypath@SHA256 1.9.2+ds-6~ + git_tree_entry_cmp@SHA256 1.9.2+ds-6~ + git_tree_entry_dup@SHA256 1.9.2+ds-6~ + git_tree_entry_filemode@SHA256 1.9.2+ds-6~ + git_tree_entry_filemode_raw@SHA256 1.9.2+ds-6~ + git_tree_entry_free@SHA256 1.9.2+ds-6~ + git_tree_entry_id@SHA256 1.9.2+ds-6~ + git_tree_entry_name@SHA256 1.9.2+ds-6~ + git_tree_entry_to_object@SHA256 1.9.2+ds-6~ + git_tree_entry_type@SHA256 1.9.2+ds-6~ + git_tree_entrycount@SHA256 1.9.2+ds-6~ + git_tree_free@SHA256 1.9.2+ds-6~ + git_tree_id@SHA256 1.9.2+ds-6~ + git_tree_lookup@SHA256 1.9.2+ds-6~ + git_tree_lookup_prefix@SHA256 1.9.2+ds-6~ + git_tree_owner@SHA256 1.9.2+ds-6~ + git_tree_walk@SHA256 1.9.2+ds-6~ + git_treebuilder_clear@SHA256 1.9.2+ds-6~ + git_treebuilder_entrycount@SHA256 1.9.2+ds-6~ + git_treebuilder_filter@SHA256 1.9.2+ds-6~ + git_treebuilder_free@SHA256 1.9.2+ds-6~ + git_treebuilder_get@SHA256 1.9.2+ds-6~ + git_treebuilder_insert@SHA256 1.9.2+ds-6~ + git_treebuilder_new@SHA256 1.9.2+ds-6~ + git_treebuilder_remove@SHA256 1.9.2+ds-6~ + git_treebuilder_write@SHA256 1.9.2+ds-6~ + git_treebuilder_write_with_buffer@SHA256 1.9.2+ds-6~ + git_worktree_add@SHA256 1.9.2+ds-6~ + git_worktree_add_init_options@SHA256 1.9.2+ds-6~ + git_worktree_add_options_init@SHA256 1.9.2+ds-6~ + git_worktree_free@SHA256 1.9.2+ds-6~ + git_worktree_is_locked@SHA256 1.9.2+ds-6~ + git_worktree_is_prunable@SHA256 1.9.2+ds-6~ + git_worktree_list@SHA256 1.9.2+ds-6~ + git_worktree_lock@SHA256 1.9.2+ds-6~ + git_worktree_lookup@SHA256 1.9.2+ds-6~ + git_worktree_name@SHA256 1.9.2+ds-6~ + git_worktree_open_from_repository@SHA256 1.9.2+ds-6~ + git_worktree_path@SHA256 1.9.2+ds-6~ + git_worktree_prune@SHA256 1.9.2+ds-6~ + git_worktree_prune_init_options@SHA256 1.9.2+ds-6~ + git_worktree_prune_options_init@SHA256 1.9.2+ds-6~ + git_worktree_unlock@SHA256 1.9.2+ds-6~ + git_worktree_validate@SHA256 1.9.2+ds-6~ + giterr_clear@SHA256 1.9.2+ds-6~ + giterr_last@SHA256 1.9.2+ds-6~ + giterr_set_oom@SHA256 1.9.2+ds-6~ + giterr_set_str@SHA256 1.9.2+ds-6~ diff --git a/debian/patches/disable-flaky-stat-tests.patch b/debian/patches/disable-flaky-stat-tests.patch index 02ef2860..50afd75e 100644 --- a/debian/patches/disable-flaky-stat-tests.patch +++ b/debian/patches/disable-flaky-stat-tests.patch @@ -11,7 +11,7 @@ Forwarded: not-needed 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/tests/libgit2/repo/init.c b/tests/libgit2/repo/init.c -index d78ec06..17a0ae2 100644 +index e23271d..7b3b343 100644 --- a/tests/libgit2/repo/init.c +++ b/tests/libgit2/repo/init.c @@ -443,12 +443,14 @@ void test_repo_init__extended_1(void) @@ -36,7 +36,7 @@ index d78ec06..17a0ae2 100644 cl_assert(git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC); cl_assert_equal_s("refs/heads/development", git_reference_symbolic_target(ref)); diff --git a/tests/libgit2/repo/template.c b/tests/libgit2/repo/template.c -index e8fe266..f771f4e 100644 +index 2b1ecf8..70b19ab 100644 --- a/tests/libgit2/repo/template.c +++ b/tests/libgit2/repo/template.c @@ -72,8 +72,8 @@ static void assert_hooks_match( diff --git a/debian/patches/disable-online-tests.patch b/debian/patches/disable-online-tests.patch index 3470ea32..970a6bb1 100644 --- a/debian/patches/disable-online-tests.patch +++ b/debian/patches/disable-online-tests.patch @@ -10,10 +10,10 @@ Forwarded: not-needed 1 file changed, 2 insertions(+) diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt -index af70f55..246da96 100644 +index 7a4bb47..938749e 100644 --- a/tests/libgit2/CMakeLists.txt +++ b/tests/libgit2/CMakeLists.txt -@@ -66,6 +66,7 @@ endif() +@@ -69,6 +69,7 @@ endif() include(AddClarTest) add_clar_test(libgit2_tests offline -v -xonline) add_clar_test(libgit2_tests invasive -v -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root -sonline::clone::connect_timeout_default) @@ -21,7 +21,7 @@ index af70f55..246da96 100644 add_clar_test(libgit2_tests online -v -sonline -xonline::customcert) add_clar_test(libgit2_tests online_customcert -v -sonline::customcert) add_clar_test(libgit2_tests gitdaemon -v -sonline::push) -@@ -75,3 +76,4 @@ add_clar_test(libgit2_tests ssh -v -sonline::push -sonline::clon +@@ -78,3 +79,4 @@ add_clar_test(libgit2_tests ssh -v -sonline::push -sonline::clon add_clar_test(libgit2_tests proxy -v -sonline::clone::proxy) add_clar_test(libgit2_tests auth_clone -v -sonline::clone::cred) add_clar_test(libgit2_tests auth_clone_and_push -v -sonline::clone::push -sonline::push) diff --git a/debian/patches/handle-bashism.patch b/debian/patches/handle-bashism.patch deleted file mode 100644 index 907ccf7f..00000000 --- a/debian/patches/handle-bashism.patch +++ /dev/null @@ -1,19 +0,0 @@ -From: =?utf-8?q?Timo_R=C3=B6hling?= -Date: Sun, 28 Aug 2022 17:42:55 +0200 -Subject: Use bash for shell script with bashisms - -Forwarded: https://github.com/libgit2/libgit2/pull/6581 ---- - tests/resources/push.sh | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tests/resources/push.sh b/tests/resources/push.sh -index 3e77fb5..54ef3dd 100644 ---- a/tests/resources/push.sh -+++ b/tests/resources/push.sh -@@ -1,4 +1,4 @@ --#!/bin/sh -+#!/bin/bash - #creates push_src repo for libgit2 push tests. - set -eu - diff --git a/debian/patches/series b/debian/patches/series index ddeb33de..2d30c22c 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,2 @@ disable-online-tests.patch -handle-bashism.patch disable-flaky-stat-tests.patch diff --git a/debian/rules b/debian/rules index d28ff486..157d303d 100755 --- a/debian/rules +++ b/debian/rules @@ -1,9 +1,11 @@ #!/usr/bin/make -f # -*- makefile -*- -# This rules file has 2 streams, the build-debian-devel stream builds -# the static library and the build-debian-release builds the dynamic -# library. +# This rules file has multiple streams: +# - build-debian-static: static library, SHA-1 hashes only +# - build-debian-shared: shared library, SHA-1 hashes only +# - build-debian-static-experimental: static library, SHA-256 support +# - build-debian-shared-experimental: shared library, SHA-256 support # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 @@ -20,34 +22,62 @@ COMMON_CMAKE_FLAGS = \ -DREGEX_BACKEND=pcre2 \ -DUSE_GSSAPI=ON \ -DUSE_HTTPS=mbedTLS \ - -DUSE_HTTP_PARSER=system \ + -DUSE_HTTP_PARSER=llhttp \ -DUSE_NTLMCLIENT=OFF \ -DUSE_SSH=ON - # The stat() in the Git fs layer has some issues export GITTEST_FLAKY_STAT = true %: dh $@ --buildsystem=cmake -override_dh_auto_configure: - dh_auto_configure --builddirectory=build-debian-devel -- \ +override_dh_auto_configure-arch: + dh_auto_configure --builddirectory=build-debian-static-experimental -- \ + -DBUILD_SHARED_LIBS=OFF \ + -DEXPERIMENTAL_SHA256=ON \ + $(COMMON_CMAKE_FLAGS) + dh_auto_configure --builddirectory=build-debian-static -- \ -DBUILD_SHARED_LIBS=OFF \ + -DEXPERIMENTAL_SHA256=OFF \ + $(COMMON_CMAKE_FLAGS) + dh_auto_configure --builddirectory=build-debian-shared-experimental -- \ + -DBUILD_SHARED_LIBS=ON \ + -DEXPERIMENTAL_SHA256=ON \ + -DCMAKE_SHARED_LINKER_FLAGS_NONE=-Wl,--version-script=$(CURDIR)/debian/libgit2-experimental.map \ $(COMMON_CMAKE_FLAGS) - dh_auto_configure --builddirectory=build-debian-release -- \ + dh_auto_configure --builddirectory=build-debian-shared -- \ -DBUILD_SHARED_LIBS=ON \ + -DEXPERIMENTAL_SHA256=OFF \ $(COMMON_CMAKE_FLAGS) -override_dh_auto_build: - dh_auto_build --builddirectory=build-debian-devel - dh_auto_build --builddirectory=build-debian-release +override_dh_auto_configure-indep: + +override_dh_auto_build-arch: + dh_auto_build --builddirectory=build-debian-static-experimental + dh_auto_build --builddirectory=build-debian-static + dh_auto_build --builddirectory=build-debian-shared-experimental + dh_auto_build --builddirectory=build-debian-shared + +override_dh_auto_build-indep: + +override_dh_auto_install-arch: + dh_auto_install --builddirectory=build-debian-static-experimental --destdir=debian/tmp-experimental + dh_auto_install --builddirectory=build-debian-static --destdir=debian/tmp + dh_auto_install --builddirectory=build-debian-shared-experimental --destdir=debian/tmp-experimental + dh_auto_install --builddirectory=build-debian-shared --destdir=debian/tmp + +override_dh_auto_install-indep: -override_dh_auto_install: - dh_auto_install --builddirectory=build-debian-devel - dh_auto_install --builddirectory=build-debian-release +override_dh_auto_test-arch: + dh_auto_test --builddirectory=build-debian-static-experimental + dh_auto_test --builddirectory=build-debian-static + dh_auto_test --builddirectory=build-debian-shared-experimental + dh_auto_test --builddirectory=build-debian-shared -override_dh_auto_test: - dh_auto_test --builddirectory=build-debian-devel - dh_auto_test --builddirectory=build-debian-release +override_dh_auto_test-indep: +override_dh_install: + dh_install -plibgit2-dev -plibgit2-1.9 --sourcedir=debian/tmp + dh_install -plibgit2-experimental-dev -plibgit2-experimental1.9 --sourcedir=debian/tmp-experimental + dh_install --remaining-packages diff --git a/debian/tests/CMakeLists.txt b/debian/tests/CMakeLists.txt deleted file mode 100644 index 46241b77..00000000 --- a/debian/tests/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -CMAKE_MINIMUM_REQUIRED (VERSION 3.7) -PROJECT (libgit2_test) - -ADD_EXECUTABLE (libgit2_test libgit2_test.c) - -FIND_PACKAGE(PkgConfig) - -PKG_CHECK_MODULES(PC_LIBGIT2 libgit2) - -SET(LIBGIT2_DEFINITIONS ${PC_LIBGIT_CFLAGS_OTHER}) - -FIND_PATH(LIBGIT2_INCLUDE_DIR git2.h - HINTS ${PC_LIBGIT_INCLUDEDIR} ${PC_LIBGIT_INCLUDE_DIRS} - PATH_SUFFIXES libgit2 ) - -FIND_LIBRARY(LIBGIT2_LIBRARY NAMES git2 libgit2 - HINTS ${PC_LIBGIT_LIBDIR} ${PC_LIBGIT_LIBRARY_DIRS} ) - -SET(LIBGIT2_LIBRARIES ${LIBGIT2_LIBRARY} ) -SET(LIBGIT2_INCLUDE_DIRS ${LIBGIT2_INCLUDE_DIR} ) - -INCLUDE(FindPackageHandleStandardArgs) - -FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibGit2 DEFAULT_MSG - LIBGIT2_LIBRARY LIBGIT2_INCLUDE_DIR) - - -mark_as_advanced(LIBGIT2_INCLUDE_DIR LIBGIT2_LIBRARY ) -include_directories(${LIBGIT2_INCLUDE_DIR}) -target_link_libraries (libgit2_test ${LIBGIT2_LIBRARY}) diff --git a/debian/tests/control b/debian/tests/control index 241da9a2..6a086451 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -1,2 +1,20 @@ -Test-Command: cmake debian/tests;make;./libgit2_test -Depends: @, cmake, gcc, g++ +Test-Command: ./debian/tests/superficial-test git2 +Features: test-name=build-with-libgit2-dev +Restrictions: superficial +Depends: + cmake, + g++, + gcc, + libgit2-dev, + pkgconf, + +Test-Command: ./debian/tests/superficial-test git2-experimental +Features: test-name=build-with-libgit2-experimental-dev +Restrictions: superficial +Depends: + cmake, + g++, + gcc, + libgit2-experimental-dev, + pkgconf, + diff --git a/debian/tests/superficial-test b/debian/tests/superficial-test new file mode 100755 index 00000000..f7c906ef --- /dev/null +++ b/debian/tests/superficial-test @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +cmake -S debian/tests/superficial-test-src -B "$AUTOPKGTEST_TMP" -DCMAKE_BUILD_TYPE=RelWithDebInfo +cmake --build "$AUTOPKGTEST_TMP" --verbose +"$AUTOPKGTEST_TMP"/libgit2_test + diff --git a/debian/tests/superficial-test-src/CMakeLists.txt b/debian/tests/superficial-test-src/CMakeLists.txt new file mode 100644 index 00000000..6ff40988 --- /dev/null +++ b/debian/tests/superficial-test-src/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.13...4.1) +project(libgit2 LANGUAGES C) + +set(CMAKE_C_STANDARD 99) + +find_package(PkgConfig REQUIRED) +pkg_search_module(pc_libgit2 REQUIRED IMPORTED_TARGET libgit2 libgit2-experimental) + +add_executable(libgit2_test libgit2_test.c) +target_link_libraries(libgit2_test PRIVATE PkgConfig::pc_libgit2) + diff --git a/debian/tests/libgit2_test.c b/debian/tests/superficial-test-src/libgit2_test.c similarity index 100% rename from debian/tests/libgit2_test.c rename to debian/tests/superficial-test-src/libgit2_test.c diff --git a/debian/watch b/debian/watch index 65b8013d..8e0d9a73 100644 --- a/debian/watch +++ b/debian/watch @@ -1,5 +1,9 @@ -version=4 -opts=filenamemangle=s%(?:.*?)?v?(\d[\d.]*@ARCHIVE_EXT@)%@PACKAGE@-$1%,\ - repack,dversionmangle=auto,repacksuffix=+ds \ - https://github.com/libgit2/libgit2/tags (?:.*?/)?v?@ANY_VERSION@@ARCHIVE_EXT@ +Version: 5 +Template: GitHub +Owner: libgit2 +Project: libgit2 +Dversion-Mangle: auto +Uversion-Mangle: auto +Repack: yes +Repack-Suffix: +ds diff --git a/deps/xdiff/xmerge.c b/deps/xdiff/xmerge.c index af40c88a..6ebf73a9 100644 --- a/deps/xdiff/xmerge.c +++ b/deps/xdiff/xmerge.c @@ -88,7 +88,7 @@ static int xdl_cleanup_merge(xdmerge_t *c) if (c->mode == 0) count++; next_c = c->next; - free(c); + xdl_free(c); } return count; } @@ -456,7 +456,7 @@ static void xdl_merge_two_conflicts(xdmerge_t *m) m->chg1 = next_m->i1 + next_m->chg1 - m->i1; m->chg2 = next_m->i2 + next_m->chg2 - m->i2; m->next = next_m->next; - free(next_m); + xdl_free(next_m); } /* diff --git a/docs/changelog.md b/docs/changelog.md index 1748309e..87e23577 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,42 +1,920 @@ -v1.7.2 +v1.9.2 ------ +This is a security release with multiple changes. + +* A bug in the external SSH execution is fixed that could cause + arbitrary command execution. Remote repository names were improperly + sent to the shell without quoting. Arguments to the external SSH + command are now sent parameterized. + +* A bug in SSH credential creation is fixed that could cause a + buffer overflow. Public keys that are not NUL terminated were + improperly zeroed. The given length of public keys is now honored. + +v1.9.1 +------ + +This release includes a number of bugfixes and compatibility +improvements. + +## What's Changed + +### Bug fixes + +* hash: allow `unsigned int` != `size_t` in sha256 by @ethomson in + https://github.com/libgit2/libgit2/pull/6996 +* include: Fix code comment termination by @florianpircher in + https://github.com/libgit2/libgit2/pull/6997 +* alternates: allow relative paths in all repositories by @vapier + in https://github.com/libgit2/libgit2/pull/7019 +* Fix potential null dereference by @peter15914 in + https://github.com/libgit2/libgit2/pull/6998 +* cli: fix undefined alloca() on CYGWIN by @carlo-bramini in + https://github.com/libgit2/libgit2/pull/7022 +* attr: honor ignorecase in attribute matching by @ethomson in + https://github.com/libgit2/libgit2/pull/7018 +* tag: Refuse to use HEAD as a tagname by @csware in + https://github.com/libgit2/libgit2/pull/7061 +* Fix memory leak in openssl fips modes by @wklatka in + https://github.com/libgit2/libgit2/pull/7064 +* Fix circular includes between types.h and oid.h by @georgthegreat + in https://github.com/libgit2/libgit2/pull/7059 +* diff: correct diff stat alignment in presence of renames w/ common + prefix. by @kivikakk in https://github.com/libgit2/libgit2/pull/7057 +* Revert include path regression by @ytnuf in + https://github.com/libgit2/libgit2/pull/7039 + +### Build and CI improvements + +* benchmarks: update path to baseline cli by @ethomson in + https://github.com/libgit2/libgit2/pull/7006 +* Update SelectSSH.cmake by @lrm29 in + https://github.com/libgit2/libgit2/pull/7012 +* ci: update download-artifact version by @ethomson in + https://github.com/libgit2/libgit2/pull/7038 +* install cmake files into configured libdir by @kanavin in + https://github.com/libgit2/libgit2/pull/7004 +* Test updates by @ethomson in + https://github.com/libgit2/libgit2/pull/7025 +* conflict tests: check `core.ignorecase` by @emilazy in + https://github.com/libgit2/libgit2/pull/7026 +* Include common.h in version.h by @ethomson in + https://github.com/libgit2/libgit2/pull/7030 +* clar: update to latest version by @ethomson in + https://github.com/libgit2/libgit2/pull/7029 +* Fix MSVC cross compilation by @Faless in + https://github.com/libgit2/libgit2/pull/7079 +* fuzzers: Fix CFLAGS by @nelhage in + https://github.com/libgit2/libgit2/pull/7044 +* Avoid duplicate definition of `git_http_auth_dummy`. by + @JohannesWilde in https://github.com/libgit2/libgit2/pull/7077 + +### Documentation improvements + +* docs: add `update_refs` as ABI breaking change by @ethomson + in https://github.com/libgit2/libgit2/pull/7005 +* docs: correct wrong docstring info for `git_remote_url` by + @DominiqueFuchs in https://github.com/libgit2/libgit2/pull/7076 + +## New Contributors + +* @peter15914 made their first contribution in + https://github.com/libgit2/libgit2/pull/6998 +* @kanavin made their first contribution in + https://github.com/libgit2/libgit2/pull/7004 +* @carlo-bramini made their first contribution in + https://github.com/libgit2/libgit2/pull/7022 +* @vapier made their first contribution in + https://github.com/libgit2/libgit2/pull/7019 +* @emilazy made their first contribution in + https://github.com/libgit2/libgit2/pull/7026 +* @ytnuf made their first contribution in + https://github.com/libgit2/libgit2/pull/7039 +* @DominiqueFuchs made their first contribution in + https://github.com/libgit2/libgit2/pull/7076 +* @wklatka made their first contribution in + https://github.com/libgit2/libgit2/pull/7064 +* @kivikakk made their first contribution in + https://github.com/libgit2/libgit2/pull/7057 +* @JohannesWilde made their first contribution in + https://github.com/libgit2/libgit2/pull/7077 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.9.0...v1.9.1 + +v1.9.0 +------ + +This is release v1.9.0, "Schwibbogen". As usual, it contains numerous +bug fixes, compatibility improvements, and new features. + +This is expected to be the final release in the libgit2 v1.x lineage. +libgit2 v2.0 is expected to be the next version, with support for +SHA256 moving to "supported" status (out of "experimental" status). +This means that v2.0 will have API and ABI changes to support SHA256. + +## Major changes + +* **Documentation improvements** + We've launched a new website for our API reference docs at + https://libgit2.org/docs/reference/main/. To support this, + we've updated the documentation to ensure that all APIs are + well-documented, and added docurium-style specifiers to indicate + more depth about the API surface. + + We now also publish a JSON blob with the API structure and the + documentation that may be helpful for binding authors. + +* **TLS cipher updates** + libgit2 has updated our TLS cipher selection to match the + "compatibility" cipher suite settings as [documented by + Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS). + +* **Blame improvements** + The blame API now contains committer information and commit summaries + for blame hunks, and the ability to get information about the line of + text that was modified. In addition, a CLI blame command has been added + so that the blame functionality can be benchmarked by our benchmark + suite. + +* **More CLI commands** + libgit2 has added `blame` and `init` commands, which have allowed for + [further benchmarking](https://benchmarks.libgit2.org/) and several API + improvements and git compatibility updates. + +* **Warning when configuring without SHA1DC** + Users are encouraged to use SHA1DC, which is _git's hash_; users + should not use SHA1 in the general case. Users will now be warned + if they try to configure cmake with a SHA1 backend (`-DUSE_SHA1=...`). + +## Breaking changes + +There are several ABI-breaking changes that integrators, particularly +maintainers of bindings or FFI users, may want to be aware of. + +* **Blame hunk structure updates** (ABI breaking change) + There are numerous additions to the `git_blame_hunk` structure to + accommodate more information about the blame process. + +* **Checkout strategy updates** (ABI breaking change) + The values for `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_NONE` have been + updated. `GIT_CHECKOUT_SAFE` is now `0`; this was implicitly the + default value (with the options constructors setting that as the + checkout strategy). It is now the default if the checkout strategy + is set to `0`. This allows for an overall code simplification in the + library. + +* **Configuration entry member removal** (ABI breaking change) + The `git_config_entry` structure no longer contains a `free` member; + this was an oversight as end-users should not try to free that + structure. + +* **Configuration backend function changes** (ABI breaking change) + `git_config_backend`s should now return `git_config_backend_entry` + objects instead of `git_config_entry` objects. This allows backends + to provide a mechanism to nicely free the configuration entries that + they provide. + +* **`update_refs` callback for remotes** (ABI breaking change) + The `update_refs` callback was added to the `git_remote_callbacks` + structure to provide additional information about updated refs; + in particular, the `git_refspec` is included for more information + about the remote ref. The `update_refs` callback will be + preferred over the now deprecated `update_tips` callback. + ## What's Changed -This release fixes three bugs that can cause undefined behavior when given well-crafted inputs, either in input files or over network connections. These bugs may be able to be leveraged to cause denial of service attacks or unauthorized code execution. +### New features + +* The `git_signature_default_from_env` API will now produce a pair + of `git_signature`s representing the author, and the committer, + taking the `GIT_AUTHOR_NAME` and `GIT_COMMITTER_NAME` environment + variables into account. Added by @u-quark in + https://github.com/libgit2/libgit2/pull/6706 + +* packbuilder can now be interrupted from a callback. Added @roberth + in https://github.com/libgit2/libgit2/pull/6874 + +* libgit2 now claims to honor the `preciousObject` repository extension. + This extension indicates that the client will never delete objects + (in other words, will not garbage collect). libgit2 has no functionality + to remove objects, so it implicitly obeys this in all cases. Added + by @ethomson in https://github.com/libgit2/libgit2/pull/6886 + +* Push status will be reported even when a push fails. This is useful + to give information from the server about possible updates, even when + the overall status failed. Added by @yerseg in + https://github.com/libgit2/libgit2/pull/6876 + +* You can now generate a thin pack from a mempack instance using + `git_mempack_write_thin_pack`. Added by @roberth in + https://github.com/libgit2/libgit2/pull/6875 + +* The new `LIBGIT2_VERSION_CHECK` macro will indicate whether the version + of libgit2 being compiled against is at least the version specified. + For example: `#if LIBGIT2_VERSION_CHECK(1, 6, 3)` is true for libgit2 + version 1.6.3 or newer. In addition, the new `LIBGIT2_VERSION_NUMBER` + macro will return an integer version representing the libgit2 version + number. For example, for version 1.6.3, `LIBGIT2_VERSION_NUMBER` will + evaluate to `010603`. Added by @HamedMasafi in + https://github.com/libgit2/libgit2/pull/6882 + +* Custom X509 certificates can be added to OpenSSL's certificate store + using the `GIT_OPT_ADD_SSL_X509_CERT` option. Added by @yerseg in + https://github.com/libgit2/libgit2/pull/6877 + +* The libgit2 compatibility CLI now has a `git blame` command. Added by + @ethomson in https://github.com/libgit2/libgit2/pull/6907 + +* Remote callbacks now provide an `update_refs` callback so that users + can now get the `refspec` of the updated reference during push. This + gives more complete information about the remote reference that was + updated. Added by @ethomson in + https://github.com/libgit2/libgit2/pull/6559 + +* An optional FIPS-compliant mode for hashing is now available; you + can set `-DUSE_SHA256=OpenSSL-FIPS` to enable it. Added by @marcind-dot + in https://github.com/libgit2/libgit2/pull/6906 + +* The git-compatible CLI now supports the `git init` command, which has + been useful in identifying API improvements and incompatibilities with + git. Added by @ethomson in https://github.com/libgit2/libgit2/pull/6984 + +* Consumers can now query more information about how libgit2 was + compiled, and query the "backends" that libgit2 uses. Added by + @ethomson in https://github.com/libgit2/libgit2/pull/6971 -Two of these issues were discovered and reported by security engineers at Amazon Web Services. We thank the AWS Security team for their efforts to identify these issues, provide helpful reproduction cases, and responsibly disclose their findings. +### Bug fixes + +* Fix constness issue introduced in #6716 by @ethomson in + https://github.com/libgit2/libgit2/pull/6829 +* odb: conditional `git_hash_ctx_cleanup` in `git_odb_stream` by + @gensmusic in https://github.com/libgit2/libgit2/pull/6836 +* Fix shallow root maintenance during fetch by @kcsaul in + https://github.com/libgit2/libgit2/pull/6846 +* Headers cleanup by @anatol in + https://github.com/libgit2/libgit2/pull/6842 +* http: Initialize `on_status` when using the http-parser backend by + @civodul in https://github.com/libgit2/libgit2/pull/6870 +* Leak in `truncate_racily_clean` in index.c by @lstoppa in + https://github.com/libgit2/libgit2/pull/6884 +* ssh: Omit port option from ssh command unless specified in remote url + by @jayong93 in https://github.com/libgit2/libgit2/pull/6845 +* diff: print the file header on `GIT_DIFF_FORMAT_PATCH_HEADER` by + @carlosmn in https://github.com/libgit2/libgit2/pull/6888 +* Add more robust reporting to SecureTransport errors on macos by + @vcfxb in https://github.com/libgit2/libgit2/pull/6848 +* transport: do not filter tags based on ref dir in local by @rindeal + in https://github.com/libgit2/libgit2/pull/6881 +* push: handle tags to blobs by @ethomson in + https://github.com/libgit2/libgit2/pull/6898 +* Fixes for OpenSSL dynamic by @ethomson in + https://github.com/libgit2/libgit2/pull/6901 +* realpath: unbreak build on OpenBSD by @ajacoutot in + https://github.com/libgit2/libgit2/pull/6932 +* util/win32: Continue if access is denied when deleting a folder by + @lrm29 in https://github.com/libgit2/libgit2/pull/6929 +* object: `git_object_short_id` fails with core.abbrev string values by + @lrm29 in https://github.com/libgit2/libgit2/pull/6944 +* Clear data after negotiation by @lrm29 in + https://github.com/libgit2/libgit2/pull/6947 +* smart: ignore shallow/unshallow packets during ACK processing by + @kempniu in https://github.com/libgit2/libgit2/pull/6973 ### Security fixes -* transport: safely handle messages with no caps -* revparse: fix parsing bug for trailing `@` -* index: correct index has_dir_name check +* ssh: Include rsa-sha2-256 and rsa-sha2-512 in the list of hostkey types + by @lrm29 in https://github.com/libgit2/libgit2/pull/6938 +* TLS: v1.2 and updated cipher list by @ethomson in + https://github.com/libgit2/libgit2/pull/6960 + +### Code cleanups + +* checkout: make safe checkout the default by @ethomson in + https://github.com/libgit2/libgit2/pull/6037 +* url: track whether url explicitly specified a port by @ethomson in + https://github.com/libgit2/libgit2/pull/6851 +* config: remove `free` ptr from `git_config_entry` by @ethomson in + https://github.com/libgit2/libgit2/pull/6804 +* Add SecCopyErrorMessageString for iOS and update README for iOS by + @Kyle-Ye in https://github.com/libgit2/libgit2/pull/6862 +* vector: free is now dispose by @ethomson in + https://github.com/libgit2/libgit2/pull/6896 +* hashmap: a libgit2-idiomatic khash by @ethomson in + https://github.com/libgit2/libgit2/pull/6897 +* hashmap: asserts by @ethomson in + https://github.com/libgit2/libgit2/pull/6902 +* hashmap: further asserts by @ethomson in + https://github.com/libgit2/libgit2/pull/6904 +* Make `GIT_WIN32` an internal declaration by @ethomson in + https://github.com/libgit2/libgit2/pull/6940 +* pathspec: additional pathspec wildcard tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6959 +* repo: don't require option when `template_path` is specified by @ethomson + in https://github.com/libgit2/libgit2/pull/6983 +* options: update X509 cert constant by @ethomson in + https://github.com/libgit2/libgit2/pull/6974 +* remote: Handle fetching negative refspecs by @ryan-ph in + https://github.com/libgit2/libgit2/pull/6962 +* Restore tls v1.0 support temporarily by @ethomson in + https://github.com/libgit2/libgit2/pull/6964 +* SHA256 improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6965 + +### Benchmarks + +* Add benchmarks for blame by @ethomson in + https://github.com/libgit2/libgit2/pull/6920 + +### Build and CI improvements + +* README: add experimental builds to ci table by @ethomson in + https://github.com/libgit2/libgit2/pull/6816 +* Update stransport.c by @ethomson in + https://github.com/libgit2/libgit2/pull/6891 +* stransport: initialize for iOS by @ethomson in + https://github.com/libgit2/libgit2/pull/6893 +* CI updates by @ethomson in + https://github.com/libgit2/libgit2/pull/6895 +* Configurable C standard by @ethomson in + https://github.com/libgit2/libgit2/pull/6911 +* cmake: update python locator by @ethomson in + https://github.com/libgit2/libgit2/pull/6915 +* ci: don't run Windows SHA256 gitdaemon tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6916 +* cmake-standard c standards by @ethomson in + https://github.com/libgit2/libgit2/pull/6914 +* ci: don't run Windows SHA256 gitdaemon tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6919 +* Improve dependency selection in CMake by @ethomson in + https://github.com/libgit2/libgit2/pull/6924 +* ci: port latest fixes to nightlies by @ethomson in + https://github.com/libgit2/libgit2/pull/6926 +* cmake: warn for not using sha1dc by @ethomson in + https://github.com/libgit2/libgit2/pull/6986 + +### Documentation improvements + +* Fix docs for `git_odb_stream_read` return value. by @ehuss in + https://github.com/libgit2/libgit2/pull/6837 +* docs: Add instructions to build examples by @thymusvulgaris in + https://github.com/libgit2/libgit2/pull/6839 +* Fix contradictory phrase in SECURITY.md by @Kyle-Ye in + https://github.com/libgit2/libgit2/pull/6859 +* Update README.md by @Kyle-Ye in + https://github.com/libgit2/libgit2/pull/6860 +* README updates by @ethomson in + https://github.com/libgit2/libgit2/pull/6908 +* typo: s/size on bytes/size in bytes/ by @John-Colvin in + https://github.com/libgit2/libgit2/pull/6909 +* readme: add OpenSSF best practices badge by @ethomson in + https://github.com/libgit2/libgit2/pull/6925 +* Update documentation of `merge_base_many` by @Caleb-T-Owens in + https://github.com/libgit2/libgit2/pull/6927 +* Include documentation generator by @ethomson in + https://github.com/libgit2/libgit2/pull/6945 +* Update documentation generation workflow by @ethomson in + https://github.com/libgit2/libgit2/pull/6948 +* Improve documentation and validate during CI by @ethomson in + https://github.com/libgit2/libgit2/pull/6949 +* Add search functionality to our docs generator by @ethomson in + https://github.com/libgit2/libgit2/pull/6953 +* Documentation: don't resort versions by @ethomson in + https://github.com/libgit2/libgit2/pull/6954 +* Documentation: update `refdb_backend` docs by @ethomson in + https://github.com/libgit2/libgit2/pull/6955 +* Documentation: clean up old documentation by @ethomson in + https://github.com/libgit2/libgit2/pull/6957 +* docs: remind people about `git_libgit2_init` by @ethomson in + https://github.com/libgit2/libgit2/pull/6958 +* Update changelog with v1.8.4 content by @ethomson in + https://github.com/libgit2/libgit2/pull/6961 + +### Git compatibility fixes + +* Limit `.gitattributes` and `.gitignore` files to 100 MiB by @csware in + https://github.com/libgit2/libgit2/pull/6834 +* refs: Handle normalizing negative refspecs by @ryan-ph in + https://github.com/libgit2/libgit2/pull/6951 +* repo: put a newline on the .git link file by @ethomson in + https://github.com/libgit2/libgit2/pull/6981 + +### Dependency updates + +* zlib: update bundled zlib to v1.3.1 by @ethomson in + https://github.com/libgit2/libgit2/pull/6905 +* Update ntlmclient dependency by @ethomson in + https://github.com/libgit2/libgit2/pull/6912 + +### Other changes + +* Create FUNDING.json by @BenJam in + https://github.com/libgit2/libgit2/pull/6853 + +## New Contributors + +* @gensmusic made their first contribution in + https://github.com/libgit2/libgit2/pull/6836 +* @u-quark made their first contribution in + https://github.com/libgit2/libgit2/pull/6706 +* @thymusvulgaris made their first contribution in + https://github.com/libgit2/libgit2/pull/6839 +* @anatol made their first contribution in + https://github.com/libgit2/libgit2/pull/6842 +* @BenJam made their first contribution in + https://github.com/libgit2/libgit2/pull/6853 +* @Kyle-Ye made their first contribution in + https://github.com/libgit2/libgit2/pull/6859 +* @civodul made their first contribution in + https://github.com/libgit2/libgit2/pull/6870 +* @lstoppa made their first contribution in + https://github.com/libgit2/libgit2/pull/6884 +* @jayong93 made their first contribution in + https://github.com/libgit2/libgit2/pull/6845 +* @roberth made their first contribution in + https://github.com/libgit2/libgit2/pull/6874 +* @vcfxb made their first contribution in + https://github.com/libgit2/libgit2/pull/6848 +* @yerseg made their first contribution in + https://github.com/libgit2/libgit2/pull/6876 +* @rindeal made their first contribution in + https://github.com/libgit2/libgit2/pull/6881 +* @HamedMasafi made their first contribution in + https://github.com/libgit2/libgit2/pull/6882 +* @John-Colvin made their first contribution in + https://github.com/libgit2/libgit2/pull/6909 +* @marcind-dot made their first contribution in + https://github.com/libgit2/libgit2/pull/6906 +* @ajacoutot made their first contribution in + https://github.com/libgit2/libgit2/pull/6932 +* @Caleb-T-Owens made their first contribution in + https://github.com/libgit2/libgit2/pull/6927 +* @ryan-ph made their first contribution in + https://github.com/libgit2/libgit2/pull/6951 +* @bmarques1995 made their first contribution in + https://github.com/libgit2/libgit2/pull/6840 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.8.4...v1.9.0 + +v1.8.4 +------ + +We erroneously shipped v1.8.3 without actually including the change +in v1.8.2. This release re-re-introduces the pre-v1.8.0 `commit` +constness behavior. + +## What's Changed + +### Bug fixes + +* Fix constness issue introduced in #6716 by @ethomson in https://github.com/libgit2/libgit2/pull/6829 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.8.3...v1.8.4 + +v1.8.3 +------ + +This release fixes a bug introduced in v1.8.1 for users of the legacy +[Node.js http-parser](https://github.com/nodejs/http-parser) +dependency. + +## What's Changed + +### Bug fixes + +* http: Backport on_status initialize fix for http-parser by @ethomson in https://github.com/libgit2/libgit2/pull/6931 + +v1.8.2 +------ + +This release reverts a const-correctness change introduced in +v1.8.0 for the `git_commit_create` functions. We now retain the +const-behavior for the `commits` arguments from prior to v1.8.0. + +This change was meant to resolve compatibility issues with bindings +and downstream users. + +## What's Changed + +### New features + +* Introduce a stricter debugging allocator for testing by @ethomson in https://github.com/libgit2/libgit2/pull/6811 + +### Bug fixes + +* Fix constness issue introduced in #6716 by @ethomson in https://github.com/libgit2/libgit2/pull/6829 + +### Build and CI improvements + +* README: add experimental builds to ci table by @ethomson in https://github.com/libgit2/libgit2/pull/6816 -**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.1...v1.7.2 +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.8.1...v1.8.2 -v1.7.1 +v1.8.1 ------ +This release primarily includes straightforward bugfixes, as well as +new functionality to have more control over the HTTP User-Agent header. +However, there is an API change from v1.8 that was required for +improved compatibility. + +In v1.8, libgit2 introduced the `report_unchanged ` member in the +`git_fetch_options` structure. We mistakenly introduced this as a +bitfield, which is not suitable for our public API. To correct this +mistake, we have _removed_ the `report_unchanged ` member. To support +the report unchanged tips option, users can set the `update_fetchhead` +member to include the `GIT_REMOTE_UPDATE_REPORT_UNCHANGED` value. + +The libgit2 projects regrets the API change, but this was required to +support cross-platform compatibility. + ## What's Changed +### New features + +* Allow more control over the user-agent by @ethomson in + https://github.com/libgit2/libgit2/pull/6788 + ### Bug fixes -* proxy: Return an error for invalid proxy URLs instead of crashing. by @lrm29 in https://github.com/libgit2/libgit2/pull/6597 -* ssh: fix known_hosts leak in _git_ssh_setup_conn by @steven9724 in https://github.com/libgit2/libgit2/pull/6599 -* repository: make cleanup safe for re-use with grafts by @carlosmn in https://github.com/libgit2/libgit2/pull/6600 -* fix: Add missing include for oidarray. by @dvzrv in https://github.com/libgit2/libgit2/pull/6608 -* Revert "CMake: Search for ssh2 instead of libssh2." by @ethomson in https://github.com/libgit2/libgit2/pull/6619 +* commit: Fix git_commit_create_from_stage without author and + committer by @florianpircher in + https://github.com/libgit2/libgit2/pull/6781 +* process.c: fix environ for macOS by @barracuda156 in + https://github.com/libgit2/libgit2/pull/6792 +* Bounds check for pack index read by @ConradIrwin in + https://github.com/libgit2/libgit2/pull/6796 +* transport: provide a useful error message during cancellation + by @ethomson in https://github.com/libgit2/libgit2/pull/6802 +* transport: support sha256 oids by @ethomson in + https://github.com/libgit2/libgit2/pull/6803 +* Revparse: Correctly accept ref with '@' at the end by @csware in + https://github.com/libgit2/libgit2/pull/6809 +* remote: drop bitfields in git_remote_fetch_options by @ethomson in + https://github.com/libgit2/libgit2/pull/6806 +* examples: fix memory leak in for-each-ref.c by @qaqland in + https://github.com/libgit2/libgit2/pull/6808 +* xdiff: use proper free function by @ethomson in + https://github.com/libgit2/libgit2/pull/6810 +* rand: avoid uninitialized loadavg warnings by @ethomson in + https://github.com/libgit2/libgit2/pull/6812 +* cli: include alloca on illumos / solaris / sunos by @ethomson in + https://github.com/libgit2/libgit2/pull/6813 +* Update git_array allocator to obey strict aliasing rules + by @ethomson in https://github.com/libgit2/libgit2/pull/6814 +* tree: avoid mixed signedness comparison by @ethomson in + https://github.com/libgit2/libgit2/pull/6815 + +### Build and CI improvements + +* ci: update nightly workflows by @ethomson in + https://github.com/libgit2/libgit2/pull/6773 +* ci: give all nightly builds a unique id by @ethomson in + https://github.com/libgit2/libgit2/pull/6782 +* cmake: remove workaround that isn't compatible with Windows on + ARM by @hackhaslam in https://github.com/libgit2/libgit2/pull/6794 + +### Documentation improvements + +* Docs meta-updates by @ethomson in + https://github.com/libgit2/libgit2/pull/6787 + +### Dependency updates + +* Enable llhttp for HTTP parsing by @sgallagher in + https://github.com/libgit2/libgit2/pull/6713 + +## New Contributors + +* @florianpircher made their first contribution in + https://github.com/libgit2/libgit2/pull/6781 +* @barracuda156 made their first contribution in + https://github.com/libgit2/libgit2/pull/6792 +* @sgallagher made their first contribution in + https://github.com/libgit2/libgit2/pull/6713 +* @ConradIrwin made their first contribution in + https://github.com/libgit2/libgit2/pull/6796 +* @qaqland made their first contribution in + https://github.com/libgit2/libgit2/pull/6808 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.8.0...v1.8.1 + +v1.8 +---- + +This is release v1.8.0, "Das Fliegende Klassenzimmer". This release +includes optional, experimental support for invoking OpenSSH to fetch +and push, an easier mechanism to perform the default behavior of +`git commit`, and has many improvements for worktrees. This release +also includes many other new features and bugfixes. + +## Major changes + +* **Executable SSH (OpenSSH) support** + libgit2 can now invoke the command-line OpenSSH to fetch from and push + to remotes over SSH. This support takes the place of libssh2 support. + To use it, configure libgit2 with `cmake -DUSE_SSH=exec`, and please + report any problems that you discover. By @ethomson in + https://github.com/libgit2/libgit2/pull/6617 + +* **Simplified commit creation** + The `git_commit_create_from_stage` API was introduced to allow users to + better emulate the behavior of `git commit` without needing to provide + unnecessary information. The current state of the index is committed to + the current branch. By @ethomson in + https://github.com/libgit2/libgit2/pull/6716 + +* **Worktree improvements** + A number of worktree improvements have been made for better + compatibility with core git. First, libgit2 now understands per-worktree + references, thanks to @csware in + https://github.com/libgit2/libgit2/pull/6387. Worktree-specific + configuration is now supported, thanks to @vermiculus in + https://github.com/libgit2/libgit2/pull/6202. And improved compatibility + with `git worktree add` is now supported, thanks to @herrerog in + https://github.com/libgit2/libgit2/pull/5319. + +## Breaking changes + +* **Adding `WORKTREE` configuration level** (ABI breaking change) + To support worktree configurations at the appropriate level (higher + priority than local configuration, but lower priority than app-specific + configuration), the `GIT_CONFIG_LEVEL_WORKTREE` level was introduced at + priority 6. `GIT_CONFIG_LEVEL_APP` now begins at priority 7. + +* **Changes to `git_config_entry`** (ABI breaking change) + The `git_config_entry` structure now contains information about the + `backend_type` and `origin_path`. The unused `payload` value has been + removed. + +* **`git_push_options` includes remote push options** (ABI breaking change) + The `git_push_options` structure now contains a value for remote push + options. + +## Other changes + +### New features + +* config: provide an "origin" for config entries by @ethomson in + https://github.com/libgit2/libgit2/pull/6615 +* cli: add a `git config` command by @ethomson in + https://github.com/libgit2/libgit2/pull/6616 +* Add OpenSSH support by @ethomson in + https://github.com/libgit2/libgit2/pull/6617 +* remote: optionally report unchanged tips by @ethomson in + https://github.com/libgit2/libgit2/pull/6645 +* Support setting oid type for in-memory repositories by @kcsaul in + https://github.com/libgit2/libgit2/pull/6671 +* cli: add `index-pack` command by @ethomson in + https://github.com/libgit2/libgit2/pull/6681 +* Add `git_repository_commit_parents` to identify the parents of the next + commit given the repository state by @ethomson in + https://github.com/libgit2/libgit2/pull/6707 +* commit: introduce `git_commit_create_from_stage` by @ethomson in + https://github.com/libgit2/libgit2/pull/6716 +* set SSH timeout by @vafada in + https://github.com/libgit2/libgit2/pull/6721 +* Implement push options on push by @russell in + https://github.com/libgit2/libgit2/pull/6439 +* Support index.skipHash true config by @parnic in + https://github.com/libgit2/libgit2/pull/6738 +* worktree: mimic 'git worktree add' behavior. by @herrerog in + https://github.com/libgit2/libgit2/pull/5319 +* Support the extension for worktree-specific config by @vermiculus in + https://github.com/libgit2/libgit2/pull/6202 +* Separate config reader and writer backend priorities (for worktree + configs) by @ethomson in https://github.com/libgit2/libgit2/pull/6756 +* fetch: enable deepening/shortening shallow clones by @kempniu in + https://github.com/libgit2/libgit2/pull/6662 + +### Bug fixes + +* repository: make cleanup safe for re-use with grafts by @carlosmn in + https://github.com/libgit2/libgit2/pull/6600 +* fix: Add missing include for `oidarray`. by @dvzrv in + https://github.com/libgit2/libgit2/pull/6608 +* ssh: fix `known_hosts` leak in `_git_ssh_setup_conn` by @steven9724 in + https://github.com/libgit2/libgit2/pull/6599 +* proxy: Return an error for invalid proxy URLs instead of crashing by + @lrm29 in https://github.com/libgit2/libgit2/pull/6597 +* errors: refactoring - never return `NULL` in `git_error_last()` by + @ethomson in https://github.com/libgit2/libgit2/pull/6625 +* Reject potential option injections over ssh by @carlosmn in + https://github.com/libgit2/libgit2/pull/6636 +* remote: fix memory leak in `git_remote_download()` by @7Ji in + https://github.com/libgit2/libgit2/pull/6651 +* git2: Fix crash when called w/o parameters by @csware in + https://github.com/libgit2/libgit2/pull/6673 +* Avoid macro redefinition of `ENABLE_INTSAFE_SIGNED_FUNCTIONS` by @csware + in https://github.com/libgit2/libgit2/pull/6666 +* util: suppress some uninitialized variable warnings by @boretrk in + https://github.com/libgit2/libgit2/pull/6659 +* push: set generic error in `push_negotiation` cb by @ethomson in + https://github.com/libgit2/libgit2/pull/6675 +* process: test `/usr/bin/false` on BSDs by @ethomson in + https://github.com/libgit2/libgit2/pull/6677 +* clone: don't mix up "http://url" with "http:/url" when figuring out if we + should do a local clone by @boretrk in + https://github.com/libgit2/libgit2/pull/6361 +* Several compatibility fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6678 +* Git blame buffer gives the wrong result in many cases where there are + by @thosey in https://github.com/libgit2/libgit2/pull/6572 +* Fix 'path cannot exist in repository' during diff for in-memory repository + by @kcsaul in https://github.com/libgit2/libgit2/pull/6683 +* process: don't try to close the status by @ethomson in + https://github.com/libgit2/libgit2/pull/6693 +* Minor bug fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6695 +* Bypass shallow clone support for in-memory repositories by @kcsaul in + https://github.com/libgit2/libgit2/pull/6684 +* examples: use `unsigned` int for bitfields by @ethomson in + https://github.com/libgit2/libgit2/pull/6699 +* Fix some bugs caught by UBscan by @ethomson in + https://github.com/libgit2/libgit2/pull/6700 +* `git_diff_find_similar` doesn't always remove unmodified deltas by @yori + in https://github.com/libgit2/libgit2/pull/6642 +* httpclient: clear `client->parser.data` after use by @ethomson in + https://github.com/libgit2/libgit2/pull/6705 +* Do not normalize `safe.directory` paths by @csware in + https://github.com/libgit2/libgit2/pull/6668 +* clone: don't swallow error in `should_checkout` by @ethomson in + https://github.com/libgit2/libgit2/pull/6727 +* Correct index add directory/file conflict detection by @ethomson in + https://github.com/libgit2/libgit2/pull/6729 +* Correct `git_revparse_single` and add revparse fuzzing by @ethomson in + https://github.com/libgit2/libgit2/pull/6730 +* config: properly delete or rename section containing multivars by + @samueltardieu in https://github.com/libgit2/libgit2/pull/6723 +* revparse: ensure bare '@' is truly bare by @ethomson in + https://github.com/libgit2/libgit2/pull/6742 +* repo: ensure we can initialize win32 paths by @ethomson in + https://github.com/libgit2/libgit2/pull/6743 +* Swap `GIT_DIFF_LINE_(ADD|DEL)_EOFNL` to match other Diffs by @xphoniex in + https://github.com/libgit2/libgit2/pull/6240 +* diff: fix test for SHA256 support in `diff_from_buffer` by @ethomson in + https://github.com/libgit2/libgit2/pull/6745 +* http: support empty http.proxy config setting by @ethomson in + https://github.com/libgit2/libgit2/pull/6744 +* More `safe.directory` improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6739 +* Ensure that completely ignored diff is empty by @ethomson in + https://github.com/libgit2/libgit2/pull/5893 +* Fix broken regexp that matches submodule names containing ".path" by + @csware in https://github.com/libgit2/libgit2/pull/6749 +* Fix memory leaks by @csware in + https://github.com/libgit2/libgit2/pull/6748 +* Make `refdb_fs` (hopefully) fully aware of per worktree refs by @csware in + https://github.com/libgit2/libgit2/pull/6387 +* fix log example by @albfan in https://github.com/libgit2/libgit2/pull/6359 +* fetch: fail on depth for local transport by @ethomson in + https://github.com/libgit2/libgit2/pull/6757 +* Fix message trailer parsing by @ethomson in + https://github.com/libgit2/libgit2/pull/6761 +* config: correct fetching the `HIGHEST_LEVEL` config by @ethomson in + https://github.com/libgit2/libgit2/pull/6766 +* Avoid some API breaking changes in v1.8 by @ethomson in + https://github.com/libgit2/libgit2/pull/6768 + +### Build and CI improvements + +* meta: update version numbers to v1.8 by @ethomson in + https://github.com/libgit2/libgit2/pull/6596 +* Revert "CMake: Search for ssh2 instead of libssh2." by @ethomson in + https://github.com/libgit2/libgit2/pull/6619 +* cmake: fix openssl build on win32 by @lazka in + https://github.com/libgit2/libgit2/pull/6626 +* ci: retry flaky online tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6628 +* ci: update to macOS 12 by @ethomson in + https://github.com/libgit2/libgit2/pull/6629 +* Use `#!/bin/bash` for script with bash-specific commands by @roehling in + https://github.com/libgit2/libgit2/pull/6581 +* ci: overwrite nonsense in `/usr/local` during macOS setup by @ethomson in + https://github.com/libgit2/libgit2/pull/6664 +* release: add a compatibility label by @ethomson in + https://github.com/libgit2/libgit2/pull/6676 +* actions: set permissions by @ethomson in + https://github.com/libgit2/libgit2/pull/6680 +* cmake: rename FindIconv to avoid collision with cmake by @ethomson in + https://github.com/libgit2/libgit2/pull/6682 +* ci: allow workflows to read and write packages by @ethomson in + https://github.com/libgit2/libgit2/pull/6687 +* ci: allow workflows to push changes by @ethomson in + https://github.com/libgit2/libgit2/pull/6688 +* tests: remove test for strcasecmp by @boretrk in + https://github.com/libgit2/libgit2/pull/6691 +* CI fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6694 +* ci: improvements to prepare for Cygwin support by @ethomson in + https://github.com/libgit2/libgit2/pull/6696 +* Yet more CI improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6697 +* Fix nightly builds by @ethomson in + https://github.com/libgit2/libgit2/pull/6709 +* Benchmarks: add a site to view results by @ethomson in + https://github.com/libgit2/libgit2/pull/6715 +* `GIT_RAND_GETENTROPY`: do not include `sys/random.h` by @semarie in + https://github.com/libgit2/libgit2/pull/6736 +* add dl to `LIBGIT2_SYSTEM_LIBS` by @christopherfujino in + https://github.com/libgit2/libgit2/pull/6631 +* meta: add dependency tag to release.yml by @ethomson in + https://github.com/libgit2/libgit2/pull/6740 +* CI: fix our nightlies by @ethomson in + https://github.com/libgit2/libgit2/pull/6751 +* trace: Re-enable tests as tracing is now enabled by default by @lrm29 in + https://github.com/libgit2/libgit2/pull/6752 +* tests: don't free an unininitialized repo by @ethomson in + https://github.com/libgit2/libgit2/pull/6763 +* ci: reduce ASLR randomization for TSAN by @ethomson in + https://github.com/libgit2/libgit2/pull/6764 +* packbuilder: adjust nondeterministic tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6762 +* Allow libgit2 to be compiled with mbedtls3. by @adamharrison in + https://github.com/libgit2/libgit2/pull/6759 +* build: update to latest actions versions by @ethomson in + https://github.com/libgit2/libgit2/pull/6765 +* ctype: cast characters to unsigned when classifying characters by + @boretrk in https://github.com/libgit2/libgit2/pull/6679 and + @ethomson in https://github.com/libgit2/libgit2/pull/6770 +* valgrind: suppress OpenSSL warnings by @ethomson in https://github.com/libgit2/libgit2/pull/6769 + +### Documentation improvements + +* README.md: Fix link to conan packages by @lrm29 in + https://github.com/libgit2/libgit2/pull/6621 +* README: replace gmaster with GitButler by @ethomson in + https://github.com/libgit2/libgit2/pull/6692 +* blame example: Fix support for line range in CLI by @wetneb in + https://github.com/libgit2/libgit2/pull/6638 +* Support authentication in push example by @pluehne in + https://github.com/libgit2/libgit2/pull/5904 +* docs: fix mistake in attr.h by @DavHau in + https://github.com/libgit2/libgit2/pull/6714 +* Fix broken links by @csware in + https://github.com/libgit2/libgit2/pull/6747 + +### Platform compatibility fixes + +* stransport: macOS: replace `errSSLNetworkTimeout`, with hard-coded + value by @mascguy in https://github.com/libgit2/libgit2/pull/6610 + +### Git compatibility fixes + +* Do not trim dots from usernames by @georgthegreat in + https://github.com/libgit2/libgit2/pull/6657 +* merge: fix incorrect rename detection for empty files by @herrerog in + https://github.com/libgit2/libgit2/pull/6717 -### Compatibility improvements +### Dependency updates -* stransport: macOS: replace errSSLNetworkTimeout, with hard-coded value by @mascguy in https://github.com/libgit2/libgit2/pull/6610 +* zlib: upgrade bundled zlib to v1.3 by @ethomson in + https://github.com/libgit2/libgit2/pull/6698 +* ntlmclient: update to latest upstream ntlmclient by @ethomson in + https://github.com/libgit2/libgit2/pull/6704 ## New Contributors -* @dvzrv made their first contribution in https://github.com/libgit2/libgit2/pull/6608 -* @steven9724 made their first contribution in https://github.com/libgit2/libgit2/pull/6599 -**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.0...v1.7.1 +* @dvzrv made their first contribution in + https://github.com/libgit2/libgit2/pull/6608 +* @mascguy made their first contribution in + https://github.com/libgit2/libgit2/pull/6610 +* @steven9724 made their first contribution in + https://github.com/libgit2/libgit2/pull/6599 +* @lazka made their first contribution in + https://github.com/libgit2/libgit2/pull/6626 +* @roehling made their first contribution in + https://github.com/libgit2/libgit2/pull/6581 +* @7Ji made their first contribution in + https://github.com/libgit2/libgit2/pull/6651 +* @kempniu made their first contribution in + https://github.com/libgit2/libgit2/pull/6662 +* @thosey made their first contribution in + https://github.com/libgit2/libgit2/pull/6572 +* @wetneb made their first contribution in + https://github.com/libgit2/libgit2/pull/6638 +* @yori made their first contribution in + https://github.com/libgit2/libgit2/pull/6642 +* @pluehne made their first contribution in + https://github.com/libgit2/libgit2/pull/5904 +* @DavHau made their first contribution in + https://github.com/libgit2/libgit2/pull/6714 +* @vafada made their first contribution in + https://github.com/libgit2/libgit2/pull/6721 +* @semarie made their first contribution in + https://github.com/libgit2/libgit2/pull/6736 +* @christopherfujino made their first contribution in + https://github.com/libgit2/libgit2/pull/6631 +* @parnic made their first contribution in + https://github.com/libgit2/libgit2/pull/6738 +* @samueltardieu made their first contribution in + https://github.com/libgit2/libgit2/pull/6723 +* @xphoniex made their first contribution in + https://github.com/libgit2/libgit2/pull/6240 +* @adamharrison made their first contribution in + https://github.com/libgit2/libgit2/pull/6759 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.0...v1.8.0 v1.7 ---- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8e38c7d4..986daae5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,7 +3,6 @@ file(GLOB SRC_EXAMPLES *.c *.h) add_executable(lg2 ${SRC_EXAMPLES}) -set_target_properties(lg2 PROPERTIES C_STANDARD 90) # Ensure that we do not use deprecated functions internally add_definitions(-DGIT_DEPRECATE_HARD) diff --git a/examples/README.md b/examples/README.md index 769c4b26..0f1f2538 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,8 +15,8 @@ so there are no restrictions on their use. For annotated HTML versions, see the "Examples" section of: - http://libgit2.github.com/libgit2 + https://libgit2.org/libgit2 such as: - http://libgit2.github.com/libgit2/ex/HEAD/general.html + https://libgit2.org/libgit2/ex/HEAD/general.html diff --git a/examples/args.h b/examples/args.h index d626f98c..4db0493b 100644 --- a/examples/args.h +++ b/examples/args.h @@ -8,7 +8,7 @@ struct args_info { int argc; char **argv; int pos; - int opts_done : 1; /**< Did we see a -- separator */ + unsigned int opts_done : 1; /**< Did we see a -- separator */ }; #define ARGS_INFO_INIT { argc, argv, 0, 0 } #define ARGS_CURRENT(args) args->argv[args->pos] diff --git a/examples/blame.c b/examples/blame.c index 77087a57..1e2869a6 100644 --- a/examples/blame.c +++ b/examples/blame.c @@ -47,6 +47,10 @@ int lg2_blame(git_repository *repo, int argc, char *argv[]) if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT; + if (o.start_line && o.end_line) { + blameopts.min_line = o.start_line; + blameopts.max_line = o.end_line; + } /** * The commit range comes in "committish" form. Use the rev-parse API to @@ -93,7 +97,7 @@ int lg2_blame(git_repository *repo, int argc, char *argv[]) while (i < rawsize) { const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i)); char oid[10] = {0}; - const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line); + const git_blame_hunk *hunk = git_blame_hunk_byline(blame, line); if (break_on_null_hunk && !hunk) break; diff --git a/examples/checkout.c b/examples/checkout.c index ac7b7422..82567cdc 100644 --- a/examples/checkout.c +++ b/examples/checkout.c @@ -35,9 +35,9 @@ */ typedef struct { - int force : 1; - int progress : 1; - int perf : 1; + unsigned int force : 1; + unsigned int progress : 1; + unsigned int perf : 1; } checkout_options; static void print_usage(void) diff --git a/examples/commit.c b/examples/commit.c index aedc1af7..c6e0a8dc 100644 --- a/examples/commit.c +++ b/examples/commit.c @@ -39,7 +39,7 @@ int lg2_commit(git_repository *repo, int argc, char **argv) git_index *index; git_object *parent = NULL; git_reference *ref = NULL; - git_signature *signature; + git_signature *author_signature, *committer_signature; /* Validate args */ if (argc < 3 || strcmp(opt, "-m") != 0) { @@ -63,21 +63,23 @@ int lg2_commit(git_repository *repo, int argc, char **argv) check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "Error looking up tree", NULL); - check_lg2(git_signature_default(&signature, repo), "Error creating signature", NULL); + check_lg2(git_signature_default_from_env(&author_signature, &committer_signature, repo), + "Error creating signature", NULL); check_lg2(git_commit_create_v( &commit_oid, repo, "HEAD", - signature, - signature, + author_signature, + committer_signature, NULL, comment, tree, parent ? 1 : 0, parent), "Error creating commit", NULL); git_index_free(index); - git_signature_free(signature); + git_signature_free(author_signature); + git_signature_free(committer_signature); git_tree_free(tree); git_object_free(parent); git_reference_free(ref); diff --git a/examples/fetch.c b/examples/fetch.c index bbd882cf..a8b3527a 100644 --- a/examples/fetch.c +++ b/examples/fetch.c @@ -13,20 +13,24 @@ static int progress_cb(const char *str, int len, void *data) * updated. The message we output depends on whether it's a new one or * an update. */ -static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) +static int update_cb(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *data) { char a_str[GIT_OID_SHA1_HEXSIZE+1], b_str[GIT_OID_SHA1_HEXSIZE+1]; + git_buf remote_name; (void)data; + if (git_refspec_rtransform(&remote_name, spec, refname) < 0) + return -1; + git_oid_fmt(b_str, b); b_str[GIT_OID_SHA1_HEXSIZE] = '\0'; if (git_oid_is_zero(a)) { - printf("[new] %.20s %s\n", b_str, refname); + printf("[new] %.20s %s -> %s\n", b_str, remote_name.ptr, refname); } else { git_oid_fmt(a_str, a); a_str[GIT_OID_SHA1_HEXSIZE] = '\0'; - printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname); + printf("[updated] %.10s..%.10s %s -> %s\n", a_str, b_str, remote_name.ptr, refname); } return 0; @@ -72,7 +76,7 @@ int lg2_fetch(git_repository *repo, int argc, char **argv) goto on_error; /* Set up the callbacks (only update_tips for now) */ - fetch_opts.callbacks.update_tips = &update_cb; + fetch_opts.callbacks.update_refs = &update_cb; fetch_opts.callbacks.sideband_progress = &progress_cb; fetch_opts.callbacks.transfer_progress = transfer_progress_cb; fetch_opts.callbacks.credentials = cred_acquire_cb; diff --git a/examples/for-each-ref.c b/examples/for-each-ref.c index a0706741..f745bc38 100644 --- a/examples/for-each-ref.c +++ b/examples/for-each-ref.c @@ -25,6 +25,8 @@ static int show_ref(git_reference *ref, void *data) git_object_type2string(git_object_type(obj)), git_reference_name(ref)); + git_object_free(obj); + git_reference_free(ref); if (resolved) git_reference_free(resolved); return 0; diff --git a/examples/general.c b/examples/general.c index 7f44cd78..0275f84a 100644 --- a/examples/general.c +++ b/examples/general.c @@ -31,8 +31,8 @@ * Git Internals that you will need to know to work with Git at this level, * check out [Chapter 10][pg] of the Pro Git book. * - * [lg]: http://libgit2.github.com - * [ap]: http://libgit2.github.com/libgit2 + * [lg]: https://libgit2.org + * [ap]: https://libgit2.org/libgit2 * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain */ @@ -97,7 +97,7 @@ int lg2_general(git_repository *repo, int argc, char** argv) * * (Try running this program against tests/resources/testrepo.git.) * - * [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + * [me]: https://libgit2.org/libgit2/#HEAD/group/repository */ repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; @@ -173,7 +173,7 @@ static void oid_parsing(git_oid *oid) * working with raw objects, we'll need to get this structure from the * repository. * - * [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb + * [odb]: https://libgit2.org/libgit2/#HEAD/group/odb */ static void object_database(git_repository *repo, git_oid *oid) { @@ -262,7 +262,7 @@ static void object_database(git_repository *repo, git_oid *oid) * of them here. You can read about the other ones in the [commit API * docs][cd]. * - * [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit + * [cd]: https://libgit2.org/libgit2/#HEAD/group/commit */ static void commit_writing(git_repository *repo) { @@ -347,7 +347,7 @@ static void commit_writing(git_repository *repo) * data in the commit - the author (name, email, datetime), committer * (same), tree, message, encoding and parent(s). * - * [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit + * [pco]: https://libgit2.org/libgit2/#HEAD/group/commit */ static void commit_parsing(git_repository *repo) { @@ -418,7 +418,7 @@ static void commit_parsing(git_repository *repo) * functions very similarly to the commit lookup, parsing and creation * methods, since the objects themselves are very similar. * - * [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag + * [tm]: https://libgit2.org/libgit2/#HEAD/group/tag */ static void tag_parsing(git_repository *repo) { @@ -472,7 +472,7 @@ static void tag_parsing(git_repository *repo) * object type in Git, but a useful structure for parsing and traversing * tree entries. * - * [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree + * [tp]: https://libgit2.org/libgit2/#HEAD/group/tree */ static void tree_parsing(git_repository *repo) { @@ -536,7 +536,7 @@ static void tree_parsing(git_repository *repo) * from disk and writing it to the db and getting the oid back so you * don't have to do all those steps yourself. * - * [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob + * [ba]: https://libgit2.org/libgit2/#HEAD/group/blob */ static void blob_parsing(git_repository *repo) { @@ -578,7 +578,7 @@ static void blob_parsing(git_repository *repo) * that were ancestors of (reachable from) a given starting point. This * can allow you to create `git log` type functionality. * - * [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk + * [rw]: https://libgit2.org/libgit2/#HEAD/group/revwalk */ static void revwalking(git_repository *repo) { @@ -643,7 +643,7 @@ static void revwalking(git_repository *repo) * The [index file API][gi] allows you to read, traverse, update and write * the Git index file (sometimes thought of as the staging area). * - * [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index + * [gi]: https://libgit2.org/libgit2/#HEAD/group/index */ static void index_walking(git_repository *repo) { @@ -687,7 +687,7 @@ static void index_walking(git_repository *repo) * references such as branches, tags and remote references (everything in * the .git/refs directory). * - * [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference + * [ref]: https://libgit2.org/libgit2/#HEAD/group/reference */ static void reference_listing(git_repository *repo) { @@ -740,7 +740,7 @@ static void reference_listing(git_repository *repo) * The [config API][config] allows you to list and update config values * in any of the accessible config file locations (system, global, local). * - * [config]: http://libgit2.github.com/libgit2/#HEAD/group/config + * [config]: https://libgit2.org/libgit2/#HEAD/group/config */ static void config_files(const char *repo_path, git_repository* repo) { diff --git a/examples/index-pack.c b/examples/index-pack.c index 0f8234c7..e9f32b8b 100644 --- a/examples/index-pack.c +++ b/examples/index-pack.c @@ -29,7 +29,7 @@ int lg2_index_pack(git_repository *repo, int argc, char **argv) } #ifdef GIT_EXPERIMENTAL_SHA256 - error = git_indexer_new(&idx, ".", git_repository_oid_type(repo), NULL); + error = git_indexer_new(&idx, ".", NULL); #else error = git_indexer_new(&idx, ".", 0, NULL, NULL); #endif diff --git a/examples/init.c b/examples/init.c index 2f681c5a..036c156a 100644 --- a/examples/init.c +++ b/examples/init.c @@ -123,14 +123,14 @@ int lg2_init(git_repository *repo, int argc, char *argv[]) */ static void create_initial_commit(git_repository *repo) { - git_signature *sig; + git_signature *author_sig = NULL, *committer_sig = NULL; git_index *index; git_oid tree_id, commit_id; git_tree *tree; /** First use the config to initialize a commit signature for the user. */ - if (git_signature_default(&sig, repo) < 0) + if ((git_signature_default_from_env(&author_sig, &committer_sig, repo) < 0)) fatal("Unable to create a commit signature.", "Perhaps 'user.name' and 'user.email' are not set"); @@ -162,14 +162,15 @@ static void create_initial_commit(git_repository *repo) */ if (git_commit_create_v( - &commit_id, repo, "HEAD", sig, sig, + &commit_id, repo, "HEAD", author_sig, committer_sig, NULL, "Initial commit", tree, 0) < 0) fatal("Could not create the initial commit", NULL); /** Clean up so we don't leak memory. */ git_tree_free(tree); - git_signature_free(sig); + git_signature_free(author_sig); + git_signature_free(committer_sig); } static void usage(const char *error, const char *arg) diff --git a/examples/log.c b/examples/log.c index 4b0a95dc..62a6eb58 100644 --- a/examples/log.c +++ b/examples/log.c @@ -50,6 +50,7 @@ static int add_revision(struct log_state *s, const char *revstr); /** log_options holds other command line options that affect log output */ struct log_options { int show_diff; + int show_oneline; int show_log_size; int skip, limit; int min_parents, max_parents; @@ -81,9 +82,11 @@ int lg2_log(git_repository *repo, int argc, char *argv[]) git_commit *commit = NULL; git_pathspec *ps = NULL; + memset(&s, 0, sizeof(s)); + /** Parse arguments and set up revwalker. */ - last_arg = parse_options(&s, &opt, argc, argv); s.repo = repo; + last_arg = parse_options(&s, &opt, argc, argv); diffopts.pathspec.strings = &argv[last_arg]; diffopts.pathspec.count = argc - last_arg; @@ -335,34 +338,45 @@ static void print_commit(git_commit *commit, struct log_options *opts) const char *scan, *eol; git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); - printf("commit %s\n", buf); - if (opts->show_log_size) { - printf("log size %d\n", (int)strlen(git_commit_message(commit))); - } + if (opts->show_oneline) { + printf("%s ", buf); + } else { + printf("commit %s\n", buf); - if ((count = (int)git_commit_parentcount(commit)) > 1) { - printf("Merge:"); - for (i = 0; i < count; ++i) { - git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); - printf(" %s", buf); + if (opts->show_log_size) { + printf("log size %d\n", (int)strlen(git_commit_message(commit))); + } + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); } - printf("\n"); - } - if ((sig = git_commit_author(commit)) != NULL) { - printf("Author: %s <%s>\n", sig->name, sig->email); - print_time(&sig->when, "Date: "); + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); } - printf("\n"); for (scan = git_commit_message(commit); scan && *scan; ) { for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; - printf(" %.*s\n", (int)(eol - scan), scan); + if (opts->show_oneline) + printf("%.*s\n", (int)(eol - scan), scan); + else + printf(" %.*s\n", (int)(eol - scan), scan); scan = *eol ? eol + 1 : NULL; + if (opts->show_oneline) + break; } - printf("\n"); + if (!opts->show_oneline) + printf("\n"); } /** Helper to find how many files in a commit changed from its nth parent. */ @@ -407,8 +421,6 @@ static int parse_options( struct log_state *s, struct log_options *opt, int argc, char **argv) { struct args_info args = ARGS_INFO_INIT; - - memset(s, 0, sizeof(*s)); s->sorting = GIT_SORT_TIME; memset(opt, 0, sizeof(*opt)); @@ -424,7 +436,7 @@ static int parse_options( else /** Try failed revision parse as filename. */ break; - } else if (!match_arg_separator(&args)) { + } else if (match_arg_separator(&args)) { break; } else if (!strcmp(a, "--date-order")) @@ -474,6 +486,8 @@ static int parse_options( opt->show_diff = 1; else if (!strcmp(a, "--log-size")) opt->show_log_size = 1; + else if (!strcmp(a, "--oneline")) + opt->show_oneline = 1; else usage("Unsupported argument", a); } diff --git a/examples/merge.c b/examples/merge.c index 460c06a2..7a76912c 100644 --- a/examples/merge.c +++ b/examples/merge.c @@ -30,7 +30,7 @@ struct merge_options { git_annotated_commit **annotated; size_t annotated_count; - int no_commit : 1; + unsigned int no_commit : 1; }; static void print_usage(void) diff --git a/examples/push.c b/examples/push.c index bcf30760..5113eed3 100644 --- a/examples/push.c +++ b/examples/push.c @@ -32,6 +32,7 @@ /** Entry point for this command */ int lg2_push(git_repository *repo, int argc, char **argv) { git_push_options options; + git_remote_callbacks callbacks; git_remote* remote = NULL; char *refspec = "refs/heads/master"; const git_strarray refspecs = { @@ -47,7 +48,11 @@ int lg2_push(git_repository *repo, int argc, char **argv) { check_lg2(git_remote_lookup(&remote, repo, "origin" ), "Unable to lookup remote", NULL); + check_lg2(git_remote_init_callbacks(&callbacks, GIT_REMOTE_CALLBACKS_VERSION), "Error initializing remote callbacks", NULL); + callbacks.credentials = cred_acquire_cb; + check_lg2(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION ), "Error initializing push", NULL); + options.callbacks = callbacks; check_lg2(git_remote_push(remote, &refspecs, &options), "Error pushing", NULL); diff --git a/examples/show-index.c b/examples/show-index.c index 0a5e7d1a..ac004087 100644 --- a/examples/show-index.c +++ b/examples/show-index.c @@ -31,7 +31,7 @@ int lg2_show_index(git_repository *repo, int argc, char **argv) dirlen = strlen(dir); if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) { #ifdef GIT_EXPERIMENTAL_SHA256 - check_lg2(git_index_open(&index, dir, GIT_OID_SHA1), "could not open index", dir); + check_lg2(git_index_open(&index, dir, NULL), "could not open index", dir); #else check_lg2(git_index_open(&index, dir), "could not open index", dir); #endif diff --git a/examples/stash.c b/examples/stash.c index 8142439c..19772436 100644 --- a/examples/stash.c +++ b/examples/stash.c @@ -108,7 +108,7 @@ static int cmd_push(git_repository *repo, struct opts *opts) if (opts->argc) usage("push does not accept any parameters"); - check_lg2(git_signature_default(&signature, repo), + check_lg2(git_signature_default_from_env(&signature, NULL, repo), "Unable to get signature", NULL); check_lg2(git_stash_save(&stashid, repo, signature, NULL, GIT_STASH_DEFAULT), "Unable to save stash", NULL); diff --git a/examples/tag.c b/examples/tag.c index e4f71ae6..ebe1a9d7 100644 --- a/examples/tag.c +++ b/examples/tag.c @@ -226,7 +226,7 @@ static void action_create_tag(tag_state *state) check_lg2(git_revparse_single(&target, repo, opts->target), "Unable to resolve spec", opts->target); - check_lg2(git_signature_default(&tagger, repo), + check_lg2(git_signature_default_from_env(&tagger, NULL, repo), "Unable to create signature", NULL); check_lg2(git_tag_create(&oid, repo, opts->tag_name, diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt index a2c19ed4..38d705f7 100644 --- a/fuzzers/CMakeLists.txt +++ b/fuzzers/CMakeLists.txt @@ -2,7 +2,6 @@ if(BUILD_FUZZERS AND NOT USE_STANDALONE_FUZZERS) set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer-no-link") - add_c_flag(-fsanitize=fuzzer) add_c_flag(-fsanitize=fuzzer-no-link) unset(CMAKE_REQUIRED_FLAGS) endif() @@ -12,17 +11,21 @@ foreach(fuzz_target_src ${SRC_FUZZERS}) string(REPLACE ".c" "" fuzz_target_name ${fuzz_target_src}) string(REPLACE "_fuzzer" "" fuzz_name ${fuzz_target_name}) - set(${fuzz_target_name}_SOURCES ${fuzz_target_src} ${LIBGIT2_OBJECTS}) + set(${fuzz_target_name}_SOURCES + ${fuzz_target_src} "fuzzer_utils.c" ${LIBGIT2_OBJECTS}) + if(USE_STANDALONE_FUZZERS) list(APPEND ${fuzz_target_name}_SOURCES "standalone_driver.c") endif() - add_executable(${fuzz_target_name} ${${fuzz_target_name}_SOURCES}) - set_target_properties(${fuzz_target_name} PROPERTIES C_STANDARD 90) + add_executable(${fuzz_target_name} ${${fuzz_target_name}_SOURCES}) target_include_directories(${fuzz_target_name} PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) target_include_directories(${fuzz_target_name} SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) target_link_libraries(${fuzz_target_name} ${LIBGIT2_SYSTEM_LIBS}) + if(NOT USE_STANDALONE_FUZZERS) + target_link_options(${fuzz_target_name} PRIVATE "-fsanitize=fuzzer") + endif() add_test(${fuzz_target_name} "${CMAKE_CURRENT_BINARY_DIR}/${fuzz_target_name}" "${CMAKE_CURRENT_SOURCE_DIR}/corpora/${fuzz_name}") endforeach() diff --git a/fuzzers/config_file_fuzzer.c b/fuzzers/config_file_fuzzer.c index 890adbfc..76303696 100644 --- a/fuzzers/config_file_fuzzer.c +++ b/fuzzers/config_file_fuzzer.c @@ -43,7 +43,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) goto out; } - if ((err = git_config_backend_from_string(&backend, (const char*)data, size)) != 0) { + if ((err = git_config_backend_from_string(&backend, (const char*)data, size, NULL)) != 0) { goto out; } if ((err = git_config_add_backend(cfg, backend, 0, NULL, 0)) != 0) { diff --git a/fuzzers/corpora/revparse/head b/fuzzers/corpora/revparse/head new file mode 100644 index 00000000..e5517e4c --- /dev/null +++ b/fuzzers/corpora/revparse/head @@ -0,0 +1 @@ +HEAD \ No newline at end of file diff --git a/fuzzers/corpora/revparse/revat b/fuzzers/corpora/revparse/revat new file mode 100644 index 00000000..382ffc0b --- /dev/null +++ b/fuzzers/corpora/revparse/revat @@ -0,0 +1 @@ +xxxxxxxxxxxxxxxx@ \ No newline at end of file diff --git a/fuzzers/download_refs_fuzzer.c b/fuzzers/download_refs_fuzzer.c index ff95cd10..c2b80ccb 100644 --- a/fuzzers/download_refs_fuzzer.c +++ b/fuzzers/download_refs_fuzzer.c @@ -16,6 +16,7 @@ #include "futils.h" #include "standalone_driver.h" +#include "fuzzer_utils.h" #define UNUSED(x) (void)(x) @@ -157,33 +158,10 @@ static int fuzzer_transport_cb(git_transport **out, git_remote *owner, void *par return git_transport_smart(out, owner, &def); } -static void fuzzer_git_abort(const char *op) -{ - const git_error *err = git_error_last(); - fprintf(stderr, "unexpected libgit error: %s: %s\n", - op, err ? err->message : ""); - abort(); -} - int LLVMFuzzerInitialize(int *argc, char ***argv) { -#if defined(_WIN32) - char tmpdir[MAX_PATH], path[MAX_PATH]; - - if (GetTempPath((DWORD)sizeof(tmpdir), tmpdir) == 0) - abort(); - - if (GetTempFileName(tmpdir, "lg2", 1, path) == 0) - abort(); - - if (git_futils_mkdir(path, 0700, 0) < 0) - abort(); -#else - char path[] = "/tmp/git2.XXXXXX"; - - if (mkdtemp(path) != path) - abort(); -#endif + UNUSED(argc); + UNUSED(argv); if (git_libgit2_init() < 0) abort(); @@ -191,12 +169,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) if (git_libgit2_opts(GIT_OPT_SET_PACK_MAX_OBJECTS, 10000000) < 0) abort(); - UNUSED(argc); - UNUSED(argv); - - if (git_repository_init(&repo, path, 1) < 0) - fuzzer_git_abort("git_repository_init"); - + repo = fuzzer_repo_init(); return 0; } diff --git a/fuzzers/fuzzer_utils.c b/fuzzers/fuzzer_utils.c new file mode 100644 index 00000000..cde5065d --- /dev/null +++ b/fuzzers/fuzzer_utils.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include + +#include "git2.h" +#include "futils.h" + +#include "fuzzer_utils.h" + +void fuzzer_git_abort(const char *op) +{ + const git_error *err = git_error_last(); + fprintf(stderr, "unexpected libgit error: %s: %s\n", + op, err ? err->message : ""); + abort(); +} + +git_repository *fuzzer_repo_init(void) +{ + git_repository *repo; + +#if defined(_WIN32) + char tmpdir[MAX_PATH], path[MAX_PATH]; + + if (GetTempPath((DWORD)sizeof(tmpdir), tmpdir) == 0) + abort(); + + if (GetTempFileName(tmpdir, "lg2", 1, path) == 0) + abort(); + + if (git_futils_mkdir(path, 0700, 0) < 0) + abort(); +#else + char path[] = "/tmp/git2.XXXXXX"; + + if (mkdtemp(path) != path) + abort(); +#endif + + if (git_repository_init(&repo, path, 1) < 0) + fuzzer_git_abort("git_repository_init"); + + return repo; +} diff --git a/src/libgit2/transports/ssh.h b/fuzzers/fuzzer_utils.h similarity index 58% rename from src/libgit2/transports/ssh.h rename to fuzzers/fuzzer_utils.h index d3e741f1..6b67c9a6 100644 --- a/src/libgit2/transports/ssh.h +++ b/fuzzers/fuzzer_utils.h @@ -4,11 +4,11 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_transports_ssh_h__ -#define INCLUDE_transports_ssh_h__ -#include "common.h" +#ifndef INCLUDE_fuzzer_utils_h__ +#define INCLUDE_fuzzer_utils_h__ -int git_transport_ssh_global_init(void); +extern void fuzzer_git_abort(const char *op); +extern git_repository *fuzzer_repo_init(void); #endif diff --git a/fuzzers/packfile_fuzzer.c b/fuzzers/packfile_fuzzer.c index aeba9575..bcbdd8bc 100644 --- a/fuzzers/packfile_fuzzer.c +++ b/fuzzers/packfile_fuzzer.c @@ -84,7 +84,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } #ifdef GIT_EXPERIMENTAL_SHA256 - error = git_indexer_new(&indexer, ".", GIT_OID_SHA1, NULL); + error = git_indexer_new(&indexer, ".", NULL); #else error = git_indexer_new(&indexer, ".", 0, odb, NULL); #endif diff --git a/fuzzers/revparse_fuzzer.c b/fuzzers/revparse_fuzzer.c new file mode 100644 index 00000000..37c22e22 --- /dev/null +++ b/fuzzers/revparse_fuzzer.c @@ -0,0 +1,52 @@ +/* + * libgit2 revparse fuzzer target. + * + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include + +#include "git2.h" + +#include "standalone_driver.h" +#include "fuzzer_utils.h" + +#define UNUSED(x) (void)(x) + +static git_repository *repo; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + UNUSED(argc); + UNUSED(argv); + + if (git_libgit2_init() < 0) + abort(); + + if (git_libgit2_opts(GIT_OPT_SET_PACK_MAX_OBJECTS, 10000000) < 0) + abort(); + + repo = fuzzer_repo_init(); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + git_object *obj = NULL; + char *c; + + if ((c = calloc(1, size + 1)) == NULL) + abort(); + + memcpy(c, data, size); + + git_revparse_single(&obj, repo, c); + git_object_free(obj); + free(c); + + return 0; +} diff --git a/fuzzers/standalone_driver.c b/fuzzers/standalone_driver.c index cd4f7175..17b54de9 100644 --- a/fuzzers/standalone_driver.c +++ b/fuzzers/standalone_driver.c @@ -67,7 +67,7 @@ int main(int argc, char **argv) fprintf(stderr, "Done %d runs\n", i); exit: - git_vector_free_deep(&corpus_files); + git_vector_dispose_deep(&corpus_files); git_libgit2_shutdown(); return error; } diff --git a/include/git2/annotated_commit.h b/include/git2/annotated_commit.h index 3b7103f2..04f3b1c3 100644 --- a/include/git2/annotated_commit.h +++ b/include/git2/annotated_commit.h @@ -13,9 +13,16 @@ /** * @file git2/annotated_commit.h - * @brief Git annotated commit routines + * @brief A commit and information about how it was looked up by the user. * @defgroup git_annotated_commit Git annotated commit routines * @ingroup Git + * + * An "annotated commit" is a commit that contains information about + * how the commit was resolved, which can be used for maintaining the + * user's "intent" through commands like merge and rebase. For example, + * if a user wants to "merge HEAD" then an annotated commit is used to + * both contain the HEAD commit _and_ the fact that it was resolved as + * the HEAD ref. * @{ */ GIT_BEGIN_DECL @@ -25,7 +32,7 @@ GIT_BEGIN_DECL * The resulting git_annotated_commit must be freed with * `git_annotated_commit_free`. * - * @param out pointer to store the git_annotated_commit result in + * @param[out] out pointer to store the git_annotated_commit result in * @param repo repository that contains the given reference * @param ref reference to use to lookup the git_annotated_commit * @return 0 on success or error code @@ -40,7 +47,7 @@ GIT_EXTERN(int) git_annotated_commit_from_ref( * The resulting git_annotated_commit must be freed with * `git_annotated_commit_free`. * - * @param out pointer to store the git_annotated_commit result in + * @param[out] out pointer to store the git_annotated_commit result in * @param repo repository that contains the given commit * @param branch_name name of the (remote) branch * @param remote_url url of the remote @@ -67,7 +74,7 @@ GIT_EXTERN(int) git_annotated_commit_from_fetchhead( * most specific function (eg `git_annotated_commit_from_ref`) * instead of this one when that data is known. * - * @param out pointer to store the git_annotated_commit result in + * @param[out] out pointer to store the git_annotated_commit result in * @param repo repository that contains the given commit * @param id the commit object id to lookup * @return 0 on success or error code @@ -84,7 +91,7 @@ GIT_EXTERN(int) git_annotated_commit_lookup( * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for * information on the syntax accepted. * - * @param out pointer to store the git_annotated_commit result in + * @param[out] out pointer to store the git_annotated_commit result in * @param repo repository that contains the given commit * @param revspec the extended sha syntax string to use to lookup the commit * @return 0 on success or error code diff --git a/include/git2/apply.h b/include/git2/apply.h index db652bde..7ab939d1 100644 --- a/include/git2/apply.h +++ b/include/git2/apply.h @@ -14,9 +14,12 @@ /** * @file git2/apply.h - * @brief Git patch application routines + * @brief Apply patches to the working directory or index * @defgroup git_apply Git patch application routines * @ingroup Git + * + * Mechanisms to apply a patch to the index, the working directory, + * or both. * @{ */ GIT_BEGIN_DECL @@ -57,7 +60,15 @@ typedef int GIT_CALLBACK(git_apply_hunk_cb)( const git_diff_hunk *hunk, void *payload); -/** Flags controlling the behavior of git_apply */ +/** + * Flags controlling the behavior of `git_apply`. + * + * When the callback: + * - returns < 0, the apply process will be aborted. + * - returns > 0, the hunk will not be applied, but the apply process + * continues + * - returns 0, the hunk is applied, and the apply process continues. + */ typedef enum { /** * Don't actually make changes, just test that the patch applies. @@ -67,12 +78,19 @@ typedef enum { } git_apply_flags_t; /** - * Apply options structure + * Apply options structure. + * + * When the callback: + * - returns < 0, the apply process will be aborted. + * - returns > 0, the hunk will not be applied, but the apply process + * continues + * - returns 0, the hunk is applied, and the apply process continues. * * Initialize with `GIT_APPLY_OPTIONS_INIT`. Alternatively, you can * use `git_apply_options_init`. * - * @see git_apply_to_tree, git_apply + * @see git_apply_to_tree + * @see git_apply */ typedef struct { unsigned int version; /**< The version */ @@ -83,14 +101,17 @@ typedef struct { /** When applying a patch, callback that will be made per hunk. */ git_apply_hunk_cb hunk_cb; - /** Payload passed to both delta_cb & hunk_cb. */ + /** Payload passed to both `delta_cb` & `hunk_cb`. */ void *payload; - /** Bitmask of git_apply_flags_t */ + /** Bitmask of `git_apply_flags_t` */ unsigned int flags; } git_apply_options; +/** Current version for the `git_apply_options` structure */ #define GIT_APPLY_OPTIONS_VERSION 1 + +/** Static constructor for `git_apply_options` */ #define GIT_APPLY_OPTIONS_INIT {GIT_APPLY_OPTIONS_VERSION} /** diff --git a/include/git2/attr.h b/include/git2/attr.h index 0c838727..e5216fef 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -12,9 +12,13 @@ /** * @file git2/attr.h - * @brief Git attribute management routines + * @brief Attribute management routines * @defgroup git_attr Git attribute management routines * @ingroup Git + * + * Attributes specify additional information about how git should + * handle particular paths - for example, they may indicate whether + * a particular filter is applied, like LFS or line ending conversions. * @{ */ GIT_BEGIN_DECL @@ -114,16 +118,18 @@ GIT_EXTERN(git_attr_value_t) git_attr_value(const char *attr); * use index only for creating archives or for a bare repo (if an * index has been specified for the bare repo). */ + +/** Examine attribute in working directory, then index */ #define GIT_ATTR_CHECK_FILE_THEN_INDEX 0 +/** Examine attribute in index, then working directory */ #define GIT_ATTR_CHECK_INDEX_THEN_FILE 1 -#define GIT_ATTR_CHECK_INDEX_ONLY 2 +/** Examine attributes only in the index */ +#define GIT_ATTR_CHECK_INDEX_ONLY 2 /** * Check attribute flags: controlling extended attribute behavior. * * Normally, attribute checks include looking in the /etc (or system - * equivalent) directory for a `gitattributes` file. Passing this - * flag will cause attribute checks to ignore that file. * equivalent) directory for a `gitattributes` file. Passing the * `GIT_ATTR_CHECK_NO_SYSTEM` flag will cause attribute checks to * ignore that file. @@ -134,8 +140,12 @@ GIT_EXTERN(git_attr_value_t) git_attr_value(const char *attr); * Passing the `GIT_ATTR_CHECK_INCLUDE_COMMIT` flag will use attributes * from a `.gitattributes` file in a specific commit. */ + +/** Ignore system attributes */ #define GIT_ATTR_CHECK_NO_SYSTEM (1 << 2) +/** Honor `.gitattributes` in the HEAD revision */ #define GIT_ATTR_CHECK_INCLUDE_HEAD (1 << 3) +/** Honor `.gitattributes` in a specific commit */ #define GIT_ATTR_CHECK_INCLUDE_COMMIT (1 << 4) /** @@ -160,7 +170,10 @@ typedef struct { git_oid attr_commit_id; } git_attr_options; +/** Current version for the `git_attr_options` structure */ #define GIT_ATTR_OPTIONS_VERSION 1 + +/** Static constructor for `git_attr_options` */ #define GIT_ATTR_OPTIONS_INIT {GIT_ATTR_OPTIONS_VERSION} /** diff --git a/include/git2/blame.h b/include/git2/blame.h index cb961a56..f3e66924 100644 --- a/include/git2/blame.h +++ b/include/git2/blame.h @@ -13,9 +13,14 @@ /** * @file git2/blame.h - * @brief Git blame routines + * @brief Specify a file's most recent changes per-line * @defgroup git_blame Git blame routines * @ingroup Git + * + * Producing a "blame" (or "annotated history") decorates individual + * lines in a file with the commit that introduced that particular line + * of changes. This can be useful to indicate when and why a particular + * change was made. * @{ */ GIT_BEGIN_DECL @@ -87,7 +92,7 @@ typedef struct git_blame_options { unsigned int version; /** A combination of `git_blame_flag_t` */ - uint32_t flags; + unsigned int flags; /** * The lower bound on the number of alphanumeric characters that @@ -122,7 +127,10 @@ typedef struct git_blame_options { size_t max_line; } git_blame_options; +/** Current version for the `git_blame_options` structure */ #define GIT_BLAME_OPTIONS_VERSION 1 + +/** Static constructor for `git_blame_options` */ #define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION} /** @@ -165,6 +173,13 @@ typedef struct git_blame_hunk { */ git_signature *final_signature; + /** + * The committer of `final_commit_id`. If `GIT_BLAME_USE_MAILMAP` has + * been specified, it will contain the canonical real name and email + * address. + */ + git_signature *final_committer; + /** * The OID of the commit where this hunk was found. * This will usually be the same as `final_commit_id`, except when @@ -190,6 +205,18 @@ typedef struct git_blame_hunk { */ git_signature *orig_signature; + /** + * The committer of `orig_commit_id`. If `GIT_BLAME_USE_MAILMAP` has + * been specified, it will contain the canonical real name and email + * address. + */ + git_signature *orig_committer; + + /* + * The summary of the commit. + */ + const char *summary; + /** * The 1 iff the hunk has been tracked to a boundary commit (the root, * or the commit specified in git_blame_options.oldest_commit) @@ -197,16 +224,75 @@ typedef struct git_blame_hunk { char boundary; } git_blame_hunk; +/** + * Structure that represents a line in a blamed file. + */ +typedef struct git_blame_line { + const char *ptr; + size_t len; +} git_blame_line; /** Opaque structure to hold blame results */ typedef struct git_blame git_blame; +/** + * Gets the number of lines that exist in the blame structure. + * + * @param blame The blame structure to query. + * @return The number of line. + */ +GIT_EXTERN(size_t) git_blame_linecount(git_blame *blame); + /** * Gets the number of hunks that exist in the blame structure. * * @param blame The blame structure to query. * @return The number of hunks. */ +GIT_EXTERN(size_t) git_blame_hunkcount(git_blame *blame); + +/** + * Gets the blame hunk at the given index. + * + * @param blame the blame structure to query + * @param index index of the hunk to retrieve + * @return the hunk at the given index, or NULL on error + */ +GIT_EXTERN(const git_blame_hunk *) git_blame_hunk_byindex( + git_blame *blame, + size_t index); + +/** + * Gets the hunk that relates to the given line number in the newest + * commit. + * + * @param blame the blame structure to query + * @param lineno the (1-based) line number to find a hunk for + * @return the hunk that contains the given line, or NULL on error + */ +GIT_EXTERN(const git_blame_hunk *) git_blame_hunk_byline( + git_blame *blame, + size_t lineno); + +/** + * Gets the information about the line in the blame. + * + * @param blame the blame structure to query + * @param idx the (1-based) line number + * @return the blamed line, or NULL on error + */ +GIT_EXTERN(const git_blame_line *) git_blame_line_byindex( + git_blame *blame, + size_t idx); + +#ifndef GIT_DEPRECATE_HARD +/** + * Gets the number of hunks that exist in the blame structure. + * + * @param blame The blame structure to query. + * @return The number of hunks. + */ + GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame); /** @@ -216,9 +302,9 @@ GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame); * @param index index of the hunk to retrieve * @return the hunk at the given index, or NULL on error */ -GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex( - git_blame *blame, - uint32_t index); +GIT_EXTERN(const git_blame_hunk *) git_blame_get_hunk_byindex( + git_blame *blame, + uint32_t index); /** * Gets the hunk that relates to the given line number in the newest commit. @@ -227,39 +313,58 @@ GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex( * @param lineno the (1-based) line number to find a hunk for * @return the hunk that contains the given line, or NULL on error */ -GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline( - git_blame *blame, - size_t lineno); +GIT_EXTERN(const git_blame_hunk *) git_blame_get_hunk_byline( + git_blame *blame, + size_t lineno); +#endif /** - * Get the blame for a single file. + * Get the blame for a single file in the repository. * * @param out pointer that will receive the blame object * @param repo repository whose history is to be walked * @param path path to file to consider - * @param options options for the blame operation. If NULL, this is treated as - * though GIT_BLAME_OPTIONS_INIT were passed. - * @return 0 on success, or an error code. (use git_error_last for information - * about the error.) + * @param options options for the blame operation or NULL + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_blame_file( - git_blame **out, - git_repository *repo, - const char *path, - git_blame_options *options); + git_blame **out, + git_repository *repo, + const char *path, + git_blame_options *options); +/** + * Get the blame for a single file in the repository, using the specified + * buffer contents as the uncommitted changes of the file (the working + * directory contents). + * + * @param out pointer that will receive the blame object + * @param repo repository whose history is to be walked + * @param path path to file to consider + * @param contents the uncommitted changes + * @param contents_len the length of the changes buffer + * @param options options for the blame operation or NULL + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_blame_file_from_buffer( + git_blame **out, + git_repository *repo, + const char *path, + const char *contents, + size_t contents_len, + git_blame_options *options); /** - * Get blame data for a file that has been modified in memory. The `reference` - * parameter is a pre-calculated blame for the in-odb history of the file. This - * means that once a file blame is completed (which can be expensive), updating - * the buffer blame is very fast. + * Get blame data for a file that has been modified in memory. The `blame` + * parameter is a pre-calculated blame for the in-odb history of the file. + * This means that once a file blame is completed (which can be expensive), + * updating the buffer blame is very fast. * - * Lines that differ between the buffer and the committed version are marked as - * having a zero OID for their final_commit_id. + * Lines that differ between the buffer and the committed version are + * marked as having a zero OID for their final_commit_id. * * @param out pointer that will receive the resulting blame data - * @param reference cached blame from the history of the file (usually the output + * @param base cached blame from the history of the file (usually the output * from git_blame_file) * @param buffer the (possibly) modified contents of the file * @param buffer_len number of valid bytes in the buffer @@ -267,10 +372,10 @@ GIT_EXTERN(int) git_blame_file( * about the error) */ GIT_EXTERN(int) git_blame_buffer( - git_blame **out, - git_blame *reference, - const char *buffer, - size_t buffer_len); + git_blame **out, + git_blame *base, + const char *buffer, + size_t buffer_len); /** * Free memory allocated by git_blame_file or git_blame_buffer. diff --git a/include/git2/blob.h b/include/git2/blob.h index 6db85f38..0ed16855 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -15,9 +15,13 @@ /** * @file git2/blob.h - * @brief Git blob load and write routines + * @brief A blob represents a file in a git repository. * @defgroup git_blob Git blob load and write routines * @ingroup Git + * + * A blob represents a file in a git repository. This is the raw data + * as it is stored in the repository itself. Blobs may be "filtered" + * to produce the on-disk content. * @{ */ GIT_BEGIN_DECL @@ -25,12 +29,15 @@ GIT_BEGIN_DECL /** * Lookup a blob object from a repository. * - * @param blob pointer to the looked up blob + * @param[out] blob pointer to the looked up blob * @param repo the repo to use when locating the blob. * @param id identity of the blob to locate. * @return 0 or an error code */ -GIT_EXTERN(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git_oid *id); +GIT_EXTERN(int) git_blob_lookup( + git_blob **blob, + git_repository *repo, + const git_oid *id); /** * Lookup a blob object from a repository, @@ -38,7 +45,7 @@ GIT_EXTERN(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git * * @see git_object_lookup_prefix * - * @param blob pointer to the looked up blob + * @param[out] blob pointer to the looked up blob * @param repo the repo to use when locating the blob. * @param id identity of the blob to locate. * @param len the length of the short identifier @@ -84,7 +91,7 @@ GIT_EXTERN(git_repository *) git_blob_owner(const git_blob *blob); * time. * * @param blob pointer to the blob - * @return the pointer, or NULL on error + * @return @type `unsigned char *` the pointer, or NULL on error */ GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob); @@ -92,12 +99,14 @@ GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob); * Get the size in bytes of the contents of a blob * * @param blob pointer to the blob - * @return size on bytes + * @return size in bytes */ GIT_EXTERN(git_object_size_t) git_blob_rawsize(const git_blob *blob); /** * Flags to control the functionality of `git_blob_filter`. + * + * @flags */ typedef enum { /** When set, filters will not be applied to binary files. */ @@ -128,16 +137,34 @@ typedef enum { * Initialize with `GIT_BLOB_FILTER_OPTIONS_INIT`. Alternatively, you can * use `git_blob_filter_options_init`. * + * @options[version] GIT_BLOB_FILTER_OPTIONS_VERSION + * @options[init_macro] GIT_BLOB_FILTER_OPTIONS_INIT + * @options[init_function] git_blob_filter_options_init */ typedef struct { + /** Version number of the options structure. */ int version; - /** Flags to control the filtering process, see `git_blob_filter_flag_t` above */ + /** + * Flags to control the filtering process, see `git_blob_filter_flag_t` above. + * + * @type[flags] git_blob_filter_flag_t + */ uint32_t flags; #ifdef GIT_DEPRECATE_HARD + /** + * Unused and reserved for ABI compatibility. + * + * @deprecated this value should not be set + */ void *reserved; #else + /** + * This value is unused and reserved for API compatibility. + * + * @deprecated this value should not be set + */ git_oid *commit_id; #endif @@ -148,8 +175,18 @@ typedef struct { git_oid attr_commit_id; } git_blob_filter_options; +/** + * The current version number for the `git_blob_filter_options` structure ABI. + */ #define GIT_BLOB_FILTER_OPTIONS_VERSION 1 -#define GIT_BLOB_FILTER_OPTIONS_INIT {GIT_BLOB_FILTER_OPTIONS_VERSION, GIT_BLOB_FILTER_CHECK_FOR_BINARY} + +/** + * The default values for `git_blob_filter_options`. + */ +#define GIT_BLOB_FILTER_OPTIONS_INIT { \ + GIT_BLOB_FILTER_OPTIONS_VERSION, \ + GIT_BLOB_FILTER_CHECK_FOR_BINARY \ + } /** * Initialize git_blob_filter_options structure @@ -158,10 +195,12 @@ typedef struct { * to creating an instance with `GIT_BLOB_FILTER_OPTIONS_INIT`. * * @param opts The `git_blob_filter_options` struct to initialize. - * @param version The struct version; pass `GIT_BLOB_FILTER_OPTIONS_VERSION`. + * @param version The struct version; pass GIT_BLOB_FILTER_OPTIONS_VERSION * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_blob_filter_options_init(git_blob_filter_options *opts, unsigned int version); +GIT_EXTERN(int) git_blob_filter_options_init( + git_blob_filter_options *opts, + unsigned int version); /** * Get a buffer with the filtered content of a blob. @@ -171,7 +210,7 @@ GIT_EXTERN(int) git_blob_filter_options_init(git_blob_filter_options *opts, unsi * CRLF filtering or other types of changes depending on the file * attributes set for the blob and the content detected in it. * - * The output is written into a `git_buf` which the caller must free + * The output is written into a `git_buf` which the caller must dispose * when done (via `git_buf_dispose`). * * If no filters need to be applied, then the `out` buffer will just @@ -183,7 +222,7 @@ GIT_EXTERN(int) git_blob_filter_options_init(git_blob_filter_options *opts, unsi * @param blob Pointer to the blob * @param as_path Path used for file attribute lookups, etc. * @param opts Options to use for filtering the blob - * @return 0 on success or an error code + * @return @type[enum] git_error_code 0 on success or an error code */ GIT_EXTERN(int) git_blob_filter( git_buf *out, @@ -192,10 +231,10 @@ GIT_EXTERN(int) git_blob_filter( git_blob_filter_options *opts); /** - * Read a file from the working folder of a repository - * and write it to the Object Database as a loose blob + * Read a file from the working folder of a repository and write it + * to the object database. * - * @param id return the id of the written blob + * @param[out] id return the id of the written blob * @param repo repository where the blob will be written. * this repository cannot be bare * @param relative_path file from which the blob will be created, @@ -205,19 +244,23 @@ GIT_EXTERN(int) git_blob_filter( GIT_EXTERN(int) git_blob_create_from_workdir(git_oid *id, git_repository *repo, const char *relative_path); /** - * Read a file from the filesystem and write its content - * to the Object Database as a loose blob + * Read a file from the filesystem (not necessarily inside the + * working folder of the repository) and write it to the object + * database. * - * @param id return the id of the written blob + * @param[out] id return the id of the written blob * @param repo repository where the blob will be written. * this repository can be bare or not * @param path file from which the blob will be created * @return 0 or an error code */ -GIT_EXTERN(int) git_blob_create_from_disk(git_oid *id, git_repository *repo, const char *path); +GIT_EXTERN(int) git_blob_create_from_disk( + git_oid *id, + git_repository *repo, + const char *path); /** - * Create a stream to write a new blob into the object db + * Create a stream to write a new blob into the object database. * * This function may need to buffer the data on disk and will in * general not be the right choice if you know the size of the data @@ -234,7 +277,7 @@ GIT_EXTERN(int) git_blob_create_from_disk(git_oid *id, git_repository *repo, con * what git filters should be applied to the object before it is written * to the object database. * - * @param out the stream into which to write + * @param[out] out the stream into which to write * @param repo Repository where the blob will be written. * This repository can be bare or not. * @param hintpath If not NULL, will be used to select data filters @@ -247,11 +290,11 @@ GIT_EXTERN(int) git_blob_create_from_stream( const char *hintpath); /** - * Close the stream and write the blob to the object db + * Close the stream and finalize writing the blob to the object database. * * The stream will be closed and freed. * - * @param out the id of the new blob + * @param[out] out the id of the new blob * @param stream the stream to close * @return 0 or an error code */ @@ -260,9 +303,9 @@ GIT_EXTERN(int) git_blob_create_from_stream_commit( git_writestream *stream); /** - * Write an in-memory buffer to the ODB as a blob + * Write an in-memory buffer to the object database as a blob. * - * @param id return the id of the written blob + * @param[out] id return the id of the written blob * @param repo repository where the blob will be written * @param buffer data to be written into the blob * @param len length of the data @@ -272,14 +315,14 @@ GIT_EXTERN(int) git_blob_create_from_buffer( git_oid *id, git_repository *repo, const void *buffer, size_t len); /** - * Determine if the blob content is most certainly binary or not. + * Determine if the blob content is most likely binary or not. * * The heuristic used to guess if a file is binary is taken from core git: * Searching for NUL bytes and looking for a reasonable ratio of printable * to non-printable characters among the first 8000 bytes. * * @param blob The blob which content should be analyzed - * @return 1 if the content of the blob is detected + * @return @type bool 1 if the content of the blob is detected * as binary; 0 otherwise. */ GIT_EXTERN(int) git_blob_is_binary(const git_blob *blob); @@ -300,7 +343,7 @@ GIT_EXTERN(int) git_blob_data_is_binary(const char *data, size_t len); * Create an in-memory copy of a blob. The copy must be explicitly * free'd or it will leak. * - * @param out Pointer to store the copy of the object + * @param[out] out Pointer to store the copy of the object * @param source Original object to copy * @return 0. */ diff --git a/include/git2/branch.h b/include/git2/branch.h index 27c6fa15..56d737d0 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -13,9 +13,15 @@ /** * @file git2/branch.h - * @brief Git branch parsing routines + * @brief Branch creation and handling * @defgroup git_branch Git branch management * @ingroup Git + * + * A branch is a specific type of reference, at any particular time, + * a git working directory typically is said to have a branch "checked out", + * meaning that commits that are created will be made "on" a branch. + * This occurs by updating the branch reference to point to the new + * commit. The checked out branch is indicated by the `HEAD` meta-ref. * @{ */ GIT_BEGIN_DECL @@ -33,18 +39,13 @@ GIT_BEGIN_DECL * See `git_tag_create()` for rules about valid names. * * @param out Pointer where to store the underlying reference. - * * @param repo the repository to create the branch in. - * * @param branch_name Name for the branch; this name is - * validated for consistency. It should also not conflict with - * an already existing branch name. - * + * validated for consistency. It should also not conflict with + * an already existing branch name. * @param target Commit to which this branch should point. This object - * must belong to the given `repo`. - * + * must belong to the given `repo`. * @param force Overwrite existing branch. - * * @return 0, GIT_EINVALIDSPEC or an error code. * A proper reference is written in the refs/heads namespace * pointing to the provided target commit. @@ -63,15 +64,21 @@ GIT_EXTERN(int) git_branch_create( * commit, which lets you specify which extended sha syntax string was * specified by a user, allowing for more exact reflog messages. * - * See the documentation for `git_branch_create()`. - * - * @see git_branch_create + * @param ref_out Pointer where to store the underlying reference. + * @param repo the repository to create the branch in. + * @param branch_name Name for the branch; this name is + * validated for consistency. It should also not conflict with + * an already existing branch name. + * @param target Annotated commit to which this branch should point. This + * object must belong to the given `repo`. + * @param force Overwrite existing branch. + * @return 0, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_branch_create_from_annotated( git_reference **ref_out, - git_repository *repository, + git_repository *repo, const char *branch_name, - const git_annotated_commit *commit, + const git_annotated_commit *target, int force); /** @@ -222,7 +229,7 @@ GIT_EXTERN(int) git_branch_upstream( * @param branch the branch to configure * @param branch_name remote-tracking or local branch to set as upstream. * - * @return 0 on success; GIT_ENOTFOUND if there's no branch named `branch_name` + * @return @type git_error_t 0 on success; GIT_ENOTFOUND if there's no branch named `branch_name` * or an error code */ GIT_EXTERN(int) git_branch_set_upstream( diff --git a/include/git2/buffer.h b/include/git2/buffer.h index 9fa97203..3fe4f854 100644 --- a/include/git2/buffer.h +++ b/include/git2/buffer.h @@ -11,9 +11,12 @@ /** * @file git2/buffer.h - * @brief Buffer export structure - * + * @brief A data structure to return data to callers * @ingroup Git + * + * The `git_buf` buffer is used to return arbitrary data - typically + * strings - to callers. Callers are responsible for freeing the memory + * in a buffer with the `git_buf_dispose` function. * @{ */ GIT_BEGIN_DECL @@ -67,8 +70,7 @@ typedef struct { */ GIT_EXTERN(void) git_buf_dispose(git_buf *buffer); -GIT_END_DECL - /** @} */ +GIT_END_DECL #endif diff --git a/include/git2/cert.h b/include/git2/cert.h index 05213a57..7b91b638 100644 --- a/include/git2/cert.h +++ b/include/git2/cert.h @@ -12,7 +12,7 @@ /** * @file git2/cert.h - * @brief Git certificate objects + * @brief TLS and SSH certificate handling * @defgroup git_cert Certificate objects * @ingroup Git * @{ @@ -169,4 +169,5 @@ typedef struct { /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 9f834111..bdea9284 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -13,9 +13,13 @@ /** * @file git2/checkout.h - * @brief Git checkout routines + * @brief Update the contents of the working directory * @defgroup git_checkout Git checkout routines * @ingroup Git + * + * Update the contents of the working directory, or a subset of the + * files in the working directory, to point to the data in the index + * or a specific commit. * @{ */ GIT_BEGIN_DECL @@ -31,17 +35,11 @@ GIT_BEGIN_DECL * check out, the "baseline" tree of what was checked out previously, the * working directory for actual files, and the index for staged changes. * - * You give checkout one of three strategies for update: - * - * - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts, - * etc., but doesn't make any actual changes. - * - * - `GIT_CHECKOUT_FORCE` is at the opposite extreme, taking any action to - * make the working directory match the target (including potentially - * discarding modified files). + * You give checkout one of two strategies for update: * - * - `GIT_CHECKOUT_SAFE` is between these two options, it will only make - * modifications that will not lose changes. + * - `GIT_CHECKOUT_SAFE` is the default, and similar to git's default, + * which will make modifications that will not lose changes in the + * working directory. * * | target == baseline | target != baseline | * ---------------------|-----------------------|----------------------| @@ -55,6 +53,10 @@ GIT_BEGIN_DECL * baseline present | | | * ---------------------|-----------------------|----------------------| * + * - `GIT_CHECKOUT_FORCE` will take any action to make the working + * directory match the target (including potentially discarding + * modified files). + * * To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout * notification callback (see below) that displays information about dirty * files. The default behavior will cancel checkout on conflicts. @@ -69,6 +71,9 @@ GIT_BEGIN_DECL * * There are some additional flags to modify the behavior of checkout: * + * - `GIT_CHECKOUT_DRY_RUN` is a dry-run strategy that checks for conflicts, + * etc., but doesn't make any actual changes. + * * - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates * even if there are conflicts (instead of cancelling the checkout). * @@ -102,29 +107,24 @@ GIT_BEGIN_DECL * files or folders that fold to the same name on case insensitive * filesystems. This can cause files to retain their existing names * and write through existing symbolic links. + * + * @flags */ typedef enum { - GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */ - /** * Allow safe updates that cannot overwrite uncommitted data. - * If the uncommitted changes don't conflict with the checked out files, - * the checkout will still proceed, leaving the changes intact. - * - * Mutually exclusive with GIT_CHECKOUT_FORCE. - * GIT_CHECKOUT_FORCE takes precedence over GIT_CHECKOUT_SAFE. + * If the uncommitted changes don't conflict with the checked + * out files, the checkout will still proceed, leaving the + * changes intact. */ - GIT_CHECKOUT_SAFE = (1u << 0), + GIT_CHECKOUT_SAFE = 0, /** - * Allow all updates to force working directory to look like index. - * - * Mutually exclusive with GIT_CHECKOUT_SAFE. - * GIT_CHECKOUT_FORCE takes precedence over GIT_CHECKOUT_SAFE. + * Allow all updates to force working directory to look like + * the index, potentially losing data in the process. */ GIT_CHECKOUT_FORCE = (1u << 1), - /** Allow checkout to recreate missing files */ GIT_CHECKOUT_RECREATE_MISSING = (1u << 2), @@ -178,8 +178,9 @@ typedef enum { GIT_CHECKOUT_DONT_WRITE_INDEX = (1u << 23), /** - * Show what would be done by a checkout. Stop after sending - * notifications; don't update the working directory or index. + * Perform a "dry run", reporting what _would_ be done but + * without actually making changes in the working directory + * or the index. */ GIT_CHECKOUT_DRY_RUN = (1u << 24), @@ -187,6 +188,14 @@ typedef enum { GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1u << 25), /** + * Do not do a checkout and do not fire callbacks; this is primarily + * useful only for internal functions that will perform the + * checkout themselves but need to pass checkout options into + * another function, for example, `git_clone`. + */ + GIT_CHECKOUT_NONE = (1u << 30), + + /* * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED */ @@ -194,7 +203,6 @@ typedef enum { GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16), /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17) - } git_checkout_strategy_t; /** @@ -210,6 +218,8 @@ typedef enum { * Notification callbacks are made prior to modifying any files on disk, * so canceling on any notification will still happen prior to any files * being modified. + * + * @flags */ typedef enum { GIT_CHECKOUT_NOTIFY_NONE = 0, @@ -251,7 +261,17 @@ typedef struct { size_t chmod_calls; } git_checkout_perfdata; -/** Checkout notification callback function */ +/** + * Checkout notification callback function. + * + * @param why the notification reason + * @param path the path to the file being checked out + * @param baseline the baseline's diff file information + * @param target the checkout target diff file information + * @param workdir the working directory diff file information + * @param payload the user-supplied callback payload + * @return 0 on success, or an error code + */ typedef int GIT_CALLBACK(git_checkout_notify_cb)( git_checkout_notify_t why, const char *path, @@ -260,14 +280,26 @@ typedef int GIT_CALLBACK(git_checkout_notify_cb)( const git_diff_file *workdir, void *payload); -/** Checkout progress notification function */ +/** + * Checkout progress notification function. + * + * @param path the path to the file being checked out + * @param completed_steps number of checkout steps completed + * @param total_steps number of total steps in the checkout process + * @param payload the user-supplied callback payload + */ typedef void GIT_CALLBACK(git_checkout_progress_cb)( const char *path, size_t completed_steps, size_t total_steps, void *payload); -/** Checkout perfdata notification function */ +/** + * Checkout performance data reporting function. + * + * @param perfdata the performance data for the checkout + * @param payload the user-supplied callback payload + */ typedef void GIT_CALLBACK(git_checkout_perfdata_cb)( const git_checkout_perfdata *perfdata, void *payload); @@ -278,10 +310,18 @@ typedef void GIT_CALLBACK(git_checkout_perfdata_cb)( * Initialize with `GIT_CHECKOUT_OPTIONS_INIT`. Alternatively, you can * use `git_checkout_options_init`. * + * @options[version] GIT_CHECKOUT_OPTIONS_VERSION + * @options[init_macro] GIT_CHECKOUT_OPTIONS_INIT + * @options[init_function] git_checkout_options_init */ typedef struct git_checkout_options { unsigned int version; /**< The version */ + /** + * Checkout strategy. Default is a safe checkout. + * + * @type[flags] git_checkout_strategy_t + */ unsigned int checkout_strategy; /**< default will be a safe checkout */ int disable_filters; /**< don't apply filters like CRLF conversion */ @@ -289,7 +329,13 @@ typedef struct git_checkout_options { unsigned int file_mode; /**< default is 0644 or 0755 as dictated by blob */ int file_open_flags; /**< default is O_CREAT | O_TRUNC | O_WRONLY */ - unsigned int notify_flags; /**< see `git_checkout_notify_t` above */ + /** + * Checkout notification flags specify what operations the notify + * callback is invoked for. + * + * @type[flags] git_checkout_notify_t + */ + unsigned int notify_flags; /** * Optional callback to get notifications on specific file states. @@ -344,8 +390,12 @@ typedef struct git_checkout_options { void *perfdata_payload; } git_checkout_options; + +/** Current version for the `git_checkout_options` structure */ #define GIT_CHECKOUT_OPTIONS_VERSION 1 -#define GIT_CHECKOUT_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE} + +/** Static constructor for `git_checkout_options` */ +#define GIT_CHECKOUT_OPTIONS_INIT { GIT_CHECKOUT_OPTIONS_VERSION } /** * Initialize git_checkout_options structure @@ -414,4 +464,5 @@ GIT_EXTERN(int) git_checkout_tree( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/cherrypick.h b/include/git2/cherrypick.h index 0e6a252e..e6cf99ea 100644 --- a/include/git2/cherrypick.h +++ b/include/git2/cherrypick.h @@ -13,9 +13,12 @@ /** * @file git2/cherrypick.h - * @brief Git cherry-pick routines + * @brief Cherry-pick the contents of an individual commit * @defgroup git_cherrypick Git cherry-pick routines * @ingroup Git + * + * "Cherry-pick" will attempts to re-apply the changes in an + * individual commit to the current index and working directory. * @{ */ GIT_BEGIN_DECL @@ -33,8 +36,13 @@ typedef struct { git_checkout_options checkout_opts; /**< Options for the checkout */ } git_cherrypick_options; +/** Current version for the `git_cherrypick_options` structure */ #define GIT_CHERRYPICK_OPTIONS_VERSION 1 -#define GIT_CHERRYPICK_OPTIONS_INIT {GIT_CHERRYPICK_OPTIONS_VERSION, 0, GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT} + +/** Static constructor for `git_cherrypick_options` */ +#define GIT_CHERRYPICK_OPTIONS_INIT { \ + GIT_CHERRYPICK_OPTIONS_VERSION, 0, \ + GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT } /** * Initialize git_cherrypick_options structure @@ -89,4 +97,3 @@ GIT_EXTERN(int) git_cherrypick( GIT_END_DECL #endif - diff --git a/include/git2/clone.h b/include/git2/clone.h index 3481f254..b7a47ab4 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -17,9 +17,13 @@ /** * @file git2/clone.h - * @brief Git cloning routines + * @brief Clone a remote repository to the local disk * @defgroup git_clone Git cloning routines * @ingroup Git + * + * Clone will take a remote repository - located on a remote server + * accessible by HTTPS or SSH, or a repository located elsewhere on + * the local disk - and place a copy in the given local path. * @{ */ GIT_BEGIN_DECL @@ -59,7 +63,7 @@ typedef enum { * Callers of git_clone may provide a function matching this signature to override * the remote creation and customization process during a clone operation. * - * @param out the resulting remote + * @param[out] out the resulting remote * @param repo the repository in which to create the remote * @param name the remote's name * @param url the remote's url @@ -81,7 +85,7 @@ typedef int GIT_CALLBACK(git_remote_create_cb)( * to override the repository creation and customization process * during a clone operation. * - * @param out the resulting repository + * @param[out] out the resulting repository * @param path path in which to create the repository * @param bare whether the repository is bare. This is the value from the clone options * @param payload payload specified by the options @@ -99,14 +103,17 @@ typedef int GIT_CALLBACK(git_repository_create_cb)( * Initialize with `GIT_CLONE_OPTIONS_INIT`. Alternatively, you can * use `git_clone_options_init`. * + * @options[version] GIT_CLONE_OPTIONS_VERSION + * @options[init_macro] GIT_CLONE_OPTIONS_INIT + * @options[init_function] git_clone_options_init */ typedef struct git_clone_options { unsigned int version; /** * These options are passed to the checkout step. To disable - * checkout, set the `checkout_strategy` to - * `GIT_CHECKOUT_NONE`. + * checkout, set the `checkout_strategy` to `GIT_CHECKOUT_NONE` + * or `GIT_CHECKOUT_DRY_RUN`. */ git_checkout_options checkout_opts; @@ -163,10 +170,14 @@ typedef struct git_clone_options { void *remote_cb_payload; } git_clone_options; +/** Current version for the `git_clone_options` structure */ #define GIT_CLONE_OPTIONS_VERSION 1 -#define GIT_CLONE_OPTIONS_INIT { GIT_CLONE_OPTIONS_VERSION, \ - { GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \ - GIT_FETCH_OPTIONS_INIT } + +/** Static constructor for `git_clone_options` */ +#define GIT_CLONE_OPTIONS_INIT \ + { GIT_CLONE_OPTIONS_VERSION, \ + GIT_CHECKOUT_OPTIONS_INIT, \ + GIT_FETCH_OPTIONS_INIT } /** * Initialize git_clone_options structure @@ -189,7 +200,11 @@ GIT_EXTERN(int) git_clone_options_init( * git's defaults. You can use the options in the callback to * customize how these are created. * - * @param out pointer that will receive the resulting repository object + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * + * @param[out] out pointer that will receive the resulting repository object * @param url the remote repository to clone * @param local_path local directory to clone to * @param options configuration options for the clone. If NULL, the @@ -206,4 +221,5 @@ GIT_EXTERN(int) git_clone( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/commit.h b/include/git2/commit.h index 67170cb9..b998e188 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -14,9 +14,13 @@ /** * @file git2/commit.h - * @brief Git commit parsing, formatting routines + * @brief A representation of a set of changes in the repository * @defgroup git_commit Git commit parsing, formatting routines * @ingroup Git + * + * A commit represents a set of changes made to the files within a + * repository, and metadata about who made the changes, and when the + * changes were made. * @{ */ GIT_BEGIN_DECL @@ -380,7 +384,38 @@ GIT_EXTERN(int) git_commit_create( * * All other parameters remain the same as `git_commit_create()`. * - * @see git_commit_create + * @param id Pointer in which to store the OID of the newly created commit + * + * @param repo Repository where to store the commit + * + * @param update_ref If not NULL, name of the reference that + * will be updated to point to this commit. If the reference + * is not direct, it will be resolved to a direct reference. + * Use "HEAD" to update the HEAD of the current branch and + * make it point to this commit. If the reference doesn't + * exist yet, it will be created. If it does exist, the first + * parent must be the tip of this branch. + * + * @param author Signature with author and author time of commit + * + * @param committer Signature with committer and * commit time of commit + * + * @param message_encoding The encoding for the message in the + * commit, represented with a standard encoding name. + * E.g. "UTF-8". If NULL, no encoding header is written and + * UTF-8 is assumed. + * + * @param message Full message for this commit + * + * @param tree An instance of a `git_tree` object that will + * be used as the tree for the commit. This tree object must + * also be owned by the given `repo`. + * + * @param parent_count Number of parents for this commit + * + * @return 0 or an error code + * The created commit will be written to the Object Database and + * the given reference will be updated to point to it */ GIT_EXTERN(int) git_commit_create_v( git_oid *id, @@ -394,6 +429,52 @@ GIT_EXTERN(int) git_commit_create_v( size_t parent_count, ...); +typedef struct { + unsigned int version; + + /** + * Flags for creating the commit. + * + * If `allow_empty_commit` is specified, a commit with no changes + * from the prior commit (and "empty" commit) is allowed. Otherwise, + * commit creation will be stopped. + */ + unsigned int allow_empty_commit : 1; + + /** The commit author, or NULL for the default. */ + const git_signature *author; + + /** The committer, or NULL for the default. */ + const git_signature *committer; + + /** Encoding for the commit message; leave NULL for default. */ + const char *message_encoding; +} git_commit_create_options; + +/** Current version for the `git_commit_create_options` structure */ +#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1 + +/** Static constructor for `git_commit_create_options` */ +#define GIT_COMMIT_CREATE_OPTIONS_INIT { GIT_COMMIT_CREATE_OPTIONS_VERSION } + +/** + * Commits the staged changes in the repository; this is a near analog to + * `git commit -m message`. + * + * By default, empty commits are not allowed. + * + * @param id pointer to store the new commit's object id + * @param repo repository to commit changes in + * @param message the commit message + * @param opts options for creating the commit + * @return 0 on success, GIT_EUNCHANGED if there were no changes to commit, or an error code + */ +GIT_EXTERN(int) git_commit_create_from_stage( + git_oid *id, + git_repository *repo, + const char *message, + const git_commit_create_options *opts); + /** * Amend an existing commit by replacing only non-NULL values. * @@ -413,7 +494,36 @@ GIT_EXTERN(int) git_commit_create_v( * * All parameters have the same meanings as in `git_commit_create()`. * - * @see git_commit_create + * @param id Pointer in which to store the OID of the newly created commit + * + * @param commit_to_amend The commit to amend + * + * @param update_ref If not NULL, name of the reference that + * will be updated to point to this commit. If the reference + * is not direct, it will be resolved to a direct reference. + * Use "HEAD" to update the HEAD of the current branch and + * make it point to this commit. If the reference doesn't + * exist yet, it will be created. If it does exist, the first + * parent must be the tip of this branch. + * + * @param author Signature with author and author time of commit + * + * @param committer Signature with committer and * commit time of commit + * + * @param message_encoding The encoding for the message in the + * commit, represented with a standard encoding name. + * E.g. "UTF-8". If NULL, no encoding header is written and + * UTF-8 is assumed. + * + * @param message Full message for this commit + * + * @param tree An instance of a `git_tree` object that will + * be used as the tree for the commit. This tree object must + * also be owned by the given `repo`. + * + * @return 0 or an error code + * The created commit will be written to the Object Database and + * the given reference will be updated to point to it */ GIT_EXTERN(int) git_commit_amend( git_oid *id, @@ -541,6 +651,25 @@ typedef int (*git_commit_create_cb)( const git_commit *parents[], void *payload); +/** An array of commits returned from the library */ +typedef struct git_commitarray { + git_commit *const *commits; + size_t count; +} git_commitarray; + +/** + * Free the commits contained in a commit array. This method should + * be called on `git_commitarray` objects that were provided by the + * library. Not doing so will result in a memory leak. + * + * This does not free the `git_commitarray` itself, since the library + * will never allocate that object directly itself. + * + * @param array The git_commitarray that contains commits to free + */ +GIT_EXTERN(void) git_commitarray_dispose(git_commitarray *array); + /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/common.h b/include/git2/common.h index ab6bc133..40a3903c 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -11,7 +11,9 @@ #include #ifdef __cplusplus + /** Start declarations in C mode for C++ compatibility */ # define GIT_BEGIN_DECL extern "C" { + /** End declarations in C mode */ # define GIT_END_DECL } #else /** Start declarations in C mode */ @@ -71,19 +73,19 @@ typedef size_t size_t; # define GIT_FORMAT_PRINTF(a,b) /* empty */ #endif -#if (defined(_WIN32)) && !defined(__CYGWIN__) -#define GIT_WIN32 1 -#endif - #ifdef __amigaos4__ #include #endif /** * @file git2/common.h - * @brief Git common platform definitions + * @brief Base platform functionality * @defgroup git_common Git common platform definitions * @ingroup Git + * + * Common platform functionality including introspecting libgit2 + * itself - information like how it was built, and the current + * running version. * @{ */ @@ -94,10 +96,10 @@ GIT_BEGIN_DECL * environment variable). A semi-colon ";" is used on Windows and * AmigaOS, and a colon ":" for all other systems. */ -#if defined(GIT_WIN32) || defined(AMIGA) -#define GIT_PATH_LIST_SEPARATOR ';' +#if (defined(_WIN32) && !defined(__CYGWIN__)) || defined(AMIGA) +# define GIT_PATH_LIST_SEPARATOR ';' #else -#define GIT_PATH_LIST_SEPARATOR ':' +# define GIT_PATH_LIST_SEPARATOR ':' #endif /** @@ -128,56 +130,80 @@ GIT_EXTERN(int) git_libgit2_version(int *major, int *minor, int *rev); GIT_EXTERN(const char *) git_libgit2_prerelease(void); /** - * Combinations of these values describe the features with which libgit2 - * was compiled + * Configurable features of libgit2; either optional settings (like + * threading), or features that can be enabled by one of a number of + * different backend "providers" (like HTTPS, which can be provided by + * OpenSSL, mbedTLS, or system libraries). */ typedef enum { - /** - * If set, libgit2 was built thread-aware and can be safely used from multiple - * threads. - */ - GIT_FEATURE_THREADS = (1 << 0), - /** - * If set, libgit2 was built with and linked against a TLS implementation. - * Custom TLS streams may still be added by the user to support HTTPS - * regardless of this. - */ - GIT_FEATURE_HTTPS = (1 << 1), - /** - * If set, libgit2 was built with and linked against libssh2. A custom - * transport may still be added by the user to support libssh2 regardless of - * this. - */ - GIT_FEATURE_SSH = (1 << 2), - /** - * If set, libgit2 was built with support for sub-second resolution in file - * modification times. - */ - GIT_FEATURE_NSEC = (1 << 3) + /** + * libgit2 is thread-aware and can be used from multiple threads + * (as described in the documentation). + */ + GIT_FEATURE_THREADS = (1 << 0), + + /** HTTPS remotes */ + GIT_FEATURE_HTTPS = (1 << 1), + + /** SSH remotes */ + GIT_FEATURE_SSH = (1 << 2), + + /** Sub-second resolution in index timestamps */ + GIT_FEATURE_NSEC = (1 << 3), + + /** HTTP parsing; always available */ + GIT_FEATURE_HTTP_PARSER = (1 << 4), + + /** Regular expression support; always available */ + GIT_FEATURE_REGEX = (1 << 5), + + /** Internationalization support for filename translation */ + GIT_FEATURE_I18N = (1 << 6), + + /** NTLM support over HTTPS */ + GIT_FEATURE_AUTH_NTLM = (1 << 7), + + /** Kerberos (SPNEGO) authentication support over HTTPS */ + GIT_FEATURE_AUTH_NEGOTIATE = (1 << 8), + + /** zlib support; always available */ + GIT_FEATURE_COMPRESSION = (1 << 9), + + /** SHA1 object support; always available */ + GIT_FEATURE_SHA1 = (1 << 10), + + /** SHA256 object support */ + GIT_FEATURE_SHA256 = (1 << 11) } git_feature_t; /** * Query compile time options for libgit2. * * @return A combination of GIT_FEATURE_* values. + */ +GIT_EXTERN(int) git_libgit2_features(void); + +/** + * Query the backend details for the compile-time feature in libgit2. * - * - GIT_FEATURE_THREADS - * Libgit2 was compiled with thread support. Note that thread support is - * still to be seen as a 'work in progress' - basic object lookups are - * believed to be threadsafe, but other operations may not be. + * This will return the "backend" for the feature, which is useful for + * things like HTTPS or SSH support, that can have multiple backends + * that could be compiled in. * - * - GIT_FEATURE_HTTPS - * Libgit2 supports the https:// protocol. This requires the openssl - * library to be found when compiling libgit2. + * For example, when libgit2 is compiled with dynamic OpenSSL support, + * the feature backend will be `openssl-dynamic`. The feature backend + * names reflect the compilation options specified to the build system + * (though in all lower case). The backend _may_ be "builtin" for + * features that are provided by libgit2 itself. * - * - GIT_FEATURE_SSH - * Libgit2 supports the SSH protocol for network operations. This requires - * the libssh2 library to be found when compiling libgit2 + * If the feature is not supported by the library, this API returns + * `NULL`. * - * - GIT_FEATURE_NSEC - * Libgit2 supports the sub-second resolution in file modification times. + * @param feature the feature to query details for + * @return the provider details, or NULL if the feature is not supported */ -GIT_EXTERN(int) git_libgit2_features(void); +GIT_EXTERN(const char *) git_libgit2_feature_backend( + git_feature_t feature); /** * Global library options @@ -228,7 +254,10 @@ typedef enum { GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, GIT_OPT_GET_SERVER_CONNECT_TIMEOUT, GIT_OPT_SET_SERVER_TIMEOUT, - GIT_OPT_GET_SERVER_TIMEOUT + GIT_OPT_GET_SERVER_TIMEOUT, + GIT_OPT_SET_USER_AGENT_PRODUCT, + GIT_OPT_GET_USER_AGENT_PRODUCT, + GIT_OPT_ADD_SSL_X509_CERT } git_libgit2_opt_t; /** @@ -333,15 +362,52 @@ typedef enum { * > - `path` is the location of a directory holding several * > certificates, one per file. * > + * > Calling `GIT_OPT_ADD_SSL_X509_CERT` may override the + * > data in `path`. + * > * > Either parameter may be `NULL`, but not both. * + * * opts(GIT_OPT_ADD_SSL_X509_CERT, const X509 *cert) + * + * > Add a raw X509 certificate into the SSL certs store. + * > This certificate is only used by libgit2 invocations + * > during the application lifetime and is not persisted + * > to disk. This certificate cannot be removed from the + * > application once is has been added. + * > + * > - `cert` is the raw X509 cert will be added to cert store. + * * * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent) * - * > Set the value of the User-Agent header. This value will be - * > appended to "git/1.0", for compatibility with other git clients. + * > Set the value of the comment section of the User-Agent header. + * > This can be information about your product and its version. + * > By default this is "libgit2" followed by the libgit2 version. + * > + * > This value will be appended to User-Agent _product_, which + * > is typically set to "git/2.0". + * > + * > Set to the empty string ("") to not send any information in the + * > comment section, or set to NULL to restore the default. + * + * * opts(GIT_OPT_GET_USER_AGENT, git_buf *out) + * + * > Get the value of the User-Agent header. + * > The User-Agent is written to the `out` buffer. + * + * * opts(GIT_OPT_SET_USER_AGENT_PRODUCT, const char *user_agent_product) + * + * > Set the value of the product portion of the User-Agent header. + * > This defaults to "git/2.0", for compatibility with other git + * > clients. It is recommended to keep this as git/ for + * > compatibility with servers that do user-agent detection. * > - * > - `user_agent` is the value that will be delivered as the - * > User-Agent header on HTTP requests. + * > Set to the empty string ("") to not send any user-agent string, + * > or set to NULL to restore the default. + * + * * opts(GIT_OPT_GET_USER_AGENT_PRODUCT, git_buf *out) + * + * > Get the value of the User-Agent product header. + * > The User-Agent product is written to the `out` buffer. * * * opts(GIT_OPT_SET_WINDOWS_SHAREMODE, unsigned long value) * @@ -377,11 +443,6 @@ typedef enum { * > * > - `ciphers` is the list of ciphers that are eanbled. * - * * opts(GIT_OPT_GET_USER_AGENT, git_buf *out) - * - * > Get the value of the User-Agent header. - * > The User-Agent is written to the `out` buffer. - * * * opts(GIT_OPT_ENABLE_OFS_DELTA, int enabled) * * > Enable or disable the use of "offset deltas" when creating packfiles, @@ -490,10 +551,9 @@ typedef enum { * * opts(GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, int timeout) * > Sets the timeout (in milliseconds) to attempt connections to - * > a remote server. This is supported only for HTTP(S) connections - * > and is not supported by SSH. Set to 0 to use the system default. - * > Note that this may not be able to be configured longer than the - * > system default, typically 75 seconds. + * > a remote server. Set to 0 to use the system default. Note that + * > this may not be able to be configured longer than the system + * > default, typically 75 seconds. * * opts(GIT_OPT_GET_SERVER_TIMEOUT, int *timeout) * > Gets the timeout (in milliseconds) for reading from and writing @@ -501,12 +561,9 @@ typedef enum { * * opts(GIT_OPT_SET_SERVER_TIMEOUT, int timeout) * > Sets the timeout (in milliseconds) for reading from and writing - * > to a remote server. This is supported only for HTTP(S) - * > connections and is not supported by SSH. Set to 0 to use the - * > system default. + * > to a remote server. Set to 0 to use the system default. * * @param option Option key - * @param ... value to set the option * @return 0 on success, <0 on failure */ GIT_EXTERN(int) git_libgit2_opts(int option, ...); diff --git a/include/git2/config.h b/include/git2/config.h index cfab0c75..f9c26675 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -13,47 +13,86 @@ /** * @file git2/config.h - * @brief Git config management routines + * @brief Per-repository, per-user or per-system configuration * @defgroup git_config Git config management routines * @ingroup Git + * + * Git configuration affects the operation of the version control + * system, and can be specified on a per-repository basis, in user + * settings, or at the system level. * @{ */ GIT_BEGIN_DECL /** * Priority level of a config file. + * * These priority levels correspond to the natural escalation logic - * (from higher to lower) when searching for config entries in git.git. + * (from higher to lower) when reading or searching for config entries + * in git.git. Meaning that for the same key, the configuration in + * the local configuration is preferred over the configuration in + * the system configuration file. + * + * Callers can add their own custom configuration, beginning at the + * `GIT_CONFIG_LEVEL_APP` level. + * + * Writes, by default, occur in the highest priority level backend + * that is writable. This ordering can be overridden with + * `git_config_set_writeorder`. * * git_config_open_default() and git_repository_config() honor those * priority levels as well. + * + * @see git_config_open_default + * @see git_repository_config */ typedef enum { - /** System-wide on Windows, for compatibility with portable git */ + /** + * System-wide on Windows, for compatibility with "Portable Git". + */ GIT_CONFIG_LEVEL_PROGRAMDATA = 1, - /** System-wide configuration file; /etc/gitconfig on Linux systems */ + /** + * System-wide configuration file; `/etc/gitconfig` on Linux. + */ GIT_CONFIG_LEVEL_SYSTEM = 2, - /** XDG compatible configuration file; typically ~/.config/git/config */ + /** + * XDG compatible configuration file; typically + * `~/.config/git/config`. + */ GIT_CONFIG_LEVEL_XDG = 3, - /** User-specific configuration file (also called Global configuration - * file); typically ~/.gitconfig + /** + * Global configuration file is the user-specific configuration; + * typically `~/.gitconfig`. */ GIT_CONFIG_LEVEL_GLOBAL = 4, - /** Repository specific configuration file; $WORK_DIR/.git/config on - * non-bare repos + /** + * Local configuration, the repository-specific configuration file; + * typically `$GIT_DIR/config`. */ GIT_CONFIG_LEVEL_LOCAL = 5, - /** Application specific configuration file; freely defined by applications + /** + * Worktree-specific configuration; typically + * `$GIT_DIR/config.worktree`. */ - GIT_CONFIG_LEVEL_APP = 6, + GIT_CONFIG_LEVEL_WORKTREE = 6, - /** Represents the highest level available config file (i.e. the most - * specific config file available that actually is loaded) + /** + * Application-specific configuration file. Callers into libgit2 + * can add their own configuration beginning at this level. + */ + GIT_CONFIG_LEVEL_APP = 7, + + /** + * Not a configuration level; callers can use this value when + * querying configuration levels to specify that they want to + * have data from the highest-level currently configuration. + * This can be used to indicate that callers want the most + * specific config file available that actually is loaded. */ GIT_CONFIG_HIGHEST_LEVEL = -1 } git_config_level_t; @@ -62,23 +101,37 @@ typedef enum { * An entry in a configuration file */ typedef struct git_config_entry { - const char *name; /**< Name of the entry (normalised) */ - const char *value; /**< String value of the entry */ - unsigned int include_depth; /**< Depth of includes where this variable was found */ - git_config_level_t level; /**< Which config file this was found in */ - void GIT_CALLBACK(free)(struct git_config_entry *entry); /**< Free function for this entry */ - void *payload; /**< Opaque value for the free function. Do not read or write */ + /** Name of the configuration entry (normalized). */ + const char *name; + + /** Literal (string) value of the entry. */ + const char *value; + + /** The type of backend that this entry exists in (eg, "file"). */ + const char *backend_type; + + /** + * The path to the origin of this entry. For config files, this is + * the path to the file. + */ + const char *origin_path; + + /** Depth of includes where this variable was found. */ + unsigned int include_depth; + + /** Configuration level for the file this was found in. */ + git_config_level_t level; } git_config_entry; /** - * Free a config entry + * Free a config entry. * * @param entry The entry to free. */ GIT_EXTERN(void) git_config_entry_free(git_config_entry *entry); /** - * A config enumeration callback + * A config enumeration callback. * * @param entry the entry currently being enumerated * @param payload a user-specified pointer @@ -87,7 +140,7 @@ GIT_EXTERN(void) git_config_entry_free(git_config_entry *entry); typedef int GIT_CALLBACK(git_config_foreach_cb)(const git_config_entry *entry, void *payload); /** - * An opaque structure for a configuration iterator + * An opaque structure for a configuration iterator. */ typedef struct git_config_iterator git_config_iterator; @@ -212,9 +265,9 @@ GIT_EXTERN(int) git_config_new(git_config **out); * @param cfg the configuration to add the file to * @param path path to the configuration file to add * @param level the priority level of the backend - * @param force replace config file at the given priority level * @param repo optional repository to allow parsing of * conditional includes + * @param force replace config file at the given priority level * @return 0 on success, GIT_EEXISTS when adding more than one file * for a given priority level (and force_replace set to 0), * GIT_ENOTFOUND when the file doesn't exist or error code @@ -276,6 +329,22 @@ GIT_EXTERN(int) git_config_open_level( */ GIT_EXTERN(int) git_config_open_global(git_config **out, git_config *config); +/** + * Set the write order for configuration backends. By default, the + * write ordering does not match the read ordering; for example, the + * worktree configuration is a high-priority for reading, but is not + * written to unless explicitly chosen. + * + * @param cfg the configuration to change write order of + * @param levels the ordering of levels for writing + * @param len the length of the levels array + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_set_writeorder( + git_config *cfg, + git_config_level_t *levels, + size_t len); + /** * Create a snapshot of the configuration * @@ -779,4 +848,5 @@ GIT_EXTERN(int) git_config_lock(git_transaction **tx, git_config *cfg); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/credential.h b/include/git2/credential.h index 7a04bc06..33755ca9 100644 --- a/include/git2/credential.h +++ b/include/git2/credential.h @@ -11,9 +11,12 @@ /** * @file git2/credential.h - * @brief Git authentication & credential management + * @brief Authentication and credential management * @defgroup git_credential Authentication & credential management * @ingroup Git + * + * Credentials specify how to authenticate to a remote system + * over HTTPS or SSH. * @{ */ GIT_BEGIN_DECL @@ -119,7 +122,7 @@ typedef struct git_credential_ssh_custom git_credential_ssh_custom; * an error. As such, it's easy to get in a loop if you fail to stop providing * the same incorrect credentials. * - * @param out The newly created credential object. + * @param[out] out The newly created credential object. * @param url The resource for which we are demanding a credential. * @param username_from_url The username that was embedded in a "user\@host" * remote url, or NULL if not included. @@ -241,6 +244,18 @@ typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT LIBSSH2_USERAUTH_KBDINT_PROMPT; typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE LIBSSH2_USERAUTH_KBDINT_RESPONSE; #endif +/** + * Callback for interactive SSH credentials. + * + * @param name the name + * @param name_len the length of the name + * @param instruction the authentication instruction + * @param instruction_len the length of the instruction + * @param num_prompts the number of prompts + * @param prompts the prompts + * @param responses the responses + * @param abstract the abstract + */ typedef void GIT_CALLBACK(git_credential_ssh_interactive_cb)( const char *name, int name_len, @@ -278,6 +293,18 @@ GIT_EXTERN(int) git_credential_ssh_key_from_agent( git_credential **out, const char *username); +/** + * Callback for credential signing. + * + * @param session the libssh2 session + * @param sig the signature + * @param sig_len the length of the signature + * @param data the data + * @param data_len the length of the data + * @param abstract the abstract + * @return 0 for success, < 0 to indicate an error, > 0 to indicate + * no credential was acquired + */ typedef int GIT_CALLBACK(git_credential_sign_cb)( LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, @@ -312,4 +339,5 @@ GIT_EXTERN(int) git_credential_ssh_custom_new( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/credential_helpers.h b/include/git2/credential_helpers.h index f0fb0704..706558d5 100644 --- a/include/git2/credential_helpers.h +++ b/include/git2/credential_helpers.h @@ -50,4 +50,5 @@ GIT_EXTERN(int) git_credential_userpass( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/deprecated.h b/include/git2/deprecated.h index 52864ebe..b8b0238d 100644 --- a/include/git2/deprecated.h +++ b/include/git2/deprecated.h @@ -52,7 +52,7 @@ /** * @file git2/deprecated.h - * @brief libgit2 deprecated functions and values + * @brief Deprecated functions and values * @ingroup Git * @{ */ @@ -69,15 +69,23 @@ GIT_BEGIN_DECL */ /**@{*/ +/** @deprecated use GIT_ATTR_VALUE_UNSPECIFIED */ #define GIT_ATTR_UNSPECIFIED_T GIT_ATTR_VALUE_UNSPECIFIED +/** @deprecated use GIT_ATTR_VALUE_TRUE */ #define GIT_ATTR_TRUE_T GIT_ATTR_VALUE_TRUE +/** @deprecated use GIT_ATTR_VALUE_FALSE */ #define GIT_ATTR_FALSE_T GIT_ATTR_VALUE_FALSE +/** @deprecated use GIT_ATTR_VALUE_STRING */ #define GIT_ATTR_VALUE_T GIT_ATTR_VALUE_STRING +/** @deprecated use GIT_ATTR_IS_TRUE */ #define GIT_ATTR_TRUE(attr) GIT_ATTR_IS_TRUE(attr) +/** @deprecated use GIT_ATTR_IS_FALSE */ #define GIT_ATTR_FALSE(attr) GIT_ATTR_IS_FALSE(attr) +/** @deprecated use GIT_ATTR_IS_UNSPECIFIED */ #define GIT_ATTR_UNSPECIFIED(attr) GIT_ATTR_IS_UNSPECIFIED(attr) +/** @deprecated use git_attr_value_t */ typedef git_attr_value_t git_attr_t; /**@}*/ @@ -93,6 +101,7 @@ typedef git_attr_value_t git_attr_t; */ /**@{*/ +/** @deprecated use GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD */ #define GIT_BLOB_FILTER_ATTTRIBUTES_FROM_HEAD GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path); @@ -285,11 +294,16 @@ typedef int (*git_commit_signing_cb)( */ /**@{*/ +/** @deprecated use GIT_CONFIGMAP_FALSE */ #define GIT_CVAR_FALSE GIT_CONFIGMAP_FALSE +/** @deprecated use GIT_CONFIGMAP_TRUE */ #define GIT_CVAR_TRUE GIT_CONFIGMAP_TRUE +/** @deprecated use GIT_CONFIGMAP_INT32 */ #define GIT_CVAR_INT32 GIT_CONFIGMAP_INT32 +/** @deprecated use GIT_CONFIGMAP_STRING */ #define GIT_CVAR_STRING GIT_CONFIGMAP_STRING +/** @deprecated use git_cvar_map */ typedef git_configmap git_cvar_map; /**@}*/ @@ -314,11 +328,12 @@ typedef enum { /** Don't insert "[PATCH]" in the subject header*/ GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER = (1 << 0) - } git_diff_format_email_flags_t; /** * Options for controlling the formatting of the generated e-mail. + * + * @deprecated use `git_email_create_options` */ typedef struct { unsigned int version; @@ -345,7 +360,9 @@ typedef struct { const git_signature *author; } git_diff_format_email_options; +/** @deprecated use `git_email_create_options` */ #define GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION 1 +/** @deprecated use `git_email_create_options` */ #define GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT {GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, 0, 1, 1, NULL, NULL, NULL, NULL} /** @@ -401,41 +418,75 @@ GIT_EXTERN(int) git_diff_format_email_options_init( */ /**@{*/ +/** @deprecated use `GIT_ERROR_NONE` */ #define GITERR_NONE GIT_ERROR_NONE +/** @deprecated use `GIT_ERROR_NOMEMORY` */ #define GITERR_NOMEMORY GIT_ERROR_NOMEMORY +/** @deprecated use `GIT_ERROR_OS` */ #define GITERR_OS GIT_ERROR_OS +/** @deprecated use `GIT_ERROR_INVALID` */ #define GITERR_INVALID GIT_ERROR_INVALID +/** @deprecated use `GIT_ERROR_REFERENCE` */ #define GITERR_REFERENCE GIT_ERROR_REFERENCE +/** @deprecated use `GIT_ERROR_ZLIB` */ #define GITERR_ZLIB GIT_ERROR_ZLIB +/** @deprecated use `GIT_ERROR_REPOSITORY` */ #define GITERR_REPOSITORY GIT_ERROR_REPOSITORY +/** @deprecated use `GIT_ERROR_CONFIG` */ #define GITERR_CONFIG GIT_ERROR_CONFIG +/** @deprecated use `GIT_ERROR_REGEX` */ #define GITERR_REGEX GIT_ERROR_REGEX +/** @deprecated use `GIT_ERROR_ODB` */ #define GITERR_ODB GIT_ERROR_ODB +/** @deprecated use `GIT_ERROR_INDEX` */ #define GITERR_INDEX GIT_ERROR_INDEX +/** @deprecated use `GIT_ERROR_OBJECT` */ #define GITERR_OBJECT GIT_ERROR_OBJECT +/** @deprecated use `GIT_ERROR_NET` */ #define GITERR_NET GIT_ERROR_NET +/** @deprecated use `GIT_ERROR_TAG` */ #define GITERR_TAG GIT_ERROR_TAG +/** @deprecated use `GIT_ERROR_TREE` */ #define GITERR_TREE GIT_ERROR_TREE +/** @deprecated use `GIT_ERROR_INDEXER` */ #define GITERR_INDEXER GIT_ERROR_INDEXER +/** @deprecated use `GIT_ERROR_SSL` */ #define GITERR_SSL GIT_ERROR_SSL +/** @deprecated use `GIT_ERROR_SUBMODULE` */ #define GITERR_SUBMODULE GIT_ERROR_SUBMODULE +/** @deprecated use `GIT_ERROR_THREAD` */ #define GITERR_THREAD GIT_ERROR_THREAD +/** @deprecated use `GIT_ERROR_STASH` */ #define GITERR_STASH GIT_ERROR_STASH +/** @deprecated use `GIT_ERROR_CHECKOUT` */ #define GITERR_CHECKOUT GIT_ERROR_CHECKOUT +/** @deprecated use `GIT_ERROR_FETCHHEAD` */ #define GITERR_FETCHHEAD GIT_ERROR_FETCHHEAD +/** @deprecated use `GIT_ERROR_MERGE` */ #define GITERR_MERGE GIT_ERROR_MERGE +/** @deprecated use `GIT_ERROR_SSH` */ #define GITERR_SSH GIT_ERROR_SSH +/** @deprecated use `GIT_ERROR_FILTER` */ #define GITERR_FILTER GIT_ERROR_FILTER +/** @deprecated use `GIT_ERROR_REVERT` */ #define GITERR_REVERT GIT_ERROR_REVERT +/** @deprecated use `GIT_ERROR_CALLBACK` */ #define GITERR_CALLBACK GIT_ERROR_CALLBACK +/** @deprecated use `GIT_ERROR_CHERRYPICK` */ #define GITERR_CHERRYPICK GIT_ERROR_CHERRYPICK +/** @deprecated use `GIT_ERROR_DESCRIBE` */ #define GITERR_DESCRIBE GIT_ERROR_DESCRIBE +/** @deprecated use `GIT_ERROR_REBASE` */ #define GITERR_REBASE GIT_ERROR_REBASE +/** @deprecated use `GIT_ERROR_FILESYSTEM` */ #define GITERR_FILESYSTEM GIT_ERROR_FILESYSTEM +/** @deprecated use `GIT_ERROR_PATCH` */ #define GITERR_PATCH GIT_ERROR_PATCH +/** @deprecated use `GIT_ERROR_WORKTREE` */ #define GITERR_WORKTREE GIT_ERROR_WORKTREE +/** @deprecated use `GIT_ERROR_SHA1` */ #define GITERR_SHA1 GIT_ERROR_SHA1 - +/** @deprecated use `GIT_ERROR_SHA` */ #define GIT_ERROR_SHA1 GIT_ERROR_SHA /** @@ -500,37 +551,63 @@ GIT_EXTERN(void) giterr_set_oom(void); */ /**@{*/ +/* The git_idxentry_extended_flag_t enum */ +/** @deprecated use `GIT_INDEX_ENTRY_NAMEMASK` */ #define GIT_IDXENTRY_NAMEMASK GIT_INDEX_ENTRY_NAMEMASK +/** @deprecated use `GIT_INDEX_ENTRY_STAGEMASK` */ #define GIT_IDXENTRY_STAGEMASK GIT_INDEX_ENTRY_STAGEMASK +/** @deprecated use `GIT_INDEX_ENTRY_STAGESHIFT` */ #define GIT_IDXENTRY_STAGESHIFT GIT_INDEX_ENTRY_STAGESHIFT /* The git_indxentry_flag_t enum */ +/** @deprecated use `GIT_INDEX_ENTRY_EXTENDED` */ #define GIT_IDXENTRY_EXTENDED GIT_INDEX_ENTRY_EXTENDED +/** @deprecated use `GIT_INDEX_ENTRY_VALID` */ #define GIT_IDXENTRY_VALID GIT_INDEX_ENTRY_VALID +/** @deprecated use `GIT_INDEX_ENTRY_STAGE` */ #define GIT_IDXENTRY_STAGE(E) GIT_INDEX_ENTRY_STAGE(E) +/** @deprecated use `GIT_INDEX_ENTRY_STAGE_SET` */ #define GIT_IDXENTRY_STAGE_SET(E,S) GIT_INDEX_ENTRY_STAGE_SET(E,S) /* The git_idxentry_extended_flag_t enum */ +/** @deprecated use `GIT_INDEX_ENTRY_INTENT_TO_ADD` */ #define GIT_IDXENTRY_INTENT_TO_ADD GIT_INDEX_ENTRY_INTENT_TO_ADD +/** @deprecated use `GIT_INDEX_ENTRY_SKIP_WORKTREE` */ #define GIT_IDXENTRY_SKIP_WORKTREE GIT_INDEX_ENTRY_SKIP_WORKTREE +/** @deprecated use `GIT_INDEX_ENTRY_INTENT_TO_ADD | GIT_INDEX_ENTRY_SKIP_WORKTREE` */ #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_INDEX_ENTRY_INTENT_TO_ADD | GIT_INDEX_ENTRY_SKIP_WORKTREE) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_EXTENDED2 (1 << 15) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_UPDATE (1 << 0) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_REMOVE (1 << 1) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_UPTODATE (1 << 2) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_ADDED (1 << 3) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_HASHED (1 << 4) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_UNHASHED (1 << 5) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_WT_REMOVE (1 << 6) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_CONFLICTED (1 << 7) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_UNPACKED (1 << 8) +/** @deprecated this value is not public */ #define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) /* The git_index_capability_t enum */ +/** @deprecated use `GIT_INDEX_CAPABILITY_IGNORE_CASE` */ #define GIT_INDEXCAP_IGNORE_CASE GIT_INDEX_CAPABILITY_IGNORE_CASE +/** @deprecated use `GIT_INDEX_CAPABILITY_NO_FILEMODE` */ #define GIT_INDEXCAP_NO_FILEMODE GIT_INDEX_CAPABILITY_NO_FILEMODE +/** @deprecated use `GIT_INDEX_CAPABILITY_NO_SYMLINKS` */ #define GIT_INDEXCAP_NO_SYMLINKS GIT_INDEX_CAPABILITY_NO_SYMLINKS +/** @deprecated use `GIT_INDEX_CAPABILITY_FROM_OWNER` */ #define GIT_INDEXCAP_FROM_OWNER GIT_INDEX_CAPABILITY_FROM_OWNER GIT_EXTERN(int) git_index_add_frombuffer( @@ -550,17 +627,28 @@ GIT_EXTERN(int) git_index_add_frombuffer( */ /**@{*/ +/** @deprecate use `git_object_t` */ #define git_otype git_object_t +/** @deprecate use `GIT_OBJECT_ANY` */ #define GIT_OBJ_ANY GIT_OBJECT_ANY +/** @deprecate use `GIT_OBJECT_INVALID` */ #define GIT_OBJ_BAD GIT_OBJECT_INVALID +/** @deprecated this value is not public */ #define GIT_OBJ__EXT1 0 +/** @deprecate use `GIT_OBJECT_COMMIT` */ #define GIT_OBJ_COMMIT GIT_OBJECT_COMMIT +/** @deprecate use `GIT_OBJECT_TREE` */ #define GIT_OBJ_TREE GIT_OBJECT_TREE +/** @deprecate use `GIT_OBJECT_BLOB` */ #define GIT_OBJ_BLOB GIT_OBJECT_BLOB +/** @deprecate use `GIT_OBJECT_TAG` */ #define GIT_OBJ_TAG GIT_OBJECT_TAG +/** @deprecated this value is not public */ #define GIT_OBJ__EXT2 5 +/** @deprecate use `GIT_OBJECT_OFS_DELTA` */ #define GIT_OBJ_OFS_DELTA GIT_OBJECT_OFS_DELTA +/** @deprecate use `GIT_OBJECT_REF_DELTA` */ #define GIT_OBJ_REF_DELTA GIT_OBJECT_REF_DELTA /** @@ -612,17 +700,27 @@ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name); /**@{*/ /** Basic type of any Git reference. */ +/** @deprecate use `git_reference_t` */ #define git_ref_t git_reference_t +/** @deprecate use `git_reference_format_t` */ #define git_reference_normalize_t git_reference_format_t +/** @deprecate use `GIT_REFERENCE_INVALID` */ #define GIT_REF_INVALID GIT_REFERENCE_INVALID +/** @deprecate use `GIT_REFERENCE_DIRECT` */ #define GIT_REF_OID GIT_REFERENCE_DIRECT +/** @deprecate use `GIT_REFERENCE_SYMBOLIC` */ #define GIT_REF_SYMBOLIC GIT_REFERENCE_SYMBOLIC +/** @deprecate use `GIT_REFERENCE_ALL` */ #define GIT_REF_LISTALL GIT_REFERENCE_ALL +/** @deprecate use `GIT_REFERENCE_FORMAT_NORMAL` */ #define GIT_REF_FORMAT_NORMAL GIT_REFERENCE_FORMAT_NORMAL +/** @deprecate use `GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL` */ #define GIT_REF_FORMAT_ALLOW_ONELEVEL GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL +/** @deprecate use `GIT_REFERENCE_FORMAT_REFSPEC_PATTERN` */ #define GIT_REF_FORMAT_REFSPEC_PATTERN GIT_REFERENCE_FORMAT_REFSPEC_PATTERN +/** @deprecate use `GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND` */ #define GIT_REF_FORMAT_REFSPEC_SHORTHAND GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND /** @@ -663,8 +761,11 @@ GIT_EXTERN(int) git_tag_create_frombuffer( typedef git_revspec_t git_revparse_mode_t; +/** @deprecated use `GIT_REVSPEC_SINGLE` */ #define GIT_REVPARSE_SINGLE GIT_REVSPEC_SINGLE +/** @deprecated use `GIT_REVSPEC_RANGE` */ #define GIT_REVPARSE_RANGE GIT_REVSPEC_RANGE +/** @deprecated use `GIT_REVSPEC_MERGE_BASE` */ #define GIT_REVPARSE_MERGE_BASE GIT_REVSPEC_MERGE_BASE /**@}*/ @@ -693,14 +794,22 @@ typedef git_credential_sign_cb git_cred_sign_cb; typedef git_credential_ssh_interactive_cb git_cred_ssh_interactive_callback; typedef git_credential_ssh_interactive_cb git_cred_ssh_interactive_cb; +/** @deprecated use `git_credential_t` */ #define git_credtype_t git_credential_t +/** @deprecated use `GIT_CREDENTIAL_USERPASS_PLAINTEXT` */ #define GIT_CREDTYPE_USERPASS_PLAINTEXT GIT_CREDENTIAL_USERPASS_PLAINTEXT +/** @deprecated use `GIT_CREDENTIAL_SSH_KEY` */ #define GIT_CREDTYPE_SSH_KEY GIT_CREDENTIAL_SSH_KEY +/** @deprecated use `GIT_CREDENTIAL_SSH_CUSTOM` */ #define GIT_CREDTYPE_SSH_CUSTOM GIT_CREDENTIAL_SSH_CUSTOM +/** @deprecated use `GIT_CREDENTIAL_DEFAULT` */ #define GIT_CREDTYPE_DEFAULT GIT_CREDENTIAL_DEFAULT +/** @deprecated use `GIT_CREDENTIAL_SSH_INTERACTIVE` */ #define GIT_CREDTYPE_SSH_INTERACTIVE GIT_CREDENTIAL_SSH_INTERACTIVE +/** @deprecated use `GIT_CREDENTIAL_USERNAME` */ #define GIT_CREDTYPE_USERNAME GIT_CREDENTIAL_USERNAME +/** @deprecated use `GIT_CREDENTIAL_SSH_MEMORY` */ #define GIT_CREDTYPE_SSH_MEMORY GIT_CREDENTIAL_SSH_MEMORY GIT_EXTERN(void) git_cred_free(git_credential *cred); @@ -778,8 +887,11 @@ typedef git_trace_cb git_trace_callback; /**@{*/ #ifndef GIT_EXPERIMENTAL_SHA256 +/** Deprecated OID "raw size" definition */ # define GIT_OID_RAWSZ GIT_OID_SHA1_SIZE +/** Deprecated OID "hex size" definition */ # define GIT_OID_HEXSZ GIT_OID_SHA1_HEXSIZE +/** Deprecated OID "hex zero" definition */ # define GIT_OID_HEX_ZERO GIT_OID_SHA1_HEXZERO #endif @@ -892,6 +1004,24 @@ GIT_EXTERN(void) git_strarray_free(git_strarray *array); /**@}*/ +/** @name Deprecated Version Constants + * + * These constants are retained for backward compatibility. The newer + * versions of these constants should be preferred in all new code. + * + * There is no plan to remove these backward compatibility constants at + * this time. + */ +/**@{*/ + +#define LIBGIT2_VER_MAJOR LIBGIT2_VERSION_MAJOR +#define LIBGIT2_VER_MINOR LIBGIT2_VERSION_MINOR +#define LIBGIT2_VER_REVISION LIBGIT2_VERSION_REVISION +#define LIBGIT2_VER_PATCH LIBGIT2_VERSION_PATCH +#define LIBGIT2_VER_PRERELEASE LIBGIT2_VERSION_PRERELEASE + +/**@}*/ + /** @name Deprecated Options Initialization Functions * * These functions are retained for backward compatibility. The newer diff --git a/include/git2/describe.h b/include/git2/describe.h index 7a796f13..938c470d 100644 --- a/include/git2/describe.h +++ b/include/git2/describe.h @@ -13,10 +13,14 @@ /** * @file git2/describe.h - * @brief Git describing routines + * @brief Describe a commit in reference to tags * @defgroup git_describe Git describing routines * @ingroup Git * @{ + * + * Describe a commit, showing information about how the current commit + * relates to the tags. This can be useful for showing how the current + * commit has changed from a particular tagged version of the repository. */ GIT_BEGIN_DECL @@ -60,10 +64,15 @@ typedef struct git_describe_options { int show_commit_oid_as_fallback; } git_describe_options; +/** Default maximum candidate tags */ #define GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS 10 +/** Default abbreviated size */ #define GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE 7 +/** Current version for the `git_describe_options` structure */ #define GIT_DESCRIBE_OPTIONS_VERSION 1 + +/** Static constructor for `git_describe_options` */ #define GIT_DESCRIBE_OPTIONS_INIT { \ GIT_DESCRIBE_OPTIONS_VERSION, \ GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS, \ @@ -110,7 +119,10 @@ typedef struct { const char *dirty_suffix; } git_describe_format_options; +/** Current version for the `git_describe_format_options` structure */ #define GIT_DESCRIBE_FORMAT_OPTIONS_VERSION 1 + +/** Static constructor for `git_describe_format_options` */ #define GIT_DESCRIBE_FORMAT_OPTIONS_INIT { \ GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, \ GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE, \ diff --git a/include/git2/diff.h b/include/git2/diff.h index 384b6e74..b12e8ab2 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -15,7 +15,7 @@ /** * @file git2/diff.h - * @brief Git tree and file differencing routines. + * @brief Indicate the differences between two versions of the repository * @ingroup Git * @{ */ @@ -342,6 +342,12 @@ typedef struct { * diff process continues. * - returns 0, the delta is inserted into the diff, and the diff process * continues. + * + * @param diff_so_far the diff structure as it currently exists + * @param delta_to_add the delta that is to be added + * @param matched_pathspec the pathspec + * @param payload the user-specified callback payload + * @return 0 on success, 1 to skip this delta, or an error code */ typedef int GIT_CALLBACK(git_diff_notify_cb)( const git_diff *diff_so_far, @@ -357,7 +363,8 @@ typedef int GIT_CALLBACK(git_diff_notify_cb)( * @param diff_so_far The diff being generated. * @param old_path The path to the old file or NULL. * @param new_path The path to the new file or NULL. - * @return Non-zero to abort the diff. + * @param payload the user-specified callback payload + * @return 0 or an error code */ typedef int GIT_CALLBACK(git_diff_progress_cb)( const git_diff *diff_so_far, @@ -463,10 +470,10 @@ typedef struct { const char *new_prefix; } git_diff_options; -/* The current version of the diff options structure */ +/** The current version of the diff options structure */ #define GIT_DIFF_OPTIONS_VERSION 1 -/* Stack initializer for diff options. Alternatively use +/** Stack initializer for diff options. Alternatively use * `git_diff_options_init` programmatic initialization. */ #define GIT_DIFF_OPTIONS_INIT \ @@ -492,12 +499,14 @@ GIT_EXTERN(int) git_diff_options_init( * @param delta A pointer to the delta data for the file * @param progress Goes from 0 to 1 over the diff * @param payload User-specified pointer from foreach function + * @return 0 or an error code */ typedef int GIT_CALLBACK(git_diff_file_cb)( const git_diff_delta *delta, float progress, void *payload); +/** Maximum size of the hunk header */ #define GIT_DIFF_HUNK_HEADER_SIZE 128 /** @@ -558,6 +567,11 @@ typedef struct { /** * When iterating over a diff, callback that will be made for * binary content within the diff. + * + * @param delta the delta + * @param binary the binary content + * @param payload the user-specified callback payload + * @return 0 or an error code */ typedef int GIT_CALLBACK(git_diff_binary_cb)( const git_diff_delta *delta, @@ -584,6 +598,11 @@ typedef struct { /** * When iterating over a diff, callback that will be made per hunk. + * + * @param delta the delta + * @param hunk the hunk + * @param payload the user-specified callback payload + * @return 0 or an error code */ typedef int GIT_CALLBACK(git_diff_hunk_cb)( const git_diff_delta *delta, @@ -645,6 +664,12 @@ typedef struct { * When printing a diff, callback that will be made to output each line * of text. This uses some extra GIT_DIFF_LINE_... constants for output * of lines of file and hunk headers. + * + * @param delta the delta that contains the line + * @param hunk the hunk that contains the line + * @param line the line in the diff + * @param payload the user-specified callback payload + * @return 0 or an error code */ typedef int GIT_CALLBACK(git_diff_line_cb)( const git_diff_delta *delta, /**< delta that contains this data */ @@ -802,7 +827,10 @@ typedef struct { git_diff_similarity_metric *metric; } git_diff_find_options; +/** Current version for the `git_diff_find_options` structure */ #define GIT_DIFF_FIND_OPTIONS_VERSION 1 + +/** Static constructor for `git_diff_find_options` */ #define GIT_DIFF_FIND_OPTIONS_INIT {GIT_DIFF_FIND_OPTIONS_VERSION} /** @@ -1296,10 +1324,10 @@ typedef struct { git_oid_t oid_type; } git_diff_parse_options; -/* The current version of the diff parse options structure */ +/** The current version of the diff parse options structure */ #define GIT_DIFF_PARSE_OPTIONS_VERSION 1 -/* Stack initializer for diff parse options. Alternatively use +/** Stack initializer for diff parse options. Alternatively use * `git_diff_parse_options_init` programmatic initialization. */ #define GIT_DIFF_PARSE_OPTIONS_INIT \ @@ -1432,7 +1460,10 @@ typedef struct git_diff_patchid_options { unsigned int version; } git_diff_patchid_options; +/** Current version for the `git_diff_patchid_options` structure */ #define GIT_DIFF_PATCHID_OPTIONS_VERSION 1 + +/** Static constructor for `git_diff_patchid_options` */ #define GIT_DIFF_PATCHID_OPTIONS_INIT { GIT_DIFF_PATCHID_OPTIONS_VERSION } /** @@ -1470,8 +1501,7 @@ GIT_EXTERN(int) git_diff_patchid_options_init( */ GIT_EXTERN(int) git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts); -GIT_END_DECL - /** @} */ +GIT_END_DECL #endif diff --git a/include/git2/email.h b/include/git2/email.h index 20393653..ad37e424 100644 --- a/include/git2/email.h +++ b/include/git2/email.h @@ -8,10 +8,11 @@ #define INCLUDE_git_email_h__ #include "common.h" +#include "diff.h" /** * @file git2/email.h - * @brief Git email formatting and application routines. + * @brief Produce email-ready patches * @ingroup Git * @{ */ @@ -70,11 +71,14 @@ typedef struct { size_t reroll_number; } git_email_create_options; -/* +/** Current version for the `git_email_create_options` structure */ +#define GIT_EMAIL_CREATE_OPTIONS_VERSION 1 + +/** Static constructor for `git_email_create_options` + * * By default, our options include rename detection and binary * diffs to match `git format-patch`. */ -#define GIT_EMAIL_CREATE_OPTIONS_VERSION 1 #define GIT_EMAIL_CREATE_OPTIONS_INIT \ { \ GIT_EMAIL_CREATE_OPTIONS_VERSION, \ @@ -83,30 +87,6 @@ typedef struct { GIT_DIFF_FIND_OPTIONS_INIT \ } -/** - * Create a diff for a commit in mbox format for sending via email. - * - * @param out buffer to store the e-mail patch in - * @param diff the changes to include in the email - * @param patch_idx the patch index - * @param patch_count the total number of patches that will be included - * @param commit_id the commit id for this change - * @param summary the commit message for this change - * @param body optional text to include above the diffstat - * @param author the person who authored this commit - * @param opts email creation options - */ -GIT_EXTERN(int) git_email_create_from_diff( - git_buf *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *opts); - /** * Create a diff for a commit in mbox format for sending via email. * The commit must not be a merge commit. @@ -114,14 +94,14 @@ GIT_EXTERN(int) git_email_create_from_diff( * @param out buffer to store the e-mail patch in * @param commit commit to create a patch for * @param opts email creation options + * @return 0 or an error code */ GIT_EXTERN(int) git_email_create_from_commit( git_buf *out, git_commit *commit, const git_email_create_options *opts); -GIT_END_DECL - /** @} */ +GIT_END_DECL #endif diff --git a/include/git2/errors.h b/include/git2/errors.h index 7180852f..11413907 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -11,7 +11,7 @@ /** * @file git2/errors.h - * @brief Git error handling routines and variables + * @brief Error handling routines and variables * @ingroup Git * @{ */ @@ -19,25 +19,32 @@ GIT_BEGIN_DECL /** Generic return codes */ typedef enum { - GIT_OK = 0, /**< No error */ + /** + * No error occurred; the call was successful. + */ + GIT_OK = 0, + + /** + * An error occurred; call `git_error_last` for more information. + */ + GIT_ERROR = -1, - GIT_ERROR = -1, /**< Generic error */ - GIT_ENOTFOUND = -3, /**< Requested object could not be found */ - GIT_EEXISTS = -4, /**< Object exists preventing operation */ - GIT_EAMBIGUOUS = -5, /**< More than one object matches */ - GIT_EBUFS = -6, /**< Output buffer too short to hold data */ + GIT_ENOTFOUND = -3, /**< Requested object could not be found. */ + GIT_EEXISTS = -4, /**< Object exists preventing operation. */ + GIT_EAMBIGUOUS = -5, /**< More than one object matches. */ + GIT_EBUFS = -6, /**< Output buffer too short to hold data. */ /** * GIT_EUSER is a special error that is never generated by libgit2 * code. You can return it from a callback (e.g to stop an iteration) * to know that it was generated by the callback and not by libgit2. */ - GIT_EUSER = -7, + GIT_EUSER = -7, - GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */ - GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */ - GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */ - GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */ + GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository. */ + GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits. */ + GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */ + GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */ GIT_EINVALIDSPEC = -12, /**< Name/ref spec was not in a valid format */ GIT_ECONFLICT = -13, /**< Checkout conflicts prevented operation */ GIT_ELOCKED = -14, /**< Lock file prevented operation */ @@ -59,21 +66,16 @@ typedef enum { GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */ GIT_EAPPLYFAIL = -35, /**< Patch application failed */ GIT_EOWNER = -36, /**< The object is not owned by the current user */ - GIT_TIMEOUT = -37 /**< The operation timed out */ + GIT_TIMEOUT = -37, /**< The operation timed out */ + GIT_EUNCHANGED = -38, /**< There were no changes */ + GIT_ENOTSUPPORTED = -39, /**< An option is not supported */ + GIT_EREADONLY = -40 /**< The subject is read-only */ } git_error_code; /** - * Structure to store extra details of the last error that occurred. - * - * This is kept on a per-thread basis if GIT_THREADS was defined when the - * library was build, otherwise one is kept globally for the library + * Error classes are the category of error. They reflect the area of the + * code where an error occurred. */ -typedef struct { - char *message; - int klass; -} git_error; - -/** Error classes */ typedef enum { GIT_ERROR_NONE = 0, GIT_ERROR_NOMEMORY, @@ -115,67 +117,38 @@ typedef enum { } git_error_t; /** - * Return the last `git_error` object that was generated for the - * current thread. - * - * The default behaviour of this function is to return NULL if no previous error has occurred. - * However, libgit2's error strings are not cleared aggressively, so a prior - * (unrelated) error may be returned. This can be avoided by only calling - * this function if the prior call to a libgit2 API returned an error. + * Structure to store extra details of the last error that occurred. * - * @return A git_error object. - */ -GIT_EXTERN(const git_error *) git_error_last(void); - -/** - * Clear the last library error that occurred for this thread. + * This is kept on a per-thread basis if GIT_THREADS was defined when the + * library was build, otherwise one is kept globally for the library */ -GIT_EXTERN(void) git_error_clear(void); +typedef struct { + char *message; /**< The error message for the last error. */ + int klass; /**< The category of the last error. @type git_error_t */ +} git_error; /** - * Set the error message string for this thread, using `printf`-style - * formatting. + * Return the last `git_error` object that was generated for the + * current thread. * - * This function is public so that custom ODB backends and the like can - * relay an error message through libgit2. Most regular users of libgit2 - * will never need to call this function -- actually, calling it in most - * circumstances (for example, calling from within a callback function) - * will just end up having the value overwritten by libgit2 internals. + * This function will never return NULL. * - * This error message is stored in thread-local storage and only applies - * to the particular thread that this libgit2 call is made from. + * Callers should not rely on this to determine whether an error has + * occurred. For error checking, callers should examine the return + * codes of libgit2 functions. * - * @param error_class One of the `git_error_t` enum above describing the - * general subsystem that is responsible for the error. - * @param fmt The `printf`-style format string; subsequent arguments must - * be the arguments for the format string. - */ -GIT_EXTERN(void) git_error_set(int error_class, const char *fmt, ...) - GIT_FORMAT_PRINTF(2, 3); - -/** - * Set the error message string for this thread. This function is like - * `git_error_set` but takes a static string instead of a `printf`-style - * format. + * This call can only reliably report error messages when an error + * has occurred. (It may contain stale information if it is called + * after a different function that succeeds.) * - * @param error_class One of the `git_error_t` enum above describing the - * general subsystem that is responsible for the error. - * @param string The error message to keep - * @return 0 on success or -1 on failure - */ -GIT_EXTERN(int) git_error_set_str(int error_class, const char *string); - -/** - * Set the error message to a special value for memory allocation failure. + * The memory for this object is managed by libgit2. It should not + * be freed. * - * The normal `git_error_set_str()` function attempts to `strdup()` the - * string that is passed in. This is not a good idea when the error in - * question is a memory allocation failure. That circumstance has a - * special setter function that sets the error string to a known and - * statically allocated internal value. + * @return A pointer to a `git_error` object that describes the error. */ -GIT_EXTERN(void) git_error_set_oom(void); +GIT_EXTERN(const git_error *) git_error_last(void); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/filter.h b/include/git2/filter.h index 79bf14ce..cf6c5f59 100644 --- a/include/git2/filter.h +++ b/include/git2/filter.h @@ -14,9 +14,15 @@ /** * @file git2/filter.h - * @brief Git filter APIs - * + * @brief Filters modify files during checkout or commit * @ingroup Git + * + * During checkout, filters update a file from a "canonical" state to + * a format appropriate for the local filesystem; during commit, filters + * produce the canonical state. For example, on Windows, the line ending + * filters _may_ take a canonical state (with Unix-style newlines) in + * the repository, and place the contents on-disk with Windows-style + * `\r\n` line endings. * @{ */ GIT_BEGIN_DECL @@ -79,8 +85,11 @@ typedef struct { git_oid attr_commit_id; } git_filter_options; - #define GIT_FILTER_OPTIONS_VERSION 1 - #define GIT_FILTER_OPTIONS_INIT {GIT_FILTER_OPTIONS_VERSION} +/** Current version for the `git_filter_options` structure */ +#define GIT_FILTER_OPTIONS_VERSION 1 + +/** Static constructor for `git_filter_options` */ +#define GIT_FILTER_OPTIONS_INIT {GIT_FILTER_OPTIONS_VERSION} /** * A filter that can transform file data @@ -268,9 +277,7 @@ GIT_EXTERN(int) git_filter_list_stream_blob( */ GIT_EXTERN(void) git_filter_list_free(git_filter_list *filters); - -GIT_END_DECL - /** @} */ +GIT_END_DECL #endif diff --git a/include/git2/global.h b/include/git2/global.h index 2a87e10c..f15eb2d2 100644 --- a/include/git2/global.h +++ b/include/git2/global.h @@ -9,6 +9,12 @@ #include "common.h" +/** + * @file git2/global.h + * @brief libgit2 library initializer and shutdown functionality + * @ingroup Git + * @{ + */ GIT_BEGIN_DECL /** @@ -32,7 +38,7 @@ GIT_EXTERN(int) git_libgit2_init(void); * many times as `git_libgit2_init()` was called - it will return the * number of remainining initializations that have not been shutdown * (after this one). - * + * * @return the number of remaining initializations of the library, or an * error code. */ @@ -40,5 +46,6 @@ GIT_EXTERN(int) git_libgit2_shutdown(void); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/graph.h b/include/git2/graph.h index 56edb2f8..1792020a 100644 --- a/include/git2/graph.h +++ b/include/git2/graph.h @@ -13,7 +13,7 @@ /** * @file git2/graph.h - * @brief Git graph traversal routines + * @brief Graph traversal routines * @defgroup git_revwalk Git graph traversal routines * @ingroup Git * @{ @@ -61,8 +61,8 @@ GIT_EXTERN(int) git_graph_descendant_of( * * @param repo the repository where the commits exist * @param commit a previously loaded commit - * @param length the number of commits in the provided `descendant_array` * @param descendant_array oids of the commits + * @param length the number of commits in the provided `descendant_array` * @return 1 if the given commit is an ancestor of any of the given potential * descendants, 0 if not, error code otherwise. */ @@ -74,4 +74,5 @@ GIT_EXTERN(int) git_graph_reachable_from_any( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/ignore.h b/include/git2/ignore.h index 4c441c63..730f2214 100644 --- a/include/git2/ignore.h +++ b/include/git2/ignore.h @@ -10,6 +10,15 @@ #include "common.h" #include "types.h" +/** + * @file git2/ignore.h + * @brief Ignore particular untracked files + * @ingroup Git + * @{ + * + * When examining the repository status, git can optionally ignore + * specified untracked files. + */ GIT_BEGIN_DECL /** @@ -73,6 +82,7 @@ GIT_EXTERN(int) git_ignore_path_is_ignored( git_repository *repo, const char *path); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/index.h b/include/git2/index.h index 6e806371..0adff1ab 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -15,9 +15,14 @@ /** * @file git2/index.h - * @brief Git index parsing and manipulation routines + * @brief Index (aka "cache" aka "staging area") * @defgroup git_index Git index parsing and manipulation routines * @ingroup Git + * + * The index (or "cache", or "staging area") is the contents of the + * next commit. In addition, the index stores other data, such as + * conflicts that occurred during the last merge operation, and + * the "treecache" to speed up various on-disk operations. * @{ */ GIT_BEGIN_DECL @@ -77,8 +82,11 @@ typedef struct git_index_entry { * data in the `flags`. */ +/** Mask for name length */ #define GIT_INDEX_ENTRY_NAMEMASK (0x0fff) +/** Mask for index entry stage */ #define GIT_INDEX_ENTRY_STAGEMASK (0x3000) +/** Shift bits for index entry */ #define GIT_INDEX_ENTRY_STAGESHIFT 12 /** @@ -89,9 +97,17 @@ typedef enum { GIT_INDEX_ENTRY_VALID = (0x8000) } git_index_entry_flag_t; +/** + * Macro to get the stage value (0 for the "main index", or a conflict + * value) from an index entry. + */ #define GIT_INDEX_ENTRY_STAGE(E) \ (((E)->flags & GIT_INDEX_ENTRY_STAGEMASK) >> GIT_INDEX_ENTRY_STAGESHIFT) +/** + * Macro to set the stage value (0 for the "main index", or a conflict + * value) for an index entry. + */ #define GIT_INDEX_ENTRY_STAGE_SET(E,S) do { \ (E)->flags = ((E)->flags & ~GIT_INDEX_ENTRY_STAGEMASK) | \ (((S) & 0x03) << GIT_INDEX_ENTRY_STAGESHIFT); } while (0) @@ -131,7 +147,14 @@ typedef enum { } git_index_capability_t; -/** Callback for APIs that add/remove/update files matching pathspec */ +/** + * Callback for APIs that add/remove/update files matching pathspec + * + * @param path the path + * @param matched_pathspec the given pathspec + * @param payload the user-specified payload + * @return 0 to continue with the index operation, positive number to skip this file for the index operation, negative number on failure + */ typedef int GIT_CALLBACK(git_index_matched_path_cb)( const char *path, const char *matched_pathspec, void *payload); @@ -166,6 +189,74 @@ typedef enum { GIT_INDEX_STAGE_THEIRS = 3 } git_index_stage_t; +#ifdef GIT_EXPERIMENTAL_SHA256 + +/** + * The options for opening or creating an index. + * + * Initialize with `GIT_INDEX_OPTIONS_INIT`. Alternatively, you can + * use `git_index_options_init`. + * + * @options[version] GIT_INDEX_OPTIONS_VERSION + * @options[init_macro] GIT_INDEX_OPTIONS_INIT + * @options[init_function] git_index_options_init + */ +typedef struct git_index_options { + unsigned int version; /**< The version */ + + /** + * The object ID type for the object IDs that exist in the index. + * + * If this is not specified, this defaults to `GIT_OID_SHA1`. + */ + git_oid_t oid_type; +} git_index_options; + +/** Current version for the `git_index_options` structure */ +#define GIT_INDEX_OPTIONS_VERSION 1 + +/** Static constructor for `git_index_options` */ +#define GIT_INDEX_OPTIONS_INIT { GIT_INDEX_OPTIONS_VERSION } + +/** + * Initialize git_index_options structure + * + * Initializes a `git_index_options` with default values. Equivalent to creating + * an instance with GIT_INDEX_OPTIONS_INIT. + * + * @param opts The `git_index_options` struct to initialize. + * @param version The struct version; pass `GIT_INDEX_OPTIONS_VERSION`. + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_index_options_init( + git_index_options *opts, + unsigned int version); + +/** + * Creates a new bare Git index object, without a repository to back + * it. This index object is capable of storing SHA256 objects. + * + * @param index_out the pointer for the new index + * @param index_path the path to the index file in disk + * @param opts the options for opening the index, or NULL + * @return 0 or an error code + */ +GIT_EXTERN(int) git_index_open( + git_index **index_out, + const char *index_path, + const git_index_options *opts); + +/** + * Create an in-memory index object. + * + * @param index_out the pointer for the new index + * @param opts the options for opening the index, or NULL + * @return 0 or an error code + */ +GIT_EXTERN(int) git_index_new(git_index **index_out, const git_index_options *opts); + +#else + /** * Create a new bare Git index object as a memory representation * of the Git index file in 'index_path', without a repository @@ -180,16 +271,11 @@ typedef enum { * * The index must be freed once it's no longer in use. * - * @param out the pointer for the new index + * @param index_out the pointer for the new index * @param index_path the path to the index file in disk * @return 0 or an error code */ - -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_index_open(git_index **out, const char *index_path, git_oid_t oid_type); -#else -GIT_EXTERN(int) git_index_open(git_index **out, const char *index_path); -#endif +GIT_EXTERN(int) git_index_open(git_index **index_out, const char *index_path); /** * Create an in-memory index object. @@ -199,13 +285,11 @@ GIT_EXTERN(int) git_index_open(git_index **out, const char *index_path); * * The index must be freed once it's no longer in use. * - * @param out the pointer for the new index + * @param index_out the pointer for the new index * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_index_new(git_index **out, git_oid_t oid_type); -#else -GIT_EXTERN(int) git_index_new(git_index **out); +GIT_EXTERN(int) git_index_new(git_index **index_out); + #endif /** @@ -845,4 +929,5 @@ GIT_EXTERN(void) git_index_conflict_iterator_free( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/indexer.h b/include/git2/indexer.h index 630eef93..9aaedc3c 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -4,13 +4,23 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef _INCLUDE_git_indexer_h__ -#define _INCLUDE_git_indexer_h__ +#ifndef INCLUDE_git_indexer_h__ +#define INCLUDE_git_indexer_h__ #include "common.h" #include "types.h" #include "oid.h" +/** + * @file git2/indexer.h + * @brief Packfile indexing + * @ingroup Git + * @{ + * + * Indexing is the operation of taking a packfile - which is simply a + * collection of unordered commits - and producing an "index" so that + * one can lookup a commit in the packfile by object ID. + */ GIT_BEGIN_DECL /** A git indexer object */ @@ -53,6 +63,7 @@ typedef struct git_indexer_progress { * * @param stats Structure containing information about the state of the transfer * @param payload Payload provided by caller + * @return 0 on success or an error code */ typedef int GIT_CALLBACK(git_indexer_progress_cb)(const git_indexer_progress *stats, void *payload); @@ -66,6 +77,9 @@ typedef struct git_indexer_options { /** permissions to use creating packfile or 0 for defaults */ unsigned int mode; + /** the type of object ids in the packfile or 0 for SHA1 */ + git_oid_t oid_type; + /** * object database from which to read base objects when * fixing thin packs. This can be NULL if there are no thin @@ -85,7 +99,10 @@ typedef struct git_indexer_options { unsigned char verify; } git_indexer_options; +/** Current version for the `git_indexer_options` structure */ #define GIT_INDEXER_OPTIONS_VERSION 1 + +/** Static constructor for `git_indexer_options` */ #define GIT_INDEXER_OPTIONS_INIT { GIT_INDEXER_OPTIONS_VERSION } /** @@ -106,13 +123,12 @@ GIT_EXTERN(int) git_indexer_options_init( * * @param out where to store the indexer instance * @param path to the directory where the packfile should be stored - * @param oid_type the oid type to use for objects + * @param opts the options to create the indexer with * @return 0 or an error code. */ GIT_EXTERN(int) git_indexer_new( git_indexer **out, const char *path, - git_oid_t oid_type, git_indexer_options *opts); #else /** @@ -190,6 +206,7 @@ GIT_EXTERN(const char *) git_indexer_name(const git_indexer *idx); */ GIT_EXTERN(void) git_indexer_free(git_indexer *idx); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/mailmap.h b/include/git2/mailmap.h index 7c3f60fc..fd6ae717 100644 --- a/include/git2/mailmap.h +++ b/include/git2/mailmap.h @@ -13,10 +13,15 @@ /** * @file git2/mailmap.h - * @brief Mailmap parsing routines + * @brief Mailmaps provide alternate email addresses for users * @defgroup git_mailmap Git mailmap routines * @ingroup Git * @{ + * + * A mailmap can be used to specify alternate email addresses for + * repository committers or authors. This allows systems to map + * commits made using different email addresses to the same logical + * person. */ GIT_BEGIN_DECL @@ -112,4 +117,5 @@ GIT_EXTERN(int) git_mailmap_resolve_signature( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/merge.h b/include/git2/merge.h index fcce5594..be3b065b 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -17,9 +17,12 @@ /** * @file git2/merge.h - * @brief Git merge routines + * @brief Merge re-joins diverging branches of history * @defgroup git_merge Git merge routines * @ingroup Git + * + * Merge will take two commits and attempt to produce a commit that + * includes the changes that were made in both branches. * @{ */ GIT_BEGIN_DECL @@ -45,7 +48,10 @@ typedef struct { unsigned int mode; } git_merge_file_input; +/** Current version for the `git_merge_file_input_options` structure */ #define GIT_MERGE_FILE_INPUT_VERSION 1 + +/** Static constructor for `git_merge_file_input_options` */ #define GIT_MERGE_FILE_INPUT_INIT {GIT_MERGE_FILE_INPUT_VERSION} /** @@ -180,6 +186,7 @@ typedef enum { GIT_MERGE_FILE_ACCEPT_CONFLICTS = (1 << 9) } git_merge_file_flag_t; +/** Default size for conflict markers */ #define GIT_MERGE_CONFLICT_MARKER_SIZE 7 /** @@ -217,7 +224,10 @@ typedef struct { unsigned short marker_size; } git_merge_file_options; +/** Current version for the `git_merge_file_options` structure */ #define GIT_MERGE_FILE_OPTIONS_VERSION 1 + +/** Static constructor for `git_merge_file_options` */ #define GIT_MERGE_FILE_OPTIONS_INIT {GIT_MERGE_FILE_OPTIONS_VERSION} /** @@ -312,7 +322,10 @@ typedef struct { uint32_t file_flags; } git_merge_options; +/** Current version for the `git_merge_options` structure */ #define GIT_MERGE_OPTIONS_VERSION 1 + +/** Static constructor for `git_merge_options` */ #define GIT_MERGE_OPTIONS_INIT { \ GIT_MERGE_OPTIONS_VERSION, GIT_MERGE_FIND_RENAMES } @@ -471,6 +484,37 @@ GIT_EXTERN(int) git_merge_base_many( /** * Find all merge bases given a list of commits * + * This behaves similar to [`git merge-base`](https://git-scm.com/docs/git-merge-base#_discussion). + * + * Given three commits `a`, `b`, and `c`, `merge_base_many` + * will compute a hypothetical commit `m`, which is a merge between `b` + * and `c`. + + * For example, with the following topology: + * ```text + * o---o---o---o---C + * / + * / o---o---o---B + * / / + * ---2---1---o---o---o---A + * ``` + * + * the result of `merge_base_many` given `a`, `b`, and `c` is 1. This is + * because the equivalent topology with the imaginary merge commit `m` + * between `b` and `c` is: + * ```text + * o---o---o---o---o + * / \ + * / o---o---o---o---M + * / / + * ---2---1---o---o---o---A + * ``` + * + * and the result of `merge_base_many` given `a` and `m` is 1. + * + * If you're looking to recieve the common ancestor between all the + * given commits, use `merge_base_octopus`. + * * @param out array in which to store the resulting ids * @param repo the repository where the commits exist * @param length The number of commits in the provided `input_array` @@ -623,4 +667,5 @@ GIT_EXTERN(int) git_merge( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/message.h b/include/git2/message.h index cd3ddf73..874d027f 100644 --- a/include/git2/message.h +++ b/include/git2/message.h @@ -12,7 +12,7 @@ /** * @file git2/message.h - * @brief Git message management routines + * @brief Commit messages * @ingroup Git * @{ */ @@ -83,4 +83,4 @@ GIT_EXTERN(void) git_message_trailer_array_free(git_message_trailer_array *arr); /** @} */ GIT_END_DECL -#endif /* INCLUDE_git_message_h__ */ +#endif diff --git a/include/git2/net.h b/include/git2/net.h index 8103eafb..93bdac49 100644 --- a/include/git2/net.h +++ b/include/git2/net.h @@ -13,12 +13,13 @@ /** * @file git2/net.h - * @brief Git networking declarations + * @brief Low-level networking functionality * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** Default git protocol port number */ #define GIT_DEFAULT_PORT "9418" /** @@ -51,4 +52,5 @@ struct git_remote_head { /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/notes.h b/include/git2/notes.h index c135881a..3784d5f5 100644 --- a/include/git2/notes.h +++ b/include/git2/notes.h @@ -11,7 +11,7 @@ /** * @file git2/notes.h - * @brief Git notes management routines + * @brief Notes are metadata attached to an object * @defgroup git_note Git notes management routines * @ingroup Git * @{ @@ -21,13 +21,15 @@ GIT_BEGIN_DECL /** * Callback for git_note_foreach. * - * Receives: - * - blob_id: Oid of the blob containing the message - * - annotated_object_id: Oid of the git object being annotated - * - payload: Payload data passed to `git_note_foreach` + * @param blob_id object id of the blob containing the message + * @param annotated_object_id the id of the object being annotated + * @param payload user-specified data to the foreach function + * @return 0 on success, or a negative number on failure */ typedef int GIT_CALLBACK(git_note_foreach_cb)( - const git_oid *blob_id, const git_oid *annotated_object_id, void *payload); + const git_oid *blob_id, + const git_oid *annotated_object_id, + void *payload); /** * note iterator @@ -303,4 +305,5 @@ GIT_EXTERN(int) git_note_foreach( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/object.h b/include/git2/object.h index 6384aaa6..8a50239f 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -14,13 +14,14 @@ /** * @file git2/object.h - * @brief Git revision object management routines + * @brief Objects are blobs (files), trees (directories), commits, and annotated tags * @defgroup git_object Git revision object management routines * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** Maximum size of a git object */ #define GIT_OBJECT_SIZE_MAX UINT64_MAX /** @@ -53,18 +54,18 @@ GIT_EXTERN(int) git_object_lookup( * * The object obtained will be so that its identifier * matches the first 'len' hexadecimal characters - * (packets of 4 bits) of the given 'id'. - * 'len' must be at least GIT_OID_MINPREFIXLEN, and - * long enough to identify a unique object matching - * the prefix; otherwise the method will fail. + * (packets of 4 bits) of the given `id`. `len` must be + * at least `GIT_OID_MINPREFIXLEN`, and long enough to + * identify a unique object matching the prefix; otherwise + * the method will fail. * * The generated reference is owned by the repository and * should be closed with the `git_object_free` method * instead of free'd manually. * - * The 'type' parameter must match the type of the object + * The `type` parameter must match the type of the object * in the odb; the method will fail otherwise. - * The special value 'GIT_OBJECT_ANY' may be passed to let + * The special value `GIT_OBJECT_ANY` may be passed to let * the method guess the object's type. * * @param object_out pointer where to store the looked-up object @@ -260,7 +261,7 @@ GIT_EXTERN(int) git_object_rawcontent_is_valid( * @warning This function is experimental and its signature may change in * the future. * - * @param valid Output pointer to set with validity of the object content + * @param[out] valid Output pointer to set with validity of the object content * @param buf The contents to validate * @param len The length of the buffer * @param object_type The type of the object in the buffer diff --git a/include/git2/odb.h b/include/git2/odb.h index c7d6a894..e809c36d 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -15,7 +15,7 @@ /** * @file git2/odb.h - * @brief Git object database routines + * @brief An object database manages the storage of git objects * @defgroup git_odb Git object database routines * @ingroup Git * @{ @@ -35,6 +35,10 @@ typedef enum { /** * Function type for callbacks from git_odb_foreach. + * + * @param id an id of an object in the object database + * @param payload the payload from the initial call to git_odb_foreach + * @return 0 on success, or an error code */ typedef int GIT_CALLBACK(git_odb_foreach_cb)(const git_oid *id, void *payload); @@ -49,30 +53,53 @@ typedef struct { git_oid_t oid_type; } git_odb_options; -/* The current version of the diff options structure */ +/** The current version of the diff options structure */ #define GIT_ODB_OPTIONS_VERSION 1 -/* Stack initializer for odb options. Alternatively use +/** + * Stack initializer for odb options. Alternatively use * `git_odb_options_init` programmatic initialization. */ #define GIT_ODB_OPTIONS_INIT { GIT_ODB_OPTIONS_VERSION } +#ifdef GIT_EXPERIMENTAL_SHA256 + /** * Create a new object database with no backends. * - * Before the ODB can be used for read/writing, a custom database - * backend must be manually added using `git_odb_add_backend()` + * @param[out] odb location to store the database pointer, if opened. + * @param opts the options for this object database or NULL for defaults + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_new(git_odb **odb, const git_odb_options *opts); + +/** + * Create a new object database and automatically add loose and packed + * backends. * - * @param out location to store the database pointer, if opened. + * @param[out] odb_out location to store the database pointer, if opened. * Set to NULL if the open failed. + * @param objects_dir path of the backends' "objects" directory. * @param opts the options for this object database or NULL for defaults * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_odb_new(git_odb **out, const git_odb_options *opts); +GIT_EXTERN(int) git_odb_open( + git_odb **odb_out, + const char *objects_dir, + const git_odb_options *opts); + #else -GIT_EXTERN(int) git_odb_new(git_odb **out); -#endif + +/** + * Create a new object database with no backends. + * + * Before the ODB can be used for read/writing, a custom database + * backend must be manually added using `git_odb_add_backend()` + * + * @param[out] odb location to store the database pointer, if opened. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_new(git_odb **odb); /** * Create a new object database and automatically add @@ -85,19 +112,12 @@ GIT_EXTERN(int) git_odb_new(git_odb **out); * assuming `objects_dir` as the Objects folder which * contains a 'pack/' folder with the corresponding data * - * @param out location to store the database pointer, if opened. + * @param[out] odb_out location to store the database pointer, if opened. * Set to NULL if the open failed. * @param objects_dir path of the backends' "objects" directory. - * @param opts the options for this object database or NULL for defaults * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_odb_open( - git_odb **out, - const char *objects_dir, - const git_odb_options *opts); -#else -GIT_EXTERN(int) git_odb_open(git_odb **out, const char *objects_dir); +GIT_EXTERN(int) git_odb_open(git_odb **odb_out, const char *objects_dir); #endif /** @@ -134,13 +154,13 @@ GIT_EXTERN(void) git_odb_free(git_odb *db); * internally cached, so it should be closed * by the user once it's no longer in use. * - * @param out pointer where to store the read object + * @param[out] obj pointer where to store the read object * @param db database to search for the object in. * @param id identity of the object to read. * @return 0 if the object was read, GIT_ENOTFOUND if the object is * not in the database. */ -GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id); +GIT_EXTERN(int) git_odb_read(git_odb_object **obj, git_odb *db, const git_oid *id); /** * Read an object from the database, given a prefix @@ -160,7 +180,7 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i * internally cached, so it should be closed * by the user once it's no longer in use. * - * @param out pointer where to store the read object + * @param[out] obj pointer where to store the read object * @param db database to search for the object in. * @param short_id a prefix of the id of the object to read. * @param len the length of the prefix @@ -168,7 +188,7 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i * database. GIT_EAMBIGUOUS if the prefix is ambiguous * (several objects match the prefix) */ -GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len); +GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **obj, git_odb *db, const git_oid *short_id, size_t len); /** * Read the header of an object from the database, without @@ -180,8 +200,8 @@ GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git * of an object, so the whole object will be read and then the * header will be returned. * - * @param len_out pointer where to store the length - * @param type_out pointer where to store the type + * @param[out] len_out pointer where to store the length + * @param[out] type_out pointer where to store the type * @param db database to search for the object in. * @param id identity of the object to read. * @return 0 if the object was read, GIT_ENOTFOUND if the object is not @@ -286,7 +306,7 @@ GIT_EXTERN(int) git_odb_expand_ids( * @param db database to refresh * @return 0 on success, error code otherwise */ -GIT_EXTERN(int) git_odb_refresh(struct git_odb *db); +GIT_EXTERN(int) git_odb_refresh(git_odb *db); /** * List all objects available in the database @@ -301,7 +321,10 @@ GIT_EXTERN(int) git_odb_refresh(struct git_odb *db); * @param payload data to pass to the callback * @return 0 on success, non-zero callback return value, or error code */ -GIT_EXTERN(int) git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload); +GIT_EXTERN(int) git_odb_foreach( + git_odb *db, + git_odb_foreach_cb cb, + void *payload); /** * Write an object directly into the ODB @@ -316,7 +339,7 @@ GIT_EXTERN(int) git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payloa * * @param out pointer to store the OID result of the write * @param odb object database where to store the object - * @param data buffer with the data to store + * @param data @type `const unsigned char *` buffer with the data to store * @param len size of the buffer * @param type type of the data to store * @return 0 or an error code @@ -382,7 +405,7 @@ GIT_EXTERN(int) git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stre * @param stream the stream * @param buffer a user-allocated buffer to store the data in. * @param len the buffer's length - * @return 0 if the read succeeded, error code otherwise + * @return the number of bytes read if succeeded, error code otherwise */ GIT_EXTERN(int) git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len); @@ -466,29 +489,54 @@ GIT_EXTERN(int) git_odb_write_pack( GIT_EXTERN(int) git_odb_write_multi_pack_index( git_odb *db); +#ifdef GIT_EXPERIMENTAL_SHA256 + /** - * Determine the object-ID (sha1 or sha256 hash) of a data buffer + * Generate the object ID (in SHA1 or SHA256 format) for a given data buffer. * - * The resulting OID will be the identifier for the data buffer as if - * the data buffer it were to written to the ODB. - * - * @param out the resulting object-ID. + * @param[out] oid the resulting object ID. * @param data data to hash * @param len size of the data * @param object_type of the data to hash * @param oid_type the oid type to hash to * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 GIT_EXTERN(int) git_odb_hash( - git_oid *out, + git_oid *oid, const void *data, size_t len, git_object_t object_type, git_oid_t oid_type); + +/** + * Determine the object ID of a file on disk. + * + * @param[out] oid oid structure the result is written into. + * @param path file to read and determine object id for + * @param object_type of the data to hash + * @param oid_type the oid type to hash to + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_hashfile( + git_oid *oid, + const char *path, + git_object_t object_type, + git_oid_t oid_type); #else -GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_object_t type); -#endif + +/** + * Determine the object-ID (sha1 or sha256 hash) of a data buffer + * + * The resulting OID will be the identifier for the data buffer as if + * the data buffer it were to written to the ODB. + * + * @param[out] oid the resulting object-ID. + * @param data data to hash + * @param len size of the data + * @param object_type of the data to hash + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_hash(git_oid *oid, const void *data, size_t len, git_object_t object_type); /** * Read a file from disk and fill a git_oid with the object id @@ -498,20 +546,13 @@ GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_obj * the `-w` flag, however, with the --no-filters flag. * If you need filters, see git_repository_hashfile. * - * @param out oid structure the result is written into. + * @param[out] oid oid structure the result is written into. * @param path file to read and determine object id for * @param object_type of the data to hash - * @param oid_type the oid type to hash to * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_odb_hashfile( - git_oid *out, - const char *path, - git_object_t object_type, - git_oid_t oid_type); -#else -GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_object_t type); +GIT_EXTERN(int) git_odb_hashfile(git_oid *oid, const char *path, git_object_t object_type); + #endif /** @@ -557,7 +598,7 @@ GIT_EXTERN(const git_oid *) git_odb_object_id(git_odb_object *object); * This pointer is owned by the object and shall not be free'd. * * @param object the object - * @return a pointer to the data + * @return @type `const unsigned char *` a pointer to the data */ GIT_EXTERN(const void *) git_odb_object_data(git_odb_object *object); @@ -651,4 +692,5 @@ GIT_EXTERN(int) git_odb_set_commit_graph(git_odb *odb, git_commit_graph *cgraph) /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index 12dd0fd3..88ca29fb 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -13,17 +13,13 @@ /** * @file git2/backend.h - * @brief Git custom backend functions + * @brief Object database backends manage the storage of git objects * @defgroup git_odb Git object database routines * @ingroup Git * @{ */ GIT_BEGIN_DECL -/* - * Constructors for in-box ODB backends. - */ - /** Options for configuring a packfile object backend. */ typedef struct { unsigned int version; /**< version for the struct */ @@ -35,56 +31,16 @@ typedef struct { git_oid_t oid_type; } git_odb_backend_pack_options; -/* The current version of the diff options structure */ +/** The current version of the diff options structure */ #define GIT_ODB_BACKEND_PACK_OPTIONS_VERSION 1 -/* Stack initializer for odb pack backend options. Alternatively use +/** + * Stack initializer for odb pack backend options. Alternatively use * `git_odb_backend_pack_options_init` programmatic initialization. */ #define GIT_ODB_BACKEND_PACK_OPTIONS_INIT \ { GIT_ODB_BACKEND_PACK_OPTIONS_VERSION } -/** - * Create a backend for the packfiles. - * - * @param out location to store the odb backend pointer - * @param objects_dir the Git repository's objects directory - * - * @return 0 or an error code - */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_odb_backend_pack( - git_odb_backend **out, - const char *objects_dir, - const git_odb_backend_pack_options *opts); -#else -GIT_EXTERN(int) git_odb_backend_pack( - git_odb_backend **out, - const char *objects_dir); -#endif - -/** - * Create a backend out of a single packfile - * - * This can be useful for inspecting the contents of a single - * packfile. - * - * @param out location to store the odb backend pointer - * @param index_file path to the packfile's .idx file - * - * @return 0 or an error code - */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_odb_backend_one_pack( - git_odb_backend **out, - const char *index_file, - const git_odb_backend_pack_options *opts); -#else -GIT_EXTERN(int) git_odb_backend_one_pack( - git_odb_backend **out, - const char *index_file); -#endif - typedef enum { GIT_ODB_BACKEND_LOOSE_FSYNC = (1 << 0) } git_odb_backend_loose_flag_t; @@ -118,30 +74,100 @@ typedef struct { git_oid_t oid_type; } git_odb_backend_loose_options; -/* The current version of the diff options structure */ +/** The current version of the diff options structure */ #define GIT_ODB_BACKEND_LOOSE_OPTIONS_VERSION 1 -/* Stack initializer for odb loose backend options. Alternatively use +/** + * Stack initializer for odb loose backend options. Alternatively use * `git_odb_backend_loose_options_init` programmatic initialization. */ #define GIT_ODB_BACKEND_LOOSE_OPTIONS_INIT \ { GIT_ODB_BACKEND_LOOSE_OPTIONS_VERSION, 0, -1 } +/* + * Constructors for in-box ODB backends. + */ + +#ifdef GIT_EXPERIMENTAL_SHA256 + +/** + * Create a backend for a directory containing packfiles. + * + * @param[out] out location to store the odb backend pointer + * @param objects_dir the Git repository's objects directory + * @param opts the options to use when creating the pack backend + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_backend_pack( + git_odb_backend **out, + const char *objects_dir, + const git_odb_backend_pack_options *opts); + +/** + * Create a backend for a single packfile. + * + * @param[out] out location to store the odb backend pointer + * @param index_file path to the packfile's .idx file + * @param opts the options to use when creating the pack backend + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_backend_one_pack( + git_odb_backend **out, + const char *index_file, + const git_odb_backend_pack_options *opts); + /** * Create a backend for loose objects * - * @param out location to store the odb backend pointer + * @param[out] out location to store the odb backend pointer * @param objects_dir the Git repository's objects directory * @param opts options for the loose object backend or NULL * * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 GIT_EXTERN(int) git_odb_backend_loose( git_odb_backend **out, const char *objects_dir, git_odb_backend_loose_options *opts); + #else + +/** + * Create a backend for a directory containing packfiles. + * + * @param[out] out location to store the odb backend pointer + * @param objects_dir the Git repository's objects directory + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_backend_pack( + git_odb_backend **out, + const char *objects_dir); + +/** + * Create a backend out of a single packfile + * + * This can be useful for inspecting the contents of a single + * packfile. + * + * @param[out] out location to store the odb backend pointer + * @param index_file path to the packfile's .idx file + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_backend_one_pack( + git_odb_backend **out, + const char *index_file); + +/** + * Create a backend for loose objects + * + * @param[out] out location to store the odb backend pointer + * @param objects_dir the Git repository's objects directory + * @param compression_level zlib compression level (0-9), or -1 for the default + * @param do_fsync if non-zero, perform an fsync on write + * @param dir_mode permission to use when creating directories, or 0 for default + * @param file_mode permission to use when creating directories, or 0 for default + * @return 0 or an error code + */ GIT_EXTERN(int) git_odb_backend_loose( git_odb_backend **out, const char *objects_dir, @@ -149,6 +175,7 @@ GIT_EXTERN(int) git_odb_backend_loose( int do_fsync, unsigned int dir_mode, unsigned int file_mode); + #endif /** Streaming mode */ @@ -218,6 +245,7 @@ struct git_odb_writepack { void GIT_CALLBACK(free)(git_odb_writepack *writepack); }; +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/oid.h b/include/git2/oid.h index 35b43ef1..8539b4e6 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -8,12 +8,11 @@ #define INCLUDE_git_oid_h__ #include "common.h" -#include "types.h" #include "experimental.h" /** * @file git2/oid.h - * @brief Git object id routines + * @brief Object IDs * @defgroup git_oid Git object id routines * @ingroup Git * @{ @@ -82,13 +81,18 @@ typedef enum { #endif -/* Maximum possible object ID size in raw / hex string format. */ -#ifndef GIT_EXPERIMENTAL_SHA256 -# define GIT_OID_MAX_SIZE GIT_OID_SHA1_SIZE -# define GIT_OID_MAX_HEXSIZE GIT_OID_SHA1_HEXSIZE -#else +/** Maximum possible object ID size in raw format */ +#ifdef GIT_EXPERIMENTAL_SHA256 # define GIT_OID_MAX_SIZE GIT_OID_SHA256_SIZE +#else +# define GIT_OID_MAX_SIZE GIT_OID_SHA1_SIZE +#endif + +/** Maximum possible object ID size in hex format */ +#ifdef GIT_EXPERIMENTAL_SHA256 # define GIT_OID_MAX_HEXSIZE GIT_OID_SHA256_HEXSIZE +#else +# define GIT_OID_MAX_HEXSIZE GIT_OID_SHA1_HEXSIZE #endif /** Minimum length (in number of hex characters, @@ -107,6 +111,15 @@ typedef struct git_oid { unsigned char id[GIT_OID_MAX_SIZE]; } git_oid; +#ifdef GIT_EXPERIMENTAL_SHA256 + +GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str, git_oid_t type); +GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str, git_oid_t type); +GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length, git_oid_t type); +GIT_EXTERN(int) git_oid_fromraw(git_oid *out, const unsigned char *raw, git_oid_t type); + +#else + /** * Parse a hex formatted object id into a git_oid. * @@ -119,28 +132,18 @@ typedef struct git_oid { * the hex sequence and have at least the number of bytes * needed for an oid encoded in hex (40 bytes for sha1, * 256 bytes for sha256). - * @param type the type of object id * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str, git_oid_t type); -#else GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str); -#endif /** * Parse a hex formatted NUL-terminated string into a git_oid. * * @param out oid structure the result is written into. * @param str input hex string; must be null-terminated. - * @param type the type of object id * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str, git_oid_t type); -#else GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str); -#endif /** * Parse N characters of a hex formatted object id into a git_oid. @@ -151,14 +154,9 @@ GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str); * @param out oid structure the result is written into. * @param str input hex string of at least size `length` * @param length length of the input string - * @param type the type of object id * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length, git_oid_t type); -#else GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length); -#endif /** * Copy an already raw oid into a git_oid structure. @@ -167,10 +165,8 @@ GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length); * @param raw the raw input bytes to be copied. * @return 0 on success or error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_oid_fromraw(git_oid *out, const unsigned char *raw, git_oid_t type); -#else GIT_EXTERN(int) git_oid_fromraw(git_oid *out, const unsigned char *raw); + #endif /** @@ -310,6 +306,7 @@ GIT_EXTERN(int) git_oid_strcmp(const git_oid *id, const char *str); /** * Check is an oid is all zeros. * + * @param id the object ID to check * @return 1 if all zeros, 0 otherwise. */ GIT_EXTERN(int) git_oid_is_zero(const git_oid *id); @@ -370,4 +367,5 @@ GIT_EXTERN(void) git_oid_shorten_free(git_oid_shorten *os); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/oidarray.h b/include/git2/oidarray.h index 94fc58da..e79a5595 100644 --- a/include/git2/oidarray.h +++ b/include/git2/oidarray.h @@ -10,6 +10,13 @@ #include "common.h" #include "oid.h" +/** + * @file git2/oidarray.h + * @brief An array of object IDs + * @defgroup git_oidarray Arrays of object IDs + * @ingroup Git + * @{ + */ GIT_BEGIN_DECL /** Array of object ids */ @@ -34,4 +41,3 @@ GIT_EXTERN(void) git_oidarray_dispose(git_oidarray *array); GIT_END_DECL #endif - diff --git a/include/git2/pack.h b/include/git2/pack.h index 0f6bd2ab..3837e044 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -233,7 +233,15 @@ GIT_EXTERN(size_t) git_packbuilder_object_count(git_packbuilder *pb); */ GIT_EXTERN(size_t) git_packbuilder_written(git_packbuilder *pb); -/** Packbuilder progress notification function */ +/** + * Packbuilder progress notification function. + * + * @param stage the stage of the packbuilder + * @param current the current object + * @param total the total number of objects + * @param payload the callback payload + * @return 0 on success or an error code + */ typedef int GIT_CALLBACK(git_packbuilder_progress)( int stage, uint32_t current, @@ -247,6 +255,9 @@ typedef int GIT_CALLBACK(git_packbuilder_progress)( * @param progress_cb Function to call with progress information during * pack building. Be aware that this is called inline with pack building * operations, so performance may be affected. + * When progress_cb returns an error, the pack building process will be + * aborted and the error will be returned from the invoked function. + * `pb` must then be freed. * @param progress_cb_payload Payload for progress callback. * @return 0 or an error code */ @@ -264,4 +275,5 @@ GIT_EXTERN(void) git_packbuilder_free(git_packbuilder *pb); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/patch.h b/include/git2/patch.h index 9cf758a3..78248215 100644 --- a/include/git2/patch.h +++ b/include/git2/patch.h @@ -14,7 +14,7 @@ /** * @file git2/patch.h - * @brief Patch handling routines. + * @brief Patches store the textual diffs in a delta * @ingroup Git * @{ */ @@ -283,8 +283,7 @@ GIT_EXTERN(int) git_patch_to_buf( git_buf *out, git_patch *patch); -GIT_END_DECL - /**@}*/ +GIT_END_DECL #endif diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h index acbd5cd1..6f6918cd 100644 --- a/include/git2/pathspec.h +++ b/include/git2/pathspec.h @@ -12,6 +12,13 @@ #include "strarray.h" #include "diff.h" +/** + * @file git2/pathspec.h + * @brief Specifiers for path matching + * @defgroup git_pathspec Specifiers for path matching + * @ingroup Git + * @{ + */ GIT_BEGIN_DECL /** @@ -276,5 +283,7 @@ GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount( GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry( const git_pathspec_match_list *m, size_t pos); +/** @} */ GIT_END_DECL + #endif diff --git a/include/git2/proxy.h b/include/git2/proxy.h index cfc0c645..195ab75e 100644 --- a/include/git2/proxy.h +++ b/include/git2/proxy.h @@ -12,6 +12,12 @@ #include "cert.h" #include "credential.h" +/** + * @file git2/proxy.h + * @brief TLS proxies + * @ingroup Git + * @{ + */ GIT_BEGIN_DECL /** @@ -78,7 +84,10 @@ typedef struct { void *payload; } git_proxy_options; +/** Current version for the `git_proxy_options` structure */ #define GIT_PROXY_OPTIONS_VERSION 1 + +/** Static constructor for `git_proxy_options` */ #define GIT_PROXY_OPTIONS_INIT {GIT_PROXY_OPTIONS_VERSION} /** @@ -93,6 +102,7 @@ typedef struct { */ GIT_EXTERN(int) git_proxy_options_init(git_proxy_options *opts, unsigned int version); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/rebase.h b/include/git2/rebase.h index b1ac71f9..3fb3e573 100644 --- a/include/git2/rebase.h +++ b/include/git2/rebase.h @@ -17,8 +17,8 @@ /** * @file git2/rebase.h - * @brief Git rebase routines - * @defgroup git_rebase Git merge routines + * @brief Rebase manipulates commits, placing them on a new parent + * @defgroup git_rebase Rebase manipulates commits, placing them on a new parent * @ingroup Git * @{ */ @@ -67,10 +67,9 @@ typedef struct { /** * Options to control how files are written during `git_rebase_init`, - * `git_rebase_next` and `git_rebase_abort`. Note that a minimum - * strategy of `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, - * and a minimum strategy of `GIT_CHECKOUT_FORCE` is defaulted in - * `abort` to match git semantics. + * `git_rebase_next` and `git_rebase_abort`. Note that during + * `abort`, these options will add an implied `GIT_CHECKOUT_FORCE` + * to match git semantics. */ git_checkout_options checkout_options; @@ -155,7 +154,10 @@ typedef enum { GIT_REBASE_OPERATION_EXEC } git_rebase_operation_t; +/** Current version for the `git_rebase_options` structure */ #define GIT_REBASE_OPTIONS_VERSION 1 + +/** Static constructor for `git_rebase_options` */ #define GIT_REBASE_OPTIONS_INIT \ { GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \ GIT_CHECKOUT_OPTIONS_INIT, NULL, NULL } @@ -396,4 +398,5 @@ GIT_EXTERN(void) git_rebase_free(git_rebase *rebase); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/refdb.h b/include/git2/refdb.h index c4849abd..536ef10d 100644 --- a/include/git2/refdb.h +++ b/include/git2/refdb.h @@ -14,8 +14,8 @@ /** * @file git2/refdb.h - * @brief Git custom refs backend functions - * @defgroup git_refdb Git custom refs backend API + * @brief A database for references (branches and tags) + * @defgroup git_refdb A database for references (branches and tags) * @ingroup Git * @{ */ diff --git a/include/git2/reflog.h b/include/git2/reflog.h index ec365c1f..a0956c63 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -13,8 +13,8 @@ /** * @file git2/reflog.h - * @brief Git reflog management routines - * @defgroup git_reflog Git reflog management routines + * @brief Reference logs store how references change + * @defgroup git_reflog Reference logs store how references change * @ingroup Git * @{ */ @@ -167,4 +167,5 @@ GIT_EXTERN(void) git_reflog_free(git_reflog *reflog); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/refs.h b/include/git2/refs.h index 06f8bb97..d820f2a1 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -14,8 +14,8 @@ /** * @file git2/refs.h - * @brief Git reference management routines - * @defgroup git_reference Git reference management routines + * @brief References point to a commit; generally these are branches and tags + * @defgroup git_reference References point to a commit; generally these are branches and tags * @ingroup Git * @{ */ @@ -29,7 +29,7 @@ GIT_BEGIN_DECL * The name will be checked for validity. * See `git_reference_symbolic_create()` for rules about valid names. * - * @param out pointer to the looked-up reference + * @param[out] out pointer to the looked-up reference * @param repo the repository to look up the reference * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code. @@ -371,6 +371,7 @@ GIT_EXTERN(int) git_reference_set_target( * reflog is enabled for the repository. We only rename * the reflog if it exists. * + * @param[out] new_ref The new reference * @param ref The reference to rename * @param new_name The new name for the reference * @param force Overwrite an existing reference @@ -406,6 +407,7 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref); * This method removes the named reference from the repository without * looking at its old value. * + * @param repo The repository to remove the reference from * @param name The reference to remove * @return 0 or an error code */ @@ -518,7 +520,7 @@ GIT_EXTERN(int) git_reference_cmp( /** * Create an iterator for the repo's references * - * @param out pointer in which to store the iterator + * @param[out] out pointer in which to store the iterator * @param repo the repository * @return 0 or an error code */ @@ -543,7 +545,7 @@ GIT_EXTERN(int) git_reference_iterator_glob_new( /** * Get the next reference * - * @param out pointer in which to store the reference + * @param[out] out pointer in which to store the reference * @param iter the iterator * @return 0, GIT_ITEROVER if there are no more; or an error code */ @@ -724,7 +726,7 @@ GIT_EXTERN(int) git_reference_normalize_name( * If you pass `GIT_OBJECT_ANY` as the target type, then the object * will be peeled until a non-tag object is met. * - * @param out Pointer to the peeled git_object + * @param[out] out Pointer to the peeled git_object * @param ref The reference to be processed * @param type The type of the requested object (GIT_OBJECT_COMMIT, * GIT_OBJECT_TAG, GIT_OBJECT_TREE, GIT_OBJECT_BLOB or GIT_OBJECT_ANY). @@ -768,4 +770,5 @@ GIT_EXTERN(const char *) git_reference_shorthand(const git_reference *ref); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/refspec.h b/include/git2/refspec.h index eaf77474..49d5f89f 100644 --- a/include/git2/refspec.h +++ b/include/git2/refspec.h @@ -14,8 +14,8 @@ /** * @file git2/refspec.h - * @brief Git refspec attributes - * @defgroup git_refspec Git refspec attributes + * @brief Refspecs map local references to remote references + * @defgroup git_refspec Refspecs map local references to remote references * @ingroup Git * @{ */ @@ -58,7 +58,7 @@ GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec); * Get the refspec's string * * @param refspec the refspec - * @returns the refspec's original string + * @return the refspec's original string */ GIT_EXTERN(const char *) git_refspec_string(const git_refspec *refspec); @@ -79,7 +79,16 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec); GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec); /** - * Check if a refspec's source descriptor matches a reference + * Check if a refspec's source descriptor matches a negative reference + * + * @param refspec the refspec + * @param refname the name of the reference to check + * @return 1 if the refspec matches, 0 otherwise + */ +GIT_EXTERN(int) git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname); + +/** + * Check if a refspec's source descriptor matches a reference * * @param refspec the refspec * @param refname the name of the reference to check @@ -116,6 +125,7 @@ GIT_EXTERN(int) git_refspec_transform(git_buf *out, const git_refspec *spec, con */ GIT_EXTERN(int) git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/remote.h b/include/git2/remote.h index e9065b25..2472dedc 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -19,8 +19,8 @@ /** * @file git2/remote.h - * @brief Git remote management functions - * @defgroup git_remote remote management functions + * @brief Remotes are where local repositories fetch from and push to + * @defgroup git_remote Remotes are where local repositories fetch from and push to * @ingroup Git * @{ */ @@ -76,6 +76,17 @@ typedef enum { GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC = (1 << 1) } git_remote_create_flags; +/** + * How to handle reference updates. + */ +typedef enum { + /* Write the fetch results to FETCH_HEAD. */ + GIT_REMOTE_UPDATE_FETCHHEAD = (1 << 0), + + /* Report unchanged tips in the update_refs callback. */ + GIT_REMOTE_UPDATE_REPORT_UNCHANGED = (1 << 1) +} git_remote_update_flags; + /** * Remote creation options structure * @@ -105,7 +116,10 @@ typedef struct git_remote_create_options { unsigned int flags; } git_remote_create_options; +/** Current version for the `git_remote_create_options` structure */ #define GIT_REMOTE_CREATE_OPTIONS_VERSION 1 + +/** Static constructor for `git_remote_create_options` */ #define GIT_REMOTE_CREATE_OPTIONS_INIT {GIT_REMOTE_CREATE_OPTIONS_VERSION} /** @@ -235,9 +249,9 @@ GIT_EXTERN(const char *) git_remote_name(const git_remote *remote); /** * Get the remote's url * - * If url.*.insteadOf has been configured for this URL, it will - * return the modified URL. If `git_remote_set_instance_pushurl` - * has been called for this remote, then that URL will be returned. + * If url.*.insteadOf has been configured for this URL, it will return + * the modified URL. This function does not consider if a push url has + * been configured for this remote (use `git_remote_pushurl` if needed). * * @param remote the remote * @return a pointer to the url @@ -455,7 +469,15 @@ typedef enum git_remote_completion_t { GIT_REMOTE_COMPLETION_ERROR } git_remote_completion_t; -/** Push network progress notification function */ +/** + * Push network progress notification callback. + * + * @param current The number of objects pushed so far + * @param total The total number of objects to push + * @param bytes The number of bytes pushed + * @param payload The user-specified payload callback + * @return 0 or an error code to stop the transfer + */ typedef int GIT_CALLBACK(git_push_transfer_progress_cb)( unsigned int current, unsigned int total, @@ -491,8 +513,12 @@ typedef struct { * as commands to the destination. * @param len number of elements in `updates` * @param payload Payload provided by the caller + * @return 0 or an error code to stop the push */ -typedef int GIT_CALLBACK(git_push_negotiation)(const git_push_update **updates, size_t len, void *payload); +typedef int GIT_CALLBACK(git_push_negotiation)( + const git_push_update **updates, + size_t len, + void *payload); /** * Callback used to inform of the update status from the remote. @@ -557,7 +583,8 @@ struct git_remote_callbacks { * Completion is called when different parts of the download * process are done (currently unused). */ - int GIT_CALLBACK(completion)(git_remote_completion_t type, void *data); + int GIT_CALLBACK(completion)(git_remote_completion_t type, + void *data); /** * This will be called if the remote host requires @@ -583,11 +610,22 @@ struct git_remote_callbacks { */ git_indexer_progress_cb transfer_progress; +#ifdef GIT_DEPRECATE_HARD + void *reserved_update_tips; +#else /** - * Each time a reference is updated locally, this function - * will be called with information about it. + * Deprecated callback for reference updates, callers should + * set `update_refs` instead. This is retained for backward + * compatibility; if you specify both `update_refs` and + * `update_tips`, then only the `update_refs` function will + * be called. + * + * @deprecated the `update_refs` callback in this structure + * should be preferred */ - int GIT_CALLBACK(update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + int GIT_CALLBACK(update_tips)(const char *refname, + const git_oid *a, const git_oid *b, void *data); +#endif /** * Function to call with progress information during pack @@ -644,9 +682,25 @@ struct git_remote_callbacks { */ git_url_resolve_cb resolve_url; #endif + + /** + * Each time a reference is updated locally, this function + * will be called with information about it. This should be + * preferred over the `update_tips` callback in this + * structure. + */ + int GIT_CALLBACK(update_refs)( + const char *refname, + const git_oid *a, + const git_oid *b, + git_refspec *spec, + void *data); }; +/** Current version for the `git_remote_callbacks_options` structure */ #define GIT_REMOTE_CALLBACKS_VERSION 1 + +/** Static constructor for `git_remote_callbacks_options` */ #define GIT_REMOTE_CALLBACKS_INIT {GIT_REMOTE_CALLBACKS_VERSION} /** @@ -733,10 +787,9 @@ typedef struct { git_fetch_prune_t prune; /** - * Whether to write the results to FETCH_HEAD. Defaults to - * on. Leave this default in order to behave like git. + * How to handle reference updates; see `git_remote_update_flags`. */ - int update_fetchhead; + unsigned int update_fetchhead; /** * Determines how to behave regarding tags on the remote, such @@ -774,9 +827,17 @@ typedef struct { git_strarray custom_headers; } git_fetch_options; +/** Current version for the `git_fetch_options` structure */ #define GIT_FETCH_OPTIONS_VERSION 1 -#define GIT_FETCH_OPTIONS_INIT { GIT_FETCH_OPTIONS_VERSION, GIT_REMOTE_CALLBACKS_INIT, GIT_FETCH_PRUNE_UNSPECIFIED, 1, \ - GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, GIT_PROXY_OPTIONS_INIT } + +/** Static constructor for `git_fetch_options` */ +#define GIT_FETCH_OPTIONS_INIT { \ + GIT_FETCH_OPTIONS_VERSION, \ + GIT_REMOTE_CALLBACKS_INIT, \ + GIT_FETCH_PRUNE_UNSPECIFIED, \ + GIT_REMOTE_UPDATE_FETCHHEAD, \ + GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, \ + GIT_PROXY_OPTIONS_INIT } /** * Initialize git_fetch_options structure @@ -830,9 +891,17 @@ typedef struct { * Extra headers for this push operation */ git_strarray custom_headers; + + /** + * "Push options" to deliver to the remote. + */ + git_strarray remote_push_options; } git_push_options; +/** Current version for the `git_push_options` structure */ #define GIT_PUSH_OPTIONS_VERSION 1 + +/** Static constructor for `git_push_options` */ #define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION, 1, GIT_REMOTE_CALLBACKS_INIT, GIT_PROXY_OPTIONS_INIT } /** @@ -876,7 +945,10 @@ typedef struct { git_strarray custom_headers; } git_remote_connect_options; +/** Current version for the `git_remote_connect_options` structure */ #define GIT_REMOTE_CONNECT_OPTIONS_VERSION 1 + +/** Static constructor for `git_remote_connect_options` */ #define GIT_REMOTE_CONNECT_OPTIONS_INIT { \ GIT_REMOTE_CONNECT_OPTIONS_VERSION, \ GIT_REMOTE_CALLBACKS_INIT, \ @@ -996,20 +1068,20 @@ GIT_EXTERN(int) git_remote_upload( * `git_remote_connect` will be used (if it was called). * * @param remote the remote to update + * @param callbacks pointer to the callback structure to use or NULL + * @param update_flags the git_remote_update_flags for these tips. + * @param download_tags what the behaviour for downloading tags is for this fetch. This is + * ignored for push. This must be the same value passed to `git_remote_download()`. * @param reflog_message The message to insert into the reflogs. If * NULL and fetching, the default is "fetch ", where is * the name of the remote (or its url, for in-memory remotes). This * parameter is ignored when pushing. - * @param callbacks pointer to the callback structure to use or NULL - * @param update_fetchhead whether to write to FETCH_HEAD. Pass 1 to behave like git. - * @param download_tags what the behaviour for downloading tags is for this fetch. This is - * ignored for push. This must be the same value passed to `git_remote_download()`. * @return 0 or an error code */ GIT_EXTERN(int) git_remote_update_tips( git_remote *remote, const git_remote_callbacks *callbacks, - int update_fetchhead, + unsigned int update_flags, git_remote_autotag_option_t download_tags, const char *reflog_message); @@ -1071,6 +1143,9 @@ GIT_EXTERN(int) git_remote_push( /** * Get the statistics structure that is filled in by the fetch operation. + * + * @param remote the remote to get statistics for + * @return the git_indexer_progress for the remote */ GIT_EXTERN(const git_indexer_progress *) git_remote_stats(git_remote *remote); @@ -1170,4 +1245,5 @@ GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/repository.h b/include/git2/repository.h index 6ec2ac82..b203576a 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -10,12 +10,14 @@ #include "common.h" #include "types.h" #include "oid.h" +#include "odb.h" #include "buffer.h" +#include "commit.h" /** * @file git2/repository.h - * @brief Git repository management routines - * @defgroup git_repository Git repository management routines + * @brief The repository stores revisions for a source tree + * @defgroup git_repository The repository stores revisions for a source tree * @ingroup Git * @{ */ @@ -30,7 +32,11 @@ GIT_BEGIN_DECL * The method will automatically detect if 'path' is a normal * or bare repository or fail is 'path' is neither. * - * @param out pointer to the repo which will be opened + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * + * @param[out] out pointer to the repo which will be opened * @param path the path to the repository * @return 0 or an error code */ @@ -56,19 +62,11 @@ GIT_EXTERN(int) git_repository_open_from_worktree(git_repository **out, git_work * * @param out pointer to the repo * @param odb the object database to wrap - * @param oid_type the oid type of the object database * @return 0 or an error code */ -#ifdef GIT_EXPERIMENTAL_SHA256 -GIT_EXTERN(int) git_repository_wrap_odb( - git_repository **out, - git_odb *odb, - git_oid_t oid_type); -#else GIT_EXTERN(int) git_repository_wrap_odb( git_repository **out, git_odb *odb); -#endif /** * Look for a git repository and copy its path in the given buffer. @@ -80,6 +78,10 @@ GIT_EXTERN(int) git_repository_wrap_odb( * The method will automatically detect if the repository is bare * (if there is a repository). * + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * * @param out A pointer to a user-allocated git_buf which will contain * the found path. * @@ -157,7 +159,11 @@ typedef enum { /** * Find and open a repository with extended controls. * - * @param out Pointer to the repo which will be opened. This can + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * + * @param[out] out Pointer to the repo which will be opened. This can * actually be NULL if you only want to use the error code to * see if a repo at this path could be opened. * @param path Path to open as git repository. If the flags @@ -185,7 +191,11 @@ GIT_EXTERN(int) git_repository_open_ext( * if you're e.g. hosting git repositories and need to access them * efficiently * - * @param out Pointer to the repo which will be opened. + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * + * @param[out] out Pointer to the repo which will be opened. * @param bare_path Direct path to the bare repository * @return 0 on success, or an error code */ @@ -210,7 +220,11 @@ GIT_EXTERN(void) git_repository_free(git_repository *repo); * TODO: * - Reinit the repository * - * @param out pointer to the repo which will be created or reinitialized + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * + * @param[out] out pointer to the repo which will be created or reinitialized * @param path the path to the repository * @param is_bare if true, a Git repository without a working directory is * created at the pointed path. If false, provided path will be @@ -372,7 +386,10 @@ typedef struct { #endif } git_repository_init_options; +/** Current version for the `git_repository_init_options` structure */ #define GIT_REPOSITORY_INIT_OPTIONS_VERSION 1 + +/** Static constructor for `git_repository_init_options` */ #define GIT_REPOSITORY_INIT_OPTIONS_INIT {GIT_REPOSITORY_INIT_OPTIONS_VERSION} /** @@ -397,6 +414,10 @@ GIT_EXTERN(int) git_repository_init_options_init( * auto-detect the case sensitivity of the file system and if the * file system supports file mode bits correctly. * + * Note that the libgit2 library _must_ be initialized using + * `git_libgit2_init` before any APIs can be called, including + * this one. + * * @param out Pointer to the repo which will be created or reinitialized. * @param repo_path The path to the repository. * @param opts Pointer to git_repository_init_options struct. @@ -414,7 +435,7 @@ GIT_EXTERN(int) git_repository_init_ext( * `git_reference_free()` must be called when done with it to release the * allocated memory and prevent a leak. * - * @param out pointer to the reference which will be retrieved + * @param[out] out pointer to the reference which will be retrieved * @param repo a repository object * * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing @@ -503,6 +524,7 @@ typedef enum { GIT_REPOSITORY_ITEM_LOGS, GIT_REPOSITORY_ITEM_MODULES, GIT_REPOSITORY_ITEM_WORKTREES, + GIT_REPOSITORY_ITEM_WORKTREE_CONFIG, GIT_REPOSITORY_ITEM__LAST } git_repository_item_t; @@ -634,7 +656,7 @@ GIT_EXTERN(int) git_repository_config_snapshot(git_config **out, git_repository * The ODB must be freed once it's no longer being used by * the user. * - * @param out Pointer to store the loaded ODB + * @param[out] out Pointer to store the loaded ODB * @param repo A repository object * @return 0, or an error code */ @@ -650,7 +672,7 @@ GIT_EXTERN(int) git_repository_odb(git_odb **out, git_repository *repo); * The refdb must be freed once it's no longer being used by * the user. * - * @param out Pointer to store the loaded refdb + * @param[out] out Pointer to store the loaded refdb * @param repo A repository object * @return 0, or an error code */ @@ -666,7 +688,7 @@ GIT_EXTERN(int) git_repository_refdb(git_refdb **out, git_repository *repo); * The index must be freed once it's no longer being used by * the user. * - * @param out Pointer to store the loaded index + * @param[out] out Pointer to store the loaded index * @param repo A repository object * @return 0, or an error code */ @@ -856,7 +878,9 @@ GIT_EXTERN(int) git_repository_set_head_detached( * * See the documentation for `git_repository_set_head_detached()`. * - * @see git_repository_set_head_detached + * @param repo Repository pointer + * @param committish annotated commit to point HEAD to + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_repository_set_head_detached_from_annotated( git_repository *repo, @@ -949,8 +973,8 @@ GIT_EXTERN(int) git_repository_is_shallow(git_repository *repo); * The memory is owned by the repository and must not be freed by the * user. * - * @param name where to store the pointer to the name - * @param email where to store the pointer to the email + * @param[out] name where to store the pointer to the name + * @param[out] email where to store the pointer to the email * @param repo the repository * @return 0 or an error code */ @@ -978,6 +1002,18 @@ GIT_EXTERN(int) git_repository_set_ident(git_repository *repo, const char *name, */ GIT_EXTERN(git_oid_t) git_repository_oid_type(git_repository *repo); +/** + * Gets the parents of the next commit, given the current repository state. + * Generally, this is the HEAD commit, except when performing a merge, in + * which case it is two or more commits. + * + * @param commits a `git_commitarray` that will contain the commit parents + * @param repo the repository + * @return 0 or an error code + */ +GIT_EXTERN(int) git_repository_commit_parents(git_commitarray *commits, git_repository *repo); + /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/reset.h b/include/git2/reset.h index b2ee2ba7..0123f7c7 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -14,7 +14,7 @@ /** * @file git2/reset.h - * @brief Git reset management routines + * @brief Reset will update the local repository to a prior state * @ingroup Git * @{ */ @@ -75,11 +75,23 @@ GIT_EXTERN(int) git_reset( * * See the documentation for `git_reset()`. * - * @see git_reset + * @param repo Repository where to perform the reset operation. + * + * @param target Annotated commit to which the Head should be moved to. + * This object must belong to the given `repo`, it will be dereferenced + * to a git_commit which oid will be used as the target of the branch. + * + * @param reset_type Kind of reset operation to perform. + * + * @param checkout_opts Optional checkout options to be used for a HARD reset. + * The checkout_strategy field will be overridden (based on reset_type). + * This parameter can be used to propagate notify and progress callbacks. + * + * @return 0 on success or an error code */ GIT_EXTERN(int) git_reset_from_annotated( git_repository *repo, - const git_annotated_commit *commit, + const git_annotated_commit *target, git_reset_t reset_type, const git_checkout_options *checkout_opts); @@ -108,4 +120,5 @@ GIT_EXTERN(int) git_reset_default( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/revert.h b/include/git2/revert.h index 331e90df..ec51eca2 100644 --- a/include/git2/revert.h +++ b/include/git2/revert.h @@ -13,8 +13,8 @@ /** * @file git2/revert.h - * @brief Git revert routines - * @defgroup git_revert Git revert routines + * @brief Cherry-pick the inverse of a change to "undo" its effects + * @defgroup git_revert Cherry-pick the inverse of a change to "undo" its effects * @ingroup Git * @{ */ @@ -33,8 +33,13 @@ typedef struct { git_checkout_options checkout_opts; /**< Options for the checkout */ } git_revert_options; +/** Current version for the `git_revert_options` structure */ #define GIT_REVERT_OPTIONS_VERSION 1 -#define GIT_REVERT_OPTIONS_INIT {GIT_REVERT_OPTIONS_VERSION, 0, GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT} + +/** Static constructor for `git_revert_options` */ +#define GIT_REVERT_OPTIONS_INIT { \ + GIT_REVERT_OPTIONS_VERSION, 0, \ + GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT } /** * Initialize git_revert_options structure @@ -87,5 +92,5 @@ GIT_EXTERN(int) git_revert( /** @} */ GIT_END_DECL -#endif +#endif diff --git a/include/git2/revparse.h b/include/git2/revparse.h index 51ea2dc1..c14fe3dc 100644 --- a/include/git2/revparse.h +++ b/include/git2/revparse.h @@ -12,8 +12,8 @@ /** * @file git2/revparse.h - * @brief Git revision parsing routines - * @defgroup git_revparse Git revision parsing routines + * @brief Parse the textual revision information + * @defgroup git_revparse Parse the textual revision information * @ingroup Git * @{ */ @@ -107,7 +107,7 @@ GIT_EXTERN(int) git_revparse( git_repository *repo, const char *spec); - /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 4aa9f5b1..7c4ac546 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -13,8 +13,8 @@ /** * @file git2/revwalk.h - * @brief Git revision traversal routines - * @defgroup git_revwalk Git revision traversal routines + * @brief Traverse (walk) the commit graph (revision history) + * @defgroup git_revwalk Traverse (walk) the commit graph (revision history) * @ingroup Git * @{ */ @@ -299,4 +299,5 @@ GIT_EXTERN(int) git_revwalk_add_hide_cb( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/signature.h b/include/git2/signature.h index 849998e6..20ec24b3 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -12,9 +12,13 @@ /** * @file git2/signature.h - * @brief Git signature creation + * @brief Signatures are the actor in a repository and when they acted * @defgroup git_signature Git signature creation * @ingroup Git + * + * Signatures contain the information about the actor (committer or + * author) in a repository, and the time that they performed the + * commit, or authoring. * @{ */ GIT_BEGIN_DECL @@ -48,6 +52,42 @@ GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const c */ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email); +/** + * Create a new author and/or committer signatures with default + * information based on the configuration and environment variables. + * + * If `author_out` is set, it will be populated with the author + * information. The `GIT_AUTHOR_NAME` and `GIT_AUTHOR_EMAIL` + * environment variables will be honored, and `user.name` and + * `user.email` configuration options will be honored if the + * environment variables are unset. For timestamps, `GIT_AUTHOR_DATE` + * will be used, otherwise the current time will be used. + * + * If `committer_out` is set, it will be populated with the + * committer information. The `GIT_COMMITTER_NAME` and + * `GIT_COMMITTER_EMAIL` environment variables will be honored, + * and `user.name` and `user.email` configuration options will + * be honored if the environment variables are unset. For timestamps, + * `GIT_COMMITTER_DATE` will be used, otherwise the current time will + * be used. + * + * If neither `GIT_AUTHOR_DATE` nor `GIT_COMMITTER_DATE` are set, + * both timestamps will be set to the same time. + * + * It will return `GIT_ENOTFOUND` if either the `user.name` or + * `user.email` are not set and there is no fallback from an environment + * variable. One of `author_out` or `committer_out` must be set. + * + * @param author_out pointer to set the author signature, or NULL + * @param committer_out pointer to set the committer signature, or NULL + * @param repo repository pointer + * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code + */ +GIT_EXTERN(int) git_signature_default_from_env( + git_signature **author_out, + git_signature **committer_out, + git_repository *repo); + /** * Create a new action signature with default user and now timestamp. * @@ -56,6 +96,10 @@ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const c * based on that information. It will return GIT_ENOTFOUND if either the * user.name or user.email are not set. * + * Note that these do not examine environment variables, only the + * configuration files. Use `git_signature_default_from_env` to + * consider the environment variables. + * * @param out new signature * @param repo repository pointer * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code @@ -100,4 +144,5 @@ GIT_EXTERN(void) git_signature_free(git_signature *sig); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/stash.h b/include/git2/stash.h index dcfc013d..ad28c326 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -13,8 +13,13 @@ /** * @file git2/stash.h - * @brief Git stash management routines + * @brief Stashes stores some uncommitted state in the repository * @ingroup Git + * + * Stashes stores some uncommitted state in the repository; generally + * this allows a user to stash some changes so that they can restore + * the working directory to an unmodified state. This can allow a + * developer to work on two different changes in parallel. * @{ */ GIT_BEGIN_DECL @@ -94,7 +99,10 @@ typedef struct git_stash_save_options { git_strarray paths; } git_stash_save_options; +/** Current version for the `git_stash_save_options` structure */ #define GIT_STASH_SAVE_OPTIONS_VERSION 1 + +/** Static constructor for `git_stash_save_options` */ #define GIT_STASH_SAVE_OPTIONS_INIT { GIT_STASH_SAVE_OPTIONS_VERSION } /** @@ -165,6 +173,10 @@ typedef enum { * Stash application progress notification function. * Return 0 to continue processing, or a negative value to * abort the stash application. + * + * @param progress the progress information + * @param payload the user-specified payload to the apply function + * @return 0 on success, -1 on error */ typedef int GIT_CALLBACK(git_stash_apply_progress_cb)( git_stash_apply_progress_t progress, @@ -191,7 +203,10 @@ typedef struct git_stash_apply_options { void *progress_payload; } git_stash_apply_options; +/** Current version for the `git_stash_apply_options` structure */ #define GIT_STASH_APPLY_OPTIONS_VERSION 1 + +/** Static constructor for `git_stash_apply_options` */ #define GIT_STASH_APPLY_OPTIONS_INIT { \ GIT_STASH_APPLY_OPTIONS_VERSION, \ GIT_STASH_APPLY_DEFAULT, \ @@ -225,8 +240,6 @@ GIT_EXTERN(int) git_stash_apply_options_init( * GIT_EMERGECONFLICT and both the working directory and index will be left * unmodified. * - * Note that a minimum checkout strategy of `GIT_CHECKOUT_SAFE` is implied. - * * @param repo The owning repository. * @param index The position within the stash list. 0 points to the * most recent stashed state. @@ -311,4 +324,5 @@ GIT_EXTERN(int) git_stash_pop( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/status.h b/include/git2/status.h index bb28e875..e13783b6 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -14,7 +14,7 @@ /** * @file git2/status.h - * @brief Git file status routines + * @brief Status indicates how a user has changed the working directory and index * @defgroup git_status Git file status routines * @ingroup Git * @{ @@ -54,11 +54,10 @@ typedef enum { /** * Function pointer to receive status on individual files * - * `path` is the relative path to the file from the root of the repository. - * - * `status_flags` is a combination of `git_status_t` values that apply. - * - * `payload` is the value you passed to the foreach function as payload. + * @param path is the path to the file + * @param status_flags the `git_status_t` values for file's status + * @param payload the user-specified payload to the foreach function + * @return 0 on success, or a negative number on failure */ typedef int GIT_CALLBACK(git_status_cb)( const char *path, unsigned int status_flags, void *payload); @@ -207,6 +206,7 @@ typedef enum { GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 15) } git_status_opt_t; +/** Default `git_status_opt_t` values */ #define GIT_STATUS_OPT_DEFAULTS \ (GIT_STATUS_OPT_INCLUDE_IGNORED | \ GIT_STATUS_OPT_INCLUDE_UNTRACKED | \ @@ -261,7 +261,10 @@ typedef struct { uint16_t rename_threshold; } git_status_options; +/** Current version for the `git_status_options` structure */ #define GIT_STATUS_OPTIONS_VERSION 1 + +/** Static constructor for `git_status_options` */ #define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} /** @@ -449,4 +452,5 @@ GIT_EXTERN(int) git_status_should_ignore( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/stdint.h b/include/git2/stdint.h index 6950427d..4f532e13 100644 --- a/include/git2/stdint.h +++ b/include/git2/stdint.h @@ -1,37 +1,37 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006-2008 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifdef _MSC_VER // [ - -#ifndef _MSC_STDINT_H_ // [ +/* ISO C9x compliant stdint.h for Microsoft Visual Studio + * Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 + * + * Copyright (c) 2006-2008 Alexander Chemeris + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************************/ + +#ifdef _MSC_VER /* [ */ + +#ifndef _MSC_STDINT_H_ /* [ */ #define _MSC_STDINT_H_ #if _MSC_VER > 1000 @@ -40,10 +40,11 @@ #include -// For Visual Studio 6 in C++ mode and for many Visual Studio versions when -// compiling for ARM we should wrap include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +/* For Visual Studio 6 in C++ mode and for many Visual Studio versions when + * compiling for ARM we should wrap include with 'extern "C++" {}' + * or compiler give many errors like this: + * error C2733: second C linkage of overloaded function 'wmemchr' not allowed +*/ #ifdef __cplusplus extern "C" { #endif @@ -52,7 +53,7 @@ extern "C" { } #endif -// Define _W64 macros to mark types changing their size, like intptr_t. +/* Define _W64 macros to mark types changing their size, like intptr_t. */ #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 @@ -62,13 +63,14 @@ extern "C" { #endif -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types - -// Visual Studio 6 and Embedded Visual C++ 4 doesn't -// realize that, e.g. char has the same size as __int8 -// so we give up on __intX for them. +/* 7.18.1 Integer types + * + * 7.18.1.1 Exact-width integer types + * + * Visual Studio 6 and Embedded Visual C++ 4 doesn't + * realize that, e.g. char has the same size as __int8 + * so we give up on __intX for them. + */ #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; @@ -88,7 +90,7 @@ typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; -// 7.18.1.2 Minimum-width integer types +/* 7.18.1.2 Minimum-width integer types */ typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; @@ -98,7 +100,7 @@ typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; -// 7.18.1.3 Fastest minimum-width integer types +/* 7.18.1.3 Fastest minimum-width integer types */ typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; @@ -108,25 +110,25 @@ typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ +/* 7.18.1.4 Integer types capable of holding object pointers */ +#ifdef _WIN64 /* [ */ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ +#else /* _WIN64 ][ */ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; -#endif // _WIN64 ] +#endif /* _WIN64 ] */ -// 7.18.1.5 Greatest-width integer types +/* 7.18.1.5 Greatest-width integer types */ typedef int64_t intmax_t; typedef uint64_t uintmax_t; -// 7.18.2 Limits of specified-width integer types +/* 7.18.2 Limits of specified-width integer types */ -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) /* [ See footnote 220 at page 257 and footnote 221 at page 259 */ -// 7.18.2.1 Limits of exact-width integer types +/* 7.18.2.1 Limits of exact-width integer types */ #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) @@ -140,7 +142,7 @@ typedef uint64_t uintmax_t; #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX -// 7.18.2.2 Limits of minimum-width integer types +/* 7.18.2.2 Limits of minimum-width integer types */ #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN @@ -154,7 +156,7 @@ typedef uint64_t uintmax_t; #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX -// 7.18.2.3 Limits of fastest minimum-width integer types +/* 7.18.2.3 Limits of fastest minimum-width integer types */ #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN @@ -168,62 +170,62 @@ typedef uint64_t uintmax_t; #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ +/* 7.18.2.4 Limits of integer types capable of holding object pointers */ +#ifdef _WIN64 /* [ */ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ +#else /* _WIN64 ][ */ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] +#endif /* _WIN64 ] */ -// 7.18.2.5 Limits of greatest-width integer types +/* 7.18.2.5 Limits of greatest-width integer types */ #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX -// 7.18.3 Limits of other integer types +/* 7.18.3 Limits of other integer types */ -#ifdef _WIN64 // [ +#ifdef _WIN64 /* [ */ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ +#else /* _WIN64 ][ */ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] +#endif /* _WIN64 ] */ #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ +#ifndef SIZE_MAX /* [ */ +# ifdef _WIN64 /* [ */ # define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ +# else /* _WIN64 ][ */ # define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] +# endif /* _WIN64 ] */ +#endif /* SIZE_MAX ] */ -// WCHAR_MIN and WCHAR_MAX are also defined in -#ifndef WCHAR_MIN // [ +/* WCHAR_MIN and WCHAR_MAX are also defined in */ +#ifndef WCHAR_MIN /* [ */ # define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ +#endif /* WCHAR_MIN ] */ +#ifndef WCHAR_MAX /* [ */ # define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] +#endif /* WCHAR_MAX ] */ #define WINT_MIN 0 #define WINT_MAX _UI16_MAX -#endif // __STDC_LIMIT_MACROS ] +#endif /* __STDC_LIMIT_MACROS ] */ -// 7.18.4 Limits of other integer types +/* 7.18.4 Limits of other integer types */ -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) /* [ See footnote 224 at page 260 */ -// 7.18.4.1 Macros for minimum-width integer constants +/* 7.18.4.1 Macros for minimum-width integer constants */ #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 @@ -235,13 +237,13 @@ typedef uint64_t uintmax_t; #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 -// 7.18.4.2 Macros for greatest-width integer constants +/* 7.18.4.2 Macros for greatest-width integer constants */ #define INTMAX_C INT64_C #define UINTMAX_C UINT64_C -#endif // __STDC_CONSTANT_MACROS ] +#endif /* __STDC_CONSTANT_MACROS ] */ -#endif // _MSC_STDINT_H_ ] +#endif /* _MSC_STDINT_H_ ] */ -#endif // _MSC_VER ] \ No newline at end of file +#endif /* _MSC_VER ] */ diff --git a/include/git2/strarray.h b/include/git2/strarray.h index 03d93f8f..dcb628a1 100644 --- a/include/git2/strarray.h +++ b/include/git2/strarray.h @@ -11,8 +11,8 @@ /** * @file git2/strarray.h - * @brief Git string array routines - * @defgroup git_strarray Git string array routines + * @brief An array of strings for the user to free + * @defgroup git_strarray An array of strings for the user to free * @ingroup Git * @{ */ @@ -40,4 +40,3 @@ GIT_EXTERN(void) git_strarray_dispose(git_strarray *array); GIT_END_DECL #endif - diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 2082966f..911b3cee 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -15,7 +15,7 @@ /** * @file git2/submodule.h - * @brief Git submodule management utilities + * @brief Submodules place another repository's contents within this one * * Submodule support in libgit2 builds a list of known submodules and keeps * it in the repository. The list is built from the .gitmodules file, the @@ -88,20 +88,27 @@ typedef enum { GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13) } git_submodule_status_t; +/** Submodule source bits */ #define GIT_SUBMODULE_STATUS__IN_FLAGS 0x000Fu +/** Submodule index status */ #define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u +/** Submodule working directory status */ #define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u +/** Whether the submodule is modified */ #define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \ (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0) +/** Whether the submodule is modified (in the index) */ #define GIT_SUBMODULE_STATUS_IS_INDEX_UNMODIFIED(S) \ (((S) & GIT_SUBMODULE_STATUS__INDEX_FLAGS) == 0) +/** Whether the submodule is modified (in the working directory) */ #define GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(S) \ (((S) & (GIT_SUBMODULE_STATUS__WD_FLAGS & \ ~GIT_SUBMODULE_STATUS_WD_UNINITIALIZED)) == 0) +/** Whether the submodule working directory is dirty */ #define GIT_SUBMODULE_STATUS_IS_WD_DIRTY(S) \ (((S) & (GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED | \ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED | \ @@ -130,10 +137,8 @@ typedef struct git_submodule_update_options { /** * These options are passed to the checkout step. To disable - * checkout, set the `checkout_strategy` to - * `GIT_CHECKOUT_NONE`. Generally you will want the use - * GIT_CHECKOUT_SAFE to update files in the working - * directory. + * checkout, set the `checkout_strategy` to `GIT_CHECKOUT_NONE` + * or `GIT_CHECKOUT_DRY_RUN`. */ git_checkout_options checkout_opts; @@ -152,11 +157,15 @@ typedef struct git_submodule_update_options { int allow_fetch; } git_submodule_update_options; +/** Current version for the `git_submodule_update_options` structure */ #define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1 + +/** Static constructor for `git_submodule_update_options` */ #define GIT_SUBMODULE_UPDATE_OPTIONS_INIT \ { GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \ - { GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \ - GIT_FETCH_OPTIONS_INIT, 1 } + GIT_CHECKOUT_OPTIONS_INIT, \ + GIT_FETCH_OPTIONS_INIT, \ + 1 } /** * Initialize git_submodule_update_options structure @@ -531,7 +540,8 @@ GIT_EXTERN(int) git_submodule_set_update( * Note that at this time, libgit2 does not honor this setting and the * fetch functionality current ignores submodules. * - * @return 0 if fetchRecurseSubmodules is false, 1 if true + * @param submodule the submodule to examine + * @return the submodule recursion configuration */ GIT_EXTERN(git_submodule_recurse_t) git_submodule_fetch_recurse_submodules( git_submodule *submodule); @@ -543,7 +553,7 @@ GIT_EXTERN(git_submodule_recurse_t) git_submodule_fetch_recurse_submodules( * * @param repo the repository to affect * @param name the submodule to configure - * @param fetch_recurse_submodules Boolean value + * @param fetch_recurse_submodules the submodule recursion configuration * @return old value for fetchRecurseSubmodules */ GIT_EXTERN(int) git_submodule_set_fetch_recurse_submodules( @@ -665,4 +675,5 @@ GIT_EXTERN(int) git_submodule_location( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/alloc.h b/include/git2/sys/alloc.h index e7f85b89..67506f2b 100644 --- a/include/git2/sys/alloc.h +++ b/include/git2/sys/alloc.h @@ -10,6 +10,17 @@ #include "git2/common.h" +/** + * @file git2/sys/alloc.h + * @brief Custom memory allocators + * @defgroup git_merge Git merge routines + * @ingroup Git + * + * Users can configure custom allocators; this is particularly + * interesting when running in constrained environments, when calling + * from another language, or during testing. + * @{ + */ GIT_BEGIN_DECL /** @@ -62,6 +73,7 @@ int git_stdalloc_init_allocator(git_allocator *allocator); */ int git_win32_crtdbg_init_allocator(git_allocator *allocator); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/commit.h b/include/git2/sys/commit.h index ba671061..a8253c06 100644 --- a/include/git2/sys/commit.h +++ b/include/git2/sys/commit.h @@ -14,7 +14,7 @@ /** * @file git2/sys/commit.h * @brief Low-level Git commit creation - * @defgroup git_backend Git custom backend APIs + * @defgroup git_commit Low-level Git commit creation * @ingroup Git * @{ */ @@ -29,7 +29,43 @@ GIT_BEGIN_DECL * the `tree`, neither the `parents` list of `git_oid`s are checked for * validity. * - * @see git_commit_create + * @param id Pointer in which to store the OID of the newly created commit + * + * @param repo Repository where to store the commit + * + * @param update_ref If not NULL, name of the reference that + * will be updated to point to this commit. If the reference + * is not direct, it will be resolved to a direct reference. + * Use "HEAD" to update the HEAD of the current branch and + * make it point to this commit. If the reference doesn't + * exist yet, it will be created. If it does exist, the first + * parent must be the tip of this branch. + * + * @param author Signature with author and author time of commit + * + * @param committer Signature with committer and * commit time of commit + * + * @param message_encoding The encoding for the message in the + * commit, represented with a standard encoding name. + * E.g. "UTF-8". If NULL, no encoding header is written and + * UTF-8 is assumed. + * + * @param message Full message for this commit + * + * @param tree An instance of a `git_tree` object that will + * be used as the tree for the commit. This tree object must + * also be owned by the given `repo`. + * + * @param parent_count Number of parents for this commit + * + * @param parents Array of `parent_count` pointers to `git_commit` + * objects that will be used as the parents for this commit. This + * array may be NULL if `parent_count` is 0 (root commit). All the + * given commits must be owned by the `repo`. + * + * @return 0 or an error code + * The created commit will be written to the Object Database and + * the given reference will be updated to point to it */ GIT_EXTERN(int) git_commit_create_from_ids( git_oid *id, @@ -49,6 +85,10 @@ GIT_EXTERN(int) git_commit_create_from_ids( * This is invoked with the count of the number of parents processed so far * along with the user supplied payload. This should return a git_oid of * the next parent or NULL if all parents have been provided. + * + * @param idx the index of the parent + * @param payload the user-specified payload + * @return the object id of the parent, or NULL if there are no further parents */ typedef const git_oid * GIT_CALLBACK(git_commit_parent_callback)(size_t idx, void *payload); @@ -61,7 +101,40 @@ typedef const git_oid * GIT_CALLBACK(git_commit_parent_callback)(size_t idx, voi * with `parent_payload` and should return `git_oid` values or NULL to * indicate that all parents are accounted for. * - * @see git_commit_create + * @param id Pointer in which to store the OID of the newly created commit + * + * @param repo Repository where to store the commit + * + * @param update_ref If not NULL, name of the reference that + * will be updated to point to this commit. If the reference + * is not direct, it will be resolved to a direct reference. + * Use "HEAD" to update the HEAD of the current branch and + * make it point to this commit. If the reference doesn't + * exist yet, it will be created. If it does exist, the first + * parent must be the tip of this branch. + * + * @param author Signature with author and author time of commit + * + * @param committer Signature with committer and * commit time of commit + * + * @param message_encoding The encoding for the message in the + * commit, represented with a standard encoding name. + * E.g. "UTF-8". If NULL, no encoding header is written and + * UTF-8 is assumed. + * + * @param message Full message for this commit + * + * @param tree An instance of a `git_tree` object that will + * be used as the tree for the commit. This tree object must + * also be owned by the given `repo`. + * + * @param parent_cb Callback to invoke to obtain parent information + * + * @param parent_payload User-specified payload to provide to the callback + * + * @return 0 or an error code + * The created commit will be written to the Object Database and + * the given reference will be updated to point to it */ GIT_EXTERN(int) git_commit_create_from_callback( git_oid *id, @@ -77,4 +150,5 @@ GIT_EXTERN(int) git_commit_create_from_callback( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/commit_graph.h b/include/git2/sys/commit_graph.h index 06e045fc..ff547ef0 100644 --- a/include/git2/sys/commit_graph.h +++ b/include/git2/sys/commit_graph.h @@ -12,13 +12,52 @@ /** * @file git2/sys/commit_graph.h - * @brief Git commit-graph - * @defgroup git_commit_graph Git commit-graph APIs + * @brief Commit graphs store information about commit relationships + * @defgroup git_commit_graph Commit graphs * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** + * Options structure for `git_commit_graph_open_new`. + * + * Initialize with `GIT_COMMIT_GRAPH_OPEN_OPTIONS_INIT`. Alternatively, + * you can use `git_commit_graph_open_options_init`. + */ +typedef struct { + unsigned int version; + +#ifdef GIT_EXPERIMENTAL_SHA256 + /** The object ID type that this commit graph contains. */ + git_oid_t oid_type; +#endif +} git_commit_graph_open_options; + +/** Current version for the `git_commit_graph_open_options` structure */ +#define GIT_COMMIT_GRAPH_OPEN_OPTIONS_VERSION 1 + +/** Static constructor for `git_commit_graph_open_options` */ +#define GIT_COMMIT_GRAPH_OPEN_OPTIONS_INIT { \ + GIT_COMMIT_GRAPH_OPEN_OPTIONS_VERSION \ + } + +/** + * Initialize git_commit_graph_open_options structure + * + * Initializes a `git_commit_graph_open_options` with default values. + * Equivalent to creating an instance with + * `GIT_COMMIT_GRAPH_OPEN_OPTIONS_INIT`. + * + * @param opts The `git_commit_graph_open_options` struct to initialize. + * @param version The struct version; pass `GIT_COMMIT_GRAPH_OPEN_OPTIONS_VERSION`. + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_commit_graph_open_options_init( + git_commit_graph_open_options *opts, + unsigned int version); + + /** * Opens a `git_commit_graph` from a path to an objects directory. * @@ -32,7 +71,7 @@ GIT_EXTERN(int) git_commit_graph_open( git_commit_graph **cgraph_out, const char *objects_dir #ifdef GIT_EXPERIMENTAL_SHA256 - , git_oid_t oid_type + , const git_commit_graph_open_options *options #endif ); @@ -46,54 +85,6 @@ GIT_EXTERN(int) git_commit_graph_open( */ GIT_EXTERN(void) git_commit_graph_free(git_commit_graph *cgraph); -/** - * Create a new writer for `commit-graph` files. - * - * @param out Location to store the writer pointer. - * @param objects_info_dir The `objects/info` directory. - * The `commit-graph` file will be written in this directory. - * @return 0 or an error code - */ -GIT_EXTERN(int) git_commit_graph_writer_new( - git_commit_graph_writer **out, - const char *objects_info_dir -#ifdef GIT_EXPERIMENTAL_SHA256 - , git_oid_t oid_type -#endif - ); - -/** - * Free the commit-graph writer and its resources. - * - * @param w The writer to free. If NULL no action is taken. - */ -GIT_EXTERN(void) git_commit_graph_writer_free(git_commit_graph_writer *w); - -/** - * Add an `.idx` file (associated to a packfile) to the writer. - * - * @param w The writer. - * @param repo The repository that owns the `.idx` file. - * @param idx_path The path of an `.idx` file. - * @return 0 or an error code - */ -GIT_EXTERN(int) git_commit_graph_writer_add_index_file( - git_commit_graph_writer *w, - git_repository *repo, - const char *idx_path); - -/** - * Add a revwalk to the writer. This will add all the commits from the revwalk - * to the commit-graph. - * - * @param w The writer. - * @param walk The git_revwalk. - * @return 0 or an error code - */ -GIT_EXTERN(int) git_commit_graph_writer_add_revwalk( - git_commit_graph_writer *w, - git_revwalk *walk); - /** * The strategy to use when adding a new set of commits to a pre-existing @@ -108,15 +99,19 @@ typedef enum { } git_commit_graph_split_strategy_t; /** - * Options structure for - * `git_commit_graph_writer_commit`/`git_commit_graph_writer_dump`. + * Options structure for `git_commit_graph_writer_new`. * - * Initialize with `GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT`. Alternatively, you - * can use `git_commit_graph_writer_options_init`. + * Initialize with `GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT`. Alternatively, + * you can use `git_commit_graph_writer_options_init`. */ typedef struct { unsigned int version; +#ifdef GIT_EXPERIMENTAL_SHA256 + /** The object ID type that this commit graph contains. */ + git_oid_t oid_type; +#endif + /** * The strategy to use when adding new commits to a pre-existing commit-graph * chain. @@ -136,7 +131,10 @@ typedef struct { size_t max_commits; } git_commit_graph_writer_options; +/** Current version for the `git_commit_graph_writer_options` structure */ #define GIT_COMMIT_GRAPH_WRITER_OPTIONS_VERSION 1 + +/** Static constructor for `git_commit_graph_writer_options` */ #define GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT { \ GIT_COMMIT_GRAPH_WRITER_OPTIONS_VERSION \ } @@ -155,30 +153,73 @@ GIT_EXTERN(int) git_commit_graph_writer_options_init( git_commit_graph_writer_options *opts, unsigned int version); +/** + * Create a new writer for `commit-graph` files. + * + * @param out Location to store the writer pointer. + * @param objects_info_dir The `objects/info` directory. + * The `commit-graph` file will be written in this directory. + * @param options The options for the commit graph writer. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_commit_graph_writer_new( + git_commit_graph_writer **out, + const char *objects_info_dir, + const git_commit_graph_writer_options *options); + +/** + * Free the commit-graph writer and its resources. + * + * @param w The writer to free. If NULL no action is taken. + */ +GIT_EXTERN(void) git_commit_graph_writer_free(git_commit_graph_writer *w); + +/** + * Add an `.idx` file (associated to a packfile) to the writer. + * + * @param w The writer. + * @param repo The repository that owns the `.idx` file. + * @param idx_path The path of an `.idx` file. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_commit_graph_writer_add_index_file( + git_commit_graph_writer *w, + git_repository *repo, + const char *idx_path); + +/** + * Add a revwalk to the writer. This will add all the commits from the revwalk + * to the commit-graph. + * + * @param w The writer. + * @param walk The git_revwalk. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_commit_graph_writer_add_revwalk( + git_commit_graph_writer *w, + git_revwalk *walk); + /** * Write a `commit-graph` file to a file. * * @param w The writer - * @param opts Pointer to git_commit_graph_writer_options struct. * @return 0 or an error code */ GIT_EXTERN(int) git_commit_graph_writer_commit( - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts); + git_commit_graph_writer *w); /** * Dump the contents of the `commit-graph` to an in-memory buffer. * - * @param buffer Buffer where to store the contents of the `commit-graph`. + * @param[out] buffer Buffer where to store the contents of the `commit-graph`. * @param w The writer. - * @param opts Pointer to git_commit_graph_writer_options struct. * @return 0 or an error code */ GIT_EXTERN(int) git_commit_graph_writer_dump( git_buf *buffer, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts); + git_commit_graph_writer *w); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 0a9005e3..cc4a3991 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -13,13 +13,28 @@ /** * @file git2/sys/config.h - * @brief Git config backend routines - * @defgroup git_backend Git custom backend APIs + * @brief Custom configuration database backends + * @defgroup git_backend Custom configuration database backends * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** + * An entry in a configuration backend. This is provided so that + * backend implementors can have a mechanism to free their data. + */ +typedef struct git_config_backend_entry { + /** The base configuration entry */ + struct git_config_entry entry; + + /** + * Free function for this entry; for internal purposes. Callers + * should call `git_config_entry_free` to free data. + */ + void GIT_CALLBACK(free)(struct git_config_backend_entry *entry); +} git_config_backend_entry; + /** * Every iterator must have this struct as its first element, so the * API can talk to it. You'd define your iterator as @@ -39,7 +54,7 @@ struct git_config_iterator { * Return the current entry and advance the iterator. The * memory belongs to the library. */ - int GIT_CALLBACK(next)(git_config_entry **entry, git_config_iterator *iter); + int GIT_CALLBACK(next)(git_config_backend_entry **entry, git_config_iterator *iter); /** * Free the iterator @@ -59,7 +74,7 @@ struct git_config_backend { /* Open means open the file/database and parse if necessary */ int GIT_CALLBACK(open)(struct git_config_backend *, git_config_level_t level, const git_repository *repo); - int GIT_CALLBACK(get)(struct git_config_backend *, const char *key, git_config_entry **entry); + int GIT_CALLBACK(get)(struct git_config_backend *, const char *key, git_config_backend_entry **entry); int GIT_CALLBACK(set)(struct git_config_backend *, const char *key, const char *value); int GIT_CALLBACK(set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int GIT_CALLBACK(del)(struct git_config_backend *, const char *key); @@ -83,7 +98,11 @@ struct git_config_backend { int GIT_CALLBACK(unlock)(struct git_config_backend *, int success); void GIT_CALLBACK(free)(struct git_config_backend *); }; + +/** Current version for the `git_config_backend_options` structure */ #define GIT_CONFIG_BACKEND_VERSION 1 + +/** Static constructor for `git_config_backend_options` */ #define GIT_CONFIG_BACKEND_INIT {GIT_CONFIG_BACKEND_VERSION} /** @@ -125,6 +144,63 @@ GIT_EXTERN(int) git_config_add_backend( const git_repository *repo, int force); +/** Options for in-memory configuration backends. */ +typedef struct { + unsigned int version; + + /** + * The type of this backend (eg, "command line"). If this is + * NULL, then this will be "in-memory". + */ + const char *backend_type; + + /** + * The path to the origin; if this is NULL then it will be + * left unset in the resulting configuration entries. + */ + const char *origin_path; +} git_config_backend_memory_options; + +/** Current version for the `git_config_backend_memory_options` structure */ +#define GIT_CONFIG_BACKEND_MEMORY_OPTIONS_VERSION 1 + +/** Static constructor for `git_config_backend_memory_options` */ +#define GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT { GIT_CONFIG_BACKEND_MEMORY_OPTIONS_VERSION } + + +/** + * Create an in-memory configuration backend from a string in standard + * git configuration file format. + * + * @param out the new backend + * @param cfg the configuration that is to be parsed + * @param len the length of the string pointed to by `cfg` + * @param opts the options to initialize this backend with, or NULL + * @return 0 on success or an error code + */ +extern int git_config_backend_from_string( + git_config_backend **out, + const char *cfg, + size_t len, + git_config_backend_memory_options *opts); + +/** + * Create an in-memory configuration backend from a list of name/value + * pairs. + * + * @param out the new backend + * @param values the configuration values to set (in "key=value" format) + * @param len the length of the values array + * @param opts the options to initialize this backend with, or NULL + * @return 0 on success or an error code + */ +extern int git_config_backend_from_values( + git_config_backend **out, + const char **values, + size_t len, + git_config_backend_memory_options *opts); + /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/credential.h b/include/git2/sys/credential.h index bb4c9f94..0d573a32 100644 --- a/include/git2/sys/credential.h +++ b/include/git2/sys/credential.h @@ -11,9 +11,9 @@ #include "git2/credential.h" /** - * @file git2/sys/cred.h - * @brief Git credentials low-level implementation - * @defgroup git_credential Git credentials low-level implementation + * @file git2/sys/credential.h + * @brief Low-level credentials implementation + * @defgroup git_credential Low-level credentials implementation * @ingroup Git * @{ */ @@ -85,6 +85,7 @@ struct git_credential_ssh_custom { void *payload; /**< Payload passed to prompt_callback */ }; +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/diff.h b/include/git2/sys/diff.h index aefd7b99..a398f549 100644 --- a/include/git2/sys/diff.h +++ b/include/git2/sys/diff.h @@ -15,7 +15,7 @@ /** * @file git2/sys/diff.h - * @brief Low-level Git diff utilities + * @brief Low-level diff utilities * @ingroup Git * @{ */ @@ -33,6 +33,12 @@ GIT_BEGIN_DECL * must pass a `git_buf *` value as the payload to the `git_diff_print` * and/or `git_patch_print` function. The data will be appended to the * buffer (after any existing content). + * + * @param delta the delta being processed + * @param hunk the hunk being processed + * @param line the line being processed + * @param payload the payload provided by the diff generator + * @return 0 on success or an error code */ GIT_EXTERN(int) git_diff_print_callback__to_buf( const git_diff_delta *delta, @@ -53,6 +59,12 @@ GIT_EXTERN(int) git_diff_print_callback__to_buf( * value from `fopen()`) as the payload to the `git_diff_print` * and/or `git_patch_print` function. If you pass NULL, this will write * data to `stdout`. + * + * @param delta the delta being processed + * @param hunk the hunk being processed + * @param line the line being processed + * @param payload the payload provided by the diff generator + * @return 0 on success or an error code */ GIT_EXTERN(int) git_diff_print_callback__to_file_handle( const git_diff_delta *delta, @@ -70,7 +82,10 @@ typedef struct { size_t oid_calculations; /**< Number of ID calculations */ } git_diff_perfdata; +/** Current version for the `git_diff_perfdata_options` structure */ #define GIT_DIFF_PERFDATA_VERSION 1 + +/** Static constructor for `git_diff_perfdata_options` */ #define GIT_DIFF_PERFDATA_INIT {GIT_DIFF_PERFDATA_VERSION,0,0} /** @@ -85,10 +100,15 @@ GIT_EXTERN(int) git_diff_get_perfdata( /** * Get performance data for diffs from a git_status_list + * + * @param out Structure to be filled with diff performance data + * @param status Diff to read performance data from + * @return 0 for success, <0 for error */ GIT_EXTERN(int) git_status_list_get_perfdata( git_diff_perfdata *out, const git_status_list *status); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/email.h b/include/git2/sys/email.h index 6f4a2866..26e792ab 100644 --- a/include/git2/sys/email.h +++ b/include/git2/sys/email.h @@ -7,6 +7,11 @@ #ifndef INCLUDE_sys_git_email_h__ #define INCLUDE_sys_git_email_h__ +#include "git2/common.h" +#include "git2/diff.h" +#include "git2/email.h" +#include "git2/types.h" + /** * @file git2/sys/email.h * @brief Advanced git email creation routines @@ -28,6 +33,7 @@ GIT_BEGIN_DECL * @param body optional text to include above the diffstat * @param author the person who authored this commit * @param opts email creation options + * @return 0 on success or an error code */ GIT_EXTERN(int) git_email_create_from_diff( git_buf *out, @@ -42,4 +48,5 @@ GIT_EXTERN(int) git_email_create_from_diff( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/errors.h b/include/git2/sys/errors.h new file mode 100644 index 00000000..44e8ecba --- /dev/null +++ b/include/git2/sys/errors.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_sys_git_errors_h__ +#define INCLUDE_sys_git_errors_h__ + +#include "git2/common.h" + +/** + * @file git2/sys/errors.h + * @brief Advanced error handling + * @ingroup Git + * + * Error handling for advanced consumers; those who use callbacks + * or those who create custom databases. + * @{ + */ +GIT_BEGIN_DECL + +/** + * Clear the last library error that occurred for this thread. + */ +GIT_EXTERN(void) git_error_clear(void); + +/** + * Set the error message string for this thread, using `printf`-style + * formatting. + * + * This function is public so that custom ODB backends and the like can + * relay an error message through libgit2. Most regular users of libgit2 + * will never need to call this function -- actually, calling it in most + * circumstances (for example, calling from within a callback function) + * will just end up having the value overwritten by libgit2 internals. + * + * This error message is stored in thread-local storage and only applies + * to the particular thread that this libgit2 call is made from. + * + * @param error_class One of the `git_error_t` enum above describing the + * general subsystem that is responsible for the error. + * @param fmt The `printf`-style format string; subsequent arguments must + * be the arguments for the format string. + */ +GIT_EXTERN(void) git_error_set(int error_class, const char *fmt, ...) + GIT_FORMAT_PRINTF(2, 3); + +/** + * Set the error message string for this thread. This function is like + * `git_error_set` but takes a static string instead of a `printf`-style + * format. + * + * @param error_class One of the `git_error_t` enum above describing the + * general subsystem that is responsible for the error. + * @param string The error message to keep + * @return 0 on success or -1 on failure + */ +GIT_EXTERN(int) git_error_set_str(int error_class, const char *string); + +/** + * Set the error message to a special value for memory allocation failure. + * + * The normal `git_error_set_str()` function attempts to `strdup()` the + * string that is passed in. This is not a good idea when the error in + * question is a memory allocation failure. That circumstance has a + * special setter function that sets the error string to a known and + * statically allocated internal value. + */ +GIT_EXTERN(void) git_error_set_oom(void); + +/** @} */ +GIT_END_DECL + +#endif diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h index b3759416..60466d17 100644 --- a/include/git2/sys/filter.h +++ b/include/git2/sys/filter.h @@ -11,8 +11,8 @@ /** * @file git2/sys/filter.h - * @brief Git filter backend and plugin routines - * @defgroup git_backend Git custom backend APIs + * @brief Custom filter backends and plugins + * @defgroup git_backend Custom filter backends and plugins * @ingroup Git * @{ */ @@ -26,7 +26,10 @@ GIT_BEGIN_DECL */ GIT_EXTERN(git_filter *) git_filter_lookup(const char *name); +/** The "crlf" filter */ #define GIT_FILTER_CRLF "crlf" + +/** The "ident" filter */ #define GIT_FILTER_IDENT "ident" /** @@ -53,6 +56,12 @@ GIT_EXTERN(git_filter *) git_filter_lookup(const char *name); * the filter list for you, but you can use this in combination with the * `git_filter_lookup` and `git_filter_list_push` functions to assemble * your own chains of filters. + * + * @param out the filter list + * @param repo the repository to use for configuration + * @param mode the filter mode (direction) + * @param options the options + * @return 0 on success or an error code */ GIT_EXTERN(int) git_filter_list_new( git_filter_list **out, @@ -72,6 +81,11 @@ GIT_EXTERN(int) git_filter_list_new( * filter. Using this function, you can either pass in a payload if you * know the expected payload format, or you can pass NULL. Some filters * may fail with a NULL payload. Good luck! + * + * @param fl the filter list + * @param filter the filter to push + * @param payload the payload for the filter + * @return 0 on success or an error code */ GIT_EXTERN(int) git_filter_list_push( git_filter_list *fl, git_filter *filter, void *payload); @@ -96,17 +110,26 @@ typedef struct git_filter_source git_filter_source; /** * Get the repository that the source data is coming from. + * + * @param src the filter source + * @return the repository for the filter information */ GIT_EXTERN(git_repository *) git_filter_source_repo(const git_filter_source *src); /** * Get the path that the source data is coming from. + * + * @param src the filter source + * @return the path that is being filtered */ GIT_EXTERN(const char *) git_filter_source_path(const git_filter_source *src); /** * Get the file mode of the source file * If the mode is unknown, this will return 0 + * + * @param src the filter source + * @return the file mode for the file being filtered */ GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src); @@ -114,16 +137,25 @@ GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src); * Get the OID of the source * If the OID is unknown (often the case with GIT_FILTER_CLEAN) then * this will return NULL. + * + * @param src the filter source + * @return the object id of the file being filtered */ GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src); /** * Get the git_filter_mode_t to be used + * + * @param src the filter source + * @return the mode (direction) of the filter */ GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(const git_filter_source *src); /** * Get the combination git_filter_flag_t options to be applied + * + * @param src the filter source + * @return the flags of the filter */ GIT_EXTERN(uint32_t) git_filter_source_flags(const git_filter_source *src); @@ -137,6 +169,9 @@ GIT_EXTERN(uint32_t) git_filter_source_flags(const git_filter_source *src); * before the first use of the filter, so you can defer expensive * initialization operations (in case libgit2 is being used in a way that * doesn't need the filter). + * + * @param self the filter to initialize + * @return 0 on success, negative number on failure */ typedef int GIT_CALLBACK(git_filter_init_fn)(git_filter *self); @@ -149,6 +184,8 @@ typedef int GIT_CALLBACK(git_filter_init_fn)(git_filter *self); * This may be called even if the `initialize` callback was not made. * * Typically this function will free the `git_filter` object itself. + * + * @param self the filter to shutdown */ typedef void GIT_CALLBACK(git_filter_shutdown_fn)(git_filter *self); @@ -171,6 +208,12 @@ typedef void GIT_CALLBACK(git_filter_shutdown_fn)(git_filter *self); * allocated (not stack), so that it doesn't go away before the `stream` * callback can use it. If a filter allocates and assigns a value to the * `payload`, it will need a `cleanup` callback to free the payload. + * + * @param self the filter check + * @param payload a data for future filter functions + * @param src the filter source + * @param attr_values the attribute values + * @return 0 on success or a negative value on error */ typedef int GIT_CALLBACK(git_filter_check_fn)( git_filter *self, @@ -191,6 +234,12 @@ typedef int GIT_CALLBACK(git_filter_check_fn)( * The `payload` value will refer to any payload that was set by the * `check` callback. It may be read from or written to as needed. * + * @param self the filter check + * @param payload a data for future filter functions + * @param to the input buffer + * @param from the output buffer + * @param src the filter source + * @return 0 on success or a negative value on error * @deprecated use git_filter_stream_fn */ typedef int GIT_CALLBACK(git_filter_apply_fn)( @@ -209,6 +258,13 @@ typedef int GIT_CALLBACK(git_filter_apply_fn)( * `git_writestream` that will the original data will be written to; * with that data, the `git_writestream` will then perform the filter * translation and stream the filtered data out to the `next` location. + * + * @param out the write stream + * @param self the filter + * @param payload a data for future filter functions + * @param src the filter source + * @param next the output stream + * @return 0 on success or a negative value on error */ typedef int GIT_CALLBACK(git_filter_stream_fn)( git_writestream **out, @@ -225,6 +281,9 @@ typedef int GIT_CALLBACK(git_filter_stream_fn)( * `stream` callbacks allocated a `payload` to keep per-source filter * state, use this callback to free that payload and release resources * as required. + * + * @param self the filter + * @param payload a data for future filter functions */ typedef void GIT_CALLBACK(git_filter_cleanup_fn)( git_filter *self, @@ -291,7 +350,10 @@ struct git_filter { git_filter_cleanup_fn cleanup; }; +/** Current version for the `git_filter_options` structure */ #define GIT_FILTER_VERSION 1 + +/** Static constructor for `git_filter_options` */ #define GIT_FILTER_INIT {GIT_FILTER_VERSION} /** @@ -300,7 +362,7 @@ struct git_filter { * * @param filter the `git_filter` struct to initialize. * @param version Version the struct; pass `GIT_FILTER_VERSION` - * @return Zero on success; -1 on failure. + * @return 0 on success; -1 on failure. */ GIT_EXTERN(int) git_filter_init(git_filter *filter, unsigned int version); @@ -350,4 +412,5 @@ GIT_EXTERN(int) git_filter_unregister(const char *name); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/hashsig.h b/include/git2/sys/hashsig.h index 09c19aec..0d7be535 100644 --- a/include/git2/sys/hashsig.h +++ b/include/git2/sys/hashsig.h @@ -9,6 +9,16 @@ #include "git2/common.h" +/** + * @file git2/sys/hashsig.h + * @brief Signatures for file similarity comparison + * @defgroup git_hashsig Git merge routines + * @ingroup Git + * + * Hash signatures are used for file similary comparison; this is + * used for git's rename handling. + * @{ + */ GIT_BEGIN_DECL /** @@ -101,6 +111,7 @@ GIT_EXTERN(int) git_hashsig_compare( const git_hashsig *a, const git_hashsig *b); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/index.h b/include/git2/sys/index.h index 1f6d93f7..b3b86a04 100644 --- a/include/git2/sys/index.h +++ b/include/git2/sys/index.h @@ -12,8 +12,8 @@ /** * @file git2/sys/index.h - * @brief Low-level Git index manipulation routines - * @defgroup git_backend Git custom backend APIs + * @brief Low-level index manipulation routines + * @defgroup git_index Low-level index manipulation routines * @ingroup Git * @{ */ @@ -67,6 +67,7 @@ GIT_EXTERN(const git_index_name_entry *) git_index_name_get_byindex( * @param ancestor the path of the file as it existed in the ancestor * @param ours the path of the file as it existed in our tree * @param theirs the path of the file as it existed in their tree + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_index_name_add(git_index *index, const char *ancestor, const char *ours, const char *theirs); diff --git a/include/git2/sys/mempack.h b/include/git2/sys/mempack.h index 17da590a..be902be2 100644 --- a/include/git2/sys/mempack.h +++ b/include/git2/sys/mempack.h @@ -15,8 +15,8 @@ /** * @file git2/sys/mempack.h - * @brief Custom ODB backend that permits packing objects in-memory - * @defgroup git_backend Git custom backend APIs + * @brief A custom object database backend for storing objects in-memory + * @defgroup git_mempack A custom object database backend for storing objects in-memory * @ingroup Git * @{ */ @@ -44,6 +44,26 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_mempack_new(git_odb_backend **out); +/** + * Write a thin packfile with the objects in the memory store. + * + * A thin packfile is a packfile that does not contain its transitive closure of + * references. This is useful for efficiently distributing additions to a + * repository over the network, but also finds use in the efficient bulk + * addition of objects to a repository, locally. + * + * This operation performs the (shallow) insert operations into the + * `git_packbuilder`, but does not write the packfile to disk; + * see `git_packbuilder_write_buf`. + * + * It also does not reset the in-memory object database; see `git_mempack_reset`. + * + * @param backend The mempack backend + * @param pb The packbuilder to use to write the packfile + * @return 0 on success or an error code + */ +GIT_EXTERN(int) git_mempack_write_thin_pack(git_odb_backend *backend, git_packbuilder *pb); + /** * Dump all the queued in-memory writes to a packfile. * @@ -82,6 +102,16 @@ GIT_EXTERN(int) git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_ba */ GIT_EXTERN(int) git_mempack_reset(git_odb_backend *backend); +/** + * Get the total number of objects in mempack + * + * @param count The count of objects in the mempack + * @param backend The mempack backend + * @return 0 on success, or -1 on error + */ +GIT_EXTERN(int) git_mempack_object_count(size_t *count, git_odb_backend *backend); + +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/merge.h b/include/git2/sys/merge.h index ef4bc5ac..a9f52205 100644 --- a/include/git2/sys/merge.h +++ b/include/git2/sys/merge.h @@ -14,13 +14,18 @@ /** * @file git2/sys/merge.h - * @brief Git merge driver backend and plugin routines - * @defgroup git_merge Git merge driver APIs + * @brief Custom merge drivers + * @defgroup git_merge Custom merge drivers * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** + * A "merge driver" is a mechanism that can be configured to handle + * conflict resolution for files changed in both the "ours" and "theirs" + * side of a merge. + */ typedef struct git_merge_driver git_merge_driver; /** @@ -31,8 +36,11 @@ typedef struct git_merge_driver git_merge_driver; */ GIT_EXTERN(git_merge_driver *) git_merge_driver_lookup(const char *name); +/** The "text" merge driver */ #define GIT_MERGE_DRIVER_TEXT "text" +/** The "binary" merge driver */ #define GIT_MERGE_DRIVER_BINARY "binary" +/** The "union" merge driver */ #define GIT_MERGE_DRIVER_UNION "union" /** @@ -40,23 +48,48 @@ GIT_EXTERN(git_merge_driver *) git_merge_driver_lookup(const char *name); */ typedef struct git_merge_driver_source git_merge_driver_source; -/** Get the repository that the source data is coming from. */ +/** + * Get the repository that the source data is coming from. + * + * @param src the merge driver source + * @return the repository + */ GIT_EXTERN(git_repository *) git_merge_driver_source_repo( const git_merge_driver_source *src); -/** Gets the ancestor of the file to merge. */ +/** + * Gets the ancestor of the file to merge. + * + * @param src the merge driver source + * @return the ancestor or NULL if there was no ancestor + */ GIT_EXTERN(const git_index_entry *) git_merge_driver_source_ancestor( const git_merge_driver_source *src); -/** Gets the ours side of the file to merge. */ +/** + * Gets the ours side of the file to merge. + * + * @param src the merge driver source + * @return the ours side or NULL if there was no ours side + */ GIT_EXTERN(const git_index_entry *) git_merge_driver_source_ours( const git_merge_driver_source *src); -/** Gets the theirs side of the file to merge. */ +/** + * Gets the theirs side of the file to merge. + * + * @param src the merge driver source + * @return the theirs side or NULL if there was no theirs side + */ GIT_EXTERN(const git_index_entry *) git_merge_driver_source_theirs( const git_merge_driver_source *src); -/** Gets the merge file options that the merge was invoked with */ +/** + * Gets the merge file options that the merge was invoked with. + * + * @param src the merge driver source + * @return the options + */ GIT_EXTERN(const git_merge_file_options *) git_merge_driver_source_file_options( const git_merge_driver_source *src); @@ -72,6 +105,9 @@ GIT_EXTERN(const git_merge_file_options *) git_merge_driver_source_file_options( * right before the first use of the driver, so you can defer expensive * initialization operations (in case libgit2 is being used in a way that * doesn't need the merge driver). + * + * @param self the merge driver to initialize + * @return 0 on success, or a negative number on failure */ typedef int GIT_CALLBACK(git_merge_driver_init_fn)(git_merge_driver *self); @@ -84,6 +120,8 @@ typedef int GIT_CALLBACK(git_merge_driver_init_fn)(git_merge_driver *self); * This may be called even if the `initialize` callback was not made. * * Typically this function will free the `git_merge_driver` object itself. + * + * @param self the merge driver to shutdown */ typedef void GIT_CALLBACK(git_merge_driver_shutdown_fn)(git_merge_driver *self); @@ -104,6 +142,14 @@ typedef void GIT_CALLBACK(git_merge_driver_shutdown_fn)(git_merge_driver *self); * specified by the file's attributes. * * The `src` contains the data about the file to be merged. + * + * @param self the merge driver + * @param path_out the resolved path + * @param mode_out the resolved mode + * @param merged_out the merged output contents + * @param filter_name the filter that was invoked + * @param src the data about the unmerged file + * @return 0 on success, or an error code */ typedef int GIT_CALLBACK(git_merge_driver_apply_fn)( git_merge_driver *self, @@ -139,6 +185,7 @@ struct git_merge_driver { git_merge_driver_apply_fn apply; }; +/** The version for the `git_merge_driver` */ #define GIT_MERGE_DRIVER_VERSION 1 /** @@ -179,4 +226,5 @@ GIT_EXTERN(int) git_merge_driver_unregister(const char *name); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/midx.h b/include/git2/sys/midx.h index 3a87484d..b3a68afb 100644 --- a/include/git2/sys/midx.h +++ b/include/git2/sys/midx.h @@ -11,14 +11,52 @@ #include "git2/types.h" /** - * @file git2/midx.h - * @brief Git multi-pack-index routines - * @defgroup git_midx Git multi-pack-index routines + * @file git2/sys/midx.h + * @brief Incremental multi-pack indexes + * @defgroup git_midx Incremental multi-pack indexes * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** + * Options structure for `git_midx_writer_options`. + * + * Initialize with `GIT_MIDX_WRITER_OPTIONS_INIT`. Alternatively, + * you can use `git_midx_writer_options_init`. + */ +typedef struct { + unsigned int version; + +#ifdef GIT_EXPERIMENTAL_SHA256 + /** The object ID type that this commit graph contains. */ + git_oid_t oid_type; +#endif +} git_midx_writer_options; + +/** Current version for the `git_midx_writer_options` structure */ +#define GIT_MIDX_WRITER_OPTIONS_VERSION 1 + +/** Static constructor for `git_midx_writer_options` */ +#define GIT_MIDX_WRITER_OPTIONS_INIT { \ + GIT_MIDX_WRITER_OPTIONS_VERSION \ + } + +/** + * Initialize git_midx_writer_options structure + * + * Initializes a `git_midx_writer_options` with default values. + * Equivalent to creating an instance with + * `GIT_MIDX_WRITER_OPTIONS_INIT`. + * + * @param opts The `git_midx_writer_options` struct to initialize. + * @param version The struct version; pass `GIT_MIDX_WRITER_OPTIONS_VERSION`. + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_midx_writer_options_init( + git_midx_writer_options *opts, + unsigned int version); + /** * Create a new writer for `multi-pack-index` files. * @@ -31,7 +69,7 @@ GIT_EXTERN(int) git_midx_writer_new( git_midx_writer **out, const char *pack_dir #ifdef GIT_EXPERIMENTAL_SHA256 - , git_oid_t oid_type + , git_midx_writer_options *options #endif ); @@ -75,4 +113,5 @@ GIT_EXTERN(int) git_midx_writer_dump( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index c42abd37..53d8d060 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -13,9 +13,9 @@ #include "git2/odb.h" /** - * @file git2/sys/backend.h - * @brief Git custom backend implementors functions - * @defgroup git_backend Git custom backend APIs + * @file git2/sys/odb_backend.h + * @brief Object database backends for custom object storage + * @defgroup git_backend Object database backends for custom object storage * @ingroup Git * @{ */ @@ -106,7 +106,10 @@ struct git_odb_backend { void GIT_CALLBACK(free)(git_odb_backend *); }; +/** Current version for the `git_odb_backend_options` structure */ #define GIT_ODB_BACKEND_VERSION 1 + +/** Static constructor for `git_odb_backend_options` */ #define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION} /** @@ -167,6 +170,7 @@ GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len); #endif +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/openssl.h b/include/git2/sys/openssl.h index b41c55c6..8b74a98c 100644 --- a/include/git2/sys/openssl.h +++ b/include/git2/sys/openssl.h @@ -9,6 +9,12 @@ #include "git2/common.h" +/** + * @file git2/sys/openssl.h + * @brief Custom OpenSSL functionality + * @defgroup git_openssl Custom OpenSSL functionality + * @{ + */ GIT_BEGIN_DECL /** @@ -33,6 +39,7 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_openssl_set_locking(void); +/** @} */ GIT_END_DECL -#endif +#endif diff --git a/include/git2/sys/path.h b/include/git2/sys/path.h index 2a0c7e00..2963bca3 100644 --- a/include/git2/sys/path.h +++ b/include/git2/sys/path.h @@ -10,6 +10,16 @@ #include "git2/common.h" +/** + * @file git2/sys/path.h + * @brief Custom path handling + * @defgroup git_path Custom path handling + * @ingroup Git + * + * Merge will take two commits and attempt to produce a commit that + * includes the changes that were made in both branches. + * @{ + */ GIT_BEGIN_DECL /** @@ -59,6 +69,7 @@ typedef enum { */ GIT_EXTERN(int) git_path_is_gitfile(const char *path, size_t pathlen, git_path_gitfile gitfile, git_path_fs fs); +/** @} */ GIT_END_DECL -#endif /* INCLUDE_sys_git_path */ +#endif diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index c31e26d9..813822a6 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -12,9 +12,9 @@ #include "git2/oid.h" /** - * @file git2/refdb_backend.h - * @brief Git custom refs backend functions - * @defgroup git_refdb_backend Git custom refs backend API + * @file git2/sys/refdb_backend.h + * @brief Custom reference database backends for refs storage + * @defgroup git_refdb_backend Custom reference database backends for refs storage * @ingroup Git * @{ */ @@ -65,9 +65,9 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg exists The implementation shall set this to `0` if a ref does + * @param exists The implementation shall set this to `0` if a ref does * not exist, otherwise to `1`. - * @arg ref_name The reference's name that should be checked for + * @param ref_name The reference's name that should be checked for * existence. * @return `0` on success, a negative error value code. */ @@ -81,9 +81,9 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg out The implementation shall set this to the allocated + * @param out The implementation shall set this to the allocated * reference, if it could be found, otherwise to `NULL`. - * @arg ref_name The reference's name that should be checked for + * @param ref_name The reference's name that should be checked for * existence. * @return `0` on success, `GIT_ENOTFOUND` if the reference does * exist, otherwise a negative error code. @@ -98,12 +98,12 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg out The implementation shall set this to the allocated + * @param out The implementation shall set this to the allocated * reference iterator. A custom structure may be used with an * embedded `git_reference_iterator` structure. Both `next` * and `next_name` functions of `git_reference_iterator` need * to be populated. - * @arg glob A pattern to filter references by. If given, the iterator + * @param glob A pattern to filter references by. If given, the iterator * shall only return references that match the glob when * passed to `wildmatch`. * @return `0` on success, otherwise a negative error code. @@ -118,20 +118,20 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg ref The reference to persist. May either be a symbolic or + * @param ref The reference to persist. May either be a symbolic or * direct reference. - * @arg force Whether to write the reference if a reference with the + * @param force Whether to write the reference if a reference with the * same name already exists. - * @arg who The person updating the reference. Shall be used to create + * @param who The person updating the reference. Shall be used to create * a reflog entry. - * @arg message The message detailing what kind of reference update is + * @param message The message detailing what kind of reference update is * performed. Shall be used to create a reflog entry. - * @arg old If not `NULL` and `force` is not set, then the + * @param old If not `NULL` and `force` is not set, then the * implementation needs to ensure that the reference is currently at * the given OID before writing the new value. If both `old` * and `old_target` are `NULL`, then the reference should not * exist at the point of writing. - * @arg old_target If not `NULL` and `force` is not set, then the + * @param old_target If not `NULL` and `force` is not set, then the * implementation needs to ensure that the symbolic * reference is currently at the given target before * writing the new value. If both `old` and @@ -149,15 +149,15 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg out The implementation shall set this to the newly created + * @param out The implementation shall set this to the newly created * reference or `NULL` on error. - * @arg old_name The current name of the reference that is to be renamed. - * @arg new_name The new name that the old reference shall be renamed to. - * @arg force Whether to write the reference if a reference with the + * @param old_name The current name of the reference that is to be renamed. + * @param new_name The new name that the old reference shall be renamed to. + * @param force Whether to write the reference if a reference with the * target name already exists. - * @arg who The person updating the reference. Shall be used to create + * @param who The person updating the reference. Shall be used to create * a reflog entry. - * @arg message The message detailing what kind of reference update is + * @param message The message detailing what kind of reference update is * performed. Shall be used to create a reflog entry. * @return `0` on success, otherwise a negative error code. */ @@ -173,11 +173,11 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg ref_name The name of the reference name that shall be deleted. - * @arg old_id If not `NULL` and `force` is not set, then the + * @param ref_name The name of the reference name that shall be deleted. + * @param old_id If not `NULL` and `force` is not set, then the * implementation needs to ensure that the reference is currently at * the given OID before writing the new value. - * @arg old_target If not `NULL` and `force` is not set, then the + * @param old_target If not `NULL` and `force` is not set, then the * implementation needs to ensure that the symbolic * reference is currently at the given target before * writing the new value. @@ -243,7 +243,7 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg reflog The complete reference log for a given reference. Note + * @param reflog The complete reference log for a given reference. Note * that this may contain entries that have already been * written to disk. * @return `0` on success, a negative error code otherwise @@ -255,8 +255,8 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg old_name The name of old reference whose reflog shall be renamed from. - * @arg new_name The name of new reference whose reflog shall be renamed to. + * @param old_name The name of old reference whose reflog shall be renamed from. + * @param new_name The name of new reference whose reflog shall be renamed to. * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name); @@ -266,7 +266,7 @@ struct git_refdb_backend { * * A refdb implementation must provide this function. * - * @arg name The name of the reference whose reflog shall be deleted. + * @param name The name of the reference whose reflog shall be deleted. * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(reflog_delete)(git_refdb_backend *backend, const char *name); @@ -277,9 +277,9 @@ struct git_refdb_backend { * A refdb implementation may provide this function; if it is not * provided, the transaction API will fail to work. * - * @arg payload_out Opaque parameter that will be passed verbosely to + * @param payload_out Opaque parameter that will be passed verbosely to * `unlock`. - * @arg refname Reference that shall be locked. + * @param refname Reference that shall be locked. * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(lock)(void **payload_out, git_refdb_backend *backend, const char *refname); @@ -294,16 +294,16 @@ struct git_refdb_backend { * A refdb implementation must provide this function if a `lock` * implementation is provided. * - * @arg payload The payload returned by `lock`. - * @arg success `1` if a reference should be updated, `2` if + * @param payload The payload returned by `lock`. + * @param success `1` if a reference should be updated, `2` if * a reference should be deleted, `0` if the lock must be * discarded. - * @arg update_reflog `1` in case the reflog should be updated, `0` + * @param update_reflog `1` in case the reflog should be updated, `0` * otherwise. - * @arg ref The reference which should be unlocked. - * @arg who The person updating the reference. Shall be used to create + * @param ref The reference which should be unlocked. + * @param who The person updating the reference. Shall be used to create * a reflog entry in case `update_reflog` is set. - * @arg message The message detailing what kind of reference update is + * @param message The message detailing what kind of reference update is * performed. Shall be used to create a reflog entry in * case `update_reflog` is set. * @return `0` on success, a negative error code otherwise @@ -312,7 +312,10 @@ struct git_refdb_backend { const git_reference *ref, const git_signature *sig, const char *message); }; +/** Current version for the `git_refdb_backend_options` structure */ #define GIT_REFDB_BACKEND_VERSION 1 + +/** Static constructor for `git_refdb_backend_options` */ #define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION} /** @@ -356,6 +359,7 @@ GIT_EXTERN(int) git_refdb_set_backend( git_refdb *refdb, git_refdb_backend *backend); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/reflog.h b/include/git2/sys/reflog.h deleted file mode 100644 index c9d0041b..00000000 --- a/include/git2/sys/reflog.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_sys_git_reflog_h__ -#define INCLUDE_sys_git_reflog_h__ - -#include "git2/common.h" -#include "git2/types.h" -#include "git2/oid.h" - -GIT_BEGIN_DECL - -GIT_EXTERN(git_reflog_entry *) git_reflog_entry__alloc(void); -GIT_EXTERN(void) git_reflog_entry__free(git_reflog_entry *entry); - -GIT_END_DECL - -#endif diff --git a/include/git2/sys/refs.h b/include/git2/sys/refs.h index d2ce2e0b..e434e67c 100644 --- a/include/git2/sys/refs.h +++ b/include/git2/sys/refs.h @@ -13,8 +13,8 @@ /** * @file git2/sys/refs.h - * @brief Low-level Git ref creation - * @defgroup git_backend Git custom backend APIs + * @brief Low-level git reference creation + * @defgroup git_backend Low-level git reference creation * @ingroup Git * @{ */ @@ -46,4 +46,5 @@ GIT_EXTERN(git_reference *) git_reference__alloc_symbolic( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h index 0eae9234..476965da 100644 --- a/include/git2/sys/remote.h +++ b/include/git2/sys/remote.h @@ -13,19 +13,25 @@ /** * @file git2/sys/remote.h * @brief Low-level remote functionality for custom transports - * @defgroup git_remote Low-level remote functionality + * @defgroup git_remote Low-level remote functionality for custom transports * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** + * A remote's capabilities. + */ typedef enum { /** Remote supports fetching an advertised object by ID. */ GIT_REMOTE_CAPABILITY_TIP_OID = (1 << 0), /** Remote supports fetching an individual reachable object. */ GIT_REMOTE_CAPABILITY_REACHABLE_OID = (1 << 1), + + /** Remote supports push options. */ + GIT_REMOTE_CAPABILITY_PUSH_OPTIONS = (1 << 2), } git_remote_capability_t; /** @@ -43,4 +49,5 @@ GIT_EXTERN(void) git_remote_connect_options_dispose( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/repository.h b/include/git2/sys/repository.h index 892be669..026ac8a1 100644 --- a/include/git2/sys/repository.h +++ b/include/git2/sys/repository.h @@ -9,16 +9,71 @@ #include "git2/common.h" #include "git2/types.h" +#include "git2/oid.h" /** * @file git2/sys/repository.h - * @brief Git repository custom implementation routines - * @defgroup git_backend Git custom backend APIs + * @brief Custom repository handling + * @defgroup git_repository Custom repository handling * @ingroup Git * @{ */ GIT_BEGIN_DECL +#ifdef GIT_EXPERIMENTAL_SHA256 + +/** + * The options for creating an repository from scratch. + * + * Initialize with `GIT_REPOSITORY_NEW_OPTIONS_INIT`. Alternatively, + * you can use `git_repository_new_options_init`. + * + * @options[version] GIT_REPOSITORY_NEW_OPTIONS_VERSION + * @options[init_macro] GIT_REPOSITORY_NEW_OPTIONS_INIT + * @options[init_function] git_repository_new_options_init + */ +typedef struct git_repository_new_options { + unsigned int version; /**< The version */ + + /** + * The object ID type for the object IDs that exist in the index. + * + * If this is not specified, this defaults to `GIT_OID_SHA1`. + */ + git_oid_t oid_type; +} git_repository_new_options; + +/** Current version for the `git_repository_new_options` structure */ +#define GIT_REPOSITORY_NEW_OPTIONS_VERSION 1 + +/** Static constructor for `git_repository_new_options` */ +#define GIT_REPOSITORY_NEW_OPTIONS_INIT { GIT_REPOSITORY_NEW_OPTIONS_VERSION } + +/** + * Initialize git_repository_new_options structure + * + * Initializes a `git_repository_new_options` with default values. + * Equivalent to creating an instance with + * `GIT_REPOSITORY_NEW_OPTIONS_INIT`. + * + * @param opts The `git_repository_new_options` struct to initialize. + * @param version The struct version; pass `GIT_REPOSITORY_NEW_OPTIONS_VERSION`. + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_repository_new_options_init( + git_repository_new_options *opts, + unsigned int version); + +/** + * Create a new repository with no backends. + * + * @param[out] out The blank repository + * @param opts the options for repository creation, or NULL for defaults + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_new(git_repository **out, git_repository_new_options *opts); +#else + /** * Create a new repository with neither backends nor config object * @@ -29,11 +84,13 @@ GIT_BEGIN_DECL * can fail to function properly: locations under $GIT_DIR, $GIT_COMMON_DIR, * or $GIT_INFO_DIR are impacted. * - * @param out The blank repository + * @param[out] out The blank repository * @return 0 on success, or an error code */ GIT_EXTERN(int) git_repository_new(git_repository **out); +#endif + /** * Reset all the internal state in a repository. * @@ -156,6 +213,7 @@ GIT_EXTERN(int) git_repository_set_bare(git_repository *repo); * and caches them so that subsequent calls to `git_submodule_lookup` are O(1). * * @param repo the repository whose submodules will be cached. + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_repository_submodule_cache_all( git_repository *repo); @@ -171,10 +229,12 @@ GIT_EXTERN(int) git_repository_submodule_cache_all( * of these has changed, the cache might become invalid. * * @param repo the repository whose submodule cache will be cleared + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_repository_submodule_cache_clear( git_repository *repo); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index 3d28d09b..eabff686 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -11,8 +11,16 @@ #include "git2/types.h" #include "git2/proxy.h" +/** + * @file git2/sys/stream.h + * @brief Streaming file I/O functionality + * @defgroup git_stream Streaming file I/O functionality + * @ingroup Git + * @{ + */ GIT_BEGIN_DECL +/** Current version for the `git_stream` structures */ #define GIT_STREAM_VERSION 1 /** @@ -29,8 +37,8 @@ GIT_BEGIN_DECL typedef struct git_stream { int version; - int encrypted : 1, - proxy_support : 1; + unsigned int encrypted : 1, + proxy_support : 1; /** * Timeout for read and write operations; can be set to `0` to @@ -147,6 +155,7 @@ GIT_EXTERN(int) git_stream_register_tls(git_stream_cb ctor); #endif +/**@}*/ GIT_END_DECL #endif diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index 370ca45d..ad6765c6 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -18,14 +18,20 @@ /** * @file git2/sys/transport.h - * @brief Git custom transport registration interfaces and functions - * @defgroup git_transport Git custom transport registration + * @brief Custom transport registration interfaces and functions + * @defgroup git_transport Custom transport registration * @ingroup Git + * + * Callers can override the default HTTPS or SSH implementation by + * specifying a custom transport. * @{ */ GIT_BEGIN_DECL +/** + * The negotiation state during a fetch smart transport negotiation. + */ typedef struct { const git_remote_head * const *refs; size_t refs_len; @@ -146,7 +152,10 @@ struct git_transport { void GIT_CALLBACK(free)(git_transport *transport); }; +/** Current version for the `git_transport` structure */ #define GIT_TRANSPORT_VERSION 1 + +/** Static constructor for `git_transport` */ #define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION} /** @@ -299,6 +308,7 @@ GIT_EXTERN(int) git_transport_smart_credentials(git_credential **out, git_transp * * @param out options struct to fill * @param transport the transport to extract the data from. + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_transport_remote_connect_options( git_remote_connect_options *out, @@ -386,7 +396,14 @@ struct git_smart_subtransport { void GIT_CALLBACK(free)(git_smart_subtransport *transport); }; -/** A function which creates a new subtransport for the smart transport */ +/** + * A function that creates a new subtransport for the smart transport + * + * @param out the smart subtransport + * @param owner the transport owner + * @param param the input parameter + * @return 0 on success, or an error code + */ typedef int GIT_CALLBACK(git_smart_subtransport_cb)( git_smart_subtransport **out, git_transport *owner, @@ -429,6 +446,7 @@ typedef struct git_smart_subtransport_definition { * * @param out The newly created subtransport * @param owner The smart transport to own this subtransport + * @param param custom parameters for the subtransport * @return 0 or an error code */ GIT_EXTERN(int) git_smart_subtransport_http( @@ -441,6 +459,7 @@ GIT_EXTERN(int) git_smart_subtransport_http( * * @param out The newly created subtransport * @param owner The smart transport to own this subtransport + * @param param custom parameters for the subtransport * @return 0 or an error code */ GIT_EXTERN(int) git_smart_subtransport_git( @@ -453,6 +472,7 @@ GIT_EXTERN(int) git_smart_subtransport_git( * * @param out The newly created subtransport * @param owner The smart transport to own this subtransport + * @param param custom parameters for the subtransport * @return 0 or an error code */ GIT_EXTERN(int) git_smart_subtransport_ssh( @@ -462,4 +482,5 @@ GIT_EXTERN(int) git_smart_subtransport_ssh( /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/tag.h b/include/git2/tag.h index 98305365..3b0c12eb 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -15,7 +15,7 @@ /** * @file git2/tag.h - * @brief Git tag parsing routines + * @brief A (nearly) immutable pointer to a commit; useful for versioning * @defgroup git_tag Git tag management * @ingroup Git * @{ @@ -335,6 +335,7 @@ typedef int GIT_CALLBACK(git_tag_foreach_cb)(const char *name, git_oid *oid, voi * @param repo Repository * @param callback Callback function * @param payload Pointer to callback data (optional) + * @return 0 on success or an error code */ GIT_EXTERN(int) git_tag_foreach( git_repository *repo, @@ -380,4 +381,5 @@ GIT_EXTERN(int) git_tag_name_is_valid(int *valid, const char *name); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/trace.h b/include/git2/trace.h index 8cee3a94..62cb87c0 100644 --- a/include/git2/trace.h +++ b/include/git2/trace.h @@ -12,8 +12,8 @@ /** * @file git2/trace.h - * @brief Git tracing configuration routines - * @defgroup git_trace Git tracing configuration routines + * @brief Tracing functionality to introspect libgit2 in your application + * @defgroup git_trace Tracing functionality to introspect libgit2 in your application * @ingroup Git * @{ */ @@ -48,8 +48,13 @@ typedef enum { /** * An instance for a tracing function + * + * @param level the trace level + * @param msg the trace message */ -typedef void GIT_CALLBACK(git_trace_cb)(git_trace_level_t level, const char *msg); +typedef void GIT_CALLBACK(git_trace_cb)( + git_trace_level_t level, + const char *msg); /** * Sets the system tracing configuration to the specified level with the @@ -64,4 +69,5 @@ GIT_EXTERN(int) git_trace_set(git_trace_level_t level, git_trace_cb cb); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/transaction.h b/include/git2/transaction.h index 4938570b..212d3291 100644 --- a/include/git2/transaction.h +++ b/include/git2/transaction.h @@ -12,8 +12,8 @@ /** * @file git2/transaction.h - * @brief Git transactional reference routines - * @defgroup git_transaction Git transactional reference routines + * @brief Transactional reference handling + * @defgroup git_transaction Transactional reference handling * @ingroup Git * @{ */ @@ -118,4 +118,5 @@ GIT_EXTERN(void) git_transaction_free(git_transaction *tx); /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/transport.h b/include/git2/transport.h index 5a27de9a..04a7390b 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -15,8 +15,8 @@ /** * @file git2/transport.h - * @brief Git transport interfaces and functions - * @defgroup git_transport interfaces and functions + * @brief Transports are the low-level mechanism to connect to a remote server + * @defgroup git_transport Transports are the low-level mechanism to connect to a remote server * @ingroup Git * @{ */ @@ -30,10 +30,18 @@ GIT_BEGIN_DECL * @param str The message from the transport * @param len The length of the message * @param payload Payload provided by the caller + * @return 0 on success or an error code */ typedef int GIT_CALLBACK(git_transport_message_cb)(const char *str, int len, void *payload); -/** Signature of a function which creates a transport */ +/** + * Signature of a function which creates a transport. + * + * @param out the transport generate + * @param owner the owner for the transport + * @param param the param to the transport creation + * @return 0 on success or an error code + */ typedef int GIT_CALLBACK(git_transport_cb)(git_transport **out, git_remote *owner, void *param); /** @} */ diff --git a/include/git2/tree.h b/include/git2/tree.h index ce0a6090..b8e2de21 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -14,8 +14,8 @@ /** * @file git2/tree.h - * @brief Git tree parsing, loading routines - * @defgroup git_tree Git tree parsing, loading routines + * @brief Trees are collections of files and folders to make up the repository hierarchy + * @defgroup git_tree Trees are collections of files and folders to make up the repository hierarchy * @ingroup Git * @{ */ @@ -24,7 +24,7 @@ GIT_BEGIN_DECL /** * Lookup a tree object from the repository. * - * @param out Pointer to the looked up tree + * @param[out] out Pointer to the looked up tree * @param repo The repo to use when locating the tree. * @param id Identity of the tree to locate. * @return 0 or an error code @@ -345,6 +345,10 @@ GIT_EXTERN(int) git_treebuilder_remove( * The return value is treated as a boolean, with zero indicating that the * entry should be left alone and any non-zero value meaning that the * entry should be removed from the treebuilder list (i.e. filtered out). + * + * @param entry the tree entry for the callback to examine + * @param payload the payload from the caller + * @return 0 to do nothing, non-zero to remove the entry */ typedef int GIT_CALLBACK(git_treebuilder_filter_cb)( const git_tree_entry *entry, void *payload); @@ -379,7 +383,14 @@ GIT_EXTERN(int) git_treebuilder_filter( GIT_EXTERN(int) git_treebuilder_write( git_oid *id, git_treebuilder *bld); -/** Callback for the tree traversal method */ +/** + * Callback for the tree traversal method. + * + * @param root the current (relative) root to the entry + * @param entry the tree entry + * @param payload the caller-provided callback payload + * @return a positive value to skip the entry, a negative value to stop the walk + */ typedef int GIT_CALLBACK(git_treewalk_cb)( const char *root, const git_tree_entry *entry, void *payload); @@ -470,6 +481,6 @@ typedef struct { GIT_EXTERN(int) git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates); /** @} */ - GIT_END_DECL + #endif diff --git a/include/git2/types.h b/include/git2/types.h index d4b033dc..a4afd18c 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -81,13 +81,18 @@ typedef enum { GIT_OBJECT_REF_DELTA = 7 /**< A delta, base is given by object id. */ } git_object_t; -/** An open object database handle. */ +/** + * An object database stores the objects (commit, trees, blobs, tags, + * etc) for a repository. + */ typedef struct git_odb git_odb; /** A custom backend in an ODB */ typedef struct git_odb_backend git_odb_backend; -/** An object read from the ODB */ +/** + * A "raw" object read from the object database. + */ typedef struct git_odb_object git_odb_object; /** A stream to read/write from the ODB */ @@ -194,7 +199,18 @@ typedef struct git_reference_iterator git_reference_iterator; /** Transactional interface to references */ typedef struct git_transaction git_transaction; -/** Annotated commits, the input to merge and rebase. */ +/** + * Annotated commits are commits with additional metadata about how the + * commit was resolved, which can be used for maintaining the user's + * "intent" through commands like merge and rebase. + * + * For example, if a user wants to conceptually "merge `HEAD`", then the + * commit portion of an annotated commit will point to the `HEAD` commit, + * but the _annotation_ will denote the ref `HEAD`. This allows git to + * perform the internal bookkeeping so that the system knows both the + * content of what is being merged but also how the content was looked up + * so that it can be recorded in the reflog appropriately. + */ typedef struct git_annotated_commit git_annotated_commit; /** Representation of a status collection */ diff --git a/include/git2/version.h b/include/git2/version.h index d6aba3be..7db5b53b 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,23 +7,33 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ +#include "common.h" + +/** + * @file git2/version.h + * @brief The version of libgit2 + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + /** * The version string for libgit2. This string follows semantic * versioning (v2) guidelines. */ -#define LIBGIT2_VERSION "1.7.2" +#define LIBGIT2_VERSION "1.9.2" /** The major version number for this version of libgit2. */ -#define LIBGIT2_VER_MAJOR 1 +#define LIBGIT2_VERSION_MAJOR 1 /** The minor version number for this version of libgit2. */ -#define LIBGIT2_VER_MINOR 7 +#define LIBGIT2_VERSION_MINOR 9 /** The revision ("teeny") version number for this version of libgit2. */ -#define LIBGIT2_VER_REVISION 2 +#define LIBGIT2_VERSION_REVISION 2 /** The Windows DLL patch number for this version of libgit2. */ -#define LIBGIT2_VER_PATCH 0 +#define LIBGIT2_VERSION_PATCH 0 /** * The prerelease string for this version of libgit2. For development @@ -31,9 +41,37 @@ * a prerelease name like "beta" or "rc1". For final releases, this will * be `NULL`. */ -#define LIBGIT2_VER_PRERELEASE NULL +#define LIBGIT2_VERSION_PRERELEASE NULL + +/** + * The library ABI soversion for this version of libgit2. This should + * only be changed when the library has a breaking ABI change, and so + * may not reflect the library's API version number. + */ +#define LIBGIT2_SOVERSION "1.9" + +/** + * An integer value representing the libgit2 version number. For example, + * libgit2 1.6.3 is 1060300. + */ +#define LIBGIT2_VERSION_NUMBER ( \ + (LIBGIT2_VERSION_MAJOR * 1000000) + \ + (LIBGIT2_VERSION_MINOR * 10000) + \ + (LIBGIT2_VERSION_REVISION * 100)) + +/** + * Compare the libgit2 version against a given version. Evaluates to true + * if the given major, minor, and revision values are greater than or equal + * to the currently running libgit2 version. For example: + * + * #if LIBGIT2_VERSION_CHECK(1, 6, 3) + * # error libgit2 version is >= 1.6.3 + * #endif + */ +#define LIBGIT2_VERSION_CHECK(major, minor, revision) \ + (LIBGIT2_VERSION_NUMBER >= ((major)*1000000)+((minor)*10000)+((revision)*100)) -/** The library ABI soversion for this version of libgit2. */ -#define LIBGIT2_SOVERSION "1.7" +/** @} */ +GIT_END_DECL #endif diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 9193eaf3..fd375175 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -11,11 +11,12 @@ #include "buffer.h" #include "types.h" #include "strarray.h" +#include "checkout.h" /** - * @file git2/worktrees.h - * @brief Git worktree related functions - * @defgroup git_commit Git worktree related functions + * @file git2/worktree.h + * @brief Additional working directories for a repository + * @defgroup git_commit Additional working directories for a repository * @ingroup Git * @{ */ @@ -85,8 +86,9 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); typedef struct git_worktree_add_options { unsigned int version; - int lock; /**< lock newly created worktree */ - git_reference *ref; /**< reference to use for the new worktree HEAD */ + int lock; /**< lock newly created worktree */ + int checkout_existing; /**< allow checkout of existing branch matching worktree name */ + git_reference *ref; /**< reference to use for the new worktree HEAD */ /** * Options for the checkout. @@ -94,8 +96,12 @@ typedef struct git_worktree_add_options { git_checkout_options checkout_options; } git_worktree_add_options; +/** Current version for the `git_worktree_add_options` structure */ #define GIT_WORKTREE_ADD_OPTIONS_VERSION 1 -#define GIT_WORKTREE_ADD_OPTIONS_INIT {GIT_WORKTREE_ADD_OPTIONS_VERSION,0,NULL,GIT_CHECKOUT_OPTIONS_INIT} + +/** Static constructor for `git_worktree_add_options` */ +#define GIT_WORKTREE_ADD_OPTIONS_INIT { GIT_WORKTREE_ADD_OPTIONS_VERSION, \ + 0, 0, NULL, GIT_CHECKOUT_OPTIONS_INIT } /** * Initialize git_worktree_add_options structure @@ -208,7 +214,10 @@ typedef struct git_worktree_prune_options { uint32_t flags; } git_worktree_prune_options; +/** Current version for the `git_worktree_prune_options` structure */ #define GIT_WORKTREE_PRUNE_OPTIONS_VERSION 1 + +/** Static constructor for `git_worktree_prune_options` */ #define GIT_WORKTREE_PRUNE_OPTIONS_INIT {GIT_WORKTREE_PRUNE_OPTIONS_VERSION,0} /** @@ -265,4 +274,5 @@ GIT_EXTERN(int) git_worktree_prune(git_worktree *wt, /** @} */ GIT_END_DECL + #endif diff --git a/package.json b/package.json index ed0ab4f4..26409260 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "1.7.2", + "version": "1.9.2", "repo": "https://github.com/libgit2/libgit2", "description": " A cross-platform, linkable library implementation of Git that you can use in your application.", "install": "mkdir build && cd build && cmake .. && cmake --build ." diff --git a/script/api-docs/.gitignore b/script/api-docs/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/script/api-docs/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/script/api-docs/README.md b/script/api-docs/README.md new file mode 100644 index 00000000..fb329e2f --- /dev/null +++ b/script/api-docs/README.md @@ -0,0 +1,13 @@ +# API Documentation Generator + +These scripts generate the "raw API" specs and reference documentation +for [www.libgit2.org](https://libgit2.org/docs/reference). + +The "raw API" specs consists of JSON documents, on per +released version or branch, that describes the APIs. This is +suitable for creating documentation from, or may be useful for +language bindings as well. + +The reference documentation is documentation fragments for each +API in each version, ready to be included in the libgit2 documentation +website. diff --git a/script/api-docs/api-generator.js b/script/api-docs/api-generator.js new file mode 100755 index 00000000..47c928ac --- /dev/null +++ b/script/api-docs/api-generator.js @@ -0,0 +1,1558 @@ +#!/usr/bin/env node + +const path = require('node:path'); +const child_process = require('node:child_process'); +const fs = require('node:fs').promises; +const util = require('node:util'); +const process = require('node:process'); + +const { program } = require('commander'); + +const includePath = (p) => `${p}/include`; +const ancientIncludePath = (p) => `${p}/src/git`; +const legacyIncludePath = (p) => `${p}/src/git2`; +const standardIncludePath = (p) => `${includePath(p)}/git2`; +const systemIncludePath = (p) => `${includePath(p)}/git2/sys`; + +const fileIgnoreList = [ 'stdint.h', 'inttypes.h' ]; +const apiIgnoreList = [ 'GIT_BEGIN_DECL', 'GIT_END_DECL', 'GIT_WIN32' ]; + +// Some older versions of libgit2 need some help with includes +const defaultIncludes = [ + 'checkout.h', 'common.h', 'diff.h', 'email.h', 'oidarray.h', 'merge.h', 'remote.h', 'types.h' +]; + +// We're unable to fully map `types.h` defined types into groups; +// provide some help. +const groupMap = { + 'filemode': 'tree', + 'treebuilder': 'tree', + 'note': 'notes', + 'packbuilder': 'pack', + 'reference': 'refs', + 'push': 'remote' }; + +async function headerPaths(p) { + const possibleIncludePaths = [ + ancientIncludePath(p), + legacyIncludePath(p), + standardIncludePath(p), + systemIncludePath(p) + ]; + + const includePaths = [ ]; + const paths = [ ]; + + for (const possibleIncludePath of possibleIncludePaths) { + try { + await fs.stat(possibleIncludePath); + includePaths.push(possibleIncludePath); + } + catch (e) { + if (e?.code !== 'ENOENT') { + throw e; + } + } + } + + if (!includePaths.length) { + throw new Error(`no include paths for ${p}`); + } + + for (const fullPath of includePaths) { + paths.push(...(await fs.readdir(fullPath)). + filter((filename) => filename.endsWith('.h')). + filter((filename) => !fileIgnoreList.includes(filename)). + filter((filename) => filename !== 'deprecated.h' || !options.deprecateHard). + map((filename) => `${fullPath}/${filename}`)); + } + + return paths; +} + +function trimPath(basePath, headerPath) { + const possibleIncludePaths = [ + ancientIncludePath(basePath), + legacyIncludePath(basePath), + standardIncludePath(basePath), + systemIncludePath(basePath) + ]; + + for (const possibleIncludePath of possibleIncludePaths) { + if (headerPath.startsWith(possibleIncludePath + '/')) { + return headerPath.substr(possibleIncludePath.length + 1); + } + } + + throw new Error("header path is not beneath include root"); +} + +function parseFileAst(path, ast) { + let currentFile = undefined; + const fileData = [ ]; + + for (const node of ast.inner) { + if (node.loc?.file && currentFile != node.loc.file) { + currentFile = node.loc.file; + } else if (node.loc?.spellingLoc?.file && currentFile != node.loc.spellingLoc.file) { + currentFile = node.loc.spellingLoc.file; + } + + if (currentFile != path) { + continue; + } + + fileData.push(node); + } + + return fileData; +} + +function includeBase(path) { + const segments = path.split('/'); + + while (segments.length > 1) { + if (segments[segments.length - 1] === 'git2' || + segments[segments.length - 1] === 'git') { + segments.pop(); + return segments.join('/'); + } + + segments.pop(); + } + + throw new Error(`could not resolve include base for ${path}`); +} + +function readAst(path, options) { + return new Promise((resolve, reject) => { + let errorMessage = ''; + const chunks = [ ]; + + const processArgs = [ path, '-Xclang', '-ast-dump=json', `-I${includeBase(path)}` ]; + + if (options?.deprecateHard) { + processArgs.push(`-DGIT_DEPRECATE_HARD`); + } + + if (options?.includeFiles) { + for (const file of options.includeFiles) { + processArgs.push(`-include`); + processArgs.push(file) + } + } + + const process = child_process.spawn('clang', processArgs); + + process.stderr.on('data', (message) => { + errorMessage += message; + }); + process.stdout.on('data', (chunk) => { + chunks.push(chunk); + }); + process.on('close', (code) => { + if (code != 0 && options.strict) { + reject(new Error(`clang exit code ${code}: ${errorMessage}`)); + } + else if (code != 0) { + resolve([ ]); + } + else { + const ast = JSON.parse(Buffer.concat(chunks).toString()); + resolve(parseFileAst(path, ast)); + } + }); + process.on('error', function (err) { + reject(err); + }); + }); +} + +async function readFile(path) { + const buf = await fs.readFile(path); + return buf.toString(); +} + +function ensure(message, test) { + if (!test) { + throw new Error(message); + } +} + +function ensureDefined(name, value) { + if (!value) { + throw new Error(`could not find ${name} for declaration`); + } + + return value; +} + +function groupifyId(location, id) { + if (!id) { + throw new Error(`could not find id in declaration`); + } + + if (!location || !location.file) { + throw new Error(`unspecified location`); + } + + return `${location.file}-${id}`; +} + +function blockCommentText(block) { + ensure('block does not have a single paragraph element', block.inner.length === 1 && block.inner[0].kind === 'ParagraphComment'); + return commentText(block.inner[0]); +} + +function richBlockCommentText(block) { + ensure('block does not have a single paragraph element', block.inner.length === 1 && block.inner[0].kind === 'ParagraphComment'); + return richCommentText(block.inner[0]); +} + +function paramCommentText(param) { + ensure('param does not have a single paragraph element', param.inner.length === 1 && param.inner[0].kind === 'ParagraphComment'); + return richCommentText(param.inner[0]); +} + +function appendCommentText(chunk) { + return chunk.startsWith(' ') ? "\n" + chunk : chunk; +} + +function commentText(para) { + let text = ''; + + for (const comment of para.inner) { + // docbook allows backslash escaped text, and reports it differently. + // we restore the literal `\`. + if (comment.kind === 'InlineCommandComment') { + text += `\\${comment.name}`; + } + else if (comment.kind === 'TextComment') { + text += text ? "\n" + comment.text : comment.text; + } else { + throw new Error(`unknown paragraph comment element: ${comment.kind}`); + } + } + + return text.trim(); +} + +function nextText(para, idx) { + if (!para.inner[idx + 1] || para.inner[idx + 1].kind !== 'TextComment') { + throw new Error("expected text comment"); + } + + return para.inner[idx + 1].text; +} + +function inlineCommandData(data, command) { + ensure(`${command} information does not follow @${command}`, data?.kind === 'TextComment'); + + const result = data.text.match(/^(?:\[([^\]]+)\])? ((?:[a-zA-Z0-9\_]+)|`[a-zA-Z0-9\_\* ]+`)(.*)/); + ensure(`${command} data does not follow @${command}`, result); + + const [ , attr, spec, remain ] = result; + return [ attr, spec.replace(/^`(.*)`$/, "$1"), remain ] +} + +function richCommentText(para) { + let text = ''; + let extendedType = undefined; + let subkind = undefined; + let versionMacro = undefined; + let initMacro = undefined; + let initFunction = undefined; + let lastComment = undefined; + + for (let i = 0; i < para.inner?.length; i++) { + const comment = para.inner[i]; + + if (comment.kind === 'InlineCommandComment' && + comment.name === 'type') { + const [ attr, data, remain ] = inlineCommandData(para.inner[++i], "type"); + + extendedType = { kind: attr, type: data }; + text += remain; + } + else if (comment.kind === 'InlineCommandComment' && + comment.name === 'flags') { + subkind = 'flags'; + } + else if (comment.kind === 'InlineCommandComment' && + comment.name === 'options') { + const [ attr, data, remain ] = inlineCommandData(para.inner[++i], "options"); + + if (attr === 'version') { + versionMacro = data; + } + else if (attr === 'init_macro') { + initMacro = data; + } + else if (attr === 'init_function') { + initFunction = data; + } + + subkind = 'options'; + text += remain; + } + // docbook allows backslash escaped text, and reports it differently. + // we restore the literal `\`. + else if (comment.kind === 'InlineCommandComment') { + text += `\\${comment.name}`; + } + else if (comment.kind === 'TextComment') { + // clang oddity: it breaks into two + // comment blocks, assuming that the trailing > should be a + // blockquote newline sort of thing. unbreak them. + if (comment.text.startsWith('>') && + lastComment && + lastComment.loc.offset + lastComment.text.length === comment.loc.offset) { + + text += comment.text; + } else { + text += text ? "\n" + comment.text : comment.text; + } + } + else if (comment.kind === 'HTMLStartTagComment' && comment.name === 'p') { + text += "\n"; + } + else { + throw new Error(`unknown paragraph comment element: ${comment.kind}`); + } + + lastComment = comment; + } + + return { + text: text.trim(), + extendedType: extendedType, + subkind: subkind, + versionMacro: versionMacro, + initMacro: initMacro, + initFunction: initFunction + } +} + +function join(arr, elem) { + if (arr) { + return [ ...arr, elem ]; + } + + return [ elem ]; +} + +function joinIfNotEmpty(arr, elem) { + if (!elem || elem === '') { + return arr; + } + + if (arr) { + return [ ...arr, elem ]; + } + + return [ elem ]; +} + +function pushIfNotEmpty(arr, elem) { + if (elem && elem !== '') { + arr.push(elem); + } +} + +function single(arr, fn, message) { + let result = undefined; + + if (!arr) { + return undefined; + } + + for (const match of arr.filter(fn)) { + if (result) { + throw new Error(`multiple matches in array for ${fn}${message ? ' (' + message + ')': ''}`); + } + + result = match; + } + + return result; +} + +function updateLocation(location, decl) { + location.file = trimBase(decl.loc?.spellingLoc?.file || decl.loc?.file) || location.file; + location.line = decl.loc?.spellingLoc?.line || decl.loc?.line || location.line; + location.column = decl.loc?.spellingLoc?.col || decl.loc?.col || location.column; + + return location; +} + +async function readFileLocation(startLocation, endLocation) { + if (startLocation.file != endLocation.file) { + throw new Error("cannot read across files"); + } + + const data = await fs.readFile(startLocation.file, "utf8"); + const lines = data.split(/\r?\n/).slice(startLocation.line - 1, endLocation.line); + + lines[lines.length - 1] = lines[lines.length - 1].slice(0, endLocation.column); + lines[0] = lines[0].slice(startLocation.column - 1); + + return lines +} + +function formatLines(lines) { + let result = ""; + let continuation = false; + + for (const i in lines) { + if (!continuation) { + lines[i] = lines[i].trimStart(); + } + + continuation = lines[i].endsWith("\\"); + + if (continuation) { + lines[i] = lines[i].slice(0, -1); + } else { + lines[i] = lines[i].trimEnd(); + } + + result += lines[i]; + } + + if (continuation) { + throw new Error("unterminated literal continuation"); + } + + return result; +} + +async function parseExternalRange(location, range) { + const startLocation = {...location}; + startLocation.file = trimBase(range.begin.spellingLoc.file || startLocation.file); + startLocation.line = range.begin.spellingLoc.line || startLocation.line; + startLocation.column = range.begin.spellingLoc.col || startLocation.column; + + const endLocation = {...startLocation}; + endLocation.file = trimBase(range.end.spellingLoc.file || endLocation.file); + endLocation.line = range.end.spellingLoc.line || endLocation.line; + endLocation.column = range.end.spellingLoc.col || endLocation.column; + + const lines = await readFileLocation(startLocation, endLocation); + + return formatLines(lines); +} + +async function parseLiteralRange(location, range) { + const startLocation = updateLocation({...location}, { loc: range.begin }); + const endLocation = updateLocation({...location}, { loc: range.end }); + + const lines = await readFileLocation(startLocation, endLocation); + + return formatLines(lines); +} + +async function parseRange(location, range) { + return range.begin.spellingLoc ? parseExternalRange(location, range) : parseLiteralRange(location, range); +} + +class ParserError extends Error { + constructor(message, location) { + if (!location) { + super(`${message} at (unknown)`); + } + else { + super(`${message} at ${location.file}:${location.line}`); + } + this.name = 'ParserError'; + } +} + +function validateParsing(test, message, location) { + if (!test) { + throw new ParserError(message, location); + } +} + +function parseComment(spec, location, comment, options) { + let result = { }; + let last = undefined; + + for (const c of comment.inner.filter(c => c.kind === 'ParagraphComment' || c.kind === 'VerbatimLineComment')) { + if (c.kind === 'ParagraphComment') { + const commentData = richCommentText(c); + + result.comment = joinIfNotEmpty(result.comment, commentData.text); + delete commentData.text; + + result = { ...result, ...commentData }; + } + else if (c.kind === 'VerbatimLineComment') { + result.comment = joinIfNotEmpty(result.comment, c.text.trim()); + } + else { + throw new Error(`unknown comment ${c.kind}`); + } + } + + for (const c of comment.inner.filter(c => c.kind !== 'ParagraphComment' && c.kind !== 'VerbatimLineComment')) { + if (c.kind === 'BlockCommandComment' && c.name === 'see') { + result.see = joinIfNotEmpty(result.see, blockCommentText(c)); + } + else if (c.kind === 'BlockCommandComment' && c.name === 'note') { + result.notes = joinIfNotEmpty(result.notes, blockCommentText(c)); + } + else if (c.kind === 'BlockCommandComment' && c.name === 'deprecated') { + result.deprecations = joinIfNotEmpty(result.deprecations, blockCommentText(c)); + } + else if (c.kind === 'BlockCommandComment' && c.name === 'warning') { + result.warnings = joinIfNotEmpty(result.warnings, blockCommentText(c)); + } + else if (c.kind === 'BlockCommandComment' && + (c.name === 'return' || (c.name === 'returns' && !options.strict))) { + const returnData = richBlockCommentText(c); + + result.returns = { + extendedType: returnData.extendedType, + comment: returnData.text + }; + } + else if (c.kind === 'ParamCommandComment') { + ensure('param has a name', c.param); + + const paramDetails = paramCommentText(c); + + result.params = join(result.params, { + name: c.param, + direction: c.direction, + values: paramDetails.type, + extendedType: paramDetails.extendedType, + comment: paramDetails.text + }); + } + else if (options.strict) { + if (c.kind === 'BlockCommandComment') { + throw new ParserError(`unknown block command comment ${c.name}`, location); + } + else if (c.kind === 'VerbatimBlockComment') { + throw new Error(`unknown verbatim command comment ${c.name}`, location); + } + else { + throw new Error(`unknown comment ${c.kind} in ${kind}`); + } + } + } + + return result; +} + +async function parseFunction(location, decl, options) { + let result = { + kind: 'function', + id: groupifyId(location, decl.id), + name: ensureDefined('name', decl.name), + location: {...location} + }; + + // prototype + const [ , returnType, ] = decl.type.qualType.match(/(.*?)(?: )?\((.*)\)$/) || [ ]; + ensureDefined('return type declaration', returnType); + result.returns = { type: returnType }; + + for (const paramDecl of decl.inner.filter(attr => attr.kind === 'ParmVarDecl')) { + updateLocation(location, paramDecl); + + const inner = paramDecl.inner || []; + const innerLocation = {...location}; + let paramAnnotations = undefined; + + for (const annotateDecl of inner.filter(attr => attr.kind === 'AnnotateAttr')) { + updateLocation(innerLocation, annotateDecl); + + paramAnnotations = join(paramAnnotations, await parseRange(innerLocation, annotateDecl.range)); + } + + result.params = join(result.params, { + name: paramDecl.name, + type: paramDecl.type.qualType, + annotations: paramAnnotations + }); + } + + // doc comment + const commentText = single(decl.inner, (attr => attr.kind === 'FullComment')); + + if (commentText) { + const commentData = parseComment(`function:${decl.name}`, location, commentText, options); + + if (result.params) { + if (options.strict && (!commentData.params || result.params.length > commentData.params.length)) { + throw new ParserError(`not all params are documented`, location); + } + + if (options.strict && result.params.length < commentData.params.length) { + throw new ParserError(`additional params are documented`, location); + } + } + + if (commentData.params) { + for (const i in result.params) { + let match; + + for (const j in commentData.params) { + if (result.params[i].name === commentData.params[j].name) { + match = j; + break; + } + } + + if (options.strict && (!match || match != i)) { + throw new ParserError( + `param documentation does not match param name '${result.params[i].name}'`, + location); + } + + if (match) { + result.params[i] = { ...result.params[i], ...commentData.params[match] }; + } + } + } else if (options.strict && result.params) { + throw new ParserError(`no params documented for ${decl.name}`, location); + } + + if (options.strict && !commentData.returns && result.returns.type != 'void') { + throw new ParserError(`return information is not documented for ${decl.name}`, location); + } + + result.returns = { ...result.returns, ...commentData.returns }; + + delete commentData.params; + delete commentData.returns; + + result = { ...result, ...commentData }; + } + else if (options.strict) { + throw new ParserError(`no documentation for function ${decl.name}`, location); + } + + return result; +} + +function parseEnum(location, decl, options) { + let result = { + kind: 'enum', + id: groupifyId(location, decl.id), + name: decl.name, + referenceName: decl.name ? `enum ${decl.name}` : undefined, + members: [ ], + comment: undefined, + location: {...location} + }; + + for (const member of decl.inner.filter(attr => attr.kind === 'EnumConstantDecl')) { + ensure('enum constant has a name', member.name); + + const explicitValue = single(member.inner, (attr => attr.kind === 'ConstantExpr')); + const implicitValue = single(member.inner, (attr => attr.kind === 'ImplicitCastExpr')); + const commentText = single(member.inner, (attr => attr.kind === 'FullComment')); + const commentData = commentText ? parseComment(`enum:${decl.name}:member:${member.name}`, location, commentText, options) : undefined; + + let value = undefined; + + if (explicitValue && explicitValue.value) { + value = explicitValue.value; + } else if (implicitValue) { + const innerExplicit = single(implicitValue.inner, (attr => attr.kind === 'ConstantExpr')); + + value = innerExplicit?.value; + } + + result.members.push({ + name: member.name, + value: value, + ...commentData + }); + } + + const commentText = single(decl.inner, (attr => attr.kind === 'FullComment')); + + if (commentText) { + result = { ...result, ...parseComment(`enum:${decl.name}`, location, commentText, options) }; + } + + return result; +} + +function resolveFunctionPointerTypedef(location, typedef) { + const signature = typedef.type.match(/^((?:const )?[^\s]+(?:\s+\*+)?)\s*\(\*\)\((.*)\)$/); + const [ , returnType, paramData ] = signature; + const params = paramData.split(/,\s+/); + + if (options.strict && (!typedef.params || params.length != typedef.params.length)) { + throw new ParserError(`not all params are documented for function pointer typedef ${typedef.name}`, typedef.location); + } + + if (!typedef.params) { + typedef.params = [ ]; + } + + for (const i in params) { + if (!typedef.params[i]) { + typedef.params[i] = { }; + } + + typedef.params[i].type = params[i]; + } + + if (typedef.returns === undefined && returnType === 'void') { + typedef.returns = { type: 'void' }; + } + else if (typedef.returns !== undefined) { + typedef.returns.type = returnType; + } + else if (options.strict) { + throw new ParserError(`return type is not documented for function pointer typedef ${typedef.name}`, typedef.location); + } +} + +function parseTypedef(location, decl, options) { + updateLocation(location, decl); + + let result = { + kind: 'typedef', + id: groupifyId(location, decl.id), + name: ensureDefined('name', decl.name), + type: ensureDefined('type.qualType', decl.type.qualType), + targetId: undefined, + comment: undefined, + location: {...location} + }; + + const elaborated = single(decl.inner, (attr => attr.kind === 'ElaboratedType')); + if (elaborated !== undefined && elaborated.ownedTagDecl?.id) { + result.targetId = groupifyId(location, elaborated.ownedTagDecl?.id); + } + + const commentText = single(decl.inner, (attr => attr.kind === 'FullComment')); + + if (commentText) { + const commentData = parseComment(`typedef:${decl.name}`, location, commentText, options); + result = { ...result, ...commentData }; + } + + if (isFunctionPointer(result.type)) { + resolveFunctionPointerTypedef(location, result); + } + + return result; +} + +function parseStruct(location, decl, options) { + let result = { + kind: 'struct', + id: groupifyId(location, decl.id), + name: decl.name, + referenceName: decl.name ? `struct ${decl.name}` : undefined, + comment: undefined, + members: [ ], + location: {...location} + }; + + for (const member of decl.inner.filter(attr => attr.kind === 'FieldDecl')) { + let memberData = { + 'name': member.name, + 'type': member.type.qualType + }; + + const commentText = single(member.inner, (attr => attr.kind === 'FullComment')); + + if (commentText) { + memberData = {...memberData, ...parseComment(`struct:${decl.name}:member:${member.name}`, location, commentText, options)}; + } + + result.members.push(memberData); + } + + const commentText = single(decl.inner, (attr => attr.kind === 'FullComment')); + + if (commentText) { + const commentData = parseComment(`struct:${decl.name}`, location, commentText, options); + result = { ...result, ...commentData }; + } + + return result; +} + +function newResults() { + return { + all: [ ], + functions: [ ], + enums: [ ], + typedefs: [ ], + structs: [ ], + macros: [ ] + }; +}; + +const returnMap = { }; +const paramMap = { }; + +function simplifyType(givenType) { + let type = givenType; + + if (type.startsWith('const ')) { + type = type.substring(6); + } + + while (type.endsWith('*') && type !== 'void *' && type !== 'char *') { + type = type.substring(0, type.length - 1).trim(); + } + + if (!type.length) { + throw new Error(`invalid type: ${result.returns.extendedType || result.returns.type}`); + } + + return type; +} + +function createAndPush(arr, name, value) { + if (!arr[name]) { + arr[name] = [ ]; + } + + if (arr[name].length && arr[name][arr[name].length - 1] === value) { + return; + } + + arr[name].push(value); +} + +function addReturn(result) { + if (!result.returns) { + return; + } + + let type = simplifyType(result.returns.extendedType?.type || result.returns.type); + + createAndPush(returnMap, type, result.name); +} + +function addParameters(result) { + if (!result.params) { + return; + } + + for (const param of result.params) { + let type = param.extendedType?.type || param.type; + + if (!type && options.strict) { + throw new Error(`parameter ${result.name} erroneously documented when not specified`); + } else if (!type) { + continue; + } + + type = simplifyType(type); + + if (param.direction === 'out') { + createAndPush(returnMap, type, result.name); + } + else { + createAndPush(paramMap, type, result.name); + } + } +} + +function addResult(results, result) { + results[`${result.kind}s`].push(result); + results.all.push(result); + + addReturn(result); + addParameters(result); +} + +function mergeResults(one, two) { + const results = newResults(); + + for (const inst of Object.keys(results)) { + results[inst].push(...one[inst]); + results[inst].push(...two[inst]); + } + + return results; +} + +function getById(results, id) { + ensure("id is set", id !== undefined); + return single(results.all.all, (item => item.id === id), id); +} + +function getByKindAndName(results, kind, name) { + ensure("kind is set", kind !== undefined); + ensure("name is set", name !== undefined); + return single(results.all[`${kind}s`], (item => item.name === name), name); +} + +function getByName(results, name) { + ensure("name is set", name !== undefined); + return single(results.all.all, (item => item.name === name), name); +} + +function isFunctionPointer(type) { + return type.match(/^(?:const )?[A-Za-z0-9_]+\s+\**\(\*/); +} + +function resolveCallbacks(results) { + // expand callback types + for (const fn of results.all.functions) { + for (const param of fn.params || [ ]) { + const typedef = getByName(results, param.type); + + if (typedef === undefined) { + continue; + } + + param.referenceType = typedef.type; + } + } + + for (const struct of results.all.structs) { + for (const member of struct.members) { + const typedef = getByKindAndName(results, 'typedef', member.type); + + if (typedef === undefined) { + continue; + } + + member.referenceType = typedef.type; + } + } +} + +function trimBase(path) { + if (!path) { + return path; + } + + for (const segment of [ 'git2', 'git' ]) { + const base = [ includeBase(path), segment ].join('/'); + + if (path.startsWith(base + '/')) { + return path.substr(base.length + 1); + } + } + + throw new Error(`header path ${path} is not beneath standard root`); +} + +function resolveTypedefs(results) { + for (const typedef of results.all.typedefs) { + let target = typedef.targetId ? getById(results, typedef.targetId) : undefined; + + if (target) { + // update the target's preferred name with the short name + target.referenceName = typedef.name; + + if (target.name === undefined) { + target.name = typedef.name; + } + } + else if (typedef.type.startsWith('struct ')) { + const path = typedef.location.file; + + /* + * See if this is actually a typedef to a declared struct, + * then it is not actually opaque. + */ + if (results.all.structs.filter(fn => fn.name === typedef.name).length > 0) { + typedef.opaque = false; + continue; + } + + opaque = { + kind: 'struct', + id: groupifyId(typedef.location, typedef.id), + name: typedef.name, + referenceName: typedef.type, + opaque: true, + comment: typedef.comment, + location: typedef.location, + group: typedef.group + }; + + addResult(results.files[path], opaque); + addResult(results.all, opaque); + } + else if (isFunctionPointer(typedef.type) || + typedef.type === 'int64_t' || + typedef.type === 'uint64_t') { + // standard types + // TODO : make these a list + } + else { + typedef.kind = 'alias'; + typedef.typedef = true; + } + } +} + +function lastCommentIsGroupDelimiter(decls) { + if (decls[decls.length - 1].inner && + decls[decls.length - 1].inner.length > 0) { + return lastCommentIsGroupDelimiter(decls[decls.length - 1].inner); + } + + if (decls.length >= 2 && + decls[decls.length - 1].kind.endsWith('Comment') && + decls[decls.length - 2].kind.endsWith('Comment') && + decls[decls.length - 2].text === '@' && + decls[decls.length - 1].text === '{') { + return true; + } + + return false; +} + +async function parseAst(decls, options) { + const location = { + file: undefined, + line: undefined, + column: undefined + }; + + const results = newResults(); + + /* The first decl might have picked up the javadoc _for the file + * itself_ based on the file's structure. Remove it. + */ + if (decls.length && decls[0].inner && + decls[0].inner.length > 0 && + decls[0].inner[0].kind === 'FullComment' && + lastCommentIsGroupDelimiter(decls[0].inner[0].inner)) { + updateLocation(location, decls[0]); + delete decls[0].inner[0]; + } + + for (const decl of decls) { + updateLocation(location, decl); + + ensureDefined('kind', decl.kind); + + if (decl.kind === 'FunctionDecl') { + addResult(results, await parseFunction({...location}, decl, options)); + } + else if (decl.kind === 'EnumDecl') { + addResult(results, parseEnum({...location}, decl, options)); + } + else if (decl.kind === 'TypedefDecl') { + addResult(results, parseTypedef({...location}, decl, options)); + } + else if (decl.kind === 'RecordDecl' && decl.tagUsed === 'struct') { + if (decl.completeDefinition) { + addResult(results, parseStruct({...location}, decl, options)); + } + } + else if (decl.kind === 'VarDecl') { + if (options.strict) { + throw new Error(`unsupported variable declaration ${decl.kind}`); + } + } + else { + throw new Error(`unknown declaration type ${decl.kind}`); + } + } + + return results; +} + +function parseCommentForMacro(lines, macroIdx, name) { + let startIdx = -1, endIdx = 0; + const commentLines = [ ]; + + while (macroIdx > 0 && + (line = lines[macroIdx - 1].trim()) && + (line.trim() === '' || + line.trim().endsWith('\\') || + line.trim().match(/^#\s*if\s+/) || + line.trim().startsWith('#ifdef ') || + line.trim().startsWith('#ifndef ') || + line.trim().startsWith('#elif ') || + line.trim().startsWith('#else ') || + line.trim().match(/^#\s*define\s+${name}\s+/))) { + macroIdx--; + } + + if (macroIdx > 0 && lines[macroIdx - 1].trim().endsWith('*/')) { + endIdx = macroIdx - 1; + } else { + return ''; + } + + for (let i = endIdx; i >= 0; i--) { + if (lines[i].trim().startsWith('/**')) { + startIdx = i; + break; + } + else if (lines[i].trim().startsWith('/*')) { + break; + } + } + + if (startIdx < 0) { + return ''; + } + + for (let i = startIdx; i <= endIdx; i++) { + let line = lines[i].trim(); + + if (i == startIdx) { + line = line.replace(/^\s*\/\*\*\s*/, ''); + } + + if (i === endIdx) { + line = line.replace(/\s*\*\/\s*$/, ''); + } + + if (i != startIdx) { + line = line.replace(/^\s*\*\s*/, ''); + } + + if (i == startIdx && (line === '@{' || line.startsWith("@{ "))) { + return ''; + } + + if (line === '') { + continue; + } + + commentLines.push(line); + } + + return commentLines.join(' '); +} + +async function parseInfo(data) { + const fileHeader = data.match(/(.*)\n+GIT_BEGIN_DECL.*/s); + const headerLines = fileHeader ? fileHeader[1].split(/\n/) : [ ]; + + let lines = [ ]; + const detailsLines = [ ]; + + let summary = undefined; + let endIdx = headerLines.length - 1; + + for (let i = headerLines.length - 1; i >= 0; i--) { + let line = headerLines[i].trim(); + + if (line.match(/^\s*\*\/\s*$/)) { + endIdx = i; + } + + if (line.match(/^\/\*\*(\s+.*)?$/)) { + lines = headerLines.slice(i + 1, endIdx); + break; + } + else if (line.match(/^\/\*(\s+.*)?$/)) { + break; + } + } + + for (let line of lines) { + line = line.replace(/^\s\*/, ''); + line = line.trim(); + + const comment = line.match(/^\@(\w+|{)\s*(.*)/); + + if (comment) { + if (comment[1] === 'brief') { + summary = comment[2]; + } + } + else if (line != '') { + detailsLines.push(line); + } + } + + const details = detailsLines.length > 0 ? detailsLines.join("\n") : undefined; + + return { + 'summary': summary, + 'details': details + }; +} + +async function parseMacros(path, data, options) { + const results = newResults(); + const lines = data.split(/\r?\n/); + + const macros = { }; + + for (let i = 0; i < lines.length; i++) { + const macro = lines[i].match(/^(\s*#\s*define\s+)([^\s\(]+)(\([^\)]+\))?\s*(.*)/); + let more = false; + + if (!macro) { + continue; + } + + let [ , prefix, name, args, value ] = macro; + + if (name.startsWith('INCLUDE_') || name.startsWith('_INCLUDE_')) { + continue; + } + + if (args) { + name = name + args; + } + + if (macros[name]) { + continue; + } + + macros[name] = true; + + value = value.trim(); + + if (value.endsWith('\\')) { + value = value.substring(0, value.length - 1).trim(); + more = true; + } + + while (more) { + more = false; + + let line = lines[++i]; + + if (line.endsWith('\\')) { + line = line.substring(0, line.length - 1); + more = true; + } + + value += ' ' + line.trim(); + } + + const comment = parseCommentForMacro(lines, i, name); + const location = { + file: path, + line: i + 1, + column: prefix.length + 1, + }; + + if (options.strict && !comment) { + throw new ParserError(`no comment for ${name}`, location); + } + + addResult(results, { + kind: 'macro', + name: name, + location: location, + value: value, + comment: comment, + }); + } + + return results; +} + +function resolveUngroupedTypes(results) { + const groups = { }; + + for (const result of results.all.all) { + result.group = result.location.file; + + if (result.group.endsWith('.h')) { + result.group = result.group.substring(0, result.group.length - 2); + groups[result.group] = true; + } + } + + for (const result of results.all.all) { + if (result.location.file === 'types.h' && + result.name.startsWith('git_')) { + let possibleGroup = result.name.substring(4); + + do { + if (groupMap[possibleGroup]) { + result.group = groupMap[possibleGroup]; + break; + } + else if (groups[possibleGroup]) { + result.group = possibleGroup; + break; + } + else if (groups[`sys/${possibleGroup}`]) { + result.group = `sys/${possibleGroup}`; + break; + } + + let match = possibleGroup.match(/^(.*)_[^_]+$/); + + if (!match) { + break; + } + + possibleGroup = match[1]; + } while (true); + } + } +} + +function resolveReturns(results) { + for (const result of results.all.all) { + result.returnedBy = returnMap[result.name]; + } +} + +function resolveParameters(results) { + for (const result of results.all.all) { + result.parameterTo = paramMap[result.name]; + } +} + +async function parseHeaders(sourcePath, options) { + const results = { all: newResults(), files: { } }; + + for (const fullPath of await headerPaths(sourcePath)) { + const path = trimPath(sourcePath, fullPath); + const fileContents = await readFile(fullPath); + + const ast = await parseAst(await readAst(fullPath, options), options); + const macros = await parseMacros(path, fileContents, options); + const info = await parseInfo(fileContents); + + const filedata = mergeResults(ast, macros); + + filedata['info'] = info; + + results.files[path] = filedata; + results.all = mergeResults(results.all, filedata); + } + + resolveCallbacks(results); + resolveTypedefs(results); + + resolveUngroupedTypes(results); + + resolveReturns(results); + resolveParameters(results); + + return results; +} + +function isFunctionPointer(type) { + return type.match(/^(const\s+)?[A-Za-z0-9_]+\s+\*?\(\*/); +} +function isEnum(type) { + return type.match(/^enum\s+/); +} +function isStruct(type) { + return type.match(/^struct\s+/); +} + +/* + * We keep the `all` arrays around so that we can lookup; drop them + * for the end result. + */ +function simplify(results) { + const simplified = { + 'info': { }, + 'groups': { } + }; + + results.all.all.sort((a, b) => { + if (!a.group) { + throw new Error(`missing group for api ${a.name}`); + } + + if (!b.group) { + throw new Error(`missing group for api ${b.name}`); + } + + const aSystem = a.group.startsWith('sys/'); + const aName = aSystem ? a.group.substr(4) : a.group; + + const bSystem = b.group.startsWith('sys/'); + const bName = bSystem ? b.group.substr(4) : b.group; + + if (aName !== bName) { + return aName.localeCompare(bName); + } + + if (aSystem !== bSystem) { + return aSystem ? 1 : -1; + } + + if (a.location.file !== b.location.file) { + return a.location.file.localeCompare(b.location.file); + } + + if (a.location.line !== b.location.line) { + return a.location.line - b.location.line; + } + + return a.location.column - b.location.column; + }); + + for (const api of results.all.all) { + delete api.id; + delete api.targetId; + + const type = api.referenceType || api.type; + + if (api.kind === 'typedef' && isFunctionPointer(type)) { + api.kind = 'callback'; + api.typedef = true; + } + else if (api.kind === 'typedef' && (!isEnum(type) && !isStruct(type))) { + api.kind = 'alias'; + api.typedef = true; + } + else if (api.kind === 'typedef') { + continue; + } + + if (apiIgnoreList.includes(api.name)) { + continue; + } + + // TODO: do a warning where there's a redefinition of a symbol + // There are occasions where we redefine a symbol. First, our + // parser is not smart enough to know #ifdef's around #define's. + // But also we declared `git_email_create_from_diff` twice (in + // email.h and sys/email.h) for several releases. + + if (!simplified['groups'][api.group]) { + simplified['groups'][api.group] = { }; + simplified['groups'][api.group].apis = { }; + simplified['groups'][api.group].info = results.files[`${api.group}.h`].info; + } + + simplified['groups'][api.group].apis[api.name] = api; + } + + return simplified; +} + +function joinArguments(next, previous) { + if (previous) { + return [...previous, next]; + } + return [next]; +} + +async function findIncludes() { + const includes = [ ]; + + for (const possible of defaultIncludes) { + const includeFile = `${docsPath}/include/git2/${possible}`; + + try { + await fs.stat(includeFile); + includes.push(`git2/${possible}`); + } + catch (e) { + if (e?.code !== 'ENOENT') { + throw e; + } + } + } + + return includes; +} + +async function execGit(path, command) { + const process = child_process.spawn('git', command, { cwd: path }); + const chunks = [ ]; + + return new Promise((resolve, reject) => { + process.stdout.on('data', (chunk) => { + chunks.push(chunk); + }); + process.on('close', (code) => { + resolve(code == 0 ? Buffer.concat(chunks).toString() : undefined); + }); + process.on('error', function (err) { + reject(err); + }); + }); +} + +async function readMetadata(path) { + let commit = await execGit(path, [ 'rev-parse', 'HEAD' ]); + + if (commit) { + commit = commit.trimEnd(); + } + + let version = await execGit(path, [ 'describe', '--tags', '--exact' ]); + + if (!version) { + const ref = await execGit(path, [ 'describe', '--all', '--exact' ]); + + if (ref && ref.startsWith('heads/')) { + version = ref.substr(6); + } + } + + if (version) { + version = version.trimEnd(); + } + + return { + 'version': version, + 'commit': commit + }; +} + +program.option('--output ') + .option('--include ', undefined, joinArguments) + .option('--no-includes') + .option('--deprecate-hard') + .option('--validate-only') + .option('--strict'); +program.parse(); + +const options = program.opts(); + +if (program.args.length != 1) { + console.error(`usage: ${path.basename(process.argv[1])} docs`); + process.exit(1); +} + +const docsPath = program.args[0]; + +if (options['include'] && !options['includes']) { + console.error(`usage: cannot combined --include with --no-include`); + process.exit(1); +} + +(async () => { + try { + if (options['include']) { + includes = options['include']; + } + else if (!options['includes']) { + includes = [ ]; + } + else { + includes = await findIncludes(); + } + + const parseOptions = { + deprecateHard: options.deprecateHard || false, + includeFiles: includes, + strict: options.strict || false + }; + + const results = await parseHeaders(docsPath, parseOptions); + const metadata = await readMetadata(docsPath); + + const simplified = simplify(results); + simplified['info'] = metadata; + + if (!options.validateOnly) { + console.log(JSON.stringify(simplified, null, 2)); + } + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/script/api-docs/docs-generator.js b/script/api-docs/docs-generator.js new file mode 100755 index 00000000..dcf25e57 --- /dev/null +++ b/script/api-docs/docs-generator.js @@ -0,0 +1,1417 @@ +#!/usr/bin/env node + +const markdownit = require('markdown-it'); +const { program } = require('commander'); + +const path = require('node:path'); +const fs = require('node:fs/promises'); +const process = require('node:process'); + +const githubPath = 'https://github.com/libgit2/libgit2'; + +const linkPrefix = '/docs/reference'; + +const projectTitle = 'libgit2'; +const includePath = 'include/git2'; + +const fileDenylist = [ 'stdint.h' ]; +const showVersions = true; + +const defaultBranch = 'main'; + +const markdown = markdownit(); +const markdownDefaults = { + code_inline: markdown.renderer.rules.code_inline +}; +markdown.renderer.rules.code_inline = (tokens, idx, options, env, self) => { + const version = env.__version || defaultBranch; + + const code = tokens[idx].content; + const text = `${nowrap(sanitize(tokens[idx].content))}`; + const link = linkForCode(version, code, text); + + return link ? link : text; +}; + +// globals +const apiData = { }; +const versions = [ ]; +const versionDeltas = { }; + +function produceVersionPicker(version, classes, cb) { + let content = ""; + + if (!showVersions) { + return content; + } + + content += `
\n`; + content += ` Version:\n`; + content += ` \n`; + + content += `
\n`; + + return content; +} + +function produceBreadcrumb(version, api, type) { + let content = ""; + let group = api.group; + let sys = false; + + if (group.endsWith('.h')) { + group = group.substr(0, group.length - 2); + } + + let groupTitle = group; + + if (groupTitle.startsWith('sys/')) { + groupTitle = groupTitle.substr(4); + groupTitle += ' (advanced)'; + } + + content += `
\n`; + content += ` \n`; + content += `
\n`; + + return content; +} + +function produceHeader(version, api, type) { + let content = ""; + + content += `
\n`; + content += `

${api.name}

\n`; + + content += produceAttributes(version, api, type); + content += produceSearchArea(version, type); + + content += produceVersionPicker(version, + `apiHeaderVersionSelect ${type}HeaderVersionSelect`, + (v) => { + const versionedApi = selectApi(v, (i => i.name === api.name)); + return versionedApi ? linkFor(v, versionedApi) : undefined; + }); + + content += `
\n`; + content += `\n`; + + return content; +} + +function produceAttributes(version, api, type) { + let content = ""; + + if (api.deprecations) { + content += ` Deprecated\n`; + } + + return content; +} + +function produceDescription(version, desc, type) { + let content = ""; + + if (! desc.comment) { + return content; + } + + content += `\n`; + content += `
\n`; + + for (const para of Array.isArray(desc.comment) ? desc.comment : [ desc.comment ]) { + content += ` ${markdown.render(para, { __version: version })}\n`; + } + + content += `
\n`; + + return content; +} + +function produceList(version, api, type, listType) { + let content = ""; + + if (!api[listType]) { + return content; + } + + const listTypeUpper = listType.charAt(0).toUpperCase() + listType.slice(1); + const listTypeTitle = listTypeUpper.replaceAll(/(.)([A-Z])/g, (match, one, two) => { return one + ' ' + two; }); + + content += `\n`; + content += `

${listTypeTitle}

\n`; + + content += `
\n`; + content += `
    \n`; + + for (const item of api[listType]) { + content += `
  • \n`; + content += ` ${linkText(version, item)}\n`; + content += `
  • \n`; + } + + content += `
\n`; + content += `
\n`; + + return content; +} + +function produceNotes(version, api, type) { + return produceList(version, api, type, 'notes'); +} + +function produceSeeAlso(version, api, type) { + return produceList(version, api, type, 'deprecated'); +} + +function produceSeeAlso(version, api, type) { + return produceList(version, api, type, 'see'); +} + +function produceWarnings(version, api, type) { + return produceList(version, api, type, 'warnings'); +} + +function produceDeprecations(version, api, type) { + return produceList(version, api, type, 'deprecations'); +} + +function produceGitHubLink(version, api, type) { + if (!api || !api.location || !api.location.file) { + return undefined; + } + + let file = api.location.file; + + let link = githubPath + '/blob/' + version + '/' + includePath + '/' + file; + + if (api.location.line) { + link += '#L' + api.location.line; + } + + return link; +} + +function produceSignatureForFunction(version, api, type) { + let content = ""; + let paramCount = 0; + + let prefix = type === 'callback' ? 'typedef' : ''; + const returnType = api.returns?.type || 'int'; + + const githubLink = produceGitHubLink(version, api, type); + + content += `\n`; + + content += `

Signature

\n`; + + if (githubLink) { + content += ` \n`; + } + + content += `
\n`; + + content += ` ${prefix ? prefix + ' ' : ''}${returnType}`; + content += returnType.endsWith('*') ? '' : ' '; + content += `${api.name}(`; + + for (const param of api.params || [ ]) { + content += (paramCount++ > 0) ? ', ' : ''; + + if (!param.type && options.strict) { + throw new Error(`param ${param.name} has no type for function ${api.name}`); + } + else if (!param.type) { + continue; + } + + content += ``; + content += `${param.type}`; + content += param.type.endsWith('*') ? '' : ' '; + + if (param.name) { + content += `${param.name}`; + } + + content += ``; + } + + content += `);\n`; + content += `
\n`; + + return content; +} + +function produceFunctionParameters(version, api, type) { + let content = ""; + + if (!api.params || api.params.length == 0) { + return content; + } + + content += `\n`; + + content += `

Parameters

\n`; + content += `
\n`; + + for (const param of api.params) { + let direction = param.direction || 'in'; + direction = direction.charAt(0).toUpperCase() + direction.slice(1); + + if (!param.type && options.strict) { + throw new Error(`param ${param.name} has no type for function ${api.name}`); + } + else if (!param.type) { + continue; + } + + content += `
\n`; + content += `
\n`; + content += ` ${linkType(version, param.type)}\n`; + content += `
\n`; + + if (param.extendedType) { + content += `
\n`; + content += ` ${linkType(version, param.extendedType.type)}\n`; + content += `
\n`; + } + + content += `
\n`; + + content += ` ${direction}\n`; + content += `
\n`; + + if (param.name) { + content += `
\n`; + content += ` ${param.name}\n`; + content += `
\n`; + } + + content += `
\n`; + content += ` ${render(version, param.comment)}\n`; + content += `
\n`; + content += `
\n`; + } + + content += `
\n`; + + return content; +} + +function produceFunctionReturn(version, api, type) { + let content = ""; + + if (api.returns && api.returns.type && api.returns.type !== 'void') { + content += `\n`; + content += `

Returns

\n`; + content += `
\n`; + content += `
\n`; + content += ` ${linkType(version, api.returns.type)}\n`; + content += `
\n`; + content += `
\n`; + content += ` ${render(version, api.returns.comment)}\n`; + content += `
\n`; + content += `
\n`; + } + + return content; +} + +function produceSignatureForObject(version, api, type) { + let content = ""; + + const githubLink = produceGitHubLink(version, api, type); + + content += `\n`; + + content += `

Signature

\n`; + + if (githubLink) { + content += ` \n`; + } + + content += `
\n`; + content += ` typedef ${api.referenceName} ${api.name}\n`; + content += `
\n`; + + return content; +} + +function produceSignatureForStruct(version, api, type) { + let content = ""; + + const githubLink = produceGitHubLink(version, api, type); + + content += `\n`; + + content += `

Signature

\n`; + + if (githubLink) { + content += ` \n`; + } + + const typedef = api.name.startsWith('struct') ? '' : 'typedef '; + + content += `
\n`; + content += ` ${typedef}struct ${api.name} {\n`; + + for (const member of api.members || [ ]) { + content += ``; + content += `${member.type}`; + content += member.type.endsWith('*') ? '' : ' '; + + if (member.name) { + content += `${member.name}`; + } + + content += `\n`; + } + + content += ` };\n`; + content += `
\n`; + + return content; +} + +function isOctalEnum(version, api, type) { + return api.name === 'git_filemode_t'; +} + +function isFlagsEnum(version, api, type) { + // TODO: also handle the flags metadata instead of always just guessing + if (type !== 'enum') { + return false; + } + + let largest = 0; + + for (const member of api.members) { + if (member.value === undefined) { + return false; + } + + if (member.value && (member.value & (member.value - 1))) { + return false; + } + + largest = member.value; + } + + return (largest > 1); +} + +function flagsOctal(v) { + const n = parseInt(v); + return n ? `0${n.toString(8)}` : 0; +} + +function flagsValue(v) { + if (v === '0') { + return '0'; + } + + return `(1 << ${Math.log2(v)})`; +} + +function produceMembers(version, api, type) { + let content = ""; + let value = 0; + + if (!api.members || api.members.length == 0) { + return ""; + } + + let title = type === 'enum' ? 'Values' : 'Members'; + const isOctal = isOctalEnum(version, api, type); + const isFlags = isFlagsEnum(version, api, type); + + content += `\n`; + + content += `

${title}

\n`; + + const githubLink = api.kind === 'struct' ? undefined : produceGitHubLink(version, api, type); + + if (githubLink) { + content += ` \n`; + } + + content += `
\n`; + + for (const member of api.members) { + value = member.value ? member.value : value; + + content += `
\n`; + + if (type === 'struct') { + content += `
\n`; + content += ` ${linkType(version, member.type)}\n`; + content += `
\n`; + } + + content += `
\n`; + content += ` ${member.name}\n`; + content += `
\n`; + + if (type === 'enum') { + const enumValue = isOctal ? flagsOctal(value) : (isFlags ? flagsValue(value) : value); + + content += `
\n`; + content += ` ${enumValue}\n`; + content += `
\n`; + } + + content += `
\n`; + content += ` ${render(version, member.comment)}\n`; + content += `
\n`; + content += `
\n`; + + value++; + } + + content += `
\n`; + + return content; +} + +function produceReturnedBy(version, api, type) { + return produceList(version, api, type, 'returnedBy'); +} + +function produceParameterTo(version, api, type) { + return produceList(version, api, type, 'parameterTo'); +} + +function produceVersionDeltas(version, api, type) { + let content = ''; + + if (!showVersions) { + return content; + } + + const deltas = versionDeltas[api.name]; + if (!deltas) { + throw new Error(`no version information for ${api.kind} ${api.name}`); + } + + content += `

Versions

\n`; + content += `
\n`; + content += `
    \n`; + + for (const idx in deltas) { + const item = deltas[idx]; + + if (idx == deltas.length - 1) { + content += `
  • \n`; + } else if (item.changed) { + content += `
  • \n`; + } else { + content += `
  • \n`; + } + + content += ` ${item.version}\n`; + content += `
  • \n`; + } + + content += `
\n`; + content += `
\n`; + + return content; +} + +async function layout(data) { + let layout; + + if (options.layout) { + layout = await fs.readFile(options.layout); + } + else if (options.jekyllLayout) { + layout = `---\ntitle: {{title}}\nlayout: ${options.jekyllLayout}\n---\n\n{{content}}`; + } + else { + return data.content; + } + + return layout.toString().replaceAll(/{{([a-z]+)}}/g, (match, p1) => data[p1] || ""); +} + +function produceSearchArea(version, type) { + let content = ""; + + content += `\n`; + content += ` \n`; + content += ` \n`; + + content += `
\n`; + content += ` \n`; + content += `
\n`; + content += `
\n`; + content += `
\n`; + content += `\n`; + + return content; +} + +async function produceDocumentationForApi(version, api, type) { + let content = ""; + + content += `
\n`; + + content += produceBreadcrumb(version, api, type); + content += produceHeader(version, api, type); + content += produceDescription(version, api, type); + content += produceNotes(version, api, type); + content += produceDeprecations(version, api, type); + content += produceSeeAlso(version, api, type); + content += produceWarnings(version, api, type); + content += produceSignature(version, api, type); + content += produceMembers(version, api, type); + content += produceFunctionParameters(version, api, type); + content += produceFunctionReturn(version, api, type); + content += produceReturnedBy(version, api, type); + content += produceParameterTo(version, api, type); + content += produceVersionDeltas(version, api, type); + + content += `
\n`; + + + const name = (type === 'macro' && api.name.includes('(')) ? + api.name.replace(/\(.*/, '') : api.name; + + const groupDir = `${outputPath}/${version}/${api.group}`; + const filename = `${groupDir}/${name}.html`; + + await fs.mkdir(groupDir, { recursive: true }); + await fs.writeFile(filename, await layout({ + title: `${api.name} (${projectTitle} ${version})`, + content: content + })); +} + +function selectApi(version, cb) { + const allApis = allApisForVersion(version, apiData[version]['groups']); + + for (const name in allApis) { + const api = allApis[name]; + + if (cb(api)) { + return api; + } + } + + return undefined; +} + +function apiFor(version, type) { + return selectApi(version, ((api) => api.name === type)); +} + +function linkFor(version, api) { + const name = (api.kind === 'macro' && api.name.includes('(')) ? + api.name.replace(/\(.*/, '') : api.name; + + return `${linkPrefix}/${version}/${api.group}/${name}.html`; +} + +function linkForCode(version, code, text) { + let api = selectApi(version, ((api) => api.name === code)); + let valueDecl = undefined; + + const apisForVersion = allApisForVersion(version, apiData[version]['groups']); + + if (!api) { + for (const enumDecl of Object.values(apisForVersion).filter(api => api.kind === 'enum')) { + const member = enumDecl.members.filter((m) => m.name === code); + + if (member && member[0]) { + api = enumDecl; + valueDecl = member[0]; + break; + } + } + } + + if (!api) { + return undefined; + } + + const kind = internalKind(version, api); + let link = linkFor(version, api); + + if (valueDecl) { + link += `#${valueDecl.name}`; + } + + if (!text) { + text = `${sanitize(code)}`; + } + + return `${text}`; +} + +function linkType(version, given) { + let type = given; + + if ((content = given.match(/^(?:const\s+)?([A-Za-z0-9_]+)(?:\s+\*+)?/))) { + type = content[1]; + } + + const api = apiFor(version, type); + + if (api) { + return `${given}`; + } + + return given; +} + +function linkText(version, str) { + const api = apiFor(version, str); + + if (api) { + return `${str}`; + } + + return sanitize(str); +} + +function render(version, str) { + let content = [ ]; + + if (!str) { + return ''; + } + + for (const s of Array.isArray(str) ? str : [ str ] ) { + content.push(markdown.render(s, { __version: version }).replaceAll(/\s+/g, ' ')); + } + + return content.join(' '); +} + +function nowrap(text) { + text = text.replaceAll(' ', ' '); + text = `${text}`; + return text; +} + +function sanitize(str) { + let content = [ ]; + + if (!str) { + return ''; + } + + for (const s of Array.isArray(str) ? str : [ str ] ) { + content.push(s.replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('{', '{') + .replaceAll('}', '}')); + } + + return content.join(' '); +} + +function produceSignatureForAlias(version, api, type) { + let content = ""; + + const githubLink = produceGitHubLink(version, api, type); + + content += `

Signature

\n`; + + if (githubLink) { + content += ` \n`; + } + + content += `
\n`; + content += ` typedef ${api.name} ${api.type};`; + content += `
\n`; + + return content; +} + +function produceSignatureForMacro(version, api, type) { + let content = ""; + + const githubLink = produceGitHubLink(version, api, type); + + content += `

Signature

\n`; + + if (githubLink) { + content += ` \n`; + } + + content += `
\n`; + content += ` #define ${api.name} ${sanitize(api.value)}`; + content += `
\n`; + + return content; +} + +function produceSignature(version, api, type) { + if (type === 'macro') { + return produceSignatureForMacro(version, api, type); + } + else if (type === 'alias') { + return produceSignatureForAlias(version, api, type); + } + else if (type === 'function' || type === 'callback') { + return produceSignatureForFunction(version, api, type); + } + else if (type === 'object') { + return produceSignatureForObject(version, api, type); + } + else if (type === 'struct') { + return produceSignatureForStruct(version, api, type); + } + else if (type === 'struct' || type === 'enum') { + return ""; + } + else { + throw new Error(`unknown type: ${api.kind}`); + } +} + +function isFunctionPointer(type) { + return type.match(/^(const\s+)?[A-Za-z0-9_]+\s+\*?\(\*/); +} + +function isEnum(type) { + return type.match(/^enum\s+/); +} + +function isStruct(type) { + return type.match(/^struct\s+/); +} + +function internalKind(version, api) { + if (api.kind === 'struct' && api.opaque) { + return 'object'; + } + + return api.kind; +} + +function externalKind(kind) { + if (kind === 'object') { + return 'struct'; + } + + return kind; +} + +async function produceIndexForGroup(version, group, versionApis) { + let content = ""; + + if (versionApis['groups'][group].apis.length == 0) { + return; + } + + const apis = Object.values(versionApis['groups'][group].apis); + + let fileName = group; + if (fileName.endsWith('.h')) { + fileName = fileName.substr(0, fileName.length - 2); + } + + const system = fileName.startsWith('sys/'); + let groupName = system ? fileName.substr(4) : fileName; + + content += `
\n`; + + content += `
\n`; + content += ` \n`; + content += `
\n`; + + content += `
\n`; + content += `

${groupName}

\n`; + + content += produceSearchArea(version, 'group'); + content += produceVersionPicker(version, "groupHeaderVersionSelect", (v) => { + if (apiData[v]['groups'][group]) { + return `${linkPrefix}/${v}/${groupName}/index.html`; + } + return undefined; + }); + + content += `
\n`; + + let details = undefined; + + if (versionApis['groups'][group].info?.details) { + details = markdown.render(versionApis['groups'][group].info.details, { __version: version }); + } else if (versionApis['groups'][group].info?.summary) { + details = versionApis['groups'][group].info.summary; + } + + if (details) { + content += `
\n`; + content += ` ${details}\n`; + content += `
\n`; + } + + for (const kind of [ 'object', 'struct', 'macro', 'enum', 'callback', 'alias', 'function' ]) { + content += produceIndexForApiKind(version, apis.filter(api => { + if (kind === 'object') { + return api.kind === 'struct' && api.opaque; + } + else if (kind === 'struct') { + return api.kind === 'struct' && !api.opaque; + } + else { + return api.kind === kind; + } + }), kind); + } + + content += `
\n`; + + const groupsDir = `${outputPath}/${version}/${fileName}`; + const filename = `${groupsDir}/index.html`; + + await fs.mkdir(groupsDir, { recursive: true }); + await fs.writeFile(filename, await layout({ + title: `${groupName} APIs (${projectTitle} ${version})`, + content: content + })); +} + +async function produceDocumentationForApis(version, apiData) { + const apis = allApisForVersion(version, apiData['groups']); + + for (const func of Object.values(apis).filter(api => api.kind === 'function')) { + await produceDocumentationForApi(version, func, 'function'); + } + + for (const struct of Object.values(apis).filter(api => api.kind === 'struct')) { + await produceDocumentationForApi(version, struct, internalKind(version, struct)); + } + + for (const e of Object.values(apis).filter(api => api.kind === 'enum')) { + await produceDocumentationForApi(version, e, 'enum'); + } + + for (const callback of Object.values(apis).filter(api => api.kind === 'callback')) { + await produceDocumentationForApi(version, callback, 'callback'); + } + + for (const alias of Object.values(apis).filter(api => api.kind === 'alias')) { + await produceDocumentationForApi(version, alias, 'alias'); + } + + for (const macro of Object.values(apis).filter(api => api.kind === 'macro')) { + await produceDocumentationForApi(version, macro, 'macro'); + } +} + +function produceIndexForApiKind(version, apis, kind) { + let content = ""; + + if (!apis || !apis.length) { + return content; + } + + let kindUpper = kind.charAt(0).toUpperCase() + kind.slice(1); + kindUpper += (kind === 'alias') ? 'es' : 's'; + + content += `\n`; + content += `

${kindUpper}

\n`; + + content += `
\n`; + + for (const item of apis) { + if (item.changed) { + content += `
\n`; + } else { + content += `
\n`; + } + + content += `
\n`; + content += ` \n`; + content += ` ${item.name}\n`; + content += ` \n`; + content += `
\n`; + + let shortComment = Array.isArray(item.comment) ? item.comment[0] : item.comment; + shortComment = shortComment || ''; + + shortComment = shortComment.replace(/\..*/, ''); + + content += `
\n`; + content += ` ${render(version, shortComment)}\n`; + content += `
\n`; + content += `
\n`; + } + + content += `
\n`; + + return content; +} + +function versionIndexContent(version, apiData) { + let content = ""; + let hasSystem = false; + + content += `
\n`; + content += `
\n`; + content += `

${projectTitle} ${version}

\n`; + + content += produceSearchArea(version, 'version'); + content += produceVersionPicker(version, "versionHeaderVersionSelect", + (v) => `${linkPrefix}/${v}/index.html`); + + content += `
\n`; + + content += `\n`; + content += `

Groups

\n`; + content += `
    \n`; + + for (const group of Object.keys(apiData['groups']).sort((a, b) => { + if (a.startsWith('sys/')) { return 1; } + if (b.startsWith('sys/')) { return -1; } + return a.localeCompare(b); + }).map(fn => { + let n = fn; + let sys = false; + + if (n.endsWith('.h')) { + n = n.substr(0, n.length - 2); + } + + if (n.startsWith('sys/')) { + n = n.substr(4); + sys = true; + } + + return { + name: n, filename: fn, system: sys, info: apiData['groups'][fn].info, apis: apiData['groups'][fn] + }; + }).filter(filedata => { + return Object.keys(filedata.apis).length > 0 && !fileDenylist.includes(filedata.filename); + })) { + if (group.system && !hasSystem) { + hasSystem = true; + + content += `
\n`; + content += `\n`; + content += `

System Groups (Advanced)

\n`; + content += `
    \n`; + } + + let link = `${linkPrefix}/${version}/`; + link += group.system ? `sys/` : ''; + link += group.name; + link += `/index.html`; + + content += `
  • \n`; + content += `
    \n`; + content += ` \n`; + content += ` ${group.name}\n`; + content += ` \n`; + content += `
    \n`; + + if (group.info?.summary) { + content += `
    \n`; + content += ` ${group.info.summary}`; + content += `
    \n`; + } + + content += `
  • \n`; + } + + content += `
\n`; + + content += `
\n`; + + return content; +} + +async function produceDocumentationIndex(version, apiData) { + const content = versionIndexContent(version, apiData); + + const versionDir = `${outputPath}/${version}`; + const filename = `${versionDir}/index.html`; + + await fs.mkdir(versionDir, { recursive: true }); + await fs.writeFile(filename, await layout({ + title: `APIs (${projectTitle} ${version})`, + content: content + })); +} + +async function documentationIsUpToDateForVersion(version, apiData) { + try { + const existingMetadata = JSON.parse(await fs.readFile(`${outputPath}/${version}/.metadata`)); + return existingMetadata?.commit === apiData.info.commit; + } + catch (e) { + } + + return false; +} + +async function produceDocumentationMetadata(version, apiData) { + const versionDir = `${outputPath}/${version}`; + const filename = `${versionDir}/.metadata`; + + await fs.mkdir(versionDir, { recursive: true }); + await fs.writeFile(filename, JSON.stringify(apiData.info, null, 2) + "\n"); +} + +async function cleanupOldDocumentation(version) { + const versionDir = `${outputPath}/${version}`; + + for (const fn of await fs.readdir(versionDir)) { + if (fn === '.metadata') { + continue; + } + + const path = `${versionDir}/${fn}`; + await fs.rm(path, { recursive: true }); + } +} + +async function produceDocumentationForVersion(version, apiData) { + if (!options.force && await documentationIsUpToDateForVersion(version, apiData)) { + if (options.verbose) { + console.log(`Documentation exists for ${version} at version ${apiData.info.commit.substr(0, 7)}; skipping...`); + } + + return; + } + + if (options.verbose) { + console.log(`Producing documentation for ${version}...`); + } + + await cleanupOldDocumentation(version); + + await produceDocumentationForApis(version, apiData); + + for (const group in apiData['groups']) { + await produceIndexForGroup(version, group, apiData); + } + + await produceDocumentationIndex(version, apiData); + + await produceDocumentationMetadata(version, apiData); +} + +function versionDeltaData(version, api) { + const base = { version: version, api: api }; + + if (api.kind === 'function') { + return { + ...base, + returns: api.returns?.type || 'int', + params: api.params?.map((p) => p.type) || [ 'void' ] + }; + } + else if (api.kind === 'enum') { + return { + ...base, + members: api.members?.map((m) => { return { 'name': m.name, 'value': m.value } }) + }; + } + else if (api.kind === 'callback') { + return { ...base, }; + } + else if (api.kind === 'alias') { + return { ...base, }; + } + else if (api.kind === 'struct') { + return { + ...base, + members: api.members?.map((m) => { return { 'name': m.name, 'type': m.type } }) + }; + } + else if (api.kind === 'macro') { + return { + ...base, + name: api.name, + value: api.value + }; + } + else { + throw new Error(`unknown api kind: '${api.kind}'`); + } +} + +function deltasEqual(a, b) { + const unversionedA = { ...a }; + const unversionedB = { ...b }; + + delete unversionedA.version; + delete unversionedA.api; + delete unversionedA.changed; + delete unversionedB.version; + delete unversionedB.api; + delete unversionedB.changed; + + return JSON.stringify(unversionedA) === JSON.stringify(unversionedB); +} + +const apiForVersionCache = { }; +function allApisForVersion(version, apiData) { + if (apiForVersionCache[version]) { + return apiForVersionCache[version]; + } + + let result = { }; + for (const file in apiData['groups']) { + result = { ...result, ...apiData['groups'][file].apis }; + } + + apiForVersionCache[version] = result; + return result; +} + +function seedVersionApis(apiData) { + for (const version in apiData) { + allApisForVersion(version, apiData[version]); + } +} + +function calculateVersionDeltas(apiData) { + for (const version in apiData) { + const apisForVersion = allApisForVersion(version, apiData[version]); + + for (const api in apisForVersion) { + if (!versionDeltas[api]) { + versionDeltas[api] = [ ]; + } + + versionDeltas[api].push(versionDeltaData(version, apisForVersion[api])); + } + } + + for (const api in versionDeltas) { + const count = versionDeltas[api].length; + + versionDeltas[api][count - 1].changed = true; + + for (let i = count - 2; i >= 0; i--) { + versionDeltas[api][i].changed = !deltasEqual(versionDeltas[api][i], versionDeltas[api][i + 1]); + } + } +} + +async function produceSearch(versions) { + if (options.verbose) { + console.log(`Producing search page...`); + } + + let content = ""; + + content += `\n`; + content += `\n`; + content += `\n`; + content += `\n`; + + content += `\n`; + + content += ` \n`; + + const filename = `${outputPath}/search.html`; + + await fs.mkdir(outputPath, { recursive: true }); + await fs.writeFile(filename, await layout({ + title: `API search (${projectTitle})`, + content: content + })); +} + +async function produceMainIndex(versions) { + const versionDefault = versions[0]; + + if (options.verbose) { + console.log(`Producing documentation index...`); + } + + let content = ""; + + content += `\n`; + content += `\n`; + + content += versionIndexContent(versionDefault, apiData[versionDefault]); + + const filename = `${outputPath}/index.html`; + + await fs.mkdir(outputPath, { recursive: true }); + await fs.writeFile(filename, await layout({ + title: `APIs (${projectTitle} ${versionDefault})`, + content: content + })); +} + +function versionSort(a, b) { + if (a === b) { + return 0; + } + + const aVersion = a.match(/^v(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?(?:-(.*))?$/); + const bVersion = b.match(/^v(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?(?:-(.*))?$/); + + if (!aVersion && !bVersion) { + return a.localeCompare(b); + } + else if (aVersion && !bVersion) { + return -1; + } + else if (!aVersion && bVersion) { + return 1; + } + + for (let i = 1; i < 5; i++) { + if (!aVersion[i] && !bVersion[i]) { + break; + } + else if (aVersion[i] && !bVersion[i]) { + return 1; + } + else if (!aVersion[i] && bVersion[i]) { + return -1; + } + else if (aVersion[i] !== bVersion[i]) { + return aVersion[i] - bVersion[i]; + } + } + + if (aVersion[5] && !bVersion[5]) { + return -1; + } + else if (!aVersion[5] && bVersion[5]) { + return 1; + } + else if (aVersion[5] && bVersion[5]) { + return aVersion[5].localeCompare(bVersion[5]); + } + + return 0; +} + +program.option('--output ') + .option('--layout ') + .option('--jekyll-layout ') + .option('--version ') + .option('--verbose') + .option('--force') + .option('--strict'); +program.parse(); + +const options = program.opts(); + +if (program.args.length != 2) { + console.error(`usage: ${path.basename(process.argv[1])} raw_api_dir output_dir`); + process.exit(1); +} + +const docsPath = program.args[0]; +const outputPath = program.args[1]; + +(async () => { + try { + const v = options.version ? options.version : + (await fs.readdir(docsPath)) + .filter(a => a.endsWith('.json')) + .map(a => a.replace(/\.json$/, '')); + + versions.push(...v.sort(versionSort).reverse()); + + for (const version of versions) { + if (options.verbose) { + console.log(`Reading documentation data for ${version}...`); + } + + apiData[version] = JSON.parse(await fs.readFile(`${docsPath}/${version}.json`)); + } + + if (showVersions) { + if (options.verbose) { + console.log(`Calculating version deltas...`); + } + + calculateVersionDeltas(apiData); + } + + for (const version of versions) { + await produceDocumentationForVersion(version, apiData[version]); + } + + await produceSearch(versions); + await produceMainIndex(versions); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/script/api-docs/generate b/script/api-docs/generate new file mode 100755 index 00000000..76f3d8ce --- /dev/null +++ b/script/api-docs/generate @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# +# Usage: generate repo_path output_path +# +# Example: generate https://github.com/libgit2/libgit2 path_to_output +# to clone the repository from GitHub and produce documentation; +# the repo_path can also be a local path + +set -eo pipefail + +source_path=$(mktemp -d) +verbose= +force= + +for var in "$@"; do + if [ "${var}" == "--verbose" ]; then + verbose=true + elif [ "${var}" == "--force" ]; then + force=true + elif [ "${repo_path}" == "" ]; then + repo_path="${var}" + elif [ "${output_path}" == "" ]; then + output_path="${var}" + else + repo_path="" + output_path="" + fi +done + +if [ "${repo_path}" = "" -o "${output_path}" = "" ]; then + echo "usage: $0 [--verbose] [--force] repo_path output_path" 1>&2 + exit 1 +fi + +function do_checkout { + if [ "$1" = "" ]; then + echo "usage: $0 source_path" 1>&2 + exit 1 + fi + + if [ "${verbose}" ]; then + echo ":: Checking out source trees..." + echo "" + fi + + source_path=$1 + + mkdir -p "${source_path}" + git clone "${repo_path}" "${source_path}/main" --no-checkout + ( cd "${source_path}/main" && git sparse-checkout set --no-cone 'include/*' ) + ( cd "${source_path}/main" && git read-tree origin/main ) + ( cd "${source_path}/main" && git checkout -- include ) + + for tag in $(git --git-dir="${source_path}/main/.git" tag -l); do + git --git-dir="${source_path}/main/.git" worktree add -f "${source_path}/${tag}" "${tag}" --no-checkout + ( cd "${source_path}/${tag}" && git sparse-checkout set --no-cone 'include/*' ) + ( cd "${source_path}/${tag}" && git read-tree HEAD ) + + if [ "${tag}" == "v0.1.0" ]; then + ( cd "${source_path}/${tag}" && git checkout -- src/git ) + elif [ "${tag}" == "v0.2.0" -o "${tag}" == "v0.3.0" ]; then + ( cd "${source_path}/${tag}" && git checkout -- src/git2 ) + else + ( cd "${source_path}/${tag}" && git checkout -- include ) + fi + done +} + +do_checkout ${source_path} + +if [ "${verbose}" ]; then + echo "" + echo ":: Generating raw API documentation..." + echo "" +fi + +for version in ${source_path}/*; do + version=$(echo "${version}" | sed -e "s/.*\///") + commit=$( cd "${source_path}/${version}" && git rev-parse HEAD ) + + if [ -f "${output_path}/api/${version}.json" ]; then + existing_commit=$(jq -r .info.commit < "${output_path}/api/${version}.json") + + if [ "${existing_commit}" == "${commit}" -a ! "${force}" ]; then + if [ "${verbose}" ]; then + echo "Raw API documentation for ${version} exists; skipping..." + fi + + continue + fi + fi + + echo "Generating raw API documentation for ${version}..." + mkdir -p "${output_path}/api" + node ./api-generator.js "${source_path}/${version}" > "${output_path}/api/${version}.json" +done + +if [ "${verbose}" ]; then + echo "" + echo ":: Generating HTML documentation..." + echo "" +fi + +search_options="" +docs_options="" +if [ "${verbose}" ]; then + search_options="${search_options} --verbose" + docs_options="${docs_options} --verbose" +fi +if [ "${force}" ]; then + docs_options="${docs_options} --force" +fi + +node ./search-generator.js ${search_options} "${output_path}/api" "${output_path}/search-index" +node ./docs-generator.js ${docs_options} --jekyll-layout default "${output_path}/api" "${output_path}/reference" diff --git a/script/api-docs/package-lock.json b/script/api-docs/package-lock.json new file mode 100644 index 00000000..3ba22260 --- /dev/null +++ b/script/api-docs/package-lock.json @@ -0,0 +1,85 @@ +{ + "name": "api-docs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^12.1.0", + "markdown-it": "^14.1.0", + "minisearch": "^7.1.1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "node_modules/minisearch": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.1.tgz", + "integrity": "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==" + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + } + } +} diff --git a/script/api-docs/package.json b/script/api-docs/package.json new file mode 100644 index 00000000..67a6fe7a --- /dev/null +++ b/script/api-docs/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "commander": "^12.1.0", + "markdown-it": "^14.1.0", + "minisearch": "^7.1.1" + } +} diff --git a/script/api-docs/search-generator.js b/script/api-docs/search-generator.js new file mode 100755 index 00000000..cad73f94 --- /dev/null +++ b/script/api-docs/search-generator.js @@ -0,0 +1,212 @@ +#!/usr/bin/env node + +const markdownit = require('markdown-it'); +const { program } = require('commander'); +const minisearch = require('minisearch'); + +const path = require('node:path'); +const fs = require('node:fs/promises'); + +const linkPrefix = '/docs/reference'; + +const defaultBranch = 'main'; + +function uniqueifyId(api, nodes) { + let suffix = "", i = 1; + + while (true) { + const possibleId = `${api.kind}-${api.name}${suffix}`; + let collision = false; + + for (const item of nodes) { + if (item.id === possibleId) { + collision = true; + break; + } + } + + if (!collision) { + return possibleId; + } + + suffix = `-${++i}`; + } +} + +async function produceSearchIndex(version, apiData) { + const nodes = [ ]; + + for (const group in apiData['groups']) { + for (const name in apiData['groups'][group]['apis']) { + const api = apiData['groups'][group]['apis'][name]; + + let displayName = name; + + if (api.kind === 'macro') { + displayName = displayName.replace(/\(.*/, ''); + } + + const apiSearchData = { + id: uniqueifyId(api, nodes), + name: displayName, + group: group, + kind: api.kind + }; + + apiSearchData.description = Array.isArray(api.comment) ? + api.comment[0] : api.comment; + + let detail = ""; + + if (api.kind === 'macro') { + detail = api.value; + } + else if (api.kind === 'alias') { + detail = api.type; + } + else { + let details = undefined; + + if (api.kind === 'struct' || api.kind === 'enum') { + details = api.members; + } + else if (api.kind === 'function' || api.kind === 'callback') { + details = api.params; + } + else { + throw new Error(`unknown api type '${api.kind}'`); + } + + for (const item of details || [ ]) { + if (detail.length > 0) { + detail += ' '; + } + + detail += item.name; + + if (item.comment) { + detail += ' '; + detail += item.comment; + } + } + + if (api.kind === 'function' || api.kind === 'callback') { + if (detail.length > 0 && api.returns?.type) { + detail += ' ' + api.returns.type; + } + + if (detail.length > 0 && api.returns?.comment) { + detail += ' ' + api.returns.comment; + } + } + } + + detail = detail.replaceAll(/\s+/g, ' ') + .replaceAll(/[\"\'\`]/g, ''); + + apiSearchData.detail = detail; + + nodes.push(apiSearchData); + } + } + + const index = new minisearch({ + fields: [ 'name', 'description', 'detail' ], + storeFields: [ 'name', 'group', 'kind', 'description' ], + searchOptions: { boost: { name: 5, description: 2 } } + }); + + index.addAll(nodes); + + const filename = `${outputPath}/${version}.json`; + await fs.mkdir(outputPath, { recursive: true }); + await fs.writeFile(filename, JSON.stringify(index, null, 2)); +} + +function versionSort(a, b) { + if (a === b) { + return 0; + } + + const aVersion = a.match(/^v(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?(?:-(.*))?$/); + const bVersion = b.match(/^v(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?(?:-(.*))?$/); + + if (!aVersion && !bVersion) { + return a.localeCompare(b); + } + else if (aVersion && !bVersion) { + return -1; + } + else if (!aVersion && bVersion) { + return 1; + } + + for (let i = 1; i < 5; i++) { + if (!aVersion[i] && !bVersion[i]) { + break; + } + else if (aVersion[i] && !bVersion[i]) { + return 1; + } + else if (!aVersion[i] && bVersion[i]) { + return -1; + } + else if (aVersion[i] !== bVersion[i]) { + return aVersion[i] - bVersion[i]; + } + } + + if (aVersion[5] && !bVersion[5]) { + return -1; + } + else if (!aVersion[5] && bVersion[5]) { + return 1; + } + else if (aVersion[5] && bVersion[5]) { + return aVersion[5].localeCompare(bVersion[5]); + } + + return 0; +} + +program.option('--verbose') + .option('--version '); +program.parse(); + +const options = program.opts(); + +if (program.args.length != 2) { + console.error(`usage: ${path.basename(process.argv[1])} raw_api_dir output_dir`); + process.exit(1); +} + +const docsPath = program.args[0]; +const outputPath = program.args[1]; + +(async () => { + try { + const v = options.version ? options.version : + (await fs.readdir(docsPath)) + .filter(a => a.endsWith('.json')) + .map(a => a.replace(/\.json$/, '')); + + const versions = v.sort(versionSort).reverse(); + + for (const version of versions) { + if (options.verbose) { + console.log(`Reading documentation data for ${version}...`); + } + + const apiData = JSON.parse(await fs.readFile(`${docsPath}/${version}.json`)); + + if (options.verbose) { + console.log(`Creating minisearch index for ${version}...`); + } + + await produceSearchIndex(version, apiData); + } + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/script/valgrind.sh b/script/valgrind.sh index b5deed2b..aacd767a 100755 --- a/script/valgrind.sh +++ b/script/valgrind.sh @@ -1,2 +1,2 @@ #!/bin/bash -exec valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@" +exec valgrind --leak-check=full --show-reachable=yes --child-silent-after-fork=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@" diff --git a/script/valgrind.supp b/script/valgrind.supp index 8c4549f6..79e8378f 100644 --- a/script/valgrind.supp +++ b/script/valgrind.supp @@ -80,6 +80,13 @@ fun:__check_pf } +{ + ignore-glibc-getaddrinfo-fn + Memcheck:Leak + ... + fun:getaddrinfo +} + { ignore-curl-global-init Memcheck:Leak @@ -191,6 +198,16 @@ ... } +{ + ignore-openssl-undefined-in-connect + Memcheck:Cond + ... + obj:*libcrypto.so* + ... + fun:openssl_connect + ... +} + { ignore-libssh2-rsa-sha1-sign Memcheck:Leak diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8525acdd..73c46e21 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,7 +127,8 @@ endif() # realtime support check_library_exists(rt clock_gettime "time.h" NEED_LIBRT) -if(NEED_LIBRT) + +if(NEED_LIBRT AND NOT CMAKE_SYSTEM_NAME MATCHES "iOS") list(APPEND LIBGIT2_SYSTEM_LIBS rt) list(APPEND LIBGIT2_PC_LIBS "-lrt") endif() @@ -135,7 +136,8 @@ endif() # platform libraries if(WIN32) - list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) + list(APPEND LIBGIT2_SYSTEM_LIBS "ws2_32" "secur32") + list(APPEND LIBGIT2_PC_LIBS "-lws2_32" "-lsecur32") endif() if(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") @@ -183,7 +185,7 @@ add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") # iconv if(USE_ICONV) - find_package(Iconv) + find_package(IntlIconv) endif() if(ICONV_FOUND) set(GIT_USE_ICONV 1) @@ -200,7 +202,7 @@ add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") add_subdirectory(libgit2) add_subdirectory(util) -if(BUILD_CLI) +if(BUILD_CLI AND NOT CMAKE_SYSTEM_NAME MATCHES "iOS") add_subdirectory(cli) endif() diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 84b6c190..d121c588 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -4,7 +4,8 @@ set(CLI_INCLUDES "${libgit2_SOURCE_DIR}/src/util" "${libgit2_SOURCE_DIR}/src/cli" "${libgit2_SOURCE_DIR}/include" - "${LIBGIT2_DEPENDENCY_INCLUDES}") + "${LIBGIT2_DEPENDENCY_INCLUDES}" + "${LIBGIT2_SYSTEM_INCLUDES}") if(WIN32 AND NOT CYGWIN) file(GLOB CLI_SRC_OS win32/*.c) @@ -39,7 +40,6 @@ add_executable(git2_cli ${CLI_SRC_C} ${CLI_SRC_OS} ${CLI_OBJECTS} ${LIBGIT2_DEPENDENCY_OBJECTS}) target_link_libraries(git2_cli ${CLI_LIBGIT2_LIBRARY} ${LIBGIT2_SYSTEM_LIBS}) -set_target_properties(git2_cli PROPERTIES C_STANDARD 90) set_target_properties(git2_cli PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) set_target_properties(git2_cli PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) diff --git a/src/cli/cmd.c b/src/cli/cmd.c index 2a7e71cd..0b1fafb4 100644 --- a/src/cli/cmd.c +++ b/src/cli/cmd.c @@ -5,7 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "cli.h" +#include "common.h" #include "cmd.h" const cli_cmd_spec *cli_cmd_spec_byname(const char *name) diff --git a/src/cli/cmd.h b/src/cli/cmd.h index 8b1a1b38..194a0b50 100644 --- a/src/cli/cmd.h +++ b/src/cli/cmd.h @@ -25,9 +25,13 @@ extern const cli_cmd_spec cli_cmds[]; extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); /* Commands */ +extern int cmd_blame(int argc, char **argv); extern int cmd_cat_file(int argc, char **argv); extern int cmd_clone(int argc, char **argv); +extern int cmd_config(int argc, char **argv); extern int cmd_hash_object(int argc, char **argv); extern int cmd_help(int argc, char **argv); +extern int cmd_index_pack(int argc, char **argv); +extern int cmd_init(int argc, char **argv); #endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_blame.c b/src/cli/cmd_blame.c new file mode 100644 index 00000000..180a948f --- /dev/null +++ b/src/cli/cmd_blame.c @@ -0,0 +1,287 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include "common.h" +#include "cmd.h" +#include "error.h" +#include "sighandler.h" +#include "progress.h" + +#include "fs_path.h" +#include "futils.h" +#include "date.h" +#include "hashmap.h" + +#define COMMAND_NAME "blame" + +static char *file; +static int porcelain, line_porcelain; + +static const cli_opt_spec opts[] = { + CLI_COMMON_OPT, + + { CLI_OPT_TYPE_SWITCH, "porcelain", 'p', &porcelain, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "show machine readable output" }, + { CLI_OPT_TYPE_SWITCH, "line-porcelain", 0, &line_porcelain, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "show individual lines in machine readable output" }, + { CLI_OPT_TYPE_LITERAL }, + { CLI_OPT_TYPE_ARG, "file", 0, &file, 0, + CLI_OPT_USAGE_REQUIRED, "file", "file to blame" }, + + { 0 } +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); + printf("\n"); + + printf("Show the origin of each line of a file.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int strintlen(size_t n) +{ + int len = 1; + + while (n > 10) { + n /= 10; + len++; + + if (len == INT_MAX) + break; + } + + return len; +} + +static int fmt_date(git_str *out, git_time_t time, int offset) +{ + time_t t; + struct tm gmt; + + GIT_ASSERT_ARG(out); + + t = (time_t)(time + offset * 60); + + if (p_gmtime_r(&t, &gmt) == NULL) + return -1; + + return git_str_printf(out, "%.4u-%02u-%02u %02u:%02u:%02u %+03d%02d", + gmt.tm_year + 1900, gmt.tm_mon + 1, gmt.tm_mday, + gmt.tm_hour, gmt.tm_min, gmt.tm_sec, + offset / 60, offset % 60); +} + +static int print_standard(git_blame *blame) +{ + size_t max_line_number = 0; + int max_lineno_len, max_line_len, max_author_len = 0, max_path_len = 0; + const char *last_path = NULL; + const git_blame_line *line; + bool paths_differ = false; + git_str date_str = GIT_STR_INIT; + size_t i; + int ret = 0; + + /* Compute the maximum size of things */ + for (i = 0; i < git_blame_hunkcount(blame); i++) { + const git_blame_hunk *hunk = git_blame_hunk_byindex(blame, i); + size_t hunk_author_len = strlen(hunk->orig_signature->name); + size_t hunk_path_len = strlen(hunk->orig_path); + size_t hunk_max_line_number = + hunk->orig_start_line_number + hunk->lines_in_hunk; + + if (hunk_max_line_number > max_line_number) + max_line_number = hunk_max_line_number; + + if (hunk_author_len > INT_MAX) + max_author_len = INT_MAX; + else if ((int)hunk_author_len > max_author_len) + max_author_len = (int)hunk_author_len; + + if (hunk_path_len > INT_MAX) + hunk_path_len = INT_MAX; + else if ((int)hunk_path_len > max_path_len) + max_path_len = (int)hunk_path_len; + + if (!paths_differ && last_path != NULL && + strcmp(last_path, hunk->orig_path) != 0) { + paths_differ = true; + } + + last_path = hunk->orig_path; + } + + max_lineno_len = strintlen(max_line_number); + + max_author_len--; + + for (i = 1; i < git_blame_linecount(blame); i++) { + const git_blame_hunk *hunk = git_blame_hunk_byline(blame, i); + int oid_abbrev; + + if (!hunk) + break; + + oid_abbrev = hunk->boundary ? 7 : 8; + printf("%s%.*s ", hunk->boundary ? "^" : "", + oid_abbrev, git_oid_tostr_s(&hunk->orig_commit_id)); + + if (paths_differ) + printf("%-*.*s ", max_path_len, max_path_len, hunk->orig_path); + + git_str_clear(&date_str); + if (fmt_date(&date_str, + hunk->orig_signature->when.time, + hunk->orig_signature->when.offset) < 0) { + ret = cli_error_git(); + goto done; + } + + if ((line = git_blame_line_byindex(blame, i)) == NULL) { + ret = cli_error_git(); + goto done; + } + + max_line_len = (int)min(line->len, INT_MAX); + + printf("(%-*.*s %s %*" PRIuZ ") %.*s" , + max_author_len, max_author_len, hunk->orig_signature->name, + date_str.ptr, + max_lineno_len, i, + max_line_len, line->ptr); + printf("\n"); + } + +done: + git_str_dispose(&date_str); + return ret; +} + +GIT_INLINE(uint32_t) oid_hashcode(const git_oid *oid) +{ + uint32_t hash; + memcpy(&hash, oid->id, sizeof(uint32_t)); + return hash; +} + +GIT_HASHSET_SETUP(git_blame_commitmap, const git_oid *, oid_hashcode, git_oid_equal); + +static int print_porcelain(git_blame *blame) +{ + git_blame_commitmap seen_ids = GIT_HASHSET_INIT; + size_t i, j; + + for (i = 0; i < git_blame_hunkcount(blame); i++) { + const git_blame_line *line; + const git_blame_hunk *hunk = git_blame_hunk_byindex(blame, i); + + for (j = 0; j < hunk->lines_in_hunk; j++) { + size_t line_number = hunk->final_start_line_number + j; + bool seen = git_blame_commitmap_contains(&seen_ids, &hunk->orig_commit_id); + + printf("%s %" PRIuZ " %" PRIuZ, + git_oid_tostr_s(&hunk->orig_commit_id), + hunk->orig_start_line_number + j, + hunk->final_start_line_number + j); + + if (!j) + printf(" %" PRIuZ, hunk->lines_in_hunk); + + printf("\n"); + + if ((!j && !seen) || line_porcelain) { + printf("author %s\n", hunk->orig_signature->name); + printf("author-mail <%s>\n", hunk->orig_signature->email); + printf("author-time %" PRId64 "\n", hunk->orig_signature->when.time); + printf("author-tz %+03d%02d\n", + hunk->orig_signature->when.offset / 60, + hunk->orig_signature->when.offset % 60); + + printf("committer %s\n", hunk->orig_committer->name); + printf("committer-mail <%s>\n", hunk->orig_committer->email); + printf("committer-time %" PRId64 "\n", hunk->orig_committer->when.time); + printf("committer-tz %+03d%02d\n", + hunk->orig_committer->when.offset / 60, + hunk->orig_committer->when.offset % 60); + + printf("summary %s\n", hunk->summary); + + /* TODO: previous */ + + printf("filename %s\n", hunk->orig_path); + } + + if ((line = git_blame_line_byindex(blame, line_number)) == NULL) + return cli_error_git(); + + printf("\t%.*s\n", (int)min(line->len, INT_MAX), + line->ptr); + + if (!seen) + git_blame_commitmap_add(&seen_ids, &hunk->orig_commit_id); + } + } + + git_blame_commitmap_dispose(&seen_ids); + return 0; +} + +int cmd_blame(int argc, char **argv) +{ + cli_repository_open_options open_opts = { argv + 1, argc - 1 }; + git_blame_options blame_opts = GIT_BLAME_OPTIONS_INIT; + git_repository *repo = NULL; + git_str workdir_path = GIT_STR_INIT; + git_blame *blame = NULL; + cli_opt invalid_opt; + int ret = 0; + + blame_opts.flags |= GIT_BLAME_USE_MAILMAP; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (cli_opt__show_help) { + print_help(); + return 0; + } + + if (!file) { + ret = cli_error_usage("you must specify a file to blame"); + goto done; + } + + if (cli_repository_open(&repo, &open_opts) < 0) + return cli_error_git(); + + if ((ret = cli_resolve_path(&workdir_path, repo, file)) != 0) + goto done; + + if (git_blame_file(&blame, repo, workdir_path.ptr, &blame_opts) < 0) { + ret = cli_error_git(); + goto done; + } + + if (porcelain || line_porcelain) + ret = print_porcelain(blame); + else + ret = print_standard(blame); + +done: + git_str_dispose(&workdir_path); + git_blame_free(blame); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/cmd_cat_file.c b/src/cli/cmd_cat_file.c index fb53a722..95a0240c 100644 --- a/src/cli/cmd_cat_file.c +++ b/src/cli/cmd_cat_file.c @@ -6,7 +6,7 @@ */ #include -#include "cli.h" +#include "common.h" #include "cmd.h" #define COMMAND_NAME "cat-file" @@ -19,14 +19,11 @@ typedef enum { DISPLAY_TYPE } display_t; -static int show_help; static int display = DISPLAY_CONTENT; static char *type_name, *object_spec; static const cli_opt_spec opts[] = { - { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, - CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, - "display help about the " COMMAND_NAME " command" }, + CLI_COMMON_OPT, { CLI_OPT_TYPE_SWITCH, NULL, 't', &display, DISPLAY_TYPE, CLI_OPT_USAGE_REQUIRED, NULL, "display the type of the object" }, @@ -45,7 +42,7 @@ static const cli_opt_spec opts[] = { static void print_help(void) { - cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); printf("\n"); printf("Display the content for the given object in the repository.\n"); @@ -139,6 +136,7 @@ static int print_pretty(git_object *object) int cmd_cat_file(int argc, char **argv) { + cli_repository_open_options open_opts = { argv + 1, argc - 1}; git_repository *repo = NULL; git_object *object = NULL; git_object_t type; @@ -148,12 +146,12 @@ int cmd_cat_file(int argc, char **argv) if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); - if (show_help) { + if (cli_opt__show_help) { print_help(); return 0; } - if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) + if (cli_repository_open(&repo, &open_opts) < 0) return cli_error_git(); if ((giterr = git_revparse_single(&object, repo, object_spec)) < 0) { diff --git a/src/cli/cmd_clone.c b/src/cli/cmd_clone.c index e4776256..c18cb28d 100644 --- a/src/cli/cmd_clone.c +++ b/src/cli/cmd_clone.c @@ -7,7 +7,7 @@ #include #include -#include "cli.h" +#include "common.h" #include "cmd.h" #include "error.h" #include "sighandler.h" @@ -19,14 +19,12 @@ #define COMMAND_NAME "clone" static char *branch, *remote_path, *local_path, *depth; -static int show_help, quiet, checkout = 1, bare; +static int quiet, checkout = 1, bare; static bool local_path_exists; static cli_progress progress = CLI_PROGRESS_INIT; static const cli_opt_spec opts[] = { - { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, - CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, - "display help about the " COMMAND_NAME " command" }, + CLI_COMMON_OPT, { CLI_OPT_TYPE_SWITCH, "quiet", 'q', &quiet, 1, CLI_OPT_USAGE_DEFAULT, NULL, "display the type of the object" }, @@ -48,7 +46,7 @@ static const cli_opt_spec opts[] = { static void print_help(void) { - cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); printf("\n"); printf("Clone a repository into a new directory.\n"); @@ -135,7 +133,7 @@ int cmd_clone(int argc, char **argv) if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); - if (show_help) { + if (cli_opt__show_help) { print_help(); return 0; } diff --git a/src/cli/cmd_config.c b/src/cli/cmd_config.c new file mode 100644 index 00000000..9056e81f --- /dev/null +++ b/src/cli/cmd_config.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "common.h" +#include "cmd.h" + +#define COMMAND_NAME "config" + +typedef enum { + ACTION_NONE = 0, + ACTION_GET, + ACTION_ADD, + ACTION_REPLACE_ALL, + ACTION_LIST +} action_t; + +static action_t action = ACTION_NONE; +static int show_origin; +static int show_scope; +static int null_separator; +static int config_level; +static char *config_filename; +static char *name, *value, *value_pattern; + +static const cli_opt_spec opts[] = { + CLI_COMMON_OPT, \ + + { CLI_OPT_TYPE_SWITCH, "null", 'z', &null_separator, 1, + 0, NULL, "use NUL as a separator" }, + + { CLI_OPT_TYPE_SWITCH, "system", 0, &config_level, GIT_CONFIG_LEVEL_SYSTEM, + 0, NULL, "read/write to system configuration" }, + { CLI_OPT_TYPE_SWITCH, "global", 0, &config_level, GIT_CONFIG_LEVEL_GLOBAL, + CLI_OPT_USAGE_CHOICE, NULL, "read/write to global configuration" }, + { CLI_OPT_TYPE_SWITCH, "local", 0, &config_level, GIT_CONFIG_LEVEL_LOCAL, + CLI_OPT_USAGE_CHOICE, NULL, "read/write to local configuration" }, + { CLI_OPT_TYPE_VALUE, "file", 0, &config_filename, 0, + CLI_OPT_USAGE_CHOICE, "filename", "read/write to specified configuration file" }, + + { CLI_OPT_TYPE_SWITCH, "get", 0, &action, ACTION_GET, + CLI_OPT_USAGE_REQUIRED, NULL, "get a configuration value" }, + { CLI_OPT_TYPE_SWITCH, "add", 0, &action, ACTION_ADD, + CLI_OPT_USAGE_CHOICE, NULL, "add a configuration value" }, + { CLI_OPT_TYPE_SWITCH, "replace-all", 0, &action, ACTION_REPLACE_ALL, + CLI_OPT_USAGE_CHOICE, NULL, "add a configuration value, replacing any old values" }, + { CLI_OPT_TYPE_SWITCH, "list", 'l', &action, ACTION_LIST, + CLI_OPT_USAGE_CHOICE | CLI_OPT_USAGE_SHOW_LONG, + NULL, "list all configuration entries" }, + { CLI_OPT_TYPE_SWITCH, "show-origin", 0, &show_origin, 1, + 0, NULL, "show origin of configuration" }, + { CLI_OPT_TYPE_SWITCH, "show-scope", 0, &show_scope, 1, + 0, NULL, "show scope of configuration" }, + { CLI_OPT_TYPE_ARG, "name", 0, &name, 0, + 0, "name", "name of configuration entry" }, + { CLI_OPT_TYPE_ARG, "value", 0, &value, 0, + 0, "value", "value of configuration entry" }, + { CLI_OPT_TYPE_ARG, "regexp", 0, &value_pattern, 0, + 0, "regexp", "regular expression of values to replace" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); + printf("\n"); + + printf("Query and set configuration options.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int get_config(git_config *config) +{ + git_buf value = GIT_BUF_INIT; + char sep = null_separator ? '\0' : '\n'; + int error; + + error = git_config_get_string_buf(&value, config, name); + + if (error && error != GIT_ENOTFOUND) + return cli_error_git(); + + else if (error == GIT_ENOTFOUND) + return 1; + + printf("%s%c", value.ptr, sep); + return 0; +} + +static int add_config(git_config *config) +{ + if (git_config_set_multivar(config, name, "$^", value) < 0) + return cli_error_git(); + + return 0; +} + +static int replace_all_config(git_config *config) +{ + if (git_config_set_multivar(config, name, value_pattern ? value_pattern : ".*", value) < 0) + return cli_error_git(); + + return 0; +} + +static const char *level_name(git_config_level_t level) +{ + switch (level) { + case GIT_CONFIG_LEVEL_PROGRAMDATA: + return "programdata"; + case GIT_CONFIG_LEVEL_SYSTEM: + return "system"; + case GIT_CONFIG_LEVEL_XDG: + return "global"; + case GIT_CONFIG_LEVEL_GLOBAL: + return "global"; + case GIT_CONFIG_LEVEL_LOCAL: + return "local"; + case GIT_CONFIG_LEVEL_APP: + return "command"; + default: + return "unknown"; + } +} + +static int list_config(git_config *config) +{ + git_config_iterator *iterator; + git_config_entry *entry; + char data_separator = null_separator ? '\0' : '\t'; + char kv_separator = null_separator ? '\n' : '='; + char entry_separator = null_separator ? '\0' : '\n'; + int error; + + if (git_config_iterator_new(&iterator, config) < 0) + return cli_error_git(); + + while ((error = git_config_next(&entry, iterator)) == 0) { + if (show_scope) + printf("%s%c", + level_name(entry->level), + data_separator); + + if (show_origin) + printf("%s%s%s%c", + entry->backend_type ? entry->backend_type : "", + entry->backend_type && entry->origin_path ? ":" : "", + entry->origin_path ? entry->origin_path : "", + data_separator); + + printf("%s%c%s%c", entry->name, kv_separator, entry->value, + entry_separator); + } + + if (error != GIT_ITEROVER) + return cli_error_git(); + + git_config_iterator_free(iterator); + return 0; +} + +int cmd_config(int argc, char **argv) +{ + git_repository *repo = NULL; + git_config *config = NULL; + cli_repository_open_options open_opts = { argv + 1, argc - 1}; + cli_opt invalid_opt; + int ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (cli_opt__show_help) { + print_help(); + return 0; + } + + if (config_filename) { + if (git_config_new(&config) < 0 || + git_config_add_file_ondisk(config, config_filename, + GIT_CONFIG_LEVEL_APP, NULL, 0) < 0) { + ret = cli_error_git(); + goto done; + } + } else { + if (cli_repository_open(&repo, &open_opts) < 0 || + git_repository_config(&config, repo) < 0) { + ret = cli_error_git(); + goto done; + } + + if (config_level && + git_config_open_level(&config, config, config_level) < 0) { + ret = cli_error_git(); + goto done; + } + } + + switch (action) { + case ACTION_ADD: + if (!name || !value || value_pattern) + ret = cli_error_usage("%s --add requires two arguments", COMMAND_NAME); + else + ret = add_config(config); + break; + case ACTION_REPLACE_ALL: + if (!name || !value) + ret = cli_error_usage("%s --replace-all requires two or three arguments", COMMAND_NAME); + else + ret = replace_all_config(config); + break; + case ACTION_GET: + if (!name) + ret = cli_error_usage("%s --get requires an argument", COMMAND_NAME); + else + ret = get_config(config); + break; + case ACTION_LIST: + if (name) + ret = cli_error_usage("%s --list does not take an argument", COMMAND_NAME); + else + ret = list_config(config); + break; + default: + ret = cli_error_usage("unknown action"); + } + +done: + git_config_free(config); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/cmd_hash_object.c b/src/cli/cmd_hash_object.c index 93b980d6..94e7eb91 100644 --- a/src/cli/cmd_hash_object.c +++ b/src/cli/cmd_hash_object.c @@ -6,22 +6,19 @@ */ #include -#include "cli.h" +#include "common.h" #include "cmd.h" #include "futils.h" #define COMMAND_NAME "hash-object" -static int show_help; static char *type_name; static int write_object, read_stdin, literally; static char **filenames; static const cli_opt_spec opts[] = { - { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, - CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, - "display help about the " COMMAND_NAME " command" }, + CLI_COMMON_OPT, { CLI_OPT_TYPE_VALUE, NULL, 't', &type_name, 0, CLI_OPT_USAGE_DEFAULT, "type", "the type of object to hash (default: \"blob\")" }, @@ -38,7 +35,7 @@ static const cli_opt_spec opts[] = { static void print_help(void) { - cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); printf("\n"); printf("Compute the object ID for a given file and optionally write that file\nto the object database.\n"); @@ -92,6 +89,7 @@ static int hash_buf( int cmd_hash_object(int argc, char **argv) { + cli_repository_open_options open_opts = { argv + 1, argc - 1}; git_repository *repo = NULL; git_odb *odb = NULL; git_oid_t oid_type; @@ -104,7 +102,7 @@ int cmd_hash_object(int argc, char **argv) if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); - if (show_help) { + if (cli_opt__show_help) { print_help(); return 0; } @@ -113,7 +111,7 @@ int cmd_hash_object(int argc, char **argv) return cli_error_usage("invalid object type '%s'", type_name); if (write_object && - (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0 || + (cli_repository_open(&repo, &open_opts) < 0 || git_repository_odb(&odb, repo) < 0)) { ret = cli_error_git(); goto done; diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c index 7ee98224..c01163d3 100644 --- a/src/cli/cmd_help.c +++ b/src/cli/cmd_help.c @@ -7,17 +7,16 @@ #include #include -#include "cli.h" +#include "common.h" #include "cmd.h" #define COMMAND_NAME "help" static char *command; -static int show_help; static const cli_opt_spec opts[] = { - { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, - CLI_OPT_USAGE_HIDDEN, NULL, "display help about the help command" }, + CLI_COMMON_OPT, + { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" }, { 0 }, @@ -25,7 +24,7 @@ static const cli_opt_spec opts[] = { static int print_help(void) { - cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, CLI_OPT_USAGE_SHOW_HIDDEN); printf("\n"); printf("Display help information about %s. If a command is specified, help\n", PROGRAM_NAME); @@ -39,7 +38,7 @@ static int print_commands(void) { const cli_cmd_spec *cmd; - cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts, CLI_OPT_USAGE_SHOW_HIDDEN); printf("\n"); printf("These are the %s commands available:\n\n", PROGRAM_NAME); @@ -62,7 +61,7 @@ int cmd_help(int argc, char **argv) return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); /* Show the meta-help */ - if (show_help) + if (cli_opt__show_help) return print_help(); /* We were not asked to show help for a specific command. */ diff --git a/src/cli/cmd_index_pack.c b/src/cli/cmd_index_pack.c new file mode 100644 index 00000000..6a67990b --- /dev/null +++ b/src/cli/cmd_index_pack.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include "common.h" +#include "cmd.h" +#include "progress.h" + +#define COMMAND_NAME "index-pack" + +#define BUFFER_SIZE (1024 * 1024) + +static int verbose, read_stdin; +static char *filename; +static cli_progress progress = CLI_PROGRESS_INIT; + +static const cli_opt_spec opts[] = { + CLI_COMMON_OPT, + + { CLI_OPT_TYPE_SWITCH, "verbose", 'v', &verbose, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display progress output" }, + + { CLI_OPT_TYPE_LITERAL }, + + { CLI_OPT_TYPE_SWITCH, "stdin", 0, &read_stdin, 1, + CLI_OPT_USAGE_REQUIRED, NULL, "read from stdin" }, + { CLI_OPT_TYPE_ARG, "pack-file", 0, &filename, 0, + CLI_OPT_USAGE_CHOICE, "pack-file", "packfile path" }, + + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); + printf("\n"); + + printf("Indexes a packfile and writes the index to disk.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +int cmd_index_pack(int argc, char **argv) +{ + cli_opt invalid_opt; + git_indexer *idx = NULL; + git_indexer_options idx_opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer_progress stats = {0}; + char buf[BUFFER_SIZE]; + ssize_t read_len; + int fd, ret; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (cli_opt__show_help) { + print_help(); + return 0; + } + + if (verbose) { + idx_opts.progress_cb = cli_progress_indexer; + idx_opts.progress_cb_payload = &progress; + } + + if (read_stdin) { + fd = fileno(stdin); + } else if ((fd = p_open(filename, O_RDONLY)) < 0) { + ret = cli_error_git(); + goto done; + } + +#ifdef GIT_EXPERIMENTAL_SHA256 + idx_opts.oid_type = GIT_OID_SHA1; + + ret = git_indexer_new(&idx, ".", &idx_opts); +#else + ret = git_indexer_new(&idx, ".", 0, NULL, &idx_opts); +#endif + + if (ret < 0) { + ret = cli_error_git(); + goto done; + } + + while ((read_len = p_read(fd, buf, sizeof(buf))) > 0) { + if (git_indexer_append(idx, buf, (size_t)read_len, &stats) < 0) { + ret = cli_error_git(); + goto done; + } + } + + if (git_indexer_commit(idx, &stats) < 0) { + ret = cli_error_git(); + goto done; + } + + cli_progress_finish(&progress); + +done: + if (!read_stdin && fd >= 0) + p_close(fd); + + cli_progress_dispose(&progress); + git_indexer_free(idx); + return ret; +} diff --git a/src/cli/cmd_init.c b/src/cli/cmd_init.c new file mode 100644 index 00000000..b6468961 --- /dev/null +++ b/src/cli/cmd_init.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include "common.h" +#include "cmd.h" +#include "error.h" +#include "sighandler.h" +#include "progress.h" + +#include "fs_path.h" +#include "futils.h" + +#define COMMAND_NAME "init" + +static char *branch, *git_dir, *template_dir, *path; +static int quiet, bare; + +static const cli_opt_spec opts[] = { + CLI_COMMON_OPT, + + { CLI_OPT_TYPE_SWITCH, "quiet", 'q', &quiet, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "quiet mode; don't display informational messages" }, + { CLI_OPT_TYPE_SWITCH, "bare", 0, &bare, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "don't create a working directory" }, + { CLI_OPT_TYPE_VALUE, "initial-branch", 'b', &branch, 0, + CLI_OPT_USAGE_DEFAULT, "name", "initial branch name" }, + { CLI_OPT_TYPE_VALUE, "separate-git-dir", 0, &git_dir, 0, + CLI_OPT_USAGE_DEFAULT, "git-dir", "path to separate git directory" }, + { CLI_OPT_TYPE_VALUE, "template", 0, &template_dir, 0, + CLI_OPT_USAGE_DEFAULT, "template-dir", "path to git directory templates" }, + { CLI_OPT_TYPE_LITERAL }, + { CLI_OPT_TYPE_ARG, "directory", 0, &path, 0, + CLI_OPT_USAGE_DEFAULT, "directory", "directory to create repository in" }, + { 0 } +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); + printf("\n"); + + printf("Create a new git repository.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +int cmd_init(int argc, char **argv) +{ + git_repository *repo = NULL; + git_repository_init_options init_opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + cli_opt invalid_opt; + const char *repo_path; + int ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (cli_opt__show_help) { + print_help(); + return 0; + } + + init_opts.flags |= GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + if (bare && git_dir) + return cli_error_usage("the '--bare' and '--separate-git-dir' options cannot be used together"); + + if (bare) + init_opts.flags |= GIT_REPOSITORY_INIT_BARE; + + init_opts.template_path = template_dir; + init_opts.initial_head = branch; + + if (git_dir) { + init_opts.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + init_opts.workdir_path = path; + + repo_path = git_dir; + } else { + repo_path = path; + } + + if (git_repository_init_ext(&repo, repo_path, &init_opts) < 0) { + ret = cli_error_git(); + } else if (!quiet) { + printf("Initialized empty Git repository in %s\n", + git_repository_path(repo)); + } + + git_repository_free(repo); + return ret; +} diff --git a/src/cli/common.c b/src/cli/common.c new file mode 100644 index 00000000..dbeefea4 --- /dev/null +++ b/src/cli/common.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include + +#include "git2_util.h" +#include "vector.h" +#include "fs_path.h" + +#include "common.h" +#include "error.h" + +static int parse_option(cli_opt *opt, void *data) +{ + git_str kv = GIT_STR_INIT, env = GIT_STR_INIT; + git_vector *cmdline_config = data; + int error = 0; + + if (opt->spec && opt->spec->alias == 'c') { + if (git_str_puts(&kv, opt->value) < 0) { + error = cli_error_git(); + goto done; + } + } + + else if (opt->spec && !strcmp(opt->spec->name, "config-env")) { + char *val = strchr(opt->value, '='); + + if (val == NULL || *(val + 1) == '\0') { + error = cli_error("invalid config format: '%s'", opt->value); + goto done; + } + + if (git_str_put(&kv, opt->value, (val - opt->value)) < 0) { + error = cli_error_git(); + goto done; + } + + val++; + + if ((error = git__getenv(&env, val)) == GIT_ENOTFOUND) { + error = cli_error("missing environment variable '%s' for configuration '%s'", val, kv.ptr); + goto done; + } else if (error) { + error = cli_error_git(); + goto done; + } + + if (git_str_putc(&kv, '=') < 0 || + git_str_puts(&kv, env.ptr) < 0) { + error = cli_error_git(); + goto done; + } + } + + if (kv.size > 0 && + git_vector_insert(cmdline_config, git_str_detach(&kv)) < 0) + error = cli_error_git(); + +done: + git_str_dispose(&env); + git_str_dispose(&kv); + return error; +} + +static int parse_common_options( + git_repository *repo, + cli_repository_open_options *opts) +{ + cli_opt_spec common_opts[] = { + { CLI_COMMON_OPT_CONFIG }, + { CLI_COMMON_OPT_CONFIG_ENV }, + { 0 } + }; + git_config_backend_memory_options config_opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + git_vector cmdline = GIT_VECTOR_INIT; + git_config *config = NULL; + git_config_backend *backend = NULL; + int error = 0; + + config_opts.backend_type = "command line"; + + if ((error = cli_opt_foreach(common_opts, opts->args, + opts->args_len, CLI_OPT_PARSE_GNU, parse_option, + &cmdline)) < 0) + goto done; + + if (git_vector_length(&cmdline) == 0) + goto done; + + if (git_repository_config(&config, repo) < 0 || + git_config_backend_from_values(&backend, + (const char **)cmdline.contents, cmdline.length, + &config_opts) < 0 || + git_config_add_backend(config, backend, GIT_CONFIG_LEVEL_APP, + repo, 0) < 0) + error = cli_error_git(); + +done: + if (error && backend) + backend->free(backend); + git_config_free(config); + git_vector_dispose_deep(&cmdline); + return error; +} + +int cli_repository_open( + git_repository **out, + cli_repository_open_options *opts) +{ + git_repository *repo; + + if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) + return -1; + + if (opts && parse_common_options(repo, opts) < 0) + return -1; + + *out = repo; + return 0; +} + +/* + * This resolves paths - not _pathspecs_ like git - it accepts an absolute + * path (to a path within the repository working directory) or a path + * relative to the current directory. + */ +int cli_resolve_path(git_str *out, git_repository *repo, const char *given_path) +{ + git_str path = GIT_STR_INIT; + int error = 0; + + if (!git_fs_path_is_absolute(given_path)) { + char cwd[GIT_PATH_MAX]; + + if (p_getcwd(cwd, GIT_PATH_MAX) < 0) + error = cli_error_os(); + else if (git_str_puts(&path, cwd) < 0 || + git_fs_path_apply_relative(&path, given_path) < 0) + error = cli_error_git(); + + if (error) + goto done; + } else if (git_str_puts(&path, given_path) < 0) { + error = cli_error_git(); + goto done; + } + + error = git_fs_path_make_relative(&path, git_repository_workdir(repo)); + + if (error == GIT_ENOTFOUND) + error = cli_error("path '%s' is not inside the git repository '%s'", + given_path, git_repository_workdir(repo)); + else if (error < 0) + error = cli_error_git(); + else + git_str_swap(out, &path); + +done: + git_str_dispose(&path); + return error; +} diff --git a/src/cli/common.h b/src/cli/common.h new file mode 100644 index 00000000..330f776c --- /dev/null +++ b/src/cli/common.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef CLI_common_h__ +#define CLI_common_h__ + +#define PROGRAM_NAME "git2" + +#include "git2_util.h" + +#include "error.h" +#include "opt.h" +#include "opt_usage.h" + +/* + * Common command arguments. + */ + +extern int cli_opt__show_help; +extern int cli_opt__use_pager; + +#define CLI_COMMON_OPT_HELP \ + CLI_OPT_TYPE_SWITCH, "help", 0, &cli_opt__show_help, 1, \ + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, \ + NULL, "display help information" +#define CLI_COMMON_OPT_CONFIG \ + CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0, \ + CLI_OPT_USAGE_HIDDEN, \ + "key=value", "add configuration value" +#define CLI_COMMON_OPT_CONFIG_ENV \ + CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0, \ + CLI_OPT_USAGE_HIDDEN, \ + "key=value", "set configuration value to environment variable" +#define CLI_COMMON_OPT_NO_PAGER \ + CLI_OPT_TYPE_SWITCH, "no-pager", 0, &cli_opt__use_pager, 0, \ + CLI_OPT_USAGE_HIDDEN, \ + NULL, "don't paginate multi-page output" + +#define CLI_COMMON_OPT \ + { CLI_COMMON_OPT_HELP }, \ + { CLI_COMMON_OPT_CONFIG }, \ + { CLI_COMMON_OPT_CONFIG_ENV }, \ + { CLI_COMMON_OPT_NO_PAGER } + +typedef struct { + char **args; + int args_len; +} cli_repository_open_options; + +extern int cli_repository_open( + git_repository **out, + cli_repository_open_options *opts); + +extern int cli_resolve_path( + git_str *out, + git_repository *repo, + const char *given_path); + +#endif /* CLI_common_h__ */ diff --git a/src/cli/error.h b/src/cli/error.h index cce7a54c..abf8a516 100644 --- a/src/cli/error.h +++ b/src/cli/error.h @@ -8,7 +8,7 @@ #ifndef CLI_error_h__ #define CLI_error_h__ -#include "cli.h" +#include "common.h" #include #define CLI_EXIT_OK 0 diff --git a/src/cli/main.c b/src/cli/main.c index cbfc50ee..be913ba6 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -7,17 +7,19 @@ #include #include -#include "cli.h" +#include "common.h" #include "cmd.h" -static int show_help = 0; +int cli_opt__show_help = 0; +int cli_opt__use_pager = 1; + static int show_version = 0; static char *command = NULL; static char **args = NULL; const cli_opt_spec cli_common_opts[] = { - { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, - CLI_OPT_USAGE_DEFAULT, NULL, "display help information" }, + CLI_COMMON_OPT, + { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1, CLI_OPT_USAGE_DEFAULT, NULL, "display the version" }, { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, @@ -28,21 +30,57 @@ const cli_opt_spec cli_common_opts[] = { }; const cli_cmd_spec cli_cmds[] = { + { "blame", cmd_blame, "Show the origin of each line of a file" }, { "cat-file", cmd_cat_file, "Display an object in the repository" }, { "clone", cmd_clone, "Clone a repository into a new directory" }, + { "config", cmd_config, "View or set configuration values " }, { "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" }, { "help", cmd_help, "Display help information" }, + { "index-pack", cmd_index_pack, "Create an index for a packfile" }, + { "init", cmd_init, "Create a new git repository" }, { NULL } }; +/* + * Reorder the argv as it was given, since git has the notion of global + * options (like `--help` or `-c key=val`) that we want to pass to the + * subcommand, and that can appear early in the arguments, before the + * command name. Put the command-name in argv[1] to allow easier parsing. + */ +static void reorder_args(char **argv, size_t first) +{ + char *tmp; + size_t i; + + if (first == 1) + return; + + tmp = argv[first]; + + for (i = first; i > 1; i--) + argv[i] = argv[i - 1]; + + argv[1] = tmp; +} + +/* + * When invoked without a command, or just with `--help`, we invoke + * the help command; but we want to preserve only arguments that would + * be useful for that. + */ +static void help_args(int *argc, char **argv) +{ + cli_opt__show_help = 0; + + argv[0] = "help"; + *argc = 1; +} + int main(int argc, char **argv) { const cli_cmd_spec *cmd; cli_opt_parser optparser; cli_opt opt; - char *help_args[3] = { NULL }; - int help_args_len; - int args_len = 0; int ret = 0; if (git_libgit2_init() < 0) { @@ -56,7 +94,7 @@ int main(int argc, char **argv) while (cli_opt_parser_next(&opt, &optparser)) { if (!opt.spec) { cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt); - cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, cli_common_opts); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, cli_common_opts, CLI_OPT_USAGE_SHOW_HIDDEN); ret = CLI_EXIT_USAGE; goto done; } @@ -66,8 +104,7 @@ int main(int argc, char **argv) * remaining arguments as args for the command itself. */ if (command) { - args = &argv[optparser.idx]; - args_len = (int)(argc - optparser.idx); + reorder_args(argv, optparser.idx); break; } } @@ -77,19 +114,10 @@ int main(int argc, char **argv) goto done; } - /* - * If `--help ` is specified, delegate to that command's - * `--help` option. If no command is specified, run the `help` - * command. Do this by updating the args to emulate that behavior. - */ - if (!command || show_help) { - help_args[0] = command ? (char *)command : "help"; - help_args[1] = command ? "--help" : NULL; - help_args_len = command ? 2 : 1; - - command = help_args[0]; - args = help_args; - args_len = help_args_len; + if (!command) { + help_args(&argc, argv); + ret = cmd_help(argc, argv); + goto done; } if ((cmd = cli_cmd_spec_byname(command)) == NULL) { @@ -98,7 +126,7 @@ int main(int argc, char **argv) goto done; } - ret = cmd->fn(args_len, args); + ret = cmd->fn(argc - 1, &argv[1]); done: git_libgit2_shutdown(); diff --git a/src/cli/opt.c b/src/cli/opt.c index 62a3430d..204de10c 100644 --- a/src/cli/opt.c +++ b/src/cli/opt.c @@ -10,7 +10,7 @@ * This file was produced by using the `rename.pl` script included with * adopt. The command-line specified was: * - * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage + * ./rename.pl cli_opt --filename=opt --include=common.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #include @@ -19,7 +19,11 @@ #include #include -#include "cli.h" +#if defined(__sun) || defined(__illumos__) || defined(__CYGWIN__) +# include +#endif + +#include "common.h" #include "opt.h" #ifdef _WIN32 @@ -73,7 +77,7 @@ GIT_INLINE(const cli_opt_spec *) spec_for_long( /* Handle --option=value arguments */ if (spec->type == CLI_OPT_TYPE_VALUE && - eql && + spec->name && eql && strncmp(arg, spec->name, eql_pos) == 0 && spec->name[eql_pos] == '\0') { *has_value = 1; @@ -575,6 +579,28 @@ cli_opt_status_t cli_opt_parse( return validate_required(opt, specs, given_specs); } +int cli_opt_foreach( + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags, + int (*callback)(cli_opt *, void *), + void *callback_data) +{ + cli_opt_parser parser; + cli_opt opt; + int ret; + + cli_opt_parser_init(&parser, specs, args, args_len, flags); + + while (cli_opt_parser_next(&opt, &parser)) { + if ((ret = callback(&opt, callback_data)) != 0) + return ret; + } + + return 0; +} + static int spec_name_fprint(FILE *file, const cli_opt_spec *spec) { int error; diff --git a/src/cli/opt.h b/src/cli/opt.h index 6c1d4603..226f74db 100644 --- a/src/cli/opt.h +++ b/src/cli/opt.h @@ -10,7 +10,7 @@ * This file was produced by using the `rename.pl` script included with * adopt. The command-line specified was: * - * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage + * ./rename.pl cli_opt --filename=opt --include=common.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #ifndef CLI_opt_h__ @@ -275,8 +275,8 @@ typedef struct cli_opt_parser { size_t arg_idx; size_t in_args; size_t in_short; - int needs_sort : 1, - in_literal : 1; + unsigned int needs_sort : 1, + in_literal : 1; } cli_opt_parser; /** @@ -300,6 +300,24 @@ cli_opt_status_t cli_opt_parse( size_t args_len, unsigned int flags); +/** + * Quickly executes the given callback for each argument. + * + * @param specs A NULL-terminated array of `cli_opt_spec`s that can be parsed + * @param args The arguments that will be parsed + * @param args_len The length of arguments to be parsed + * @param flags The `cli_opt_flag_t flags for parsing + * @param callback The callback to invoke for each specified option + * @param callback_data Data to be provided to the callback + */ +int cli_opt_foreach( + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags, + int (*callback)(cli_opt *, void *), + void *callback_data); + /** * Initializes a parser that parses the given arguments according to the * given specifications. diff --git a/src/cli/opt_usage.c b/src/cli/opt_usage.c index 478b4163..b887509d 100644 --- a/src/cli/opt_usage.c +++ b/src/cli/opt_usage.c @@ -5,35 +5,84 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "cli.h" +#include "common.h" #include "str.h" -static int print_spec_name(git_str *out, const cli_opt_spec *spec) +#define is_switch_or_value(spec) \ + ((spec)->type == CLI_OPT_TYPE_SWITCH || \ + (spec)->type == CLI_OPT_TYPE_VALUE) + +static int print_spec_args(git_str *out, const cli_opt_spec *spec) { - if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && - !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && - !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - return git_str_printf(out, "-%c <%s>", spec->alias, spec->value_name); - if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && - !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - return git_str_printf(out, "-%c [<%s>]", spec->alias, spec->value_name); - if (spec->type == CLI_OPT_TYPE_VALUE && - !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) - return git_str_printf(out, "--%s[=<%s>]", spec->name, spec->value_name); - if (spec->type == CLI_OPT_TYPE_VALUE) - return git_str_printf(out, "--%s=<%s>", spec->name, spec->value_name); + GIT_ASSERT(!is_switch_or_value(spec)); + if (spec->type == CLI_OPT_TYPE_ARG) return git_str_printf(out, "<%s>", spec->value_name); if (spec->type == CLI_OPT_TYPE_ARGS) return git_str_printf(out, "<%s>...", spec->value_name); if (spec->type == CLI_OPT_TYPE_LITERAL) return git_str_printf(out, "--"); - if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + + GIT_ASSERT(!"unknown option spec type"); + return -1; +} + +GIT_INLINE(int) print_spec_alias(git_str *out, const cli_opt_spec *spec) +{ + GIT_ASSERT(is_switch_or_value(spec) && spec->alias); + + if (spec->type == CLI_OPT_TYPE_VALUE && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + return git_str_printf(out, "-%c <%s>", spec->alias, spec->value_name); + else if (spec->type == CLI_OPT_TYPE_VALUE) + return git_str_printf(out, "-%c [<%s>]", spec->alias, spec->value_name); + else return git_str_printf(out, "-%c", spec->alias); - if (spec->name) +} + +GIT_INLINE(int) print_spec_name(git_str *out, const cli_opt_spec *spec) +{ + GIT_ASSERT(is_switch_or_value(spec) && spec->name); + + if (spec->type == CLI_OPT_TYPE_VALUE && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + return git_str_printf(out, "--%s=<%s>", spec->name, spec->value_name); + else if (spec->type == CLI_OPT_TYPE_VALUE) + return git_str_printf(out, "--%s[=<%s>]", spec->name, spec->value_name); + else return git_str_printf(out, "--%s", spec->name); +} + +GIT_INLINE(int) print_spec_full(git_str *out, const cli_opt_spec *spec) +{ + int error = 0; - GIT_ASSERT(0); + if (is_switch_or_value(spec)) { + if (spec->alias) + error |= print_spec_alias(out, spec); + + if (spec->alias && spec->name) + error |= git_str_printf(out, ", "); + + if (spec->name) + error |= print_spec_name(out, spec); + } else { + error |= print_spec_args(out, spec); + } + + return error; +} + +GIT_INLINE(int) print_spec(git_str *out, const cli_opt_spec *spec) +{ + if (is_switch_or_value(spec)) { + if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return print_spec_alias(out, spec); + else + return print_spec_name(out, spec); + } + + return print_spec_args(out, spec); } /* @@ -46,7 +95,8 @@ int cli_opt_usage_fprint( FILE *file, const char *command, const char *subcommand, - const cli_opt_spec specs[]) + const cli_opt_spec specs[], + unsigned int print_flags) { git_str usage = GIT_BUF_INIT, opt = GIT_BUF_INIT; const cli_opt_spec *spec; @@ -55,7 +105,7 @@ int cli_opt_usage_fprint( int error; /* TODO: query actual console width. */ - int console_width = 80; + int console_width = 78; if ((error = git_str_printf(&usage, "usage: %s", command)) < 0) goto done; @@ -73,7 +123,8 @@ int cli_opt_usage_fprint( next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); - if (spec->usage & CLI_OPT_USAGE_HIDDEN) + if ((spec->usage & CLI_OPT_USAGE_HIDDEN) && + !(print_flags & CLI_OPT_USAGE_SHOW_HIDDEN)) continue; if (choice) @@ -86,7 +137,7 @@ int cli_opt_usage_fprint( if (!optional && !choice && next_choice) git_str_putc(&opt, '('); - if ((error = print_spec_name(&opt, spec)) < 0) + if ((error = print_spec(&opt, spec)) < 0) goto done; if (!optional && choice && !next_choice) @@ -111,11 +162,11 @@ int cli_opt_usage_fprint( git_str_putc(&usage, ' '); linelen = prefixlen; - } else { - git_str_putc(&usage, ' '); - linelen += git_str_len(&opt) + 1; } + git_str_putc(&usage, ' '); + linelen += git_str_len(&opt) + 1; + git_str_puts(&usage, git_str_cstr(&opt)); if (git_str_oom(&usage)) { @@ -140,7 +191,7 @@ int cli_opt_usage_error( const cli_opt *invalid_opt) { cli_opt_status_fprint(stderr, PROGRAM_NAME, invalid_opt); - cli_opt_usage_fprint(stderr, PROGRAM_NAME, subcommand, specs); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, subcommand, specs, 0); return CLI_EXIT_USAGE; } @@ -150,35 +201,53 @@ int cli_opt_help_fprint( { git_str help = GIT_BUF_INIT; const cli_opt_spec *spec; + bool required; int error = 0; /* Display required arguments first */ for (spec = specs; spec->type; ++spec) { - if (! (spec->usage & CLI_OPT_USAGE_REQUIRED) || - (spec->usage & CLI_OPT_USAGE_HIDDEN)) + if ((spec->usage & CLI_OPT_USAGE_HIDDEN) || + (spec->type == CLI_OPT_TYPE_LITERAL)) + continue; + + required = ((spec->usage & CLI_OPT_USAGE_REQUIRED) || + ((spec->usage & CLI_OPT_USAGE_CHOICE) && required)); + + if (!required) continue; git_str_printf(&help, " "); - if ((error = print_spec_name(&help, spec)) < 0) + if ((error = print_spec_full(&help, spec)) < 0) goto done; - git_str_printf(&help, ": %s\n", spec->help); + git_str_printf(&help, "\n"); + + if (spec->help) + git_str_printf(&help, " %s\n", spec->help); } /* Display the remaining arguments */ for (spec = specs; spec->type; ++spec) { - if ((spec->usage & CLI_OPT_USAGE_REQUIRED) || - (spec->usage & CLI_OPT_USAGE_HIDDEN)) + if ((spec->usage & CLI_OPT_USAGE_HIDDEN) || + (spec->type == CLI_OPT_TYPE_LITERAL)) + continue; + + required = ((spec->usage & CLI_OPT_USAGE_REQUIRED) || + ((spec->usage & CLI_OPT_USAGE_CHOICE) && required)); + + if (required) continue; git_str_printf(&help, " "); - if ((error = print_spec_name(&help, spec)) < 0) + if ((error = print_spec_full(&help, spec)) < 0) goto done; - git_str_printf(&help, ": %s\n", spec->help); + git_str_printf(&help, "\n"); + if (spec->help) + git_str_printf(&help, " %s\n", spec->help); } if (git_str_oom(&help) || diff --git a/src/cli/opt_usage.h b/src/cli/opt_usage.h index c752494e..b9b75b84 100644 --- a/src/cli/opt_usage.h +++ b/src/cli/opt_usage.h @@ -8,6 +8,10 @@ #ifndef CLI_opt_usage_h__ #define CLI_opt_usage_h__ +typedef enum { + CLI_OPT_USAGE_SHOW_HIDDEN = (1 << 0), +} cli_opt_usage_flags; + /** * Prints usage information to the given file handle. * @@ -21,7 +25,8 @@ int cli_opt_usage_fprint( FILE *file, const char *command, const char *subcommand, - const cli_opt_spec specs[]); + const cli_opt_spec specs[], + unsigned int print_flags); int cli_opt_usage_error( const char *subcommand, diff --git a/src/cli/progress.c b/src/cli/progress.c index ddfbafb7..d975b095 100644 --- a/src/cli/progress.c +++ b/src/cli/progress.c @@ -242,7 +242,21 @@ static int fetch_receiving( done ? ", done." : ""); } -static int fetch_resolving( +static int indexer_indexing( + cli_progress *progress, + const git_indexer_progress *stats) +{ + bool done = (stats->received_objects == stats->total_objects); + + return progress_printf(progress, false, + "Indexing objects: %3d%% (%d/%d)%s\r", + percent(stats->received_objects, stats->total_objects), + stats->received_objects, + stats->total_objects, + done ? ", done." : ""); +} + +static int indexer_resolving( cli_progress *progress, const git_indexer_progress *stats) { @@ -283,7 +297,42 @@ int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload /* fall through */ case CLI_PROGRESS_RESOLVING: - error = fetch_resolving(progress, stats); + error = indexer_resolving(progress, stats); + break; + + default: + /* should not be reached */ + GIT_ASSERT(!"unexpected progress state"); + } + + return error; +} + +int cli_progress_indexer( + const git_indexer_progress *stats, + void *payload) +{ + cli_progress *progress = (cli_progress *)payload; + int error = 0; + + switch (progress->action) { + case CLI_PROGRESS_NONE: + progress->action = CLI_PROGRESS_INDEXING; + /* fall through */ + + case CLI_PROGRESS_INDEXING: + if ((error = indexer_indexing(progress, stats)) < 0) + break; + + if (stats->indexed_deltas == stats->total_deltas) + break; + + progress_complete(progress); + progress->action = CLI_PROGRESS_RESOLVING; + /* fall through */ + + case CLI_PROGRESS_RESOLVING: + error = indexer_resolving(progress, stats); break; default: diff --git a/src/cli/progress.h b/src/cli/progress.h index 886fef89..f08d68f1 100644 --- a/src/cli/progress.h +++ b/src/cli/progress.h @@ -22,6 +22,7 @@ typedef enum { CLI_PROGRESS_NONE, CLI_PROGRESS_RECEIVING, + CLI_PROGRESS_INDEXING, CLI_PROGRESS_RESOLVING, CLI_PROGRESS_CHECKING_OUT } cli_progress_t; @@ -74,6 +75,17 @@ extern int cli_progress_fetch_transfer( const git_indexer_progress *stats, void *payload); +/** + * Prints indexer progress to the console. Suitable for a + * `progress_cb` callback for `git_indexer_options`. + * + * @param stats The indexer stats + * @param payload A pointer to the cli_progress + */ +extern int cli_progress_indexer( + const git_indexer_progress *stats, + void *payload); + /** * Prints checkout progress to the console. Suitable for a * `progress_cb` callback for `git_checkout_options`. diff --git a/src/cli/unix/sighandler.c b/src/cli/unix/sighandler.c index 6b4982d4..05ac8672 100644 --- a/src/cli/unix/sighandler.c +++ b/src/cli/unix/sighandler.c @@ -8,7 +8,8 @@ #include #include #include "git2_util.h" -#include "cli.h" +#include "common.h" +#include "sighandler.h" static void (*interrupt_handler)(void) = NULL; diff --git a/src/cli/win32/precompiled.h b/src/cli/win32/precompiled.h index b0309b86..031370e8 100644 --- a/src/cli/win32/precompiled.h +++ b/src/cli/win32/precompiled.h @@ -1,3 +1,3 @@ #include -#include "cli.h" +#include "common.h" diff --git a/src/cli/win32/sighandler.c b/src/cli/win32/sighandler.c index cc0b6464..05a67fb1 100644 --- a/src/cli/win32/sighandler.c +++ b/src/cli/win32/sighandler.c @@ -8,7 +8,7 @@ #include "git2_util.h" #include -#include "cli.h" +#include "sighandler.h" static void (*interrupt_handler)(void) = NULL; diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 876a703e..0dddb025 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -2,10 +2,9 @@ # git library functionality. add_library(libgit2 OBJECT) -set_target_properties(libgit2 PROPERTIES C_STANDARD 90) -set_target_properties(libgit2 PROPERTIES C_EXTENSIONS OFF) include(PkgBuildConfig) +include(CMakePackageConfigHelpers) set(LIBGIT2_INCLUDES "${PROJECT_BINARY_DIR}/src/util" @@ -59,18 +58,14 @@ set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) add_library(libgit2package ${SRC_RC} ${LIBGIT2_OBJECTS}) target_link_libraries(libgit2package ${LIBGIT2_SYSTEM_LIBS}) target_include_directories(libgit2package SYSTEM PRIVATE ${LIBGIT2_INCLUDES}) +target_include_directories(libgit2package INTERFACE $) set_target_properties(libgit2package PROPERTIES C_STANDARD 90) +set_target_properties(libgit2package PROPERTIES C_EXTENSIONS OFF) set_target_properties(libgit2package PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set_target_properties(libgit2package PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set_target_properties(libgit2package PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) -# Win64+MSVC+static libs = linker error -if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) - set_target_properties(libgit2package PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") -endif() - ide_split_sources(libgit2package) if(SONAME) @@ -108,9 +103,32 @@ FILE(READ "${PROJECT_SOURCE_DIR}/include/git2.h" LIBGIT2_INCLUDE) STRING(REGEX REPLACE "#include \"git2\/" "#include \"${LIBGIT2_FILENAME}/" LIBGIT2_INCLUDE "${LIBGIT2_INCLUDE}") FILE(WRITE "${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h" ${LIBGIT2_INCLUDE}) +# cmake package targets + +set(LIBGIT2_TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") + +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +configure_file(config.cmake.in + "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake" + @ONLY) + +install(FILES + "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake" + "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +install( + EXPORT ${LIBGIT2_TARGETS_EXPORT_NAME} + NAMESPACE "${PROJECT_NAME}::" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + # Install install(TARGETS libgit2package + EXPORT ${LIBGIT2_TARGETS_EXPORT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libgit2/apply.c b/src/libgit2/apply.c index 6b55b812..07e502db 100644 --- a/src/libgit2/apply.c +++ b/src/libgit2/apply.c @@ -20,6 +20,7 @@ #include "reader.h" #include "index.h" #include "repository.h" +#include "hashmap_str.h" #include "apply.h" typedef struct { @@ -96,7 +97,7 @@ static void patch_image_free(patch_image *image) return; git_pool_clear(&image->pool); - git_vector_free(&image->lines); + git_vector_dispose(&image->lines); } static bool match_hunk( @@ -452,7 +453,7 @@ static int apply_one( git_reader *postimage_reader, git_index *postimage, git_diff *diff, - git_strmap *removed_paths, + git_hashset_str *removed_paths, size_t i, const git_apply_options *opts) { @@ -489,7 +490,7 @@ static int apply_one( */ if (delta->status != GIT_DELTA_RENAMED && delta->status != GIT_DELTA_ADDED) { - if (git_strmap_exists(removed_paths, delta->old_file.path)) { + if (git_hashset_str_contains(removed_paths, delta->old_file.path)) { error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path); goto done; } @@ -573,11 +574,11 @@ static int apply_one( if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_DELETED) - error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path); + error = git_hashset_str_add(removed_paths, delta->old_file.path); if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_ADDED) - git_strmap_delete(removed_paths, delta->new_file.path); + git_hashset_str_remove(removed_paths, delta->new_file.path); done: git_str_dispose(&pre_contents); @@ -597,20 +598,17 @@ static int apply_deltas( git_diff *diff, const git_apply_options *opts) { - git_strmap *removed_paths; + git_hashset_str removed_paths = GIT_HASHSET_INIT; size_t i; int error = 0; - if (git_strmap_new(&removed_paths) < 0) - return -1; - for (i = 0; i < git_diff_num_deltas(diff); i++) { - if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0) + if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, &removed_paths, i, opts)) < 0) goto done; } done: - git_strmap_free(removed_paths); + git_hashset_str_dispose(&removed_paths); return error; } @@ -715,7 +713,6 @@ static int git_apply__to_workdir( goto done; } - checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE; checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; @@ -730,7 +727,7 @@ static int git_apply__to_workdir( error = git_checkout_index(repo, postimage, &checkout_opts); done: - git_vector_free(&paths); + git_vector_dispose(&paths); return error; } diff --git a/src/libgit2/attr.c b/src/libgit2/attr.c index 1623b1d4..8d096d7d 100644 --- a/src/libgit2/attr.c +++ b/src/libgit2/attr.c @@ -13,6 +13,7 @@ #include "attr_file.h" #include "ignore.h" #include "git2/oid.h" +#include "hashmap_str.h" #include const char *git_attr__true = "[internal]__TRUE__"; @@ -254,7 +255,7 @@ int git_attr_foreach_ext( git_attr_file *file; git_attr_rule *rule; git_attr_assignment *assign; - git_strmap *seen = NULL; + git_hashset_str seen = GIT_HASHSET_INIT; git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; GIT_ASSERT_ARG(repo); @@ -267,8 +268,7 @@ int git_attr_foreach_ext( if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) return -1; - if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 || - (error = git_strmap_new(&seen)) < 0) + if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0) goto cleanup; git_vector_foreach(&files, i, file) { @@ -277,10 +277,10 @@ int git_attr_foreach_ext( git_vector_foreach(&rule->assigns, k, assign) { /* skip if higher priority assignment was already seen */ - if (git_strmap_exists(seen, assign->name)) + if (git_hashset_str_contains(&seen, assign->name)) continue; - if ((error = git_strmap_set(seen, assign->name, assign)) < 0) + if ((error = git_hashset_str_add(&seen, assign->name)) < 0) goto cleanup; error = callback(assign->name, assign->value, payload); @@ -293,7 +293,7 @@ int git_attr_foreach_ext( } cleanup: - git_strmap_free(seen); + git_hashset_str_dispose(&seen); release_attr_files(&files); git_attr_path__free(&path); @@ -384,6 +384,8 @@ static int attr_setup( git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE, NULL }; git_attr_file_source head_source = { GIT_ATTR_FILE_SOURCE_HEAD, NULL, GIT_ATTR_FILE, NULL }; git_attr_file_source commit_source = { GIT_ATTR_FILE_SOURCE_COMMIT, NULL, GIT_ATTR_FILE, NULL }; + git_attr_cache *attrcache; + const char *attr_cfg_file = NULL; git_index *idx = NULL; const char *workdir; int error = 0; @@ -407,8 +409,10 @@ static int attr_setup( error = 0; } - if ((error = preload_attr_file(repo, attr_session, NULL, - git_repository_attr_cache(repo)->cfg_attr_file)) < 0) + if ((attrcache = git_repository_attr_cache(repo)) != NULL) + attr_cfg_file = git_attr_cache_attributesfile(attrcache); + + if ((error = preload_attr_file(repo, attr_session, NULL, attr_cfg_file)) < 0) goto out; if ((error = git_repository__item_path(&info, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || @@ -424,9 +428,13 @@ static int attr_setup( goto out; if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = preload_attr_source(repo, attr_session, &index_source)) < 0) + (error = preload_attr_source(repo, attr_session, &index_source)) < 0) { + if (error != GIT_ENOTFOUND) goto out; + error = 0; + } + if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) && (error = preload_attr_source(repo, attr_session, &head_source)) < 0) goto out; @@ -460,6 +468,7 @@ int git_attr_add_macro( { int error; git_attr_rule *macro = NULL; + git_attr_cache *attrcache; git_pool *pool; GIT_ASSERT_ARG(repo); @@ -471,7 +480,8 @@ int git_attr_add_macro( macro = git__calloc(1, sizeof(git_attr_rule)); GIT_ERROR_CHECK_ALLOC(macro); - pool = &git_repository_attr_cache(repo)->pool; + attrcache = git_repository_attr_cache(repo); + pool = git_attr_cache_pool(attrcache); macro->match.pattern = git_pool_strdup(pool, name); GIT_ERROR_CHECK_ALLOC(macro->match.pattern); @@ -614,7 +624,7 @@ static void release_attr_files(git_vector *files) git_attr_file__free(file); files->contents[i] = NULL; } - git_vector_free(files); + git_vector_dispose(files); } static int collect_attr_files( @@ -627,6 +637,8 @@ static int collect_attr_files( int error = 0; git_str dir = GIT_STR_INIT, attrfile = GIT_STR_INIT; const char *workdir = git_repository_workdir(repo); + git_attr_cache *attrcache; + const char *attr_cfg_file = NULL; attr_walk_up_info info = { NULL }; GIT_ASSERT(!git_fs_path_is_absolute(path)); @@ -675,8 +687,13 @@ static int collect_attr_files( if (error < 0) goto cleanup; - if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { - error = push_attr_file(repo, attr_session, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if ((attrcache = git_repository_attr_cache(repo)) != NULL) + attr_cfg_file = git_attr_cache_attributesfile(attrcache); + + + if (attr_cfg_file) { + error = push_attr_file(repo, attr_session, files, NULL, attr_cfg_file); + if (error < 0) goto cleanup; } diff --git a/src/libgit2/attr_file.c b/src/libgit2/attr_file.c index afa8ec7b..56647042 100644 --- a/src/libgit2/attr_file.c +++ b/src/libgit2/attr_file.c @@ -69,7 +69,7 @@ int git_attr_file__clear_rules(git_attr_file *file, bool need_lock) git_vector_foreach(&file->rules, i, rule) git_attr_rule__free(rule); - git_vector_free(&file->rules); + git_vector_dispose(&file->rules); if (need_lock) git_mutex_unlock(&file->lock); @@ -143,6 +143,8 @@ int git_attr_file__load( blobsize = git_blob_rawsize(blob); GIT_ERROR_CHECK_BLOBSIZE(blobsize); + if (blobsize > GIT_ATTR_MAX_FILE_SIZE) /* TODO: issue warning when warning API is available */ + goto cleanup; git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize); break; } @@ -155,6 +157,7 @@ int git_attr_file__load( if (p_stat(entry->fullpath, &st) < 0 || S_ISDIR(st.st_mode) || (fd = git_futils_open_ro(entry->fullpath)) < 0 || + (st.st_size > GIT_ATTR_MAX_FILE_SIZE) || (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) nonexistent = true; @@ -198,6 +201,8 @@ int git_attr_file__load( blobsize = git_blob_rawsize(blob); GIT_ERROR_CHECK_BLOBSIZE(blobsize); + if (blobsize > GIT_ATTR_MAX_FILE_SIZE) /* TODO: issue warning when warning API is available */ + goto cleanup; if ((error = git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize)) < 0) goto cleanup; @@ -342,7 +347,7 @@ int git_attr_file__parse_buffer( { const char *scan = data, *context = NULL; git_attr_rule *rule = NULL; - int error = 0; + int ignorecase = 0, error = 0; /* If subdir file path, convert context for file paths */ if (attrs->entry && git_fs_path_root(attrs->entry->path) < 0 && @@ -374,6 +379,13 @@ int git_attr_file__parse_buffer( continue; } + if (repo && + (error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) + goto out; + + if (ignorecase) + rule->match.flags |= GIT_ATTR_FNMATCH_ICASE; + if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) { /* TODO: warning if macro found in file below repo root */ if (!allow_macros) @@ -477,7 +489,7 @@ bool git_attr_fnmatch__match( */ if (match->containing_dir) { if (match->flags & GIT_ATTR_FNMATCH_ICASE) { - if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length)) + if (git__prefixcmp_icase(path->path, match->containing_dir)) return 0; } else { if (git__prefixcmp(path->path, match->containing_dir)) @@ -991,7 +1003,7 @@ static void git_attr_rule__clear(git_attr_rule *rule) if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { git_vector_foreach(&rule->assigns, i, assign) GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); - git_vector_free(&rule->assigns); + git_vector_dispose(&rule->assigns); } /* match.pattern is stored in a git_pool, so no need to free */ diff --git a/src/libgit2/attr_file.h b/src/libgit2/attr_file.h index 08630d1a..8025359b 100644 --- a/src/libgit2/attr_file.h +++ b/src/libgit2/attr_file.h @@ -21,6 +21,8 @@ #define GIT_ATTR_FILE_SYSTEM "gitattributes" #define GIT_ATTR_FILE_XDG "attributes" +#define GIT_ATTR_MAX_FILE_SIZE 100 * 1024 * 1024 + #define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) #define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) #define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) diff --git a/src/libgit2/attrcache.c b/src/libgit2/attrcache.c index 405944ed..2a822147 100644 --- a/src/libgit2/attrcache.c +++ b/src/libgit2/attrcache.c @@ -13,6 +13,38 @@ #include "sysdir.h" #include "ignore.h" #include "path.h" +#include "hashmap_str.h" + +GIT_HASHMAP_STR_SETUP(git_attr_cache_filemap, git_attr_file_entry *); +GIT_HASHMAP_STR_SETUP(git_attr_cache_macromap, git_attr_rule *); + +struct git_attr_cache { + char *cfg_attr_file; /* cached value of core.attributesfile */ + char *cfg_excl_file; /* cached value of core.excludesfile */ + + /* hash path to git_attr_file_entry records */ + git_attr_cache_filemap files; + /* hash name to git_attr_rule */ + git_attr_cache_macromap macros; + + git_mutex lock; + git_pool pool; +}; + +const char *git_attr_cache_attributesfile(git_attr_cache *cache) +{ + return cache->cfg_attr_file; +} + +const char *git_attr_cache_excludesfile(git_attr_cache *cache) +{ + return cache->cfg_excl_file; +} + +git_pool *git_attr_cache_pool(git_attr_cache *cache) +{ + return &cache->pool; +} GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache) { @@ -34,7 +66,12 @@ GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache) GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry( git_attr_cache *cache, const char *path) { - return git_strmap_get(cache->files, path); + git_attr_file_entry *result; + + if (git_attr_cache_filemap_get(&result, &cache->files, path) == 0) + return result; + + return NULL; } int git_attr_cache__alloc_file_entry( @@ -92,7 +129,7 @@ static int attr_cache_make_entry( git_repository_workdir(repo), path, &cache->pool)) < 0) return error; - if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0) + if ((error = git_attr_cache_filemap_put(&cache->files, entry->path, entry)) < 0) return error; *out = entry; @@ -271,12 +308,11 @@ bool git_attr_cache__is_cached( { git_attr_cache *cache = git_repository_attr_cache(repo); git_attr_file_entry *entry; - git_strmap *files; - if (!cache || !(files = cache->files)) + if (!cache) return false; - if ((entry = git_strmap_get(files, filename)) == NULL) + if (git_attr_cache_filemap_get(&entry, &cache->files, filename) != 0) return false; return entry && (entry->file[source_type] != NULL); @@ -318,6 +354,9 @@ static int attr_cache__lookup_path( static void attr_cache__free(git_attr_cache *cache) { + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + git_attr_rule *rule; + git_attr_file_entry *entry; bool unlock; if (!cache) @@ -325,30 +364,24 @@ static void attr_cache__free(git_attr_cache *cache) unlock = (attr_cache_lock(cache) == 0); - if (cache->files != NULL) { - git_attr_file_entry *entry; + while (git_attr_cache_filemap_iterate(&iter, NULL, &entry, &cache->files) == 0) { git_attr_file *file; - int i; - - git_strmap_foreach_value(cache->files, entry, { - for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) { - if ((file = git_atomic_swap(entry->file[i], NULL)) != NULL) { - GIT_REFCOUNT_OWN(file, NULL); - git_attr_file__free(file); - } + size_t i; + + for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; i++) { + if ((file = git_atomic_swap(entry->file[i], NULL)) != NULL) { + GIT_REFCOUNT_OWN(file, NULL); + git_attr_file__free(file); } - }); - git_strmap_free(cache->files); + } } - if (cache->macros != NULL) { - git_attr_rule *rule; + iter = GIT_HASHMAP_ITER_INIT; + while (git_attr_cache_macromap_iterate(&iter, NULL, &rule, &cache->macros) == 0) + git_attr_rule__free(rule); - git_strmap_foreach_value(cache->macros, rule, { - git_attr_rule__free(rule); - }); - git_strmap_free(cache->macros); - } + git_attr_cache_filemap_dispose(&cache->files); + git_attr_cache_macromap_dispose(&cache->macros); git_pool_clear(&cache->pool); @@ -401,9 +434,7 @@ int git_attr_cache__init(git_repository *repo) /* allocate hashtable for attribute and ignore file contents, * hashtable for attribute macros, and string pool */ - if ((ret = git_strmap_new(&cache->files)) < 0 || - (ret = git_strmap_new(&cache->macros)) < 0 || - (ret = git_pool_init(&cache->pool, 1)) < 0) + if ((ret = git_pool_init(&cache->pool, 1)) < 0) goto cancel; if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL) @@ -457,11 +488,11 @@ int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) goto out; locked = true; - if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL) - git_attr_rule__free(preexisting); + if (git_attr_cache_macromap_get(&preexisting, &cache->macros, macro->match.pattern) == 0) + git_attr_rule__free(preexisting); - if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0) - goto out; + if ((error = git_attr_cache_macromap_put(&cache->macros, macro->match.pattern, macro)) < 0) + goto out; out: if (locked) @@ -472,7 +503,12 @@ int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) git_attr_rule *git_attr_cache__lookup_macro( git_repository *repo, const char *name) { - git_strmap *macros = git_repository_attr_cache(repo)->macros; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_rule *rule; + + if (!cache || + git_attr_cache_macromap_get(&rule, &cache->macros, name) != 0) + return NULL; - return git_strmap_get(macros, name); + return rule; } diff --git a/src/libgit2/attrcache.h b/src/libgit2/attrcache.h index b13e0e8f..2693278d 100644 --- a/src/libgit2/attrcache.h +++ b/src/libgit2/attrcache.h @@ -10,22 +10,18 @@ #include "common.h" #include "attr_file.h" -#include "strmap.h" #define GIT_ATTR_CONFIG "core.attributesfile" #define GIT_IGNORE_CONFIG "core.excludesfile" -typedef struct { - char *cfg_attr_file; /* cached value of core.attributesfile */ - char *cfg_excl_file; /* cached value of core.excludesfile */ - git_strmap *files; /* hash path to git_attr_cache_entry records */ - git_strmap *macros; /* hash name to vector */ - git_mutex lock; - git_pool pool; -} git_attr_cache; +typedef struct git_attr_cache git_attr_cache; extern int git_attr_cache__init(git_repository *repo); +extern const char *git_attr_cache_attributesfile(git_attr_cache *ac); +extern const char *git_attr_cache_excludesfile(git_attr_cache *ac); +extern git_pool *git_attr_cache_pool(git_attr_cache *ac); + /* get file - loading and reload as needed */ extern int git_attr_cache__get( git_attr_file **file, diff --git a/src/libgit2/blame.c b/src/libgit2/blame.c index d93dd5e7..4f99de69 100644 --- a/src/libgit2/blame.c +++ b/src/libgit2/blame.c @@ -19,7 +19,6 @@ #include "repository.h" #include "blame_git.h" - static int hunk_byfinalline_search_cmp(const void *key, const void *entry) { git_blame_hunk *hunk = (git_blame_hunk*)entry; @@ -81,9 +80,12 @@ static git_blame_hunk *new_hunk( static void free_hunk(git_blame_hunk *hunk) { - git__free((void*)hunk->orig_path); + git__free((char *)hunk->orig_path); + git__free((char *)hunk->summary); git_signature_free(hunk->final_signature); + git_signature_free(hunk->final_committer); git_signature_free(hunk->orig_signature); + git_signature_free(hunk->orig_committer); git__free(hunk); } @@ -104,7 +106,10 @@ static git_blame_hunk *dup_hunk(git_blame_hunk *hunk, git_blame *blame) newhunk->boundary = hunk->boundary; if (git_signature_dup(&newhunk->final_signature, hunk->final_signature) < 0 || - git_signature_dup(&newhunk->orig_signature, hunk->orig_signature) < 0) { + git_signature_dup(&newhunk->final_committer, hunk->final_committer) < 0 || + git_signature_dup(&newhunk->orig_signature, hunk->orig_signature) < 0 || + git_signature_dup(&newhunk->orig_committer, hunk->orig_committer) < 0 || + (newhunk->summary = git__strdup(hunk->summary)) == NULL) { free_hunk(newhunk); return NULL; } @@ -117,12 +122,12 @@ static git_blame_hunk *dup_hunk(git_blame_hunk *hunk, git_blame *blame) static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by) { size_t i; - - if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) { - for (; i < v->length; i++) { - git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; - hunk->final_start_line_number += shift_by; + for (i = 0; i < v->length; i++) { + git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; + if(hunk->final_start_line_number < start_line){ + continue; } + hunk->final_start_line_number += shift_by; } } @@ -139,10 +144,9 @@ git_blame *git_blame__alloc( gbr->options = opts; if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 || - git_vector_init(&gbr->paths, 8, paths_cmp) < 0 || - (gbr->path = git__strdup(path)) == NULL || - git_vector_insert(&gbr->paths, git__strdup(path)) < 0) - { + git_vector_init(&gbr->paths, 8, paths_cmp) < 0 || + (gbr->path = git__strdup(path)) == NULL || + git_vector_insert(&gbr->paths, git__strdup(path)) < 0) { git_blame_free(gbr); return NULL; } @@ -165,9 +169,11 @@ void git_blame_free(git_blame *blame) git_vector_foreach(&blame->hunks, i, hunk) free_hunk(hunk); - git_vector_free(&blame->hunks); - git_vector_free_deep(&blame->paths); + git_vector_dispose(&blame->hunks); + git_array_clear(blame->lines); + + git_vector_dispose_deep(&blame->paths); git_array_clear(blame->line_index); @@ -178,31 +184,76 @@ void git_blame_free(git_blame *blame) git__free(blame); } -uint32_t git_blame_get_hunk_count(git_blame *blame) +size_t git_blame_hunkcount(git_blame *blame) { GIT_ASSERT_ARG(blame); - return (uint32_t)blame->hunks.length; + + return blame->hunks.length; +} + +size_t git_blame_linecount(git_blame *blame) +{ + GIT_ASSERT_ARG(blame); + + return git_array_size(blame->line_index); +} + +const git_blame_line *git_blame_line_byindex( + git_blame *blame, + size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); + GIT_ASSERT_WITH_RETVAL(idx > 0 && idx <= git_array_size(blame->line_index), NULL); + + return git_array_get(blame->lines, idx - 1); } -const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index) +const git_blame_hunk *git_blame_hunk_byindex( + git_blame *blame, + size_t index) { GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); - return (git_blame_hunk*)git_vector_get(&blame->hunks, index); + return git_vector_get(&blame->hunks, index); } -const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno) +const git_blame_hunk *git_blame_hunk_byline( + git_blame *blame, + size_t lineno) { size_t i, new_lineno = lineno; GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); - if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) { - return git_blame_get_hunk_byindex(blame, (uint32_t)i); - } + if (git_vector_bsearch2(&i, &blame->hunks, + hunk_byfinalline_search_cmp, &new_lineno) != 0) + return NULL; + + return git_blame_hunk_byindex(blame, i); +} - return NULL; +#ifndef GIT_DEPRECATE_HARD +uint32_t git_blame_get_hunk_count(git_blame *blame) +{ + size_t count = git_blame_hunkcount(blame); + GIT_ASSERT(count < UINT32_MAX); + return (uint32_t)count; } +const git_blame_hunk *git_blame_get_hunk_byindex( + git_blame *blame, + uint32_t index) +{ + return git_blame_hunk_byindex(blame, index); +} + +const git_blame_hunk *git_blame_get_hunk_byline( + git_blame *blame, + size_t lineno) +{ + return git_blame_hunk_byline(blame, lineno); +} +#endif + static int normalize_options( git_blame_options *out, const git_blame_options *in, @@ -282,31 +333,55 @@ static int index_blob_lines(git_blame *blame) const char *buf = blame->final_buf; size_t len = blame->final_buf_size; int num = 0, incomplete = 0, bol = 1; + git_blame_line *line = NULL; size_t *i; if (len && buf[len-1] != '\n') incomplete++; /* incomplete line at the end */ + while (len--) { if (bol) { i = git_array_alloc(blame->line_index); GIT_ERROR_CHECK_ALLOC(i); *i = buf - blame->final_buf; + + GIT_ASSERT(line == NULL); + line = git_array_alloc(blame->lines); + GIT_ERROR_CHECK_ALLOC(line); + + line->ptr = buf; bol = 0; } + if (*buf++ == '\n') { + GIT_ASSERT(line); + line->len = (buf - line->ptr) - 1; + line = NULL; + num++; bol = 1; } } + i = git_array_alloc(blame->line_index); GIT_ERROR_CHECK_ALLOC(i); *i = buf - blame->final_buf; + + if (!bol) { + GIT_ASSERT(line); + line->len = buf - line->ptr; + line = NULL; + } + + GIT_ASSERT(!line); + blame->num_lines = num + incomplete; return blame->num_lines; } static git_blame_hunk *hunk_from_entry(git_blame__entry *e, git_blame *blame) { + const char *summary; git_blame_hunk *h = new_hunk( e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path, blame); @@ -316,9 +391,19 @@ static git_blame_hunk *hunk_from_entry(git_blame__entry *e, git_blame *blame) git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit)); git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit)); - git_commit_author_with_mailmap( - &h->final_signature, e->suspect->commit, blame->mailmap); - git_signature_dup(&h->orig_signature, h->final_signature); + + if (git_commit_author_with_mailmap( + &h->final_signature, e->suspect->commit, blame->mailmap) < 0 || + git_commit_committer_with_mailmap( + &h->final_committer, e->suspect->commit, blame->mailmap) < 0 || + git_signature_dup(&h->orig_signature, h->final_signature) < 0 || + git_signature_dup(&h->orig_committer, h->final_committer) < 0 || + (summary = git_commit_summary(e->suspect->commit)) == NULL || + (h->summary = git__strdup(summary)) == NULL) { + free_hunk(h); + return NULL; + } + h->boundary = e->is_boundary ? 1 : 0; return h; } @@ -347,12 +432,12 @@ static int blame_internal(git_blame *blame) if ((error = load_blob(blame)) < 0 || (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0) - goto cleanup; + goto on_error; if (git_blob_rawsize(blame->final_blob) > SIZE_MAX) { git_error_set(GIT_ERROR_NOMEMORY, "blob is too large to blame"); error = -1; - goto cleanup; + goto on_error; } blame->final_buf = git_blob_rawcontent(blame->final_blob); @@ -371,17 +456,19 @@ static int blame_internal(git_blame *blame) blame->ent = ent; - error = git_blame__like_git(blame, blame->options.flags); + if ((error = git_blame__like_git(blame, blame->options.flags)) < 0) + goto on_error; -cleanup: - for (ent = blame->ent; ent; ) { - git_blame__entry *e = ent->next; + for (ent = blame->ent; ent; ent = ent->next) { git_blame_hunk *h = hunk_from_entry(ent, blame); - git_vector_insert(&blame->hunks, h); + } +on_error: + for (ent = blame->ent; ent; ) { + git_blame__entry *next = ent->next; git_blame__free_entry(ent); - ent = e; + ent = next; } return error; @@ -444,21 +531,20 @@ static int buffer_hunk_cb( GIT_UNUSED(delta); - wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; + wedge_line = (hunk->new_start >= hunk->old_start || hunk->old_lines==0) ? hunk->new_start : hunk->old_start; blame->current_diff_line = wedge_line; - - blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line); + blame->current_hunk = (git_blame_hunk*)git_blame_hunk_byline(blame, wedge_line); if (!blame->current_hunk) { /* Line added at the end of the file */ blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path, blame); + blame->current_diff_line++; GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - git_vector_insert(&blame->hunks, blame->current_hunk); } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){ /* If this hunk doesn't start between existing hunks, split a hunk up so it does */ blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk, - wedge_line - blame->current_hunk->orig_start_line_number, true, + wedge_line - blame->current_hunk->final_start_line_number, true, blame); GIT_ERROR_CHECK_ALLOC(blame->current_hunk); } @@ -484,13 +570,12 @@ static int buffer_line_cb( hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) { /* Append to the current buffer-blame hunk */ blame->current_hunk->lines_in_hunk++; - shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1); + shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); } else { /* Create a new buffer-blame hunk with this line */ shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path, blame); GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); } blame->current_diff_line++; @@ -498,15 +583,16 @@ static int buffer_line_cb( if (line->origin == GIT_DIFF_LINE_DELETION) { /* Trim the line from the current hunk; remove it if it's now empty */ - size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1; + size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk; if (--(blame->current_hunk->lines_in_hunk) == 0) { size_t i; - shift_base--; + size_t i_next; if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) { git_vector_remove(&blame->hunks, i); free_hunk(blame->current_hunk); - blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i); + i_next = min( i , blame->hunks.length -1); + blame->current_hunk = (git_blame_hunk*)git_blame_hunk_byindex(blame, (uint32_t)i_next); } } shift_hunks_by(&blame->hunks, shift_base, -1); diff --git a/src/libgit2/blame.h b/src/libgit2/blame.h index 4141e2bb..152834eb 100644 --- a/src/libgit2/blame.h +++ b/src/libgit2/blame.h @@ -71,6 +71,7 @@ struct git_blame { git_blame_options options; git_vector hunks; + git_array_t(git_blame_line) lines; git_vector paths; git_blob *final_blob; diff --git a/src/libgit2/cache.c b/src/libgit2/cache.c index 2f68e357..629c5638 100644 --- a/src/libgit2/cache.c +++ b/src/libgit2/cache.c @@ -14,6 +14,9 @@ #include "odb.h" #include "object.h" #include "git2/oid.h" +#include "hashmap_oid.h" + +GIT_HASHMAP_OID_FUNCTIONS(git_cache_oidmap, GIT_HASHMAP_INLINE, git_cached_obj *); bool git_cache__enabled = true; ssize_t git_cache__max_storage = (256 * 1024 * 1024); @@ -45,9 +48,6 @@ int git_cache_init(git_cache *cache) { memset(cache, 0, sizeof(*cache)); - if ((git_oidmap_new(&cache->map)) < 0) - return -1; - if (git_rwlock_init(&cache->lock)) { git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock"); return -1; @@ -60,15 +60,15 @@ int git_cache_init(git_cache *cache) static void clear_cache(git_cache *cache) { git_cached_obj *evict = NULL; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; if (git_cache_size(cache) == 0) return; - git_oidmap_foreach_value(cache->map, evict, { + while (git_cache_oidmap_iterate(&iter, NULL, &evict, &cache->map) == 0) git_cached_obj_decref(evict); - }); - git_oidmap_clear(cache->map); + git_cache_oidmap_clear(&cache->map); git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory); cache->used_memory = 0; } @@ -83,10 +83,15 @@ void git_cache_clear(git_cache *cache) git_rwlock_wrunlock(&cache->lock); } +size_t git_cache_size(git_cache *cache) +{ + return git_cache_oidmap_size(&cache->map); +} + void git_cache_dispose(git_cache *cache) { git_cache_clear(cache); - git_oidmap_free(cache->map); + git_cache_oidmap_dispose(&cache->map); git_rwlock_free(&cache->lock); git__memzero(cache, sizeof(*cache)); } @@ -94,8 +99,9 @@ void git_cache_dispose(git_cache *cache) /* Called with lock */ static void cache_evict_entries(git_cache *cache) { - size_t evict_count = git_cache_size(cache) / 2048, i; + size_t evict_count = git_cache_size(cache) / 2048; ssize_t evicted_memory = 0; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; if (evict_count < 8) evict_count = 8; @@ -106,17 +112,16 @@ static void cache_evict_entries(git_cache *cache) return; } - i = 0; while (evict_count > 0) { - git_cached_obj *evict; const git_oid *key; + git_cached_obj *evict; - if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER) + if (git_cache_oidmap_iterate(&iter, &key, &evict, &cache->map) != 0) break; evict_count--; evicted_memory += evict->size; - git_oidmap_delete(cache->map, key); + git_cache_oidmap_remove(&cache->map, key); git_cached_obj_decref(evict); } @@ -132,12 +137,12 @@ static bool cache_should_store(git_object_t object_type, size_t object_size) static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) { - git_cached_obj *entry; + git_cached_obj *entry = NULL; if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) return NULL; - if ((entry = git_oidmap_get(cache->map, oid)) != NULL) { + if (git_cache_oidmap_get(&entry, &cache->map, oid) == 0) { if (flags && entry->flags != flags) { entry = NULL; } else { @@ -172,8 +177,8 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry) cache_evict_entries(cache); /* not found */ - if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) { - if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { + if (git_cache_oidmap_get(&stored_entry, &cache->map, &entry->oid) != 0) { + if (git_cache_oidmap_put(&cache->map, &entry->oid, entry) == 0) { git_cached_obj_incref(entry); cache->used_memory += entry->size; git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size); @@ -187,7 +192,7 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry) entry = stored_entry; } else if (stored_entry->flags == GIT_CACHE_STORE_RAW && entry->flags == GIT_CACHE_STORE_PARSED) { - if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { + if (git_cache_oidmap_put(&cache->map, &entry->oid, entry) == 0) { git_cached_obj_decref(stored_entry); git_cached_obj_incref(entry); } else { diff --git a/src/libgit2/cache.h b/src/libgit2/cache.h index 42c4fa80..4c14013a 100644 --- a/src/libgit2/cache.h +++ b/src/libgit2/cache.h @@ -14,7 +14,7 @@ #include "git2/odb.h" #include "thread.h" -#include "oidmap.h" +#include "hashmap_oid.h" enum { GIT_CACHE_STORE_ANY = 0, @@ -30,10 +30,12 @@ typedef struct { git_atomic32 refcount; } git_cached_obj; +GIT_HASHMAP_OID_STRUCT(git_cache_oidmap, git_cached_obj *); + typedef struct { - git_oidmap *map; - git_rwlock lock; - ssize_t used_memory; + git_cache_oidmap map; + git_rwlock lock; + ssize_t used_memory; } git_cache; extern bool git_cache__enabled; @@ -45,6 +47,7 @@ int git_cache_set_max_object_size(git_object_t type, size_t size); int git_cache_init(git_cache *cache); void git_cache_dispose(git_cache *cache); void git_cache_clear(git_cache *cache); +size_t git_cache_size(git_cache *cache); void *git_cache_store_raw(git_cache *cache, git_odb_object *entry); void *git_cache_store_parsed(git_cache *cache, git_object *entry); @@ -53,11 +56,6 @@ git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid); git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid); void *git_cache_get_any(git_cache *cache, const git_oid *oid); -GIT_INLINE(size_t) git_cache_size(git_cache *cache) -{ - return (size_t)git_oidmap_size(cache->map); -} - GIT_INLINE(void) git_cached_obj_incref(void *_obj) { git_cached_obj *obj = _obj; diff --git a/src/libgit2/checkout.c b/src/libgit2/checkout.c index 6a464319..f4b1ea96 100644 --- a/src/libgit2/checkout.c +++ b/src/libgit2/checkout.c @@ -30,8 +30,8 @@ #include "fs_path.h" #include "attr.h" #include "pool.h" -#include "strmap.h" #include "path.h" +#include "hashmap_str.h" /* See docs/checkout-internals.md for more information */ @@ -72,7 +72,7 @@ typedef struct { size_t total_steps; size_t completed_steps; git_checkout_perfdata perfdata; - git_strmap *mkdir_map; + git_hashset_str mkdir_pathcache; git_attr_session attr_session; } checkout_data; @@ -294,6 +294,9 @@ static int checkout_action_no_wd( *action = CHECKOUT_ACTION__NONE; + if ((data->strategy & GIT_CHECKOUT_NONE)) + return 0; + switch (delta->status) { case GIT_DELTA_UNMODIFIED: /* case 12 */ error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL); @@ -302,17 +305,17 @@ static int checkout_action_no_wd( *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE); break; case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + *action = CHECKOUT_ACTION__UPDATE_BLOB; break; case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT); break; case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ if (delta->new_file.mode == GIT_FILEMODE_TREE) - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + *action = CHECKOUT_ACTION__UPDATE_BLOB; break; case GIT_DELTA_DELETED: /* case 8 or 25 */ - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); + *action = CHECKOUT_ACTION__REMOVE; break; default: /* impossible */ break; @@ -494,6 +497,9 @@ static int checkout_action_with_wd( { *action = CHECKOUT_ACTION__NONE; + if ((data->strategy & GIT_CHECKOUT_NONE)) + return 0; + switch (delta->status) { case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) { @@ -512,14 +518,14 @@ static int checkout_action_with_wd( if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); else - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); + *action = CHECKOUT_ACTION__REMOVE; break; case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ if (wd->mode != GIT_FILEMODE_COMMIT && checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); else - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + *action = CHECKOUT_ACTION__UPDATE_BLOB; break; case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ if (delta->old_file.mode == GIT_FILEMODE_TREE) { @@ -527,13 +533,13 @@ static int checkout_action_with_wd( /* either deleting items in old tree will delete the wd dir, * or we'll get a conflict when we attempt blob update... */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + *action = CHECKOUT_ACTION__UPDATE_BLOB; else if (wd->mode == GIT_FILEMODE_COMMIT) { /* workdir is possibly a "phantom" submodule - treat as a * tree if the only submodule info came from the config */ if (submodule_is_config_only(data, wd->path)) - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + *action = CHECKOUT_ACTION__UPDATE_BLOB; else *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); } else @@ -542,7 +548,7 @@ static int checkout_action_with_wd( else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); else - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); + *action = CHECKOUT_ACTION__REMOVE_AND_UPDATE; /* don't update if the typechange is to a tree */ if (delta->new_file.mode == GIT_FILEMODE_TREE) @@ -563,6 +569,9 @@ static int checkout_action_with_wd_blocker( { *action = CHECKOUT_ACTION__NONE; + if ((data->strategy & GIT_CHECKOUT_NONE)) + return 0; + switch (delta->status) { case GIT_DELTA_UNMODIFIED: /* should show delta as dirty / deleted */ @@ -597,6 +606,9 @@ static int checkout_action_with_wd_dir( { *action = CHECKOUT_ACTION__NONE; + if ((data->strategy & GIT_CHECKOUT_NONE)) + return 0; + switch (delta->status) { case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */ GIT_ERROR_CHECK_ERROR( @@ -627,7 +639,7 @@ static int checkout_action_with_wd_dir( * directory if is it left empty, so we can defer removing the * dir and it will succeed if no children are left. */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + *action = CHECKOUT_ACTION__UPDATE_BLOB; } else if (delta->new_file.mode != GIT_FILEMODE_TREE) /* For typechange to dir, dir is already created so no action */ @@ -1419,8 +1431,10 @@ static int checkout_mkdir( struct git_futils_mkdir_options mkdir_opts = {0}; int error; - mkdir_opts.dir_map = data->mkdir_map; - mkdir_opts.pool = &data->pool; + if (git_pool_is_initialized(&data->pool)) { + mkdir_opts.cache_pool = &data->pool; + mkdir_opts.cache_pathset = &data->mkdir_pathcache; + } error = git_futils_mkdir_relative( path, base, mode, flags, &mkdir_opts); @@ -2316,11 +2330,11 @@ static void checkout_data_clear(checkout_data *data) data->opts.baseline = NULL; } - git_vector_free(&data->removes); + git_vector_dispose(&data->removes); git_pool_clear(&data->pool); - git_vector_free_deep(&data->remove_conflicts); - git_vector_free_deep(&data->update_conflicts); + git_vector_dispose_deep(&data->remove_conflicts); + git_vector_dispose_deep(&data->update_conflicts); git__free(data->pfx); data->pfx = NULL; @@ -2331,8 +2345,7 @@ static void checkout_data_clear(checkout_data *data) git_index_free(data->index); data->index = NULL; - git_strmap_free(data->mkdir_map); - data->mkdir_map = NULL; + git_hashset_str_dispose(&data->mkdir_pathcache); git_attr_session__free(&data->attr_session); } @@ -2432,14 +2445,12 @@ static int checkout_data_init( /* if you are forcing, allow all safe updates, plus recreate missing */ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; + data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING; /* if the repository does not actually have an index file, then this * is an initial checkout (perhaps from clone), so we allow safe updates */ - if (!data->index->on_disk && - (data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0) + if (!data->index->on_disk) data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING; data->strategy = data->opts.checkout_strategy; @@ -2513,8 +2524,7 @@ static int checkout_data_init( (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || (error = git_str_puts(&data->target_path, data->opts.target_directory)) < 0 || - (error = git_fs_path_to_dir(&data->target_path)) < 0 || - (error = git_strmap_new(&data->mkdir_map)) < 0) + (error = git_fs_path_to_dir(&data->target_path)) < 0) goto cleanup; data->target_len = git_str_len(&data->target_path); diff --git a/src/libgit2/checkout.h b/src/libgit2/checkout.h index 517fbf3b..e613325c 100644 --- a/src/libgit2/checkout.h +++ b/src/libgit2/checkout.h @@ -12,8 +12,6 @@ #include "git2/checkout.h" #include "iterator.h" -#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12) - /** * Update the working directory to match the target iterator. The * expected baseline value can be passed in via the checkout options diff --git a/src/libgit2/cherrypick.c b/src/libgit2/cherrypick.c index 3ef42d5e..56137016 100644 --- a/src/libgit2/cherrypick.c +++ b/src/libgit2/cherrypick.c @@ -73,8 +73,7 @@ static int cherrypick_normalize_opts( const char *their_label) { int error = 0; - unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; + unsigned int default_checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS; GIT_UNUSED(repo); diff --git a/src/libgit2/clone.c b/src/libgit2/clone.c index fca0ca0c..237efc0b 100644 --- a/src/libgit2/clone.c +++ b/src/libgit2/clone.c @@ -16,14 +16,14 @@ #include "git2/commit.h" #include "git2/tree.h" +#include "checkout.h" #include "remote.h" #include "futils.h" #include "refs.h" #include "fs_path.h" #include "repository.h" #include "odb.h" - -static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link); +#include "net.h" static int create_branch( git_reference **branch, @@ -336,8 +336,9 @@ static int create_and_configure_origin( git_remote_create_cb remote_create = options->remote_cb; void *payload = options->remote_cb_payload; - /* If the path exists and is a dir, the url should be the absolute path */ - if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) { + /* If the path is local and exists it should be the absolute path. */ + if (!git_net_str_is_url(url) && git_fs_path_root(url) < 0 && + git_fs_path_exists(url)) { if (p_realpath(url, buf) == NULL) return -1; @@ -360,35 +361,50 @@ static int create_and_configure_origin( return error; } -static bool should_checkout( +static int should_checkout( + bool *out, git_repository *repo, bool is_bare, - const git_checkout_options *opts) + const git_clone_options *opts) { - if (is_bare) - return false; + int error; - if (!opts) - return false; + if (!opts || is_bare || + opts->checkout_opts.checkout_strategy == GIT_CHECKOUT_NONE) { + *out = false; + return 0; + } - if (opts->checkout_strategy == GIT_CHECKOUT_NONE) - return false; + if ((error = git_repository_head_unborn(repo)) < 0) + return error; - return !git_repository_head_unborn(repo); + *out = !error; + return 0; } -static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) +static int checkout_branch( + git_repository *repo, + git_remote *remote, + const git_clone_options *opts, + const char *reflog_message) { + bool checkout; int error; - if (branch) - error = update_head_to_branch(repo, remote, branch, reflog_message); + if (opts->checkout_branch) + error = update_head_to_branch(repo, remote, opts->checkout_branch, reflog_message); /* Point HEAD to the same ref as the remote's head */ else error = update_head_to_remote(repo, remote, reflog_message); - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) - error = git_checkout_head(repo, co_opts); + if (error < 0) + return error; + + if ((error = should_checkout(&checkout, repo, git_repository_is_bare(repo), opts)) < 0) + return error; + + if (checkout) + error = git_checkout_head(repo, &opts->checkout_opts); return error; } @@ -396,16 +412,13 @@ static int checkout_branch(git_repository *repo, git_remote *remote, const git_c static int clone_into( git_repository *repo, git_remote *_remote, - const git_fetch_options *opts, - const git_checkout_options *co_opts, - const char *branch) + const git_clone_options *opts) { - int error; git_str reflog_message = GIT_STR_INIT; git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - git_fetch_options fetch_opts; git_remote *remote; git_oid_t oid_type; + int error; GIT_ASSERT_ARG(repo); GIT_ASSERT_ARG(_remote); @@ -418,13 +431,7 @@ static int clone_into( if ((error = git_remote_dup(&remote, _remote)) < 0) return error; - memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); - fetch_opts.update_fetchhead = 0; - - if (!opts->depth) - fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - - if ((error = git_remote_connect_options__from_fetch_opts(&connect_opts, remote, &fetch_opts)) < 0) + if ((error = git_remote_connect_options__from_fetch_opts(&connect_opts, remote, &opts->fetch_opts)) < 0) goto cleanup; git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); @@ -442,10 +449,10 @@ static int clone_into( (error = git_repository__set_objectformat(repo, oid_type)) < 0) goto cleanup; - if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_str_cstr(&reflog_message))) != 0) + if ((error = git_remote_fetch(remote, NULL, &opts->fetch_opts, git_str_cstr(&reflog_message))) != 0) goto cleanup; - error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); + error = checkout_branch(repo, remote, opts, git_str_cstr(&reflog_message)); cleanup: git_remote_free(remote); @@ -455,37 +462,142 @@ static int clone_into( return error; } -int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) +static bool can_link(const char *src, const char *dst, int link) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(src); + GIT_UNUSED(dst); + GIT_UNUSED(link); + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +static int clone_local_into( + git_repository *repo, + git_remote *remote, + const git_clone_options *opts) +{ + int error, flags; + git_repository *src; + git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; + git_str reflog_message = GIT_STR_INIT; + bool link = (opts && opts->local != GIT_CLONE_LOCAL_NO_LINKS); + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(remote); + + if (!git_repository_is_empty(repo)) { + git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); + return -1; + } + + /* + * Let's figure out what path we should use for the source + * repo, if it's not rooted, the path should be relative to + * the repository's worktree/gitdir. + */ + if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; + + /* Copy .git/objects/ from the source to the target */ + if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { + git_str_dispose(&src_path); + return error; + } + + if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || + git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { + error = -1; + goto cleanup; + } + + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + + error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE); + + /* + * can_link() doesn't catch all variations, so if we hit an + * error and did want to link, let's try again without trying + * to link. + */ + if (error < 0 && link) { + flags &= ~GIT_CPDIR_LINK_FILES; + error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE); + } + + if (error < 0) + goto cleanup; + + git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, NULL, &opts->fetch_opts, git_str_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, opts, git_str_cstr(&reflog_message)); + +cleanup: + git_str_dispose(&reflog_message); + git_str_dispose(&src_path); + git_str_dispose(&src_odb); + git_str_dispose(&dst_odb); + git_repository_free(src); + return error; +} + +int git_clone__should_clone_local( + bool *out, + const char *url_or_path, + git_clone_local_t local) { git_str fromurl = GIT_STR_INIT; - const char *path = url_or_path; - bool is_url, is_local; + + *out = false; if (local == GIT_CLONE_NO_LOCAL) return 0; - if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) { - if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) { - is_local = -1; - goto done; - } + if (git_net_str_is_url(url_or_path)) { + /* If GIT_CLONE_LOCAL_AUTO is specified, any url should + * be treated as remote */ + if (local == GIT_CLONE_LOCAL_AUTO || + !git_fs_path_is_local_file_url(url_or_path)) + return 0; - path = fromurl.ptr; - } + if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) + return -1; - is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && - git_fs_path_isdir(path); + *out = git_fs_path_isdir(git_str_cstr(&fromurl)); + git_str_dispose(&fromurl); + } else { + *out = git_fs_path_isdir(url_or_path); + } -done: - git_str_dispose(&fromurl); - return is_local; + return 0; } -static int git__clone( +static int clone_repo( git_repository **out, const char *url, const char *local_path, - const git_clone_options *_options, + const git_clone_options *given_opts, int use_existing) { int error = 0; @@ -499,11 +611,17 @@ static int git__clone( GIT_ASSERT_ARG(url); GIT_ASSERT_ARG(local_path); - if (_options) - memcpy(&options, _options, sizeof(git_clone_options)); + if (given_opts) + memcpy(&options, given_opts, sizeof(git_clone_options)); GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); + /* enforce some behavior on fetch */ + options.fetch_opts.update_fetchhead = 0; + + if (!options.fetch_opts.depth) + options.fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + /* Only clone to a new directory or an empty directory */ if (git_fs_path_exists(local_path) && !use_existing && !git_fs_path_is_empty_dir(local_path)) { git_error_set(GIT_ERROR_INVALID, @@ -524,33 +642,31 @@ static int git__clone( return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - int clone_local = git_clone__should_clone_local(url, options.local); - int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; - - if (clone_local == 1) - error = clone_local_into( - repo, origin, &options.fetch_opts, &options.checkout_opts, - options.checkout_branch, link); - else if (clone_local == 0) - error = clone_into( - repo, origin, &options.fetch_opts, &options.checkout_opts, - options.checkout_branch); + bool clone_local; + + if ((error = git_clone__should_clone_local(&clone_local, url, options.local)) < 0) { + git_remote_free(origin); + return error; + } + + if (clone_local) + error = clone_local_into(repo, origin, &options); else - error = -1; + error = clone_into(repo, origin, &options); git_remote_free(origin); } if (error != 0) { - git_error_state last_error = {0}; - git_error_state_capture(&last_error, error); + git_error *last_error; + git_error_save(&last_error); git_repository_free(repo); repo = NULL; (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); - git_error_state_restore(&last_error); + git_error_restore(last_error); } *out = repo; @@ -561,18 +677,18 @@ int git_clone( git_repository **out, const char *url, const char *local_path, - const git_clone_options *_options) + const git_clone_options *options) { - return git__clone(out, url, local_path, _options, 0); + return clone_repo(out, url, local_path, options, 0); } int git_clone__submodule( git_repository **out, const char *url, const char *local_path, - const git_clone_options *_options) + const git_clone_options *options) { - return git__clone(out, url, local_path, _options, 1); + return clone_repo(out, url, local_path, options, 1); } int git_clone_options_init(git_clone_options *opts, unsigned int version) @@ -588,99 +704,3 @@ int git_clone_init_options(git_clone_options *opts, unsigned int version) return git_clone_options_init(opts, version); } #endif - -static bool can_link(const char *src, const char *dst, int link) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(src); - GIT_UNUSED(dst); - GIT_UNUSED(link); - return false; -#else - - struct stat st_src, st_dst; - - if (!link) - return false; - - if (p_stat(src, &st_src) < 0) - return false; - - if (p_stat(dst, &st_dst) < 0) - return false; - - return st_src.st_dev == st_dst.st_dev; -#endif -} - -static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link) -{ - int error, flags; - git_repository *src; - git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; - git_str reflog_message = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(remote); - - if (!git_repository_is_empty(repo)) { - git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); - return -1; - } - - /* - * Let's figure out what path we should use for the source - * repo, if it's not rooted, the path should be relative to - * the repository's worktree/gitdir. - */ - if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) - return error; - - /* Copy .git/objects/ from the source to the target */ - if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { - git_str_dispose(&src_path); - return error; - } - - if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || - git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { - error = -1; - goto cleanup; - } - - flags = 0; - if (can_link(git_repository_path(src), git_repository_path(repo), link)) - flags |= GIT_CPDIR_LINK_FILES; - - error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), - flags, GIT_OBJECT_DIR_MODE); - - /* - * can_link() doesn't catch all variations, so if we hit an - * error and did want to link, let's try again without trying - * to link. - */ - if (error < 0 && link) { - flags &= ~GIT_CPDIR_LINK_FILES; - error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), - flags, GIT_OBJECT_DIR_MODE); - } - - if (error < 0) - goto cleanup; - - git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - - if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_str_cstr(&reflog_message))) != 0) - goto cleanup; - - error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); - -cleanup: - git_str_dispose(&reflog_message); - git_str_dispose(&src_path); - git_str_dispose(&src_odb); - git_str_dispose(&dst_odb); - git_repository_free(src); - return error; -} diff --git a/src/libgit2/clone.h b/src/libgit2/clone.h index 7d73cabd..fde796f9 100644 --- a/src/libgit2/clone.h +++ b/src/libgit2/clone.h @@ -15,6 +15,9 @@ extern int git_clone__submodule(git_repository **out, const char *url, const char *local_path, const git_clone_options *_options); -extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); +extern int git_clone__should_clone_local( + bool *out, + const char *url, + git_clone_local_t local); #endif diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c index f7be73ac..10df4362 100644 --- a/src/libgit2/commit.c +++ b/src/libgit2/commit.c @@ -1086,6 +1086,83 @@ int git_commit_create_with_signature( return error; } +int git_commit_create_from_stage( + git_oid *out, + git_repository *repo, + const char *message, + const git_commit_create_options *given_opts) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_signature *default_signature = NULL; + const git_signature *author, *committer; + git_index *index = NULL; + git_diff *diff = NULL; + git_oid tree_id; + git_tree *head_tree = NULL, *tree = NULL; + git_commitarray parents = { 0 }; + int error = -1; + + GIT_ASSERT_ARG(out && repo); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_commit_create_options)); + + author = opts.author; + committer = opts.committer; + + if (!author || !committer) { + if (git_signature_default(&default_signature, repo) < 0) + goto done; + + if (!author) + author = default_signature; + + if (!committer) + committer = default_signature; + } + + if (git_repository_index(&index, repo) < 0) + goto done; + + if (!opts.allow_empty_commit) { + error = git_repository_head_tree(&head_tree, repo); + + if (error && error != GIT_EUNBORNBRANCH) + goto done; + + error = -1; + + if (git_diff_tree_to_index(&diff, repo, head_tree, index, NULL) < 0) + goto done; + + if (git_diff_num_deltas(diff) == 0) { + git_error_set(GIT_ERROR_REPOSITORY, + "no changes are staged for commit"); + error = GIT_EUNCHANGED; + goto done; + } + } + + if (git_index_write_tree(&tree_id, index) < 0 || + git_tree_lookup(&tree, repo, &tree_id) < 0 || + git_repository_commit_parents(&parents, repo) < 0) + goto done; + + error = git_commit_create(out, repo, "HEAD", author, committer, + opts.message_encoding, message, + tree, parents.count, + (const git_commit **)parents.commits); + +done: + git_commitarray_dispose(&parents); + git_signature_free(default_signature); + git_tree_free(tree); + git_tree_free(head_tree); + git_diff_free(diff); + git_index_free(index); + return error; +} + int git_commit_committer_with_mailmap( git_signature **out, const git_commit *commit, const git_mailmap *mailmap) { @@ -1097,3 +1174,18 @@ int git_commit_author_with_mailmap( { return git_mailmap_resolve_signature(out, mailmap, commit->author); } + +void git_commitarray_dispose(git_commitarray *array) +{ + size_t i; + + if (array == NULL) + return; + + for (i = 0; i < array->count; i++) + git_commit_free(array->commits[i]); + + git__free((git_commit **)array->commits); + + memset(array, 0, sizeof(*array)); +} diff --git a/src/libgit2/commit_graph.c b/src/libgit2/commit_graph.c index 4edd7110..92785049 100644 --- a/src/libgit2/commit_graph.c +++ b/src/libgit2/commit_graph.c @@ -13,7 +13,6 @@ #include "futils.h" #include "hash.h" #include "oidarray.h" -#include "oidmap.h" #include "pack.h" #include "repository.h" #include "revwalk.h" @@ -25,6 +24,7 @@ #define COMMIT_GRAPH_SIGNATURE 0x43475048 /* "CGPH" */ #define COMMIT_GRAPH_VERSION 1 #define COMMIT_GRAPH_OBJECT_ID_VERSION 1 + struct git_commit_graph_header { uint32_t signature; uint8_t version; @@ -365,15 +365,24 @@ int git_commit_graph_open( git_commit_graph **cgraph_out, const char *objects_dir #ifdef GIT_EXPERIMENTAL_SHA256 - , git_oid_t oid_type + , const git_commit_graph_open_options *opts #endif ) { -#ifndef GIT_EXPERIMENTAL_SHA256 - git_oid_t oid_type = GIT_OID_SHA1; -#endif + git_oid_t oid_type; int error; +#ifdef GIT_EXPERIMENTAL_SHA256 + GIT_ERROR_CHECK_VERSION(opts, + GIT_COMMIT_GRAPH_OPEN_OPTIONS_VERSION, + "git_commit_graph_open_options"); + + oid_type = opts && opts->oid_type ? opts->oid_type : GIT_OID_DEFAULT; + GIT_ASSERT_ARG(git_oid_type_is_valid(oid_type)); +#else + oid_type = GIT_OID_SHA1; +#endif + error = git_commit_graph_new(cgraph_out, objects_dir, true, oid_type); @@ -684,21 +693,40 @@ static int packed_commit__cmp(const void *a_, const void *b_) return git_oid_cmp(&a->sha1, &b->sha1); } +int git_commit_graph_writer_options_init( + git_commit_graph_writer_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, + version, + git_commit_graph_writer_options, + GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT); + return 0; +} + int git_commit_graph_writer_new( git_commit_graph_writer **out, - const char *objects_info_dir -#ifdef GIT_EXPERIMENTAL_SHA256 - , git_oid_t oid_type -#endif + const char *objects_info_dir, + const git_commit_graph_writer_options *opts ) { git_commit_graph_writer *w; + git_oid_t oid_type; -#ifndef GIT_EXPERIMENTAL_SHA256 - git_oid_t oid_type = GIT_OID_SHA1; +#ifdef GIT_EXPERIMENTAL_SHA256 + GIT_ERROR_CHECK_VERSION(opts, + GIT_COMMIT_GRAPH_WRITER_OPTIONS_VERSION, + "git_commit_graph_writer_options"); + + oid_type = opts && opts->oid_type ? opts->oid_type : GIT_OID_DEFAULT; + GIT_ASSERT_ARG(git_oid_type_is_valid(oid_type)); +#else + GIT_UNUSED(opts); + oid_type = GIT_OID_SHA1; #endif - GIT_ASSERT_ARG(out && objects_info_dir && oid_type); + GIT_ASSERT_ARG(out && objects_info_dir); w = git__calloc(1, sizeof(git_commit_graph_writer)); GIT_ERROR_CHECK_ALLOC(w); @@ -730,7 +758,7 @@ void git_commit_graph_writer_free(git_commit_graph_writer *w) git_vector_foreach (&w->commits, i, packed_commit) packed_commit_free(packed_commit); - git_vector_free(&w->commits); + git_vector_dispose(&w->commits); git_str_dispose(&w->objects_info_dir); git__free(w); } @@ -775,9 +803,9 @@ static int object_entry__cb(const git_oid *id, void *data) } int git_commit_graph_writer_add_index_file( - git_commit_graph_writer *w, - git_repository *repo, - const char *idx_path) + git_commit_graph_writer *w, + git_repository *repo, + const char *idx_path) { int error; struct git_pack_file *p = NULL; @@ -839,6 +867,8 @@ enum generation_number_commit_state { GENERATION_NUMBER_COMMIT_STATE_VISITED = 3 }; +GIT_HASHMAP_OID_SETUP(git_commit_graph_oidmap, struct packed_commit *); + static int compute_generation_numbers(git_vector *commits) { git_array_t(size_t) index_stack = GIT_ARRAY_INIT; @@ -846,17 +876,14 @@ static int compute_generation_numbers(git_vector *commits) size_t *parent_idx; enum generation_number_commit_state *commit_states = NULL; struct packed_commit *child_packed_commit; - git_oidmap *packed_commit_map = NULL; + git_commit_graph_oidmap packed_commit_map = GIT_HASHMAP_INIT; int error = 0; /* First populate the parent indices fields */ - error = git_oidmap_new(&packed_commit_map); - if (error < 0) - goto cleanup; git_vector_foreach (commits, i, child_packed_commit) { child_packed_commit->index = i; - error = git_oidmap_set( - packed_commit_map, &child_packed_commit->sha1, child_packed_commit); + error = git_commit_graph_oidmap_put(&packed_commit_map, + &child_packed_commit->sha1, child_packed_commit); if (error < 0) goto cleanup; } @@ -874,8 +901,7 @@ static int compute_generation_numbers(git_vector *commits) goto cleanup; } git_array_foreach (child_packed_commit->parents, parent_i, parent_id) { - parent_packed_commit = git_oidmap_get(packed_commit_map, parent_id); - if (!parent_packed_commit) { + if (git_commit_graph_oidmap_get(&parent_packed_commit, &packed_commit_map, parent_id) != 0) { git_error_set(GIT_ERROR_ODB, "parent commit %s not found in commit graph", git_oid_tostr_s(parent_id)); @@ -975,7 +1001,7 @@ static int compute_generation_numbers(git_vector *commits) } cleanup: - git_oidmap_free(packed_commit_map); + git_commit_graph_oidmap_dispose(&packed_commit_map); git__free(commit_states); git_array_clear(index_stack); @@ -1029,9 +1055,12 @@ static int commit_graph_write_hash(const char *buf, size_t size, void *data) struct commit_graph_write_hash_context *ctx = data; int error; - error = git_hash_update(ctx->ctx, buf, size); - if (error < 0) - return error; + if (ctx->ctx) { + error = git_hash_update(ctx->ctx, buf, size); + + if (error < 0) + return error; + } return ctx->write_cb(buf, size, ctx->cb_data); } @@ -1042,9 +1071,9 @@ static void packed_commit_free_dup(void *packed_commit) } static int commit_graph_write( - git_commit_graph_writer *w, - commit_graph_write_cb write_cb, - void *cb_data) + git_commit_graph_writer *w, + commit_graph_write_cb write_cb, + void *cb_data) { int error = 0; size_t i; @@ -1227,6 +1256,9 @@ static int commit_graph_write( error = git_hash_final(checksum, &ctx); if (error < 0) goto cleanup; + + hash_cb_data.ctx = NULL; + error = write_cb((char *)checksum, checksum_size, cb_data); if (error < 0) goto cleanup; @@ -1245,30 +1277,13 @@ static int commit_graph_write_filebuf(const char *buf, size_t size, void *data) return git_filebuf_write(f, buf, size); } -int git_commit_graph_writer_options_init( - git_commit_graph_writer_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, - version, - git_commit_graph_writer_options, - GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT); - return 0; -} - -int git_commit_graph_writer_commit( - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) +int git_commit_graph_writer_commit(git_commit_graph_writer *w) { int error; int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; git_str commit_graph_path = GIT_STR_INIT; git_filebuf output = GIT_FILEBUF_INIT; - /* TODO: support options and fill in defaults. */ - GIT_UNUSED(opts); - error = git_str_joinpath( &commit_graph_path, git_str_cstr(&w->objects_info_dir), "commit-graph"); if (error < 0) @@ -1292,18 +1307,14 @@ int git_commit_graph_writer_commit( int git_commit_graph_writer_dump( git_buf *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) + git_commit_graph_writer *w) { - GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w, opts); + GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w); } int git_commit_graph__writer_dump( git_str *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) + git_commit_graph_writer *w) { - /* TODO: support options. */ - GIT_UNUSED(opts); return commit_graph_write(w, commit_graph_write_buf, cgraph); } diff --git a/src/libgit2/commit_graph.h b/src/libgit2/commit_graph.h index ecf4379b..a06f3f86 100644 --- a/src/libgit2/commit_graph.h +++ b/src/libgit2/commit_graph.h @@ -156,8 +156,7 @@ struct git_commit_graph_writer { int git_commit_graph__writer_dump( git_str *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts); + git_commit_graph_writer *w); /* * Returns whether the git_commit_graph_file needs to be reloaded since the diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 23a8f9ff..b16d981d 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -22,83 +22,112 @@ #include +/* + * A refcounted instance of a config_backend that can be shared across + * a configuration instance, any snapshots, and individual configuration + * levels (from `git_config_open_level`). + */ +typedef struct { + git_refcount rc; + git_config_backend *backend; +} backend_instance; + +/* + * An entry in the readers or writers vector in the configuration. + * This is kept separate from the refcounted instance so that different + * views of the configuration can have different notions of levels or + * write orders. + * + * (eg, a standard configuration has a priority ordering of writers, a + * snapshot has *no* writers, and an individual level has a single + * writer.) + */ +typedef struct { + backend_instance *instance; + git_config_level_t level; + int write_order; +} backend_entry; + void git_config_entry_free(git_config_entry *entry) { + git_config_backend_entry *be; + if (!entry) return; - entry->free(entry); + be = (git_config_backend_entry *)entry; + be->free(be); } -typedef struct { - git_refcount rc; - - git_config_backend *backend; - git_config_level_t level; -} backend_internal; - -static void backend_internal_free(backend_internal *internal) +static void backend_instance_free(backend_instance *instance) { git_config_backend *backend; - backend = internal->backend; + backend = instance->backend; backend->free(backend); - git__free(internal); + git__free(instance); } -static void config_free(git_config *cfg) +static void config_free(git_config *config) { size_t i; - backend_internal *internal; + backend_entry *entry; - for (i = 0; i < cfg->backends.length; ++i) { - internal = git_vector_get(&cfg->backends, i); - GIT_REFCOUNT_DEC(internal, backend_internal_free); + git_vector_foreach(&config->readers, i, entry) { + GIT_REFCOUNT_DEC(entry->instance, backend_instance_free); + git__free(entry); } - git_vector_free(&cfg->backends); - - git__memzero(cfg, sizeof(*cfg)); - git__free(cfg); + git_vector_dispose(&config->readers); + git_vector_dispose(&config->writers); + git__free(config); } -void git_config_free(git_config *cfg) +void git_config_free(git_config *config) { - if (cfg == NULL) + if (config == NULL) return; - GIT_REFCOUNT_DEC(cfg, config_free); + GIT_REFCOUNT_DEC(config, config_free); } -static int config_backend_cmp(const void *a, const void *b) +static int reader_cmp(const void *_a, const void *_b) { - const backend_internal *bk_a = (const backend_internal *)(a); - const backend_internal *bk_b = (const backend_internal *)(b); + const backend_entry *a = _a; + const backend_entry *b = _b; - return bk_b->level - bk_a->level; + return b->level - a->level; } -int git_config_new(git_config **out) +static int writer_cmp(const void *_a, const void *_b) { - git_config *cfg; + const backend_entry *a = _a; + const backend_entry *b = _b; - cfg = git__malloc(sizeof(git_config)); - GIT_ERROR_CHECK_ALLOC(cfg); + return b->write_order - a->write_order; +} + +int git_config_new(git_config **out) +{ + git_config *config; - memset(cfg, 0x0, sizeof(git_config)); + config = git__calloc(1, sizeof(git_config)); + GIT_ERROR_CHECK_ALLOC(config); - if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { - git__free(cfg); + if (git_vector_init(&config->readers, 8, reader_cmp) < 0 || + git_vector_init(&config->writers, 8, writer_cmp) < 0) { + config_free(config); return -1; } - *out = cfg; - GIT_REFCOUNT_INC(cfg); + GIT_REFCOUNT_INC(config); + + *out = config; return 0; } int git_config_add_file_ondisk( - git_config *cfg, + git_config *config, const char *path, git_config_level_t level, const git_repository *repo, @@ -108,7 +137,7 @@ int git_config_add_file_ondisk( struct stat st; int res; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); GIT_ASSERT_ARG(path); res = p_stat(path, &st); @@ -120,7 +149,7 @@ int git_config_add_file_ondisk( if (git_config_backend_from_file(&file, path) < 0) return -1; - if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { + if ((res = git_config_add_backend(config, file, level, repo, force)) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup @@ -154,7 +183,7 @@ int git_config_snapshot(git_config **out, git_config *in) { int error = 0; size_t i; - backend_internal *internal; + backend_entry *entry; git_config *config; *out = NULL; @@ -162,18 +191,20 @@ int git_config_snapshot(git_config **out, git_config *in) if (git_config_new(&config) < 0) return -1; - git_vector_foreach(&in->backends, i, internal) { + git_vector_foreach(&in->readers, i, entry) { git_config_backend *b; - if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) + if ((error = entry->instance->backend->snapshot(&b, entry->instance->backend)) < 0) break; - if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { + if ((error = git_config_add_backend(config, b, entry->level, NULL, 0)) < 0) { b->free(b); break; } } + git_config_set_writeorder(config, NULL, 0); + if (error < 0) git_config_free(config); else @@ -183,141 +214,162 @@ int git_config_snapshot(git_config **out, git_config *in) } static int find_backend_by_level( - backend_internal **out, - const git_config *cfg, + backend_instance **out, + const git_config *config, git_config_level_t level) { - int pos = -1; - backend_internal *internal; + backend_entry *entry, *found = NULL; size_t i; - /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend - * which has the highest level. As config backends are stored in a vector - * sorted by decreasing order of level, getting the backend at position 0 - * will do the job. + /* + * when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the + * config backend which has the highest level. As config backends + * are stored in a vector sorted by decreasing order of level, + * getting the backend at position 0 will do the job. */ if (level == GIT_CONFIG_HIGHEST_LEVEL) { - pos = 0; + found = git_vector_get(&config->readers, 0); } else { - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; + git_vector_foreach(&config->readers, i, entry) { + if (entry->level == level) { + found = entry; + break; + } } } - if (pos == -1) { + if (!found) { git_error_set(GIT_ERROR_CONFIG, - "no configuration exists for the given level '%i'", (int)level); + "no configuration exists for the given level '%d'", level); return GIT_ENOTFOUND; } - *out = git_vector_get(&cfg->backends, pos); - + *out = found->instance; return 0; } -static int duplicate_level(void **old_raw, void *new_raw) +static int duplicate_level(void **_old, void *_new) { - backend_internal **old = (backend_internal **)old_raw; + backend_entry **old = (backend_entry **)_old; - GIT_UNUSED(new_raw); + GIT_UNUSED(_new); - git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); + git_error_set(GIT_ERROR_CONFIG, "configuration at level %d already exists", (*old)->level); return GIT_EEXISTS; } static void try_remove_existing_backend( - git_config *cfg, + git_config *config, git_config_level_t level) { - int pos = -1; - backend_internal *internal; + backend_entry *entry, *found = NULL; size_t i; - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; + git_vector_foreach(&config->readers, i, entry) { + if (entry->level == level) { + git_vector_remove(&config->readers, i); + found = entry; + break; + } } - if (pos == -1) + if (!found) return; - internal = git_vector_get(&cfg->backends, pos); - - if (git_vector_remove(&cfg->backends, pos) < 0) - return; + git_vector_foreach(&config->writers, i, entry) { + if (entry->level == level) { + git_vector_remove(&config->writers, i); + break; + } + } - GIT_REFCOUNT_DEC(internal, backend_internal_free); + GIT_REFCOUNT_DEC(found->instance, backend_instance_free); + git__free(found); } -static int git_config__add_internal( - git_config *cfg, - backend_internal *internal, +static int git_config__add_instance( + git_config *config, + backend_instance *instance, git_config_level_t level, int force) { + backend_entry *entry; int result; /* delete existing config backend for level if it exists */ if (force) - try_remove_existing_backend(cfg, level); + try_remove_existing_backend(config, level); - if ((result = git_vector_insert_sorted(&cfg->backends, - internal, &duplicate_level)) < 0) - return result; + entry = git__malloc(sizeof(backend_entry)); + GIT_ERROR_CHECK_ALLOC(entry); - git_vector_sort(&cfg->backends); - internal->backend->cfg = cfg; + entry->instance = instance; + entry->level = level; + entry->write_order = level; - GIT_REFCOUNT_INC(internal); + if ((result = git_vector_insert_sorted(&config->readers, + entry, &duplicate_level)) < 0 || + (result = git_vector_insert_sorted(&config->writers, + entry, NULL)) < 0) { + git__free(entry); + return result; + } + + GIT_REFCOUNT_INC(entry->instance); return 0; } -int git_config_open_global(git_config **cfg_out, git_config *cfg) +int git_config_open_global(git_config **out, git_config *config) { - if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) + int error; + + error = git_config_open_level(out, config, GIT_CONFIG_LEVEL_XDG); + + if (error == 0) return 0; + else if (error != GIT_ENOTFOUND) + return error; - return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); + return git_config_open_level(out, config, GIT_CONFIG_LEVEL_GLOBAL); } int git_config_open_level( - git_config **cfg_out, - const git_config *cfg_parent, + git_config **out, + const git_config *parent, git_config_level_t level) { - git_config *cfg; - backend_internal *internal; + git_config *config; + backend_instance *instance; int res; - if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) + if ((res = find_backend_by_level(&instance, parent, level)) < 0) return res; - if ((res = git_config_new(&cfg)) < 0) + if ((res = git_config_new(&config)) < 0) return res; - if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { - git_config_free(cfg); + if ((res = git_config__add_instance(config, instance, level, true)) < 0) { + git_config_free(config); return res; } - *cfg_out = cfg; + *out = config; return 0; } int git_config_add_backend( - git_config *cfg, + git_config *config, git_config_backend *backend, git_config_level_t level, const git_repository *repo, int force) { - backend_internal *internal; + backend_instance *instance; int result; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); GIT_ASSERT_ARG(backend); GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); @@ -325,22 +377,50 @@ int git_config_add_backend( if ((result = backend->open(backend, level, repo)) < 0) return result; - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); - - memset(internal, 0x0, sizeof(backend_internal)); + instance = git__calloc(1, sizeof(backend_instance)); + GIT_ERROR_CHECK_ALLOC(instance); - internal->backend = backend; - internal->level = level; + instance->backend = backend; + instance->backend->cfg = config; - if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { - git__free(internal); + if ((result = git_config__add_instance(config, instance, level, force)) < 0) { + git__free(instance); return result; } return 0; } +int git_config_set_writeorder( + git_config *config, + git_config_level_t *levels, + size_t len) +{ + backend_entry *entry; + size_t i, j; + + GIT_ASSERT(len < INT_MAX); + + git_vector_foreach(&config->readers, i, entry) { + bool found = false; + + for (j = 0; j < len; j++) { + if (levels[j] == entry->level) { + entry->write_order = (int)j; + found = true; + break; + } + } + + if (!found) + entry->write_order = -1; + } + + git_vector_sort(&config->writers); + + return 0; +} + /* * Loop over all the variables */ @@ -348,37 +428,24 @@ int git_config_add_backend( typedef struct { git_config_iterator parent; git_config_iterator *current; - const git_config *cfg; + const git_config *config; git_regexp regex; size_t i; } all_iter; -static int find_next_backend(size_t *out, const git_config *cfg, size_t i) -{ - backend_internal *internal; - - for (; i > 0; --i) { - internal = git_vector_get(&cfg->backends, i - 1); - if (!internal || !internal->backend) - continue; - - *out = i; - return 0; - } - - return -1; -} - -static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) +static int all_iter_next( + git_config_backend_entry **out, + git_config_iterator *_iter) { all_iter *iter = (all_iter *) _iter; - backend_internal *internal; + backend_entry *entry; git_config_backend *backend; - size_t i; + git_config_backend_entry *be; int error = 0; if (iter->current != NULL && - (error = iter->current->next(entry, iter->current)) == 0) { + (error = iter->current->next(&be, iter->current)) == 0) { + *out = be; return 0; } @@ -386,25 +453,32 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) return error; do { - if (find_next_backend(&i, iter->cfg, iter->i) < 0) + if (iter->i == 0) return GIT_ITEROVER; - internal = git_vector_get(&iter->cfg->backends, i - 1); - backend = internal->backend; - iter->i = i - 1; + entry = git_vector_get(&iter->config->readers, iter->i - 1); + GIT_ASSERT(entry && entry->instance && entry->instance->backend); + + backend = entry->instance->backend; + iter->i--; if (iter->current) iter->current->free(iter->current); iter->current = NULL; error = backend->iterator(&iter->current, backend); + if (error == GIT_ENOTFOUND) continue; if (error < 0) return error; - error = iter->current->next(entry, iter->current); + if ((error = iter->current->next(&be, iter->current)) == 0) { + *out = be; + return 0; + } + /* If this backend is empty, then keep going */ if (error == GIT_ITEROVER) continue; @@ -416,18 +490,20 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) return GIT_ITEROVER; } -static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) +static int all_iter_glob_next( + git_config_backend_entry **entry, + git_config_iterator *_iter) { int error; all_iter *iter = (all_iter *) _iter; /* * We use the "normal" function to grab the next one across - * backends and then apply the regex + * readers and then apply the regex */ while ((error = all_iter_next(entry, _iter)) == 0) { /* skip non-matching keys if regexp was provided */ - if (git_regexp_match(&iter->regex, (*entry)->name) != 0) + if (git_regexp_match(&iter->regex, (*entry)->entry.name) != 0) continue; /* and simply return if we like the entry's name */ @@ -455,7 +531,7 @@ static void all_iter_glob_free(git_config_iterator *_iter) all_iter_free(_iter); } -int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +int git_config_iterator_new(git_config_iterator **out, const git_config *config) { all_iter *iter; @@ -465,21 +541,21 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) iter->parent.free = all_iter_free; iter->parent.next = all_iter_next; - iter->i = cfg->backends.length; - iter->cfg = cfg; + iter->i = config->readers.length; + iter->config = config; *out = (git_config_iterator *) iter; return 0; } -int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *config, const char *regexp) { all_iter *iter; int result; if (regexp == NULL) - return git_config_iterator_new(out, cfg); + return git_config_iterator_new(out, config); iter = git__calloc(1, sizeof(all_iter)); GIT_ERROR_CHECK_ALLOC(iter); @@ -491,8 +567,8 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf iter->parent.next = all_iter_glob_next; iter->parent.free = all_iter_glob_free; - iter->i = cfg->backends.length; - iter->cfg = cfg; + iter->i = config->readers.length; + iter->config = config; *out = (git_config_iterator *) iter; @@ -500,9 +576,9 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf } int git_config_foreach( - const git_config *cfg, git_config_foreach_cb cb, void *payload) + const git_config *config, git_config_foreach_cb cb, void *payload) { - return git_config_foreach_match(cfg, NULL, cb, payload); + return git_config_foreach_match(config, NULL, cb, payload); } int git_config_backend_foreach_match( @@ -511,7 +587,7 @@ int git_config_backend_foreach_match( git_config_foreach_cb cb, void *payload) { - git_config_entry *entry; + git_config_backend_entry *entry; git_config_iterator *iter; git_regexp regex; int error = 0; @@ -529,11 +605,11 @@ int git_config_backend_foreach_match( while (!(iter->next(&entry, iter) < 0)) { /* skip non-matching keys if regexp was provided */ - if (regexp && git_regexp_match(®ex, entry->name) != 0) + if (regexp && git_regexp_match(®ex, entry->entry.name) != 0) continue; /* abort iterator on non-zero return value */ - if ((error = cb(entry, payload)) != 0) { + if ((error = cb(&entry->entry, payload)) != 0) { git_error_set_after_callback(error); break; } @@ -548,7 +624,7 @@ int git_config_backend_foreach_match( } int git_config_foreach_match( - const git_config *cfg, + const git_config *config, const char *regexp, git_config_foreach_cb cb, void *payload) @@ -557,7 +633,7 @@ int git_config_foreach_match( git_config_iterator *iter; git_config_entry *entry; - if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + if ((error = git_config_iterator_glob_new(&iter, config, regexp)) < 0) return error; while (!(error = git_config_next(&entry, iter))) { @@ -579,72 +655,59 @@ int git_config_foreach_match( * Setters **************/ -typedef enum { - BACKEND_USE_SET, - BACKEND_USE_DELETE -} backend_use; - -static const char *uses[] = { - "set", - "delete" -}; - -static int get_backend_for_use(git_config_backend **out, - git_config *cfg, const char *name, backend_use use) -{ + static backend_instance *get_writer_instance(git_config *config) + { + backend_entry *entry; size_t i; - backend_internal *backend; - *out = NULL; + git_vector_foreach(&config->writers, i, entry) { + if (entry->instance->backend->readonly) + continue; - if (git_vector_length(&cfg->backends) == 0) { - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when no config backends exist", - uses[use], name); - return GIT_ENOTFOUND; - } + if (entry->write_order < 0) + continue; - git_vector_foreach(&cfg->backends, i, backend) { - if (!backend->backend->readonly) { - *out = backend->backend; - return 0; - } + return entry->instance; } - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when all config backends are readonly", - uses[use], name); - return GIT_ENOTFOUND; + return NULL; + } + +static git_config_backend *get_writer(git_config *config) +{ + backend_instance *instance = get_writer_instance(config); + + return instance ? instance->backend : NULL; } -int git_config_delete_entry(git_config *cfg, const char *name) +int git_config_delete_entry(git_config *config, const char *name) { git_config_backend *backend; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((backend = get_writer(config)) == NULL) + return GIT_EREADONLY; return backend->del(backend, name); } -int git_config_set_int64(git_config *cfg, const char *name, int64_t value) +int git_config_set_int64(git_config *config, const char *name, int64_t value) { char str_value[32]; /* All numbers should fit in here */ p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); - return git_config_set_string(cfg, name, str_value); + return git_config_set_string(config, name, str_value); } -int git_config_set_int32(git_config *cfg, const char *name, int32_t value) +int git_config_set_int32(git_config *config, const char *name, int32_t value) { - return git_config_set_int64(cfg, name, (int64_t)value); + return git_config_set_int64(config, name, (int64_t)value); } -int git_config_set_bool(git_config *cfg, const char *name, int value) +int git_config_set_bool(git_config *config, const char *name, int value) { - return git_config_set_string(cfg, name, value ? "true" : "false"); + return git_config_set_string(config, name, value ? "true" : "false"); } -int git_config_set_string(git_config *cfg, const char *name, const char *value) +int git_config_set_string(git_config *config, const char *name, const char *value) { int error; git_config_backend *backend; @@ -654,13 +717,15 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return -1; } - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); + return GIT_EREADONLY; + } error = backend->set(backend, name, value); - if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) - git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); + if (!error && GIT_REFCOUNT_OWNER(config) != NULL) + git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(config)); return error; } @@ -714,16 +779,18 @@ enum { static int get_entry( git_config_entry **out, - const git_config *cfg, + const git_config *config, const char *name, bool normalize_name, int want_errors) { + backend_entry *entry; + git_config_backend *backend; + git_config_backend_entry *be; int res = GIT_ENOTFOUND; const char *key = name; char *normalized = NULL; size_t i; - backend_internal *internal; *out = NULL; @@ -734,21 +801,24 @@ static int get_entry( } res = GIT_ENOTFOUND; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; + git_vector_foreach(&config->readers, i, entry) { + GIT_ASSERT(entry->instance && entry->instance->backend); + + backend = entry->instance->backend; + res = backend->get(backend, key, &be); - res = internal->backend->get(internal->backend, key, out); - if (res != GIT_ENOTFOUND) + if (res != GIT_ENOTFOUND) { + *out = &be->entry; break; + } } git__free(normalized); cleanup: - if (res == GIT_ENOTFOUND) + if (res == GIT_ENOTFOUND) { res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); - else if (res && (want_errors == GET_NO_ERRORS)) { + } else if (res && (want_errors == GET_NO_ERRORS)) { git_error_clear(); res = 0; } @@ -757,24 +827,24 @@ static int get_entry( } int git_config_get_entry( - git_config_entry **out, const git_config *cfg, const char *name) + git_config_entry **out, const git_config *config, const char *name) { - return get_entry(out, cfg, name, true, GET_ALL_ERRORS); + return get_entry(out, config, name, true, GET_ALL_ERRORS); } int git_config__lookup_entry( git_config_entry **out, - const git_config *cfg, + const git_config *config, const char *key, bool no_errors) { return get_entry( - out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); + out, config, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); } int git_config_get_mapped( int *out, - const git_config *cfg, + const git_config *config, const char *name, const git_configmap *maps, size_t map_n) @@ -782,7 +852,7 @@ int git_config_get_mapped( git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_lookup_map_value(out, maps, map_n, entry->value); @@ -791,12 +861,12 @@ int git_config_get_mapped( return ret; } -int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) +int git_config_get_int64(int64_t *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_int64(out, entry->value); @@ -805,12 +875,12 @@ int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) return ret; } -int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) +int git_config_get_int32(int32_t *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_int32(out, entry->value); @@ -819,12 +889,12 @@ int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) return ret; } -int git_config_get_bool(int *out, const git_config *cfg, const char *name) +int git_config_get_bool(int *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_bool(out, entry->value); @@ -833,16 +903,15 @@ int git_config_get_bool(int *out, const git_config *cfg, const char *name) return ret; } -static int is_readonly(const git_config *cfg) +static int is_readonly(const git_config *config) { + backend_entry *entry; size_t i; - backend_internal *internal; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; + git_vector_foreach(&config->writers, i, entry) { + GIT_ASSERT(entry->instance && entry->instance->backend); - if (!internal->backend->readonly) + if (!entry->instance->backend->readonly) return 0; } @@ -873,21 +942,21 @@ int git_config_parse_path(git_buf *out, const char *value) int git_config_get_path( git_buf *out, - const git_config *cfg, + const git_config *config, const char *name) { - GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); + GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, config, name); } int git_config__get_path( git_str *out, - const git_config *cfg, + const git_config *config, const char *name) { git_config_entry *entry; int error; - if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((error = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return error; error = git_config__parse_path(out, entry->value); @@ -897,17 +966,17 @@ int git_config__get_path( } int git_config_get_string( - const char **out, const git_config *cfg, const char *name) + const char **out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if (!is_readonly(cfg)) { + if (!is_readonly(config)) { git_error_set(GIT_ERROR_CONFIG, "get_string called on a live config object"); return -1; } - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS); *out = !ret ? (entry->value ? entry->value : "") : NULL; git_config_entry_free(entry); @@ -916,22 +985,22 @@ int git_config_get_string( } int git_config_get_string_buf( - git_buf *out, const git_config *cfg, const char *name) + git_buf *out, const git_config *config, const char *name) { - GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); + GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, config, name); } int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name) + git_str *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; const char *str; GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS); str = !ret ? (entry->value ? entry->value : "") : NULL; if (str) @@ -943,12 +1012,12 @@ int git_config__get_string_buf( } char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value) + const git_config *config, const char *key, const char *fallback_value) { git_config_entry *entry; char *ret; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, key, false, GET_NO_ERRORS); ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL; git_config_entry_free(entry); @@ -956,12 +1025,12 @@ char *git_config__get_string_force( } int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value) + const git_config *config, const char *key, int fallback_value) { int val = fallback_value; git_config_entry *entry; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, key, false, GET_NO_ERRORS); if (entry && git_config_parse_bool(&val, entry->value) < 0) git_error_clear(); @@ -971,12 +1040,12 @@ int git_config__get_bool_force( } int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value) + const git_config *config, const char *key, int fallback_value) { int32_t val = (int32_t)fallback_value; git_config_entry *entry; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, key, false, GET_NO_ERRORS); if (entry && git_config_parse_int32(&val, entry->value) < 0) git_error_clear(); @@ -986,21 +1055,21 @@ int git_config__get_int_force( } int git_config_get_multivar_foreach( - const git_config *cfg, const char *name, const char *regexp, + const git_config *config, const char *name, const char *regexp, git_config_foreach_cb cb, void *payload) { int err, found; git_config_iterator *iter; - git_config_entry *entry; + git_config_backend_entry *be; - if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) + if ((err = git_config_multivar_iterator_new(&iter, config, name, regexp)) < 0) return err; found = 0; - while ((err = iter->next(&entry, iter)) == 0) { + while ((err = iter->next(&be, iter)) == 0) { found = 1; - if ((err = cb(entry, payload)) != 0) { + if ((err = cb(&be->entry, payload)) != 0) { git_error_set_after_callback(err); break; } @@ -1024,19 +1093,21 @@ typedef struct { int have_regex; } multivar_iter; -static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) +static int multivar_iter_next( + git_config_backend_entry **entry, + git_config_iterator *_iter) { multivar_iter *iter = (multivar_iter *) _iter; int error = 0; while ((error = iter->iter->next(entry, iter->iter)) == 0) { - if (git__strcmp(iter->name, (*entry)->name)) + if (git__strcmp(iter->name, (*entry)->entry.name)) continue; if (!iter->have_regex) return 0; - if (git_regexp_match(&iter->regex, (*entry)->value) == 0) + if (git_regexp_match(&iter->regex, (*entry)->entry.value) == 0) return 0; } @@ -1055,13 +1126,13 @@ static void multivar_iter_free(git_config_iterator *_iter) git__free(iter); } -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *config, const char *name, const char *regexp) { multivar_iter *iter = NULL; git_config_iterator *inner = NULL; int error; - if ((error = git_config_iterator_new(&inner, cfg)) < 0) + if ((error = git_config_iterator_new(&inner, config)) < 0) return error; iter = git__calloc(1, sizeof(multivar_iter)); @@ -1092,29 +1163,38 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config return error; } -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) +int git_config_set_multivar(git_config *config, const char *name, const char *regexp, const char *value) { git_config_backend *backend; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); + return GIT_EREADONLY; + } return backend->set_multivar(backend, name, regexp, value); } -int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) +int git_config_delete_multivar(git_config *config, const char *name, const char *regexp) { git_config_backend *backend; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((backend = get_writer(config)) == NULL) + return GIT_EREADONLY; return backend->del_multivar(backend, name, regexp); } int git_config_next(git_config_entry **entry, git_config_iterator *iter) { - return iter->next(entry, iter); + git_config_backend_entry *be; + int error; + + if ((error = iter->next(&be, iter)) != 0) + return error; + + *entry = &be->entry; + return 0; } void git_config_iterator_free(git_config_iterator *iter) @@ -1218,79 +1298,77 @@ int git_config__global_location(git_str *buf) int git_config_open_default(git_config **out) { int error; - git_config *cfg = NULL; + git_config *config = NULL; git_str buf = GIT_STR_INIT; - if ((error = git_config_new(&cfg)) < 0) + if ((error = git_config_new(&config)) < 0) return error; if (!git_config__find_global(&buf) || !git_config__global_location(&buf)) { - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); } if (!error && !git_config__find_xdg(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_XDG, NULL, 0); if (!error && !git_config__find_system(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); if (!error && !git_config__find_programdata(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); git_str_dispose(&buf); if (error) { - git_config_free(cfg); - cfg = NULL; + git_config_free(config); + config = NULL; } - *out = cfg; + *out = config; return error; } -int git_config_lock(git_transaction **out, git_config *cfg) +int git_config_lock(git_transaction **out, git_config *config) { + backend_instance *instance; int error; - git_config_backend *backend; - backend_internal *internal; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - internal = git_vector_get(&cfg->backends, 0); - if (!internal || !internal->backend) { - git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); - return -1; + if ((instance = get_writer_instance(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock: the configuration is read-only"); + return GIT_EREADONLY; } - backend = internal->backend; - if ((error = backend->lock(backend)) < 0) + if ((error = instance->backend->lock(instance->backend)) < 0 || + (error = git_transaction_config_new(out, config, instance)) < 0) return error; - return git_transaction_config_new(out, cfg); + GIT_REFCOUNT_INC(instance); + return 0; } -int git_config_unlock(git_config *cfg, int commit) +int git_config_unlock( + git_config *config, + void *data, + int commit) { - git_config_backend *backend; - backend_internal *internal; - - GIT_ASSERT_ARG(cfg); + backend_instance *instance = data; + int error; - internal = git_vector_get(&cfg->backends, 0); - if (!internal || !internal->backend) { - git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); - return -1; - } + GIT_ASSERT_ARG(config && data); + GIT_UNUSED(config); - backend = internal->backend; + error = instance->backend->unlock(instance->backend, commit); + GIT_REFCOUNT_DEC(instance, backend_instance_free); - return backend->unlock(backend, commit); + return error; } /*********** @@ -1447,7 +1525,7 @@ static int normalize_section(char *start, char *end) for (scan = start; *scan; ++scan) { if (end && scan >= end) break; - if (isalnum(*scan)) + if (git__isalnum(*scan)) *scan = (char)git__tolower(*scan); else if (*scan != '-' || scan == start) return GIT_EINVALIDSPEC; @@ -1509,19 +1587,32 @@ static int rename_config_entries_cb( int error = 0; struct rename_data *data = (struct rename_data *)payload; size_t base_len = git_str_len(data->name); + git_str value = GIT_STR_INIT; + + if (base_len > 0) { + if ((error = git_str_puts(data->name, + entry->name + data->old_len)) < 0 || + (error = git_config_set_multivar( + data->config, git_str_cstr(data->name), "^$", + entry->value)) < 0) + goto cleanup; + } - if (base_len > 0 && - !(error = git_str_puts(data->name, entry->name + data->old_len))) - { - error = git_config_set_string( - data->config, git_str_cstr(data->name), entry->value); + git_str_putc(&value, '^'); + git_str_puts_escape_regex(&value, entry->value); + git_str_putc(&value, '$'); - git_str_truncate(data->name, base_len); + if (git_str_oom(&value)) { + error = -1; + goto cleanup; } - if (!error) - error = git_config_delete_entry(data->config, entry->name); + error = git_config_delete_multivar( + data->config, entry->name, git_str_cstr(&value)); + cleanup: + git_str_truncate(data->name, base_len); + git_str_dispose(&value); return error; } diff --git a/src/libgit2/config.cmake.in b/src/libgit2/config.cmake.in new file mode 100644 index 00000000..6d15e058 --- /dev/null +++ b/src/libgit2/config.cmake.in @@ -0,0 +1,3 @@ +include(CMakeFindDependencyMacro) + +include("${CMAKE_CURRENT_LIST_DIR}/@LIBGIT2_TARGETS_EXPORT_NAME@.cmake") \ No newline at end of file diff --git a/src/libgit2/config.h b/src/libgit2/config.h index 01b84b15..5003cbf9 100644 --- a/src/libgit2/config.h +++ b/src/libgit2/config.h @@ -24,7 +24,8 @@ struct git_config { git_refcount rc; - git_vector backends; + git_vector readers; + git_vector writers; }; extern int git_config__global_location(git_str *buf); @@ -94,17 +95,21 @@ int git_config_lookup_map_enum(git_configmap_t *type_out, size_t map_n, int enum_val); /** - * Unlock the backend with the highest priority + * Unlock the given backend that was previously locked. * * Unlocking will allow other writers to update the configuration * file. Optionally, any changes performed since the lock will be * applied to the configuration. * - * @param cfg the configuration + * @param config the config instance + * @param data the config data passed to git_transaction_new * @param commit boolean which indicates whether to commit any changes * done since locking * @return 0 or an error code */ -GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); +GIT_EXTERN(int) git_config_unlock( + git_config *config, + void *data, + int commit); #endif diff --git a/src/libgit2/config_backend.h b/src/libgit2/config_backend.h index dbb19051..786c5de1 100644 --- a/src/libgit2/config_backend.h +++ b/src/libgit2/config_backend.h @@ -37,15 +37,6 @@ extern int git_config_backend_from_file(git_config_backend **out, const char *pa */ extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source); -/** - * Create an in-memory configuration file backend - * - * @param out the new backend - * @param cfg the configuration that is to be parsed - * @param len the length of the string pointed to by `cfg` - */ -extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len); - GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) { return cfg->open(cfg, level, repo); @@ -60,7 +51,14 @@ GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg) GIT_INLINE(int) git_config_backend_get_string( git_config_entry **out, git_config_backend *cfg, const char *name) { - return cfg->get(cfg, name, out); + git_config_backend_entry *be; + int error; + + if ((error = cfg->get(cfg, name, &be)) < 0) + return error; + + *out = &be->entry; + return 0; } GIT_INLINE(int) git_config_backend_set_string( diff --git a/src/libgit2/config_cache.c b/src/libgit2/config_cache.c index 4bb91f52..05d9d582 100644 --- a/src/libgit2/config_cache.c +++ b/src/libgit2/config_cache.c @@ -64,11 +64,10 @@ static git_configmap _configmap_logallrefupdates[] = { {GIT_CONFIGMAP_STRING, "always", GIT_LOGALLREFUPDATES_ALWAYS}, }; -/* - * Generic map for integer values - */ -static git_configmap _configmap_int[] = { +static git_configmap _configmap_abbrev[] = { {GIT_CONFIGMAP_INT32, NULL, 0}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_ABBREV_FALSE}, + {GIT_CONFIGMAP_STRING, "auto", GIT_ABBREV_DEFAULT} }; static struct map_data _configmaps[] = { @@ -79,7 +78,7 @@ static struct map_data _configmaps[] = { {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT }, {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT }, {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, - {"core.abbrev", _configmap_int, 1, GIT_ABBREV_DEFAULT }, + {"core.abbrev", _configmap_abbrev, ARRAY_SIZE(_configmap_abbrev), GIT_ABBREV_DEFAULT }, {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, {"core.safecrlf", _configmap_safecrlf, ARRAY_SIZE(_configmap_safecrlf), GIT_SAFE_CRLF_DEFAULT}, {"core.logallrefupdates", _configmap_logallrefupdates, ARRAY_SIZE(_configmap_logallrefupdates), GIT_LOGALLREFUPDATES_DEFAULT}, diff --git a/src/libgit2/config_entries.c b/src/libgit2/config_entries.c deleted file mode 100644 index 66aae096..00000000 --- a/src/libgit2/config_entries.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config_entries.h" - -typedef struct config_entry_list { - struct config_entry_list *next; - struct config_entry_list *last; - git_config_entry *entry; -} config_entry_list; - -typedef struct { - git_config_entry *entry; - bool multivar; -} config_entry_map_head; - -typedef struct config_entries_iterator { - git_config_iterator parent; - git_config_entries *entries; - config_entry_list *head; -} config_entries_iterator; - -struct git_config_entries { - git_refcount rc; - git_strmap *map; - config_entry_list *list; -}; - -int git_config_entries_new(git_config_entries **out) -{ - git_config_entries *entries; - int error; - - entries = git__calloc(1, sizeof(git_config_entries)); - GIT_ERROR_CHECK_ALLOC(entries); - GIT_REFCOUNT_INC(entries); - - if ((error = git_strmap_new(&entries->map)) < 0) - git__free(entries); - else - *out = entries; - - return error; -} - -int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry) -{ - git_config_entry *duplicated; - int error; - - duplicated = git__calloc(1, sizeof(git_config_entry)); - GIT_ERROR_CHECK_ALLOC(duplicated); - - duplicated->name = git__strdup(entry->name); - GIT_ERROR_CHECK_ALLOC(duplicated->name); - - if (entry->value) { - duplicated->value = git__strdup(entry->value); - GIT_ERROR_CHECK_ALLOC(duplicated->value); - } - duplicated->level = entry->level; - duplicated->include_depth = entry->include_depth; - - if ((error = git_config_entries_append(entries, duplicated)) < 0) - goto out; - -out: - if (error && duplicated) { - git__free((char *) duplicated->name); - git__free((char *) duplicated->value); - git__free(duplicated); - } - return error; -} - -int git_config_entries_dup(git_config_entries **out, git_config_entries *entries) -{ - git_config_entries *result = NULL; - config_entry_list *head; - int error; - - if ((error = git_config_entries_new(&result)) < 0) - goto out; - - for (head = entries->list; head; head = head->next) - if ((git_config_entries_dup_entry(result, head->entry)) < 0) - goto out; - - *out = result; - result = NULL; - -out: - git_config_entries_free(result); - return error; -} - -void git_config_entries_incref(git_config_entries *entries) -{ - GIT_REFCOUNT_INC(entries); -} - -static void config_entries_free(git_config_entries *entries) -{ - config_entry_list *list = NULL, *next; - config_entry_map_head *head; - - git_strmap_foreach_value(entries->map, head, - git__free((char *) head->entry->name); git__free(head) - ); - git_strmap_free(entries->map); - - list = entries->list; - while (list != NULL) { - next = list->next; - git__free((char *) list->entry->value); - git__free(list->entry); - git__free(list); - list = next; - } - - git__free(entries); -} - -void git_config_entries_free(git_config_entries *entries) -{ - if (entries) - GIT_REFCOUNT_DEC(entries, config_entries_free); -} - -int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) -{ - config_entry_list *list_head; - config_entry_map_head *map_head; - - if ((map_head = git_strmap_get(entries->map, entry->name)) != NULL) { - map_head->multivar = true; - /* - * This is a micro-optimization for configuration files - * with a lot of same keys. As for multivars the entry's - * key will be the same for all entries, we can just free - * all except the first entry's name and just re-use it. - */ - git__free((char *) entry->name); - entry->name = map_head->entry->name; - } else { - map_head = git__calloc(1, sizeof(*map_head)); - if ((git_strmap_set(entries->map, entry->name, map_head)) < 0) - return -1; - } - map_head->entry = entry; - - list_head = git__calloc(1, sizeof(config_entry_list)); - GIT_ERROR_CHECK_ALLOC(list_head); - list_head->entry = entry; - - if (entries->list) - entries->list->last->next = list_head; - else - entries->list = list_head; - entries->list->last = list_head; - - return 0; -} - -int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) -{ - config_entry_map_head *entry; - if ((entry = git_strmap_get(entries->map, key)) == NULL) - return GIT_ENOTFOUND; - *out = entry->entry; - return 0; -} - -int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key) -{ - config_entry_map_head *entry; - - if ((entry = git_strmap_get(entries->map, key)) == NULL) - return GIT_ENOTFOUND; - - if (entry->multivar) { - git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); - return -1; - } - - if (entry->entry->include_depth) { - git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included"); - return -1; - } - - *out = entry->entry; - - return 0; -} - -static void config_iterator_free(git_config_iterator *iter) -{ - config_entries_iterator *it = (config_entries_iterator *) iter; - git_config_entries_free(it->entries); - git__free(it); -} - -static int config_iterator_next( - git_config_entry **entry, - git_config_iterator *iter) -{ - config_entries_iterator *it = (config_entries_iterator *) iter; - - if (!it->head) - return GIT_ITEROVER; - - *entry = it->head->entry; - it->head = it->head->next; - - return 0; -} - -int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries) -{ - config_entries_iterator *it; - - it = git__calloc(1, sizeof(config_entries_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - it->parent.next = config_iterator_next; - it->parent.free = config_iterator_free; - it->head = entries->list; - it->entries = entries; - - git_config_entries_incref(entries); - *out = &it->parent; - - return 0; -} diff --git a/src/libgit2/config_entries.h b/src/libgit2/config_entries.h deleted file mode 100644 index 832379e7..00000000 --- a/src/libgit2/config_entries.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/sys/config.h" -#include "config.h" - -typedef struct git_config_entries git_config_entries; - -int git_config_entries_new(git_config_entries **out); -int git_config_entries_dup(git_config_entries **out, git_config_entries *entries); -int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry); -void git_config_entries_incref(git_config_entries *entries); -void git_config_entries_free(git_config_entries *entries); -/* Add or append the new config option */ -int git_config_entries_append(git_config_entries *entries, git_config_entry *entry); -int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key); -int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key); -int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries); diff --git a/src/libgit2/config_file.c b/src/libgit2/config_file.c index 716924de..510f6fd0 100644 --- a/src/libgit2/config_file.c +++ b/src/libgit2/config_file.c @@ -13,7 +13,7 @@ #include "array.h" #include "str.h" #include "config_backend.h" -#include "config_entries.h" +#include "config_list.h" #include "config_parse.h" #include "filebuf.h" #include "regexp.h" @@ -24,6 +24,8 @@ /* Max depth for [include] directives */ #define MAX_INCLUDE_DEPTH 10 +#define CONFIG_FILE_TYPE "file" + typedef struct config_file { git_futils_filestamp stamp; unsigned char checksum[GIT_HASH_SHA256_SIZE]; @@ -34,7 +36,7 @@ typedef struct config_file { typedef struct { git_config_backend parent; git_mutex values_mutex; - git_config_entries *entries; + git_config_list *config_list; const git_repository *repo; git_config_level_t level; @@ -50,13 +52,13 @@ typedef struct { typedef struct { const git_repository *repo; config_file *file; - git_config_entries *entries; + git_config_list *config_list; git_config_level_t level; unsigned int depth; } config_file_parse_data; -static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth); -static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen); +static int config_file_read(git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, int depth); +static int config_file_read_buffer(git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen); static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value); static char *escape_value(const char *ptr); @@ -65,7 +67,7 @@ static char *escape_value(const char *ptr); * refcount. This is its own function to make sure we use the mutex to * avoid the map pointer from changing under us. */ -static int config_file_entries_take(git_config_entries **out, config_file_backend *b) +static int config_file_take_list(git_config_list **out, config_file_backend *b) { int error; @@ -74,8 +76,8 @@ static int config_file_entries_take(git_config_entries **out, config_file_backen return error; } - git_config_entries_incref(b->entries); - *out = b->entries; + git_config_list_incref(b->config_list); + *out = b->config_list; git_mutex_unlock(&b->values_mutex); @@ -106,7 +108,7 @@ static int config_file_open(git_config_backend *cfg, git_config_level_t level, c b->level = level; b->repo = repo; - if ((res = git_config_entries_new(&b->entries)) < 0) + if ((res = git_config_list_new(&b->config_list)) < 0) return res; if (!git_fs_path_exists(b->file.path)) @@ -121,9 +123,9 @@ static int config_file_open(git_config_backend *cfg, git_config_level_t level, c if (p_access(b->file.path, R_OK) < 0) return GIT_ENOTFOUND; - if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) { - git_config_entries_free(b->entries); - b->entries = NULL; + if (res < 0 || (res = config_file_read(b->config_list, repo, &b->file, level, 0)) < 0) { + git_config_list_free(b->config_list); + b->config_list = NULL; } return res; @@ -175,10 +177,10 @@ static void config_file_clear_includes(config_file_backend *cfg) git_array_clear(cfg->file.includes); } -static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) +static int config_file_set_entries(git_config_backend *cfg, git_config_list *config_list) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *old = NULL; + git_config_list *old = NULL; int error; if (b->parent.readonly) { @@ -191,40 +193,40 @@ static int config_file_set_entries(git_config_backend *cfg, git_config_entries * goto out; } - old = b->entries; - b->entries = entries; + old = b->config_list; + b->config_list = config_list; git_mutex_unlock(&b->values_mutex); out: - git_config_entries_free(old); + git_config_list_free(old); return error; } static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; + git_config_list *config_list = NULL; int error; config_file_clear_includes(b); - if ((error = git_config_entries_new(&entries)) < 0 || - (error = config_file_read_buffer(entries, b->repo, &b->file, + if ((error = git_config_list_new(&config_list)) < 0 || + (error = config_file_read_buffer(config_list, b->repo, &b->file, b->level, 0, buf, buflen)) < 0 || - (error = config_file_set_entries(cfg, entries)) < 0) + (error = config_file_set_entries(cfg, config_list)) < 0) goto out; - entries = NULL; + config_list = NULL; out: - git_config_entries_free(entries); + git_config_list_free(config_list); return error; } static int config_file_refresh(git_config_backend *cfg) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; + git_config_list *config_list = NULL; int error, modified; if (cfg->readonly) @@ -238,14 +240,14 @@ static int config_file_refresh(git_config_backend *cfg) config_file_clear_includes(b); - if ((error = git_config_entries_new(&entries)) < 0 || - (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 || - (error = config_file_set_entries(cfg, entries)) < 0) + if ((error = git_config_list_new(&config_list)) < 0 || + (error = config_file_read(config_list, b->repo, &b->file, b->level, 0)) < 0 || + (error = config_file_set_entries(cfg, config_list)) < 0) goto out; - entries = NULL; + config_list = NULL; out: - git_config_entries_free(entries); + git_config_list_free(config_list); return (error == GIT_ENOTFOUND) ? 0 : error; } @@ -258,7 +260,7 @@ static void config_file_free(git_config_backend *_backend) return; config_file_clear(&backend->file); - git_config_entries_free(backend->entries); + git_config_list_free(backend->config_list); git_mutex_free(&backend->values_mutex); git__free(backend); } @@ -268,19 +270,19 @@ static int config_file_iterator( struct git_config_backend *backend) { config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); - git_config_entries *dupped = NULL, *entries = NULL; + git_config_list *dupped = NULL, *config_list = NULL; int error; if ((error = config_file_refresh(backend)) < 0 || - (error = config_file_entries_take(&entries, b)) < 0 || - (error = git_config_entries_dup(&dupped, entries)) < 0 || - (error = git_config_entries_iterator_new(iter, dupped)) < 0) + (error = config_file_take_list(&config_list, b)) < 0 || + (error = git_config_list_dup(&dupped, config_list)) < 0 || + (error = git_config_list_iterator_new(iter, dupped)) < 0) goto out; out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); - git_config_entries_free(dupped); + /* Let iterator delete duplicated config_list when it's done */ + git_config_list_free(config_list); + git_config_list_free(dupped); return error; } @@ -292,24 +294,24 @@ static int config_file_snapshot(git_config_backend **out, git_config_backend *ba static int config_file_set(git_config_backend *cfg, const char *name, const char *value) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries; - git_config_entry *existing; + git_config_list *config_list; + git_config_list_entry *existing; char *key, *esc_value = NULL; int error; if ((error = git_config__normalize_name(name, &key)) < 0) return error; - if ((error = config_file_entries_take(&entries, b)) < 0) + if ((error = config_file_take_list(&config_list, b)) < 0) return error; /* Check whether we'd be modifying an included or multivar key */ - if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) { + if ((error = git_config_list_get_unique(&existing, config_list, key)) < 0) { if (error != GIT_ENOTFOUND) goto out; error = 0; - } else if ((!existing->value && !value) || - (existing->value && value && !strcmp(existing->value, value))) { + } else if ((!existing->base.entry.value && !value) || + (existing->base.entry.value && value && !strcmp(existing->base.entry.value, value))) { /* don't update if old and new values already match */ error = 0; goto out; @@ -325,43 +327,37 @@ static int config_file_set(git_config_backend *cfg, const char *name, const char goto out; out: - git_config_entries_free(entries); + git_config_list_free(config_list); git__free(esc_value); git__free(key); return error; } -/* release the map containing the entry as an equivalent to freeing it */ -static void config_file_entry_free(git_config_entry *entry) -{ - git_config_entries *entries = (git_config_entries *) entry->payload; - git_config_entries_free(entries); -} - /* * Internal function that actually gets the value in string form */ -static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) +static int config_file_get( + git_config_backend *cfg, + const char *key, + git_config_backend_entry **out) { config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; + git_config_list *config_list = NULL; + git_config_list_entry *entry; int error = 0; if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0)) return error; - if ((error = config_file_entries_take(&entries, h)) < 0) + if ((error = config_file_take_list(&config_list, h)) < 0) return error; - if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { - git_config_entries_free(entries); + if ((error = (git_config_list_get(&entry, config_list, key))) < 0) { + git_config_list_free(config_list); return error; } - entry->free = config_file_entry_free; - entry->payload = entries; - *out = entry; + *out = &entry->base; return 0; } @@ -396,29 +392,29 @@ static int config_file_set_multivar( static int config_file_delete(git_config_backend *cfg, const char *name) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; + git_config_list *config_list = NULL; + git_config_list_entry *entry; char *key = NULL; int error; if ((error = git_config__normalize_name(name, &key)) < 0) goto out; - if ((error = config_file_entries_take(&entries, b)) < 0) + if ((error = config_file_take_list(&config_list, b)) < 0) goto out; /* Check whether we'd be modifying an included or multivar key */ - if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) { + if ((error = git_config_list_get_unique(&entry, config_list, key)) < 0) { if (error == GIT_ENOTFOUND) git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); goto out; } - if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) + if ((error = config_file_write(b, name, entry->base.entry.name, NULL, NULL)) < 0) goto out; out: - git_config_entries_free(entries); + git_config_list_free(config_list); git__free(key); return error; } @@ -426,8 +422,8 @@ static int config_file_delete(git_config_backend *cfg, const char *name) static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) { config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry = NULL; + git_config_list *config_list = NULL; + git_config_list_entry *entry = NULL; git_regexp preg = GIT_REGEX_INIT; char *key = NULL; int result; @@ -435,10 +431,10 @@ static int config_file_delete_multivar(git_config_backend *cfg, const char *name if ((result = git_config__normalize_name(name, &key)) < 0) goto out; - if ((result = config_file_entries_take(&entries, b)) < 0) + if ((result = config_file_take_list(&config_list, b)) < 0) goto out; - if ((result = git_config_entries_get(&entry, entries, key)) < 0) { + if ((result = git_config_list_get(&entry, config_list, key)) < 0) { if (result == GIT_ENOTFOUND) git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); goto out; @@ -451,7 +447,7 @@ static int config_file_delete_multivar(git_config_backend *cfg, const char *name goto out; out: - git_config_entries_free(entries); + git_config_list_free(config_list); git__free(key); git_regexp_dispose(&preg); return result; @@ -591,7 +587,7 @@ static int parse_include(config_file_parse_data *parse_data, const char *file) git_array_init(include->includes); include->path = git_str_detach(&path); - result = config_file_read(parse_data->entries, parse_data->repo, include, + result = config_file_read(parse_data->config_list, parse_data->repo, include, parse_data->level, parse_data->depth+1); if (result == GIT_ENOTFOUND) { @@ -776,7 +772,7 @@ static int read_on_variable( { config_file_parse_data *parse_data = (config_file_parse_data *)data; git_str buf = GIT_STR_INIT; - git_config_entry *entry; + git_config_list_entry *entry; const char *c; int result = 0; @@ -799,30 +795,45 @@ static int read_on_variable( if (git_str_oom(&buf)) return -1; - entry = git__calloc(1, sizeof(git_config_entry)); + entry = git__calloc(1, sizeof(git_config_list_entry)); GIT_ERROR_CHECK_ALLOC(entry); - entry->name = git_str_detach(&buf); - entry->value = var_value ? git__strdup(var_value) : NULL; - entry->level = parse_data->level; - entry->include_depth = parse_data->depth; - if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) + entry->base.entry.name = git_str_detach(&buf); + GIT_ERROR_CHECK_ALLOC(entry->base.entry.name); + + if (var_value) { + entry->base.entry.value = git__strdup(var_value); + GIT_ERROR_CHECK_ALLOC(entry->base.entry.value); + } + + entry->base.entry.backend_type = git_config_list_add_string(parse_data->config_list, CONFIG_FILE_TYPE); + GIT_ERROR_CHECK_ALLOC(entry->base.entry.backend_type); + + entry->base.entry.origin_path = git_config_list_add_string(parse_data->config_list, parse_data->file->path); + GIT_ERROR_CHECK_ALLOC(entry->base.entry.origin_path); + + entry->base.entry.level = parse_data->level; + entry->base.entry.include_depth = parse_data->depth; + entry->base.free = git_config_list_entry_free; + entry->config_list = parse_data->config_list; + + if ((result = git_config_list_append(parse_data->config_list, entry)) < 0) return result; result = 0; /* Add or append the new config option */ - if (!git__strcmp(entry->name, "include.path")) - result = parse_include(parse_data, entry->value); - else if (!git__prefixcmp(entry->name, "includeif.") && - !git__suffixcmp(entry->name, ".path")) - result = parse_conditional_include(parse_data, entry->name, entry->value); + if (!git__strcmp(entry->base.entry.name, "include.path")) + result = parse_include(parse_data, entry->base.entry.value); + else if (!git__prefixcmp(entry->base.entry.name, "includeif.") && + !git__suffixcmp(entry->base.entry.name, ".path")) + result = parse_conditional_include(parse_data, entry->base.entry.name, entry->base.entry.value); return result; } static int config_file_read_buffer( - git_config_entries *entries, + git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, @@ -851,7 +862,7 @@ static int config_file_read_buffer( parse_data.repo = repo; parse_data.file = file; - parse_data.entries = entries; + parse_data.config_list = config_list; parse_data.level = level; parse_data.depth = depth; @@ -862,7 +873,7 @@ static int config_file_read_buffer( } static int config_file_read( - git_config_entries *entries, + git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, @@ -884,7 +895,7 @@ static int config_file_read( if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA256)) < 0) goto out; - if ((error = config_file_read_buffer(entries, repo, file, level, depth, + if ((error = config_file_read_buffer(config_list, repo, file, level, depth, contents.ptr, contents.size)) < 0) goto out; diff --git a/src/libgit2/config_list.c b/src/libgit2/config_list.c new file mode 100644 index 00000000..0e655979 --- /dev/null +++ b/src/libgit2/config_list.c @@ -0,0 +1,285 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config_list.h" +#include "hashmap_str.h" + +typedef struct config_entry_list { + struct config_entry_list *next; + struct config_entry_list *last; + git_config_list_entry *entry; +} config_entry_list; + +typedef struct { + git_config_list_entry *entry; + bool multivar; +} config_entry_map_head; + +typedef struct config_list_iterator { + git_config_iterator parent; + git_config_list *list; + config_entry_list *head; +} config_list_iterator; + +GIT_HASHMAP_STR_SETUP(git_config_list_pathmap, char *); +GIT_HASHMAP_STR_SETUP(git_config_list_headmap, config_entry_map_head *); + +struct git_config_list { + git_refcount rc; + + /* Interned strings - paths to config files or backend types */ + git_config_list_pathmap strings; + + /* Config entries */ + git_config_list_headmap map; + config_entry_list *entries; +}; + +int git_config_list_new(git_config_list **out) +{ + git_config_list *config_list; + + config_list = git__calloc(1, sizeof(git_config_list)); + GIT_ERROR_CHECK_ALLOC(config_list); + GIT_REFCOUNT_INC(config_list); + + *out = config_list; + return 0; +} + +int git_config_list_dup_entry(git_config_list *config_list, const git_config_entry *entry) +{ + git_config_list_entry *duplicated; + int error; + + duplicated = git__calloc(1, sizeof(git_config_list_entry)); + GIT_ERROR_CHECK_ALLOC(duplicated); + + duplicated->base.entry.name = git__strdup(entry->name); + GIT_ERROR_CHECK_ALLOC(duplicated->base.entry.name); + + if (entry->value) { + duplicated->base.entry.value = git__strdup(entry->value); + GIT_ERROR_CHECK_ALLOC(duplicated->base.entry.value); + } + + duplicated->base.entry.backend_type = git_config_list_add_string(config_list, entry->backend_type); + GIT_ERROR_CHECK_ALLOC(duplicated->base.entry.backend_type); + + if (entry->origin_path) { + duplicated->base.entry.origin_path = git_config_list_add_string(config_list, entry->origin_path); + GIT_ERROR_CHECK_ALLOC(duplicated->base.entry.origin_path); + } + + duplicated->base.entry.level = entry->level; + duplicated->base.entry.include_depth = entry->include_depth; + duplicated->base.free = git_config_list_entry_free; + duplicated->config_list = config_list; + + if ((error = git_config_list_append(config_list, duplicated)) < 0) + goto out; + +out: + if (error && duplicated) { + git__free((char *) duplicated->base.entry.name); + git__free((char *) duplicated->base.entry.value); + git__free(duplicated); + } + return error; +} + +int git_config_list_dup(git_config_list **out, git_config_list *config_list) +{ + git_config_list *result = NULL; + config_entry_list *head; + int error; + + if ((error = git_config_list_new(&result)) < 0) + goto out; + + for (head = config_list->entries; head; head = head->next) + if ((git_config_list_dup_entry(result, &head->entry->base.entry)) < 0) + goto out; + + *out = result; + result = NULL; + +out: + git_config_list_free(result); + return error; +} + +void git_config_list_incref(git_config_list *config_list) +{ + GIT_REFCOUNT_INC(config_list); +} + +static void config_list_free(git_config_list *config_list) +{ + config_entry_list *entry_list = NULL, *next; + config_entry_map_head *head; + char *str; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + + while (git_config_list_pathmap_iterate(&iter, NULL, &str, &config_list->strings) == 0) + git__free(str); + + git_config_list_pathmap_dispose(&config_list->strings); + + iter = GIT_HASHMAP_ITER_INIT; + while (git_config_list_headmap_iterate(&iter, NULL, &head, &config_list->map) == 0) { + git__free((char *) head->entry->base.entry.name); + git__free(head); + } + git_config_list_headmap_dispose(&config_list->map); + + entry_list = config_list->entries; + while (entry_list != NULL) { + next = entry_list->next; + git__free((char *) entry_list->entry->base.entry.value); + git__free(entry_list->entry); + git__free(entry_list); + entry_list = next; + } + + git__free(config_list); +} + +void git_config_list_free(git_config_list *config_list) +{ + if (config_list) + GIT_REFCOUNT_DEC(config_list, config_list_free); +} + +int git_config_list_append(git_config_list *config_list, git_config_list_entry *entry) +{ + config_entry_list *list_head; + config_entry_map_head *map_head; + + if (git_config_list_headmap_get(&map_head, &config_list->map, entry->base.entry.name) == 0) { + map_head->multivar = true; + /* + * This is a micro-optimization for configuration files + * with a lot of same keys. As for multivars the entry's + * key will be the same for all list, we can just free + * all except the first entry's name and just re-use it. + */ + git__free((char *) entry->base.entry.name); + entry->base.entry.name = map_head->entry->base.entry.name; + } else { + map_head = git__calloc(1, sizeof(*map_head)); + if ((git_config_list_headmap_put(&config_list->map, entry->base.entry.name, map_head)) < 0) + return -1; + } + map_head->entry = entry; + + list_head = git__calloc(1, sizeof(config_entry_list)); + GIT_ERROR_CHECK_ALLOC(list_head); + list_head->entry = entry; + + if (config_list->entries) + config_list->entries->last->next = list_head; + else + config_list->entries = list_head; + config_list->entries->last = list_head; + + return 0; +} + +int git_config_list_get(git_config_list_entry **out, git_config_list *config_list, const char *key) +{ + config_entry_map_head *entry; + + if (git_config_list_headmap_get(&entry, &config_list->map, key) != 0) + return GIT_ENOTFOUND; + + *out = entry->entry; + return 0; +} + +int git_config_list_get_unique(git_config_list_entry **out, git_config_list *config_list, const char *key) +{ + config_entry_map_head *entry; + + if (git_config_list_headmap_get(&entry, &config_list->map, key) != 0) + return GIT_ENOTFOUND; + + if (entry->multivar) { + git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); + return -1; + } + + if (entry->entry->base.entry.include_depth) { + git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included"); + return -1; + } + + *out = entry->entry; + return 0; +} + +static void config_iterator_free(git_config_iterator *iter) +{ + config_list_iterator *it = (config_list_iterator *) iter; + git_config_list_free(it->list); + git__free(it); +} + +static int config_iterator_next( + git_config_backend_entry **entry, + git_config_iterator *iter) +{ + config_list_iterator *it = (config_list_iterator *) iter; + + if (!it->head) + return GIT_ITEROVER; + + *entry = &it->head->entry->base; + it->head = it->head->next; + + return 0; +} + +int git_config_list_iterator_new(git_config_iterator **out, git_config_list *config_list) +{ + config_list_iterator *it; + + it = git__calloc(1, sizeof(config_list_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + it->parent.next = config_iterator_next; + it->parent.free = config_iterator_free; + it->head = config_list->entries; + it->list = config_list; + + git_config_list_incref(config_list); + *out = &it->parent; + + return 0; +} + +/* release the map containing the entry as an equivalent to freeing it */ +void git_config_list_entry_free(git_config_backend_entry *e) +{ + git_config_list_entry *entry = (git_config_list_entry *)e; + git_config_list_free(entry->config_list); +} + +const char *git_config_list_add_string( + git_config_list *config_list, + const char *str) +{ + char *s; + + if (git_config_list_pathmap_get(&s, &config_list->strings, str) == 0) + return s; + + if ((s = git__strdup(str)) == NULL || + git_config_list_pathmap_put(&config_list->strings, s, s) < 0) + return NULL; + + return s; +} diff --git a/src/libgit2/config_list.h b/src/libgit2/config_list.h new file mode 100644 index 00000000..091a59b9 --- /dev/null +++ b/src/libgit2/config_list.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/sys/config.h" +#include "config.h" + +typedef struct git_config_list git_config_list; + +typedef struct { + git_config_backend_entry base; + git_config_list *config_list; +} git_config_list_entry; + +int git_config_list_new(git_config_list **out); +int git_config_list_dup(git_config_list **out, git_config_list *list); +int git_config_list_dup_entry(git_config_list *list, const git_config_entry *entry); +void git_config_list_incref(git_config_list *list); +void git_config_list_free(git_config_list *list); +/* Add or append the new config option */ +int git_config_list_append(git_config_list *list, git_config_list_entry *entry); +int git_config_list_get(git_config_list_entry **out, git_config_list *list, const char *key); +int git_config_list_get_unique(git_config_list_entry **out, git_config_list *list, const char *key); +int git_config_list_iterator_new(git_config_iterator **out, git_config_list *list); +const char *git_config_list_add_string(git_config_list *list, const char *str); + +void git_config_list_entry_free(git_config_backend_entry *entry); diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c index 560229cf..3c159073 100644 --- a/src/libgit2/config_mem.c +++ b/src/libgit2/config_mem.c @@ -9,16 +9,29 @@ #include "config_backend.h" #include "config_parse.h" -#include "config_entries.h" +#include "config_list.h" +#include "strlist.h" typedef struct { git_config_backend parent; - git_config_entries *entries; + + char *backend_type; + char *origin_path; + + git_config_list *config_list; + + /* Configuration data in the config file format */ git_str cfg; + + /* Array of key=value pairs */ + char **values; + size_t values_len; } config_memory_backend; typedef struct { - git_config_entries *entries; + const char *backend_type; + const char *origin_path; + git_config_list *config_list; git_config_level_t level; } config_memory_parse_data; @@ -39,7 +52,7 @@ static int read_variable_cb( { config_memory_parse_data *parse_data = (config_memory_parse_data *) payload; git_str buf = GIT_STR_INIT; - git_config_entry *entry; + git_config_list_entry *entry; const char *c; int result; @@ -62,35 +75,46 @@ static int read_variable_cb( if (git_str_oom(&buf)) return -1; - entry = git__calloc(1, sizeof(git_config_entry)); + entry = git__calloc(1, sizeof(git_config_list_entry)); GIT_ERROR_CHECK_ALLOC(entry); - entry->name = git_str_detach(&buf); - entry->value = var_value ? git__strdup(var_value) : NULL; - entry->level = parse_data->level; - entry->include_depth = 0; - - if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) + entry->base.entry.name = git_str_detach(&buf); + entry->base.entry.value = var_value ? git__strdup(var_value) : NULL; + entry->base.entry.level = parse_data->level; + entry->base.entry.include_depth = 0; + entry->base.entry.backend_type = parse_data->backend_type; + entry->base.entry.origin_path = parse_data->origin_path; + entry->base.free = git_config_list_entry_free; + entry->config_list = parse_data->config_list; + + if ((result = git_config_list_append(parse_data->config_list, entry)) < 0) return result; return result; } -static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) +static int parse_config( + config_memory_backend *memory_backend, + git_config_level_t level) { - config_memory_backend *memory_backend = (config_memory_backend *) backend; git_config_parser parser = GIT_PARSE_CTX_INIT; config_memory_parse_data parse_data; int error; - GIT_UNUSED(repo); - - if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr, - memory_backend->cfg.size)) < 0) + if ((error = git_config_parser_init(&parser, "in-memory", + memory_backend->cfg.ptr, memory_backend->cfg.size)) < 0) goto out; - parse_data.entries = memory_backend->entries; + + parse_data.backend_type = git_config_list_add_string( + memory_backend->config_list, memory_backend->backend_type); + parse_data.origin_path = memory_backend->origin_path ? + git_config_list_add_string(memory_backend->config_list, + memory_backend->origin_path) : + NULL; + parse_data.config_list = memory_backend->config_list; parse_data.level = level; - if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0) + if ((error = git_config_parse(&parser, NULL, read_variable_cb, + NULL, NULL, &parse_data)) < 0) goto out; out: @@ -98,10 +122,85 @@ static int config_memory_open(git_config_backend *backend, git_config_level_t le return error; } -static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out) +static int parse_values( + config_memory_backend *memory_backend, + git_config_level_t level) +{ + git_config_list_entry *entry; + const char *eql, *backend_type, *origin_path; + size_t name_len, i; + + backend_type = git_config_list_add_string( + memory_backend->config_list, memory_backend->backend_type); + GIT_ERROR_CHECK_ALLOC(backend_type); + + origin_path = memory_backend->origin_path ? + git_config_list_add_string(memory_backend->config_list, + memory_backend->origin_path) : + NULL; + + for (i = 0; i < memory_backend->values_len; i++) { + eql = strchr(memory_backend->values[i], '='); + name_len = eql - memory_backend->values[i]; + + if (name_len == 0) { + git_error_set(GIT_ERROR_CONFIG, "empty config key"); + return -1; + } + + entry = git__calloc(1, sizeof(git_config_list_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->base.entry.name = git__strndup(memory_backend->values[i], name_len); + GIT_ERROR_CHECK_ALLOC(entry->base.entry.name); + + if (eql) { + entry->base.entry.value = git__strdup(eql + 1); + GIT_ERROR_CHECK_ALLOC(entry->base.entry.value); + } + + entry->base.entry.level = level; + entry->base.entry.include_depth = 0; + entry->base.entry.backend_type = backend_type; + entry->base.entry.origin_path = origin_path; + entry->base.free = git_config_list_entry_free; + entry->config_list = memory_backend->config_list; + + if (git_config_list_append(memory_backend->config_list, entry) < 0) + return -1; + } + + return 0; +} + +static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + + GIT_UNUSED(repo); + + if (memory_backend->cfg.size > 0 && + parse_config(memory_backend, level) < 0) + return -1; + + if (memory_backend->values_len > 0 && + parse_values(memory_backend, level) < 0) + return -1; + + return 0; +} + +static int config_memory_get(git_config_backend *backend, const char *key, git_config_backend_entry **out) { config_memory_backend *memory_backend = (config_memory_backend *) backend; - return git_config_entries_get(out, memory_backend->entries, key); + git_config_list_entry *entry; + int error; + + if ((error = git_config_list_get(&entry, memory_backend->config_list, key)) != 0) + return error; + + *out = &entry->base; + return 0; } static int config_memory_iterator( @@ -109,18 +208,18 @@ static int config_memory_iterator( git_config_backend *backend) { config_memory_backend *memory_backend = (config_memory_backend *) backend; - git_config_entries *entries; + git_config_list *config_list; int error; - if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0) + if ((error = git_config_list_dup(&config_list, memory_backend->config_list)) < 0) goto out; - if ((error = git_config_entries_iterator_new(iter, entries)) < 0) + if ((error = git_config_list_iterator_new(iter, config_list)) < 0) goto out; out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); + /* Let iterator delete duplicated config_list when it's done */ + git_config_list_free(config_list); return error; } @@ -177,28 +276,24 @@ static void config_memory_free(git_config_backend *_backend) if (backend == NULL) return; - git_config_entries_free(backend->entries); + git__free(backend->origin_path); + git__free(backend->backend_type); + git_config_list_free(backend->config_list); + git_strlist_free(backend->values, backend->values_len); git_str_dispose(&backend->cfg); git__free(backend); } -int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len) +static config_memory_backend *config_backend_new( + git_config_backend_memory_options *opts) { config_memory_backend *backend; - backend = git__calloc(1, sizeof(config_memory_backend)); - GIT_ERROR_CHECK_ALLOC(backend); + if ((backend = git__calloc(1, sizeof(config_memory_backend))) == NULL) + return NULL; - if (git_config_entries_new(&backend->entries) < 0) { - git__free(backend); - return -1; - } - - if (git_str_set(&backend->cfg, cfg, len) < 0) { - git_config_entries_free(backend->entries); - git__free(backend); - return -1; - } + if (git_config_list_new(&backend->config_list) < 0) + goto on_error; backend->parent.version = GIT_CONFIG_BACKEND_VERSION; backend->parent.readonly = 1; @@ -214,7 +309,66 @@ int git_config_backend_from_string(git_config_backend **out, const char *cfg, si backend->parent.snapshot = git_config_backend_snapshot; backend->parent.free = config_memory_free; + backend->backend_type = git__strdup(opts && opts->backend_type ? + opts->backend_type : "in-memory"); + + if (backend->backend_type == NULL) + goto on_error; + + if (opts && opts->origin_path && + (backend->origin_path = git__strdup(opts->origin_path)) == NULL) + goto on_error; + + return backend; + +on_error: + git_config_list_free(backend->config_list); + git__free(backend->origin_path); + git__free(backend->backend_type); + git__free(backend); + return NULL; +} + +int git_config_backend_from_string( + git_config_backend **out, + const char *cfg, + size_t len, + git_config_backend_memory_options *opts) +{ + config_memory_backend *backend; + + if ((backend = config_backend_new(opts)) == NULL) + return -1; + + if (git_str_set(&backend->cfg, cfg, len) < 0) { + git_config_list_free(backend->config_list); + git__free(backend); + return -1; + } + *out = (git_config_backend *)backend; + return 0; +} + +int git_config_backend_from_values( + git_config_backend **out, + const char **values, + size_t len, + git_config_backend_memory_options *opts) +{ + config_memory_backend *backend; + if ((backend = config_backend_new(opts)) == NULL) + return -1; + + if (git_strlist_copy(&backend->values, values, len) < 0) { + git_config_list_free(backend->config_list); + git__free(backend); + return -1; + } + + backend->values_len = len; + + *out = (git_config_backend *)backend; return 0; } diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c index 06931368..7f933db4 100644 --- a/src/libgit2/config_parse.c +++ b/src/libgit2/config_parse.c @@ -25,9 +25,9 @@ static void set_parse_error(git_config_parser *reader, int col, const char *erro } -GIT_INLINE(int) config_keychar(int c) +GIT_INLINE(int) config_keychar(char c) { - return isalnum(c) || c == '-'; + return git__isalnum(c) || c == '-'; } static int strip_comments(char *line, int in_quotes) @@ -158,9 +158,10 @@ static int parse_subsection_header(git_config_parser *reader, const char *line, static int parse_section_header(git_config_parser *reader, char **section_out) { char *name, *name_end; - int name_length, c, pos; + int name_length, pos; int result; char *line; + char c; size_t line_len; git_parse_advance_ws(&reader->ctx); @@ -279,8 +280,7 @@ static int skip_bom(git_parse_ctx *parser) */ /* '\"' -> '"' etc */ -static int unescape_line( - char **out, bool *is_multi, const char *ptr, int quote_count) +static int unescape_line(char **out, bool *is_multi, const char *ptr, int *quote_count) { char *str, *fixed, *esc; size_t ptr_len = strlen(ptr), alloc_len; @@ -296,7 +296,8 @@ static int unescape_line( while (*ptr != '\0') { if (*ptr == '"') { - quote_count++; + if (quote_count) + (*quote_count)++; } else if (*ptr != '\\') { *fixed++ = *ptr; } else { @@ -358,7 +359,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_str *value, i goto next; if ((error = unescape_line(&proc_line, &multiline, - line, in_quotes)) < 0) + line, &in_quotes)) < 0) goto out; /* Add this line to the multiline var */ @@ -382,7 +383,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_str *value, i GIT_INLINE(bool) is_namechar(char c) { - return isalnum(c) || c == '-'; + return git__isalnum(c) || c == '-'; } static int parse_name( @@ -445,7 +446,7 @@ static int parse_variable(git_config_parser *reader, char **var_name, char **var while (git__isspace(value_start[0])) value_start++; - if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0) + if ((error = unescape_line(&value, &multiline, value_start, NULL)) < 0) goto out; if (multiline) { diff --git a/src/libgit2/config_snapshot.c b/src/libgit2/config_snapshot.c index e295d2f7..d20984f5 100644 --- a/src/libgit2/config_snapshot.c +++ b/src/libgit2/config_snapshot.c @@ -8,12 +8,12 @@ #include "config_backend.h" #include "config.h" -#include "config_entries.h" +#include "config_list.h" typedef struct { git_config_backend parent; git_mutex values_mutex; - git_config_entries *entries; + git_config_list *config_list; git_config_backend *source; } config_snapshot_backend; @@ -28,31 +28,27 @@ static int config_snapshot_iterator( struct git_config_backend *backend) { config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent); - git_config_entries *entries = NULL; + git_config_list *config_list = NULL; int error; - if ((error = git_config_entries_dup(&entries, b->entries)) < 0 || - (error = git_config_entries_iterator_new(iter, entries)) < 0) + if ((error = git_config_list_dup(&config_list, b->config_list)) < 0 || + (error = git_config_list_iterator_new(iter, config_list)) < 0) goto out; out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); + /* Let iterator delete duplicated config_list when it's done */ + git_config_list_free(config_list); return error; } -/* release the map containing the entry as an equivalent to freeing it */ -static void config_snapshot_entry_free(git_config_entry *entry) -{ - git_config_entries *entries = (git_config_entries *) entry->payload; - git_config_entries_free(entries); -} - -static int config_snapshot_get(git_config_backend *cfg, const char *key, git_config_entry **out) +static int config_snapshot_get( + git_config_backend *cfg, + const char *key, + git_config_backend_entry **out) { config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; + git_config_list *config_list = NULL; + git_config_list_entry *entry; int error = 0; if (git_mutex_lock(&b->values_mutex) < 0) { @@ -60,19 +56,16 @@ static int config_snapshot_get(git_config_backend *cfg, const char *key, git_con return -1; } - entries = b->entries; - git_config_entries_incref(entries); + config_list = b->config_list; + git_config_list_incref(config_list); git_mutex_unlock(&b->values_mutex); - if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { - git_config_entries_free(entries); + if ((error = (git_config_list_get(&entry, config_list, key))) < 0) { + git_config_list_free(config_list); return error; } - entry->free = config_snapshot_entry_free; - entry->payload = entries; - *out = entry; - + *out = &entry->base; return 0; } @@ -135,7 +128,7 @@ static void config_snapshot_free(git_config_backend *_backend) if (backend == NULL) return; - git_config_entries_free(backend->entries); + git_config_list_free(backend->config_list); git_mutex_free(&backend->values_mutex); git__free(backend); } @@ -143,7 +136,7 @@ static void config_snapshot_free(git_config_backend *_backend) static int config_snapshot_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) { config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); - git_config_entries *entries = NULL; + git_config_list *config_list = NULL; git_config_iterator *it = NULL; git_config_entry *entry; int error; @@ -152,12 +145,12 @@ static int config_snapshot_open(git_config_backend *cfg, git_config_level_t leve GIT_UNUSED(level); GIT_UNUSED(repo); - if ((error = git_config_entries_new(&entries)) < 0 || + if ((error = git_config_list_new(&config_list)) < 0 || (error = b->source->iterator(&it, b->source)) < 0) goto out; while ((error = git_config_next(&entry, it)) == 0) - if ((error = git_config_entries_dup_entry(entries, entry)) < 0) + if ((error = git_config_list_dup_entry(config_list, entry)) < 0) goto out; if (error < 0) { @@ -166,12 +159,12 @@ static int config_snapshot_open(git_config_backend *cfg, git_config_level_t leve error = 0; } - b->entries = entries; + b->config_list = config_list; out: git_config_iterator_free(it); if (error) - git_config_entries_free(entries); + git_config_list_free(config_list); return error; } diff --git a/src/libgit2/describe.c b/src/libgit2/describe.c index 04453472..dfbe7b4a 100644 --- a/src/libgit2/describe.c +++ b/src/libgit2/describe.c @@ -14,7 +14,6 @@ #include "buf.h" #include "commit.h" #include "commit_list.h" -#include "oidmap.h" #include "refs.h" #include "repository.h" #include "revwalk.h" @@ -22,6 +21,7 @@ #include "tag.h" #include "vector.h" #include "wildmatch.h" +#include "hashmap_oid.h" /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ @@ -32,20 +32,22 @@ struct commit_name { git_oid sha1; char *path; - /* Khash workaround. They original key has to still be reachable */ + /* The original key for the hashmap */ git_oid peeled; }; -static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) -{ - return git_oidmap_get(map, key); -} +GIT_HASHMAP_OID_SETUP(git_describe_oidmap, struct commit_name *); static struct commit_name *find_commit_name( - git_oidmap *names, + git_describe_oidmap *names, const git_oid *peeled) { - return (struct commit_name *)(oidmap_value_bykey(names, peeled)); + struct commit_name *result; + + if (git_describe_oidmap_get(&result, names, peeled) == 0) + return result; + + return NULL; } static int replace_name( @@ -92,7 +94,7 @@ static int replace_name( static int add_to_known_names( git_repository *repo, - git_oidmap *names, + git_describe_oidmap *names, const char *path, const git_oid *peeled, unsigned int prio, @@ -121,7 +123,7 @@ static int add_to_known_names( e->path = git__strdup(path); git_oid_cpy(&e->peeled, peeled); - if (!found && git_oidmap_set(names, &e->peeled, e) < 0) + if (!found && git_describe_oidmap_put(names, &e->peeled, e) < 0) return -1; } else @@ -174,7 +176,7 @@ struct get_name_data { git_describe_options *opts; git_repository *repo; - git_oidmap *names; + git_describe_oidmap names; git_describe_result *result; }; @@ -240,7 +242,7 @@ static int get_name(const char *refname, void *payload) else prio = 0; - add_to_known_names(data->repo, data->names, + add_to_known_names(data->repo, &data->names, all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), &peeled, prio, &sha1); return 0; @@ -451,7 +453,7 @@ static int describe( git_oid_cpy(&data->result->commit_id, git_commit_id(commit)); - n = find_commit_name(data->names, git_commit_id(commit)); + n = find_commit_name(&data->names, git_commit_id(commit)); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. @@ -492,7 +494,7 @@ static int describe( git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); seen_commits++; - n = find_commit_name(data->names, &c->oid); + n = find_commit_name(&data->names, &c->oid); if (n) { if (!tags && !all && n->prio < 2) { @@ -627,7 +629,7 @@ static int describe( git__free(match); } } - git_vector_free(&all_matches); + git_vector_dispose(&all_matches); git_pqueue_free(&list); git_revwalk_free(walk); return error; @@ -653,11 +655,12 @@ int git_describe_commit( git_object *committish, git_describe_options *opts) { - struct get_name_data data; + struct get_name_data data = {0}; struct commit_name *name; git_commit *commit; - int error = -1; git_describe_options normalized; + git_hashmap_iter_t iter = GIT_HASHMAP_INIT; + int error = -1; GIT_ASSERT_ARG(result); GIT_ASSERT_ARG(committish); @@ -677,9 +680,6 @@ int git_describe_commit( "git_describe_options"); data.opts = &normalized; - if ((error = git_oidmap_new(&data.names)) < 0) - return error; - /** TODO: contains to be implemented */ if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0) @@ -690,7 +690,7 @@ int git_describe_commit( get_name, &data)) < 0) goto cleanup; - if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) { + if (git_describe_oidmap_size(&data.names) == 0 && !normalized.show_commit_oid_as_fallback) { git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " "no reference found, cannot describe anything."); error = -1; @@ -703,13 +703,13 @@ int git_describe_commit( cleanup: git_commit_free(commit); - git_oidmap_foreach_value(data.names, name, { + while (git_describe_oidmap_iterate(&iter, NULL, &name, &data.names) == 0) { git_tag_free(name->tag); git__free(name->path); git__free(name); - }); + } - git_oidmap_free(data.names); + git_describe_oidmap_dispose(&data.names); if (error < 0) git_describe_result_free(data.result); diff --git a/src/libgit2/diff.c b/src/libgit2/diff.c index db12ccd6..80027ba3 100644 --- a/src/libgit2/diff.c +++ b/src/libgit2/diff.c @@ -16,7 +16,7 @@ #include "diff_generate.h" #include "git2/version.h" -#include "git2/email.h" +#include "git2/sys/email.h" struct patch_id_args { git_diff *diff; diff --git a/src/libgit2/diff_driver.c b/src/libgit2/diff_driver.c index 5f25fdb4..7055575e 100644 --- a/src/libgit2/diff_driver.c +++ b/src/libgit2/diff_driver.c @@ -11,11 +11,11 @@ #include "common.h" #include "diff.h" -#include "strmap.h" #include "map.h" #include "config.h" #include "regexp.h" #include "repository.h" +#include "userdiff.h" typedef enum { DIFF_DRIVER_AUTO = 0, @@ -43,10 +43,10 @@ struct git_diff_driver { char name[GIT_FLEX_ARRAY]; }; -#include "userdiff.h" +GIT_HASHMAP_STR_SETUP(git_diff_driver_map, git_diff_driver *); struct git_diff_driver_registry { - git_strmap *drivers; + git_diff_driver_map map; }; #define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY) @@ -57,28 +57,21 @@ static git_diff_driver diff_driver_text = { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE git_diff_driver_registry *git_diff_driver_registry_new(void) { - git_diff_driver_registry *reg = - git__calloc(1, sizeof(git_diff_driver_registry)); - if (!reg) - return NULL; - - if (git_strmap_new(®->drivers) < 0) { - git_diff_driver_registry_free(reg); - return NULL; - } - - return reg; + return git__calloc(1, sizeof(git_diff_driver_registry)); } void git_diff_driver_registry_free(git_diff_driver_registry *reg) { git_diff_driver *drv; + git_hashmap_iter_t iter = 0; if (!reg) return; - git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv)); - git_strmap_free(reg->drivers); + while (git_diff_driver_map_iterate(&iter, NULL, &drv, ®->map) == 0) + git_diff_driver_free(drv); + + git_diff_driver_map_dispose(®->map); git__free(reg); } @@ -215,7 +208,7 @@ static int git_diff_driver_builtin( (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0) goto done; - if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + if ((error = git_diff_driver_map_put(®->map, drv->name, drv)) < 0) goto done; done: @@ -242,7 +235,7 @@ static int git_diff_driver_load( if ((reg = git_repository_driver_registry(repo)) == NULL) return -1; - if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) { + if (git_diff_driver_map_get(&drv, ®->map, driver_name) == 0) { *out = drv; return 0; } @@ -331,7 +324,7 @@ static int git_diff_driver_load( goto done; /* store driver in registry */ - if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + if ((error = git_diff_driver_map_put(®->map, drv->name, drv)) < 0) goto done; *out = drv; diff --git a/src/libgit2/diff_driver.h b/src/libgit2/diff_driver.h index 03711e89..ca0a7ae1 100644 --- a/src/libgit2/diff_driver.h +++ b/src/libgit2/diff_driver.h @@ -11,14 +11,14 @@ #include "attr_file.h" #include "str.h" +#include "hashmap.h" +typedef struct git_diff_driver git_diff_driver; typedef struct git_diff_driver_registry git_diff_driver_registry; git_diff_driver_registry *git_diff_driver_registry_new(void); void git_diff_driver_registry_free(git_diff_driver_registry *); -typedef struct git_diff_driver git_diff_driver; - int git_diff_driver_lookup(git_diff_driver **, git_repository *, git_attr_session *attrsession, const char *); void git_diff_driver_free(git_diff_driver *); diff --git a/src/libgit2/diff_generate.c b/src/libgit2/diff_generate.c index 78fe510e..654145e3 100644 --- a/src/libgit2/diff_generate.c +++ b/src/libgit2/diff_generate.c @@ -429,7 +429,7 @@ static void diff_generated_free(git_diff *d) git_diff_generated *diff = (git_diff_generated *)d; git_attr_session__free(&diff->base.attrsession); - git_vector_free_deep(&diff->base.deltas); + git_vector_dispose_deep(&diff->base.deltas); git_pathspec__vfree(&diff->pathspec); git_pool_clear(&diff->base.pool); @@ -729,7 +729,7 @@ typedef struct { git_iterator *new_iter; const git_index_entry *oitem; const git_index_entry *nitem; - git_strmap *submodule_cache; + git_submodule_cache *submodule_cache; bool submodule_cache_initialized; } diff_in_progress; @@ -745,7 +745,7 @@ static int maybe_modified_submodule( git_submodule *sub; unsigned int sm_status = 0; git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; - git_strmap *submodule_cache = NULL; + git_submodule_cache *submodule_cache = NULL; *status = GIT_DELTA_UNMODIFIED; diff --git a/src/libgit2/diff_parse.c b/src/libgit2/diff_parse.c index 04603969..02eb21ef 100644 --- a/src/libgit2/diff_parse.c +++ b/src/libgit2/diff_parse.c @@ -20,9 +20,9 @@ static void diff_parsed_free(git_diff *d) git_vector_foreach(&diff->patches, i, patch) git_patch_free(patch); - git_vector_free(&diff->patches); + git_vector_dispose(&diff->patches); - git_vector_free(&diff->base.deltas); + git_vector_dispose(&diff->base.deltas); git_pool_clear(&diff->base.pool); git__memzero(diff, sizeof(*diff)); diff --git a/src/libgit2/diff_print.c b/src/libgit2/diff_print.c index 32c93682..0ffba0d5 100644 --- a/src/libgit2/diff_print.c +++ b/src/libgit2/diff_print.c @@ -15,6 +15,7 @@ #include "zstream.h" #include "blob.h" #include "delta.h" +#include "repository.h" #include "git2/sys/diff.h" typedef struct { @@ -29,6 +30,7 @@ typedef struct { const char *new_prefix; uint32_t flags; int id_strlen; + unsigned int sent_file_header; git_oid_t oid_type; int (*strcomp)(const char *, const char *); @@ -52,14 +54,10 @@ static int diff_print_info_init__common( if (!pi->id_strlen) { if (!repo) pi->id_strlen = GIT_ABBREV_DEFAULT; - else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) + else if (git_repository__abbrev_length(&pi->id_strlen, repo) < 0) return -1; } - if (pi->id_strlen > 0 && - (size_t)pi->id_strlen > git_oid_hexsize(pi->oid_type)) - pi->id_strlen = (int)git_oid_hexsize(pi->oid_type); - memset(&pi->line, 0, sizeof(pi->line)); pi->line.old_lineno = -1; pi->line.new_lineno = -1; @@ -579,6 +577,30 @@ static int diff_print_patch_file_binary( return error; } +GIT_INLINE(int) should_force_header(const git_diff_delta *delta) +{ + if (delta->old_file.mode != delta->new_file.mode) + return 1; + + if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) + return 1; + + return 0; +} + +GIT_INLINE(int) flush_file_header(const git_diff_delta *delta, diff_print_info *pi) +{ + if (pi->sent_file_header) + return 0; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(pi->buf); + pi->line.content_len = git_str_len(pi->buf); + pi->sent_file_header = 1; + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { @@ -609,15 +631,22 @@ static int diff_print_patch_file( (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) return 0; + pi->sent_file_header = 0; + if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx, id_strlen, print_index)) < 0) return error; - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(pi->buf); - pi->line.content_len = git_str_len(pi->buf); + /* + * pi->buf now contains the file header data. Go ahead and send it + * if there's useful data in there, like similarity. Otherwise, we + * should queue it to send when we see the first hunk. This prevents + * us from sending a header when all hunks were ignored. + */ + if (should_force_header(delta) && (error = flush_file_header(delta, pi)) < 0) + return error; - return pi->print_cb(delta, NULL, &pi->line, pi->payload); + return 0; } static int diff_print_patch_binary( @@ -632,6 +661,16 @@ static int diff_print_patch_binary( pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; int error; + if ((error = flush_file_header(delta, pi)) < 0) + return error; + + /* + * If the caller only wants the header, we just needed to make sure to + * call flush_file_header + */ + if (pi->format == GIT_DIFF_FORMAT_PATCH_HEADER) + return 0; + git_str_clear(pi->buf); if ((error = diff_print_patch_file_binary( @@ -651,10 +690,21 @@ static int diff_print_patch_hunk( void *data) { diff_print_info *pi = data; + int error; if (S_ISDIR(d->new_file.mode)) return 0; + if ((error = flush_file_header(d, pi)) < 0) + return error; + + /* + * If the caller only wants the header, we just needed to make sure to + * call flush_file_header + */ + if (pi->format == GIT_DIFF_FORMAT_PATCH_HEADER) + return 0; + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; pi->line.content = h->header; pi->line.content_len = h->header_len; @@ -669,10 +719,14 @@ static int diff_print_patch_line( void *data) { diff_print_info *pi = data; + int error; if (S_ISDIR(delta->new_file.mode)) return 0; + if ((error = flush_file_header(delta, pi)) < 0) + return error; + return pi->print_cb(delta, hunk, line, pi->payload); } @@ -705,6 +759,8 @@ int git_diff_print( break; case GIT_DIFF_FORMAT_PATCH_HEADER: print_file = diff_print_patch_file; + print_binary = diff_print_patch_binary; + print_hunk = diff_print_patch_hunk; break; case GIT_DIFF_FORMAT_RAW: print_file = diff_print_one_raw; diff --git a/src/libgit2/diff_stats.c b/src/libgit2/diff_stats.c index 25993984..b67aa48d 100644 --- a/src/libgit2/diff_stats.c +++ b/src/libgit2/diff_stats.c @@ -28,7 +28,6 @@ struct git_diff_stats { size_t files_changed; size_t insertions; size_t deletions; - size_t renames; size_t max_name; size_t max_filestat; @@ -68,17 +67,19 @@ static int diff_file_stats_full_to_buf( size_t common_dirlen; int error; - padding = stats->max_name - strlen(old_path) - strlen(new_path); - if ((common_dirlen = git_fs_path_common_dirlen(old_path, new_path)) && common_dirlen <= INT_MAX) { error = git_str_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}", (int) common_dirlen, old_path, old_path + common_dirlen, new_path + common_dirlen); + padding = stats->max_name + common_dirlen - strlen(old_path) + - strlen(new_path) - 2 - strlen(DIFF_RENAME_FILE_SEPARATOR); } else { error = git_str_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s", old_path, new_path); + padding = stats->max_name - strlen(old_path) + - strlen(new_path) - strlen(DIFF_RENAME_FILE_SEPARATOR); } if (error < 0) @@ -89,9 +90,6 @@ static int diff_file_stats_full_to_buf( goto on_error; padding = stats->max_name - strlen(adddel_path); - - if (stats->renames > 0) - padding += strlen(DIFF_RENAME_FILE_SEPARATOR); } if (git_str_putcn(out, ' ', padding) < 0 || @@ -210,14 +208,23 @@ int git_diff_get_stats( if ((error = git_patch_from_diff(&patch, diff, i)) < 0) break; - /* keep a count of renames because it will affect formatting */ + /* Length calculation for renames mirrors the actual presentation format + * generated in diff_file_stats_full_to_buf; namelen is the full length of + * what will be printed, taking into account renames and common prefixes. + */ delta = patch->delta; - - /* TODO ugh */ namelen = strlen(delta->new_file.path); - if (delta->old_file.path && strcmp(delta->old_file.path, delta->new_file.path) != 0) { - namelen += strlen(delta->old_file.path); - stats->renames++; + if (delta->old_file.path && + strcmp(delta->old_file.path, delta->new_file.path) != 0) { + size_t common_dirlen; + if ((common_dirlen = git_fs_path_common_dirlen(delta->old_file.path, delta->new_file.path)) && + common_dirlen <= INT_MAX) { + namelen += strlen(delta->old_file.path) + 2 + + strlen(DIFF_RENAME_FILE_SEPARATOR) - common_dirlen; + } else { + namelen += strlen(delta->old_file.path) + + strlen(DIFF_RENAME_FILE_SEPARATOR); + } } /* and, of course, count the line stats */ diff --git a/src/libgit2/diff_tform.c b/src/libgit2/diff_tform.c index 4a156c7a..33ed2d11 100644 --- a/src/libgit2/diff_tform.c +++ b/src/libgit2/diff_tform.c @@ -190,7 +190,7 @@ int git_diff__merge( git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); } - git_vector_free_deep(&onto_new); + git_vector_dispose_deep(&onto_new); git_pool_clear(&onto_pool); return error; @@ -424,13 +424,13 @@ static int apply_splits_and_deletes( /* swap new delta list into place */ git_vector_swap(&diff->deltas, &onto); - git_vector_free(&onto); + git_vector_dispose(&onto); git_vector_sort(&diff->deltas); return 0; on_error: - git_vector_free_deep(&onto); + git_vector_dispose_deep(&onto); return -1; } @@ -653,6 +653,23 @@ static int calc_self_similarity( return 0; } +static void handle_non_blob( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx) +{ + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + /* skip things that are blobs */ + if (GIT_MODE_ISBLOB(delta->old_file.mode)) + return; + + /* honor "remove unmodified" flag for non-blobs (eg submodules) */ + if (delta->status == GIT_DELTA_UNMODIFIED && + FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED)) + delta->flags |= GIT_DIFF_FLAG__TO_DELETE; +} + static bool is_rename_target( git_diff *diff, const git_diff_find_options *opts, @@ -810,7 +827,8 @@ int git_diff_find_similar( git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; size_t num_deltas, num_srcs = 0, num_tgts = 0; size_t tried_srcs = 0, tried_tgts = 0; - size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; + size_t num_rewrites = 0, num_updates = 0, num_bumped = 0, + num_to_delete = 0; size_t sigcache_size; void **sigcache = NULL; /* cache of similarity metric file signatures */ diff_find_match *tgt2src = NULL; @@ -844,6 +862,8 @@ int git_diff_find_similar( * mark them for splitting if break-rewrites is enabled */ git_vector_foreach(&diff->deltas, t, tgt) { + handle_non_blob(diff, &opts, t); + if (is_rename_source(diff, &opts, t, sigcache)) ++num_srcs; @@ -852,11 +872,14 @@ int git_diff_find_similar( if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) num_rewrites++; + + if ((tgt->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) + num_to_delete++; } - /* if there are no candidate srcs or tgts, we're done */ + /* If there are no candidate srcs or tgts, no need to find matches */ if (!num_srcs || !num_tgts) - goto cleanup; + goto split_and_delete; src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); GIT_ERROR_CHECK_ALLOC(src2tgt); @@ -1093,15 +1116,20 @@ int git_diff_find_similar( } } +split_and_delete: /* * Actually split and delete entries as needed */ - if (num_rewrites > 0 || num_updates > 0) + if (num_rewrites > 0 || num_updates > 0 || num_to_delete > 0) { + size_t apply_len = diff->deltas.length - + num_rewrites - num_to_delete; + error = apply_splits_and_deletes( - diff, diff->deltas.length - num_rewrites, + diff, apply_len, FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); + } cleanup: git__free(tgt2src); diff --git a/src/libgit2/email.c b/src/libgit2/email.c index 8a10a12b..c1470c16 100644 --- a/src/libgit2/email.c +++ b/src/libgit2/email.c @@ -16,6 +16,7 @@ #include "git2/email.h" #include "git2/patch.h" +#include "git2/sys/email.h" #include "git2/version.h" /* diff --git a/src/libgit2/errors.c b/src/libgit2/errors.c deleted file mode 100644 index 2e58948d..00000000 --- a/src/libgit2/errors.c +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "threadstate.h" -#include "posix.h" -#include "str.h" -#include "libgit2.h" - -/******************************************** - * New error handling - ********************************************/ - -static git_error oom_error = { - "Out of memory", - GIT_ERROR_NOMEMORY -}; - -static git_error uninitialized_error = { - "libgit2 has not been initialized; you must call git_libgit2_init", - GIT_ERROR_INVALID -}; - -static git_error tlsdata_error = { - "thread-local data initialization failure", - GIT_ERROR -}; - -static void set_error_from_buffer(int error_class) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_error *error; - git_str *buf; - - if (!threadstate) - return; - - error = &threadstate->error_t; - buf = &threadstate->error_buf; - - error->message = buf->ptr; - error->klass = error_class; - - threadstate->last_error = error; -} - -static void set_error(int error_class, char *string) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_str *buf; - - if (!threadstate) - return; - - buf = &threadstate->error_buf; - - git_str_clear(buf); - - if (string) { - git_str_puts(buf, string); - git__free(string); - } - - set_error_from_buffer(error_class); -} - -void git_error_set_oom(void) -{ - git_threadstate *threadstate = git_threadstate_get(); - - if (!threadstate) - return; - - threadstate->last_error = &oom_error; -} - -void git_error_set(int error_class, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - git_error_vset(error_class, fmt, ap); - va_end(ap); -} - -void git_error_vset(int error_class, const char *fmt, va_list ap) -{ -#ifdef GIT_WIN32 - DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; -#endif - - git_threadstate *threadstate = git_threadstate_get(); - int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; - git_str *buf; - - if (!threadstate) - return; - - buf = &threadstate->error_buf; - - git_str_clear(buf); - - if (fmt) { - git_str_vprintf(buf, fmt, ap); - if (error_class == GIT_ERROR_OS) - git_str_PUTS(buf, ": "); - } - - if (error_class == GIT_ERROR_OS) { -#ifdef GIT_WIN32 - char *win32_error = git_win32_get_error_message(win32_error_code); - if (win32_error) { - git_str_puts(buf, win32_error); - git__free(win32_error); - - SetLastError(0); - } - else -#endif - if (error_code) - git_str_puts(buf, strerror(error_code)); - - if (error_code) - errno = 0; - } - - if (!git_str_oom(buf)) - set_error_from_buffer(error_class); -} - -int git_error_set_str(int error_class, const char *string) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_str *buf; - - GIT_ASSERT_ARG(string); - - if (!threadstate) - return -1; - - buf = &threadstate->error_buf; - - git_str_clear(buf); - git_str_puts(buf, string); - - if (git_str_oom(buf)) - return -1; - - set_error_from_buffer(error_class); - return 0; -} - -void git_error_clear(void) -{ - git_threadstate *threadstate = git_threadstate_get(); - - if (!threadstate) - return; - - if (threadstate->last_error != NULL) { - set_error(0, NULL); - threadstate->last_error = NULL; - } - - errno = 0; -#ifdef GIT_WIN32 - SetLastError(0); -#endif -} - -const git_error *git_error_last(void) -{ - git_threadstate *threadstate; - - /* If the library is not initialized, return a static error. */ - if (!git_libgit2_init_count()) - return &uninitialized_error; - - if ((threadstate = git_threadstate_get()) == NULL) - return &tlsdata_error; - - return threadstate->last_error; -} - -int git_error_state_capture(git_error_state *state, int error_code) -{ - git_threadstate *threadstate = git_threadstate_get(); - git_error *error; - git_str *error_buf; - - if (!threadstate) - return -1; - - error = threadstate->last_error; - error_buf = &threadstate->error_buf; - - memset(state, 0, sizeof(git_error_state)); - - if (!error_code) - return 0; - - state->error_code = error_code; - state->oom = (error == &oom_error); - - if (error) { - state->error_msg.klass = error->klass; - - if (state->oom) - state->error_msg.message = oom_error.message; - else - state->error_msg.message = git_str_detach(error_buf); - } - - git_error_clear(); - return error_code; -} - -int git_error_state_restore(git_error_state *state) -{ - int ret = 0; - - git_error_clear(); - - if (state && state->error_msg.message) { - if (state->oom) - git_error_set_oom(); - else - set_error(state->error_msg.klass, state->error_msg.message); - - ret = state->error_code; - memset(state, 0, sizeof(git_error_state)); - } - - return ret; -} - -void git_error_state_free(git_error_state *state) -{ - if (!state) - return; - - if (!state->oom) - git__free(state->error_msg.message); - - memset(state, 0, sizeof(git_error_state)); -} - -int git_error_system_last(void) -{ -#ifdef GIT_WIN32 - return GetLastError(); -#else - return errno; -#endif -} - -void git_error_system_set(int code) -{ -#ifdef GIT_WIN32 - SetLastError(code); -#else - errno = code; -#endif -} - -/* Deprecated error values and functions */ - -#ifndef GIT_DEPRECATE_HARD -const git_error *giterr_last(void) -{ - return git_error_last(); -} - -void giterr_clear(void) -{ - git_error_clear(); -} - -void giterr_set_str(int error_class, const char *string) -{ - git_error_set_str(error_class, string); -} - -void giterr_set_oom(void) -{ - git_error_set_oom(); -} -#endif diff --git a/src/libgit2/fetch.c b/src/libgit2/fetch.c index d74abb4a..8e2660f2 100644 --- a/src/libgit2/fetch.c +++ b/src/libgit2/fetch.c @@ -60,9 +60,11 @@ static int mark_local(git_remote *remote) git_vector_foreach(&remote->refs, i, head) { /* If we have the object, mark it so we don't ask for it. - However if we are unshallowing, we need to ask for it - even though the head exists locally. */ - if (remote->nego.depth != INT_MAX && git_odb_exists(odb, &head->oid)) + However if we are unshallowing or changing history + depth, we need to ask for it even though the head + exists locally. */ + if (remote->nego.depth == GIT_FETCH_DEPTH_FULL && + git_odb_exists(odb, &head->oid)) head->local = 1; else remote->need_pack = 1; diff --git a/src/libgit2/filter.c b/src/libgit2/filter.c index 80a3cae6..9e0910c8 100644 --- a/src/libgit2/filter.c +++ b/src/libgit2/filter.c @@ -239,7 +239,7 @@ static void git_filter_global_shutdown(void) git__free(fdef); } - git_vector_free(&filter_registry.filters); + git_vector_dispose(&filter_registry.filters); git_rwlock_wrunlock(&filter_registry.lock); git_rwlock_free(&filter_registry.lock); @@ -908,7 +908,7 @@ static int buffered_stream_close(git_writestream *s) { struct buffered_stream *buffered_stream = (struct buffered_stream *)s; git_str *writebuf; - git_error_state error_state = {0}; + git_error *last_error; int error; GIT_ASSERT_ARG(buffered_stream); @@ -946,9 +946,9 @@ static int buffered_stream_close(git_writestream *s) } else { /* close stream before erroring out taking care * to preserve the original error */ - git_error_state_capture(&error_state, error); + git_error_save(&last_error); buffered_stream->target->close(buffered_stream->target); - git_error_state_restore(&error_state); + git_error_restore(last_error); return error; } @@ -1106,7 +1106,7 @@ static void filter_streams_free(git_vector *streams) git_vector_foreach(streams, i, stream) stream->free(stream); - git_vector_free(streams); + git_vector_dispose(streams); } int git_filter_list_stream_file( diff --git a/src/libgit2/git2.rc b/src/libgit2/git2.rc index d273afd7..07592d15 100644 --- a/src/libgit2/git2.rc +++ b/src/libgit2/git2.rc @@ -10,7 +10,7 @@ #endif #ifndef LIBGIT2_COMMENTS -# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" +# define LIBGIT2_COMMENTS "For more information visit https://libgit2.org/" #endif #ifdef __GNUC__ @@ -25,8 +25,8 @@ VS_VERSION_INFO VERSIONINFO #else VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE #endif - FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + FILEVERSION LIBGIT2_VERSION_MAJOR,LIBGIT2_VERSION_MINOR,LIBGIT2_VERSION_REVISION,LIBGIT2_VERSION_PATCH + PRODUCTVERSION LIBGIT2_VERSION_MAJOR,LIBGIT2_VERSION_MINOR,LIBGIT2_VERSION_REVISION,LIBGIT2_VERSION_PATCH FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG diff --git a/src/libgit2/grafts.c b/src/libgit2/grafts.c index 1d9373a5..d31b4efd 100644 --- a/src/libgit2/grafts.c +++ b/src/libgit2/grafts.c @@ -11,10 +11,13 @@ #include "oid.h" #include "oidarray.h" #include "parse.h" +#include "hashmap_oid.h" + +GIT_HASHMAP_OID_SETUP(git_grafts_oidmap, git_commit_graft *); struct git_grafts { /* Map of `git_commit_graft`s */ - git_oidmap *commits; + git_grafts_oidmap commits; /* Type of object IDs */ git_oid_t oid_type; @@ -33,11 +36,6 @@ int git_grafts_new(git_grafts **out, git_oid_t oid_type) grafts = git__calloc(1, sizeof(*grafts)); GIT_ERROR_CHECK_ALLOC(grafts); - if ((git_oidmap_new(&grafts->commits)) < 0) { - git__free(grafts); - return -1; - } - grafts->oid_type = oid_type; *out = grafts; @@ -88,23 +86,24 @@ void git_grafts_free(git_grafts *grafts) return; git__free(grafts->path); git_grafts_clear(grafts); - git_oidmap_free(grafts->commits); + git_grafts_oidmap_dispose(&grafts->commits); git__free(grafts); } void git_grafts_clear(git_grafts *grafts) { + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; git_commit_graft *graft; if (!grafts) return; - git_oidmap_foreach_value(grafts->commits, graft, { + while (git_grafts_oidmap_iterate(&iter, NULL, &graft, &grafts->commits) == 0) { git__free(graft->parents.ptr); git__free(graft); - }); + } - git_oidmap_clear(grafts->commits); + git_grafts_oidmap_clear(&grafts->commits); } int git_grafts_refresh(git_grafts *grafts) @@ -205,7 +204,7 @@ int git_grafts_add(git_grafts *grafts, const git_oid *oid, git_array_oid_t paren if ((error = git_grafts_remove(grafts, &graft->oid)) < 0 && error != GIT_ENOTFOUND) goto cleanup; - if ((error = git_oidmap_set(grafts->commits, &graft->oid, graft)) < 0) + if ((error = git_grafts_oidmap_put(&grafts->commits, &graft->oid, graft)) < 0) goto cleanup; return 0; @@ -223,10 +222,10 @@ int git_grafts_remove(git_grafts *grafts, const git_oid *oid) GIT_ASSERT_ARG(grafts && oid); - if ((graft = git_oidmap_get(grafts->commits, oid)) == NULL) + if (git_grafts_oidmap_get(&graft, &grafts->commits, oid) != 0) return GIT_ENOTFOUND; - if ((error = git_oidmap_delete(grafts->commits, oid)) < 0) + if ((error = git_grafts_oidmap_remove(&grafts->commits, oid)) < 0) return error; git__free(graft->parents.ptr); @@ -238,23 +237,22 @@ int git_grafts_remove(git_grafts *grafts, const git_oid *oid) int git_grafts_get(git_commit_graft **out, git_grafts *grafts, const git_oid *oid) { GIT_ASSERT_ARG(out && grafts && oid); - if ((*out = git_oidmap_get(grafts->commits, oid)) == NULL) - return GIT_ENOTFOUND; - return 0; + return git_grafts_oidmap_get(out, &grafts->commits, oid); } int git_grafts_oids(git_oid **out, size_t *out_len, git_grafts *grafts) { + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; git_array_oid_t array = GIT_ARRAY_INIT; const git_oid *oid; - size_t existing, i = 0; + size_t existing; GIT_ASSERT_ARG(out && grafts); - if ((existing = git_oidmap_size(grafts->commits)) > 0) + if ((existing = git_grafts_oidmap_size(&grafts->commits)) > 0) git_array_init_to_size(array, existing); - while (git_oidmap_iterate(NULL, grafts->commits, &i, &oid) == 0) { + while (git_grafts_oidmap_iterate(&iter, &oid, NULL, &grafts->commits) == 0) { git_oid *cpy = git_array_alloc(array); GIT_ERROR_CHECK_ALLOC(cpy); git_oid_cpy(cpy, oid); @@ -268,5 +266,5 @@ int git_grafts_oids(git_oid **out, size_t *out_len, git_grafts *grafts) size_t git_grafts_size(git_grafts *grafts) { - return git_oidmap_size(grafts->commits); + return git_grafts_oidmap_size(&grafts->commits); } diff --git a/src/libgit2/grafts.h b/src/libgit2/grafts.h index 394867fd..ab59f56b 100644 --- a/src/libgit2/grafts.h +++ b/src/libgit2/grafts.h @@ -9,7 +9,6 @@ #include "common.h" #include "oidarray.h" -#include "oidmap.h" /** graft commit */ typedef struct { diff --git a/src/libgit2/graph.c b/src/libgit2/graph.c index 35e914f7..3cce7fd1 100644 --- a/src/libgit2/graph.c +++ b/src/libgit2/graph.c @@ -243,7 +243,7 @@ int git_graph_reachable_from_any( done: git_commit_list_free(&result); - git_vector_free(&list); + git_vector_dispose(&list); git_revwalk_free(walk); return error; } diff --git a/src/libgit2/hashmap_oid.h b/src/libgit2/hashmap_oid.h new file mode 100644 index 00000000..cb1143cc --- /dev/null +++ b/src/libgit2/hashmap_oid.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_hashmap_oid_h__ +#define INCLUDE_hashmap_oid_h__ + +#include "hashmap.h" + +GIT_INLINE(uint32_t) git_hashmap_oid_hashcode(const git_oid *oid) +{ + uint32_t hash; + memcpy(&hash, oid->id, sizeof(uint32_t)); + return hash; +} + +#define GIT_HASHMAP_OID_STRUCT(name, val_t) \ + GIT_HASHMAP_STRUCT(name, const git_oid *, val_t) +#define GIT_HASHMAP_OID_PROTOTYPES(name, val_t) \ + GIT_HASHMAP_PROTOTYPES(name, const git_oid *, val_t) +#define GIT_HASHMAP_OID_FUNCTIONS(name, scope, val_t) \ + GIT_HASHMAP_FUNCTIONS(name, scope, const git_oid *, val_t, git_hashmap_oid_hashcode, git_oid_equal) + +#define GIT_HASHMAP_OID_SETUP(name, val_t) \ + GIT_HASHMAP_OID_STRUCT(name, val_t) \ + GIT_HASHMAP_OID_FUNCTIONS(name, GIT_HASHMAP_INLINE, val_t) + +#endif diff --git a/src/libgit2/idxmap.c b/src/libgit2/idxmap.c deleted file mode 100644 index bc23608f..00000000 --- a/src/libgit2/idxmap.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "idxmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) -__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) - -/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ -static kh_inline khint_t idxentry_hash(const git_index_entry *e) -{ - const char *s = e->path; - khint_t h = (khint_t)git__tolower(*s); - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); - return h + GIT_INDEX_ENTRY_STAGE(e); -} - -#define idxentry_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) -#define idxentry_icase_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) - -__KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal) -__KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal) - -int git_idxmap_new(git_idxmap **out) -{ - *out = kh_init(idx); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -int git_idxmap_icase_new(git_idxmap_icase **out) -{ - *out = kh_init(idxicase); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_idxmap_free(git_idxmap *map) -{ - kh_destroy(idx, map); -} - -void git_idxmap_icase_free(git_idxmap_icase *map) -{ - kh_destroy(idxicase, map); -} - -void git_idxmap_clear(git_idxmap *map) -{ - kh_clear(idx, map); -} - -void git_idxmap_icase_clear(git_idxmap_icase *map) -{ - kh_clear(idxicase, map); -} - -int git_idxmap_resize(git_idxmap *map, size_t size) -{ - if (!git__is_uint32(size) || - kh_resize(idx, map, (khiter_t)size) < 0) { - git_error_set_oom(); - return -1; - } - return 0; -} - -int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size) -{ - if (!git__is_uint32(size) || - kh_resize(idxicase, map, (khiter_t)size) < 0) { - git_error_set_oom(); - return -1; - } - return 0; -} - -void *git_idxmap_get(git_idxmap *map, const git_index_entry *key) -{ - size_t idx = kh_get(idx, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(idx, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(idxicase, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key) -{ - size_t idx = kh_get(idxicase, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_idxmap_delete(git_idxmap *map, const git_index_entry *key) -{ - khiter_t idx = kh_get(idx, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(idx, map, idx); - return 0; -} - -int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key) -{ - khiter_t idx = kh_get(idxicase, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(idxicase, map, idx); - return 0; -} diff --git a/src/libgit2/idxmap.h b/src/libgit2/idxmap.h deleted file mode 100644 index 76170ef3..00000000 --- a/src/libgit2/idxmap.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_idxmap_h__ -#define INCLUDE_idxmap_h__ - -#include "common.h" - -#include "git2/index.h" - -/** A map with `git_index_entry`s as key. */ -typedef struct kh_idx_s git_idxmap; -/** A map with case-insensitive `git_index_entry`s as key */ -typedef struct kh_idxicase_s git_idxmap_icase; - -/** - * Allocate a new index entry map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_idxmap_new(git_idxmap **out); - -/** - * Allocate a new case-insensitive index entry map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_idxmap_icase_new(git_idxmap_icase **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_idxmap_free(git_idxmap *map); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_idxmap_icase_free(git_idxmap_icase *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_idxmap_clear(git_idxmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_idxmap_icase_clear(git_idxmap_icase *map); - -/** - * Resize the map by allocating more memory. - * - * @param map map that shall be resized - * @param size count of entries that the map shall hold - * @return `0` if the map was successfully resized, a negative - * error code otherwise - */ -int git_idxmap_resize(git_idxmap *map, size_t size); - -/** - * Resize the map by allocating more memory. - * - * @param map map that shall be resized - * @param size count of entries that the map shall hold - * @return `0` if the map was successfully resized, a negative - * error code otherwise - */ -int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for; the index entry will be searched - * for by its case-sensitive path - * @return value associated with the given key or NULL if the key was not found - */ -void *git_idxmap_get(git_idxmap *map, const git_index_entry *key); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for; the index entry will be searched - * for by its case-insensitive path - * @return value associated with the given key or NULL if the key was not found - */ -void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_idxmap_delete(git_idxmap *map, const git_index_entry *key); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key); - -#endif diff --git a/src/libgit2/ignore.c b/src/libgit2/ignore.c index cee58d7f..13a67343 100644 --- a/src/libgit2/ignore.c +++ b/src/libgit2/ignore.c @@ -296,6 +296,8 @@ int git_ignore__for_path( { int error = 0; const char *workdir = git_repository_workdir(repo); + git_attr_cache *attrcache; + const char *excludes_file = NULL; git_str infopath = GIT_STR_INIT; GIT_ASSERT_ARG(repo); @@ -358,10 +360,12 @@ int git_ignore__for_path( } /* load core.excludesfile */ - if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) + attrcache = git_repository_attr_cache(repo); + excludes_file = git_attr_cache_excludesfile(attrcache); + + if (excludes_file != NULL) error = push_ignore_file( - ignores, &ignores->ign_global, NULL, - git_repository_attr_cache(repo)->cfg_excl_file); + ignores, &ignores->ign_global, NULL, excludes_file); cleanup: git_str_dispose(&infopath); @@ -428,13 +432,13 @@ void git_ignore__free(git_ignores *ignores) git_attr_file__free(file); ignores->ign_path.contents[i] = NULL; } - git_vector_free(&ignores->ign_path); + git_vector_dispose(&ignores->ign_path); git_vector_foreach(&ignores->ign_global, i, file) { git_attr_file__free(file); ignores->ign_global.contents[i] = NULL; } - git_vector_free(&ignores->ign_global); + git_vector_dispose(&ignores->ign_global); git_str_dispose(&ignores->dir); } diff --git a/src/libgit2/index.c b/src/libgit2/index.c index ccb38230..a3142c8b 100644 --- a/src/libgit2/index.c +++ b/src/libgit2/index.c @@ -17,10 +17,10 @@ #include "pathspec.h" #include "ignore.h" #include "blob.h" -#include "idxmap.h" #include "diff.h" #include "varint.h" #include "path.h" +#include "index_map.h" #include "git2/odb.h" #include "git2/oid.h" @@ -133,30 +133,6 @@ static int write_index(unsigned char checksum[GIT_HASH_MAX_SIZE], size_t *checks static void index_entry_free(git_index_entry *entry); static void index_entry_reuc_free(git_index_reuc_entry *reuc); -GIT_INLINE(int) index_map_set(git_idxmap *map, git_index_entry *e, bool ignore_case) -{ - if (ignore_case) - return git_idxmap_icase_set((git_idxmap_icase *) map, e, e); - else - return git_idxmap_set(map, e, e); -} - -GIT_INLINE(int) index_map_delete(git_idxmap *map, git_index_entry *e, bool ignore_case) -{ - if (ignore_case) - return git_idxmap_icase_delete((git_idxmap_icase *) map, e); - else - return git_idxmap_delete(map, e); -} - -GIT_INLINE(int) index_map_resize(git_idxmap *map, size_t count, bool ignore_case) -{ - if (ignore_case) - return git_idxmap_icase_resize((git_idxmap_icase *) map, count); - else - return git_idxmap_resize(map, count); -} - int git_index_entry_srch(const void *key, const void *array_member) { const struct entry_srch_key *srch_key = key; @@ -388,6 +364,7 @@ GIT_INLINE(int) index_find( void git_index__set_ignore_case(git_index *index, bool ignore_case) { index->ignore_case = ignore_case; + index->entries_map.ignore_case = ignore_case; if (ignore_case) { index->entries_cmp_path = git__strcasecmp_cb; @@ -422,6 +399,7 @@ int git_index__open( index = git__calloc(1, sizeof(git_index)); GIT_ERROR_CHECK_ALLOC(index); + GIT_ASSERT_ARG(git_oid_type_is_valid(oid_type)); index->oid_type = oid_type; if (git_pool_init(&index->tree_pool, 1) < 0) @@ -438,7 +416,6 @@ int git_index__open( } if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 || - git_idxmap_new(&index->entries_map) < 0 || git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) @@ -465,9 +442,13 @@ int git_index__open( } #ifdef GIT_EXPERIMENTAL_SHA256 -int git_index_open(git_index **index_out, const char *index_path, git_oid_t oid_type) +int git_index_open( + git_index **index_out, + const char *index_path, + const git_index_options *opts) { - return git_index__open(index_out, index_path, oid_type); + return git_index__open(index_out, index_path, + opts && opts->oid_type ? opts->oid_type : GIT_OID_DEFAULT); } #else int git_index_open(git_index **index_out, const char *index_path) @@ -482,9 +463,10 @@ int git_index__new(git_index **out, git_oid_t oid_type) } #ifdef GIT_EXPERIMENTAL_SHA256 -int git_index_new(git_index **out, git_oid_t oid_type) +int git_index_new(git_index **out, const git_index_options *opts) { - return git_index__new(out, oid_type); + return git_index__new(out, + opts && opts->oid_type ? opts->oid_type : GIT_OID_DEFAULT); } #else int git_index_new(git_index **out) @@ -502,11 +484,11 @@ static void index_free(git_index *index) return; git_index_clear(index); - git_idxmap_free(index->entries_map); - git_vector_free(&index->entries); - git_vector_free(&index->names); - git_vector_free(&index->reuc); - git_vector_free(&index->deleted); + git_index_entrymap_dispose(&index->entries_map); + git_vector_dispose(&index->entries); + git_vector_dispose(&index->names); + git_vector_dispose(&index->reuc); + git_vector_dispose(&index->deleted); git__free(index->index_file_path); @@ -547,7 +529,7 @@ static int index_remove_entry(git_index *index, size_t pos) if (entry != NULL) { git_tree_cache_invalidate_path(index->tree, entry->path); - index_map_delete(index->entries_map, entry, index->ignore_case); + git_index_entrymap_remove(&index->entries_map, entry); } error = git_vector_remove(&index->entries, pos); @@ -575,7 +557,8 @@ int git_index_clear(git_index *index) index->tree = NULL; git_pool_clear(&index->tree_pool); - git_idxmap_clear(index->entries_map); + git_index_entrymap_clear(&index->entries_map); + while (!error && index->entries.length > 0) error = index_remove_entry(index, index->entries.length - 1); @@ -786,8 +769,10 @@ static int truncate_racily_clean(git_index *index) diff_opts.pathspec.count = paths.length; diff_opts.pathspec.strings = (char **)paths.contents; - if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) + if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) { + git_vector_dispose(&paths); return error; + } git_vector_foreach(&diff->deltas, i, delta) { entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0); @@ -803,7 +788,7 @@ static int truncate_racily_clean(git_index *index) done: git_diff_free(diff); - git_vector_free(&paths); + git_vector_dispose(&paths); return 0; } @@ -904,14 +889,9 @@ const git_index_entry *git_index_get_bypath( key.path = path; GIT_INDEX_ENTRY_STAGE_SET(&key, stage); - if (index->ignore_case) - value = git_idxmap_icase_get((git_idxmap_icase *) index->entries_map, &key); - else - value = git_idxmap_get(index->entries_map, &key); - - if (!value) { - git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); - return NULL; + if (git_index_entrymap_get(&value, &index->entries_map, &key) != 0) { + git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); + return NULL; } return value; @@ -1453,7 +1433,7 @@ static int index_insert( * check for dups, this is actually cheaper in the long run.) */ if ((error = git_vector_insert_sorted(&index->entries, entry, index_no_dups)) < 0 || - (error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) + (error = git_index_entrymap_put(&index->entries_map, entry)) < 0) goto out; } @@ -1612,15 +1592,17 @@ int git_index_add_bypath(git_index *index, const char *path) if (ret == GIT_EDIRECTORY) { git_submodule *sm; - git_error_state err; + git_error *last_error; - git_error_state_capture(&err, ret); + git_error_save(&last_error); ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); - if (ret == GIT_ENOTFOUND) - return git_error_state_restore(&err); + if (ret == GIT_ENOTFOUND) { + git_error_restore(last_error); + return GIT_EDIRECTORY; + } - git_error_state_free(&err); + git_error_free(last_error); /* * EEXISTS means that there is a repository at that path, but it's not known @@ -1680,8 +1662,7 @@ int git_index__fill(git_index *index, const git_vector *source_entries) return 0; if (git_vector_size_hint(&index->entries, source_entries->length) < 0 || - index_map_resize(index->entries_map, (size_t)(source_entries->length * 1.3), - index->ignore_case) < 0) + git_index_entrymap_resize(&index->entries_map, (size_t)(source_entries->length * 1.3)) < 0) return -1; git_vector_foreach(source_entries, i, source_entry) { @@ -1694,10 +1675,8 @@ int git_index__fill(git_index *index, const git_vector *source_entries) entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; entry->mode = git_index__create_mode(entry->mode); - if ((error = git_vector_insert(&index->entries, entry)) < 0) - break; - - if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) + if ((error = git_vector_insert(&index->entries, entry)) < 0 || + (error = git_index_entrymap_put(&index->entries_map, entry)) < 0) break; index->dirty = 1; @@ -1740,7 +1719,7 @@ int git_index_remove(git_index *index, const char *path, int stage) remove_key.path = path; GIT_INDEX_ENTRY_STAGE_SET(&remove_key, stage); - index_map_delete(index->entries_map, &remove_key, index->ignore_case); + git_index_entrymap_remove(&index->entries_map, &remove_key); if (index_find(&position, index, path, 0, stage) < 0) { git_error_set( @@ -2758,6 +2737,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) unsigned int i; struct index_header header = { 0 }; unsigned char checksum[GIT_HASH_MAX_SIZE]; + unsigned char zero_checksum[GIT_HASH_MAX_SIZE] = { 0 }; size_t checksum_size = git_hash_size(git_oid_algorithm(index->oid_type)); const char *last = NULL; const char *empty = ""; @@ -2792,7 +2772,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) GIT_ASSERT(!index->entries.length); - if ((error = index_map_resize(index->entries_map, header.entry_count, index->ignore_case)) < 0) + if ((error = git_index_entrymap_resize(&index->entries_map, header.entry_count)) < 0) return error; /* Parse all the entries */ @@ -2810,7 +2790,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) goto done; } - if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) { + if ((error = git_index_entrymap_put(&index->entries_map, entry)) < 0) { index_entry_free(entry); goto done; } @@ -2847,8 +2827,11 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* * SHA-1 or SHA-256 (depending on the repository's object format) * over the content of the index file before this checksum. + * Note: checksum may be 0 if the index was written by a client + * where index.skipHash was set to true. */ - if (memcmp(checksum, buffer, checksum_size) != 0) { + if (memcmp(zero_checksum, buffer, checksum_size) != 0 && + memcmp(checksum, buffer, checksum_size) != 0) { error = index_error_invalid( "calculated checksum does not match expected"); goto done; @@ -3083,7 +3066,7 @@ static int write_entries(git_index *index, git_filebuf *file) } done: - git_vector_free(&case_sorted); + git_vector_dispose(&case_sorted); return error; } @@ -3359,14 +3342,11 @@ int git_index_read_tree(git_index *index, const git_tree *tree) { int error = 0; git_vector entries = GIT_VECTOR_INIT; - git_idxmap *entries_map; + git_index_entrymap entries_map = GIT_INDEX_ENTRYMAP_INIT; read_tree_data data; size_t i; git_index_entry *e; - if (git_idxmap_new(&entries_map) < 0) - return -1; - git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ data.index = index; @@ -3382,11 +3362,11 @@ int git_index_read_tree(git_index *index, const git_tree *tree) if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) goto cleanup; - if ((error = index_map_resize(entries_map, entries.length, index->ignore_case)) < 0) + if ((error = git_index_entrymap_resize(&entries_map, entries.length)) < 0) goto cleanup; git_vector_foreach(&entries, i, e) { - if ((error = index_map_set(entries_map, e, index->ignore_case)) < 0) { + if ((error = git_index_entrymap_put(&entries_map, e)) < 0) { git_error_set(GIT_ERROR_INDEX, "failed to insert entry into map"); return error; } @@ -3396,18 +3376,18 @@ int git_index_read_tree(git_index *index, const git_tree *tree) git_vector_sort(&entries); - if ((error = git_index_clear(index)) < 0) { - /* well, this isn't good */; - } else { - git_vector_swap(&entries, &index->entries); - entries_map = git_atomic_swap(index->entries_map, entries_map); - } + if ((error = git_index_clear(index)) < 0) + goto cleanup; + + git_vector_swap(&entries, &index->entries); + git_index_entrymap_swap(&entries_map, &index->entries_map); index->dirty = 1; cleanup: - git_vector_free(&entries); - git_idxmap_free(entries_map); + git_vector_dispose(&entries); + git_index_entrymap_dispose(&entries_map); + if (error < 0) return error; @@ -3423,7 +3403,7 @@ static int git_index_read_iterator( { git_vector new_entries = GIT_VECTOR_INIT, remove_entries = GIT_VECTOR_INIT; - git_idxmap *new_entries_map = NULL; + git_index_entrymap new_entries_map = GIT_INDEX_ENTRYMAP_INIT; git_iterator *index_iterator = NULL; git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *old_entry, *new_entry; @@ -3434,12 +3414,11 @@ static int git_index_read_iterator( GIT_ASSERT((new_iterator->flags & GIT_ITERATOR_DONT_IGNORE_CASE)); if ((error = git_vector_init(&new_entries, new_length_hint, index->entries._cmp)) < 0 || - (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || - (error = git_idxmap_new(&new_entries_map)) < 0) + (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0) goto done; - if (new_length_hint && (error = index_map_resize(new_entries_map, new_length_hint, - index->ignore_case)) < 0) + if (new_length_hint && + (error = git_index_entrymap_resize(&new_entries_map, new_length_hint)) < 0) goto done; opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | @@ -3504,8 +3483,7 @@ static int git_index_read_iterator( if (add_entry) { if ((error = git_vector_insert(&new_entries, add_entry)) == 0) - error = index_map_set(new_entries_map, add_entry, - index->ignore_case); + error = git_index_entrymap_put(&new_entries_map, add_entry); } if (remove_entry && error >= 0) @@ -3534,7 +3512,7 @@ static int git_index_read_iterator( goto done; git_vector_swap(&new_entries, &index->entries); - new_entries_map = git_atomic_swap(index->entries_map, new_entries_map); + git_index_entrymap_swap(&index->entries_map, &new_entries_map); git_vector_foreach(&remove_entries, i, entry) { if (index->tree) @@ -3549,9 +3527,9 @@ static int git_index_read_iterator( error = 0; done: - git_idxmap_free(new_entries_map); - git_vector_free(&new_entries); - git_vector_free(&remove_entries); + git_index_entrymap_dispose(&new_entries_map); + git_vector_dispose(&new_entries); + git_vector_dispose(&remove_entries); git_iterator_free(index_iterator); return error; } @@ -3852,7 +3830,7 @@ int git_index_snapshot_new(git_vector *snap, git_index *index) void git_index_snapshot_release(git_vector *snap, git_index *index) { - git_vector_free(snap); + git_vector_dispose(snap); git_atomic32_dec(&index->readers); diff --git a/src/libgit2/index.h b/src/libgit2/index.h index 53c29977..601e98f1 100644 --- a/src/libgit2/index.h +++ b/src/libgit2/index.h @@ -12,8 +12,8 @@ #include "futils.h" #include "filebuf.h" #include "vector.h" -#include "idxmap.h" #include "tree-cache.h" +#include "index_map.h" #include "git2/odb.h" #include "git2/index.h" @@ -30,7 +30,7 @@ struct git_index { unsigned char checksum[GIT_HASH_MAX_SIZE]; git_vector entries; - git_idxmap *entries_map; + git_index_entrymap entries_map; git_vector deleted; /* deleted entries if readers > 0 */ git_atomic32 readers; /* number of active iterators */ diff --git a/src/libgit2/index_map.c b/src/libgit2/index_map.c new file mode 100644 index 00000000..4c50ca7a --- /dev/null +++ b/src/libgit2/index_map.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "hashmap.h" +#include "index_map.h" + +typedef git_index_entrymap git_index_entrymap_default; +typedef git_index_entrymap git_index_entrymap_icase; + +/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ +GIT_INLINE(uint32_t) git_index_entrymap_hash(const git_index_entry *e) +{ + const char *s = e->path; + uint32_t h = (uint32_t)git__tolower(*s); + if (h) { + for (++s ; *s; ++s) + h = (h << 5) - h + (uint32_t)git__tolower(*s); + } + return h + GIT_INDEX_ENTRY_STAGE(e); +} + +#define git_index_entrymap_equal_default(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) +#define git_index_entrymap_equal_icase(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) + +GIT_HASHMAP_FUNCTIONS(git_index_entrymap_default, GIT_HASHMAP_INLINE, git_index_entry *, git_index_entry *, git_index_entrymap_hash, git_index_entrymap_equal_default) +GIT_HASHMAP_FUNCTIONS(git_index_entrymap_icase, GIT_HASHMAP_INLINE, git_index_entry *, git_index_entry *, git_index_entrymap_hash, git_index_entrymap_equal_icase) + +int git_index_entrymap_put(git_index_entrymap *map, git_index_entry *e) +{ + if (map->ignore_case) + return git_index_entrymap_icase_put((git_index_entrymap_icase *)map, e, e); + else + return git_index_entrymap_default_put((git_index_entrymap_default *)map, e, e); +} + +int git_index_entrymap_get(git_index_entry **out, git_index_entrymap *map, git_index_entry *e) +{ + if (map->ignore_case) + return git_index_entrymap_icase_get(out, (git_index_entrymap_icase *)map, e); + else + return git_index_entrymap_default_get(out, (git_index_entrymap_default *)map, e); +} + +int git_index_entrymap_remove(git_index_entrymap *map, git_index_entry *e) +{ + if (map->ignore_case) + return git_index_entrymap_icase_remove((git_index_entrymap_icase *)map, e); + else + return git_index_entrymap_default_remove((git_index_entrymap_default *)map, e); +} + +int git_index_entrymap_resize(git_index_entrymap *map, size_t count) +{ + if (count > UINT32_MAX) { + git_error_set(GIT_ERROR_INDEX, "index map is out of bounds"); + return -1; + } + + if (map->ignore_case) + return git_index_entrymap_icase__resize((git_index_entrymap_icase *)map, (uint32_t)count); + else + return git_index_entrymap_default__resize((git_index_entrymap_default *)map, (uint32_t)count); +} + +void git_index_entrymap_swap(git_index_entrymap *a, git_index_entrymap *b) +{ + git_index_entrymap t; + + if (a != b) { + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); + } +} + +void git_index_entrymap_clear(git_index_entrymap *map) +{ + if (map->ignore_case) + git_index_entrymap_icase_clear((git_index_entrymap_icase *)map); + else + git_index_entrymap_default_clear((git_index_entrymap_default *)map); +} + +void git_index_entrymap_dispose(git_index_entrymap *map) +{ + if (map->ignore_case) + git_index_entrymap_icase_dispose((git_index_entrymap_icase *)map); + else + git_index_entrymap_default_dispose((git_index_entrymap_default *)map); +} diff --git a/src/libgit2/index_map.h b/src/libgit2/index_map.h new file mode 100644 index 00000000..177a3f19 --- /dev/null +++ b/src/libgit2/index_map.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_index_map_h__ +#define INCLUDE_index_map_h__ + +#include "common.h" +#include "hashmap.h" + +typedef struct { + unsigned int ignore_case; + GIT_HASHMAP_STRUCT_MEMBERS(git_index_entry *, git_index_entry *) +} git_index_entrymap; + +#define GIT_INDEX_ENTRYMAP_INIT { 0 } + +extern int git_index_entrymap_get(git_index_entry **out, git_index_entrymap *map, git_index_entry *e); +extern int git_index_entrymap_put(git_index_entrymap *map, git_index_entry *e); +extern int git_index_entrymap_remove(git_index_entrymap *map, git_index_entry *e); +extern int git_index_entrymap_resize(git_index_entrymap *map, size_t count); +extern void git_index_entrymap_swap(git_index_entrymap *a, git_index_entrymap *b); +extern void git_index_entrymap_clear(git_index_entrymap *map); +extern void git_index_entrymap_dispose(git_index_entrymap *map); + +#endif diff --git a/src/libgit2/indexer.c b/src/libgit2/indexer.c index e559a194..e62daacf 100644 --- a/src/libgit2/indexer.c +++ b/src/libgit2/indexer.c @@ -20,14 +20,16 @@ #include "filebuf.h" #include "oid.h" #include "oidarray.h" -#include "oidmap.h" #include "zstream.h" #include "object.h" +#include "hashmap_oid.h" size_t git_indexer__max_objects = UINT32_MAX; #define UINT31_MAX (0x7FFFFFFF) +GIT_HASHMAP_OID_SETUP(git_indexer_oidmap, git_oid *); + struct entry { git_oid oid; uint32_t crc; @@ -63,7 +65,7 @@ struct git_indexer { char objbuf[8*1024]; /* OIDs referenced from pack objects. Used for verification. */ - git_oidmap *expected_oids; + git_indexer_oidmap expected_oids; /* Needed to look up objects which we want to inject to fix a thin pack */ git_odb *odb; @@ -169,9 +171,12 @@ static int indexer_new( if (in_opts) memcpy(&opts, in_opts, sizeof(opts)); + if (oid_type) + GIT_ASSERT_ARG(git_oid_type_is_valid(oid_type)); + idx = git__calloc(1, sizeof(git_indexer)); GIT_ERROR_CHECK_ALLOC(idx); - idx->oid_type = oid_type; + idx->oid_type = oid_type ? oid_type : GIT_OID_DEFAULT; idx->odb = odb; idx->progress_cb = opts.progress_cb; idx->progress_payload = opts.progress_cb_payload; @@ -181,8 +186,7 @@ static int indexer_new( checksum_type = indexer_hash_algorithm(idx); if ((error = git_hash_ctx_init(&idx->hash_ctx, checksum_type)) < 0 || - (error = git_hash_ctx_init(&idx->trailer, checksum_type)) < 0 || - (error = git_oidmap_new(&idx->expected_oids)) < 0) + (error = git_hash_ctx_init(&idx->trailer, checksum_type)) < 0) goto cleanup; idx->do_verify = opts.verify; @@ -232,13 +236,12 @@ static int indexer_new( int git_indexer_new( git_indexer **out, const char *prefix, - git_oid_t oid_type, git_indexer_options *opts) { return indexer_new( out, prefix, - oid_type, + opts ? opts->oid_type : 0, opts ? opts->mode : 0, opts ? opts->odb : NULL, opts); @@ -380,12 +383,12 @@ static int add_expected_oid(git_indexer *idx, const git_oid *oid) * not have to expect it. */ if ((!idx->odb || !git_odb_exists(idx->odb, oid)) && - !git_oidmap_exists(idx->pack->idx_cache, oid) && - !git_oidmap_exists(idx->expected_oids, oid)) { + !git_pack_oidmap_contains(&idx->pack->idx_cache, oid) && + !git_indexer_oidmap_contains(&idx->expected_oids, oid)) { git_oid *dup = git__malloc(sizeof(*oid)); GIT_ERROR_CHECK_ALLOC(dup); git_oid_cpy(dup, oid); - return git_oidmap_set(idx->expected_oids, dup, dup); + return git_indexer_oidmap_put(&idx->expected_oids, dup, dup); } return 0; @@ -412,8 +415,8 @@ static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) goto out; } - if ((expected = git_oidmap_get(idx->expected_oids, &object->cached.oid)) != NULL) { - git_oidmap_delete(idx->expected_oids, &object->cached.oid); + if (git_indexer_oidmap_get(&expected, &idx->expected_oids, &object->cached.oid) == 0) { + git_indexer_oidmap_remove(&idx->expected_oids, &object->cached.oid); git__free(expected); } @@ -518,7 +521,7 @@ static int store_object(git_indexer *idx) git_oid_cpy(&pentry->id, &oid); pentry->offset = entry_start; - if (git_oidmap_exists(idx->pack->idx_cache, &pentry->id)) { + if (git_pack_oidmap_contains(&idx->pack->idx_cache, &pentry->id)) { const char *idstr = git_oid_tostr_s(&pentry->id); if (!idstr) @@ -530,7 +533,7 @@ static int store_object(git_indexer *idx) goto on_error; } - if ((error = git_oidmap_set(idx->pack->idx_cache, &pentry->id, pentry)) < 0) { + if ((error = git_pack_oidmap_put(&idx->pack->idx_cache, &pentry->id, pentry)) < 0) { git__free(pentry); git_error_set_oom(); goto on_error; @@ -559,7 +562,7 @@ static int store_object(git_indexer *idx) GIT_INLINE(bool) has_entry(git_indexer *idx, git_oid *id) { - return git_oidmap_exists(idx->pack->idx_cache, id); + return git_pack_oidmap_contains(&idx->pack->idx_cache, id); } static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, off64_t entry_start) @@ -575,8 +578,8 @@ static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_ent pentry->offset = entry_start; - if (git_oidmap_exists(idx->pack->idx_cache, &pentry->id) || - git_oidmap_set(idx->pack->idx_cache, &pentry->id, pentry) < 0) { + if (git_pack_oidmap_contains(&idx->pack->idx_cache, &pentry->id) || + git_pack_oidmap_put(&idx->pack->idx_cache, &pentry->id, pentry) < 0) { git_error_set(GIT_ERROR_INDEXER, "cannot insert object into pack"); return -1; } @@ -911,9 +914,6 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_inde return -1; } - if (git_oidmap_new(&idx->pack->idx_cache) < 0) - return -1; - idx->pack->has_cache = 1; if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0) return -1; @@ -1301,9 +1301,9 @@ int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) * bail out due to an incomplete and thus corrupt * packfile. */ - if (git_oidmap_size(idx->expected_oids) > 0) { + if (git_indexer_oidmap_size(&idx->expected_oids) > 0) { git_error_set(GIT_ERROR_INDEXER, "packfile is missing %"PRIuZ" objects", - git_oidmap_size(idx->expected_oids)); + (size_t)git_indexer_oidmap_size(&idx->expected_oids)); return -1; } @@ -1446,9 +1446,9 @@ int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) void git_indexer_free(git_indexer *idx) { - const git_oid *key; - git_oid *value; - size_t iter; + struct git_pack_entry *pentry; + git_oid *id; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; if (idx == NULL) return; @@ -1456,28 +1456,24 @@ void git_indexer_free(git_indexer *idx) if (idx->have_stream) git_packfile_stream_dispose(&idx->stream); - git_vector_free_deep(&idx->objects); + git_vector_dispose_deep(&idx->objects); - if (idx->pack->idx_cache) { - struct git_pack_entry *pentry; - git_oidmap_foreach_value(idx->pack->idx_cache, pentry, { - git__free(pentry); - }); + while (git_pack_oidmap_iterate(&iter, NULL, &pentry, &idx->pack->idx_cache) == 0) + git__free(pentry); - git_oidmap_free(idx->pack->idx_cache); - } + git_pack_oidmap_dispose(&idx->pack->idx_cache); - git_vector_free_deep(&idx->deltas); + git_vector_dispose_deep(&idx->deltas); git_packfile_free(idx->pack, !idx->pack_committed); - iter = 0; - while (git_oidmap_iterate((void **) &value, idx->expected_oids, &iter, &key) == 0) - git__free(value); + iter = GIT_HASHMAP_ITER_INIT; + while (git_indexer_oidmap_iterate(&iter, NULL, &id, &idx->expected_oids) == 0) + git__free(id); git_hash_ctx_cleanup(&idx->trailer); git_hash_ctx_cleanup(&idx->hash_ctx); git_str_dispose(&idx->entry_data); - git_oidmap_free(idx->expected_oids); + git_indexer_oidmap_dispose(&idx->expected_oids); git__free(idx); } diff --git a/src/libgit2/iterator.c b/src/libgit2/iterator.c index 95ded104..5b3e0248 100644 --- a/src/libgit2/iterator.c +++ b/src/libgit2/iterator.c @@ -26,9 +26,10 @@ #define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) #define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) - static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) { + int (*vector_cmp)(const void *a, const void *b); + if (ignore_case) iter->flags |= GIT_ITERATOR_IGNORE_CASE; else @@ -39,7 +40,9 @@ static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; - git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); + vector_cmp = ignore_case ? git__strcasecmp_cb : git__strcmp_cb; + + git_vector_set_cmp(&iter->pathlist, vector_cmp); } static int iterator_range_init( @@ -299,6 +302,7 @@ typedef enum { static iterator_pathlist_search_t iterator_pathlist_search( git_iterator *iter, const char *path, size_t path_len) { + int (*vector_cmp)(const void *a, const void *b); const char *p; size_t idx; int error; @@ -308,8 +312,10 @@ static iterator_pathlist_search_t iterator_pathlist_search( git_vector_sort(&iter->pathlist); - error = git_vector_bsearch2(&idx, &iter->pathlist, - (git_vector_cmp)iter->strcomp, path); + vector_cmp = (iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? + git__strcasecmp_cb : git__strcmp_cb; + + error = git_vector_bsearch2(&idx, &iter->pathlist, vector_cmp, path); /* the given path was found in the pathlist. since the pathlist only * matches directories when they're suffixed with a '/', analyze the @@ -690,7 +696,7 @@ static int tree_iterator_frame_pop(tree_iterator *iter) frame = git_array_pop(iter->frames); - git_vector_free(&frame->entries); + git_vector_dispose(&frame->entries); git_tree_free(frame->tree); do { @@ -703,7 +709,7 @@ static int tree_iterator_frame_pop(tree_iterator *iter) git_vector_foreach(&frame->similar_trees, i, tree) git_tree_free(tree); - git_vector_free(&frame->similar_trees); + git_vector_dispose(&frame->similar_trees); git_str_dispose(&frame->path); @@ -1495,7 +1501,7 @@ GIT_INLINE(int) filesystem_iterator_frame_pop(filesystem_iterator *iter) filesystem_iterator_frame_pop_ignores(iter); git_pool_clear(&frame->entry_pool); - git_vector_free(&frame->entries); + git_vector_dispose(&frame->entries); return 0; } @@ -2330,7 +2336,7 @@ void git_iterator_free(git_iterator *iter) iter->cb->free(iter); - git_vector_free(&iter->pathlist); + git_vector_dispose(&iter->pathlist); git__free(iter->start); git__free(iter->end); diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c index ce287147..de72cfe7 100644 --- a/src/libgit2/libgit2.c +++ b/src/libgit2/libgit2.c @@ -5,67 +5,32 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "libgit2.h" - #include #include "alloc.h" #include "buf.h" -#include "cache.h" #include "common.h" #include "filter.h" -#include "grafts.h" #include "hash.h" -#include "index.h" #include "merge_driver.h" #include "pool.h" #include "mwindow.h" -#include "object.h" -#include "odb.h" +#include "oid.h" #include "rand.h" -#include "refs.h" #include "runtime.h" +#include "settings.h" #include "sysdir.h" #include "thread.h" -#include "threadstate.h" #include "git2/global.h" #include "streams/registry.h" #include "streams/mbedtls.h" #include "streams/openssl.h" #include "streams/socket.h" -#include "transports/smart.h" -#include "transports/http.h" -#include "transports/ssh.h" +#include "transports/ssh_libssh2.h" #ifdef GIT_WIN32 # include "win32/w32_leakcheck.h" #endif -/* Declarations for tuneable settings */ -extern size_t git_mwindow__window_size; -extern size_t git_mwindow__mapped_limit; -extern size_t git_mwindow__file_limit; -extern size_t git_indexer__max_objects; -extern bool git_disable_pack_keep_file_checks; -extern int git_odb__packed_priority; -extern int git_odb__loose_priority; -extern int git_socket_stream__connect_timeout; -extern int git_socket_stream__timeout; - -char *git__user_agent; -char *git__ssl_ciphers; - -static void libgit2_settings_global_shutdown(void) -{ - git__free(git__user_agent); - git__free(git__ssl_ciphers); - git_repository__free_extensions(); -} - -static int git_libgit2_settings_global_init(void) -{ - return git_runtime_shutdown_register(libgit2_settings_global_shutdown); -} - int git_libgit2_init(void) { static git_runtime_init_fn init_fns[] = { @@ -73,31 +38,27 @@ int git_libgit2_init(void) git_win32_leakcheck_global_init, #endif git_allocator_global_init, - git_threadstate_global_init, + git_error_global_init, git_threads_global_init, + git_oid_global_init, git_rand_global_init, git_hash_global_init, git_sysdir_global_init, git_filter_global_init, git_merge_driver_global_init, - git_transport_ssh_global_init, + git_transport_ssh_libssh2_global_init, git_stream_registry_global_init, git_socket_stream_global_init, git_openssl_stream_global_init, git_mbedtls_stream_global_init, git_mwindow_global_init, git_pool_global_init, - git_libgit2_settings_global_init + git_settings_global_init }; return git_runtime_init(init_fns, ARRAY_SIZE(init_fns)); } -int git_libgit2_init_count(void) -{ - return git_runtime_init_count(); -} - int git_libgit2_shutdown(void) { return git_runtime_shutdown(); @@ -105,16 +66,16 @@ int git_libgit2_shutdown(void) int git_libgit2_version(int *major, int *minor, int *rev) { - *major = LIBGIT2_VER_MAJOR; - *minor = LIBGIT2_VER_MINOR; - *rev = LIBGIT2_VER_REVISION; + *major = LIBGIT2_VERSION_MAJOR; + *minor = LIBGIT2_VERSION_MINOR; + *rev = LIBGIT2_VERSION_REVISION; return 0; } const char *git_libgit2_prerelease(void) { - return LIBGIT2_VER_PRERELEASE; + return LIBGIT2_VERSION_PRERELEASE; } int git_libgit2_features(void) @@ -126,358 +87,182 @@ int git_libgit2_features(void) #ifdef GIT_HTTPS | GIT_FEATURE_HTTPS #endif -#if defined(GIT_SSH) +#ifdef GIT_SSH | GIT_FEATURE_SSH #endif -#if defined(GIT_USE_NSEC) +#ifdef GIT_USE_NSEC | GIT_FEATURE_NSEC +#endif + | GIT_FEATURE_HTTP_PARSER + | GIT_FEATURE_REGEX +#ifdef GIT_USE_ICONV + | GIT_FEATURE_I18N +#endif +#if defined(GIT_NTLM) || defined(GIT_WIN32) + | GIT_FEATURE_AUTH_NTLM +#endif +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) || defined(GIT_WIN32) + | GIT_FEATURE_AUTH_NEGOTIATE +#endif + | GIT_FEATURE_COMPRESSION + | GIT_FEATURE_SHA1 +#ifdef GIT_EXPERIMENTAL_SHA256 + | GIT_FEATURE_SHA256 #endif ; } -static int config_level_to_sysdir(int *out, int config_level) -{ - switch (config_level) { - case GIT_CONFIG_LEVEL_SYSTEM: - *out = GIT_SYSDIR_SYSTEM; - return 0; - case GIT_CONFIG_LEVEL_XDG: - *out = GIT_SYSDIR_XDG; - return 0; - case GIT_CONFIG_LEVEL_GLOBAL: - *out = GIT_SYSDIR_GLOBAL; - return 0; - case GIT_CONFIG_LEVEL_PROGRAMDATA: - *out = GIT_SYSDIR_PROGRAMDATA; - return 0; - default: - break; - } - - git_error_set( - GIT_ERROR_INVALID, "invalid config path selector %d", config_level); - return -1; -} - -const char *git_libgit2__user_agent(void) +const char *git_libgit2_feature_backend(git_feature_t feature) { - return git__user_agent; -} - -const char *git_libgit2__ssl_ciphers(void) -{ - return git__ssl_ciphers; -} - -int git_libgit2_opts(int key, ...) -{ - int error = 0; - va_list ap; - - va_start(ap, key); - - switch (key) { - case GIT_OPT_SET_MWINDOW_SIZE: - git_mwindow__window_size = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_SIZE: - *(va_arg(ap, size_t *)) = git_mwindow__window_size; - break; - - case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: - git_mwindow__mapped_limit = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: - *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; - break; - - case GIT_OPT_SET_MWINDOW_FILE_LIMIT: - git_mwindow__file_limit = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_FILE_LIMIT: - *(va_arg(ap, size_t *)) = git_mwindow__file_limit; - break; - - case GIT_OPT_GET_SEARCH_PATH: - { - int sysdir = va_arg(ap, int); - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - int level; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = config_level_to_sysdir(&level, sysdir)) < 0 || - (error = git_sysdir_get(&tmp, level)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_SEARCH_PATH: - { - int level; - - if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0) - error = git_sysdir_set(level, va_arg(ap, const char *)); - } - break; - - case GIT_OPT_SET_CACHE_OBJECT_LIMIT: - { - git_object_t type = (git_object_t)va_arg(ap, int); - size_t size = va_arg(ap, size_t); - error = git_cache_set_max_object_size(type, size); - break; - } - - case GIT_OPT_SET_CACHE_MAX_SIZE: - git_cache__max_storage = va_arg(ap, ssize_t); - break; - - case GIT_OPT_ENABLE_CACHING: - git_cache__enabled = (va_arg(ap, int) != 0); + switch (feature) { + case GIT_FEATURE_THREADS: +#if defined(GIT_THREADS) && defined(GIT_WIN32) + return "win32"; +#elif defined(GIT_THREADS) + return "pthread"; +#endif break; - case GIT_OPT_GET_CACHED_MEMORY: - *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; - *(va_arg(ap, ssize_t *)) = git_cache__max_storage; + case GIT_FEATURE_HTTPS: +#if defined(GIT_HTTPS) && defined(GIT_OPENSSL) + return "openssl"; +#elif defined(GIT_HTTPS) && defined(GIT_OPENSSL_DYNAMIC) + return "openssl-dynamic"; +#elif defined(GIT_HTTPS) && defined(GIT_MBEDTLS) + return "mbedtls"; +#elif defined(GIT_HTTPS) && defined(GIT_SECURE_TRANSPORT) + return "securetransport"; +#elif defined(GIT_HTTPS) && defined(GIT_SCHANNEL) + return "schannel"; +#elif defined(GIT_HTTPS) && defined(GIT_WINHTTP) + return "winhttp"; +#elif defined(GIT_HTTPS) + GIT_ASSERT_WITH_RETVAL(!"Unknown HTTPS backend", NULL); +#endif break; - case GIT_OPT_GET_TEMPLATE_PATH: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } + case GIT_FEATURE_SSH: +#if defined(GIT_SSH_EXEC) + return "exec"; +#elif defined(GIT_SSH_LIBSSH2) + return "libssh2"; +#elif defined(GIT_SSH) + GIT_ASSERT_WITH_RETVAL(!"Unknown SSH backend", NULL); +#endif break; - case GIT_OPT_SET_TEMPLATE_PATH: - error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *)); + case GIT_FEATURE_NSEC: +#if defined(GIT_USE_NSEC) && defined(GIT_USE_STAT_MTIMESPEC) + return "mtimespec"; +#elif defined(GIT_USE_NSEC) && defined(GIT_USE_STAT_MTIM) + return "mtim"; +#elif defined(GIT_USE_NSEC) && defined(GIT_USE_STAT_MTIME_NSEC) + return "mtime"; +#elif defined(GIT_USE_NSEC) && defined(GIT_WIN32) + return "win32"; +#elif defined(GIT_USE_NSEC) + GIT_ASSERT_WITH_RETVAL(!"Unknown high-resolution time backend", NULL); +#endif break; - case GIT_OPT_SET_SSL_CERT_LOCATIONS: -#ifdef GIT_OPENSSL - { - const char *file = va_arg(ap, const char *); - const char *path = va_arg(ap, const char *); - error = git_openssl__set_cert_location(file, path); - } -#elif defined(GIT_MBEDTLS) - { - const char *file = va_arg(ap, const char *); - const char *path = va_arg(ap, const char *); - error = git_mbedtls__set_cert_location(file, path); - } -#else - git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations"); - error = -1; + case GIT_FEATURE_HTTP_PARSER: +#if defined(GIT_HTTPPARSER_HTTPPARSER) + return "httpparser"; +#elif defined(GIT_HTTPPARSER_LLHTTP) + return "llhttp"; +#elif defined(GIT_HTTPPARSER_BUILTIN) + return "builtin"; +#endif + GIT_ASSERT_WITH_RETVAL(!"Unknown HTTP parser backend", NULL); + break; + + case GIT_FEATURE_REGEX: +#if defined(GIT_REGEX_REGCOMP_L) + return "regcomp_l"; +#elif defined(GIT_REGEX_REGCOMP) + return "regcomp"; +#elif defined(GIT_REGEX_PCRE) + return "pcre"; +#elif defined(GIT_REGEX_PCRE2) + return "pcre2"; +#elif defined(GIT_REGEX_BUILTIN) + return "builtin"; #endif + GIT_ASSERT_WITH_RETVAL(!"Unknown regular expression backend", NULL); break; - case GIT_OPT_SET_USER_AGENT: - git__free(git__user_agent); - git__user_agent = git__strdup(va_arg(ap, const char *)); - if (!git__user_agent) { - git_error_set_oom(); - error = -1; - } + case GIT_FEATURE_I18N: +#if defined(GIT_USE_ICONV) + return "iconv"; +#endif break; - case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: - git_object__strict_input_validation = (va_arg(ap, int) != 0); + case GIT_FEATURE_AUTH_NTLM: +#if defined(GIT_NTLM) + return "ntlmclient"; +#elif defined(GIT_WIN32) + return "sspi"; +#endif break; - case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: - git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0); + case GIT_FEATURE_AUTH_NEGOTIATE: +#if defined(GIT_GSSAPI) + return "gssapi"; +#elif defined(GIT_WIN32) + return "sspi"; +#endif break; - case GIT_OPT_SET_SSL_CIPHERS: -#if (GIT_OPENSSL || GIT_MBEDTLS) - { - git__free(git__ssl_ciphers); - git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); - if (!git__ssl_ciphers) { - git_error_set_oom(); - error = -1; - } - } + case GIT_FEATURE_COMPRESSION: +#if defined(GIT_COMPRESSION_ZLIB) + return "zlib"; +#elif defined(GIT_COMPRESSION_BUILTIN) + return "builtin"; #else - git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); - error = -1; + GIT_ASSERT_WITH_RETVAL(!"Unknown compression backend", NULL); #endif break; - case GIT_OPT_GET_USER_AGENT: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_str_puts(&str, git__user_agent)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_ENABLE_OFS_DELTA: - git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_FSYNC_GITDIR: - git_repository__fsync_gitdir = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_WINDOWS_SHAREMODE: -#ifdef GIT_WIN32 - *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; + case GIT_FEATURE_SHA1: +#if defined(GIT_SHA1_COLLISIONDETECT) + return "builtin"; +#elif defined(GIT_SHA1_OPENSSL) + return "openssl"; +#elif defined(GIT_SHA1_OPENSSL_FIPS) + return "openssl-fips"; +#elif defined(GIT_SHA1_OPENSSL_DYNAMIC) + return "openssl-dynamic"; +#elif defined(GIT_SHA1_MBEDTLS) + return "mbedtls"; +#elif defined(GIT_SHA1_COMMON_CRYPTO) + return "commoncrypto"; +#elif defined(GIT_SHA1_WIN32) + return "win32"; +#else + GIT_ASSERT_WITH_RETVAL(!"Unknown SHA1 backend", NULL); #endif break; - case GIT_OPT_SET_WINDOWS_SHAREMODE: -#ifdef GIT_WIN32 - git_win32__createfile_sharemode = va_arg(ap, unsigned long); + case GIT_FEATURE_SHA256: +#if defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_BUILTIN) + return "builtin"; +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_OPENSSL) + return "openssl"; +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_OPENSSL_FIPS) + return "openssl-fips"; +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_OPENSSL_DYNAMIC) + return "openssl-dynamic"; +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_MBEDTLS) + return "mbedtls"; +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_COMMON_CRYPTO) + return "commoncrypto"; +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_WIN32) + return "win32"; +#elif defined(GIT_EXPERIMENTAL_SHA256) + GIT_ASSERT_WITH_RETVAL(!"Unknown SHA256 backend", NULL); #endif break; - - case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: - git_odb__strict_hash_verification = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_ALLOCATOR: - error = git_allocator_setup(va_arg(ap, git_allocator *)); - break; - - case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: - git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_PACK_MAX_OBJECTS: - git_indexer__max_objects = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_PACK_MAX_OBJECTS: - *(va_arg(ap, size_t *)) = git_indexer__max_objects; - break; - - case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: - git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: - git_http__expect_continue = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_ODB_PACKED_PRIORITY: - git_odb__packed_priority = va_arg(ap, int); - break; - - case GIT_OPT_SET_ODB_LOOSE_PRIORITY: - git_odb__loose_priority = va_arg(ap, int); - break; - - case GIT_OPT_SET_EXTENSIONS: - { - const char **extensions = va_arg(ap, const char **); - size_t len = va_arg(ap, size_t); - error = git_repository__set_extensions(extensions, len); - } - break; - - case GIT_OPT_GET_EXTENSIONS: - { - git_strarray *out = va_arg(ap, git_strarray *); - char **extensions; - size_t len; - - if ((error = git_repository__extensions(&extensions, &len)) < 0) - break; - - out->strings = extensions; - out->count = len; - } - break; - - case GIT_OPT_GET_OWNER_VALIDATION: - *(va_arg(ap, int *)) = git_repository__validate_ownership; - break; - - case GIT_OPT_SET_OWNER_VALIDATION: - git_repository__validate_ownership = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_HOMEDIR: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_sysdir_get(&tmp, GIT_SYSDIR_HOME)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_HOMEDIR: - error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *)); - break; - - case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: - *(va_arg(ap, int *)) = git_socket_stream__connect_timeout; - break; - - case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: - { - int timeout = va_arg(ap, int); - - if (timeout < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); - error = -1; - } else { - git_socket_stream__connect_timeout = timeout; - } - } - break; - - case GIT_OPT_GET_SERVER_TIMEOUT: - *(va_arg(ap, int *)) = git_socket_stream__timeout; - break; - - case GIT_OPT_SET_SERVER_TIMEOUT: - { - int timeout = va_arg(ap, int); - - if (timeout < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid timeout"); - error = -1; - } else { - git_socket_stream__timeout = timeout; - } - } - break; - - default: - git_error_set(GIT_ERROR_INVALID, "invalid option key"); - error = -1; } - va_end(ap); - - return error; + return NULL; } diff --git a/src/libgit2/libgit2.h b/src/libgit2/libgit2.h deleted file mode 100644 index a898367a..00000000 --- a/src/libgit2/libgit2.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_libgit2_h__ -#define INCLUDE_libgit2_h__ - -extern int git_libgit2_init_count(void); - -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); - -#endif diff --git a/src/libgit2/mailmap.c b/src/libgit2/mailmap.c index 4336fe3e..05c88ff0 100644 --- a/src/libgit2/mailmap.c +++ b/src/libgit2/mailmap.c @@ -173,7 +173,7 @@ void git_mailmap_free(git_mailmap *mm) git_vector_foreach(&mm->entries, idx, entry) mailmap_entry_free(entry); - git_vector_free(&mm->entries); + git_vector_dispose(&mm->entries); git__free(mm); } diff --git a/src/libgit2/merge.c b/src/libgit2/merge.c index 0114e4b7..25834c69 100644 --- a/src/libgit2/merge.c +++ b/src/libgit2/merge.c @@ -32,7 +32,6 @@ #include "commit.h" #include "oidarray.h" #include "merge_driver.h" -#include "oidmap.h" #include "array.h" #include "git2/types.h" @@ -124,11 +123,11 @@ static int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_r *out = result; *walk_out = walk; - git_vector_free(&list); + git_vector_dispose(&list); return 0; on_error: - git_vector_free(&list); + git_vector_dispose(&list); git_revwalk_free(walk); return error; } @@ -511,7 +510,7 @@ static int remove_redundant(git_revwalk *walk, git_vector *commits, uint32_t min done: git__free(redundant); git__free(filled_index); - git_vector_free(&work); + git_vector_dispose(&work); return error; } @@ -570,7 +569,7 @@ int git_merge__bases_many( if ((error = clear_commit_marks(one, ALL_FLAGS)) < 0 || (error = clear_commit_marks_many(twos, ALL_FLAGS)) < 0 || (error = remove_redundant(walk, &redundant, minimum_generation)) < 0) { - git_vector_free(&redundant); + git_vector_dispose(&redundant); return error; } @@ -579,7 +578,7 @@ int git_merge__bases_many( git_commit_list_insert_by_date(two, &result); } - git_vector_free(&redundant); + git_vector_dispose(&redundant); } *out = result; @@ -1144,24 +1143,28 @@ typedef struct { size_t first_entry; } deletes_by_oid_queue; -static void deletes_by_oid_free(git_oidmap *map) { +GIT_HASHMAP_OID_SETUP(git_merge_deletes_oidmap, deletes_by_oid_queue *); + +static void deletes_by_oid_dispose(git_merge_deletes_oidmap *map) +{ + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; deletes_by_oid_queue *queue; if (!map) return; - git_oidmap_foreach_value(map, queue, { + while (git_merge_deletes_oidmap_iterate(&iter, NULL, &queue, map) == 0) git_array_clear(queue->arr); - }); - git_oidmap_free(map); + + git_merge_deletes_oidmap_dispose(map); } -static int deletes_by_oid_enqueue(git_oidmap *map, git_pool *pool, const git_oid *id, size_t idx) +static int deletes_by_oid_enqueue(git_merge_deletes_oidmap *map, git_pool *pool, const git_oid *id, size_t idx) { deletes_by_oid_queue *queue; size_t *array_entry; - if ((queue = git_oidmap_get(map, id)) == NULL) { + if (git_merge_deletes_oidmap_get(&queue, map, id) != 0) { queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); GIT_ERROR_CHECK_ALLOC(queue); @@ -1169,7 +1172,7 @@ static int deletes_by_oid_enqueue(git_oidmap *map, git_pool *pool, const git_oid queue->next_pos = 0; queue->first_entry = idx; - if (git_oidmap_set(map, id, queue) < 0) + if (git_merge_deletes_oidmap_put(map, id, queue) < 0) return -1; } else { array_entry = git_array_alloc(queue->arr); @@ -1180,13 +1183,14 @@ static int deletes_by_oid_enqueue(git_oidmap *map, git_pool *pool, const git_oid return 0; } -static int deletes_by_oid_dequeue(size_t *idx, git_oidmap *map, const git_oid *id) +static int deletes_by_oid_dequeue(size_t *idx, git_merge_deletes_oidmap *map, const git_oid *id) { deletes_by_oid_queue *queue; size_t *array_entry; + int error; - if ((queue = git_oidmap_get(map, id)) == NULL) - return GIT_ENOTFOUND; + if ((error = git_merge_deletes_oidmap_get(&queue, map, id)) != 0) + return error; if (queue->next_pos == 0) { *idx = queue->first_entry; @@ -1209,15 +1213,10 @@ static int merge_diff_mark_similarity_exact( { size_t i, j; git_merge_diff *conflict_src, *conflict_tgt; - git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; + git_merge_deletes_oidmap ours_deletes_by_oid = GIT_HASHMAP_INIT, + theirs_deletes_by_oid = GIT_HASHMAP_INIT; int error = 0; - if (git_oidmap_new(&ours_deletes_by_oid) < 0 || - git_oidmap_new(&theirs_deletes_by_oid) < 0) { - error = -1; - goto done; - } - /* Build a map of object ids to conflicts */ git_vector_foreach(&diff_list->conflicts, i, conflict_src) { /* Items can be the source of a rename iff they have an item in the @@ -1225,14 +1224,21 @@ static int merge_diff_mark_similarity_exact( if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) continue; + /* + * Ignore empty files because it has always the same blob sha1 + * and will lead to incorrect matches between all entries. + */ + if (git_oid_equal(&conflict_src->ancestor_entry.id, &git_oid__empty_blob_sha1)) + continue; + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { - error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + error = deletes_by_oid_enqueue(&ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); if (error < 0) goto done; } if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { - error = deletes_by_oid_enqueue(theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + error = deletes_by_oid_enqueue(&theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); if (error < 0) goto done; } @@ -1243,7 +1249,7 @@ static int merge_diff_mark_similarity_exact( continue; if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry)) { - if (deletes_by_oid_dequeue(&i, ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { + if (deletes_by_oid_dequeue(&i, &ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { similarity_ours[i].similarity = 100; similarity_ours[i].other_idx = j; @@ -1253,7 +1259,7 @@ static int merge_diff_mark_similarity_exact( } if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry)) { - if (deletes_by_oid_dequeue(&i, theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { + if (deletes_by_oid_dequeue(&i, &theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { similarity_theirs[i].similarity = 100; similarity_theirs[i].other_idx = j; @@ -1264,8 +1270,8 @@ static int merge_diff_mark_similarity_exact( } done: - deletes_by_oid_free(ours_deletes_by_oid); - deletes_by_oid_free(theirs_deletes_by_oid); + deletes_by_oid_dispose(&ours_deletes_by_oid); + deletes_by_oid_dispose(&theirs_deletes_by_oid); return error; } @@ -1859,9 +1865,9 @@ void git_merge_diff_list__free(git_merge_diff_list *diff_list) if (!diff_list) return; - git_vector_free(&diff_list->staged); - git_vector_free(&diff_list->conflicts); - git_vector_free(&diff_list->resolved); + git_vector_dispose(&diff_list->staged); + git_vector_dispose(&diff_list->conflicts); + git_vector_dispose(&diff_list->resolved); git_pool_clear(&diff_list->pool); git__free(diff_list); } @@ -2829,7 +2835,7 @@ static int write_merge_msg( git_str_dispose(&file_path); - git_vector_free(&matching); + git_vector_dispose(&matching); git__free(entries); return error; @@ -3008,7 +3014,7 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index git_iterator_free(iter_new); git_diff_free(staged_diff_list); git_diff_free(index_diff_list); - git_vector_free(&staged_paths); + git_vector_dispose(&staged_paths); return error; } @@ -3105,7 +3111,7 @@ int git_merge__check_result(git_repository *repo, git_index *index_new) } done: - git_vector_free(&paths); + git_vector_dispose(&paths); git_tree_free(head_tree); git_iterator_free(iter_head); git_iterator_free(iter_new); @@ -3346,8 +3352,7 @@ int git_merge( goto done; checkout_strategy = given_checkout_opts ? - given_checkout_opts->checkout_strategy : - GIT_CHECKOUT_SAFE; + given_checkout_opts->checkout_strategy : 0; if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, &checkout_strategy)) < 0) diff --git a/src/libgit2/merge_driver.c b/src/libgit2/merge_driver.c index 19b35ac0..300964af 100644 --- a/src/libgit2/merge_driver.c +++ b/src/libgit2/merge_driver.c @@ -218,7 +218,7 @@ int git_merge_driver_global_init(void) done: if (error < 0) - git_vector_free_deep(&merge_driver_registry.drivers); + git_vector_dispose_deep(&merge_driver_registry.drivers); return error; } @@ -238,7 +238,7 @@ static void git_merge_driver_global_shutdown(void) git__free(entry); } - git_vector_free(&merge_driver_registry.drivers); + git_vector_dispose(&merge_driver_registry.drivers); git_rwlock_wrunlock(&merge_driver_registry.lock); git_rwlock_free(&merge_driver_registry.lock); diff --git a/src/libgit2/midx.c b/src/libgit2/midx.c index d73a1da4..1336ed85 100644 --- a/src/libgit2/midx.c +++ b/src/libgit2/midx.c @@ -484,7 +484,7 @@ int git_midx_close(git_midx_file *idx) if (idx->index_map.data) git_futils_mmap_free(&idx->index_map); - git_vector_free(&idx->packfile_names); + git_vector_dispose(&idx->packfile_names); return 0; } @@ -508,20 +508,28 @@ static int packfile__cmp(const void *a_, const void *b_) } int git_midx_writer_new( - git_midx_writer **out, - const char *pack_dir + git_midx_writer **out, + const char *pack_dir #ifdef GIT_EXPERIMENTAL_SHA256 - , git_oid_t oid_type + , git_midx_writer_options *opts #endif ) { git_midx_writer *w; + git_oid_t oid_type; -#ifndef GIT_EXPERIMENTAL_SHA256 - git_oid_t oid_type = GIT_OID_SHA1; -#endif + GIT_ASSERT_ARG(out && pack_dir); - GIT_ASSERT_ARG(out && pack_dir && oid_type); +#ifdef GIT_EXPERIMENTAL_SHA256 + GIT_ERROR_CHECK_VERSION(opts, + GIT_MIDX_WRITER_OPTIONS_VERSION, + "git_midx_writer_options"); + + oid_type = opts && opts->oid_type ? opts->oid_type : GIT_OID_DEFAULT; + GIT_ASSERT_ARG(git_oid_type_is_valid(oid_type)); +#else + oid_type = GIT_OID_SHA1; +#endif w = git__calloc(1, sizeof(git_midx_writer)); GIT_ERROR_CHECK_ALLOC(w); @@ -554,7 +562,7 @@ void git_midx_writer_free(git_midx_writer *w) git_vector_foreach (&w->packs, i, p) git_mwindow_put_pack(p); - git_vector_free(&w->packs); + git_vector_dispose(&w->packs); git_str_dispose(&w->pack_dir); git__free(w); } @@ -660,9 +668,11 @@ static int midx_write_hash(const char *buf, size_t size, void *data) struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data; int error; - error = git_hash_update(ctx->ctx, buf, size); - if (error < 0) - return error; + if (ctx->ctx) { + error = git_hash_update(ctx->ctx, buf, size); + if (error < 0) + return error; + } return ctx->write_cb(buf, size, ctx->cb_data); } @@ -703,9 +713,9 @@ static int midx_write( hash_cb_data.ctx = &ctx; oid_size = git_oid_size(w->oid_type); - - GIT_ASSERT((checksum_type = git_oid_algorithm(w->oid_type))); + checksum_type = git_oid_algorithm(w->oid_type); checksum_size = git_hash_size(checksum_type); + GIT_ASSERT(oid_size && checksum_type && checksum_size); if ((error = git_hash_ctx_init(&ctx, checksum_type)) < 0) return error; @@ -863,13 +873,16 @@ static int midx_write( error = git_hash_final(checksum, &ctx); if (error < 0) goto cleanup; + + hash_cb_data.ctx = NULL; + error = write_cb((char *)checksum, checksum_size, cb_data); if (error < 0) goto cleanup; cleanup: git_array_clear(object_entries_array); - git_vector_free(&object_entries); + git_vector_dispose(&object_entries); git_str_dispose(&packfile_names); git_str_dispose(&oid_lookup); git_str_dispose(&object_offsets); diff --git a/src/libgit2/mwindow.c b/src/libgit2/mwindow.c index b8295d9d..7e4054df 100644 --- a/src/libgit2/mwindow.c +++ b/src/libgit2/mwindow.c @@ -11,7 +11,6 @@ #include "futils.h" #include "map.h" #include "runtime.h" -#include "strmap.h" #include "pack.h" #define DEFAULT_WINDOW_SIZE \ @@ -29,33 +28,27 @@ size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE; size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; size_t git_mwindow__file_limit = DEFAULT_FILE_LIMIT; -/* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */ -git_mutex git__mwindow_mutex; +/* Mutex to control access to `git_mwindow__mem_ctl` and `git_mwindow__pack_cache`. */ +git_mutex git_mwindow__mutex; -/* Whenever you want to read or modify this, grab `git__mwindow_mutex` */ +/* Whenever you want to read or modify this, grab `git_mwindow__mutex` */ git_mwindow_ctl git_mwindow__mem_ctl; /* Global list of mwindow files, to open packs once across repos */ -git_strmap *git__pack_cache = NULL; +GIT_HASHMAP_STR_FUNCTIONS(git_mwindow_packmap, , struct git_pack_file *); +git_mwindow_packmap git_mwindow__pack_cache; static void git_mwindow_global_shutdown(void) { - git_strmap *tmp = git__pack_cache; - - git_mutex_free(&git__mwindow_mutex); - - git__pack_cache = NULL; - git_strmap_free(tmp); + git_mutex_free(&git_mwindow__mutex); + git_mwindow_packmap_dispose(&git_mwindow__pack_cache); } int git_mwindow_global_init(void) { int error; - GIT_ASSERT(!git__pack_cache); - - if ((error = git_mutex_init(&git__mwindow_mutex)) < 0 || - (error = git_strmap_new(&git__pack_cache)) < 0) + if ((error = git_mutex_init(&git_mwindow__mutex)) < 0) return error; return git_runtime_shutdown_register(git_mwindow_global_shutdown); @@ -73,31 +66,34 @@ int git_mwindow_get_pack( if ((error = git_packfile__name(&packname, path)) < 0) return error; - if (git_mutex_lock(&git__mwindow_mutex) < 0) { + if (git_mutex_lock(&git_mwindow__mutex) < 0) { git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex"); return -1; } - pack = git_strmap_get(git__pack_cache, packname); + error = git_mwindow_packmap_get(&pack, &git_mwindow__pack_cache, packname); git__free(packname); - if (pack != NULL) { + if (error == 0) { git_atomic32_inc(&pack->refcount); - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); *out = pack; return 0; + } else if (error != GIT_ENOTFOUND) { + return error; } /* If we didn't find it, we need to create it */ if ((error = git_packfile_alloc(&pack, path, oid_type)) < 0) { - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); return error; } git_atomic32_inc(&pack->refcount); - error = git_strmap_set(git__pack_cache, pack->pack_name, pack); - git_mutex_unlock(&git__mwindow_mutex); + error = git_mwindow_packmap_put(&git_mwindow__pack_cache, pack->pack_name, pack); + git_mutex_unlock(&git_mwindow__mutex); + if (error < 0) { git_packfile_free(pack, false); return error; @@ -112,21 +108,18 @@ int git_mwindow_put_pack(struct git_pack_file *pack) int count, error; struct git_pack_file *pack_to_delete = NULL; - if ((error = git_mutex_lock(&git__mwindow_mutex)) < 0) + if ((error = git_mutex_lock(&git_mwindow__mutex)) < 0) return error; - /* put before get would be a corrupted state */ - GIT_ASSERT(git__pack_cache); - /* if we cannot find it, the state is corrupted */ - GIT_ASSERT(git_strmap_exists(git__pack_cache, pack->pack_name)); + GIT_ASSERT(git_mwindow_packmap_contains(&git_mwindow__pack_cache, pack->pack_name)); count = git_atomic32_dec(&pack->refcount); if (count == 0) { - git_strmap_delete(git__pack_cache, pack->pack_name); + git_mwindow_packmap_remove(&git_mwindow__pack_cache, pack->pack_name); pack_to_delete = pack; } - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); git_packfile_free(pack_to_delete, false); return 0; @@ -134,7 +127,7 @@ int git_mwindow_put_pack(struct git_pack_file *pack) /* * Free all the windows in a sequence, typically because we're done - * with the file. Needs to hold the git__mwindow_mutex. + * with the file. Needs to hold the git_mwindow__mutex. */ static int git_mwindow_free_all_locked(git_mwindow_file *mwf) { @@ -152,7 +145,7 @@ static int git_mwindow_free_all_locked(git_mwindow_file *mwf) } if (ctl->windowfiles.length == 0) { - git_vector_free(&ctl->windowfiles); + git_vector_dispose(&ctl->windowfiles); ctl->windowfiles.contents = NULL; } @@ -176,14 +169,14 @@ int git_mwindow_free_all(git_mwindow_file *mwf) { int error; - if (git_mutex_lock(&git__mwindow_mutex)) { + if (git_mutex_lock(&git_mwindow__mutex)) { git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); return -1; } error = git_mwindow_free_all_locked(mwf); - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); return error; } @@ -405,7 +398,7 @@ unsigned char *git_mwindow_open( git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; git_mwindow *w = *cursor; - if (git_mutex_lock(&git__mwindow_mutex)) { + if (git_mutex_lock(&git_mwindow__mutex)) { git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); return NULL; } @@ -427,7 +420,7 @@ unsigned char *git_mwindow_open( if (!w) { w = new_window_locked(mwf->fd, mwf->size, offset); if (w == NULL) { - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); return NULL; } w->next = mwf->windows; @@ -447,7 +440,7 @@ unsigned char *git_mwindow_open( if (left) *left = (unsigned int)(w->window_map.len - offset); - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); return (unsigned char *) w->window_map.data + offset; } @@ -459,14 +452,14 @@ int git_mwindow_file_register(git_mwindow_file *mwf) size_t i; git_mwindow_file *closed_file = NULL; - if (git_mutex_lock(&git__mwindow_mutex)) { + if (git_mutex_lock(&git_mwindow__mutex)) { git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); return -1; } if (ctl->windowfiles.length == 0 && (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < 0) { - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); goto cleanup; } @@ -486,7 +479,7 @@ int git_mwindow_file_register(git_mwindow_file *mwf) } error = git_vector_insert(&ctl->windowfiles, mwf); - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); if (error < 0) goto cleanup; @@ -505,7 +498,7 @@ int git_mwindow_file_register(git_mwindow_file *mwf) } cleanup: - git_vector_free(&closed_files); + git_vector_dispose(&closed_files); return error; } @@ -515,30 +508,30 @@ void git_mwindow_file_deregister(git_mwindow_file *mwf) git_mwindow_file *cur; size_t i; - if (git_mutex_lock(&git__mwindow_mutex)) + if (git_mutex_lock(&git_mwindow__mutex)) return; git_vector_foreach(&ctl->windowfiles, i, cur) { if (cur == mwf) { git_vector_remove(&ctl->windowfiles, i); - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); return; } } - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); } void git_mwindow_close(git_mwindow **window) { git_mwindow *w = *window; if (w) { - if (git_mutex_lock(&git__mwindow_mutex)) { + if (git_mutex_lock(&git_mwindow__mutex)) { git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); return; } w->inuse_cnt--; - git_mutex_unlock(&git__mwindow_mutex); + git_mutex_unlock(&git_mwindow__mutex); *window = NULL; } } diff --git a/src/libgit2/mwindow.h b/src/libgit2/mwindow.h index 8e6df261..43352c41 100644 --- a/src/libgit2/mwindow.h +++ b/src/libgit2/mwindow.h @@ -12,6 +12,10 @@ #include "map.h" #include "vector.h" +#include "hashmap_str.h" + +GIT_HASHMAP_STR_STRUCT(git_mwindow_packmap, struct git_pack_file *); +GIT_HASHMAP_STR_PROTOTYPES(git_mwindow_packmap, struct git_pack_file *); typedef struct git_mwindow { struct git_mwindow *next; diff --git a/src/libgit2/object.c b/src/libgit2/object.c index 5fab77e6..36665c67 100644 --- a/src/libgit2/object.c +++ b/src/libgit2/object.c @@ -526,7 +526,7 @@ static int git_object__short_id(git_str *out, const git_object *obj) git_oid id; git_odb *odb; size_t oid_hexsize; - int len = GIT_ABBREV_DEFAULT, error; + int len, error; GIT_ASSERT_ARG(out); GIT_ASSERT_ARG(obj); @@ -536,12 +536,13 @@ static int git_object__short_id(git_str *out, const git_object *obj) git_oid_clear(&id, repo->oid_type); oid_hexsize = git_oid_hexsize(repo->oid_type); - if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) + if ((error = git_repository__abbrev_length(&len, repo)) < 0) return error; - if (len < 0 || (size_t)len > oid_hexsize) { - git_error_set(GIT_ERROR_CONFIG, "invalid oid abbreviation setting: '%d'", len); - return -1; + if ((size_t)len == oid_hexsize) { + if ((error = git_oid_cpy(&id, &obj->cached.oid)) < 0) { + return error; + } } if ((error = git_repository_odb(&odb, repo)) < 0) diff --git a/src/libgit2/odb.c b/src/libgit2/odb.c index fec1e45b..653c7c03 100644 --- a/src/libgit2/odb.c +++ b/src/libgit2/odb.c @@ -790,12 +790,8 @@ static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_ if (*alternate == '\0' || *alternate == '#') continue; - /* - * Relative path: build based on the current `objects` - * folder. However, relative paths are only allowed in - * the current repository. - */ - if (*alternate == '.' && !alternate_depth) { + /* Relative path: build based on the current `objects` folder. */ + if (*alternate == '.') { if ((result = git_str_joinpath(&alternates_path, objects_dir, alternate)) < 0) break; alternate = git_str_cstr(&alternates_path); @@ -915,7 +911,7 @@ static void odb_free(git_odb *db) git_mutex_unlock(&db->lock); git_commit_graph_free(db->cgraph); - git_vector_free(&db->backends); + git_vector_dispose(&db->backends); git_cache_dispose(&db->own_cache); git_mutex_free(&db->lock); @@ -1609,7 +1605,7 @@ int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) } cleanup: - git_vector_free(&backends); + git_vector_dispose(&backends); return error; } @@ -1796,7 +1792,8 @@ void git_odb_stream_free(git_odb_stream *stream) if (stream == NULL) return; - git_hash_ctx_cleanup(stream->hash_ctx); + if (stream->hash_ctx) + git_hash_ctx_cleanup(stream->hash_ctx); git__free(stream->hash_ctx); stream->free(stream); } @@ -1922,7 +1919,7 @@ void git_odb_backend_data_free(git_odb_backend *backend, void *data) git__free(data); } -int git_odb_refresh(struct git_odb *db) +int git_odb_refresh(git_odb *db) { size_t i; int error; diff --git a/src/libgit2/odb_mempack.c b/src/libgit2/odb_mempack.c index 6f27f45f..2f3fba9a 100644 --- a/src/libgit2/odb_mempack.c +++ b/src/libgit2/odb_mempack.c @@ -12,7 +12,6 @@ #include "hash.h" #include "odb.h" #include "array.h" -#include "oidmap.h" #include "pack-objects.h" #include "git2/odb_backend.h" @@ -29,9 +28,11 @@ struct memobject { char data[GIT_FLEX_ARRAY]; }; +GIT_HASHMAP_OID_SETUP(git_odb_mempack_oidmap, struct memobject *); + struct memory_packer_db { git_odb_backend parent; - git_oidmap *objects; + git_odb_mempack_oidmap objects; git_array_t(struct memobject *) commits; }; @@ -41,7 +42,7 @@ static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void struct memobject *obj = NULL; size_t alloc_len; - if (git_oidmap_exists(db->objects, oid)) + if (git_odb_mempack_oidmap_contains(&db->objects, oid)) return 0; GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len); @@ -53,7 +54,7 @@ static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void obj->len = len; obj->type = type; - if (git_oidmap_set(db->objects, &obj->oid, obj) < 0) + if (git_odb_mempack_oidmap_put(&db->objects, &obj->oid, obj) < 0) return -1; if (type == GIT_OBJECT_COMMIT) { @@ -69,16 +70,17 @@ static int impl__exists(git_odb_backend *backend, const git_oid *oid) { struct memory_packer_db *db = (struct memory_packer_db *)backend; - return git_oidmap_exists(db->objects, oid); + return git_odb_mempack_oidmap_contains(&db->objects, oid); } static int impl__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) { struct memory_packer_db *db = (struct memory_packer_db *)backend; struct memobject *obj; + int error; - if ((obj = git_oidmap_get(db->objects, oid)) == NULL) - return GIT_ENOTFOUND; + if ((error = git_odb_mempack_oidmap_get(&obj, &db->objects, oid)) != 0) + return error; *len_p = obj->len; *type_p = obj->type; @@ -93,9 +95,10 @@ static int impl__read_header(size_t *len_p, git_object_t *type_p, git_odb_backen { struct memory_packer_db *db = (struct memory_packer_db *)backend; struct memobject *obj; + int error; - if ((obj = git_oidmap_get(db->objects, oid)) == NULL) - return GIT_ENOTFOUND; + if ((error = git_odb_mempack_oidmap_get(&obj, &db->objects, oid)) != 0) + return error; *len_p = obj->len; *type_p = obj->type; @@ -132,6 +135,29 @@ static int git_mempack__dump( return err; } +int git_mempack_write_thin_pack(git_odb_backend *backend, git_packbuilder *pb) +{ + struct memory_packer_db *db = (struct memory_packer_db *)backend; + const git_oid *oid; + git_hashmap_iter_t iter = GIT_HASHMAP_INIT; + int err; + + while (true) { + err = git_odb_mempack_oidmap_iterate(&iter, &oid, NULL, &db->objects); + + if (err == GIT_ITEROVER) + break; + else if (err != 0) + return err; + + err = git_packbuilder_insert(pb, oid, NULL); + if (err != 0) + return err; + } + + return 0; +} + int git_mempack_dump( git_buf *pack, git_repository *repo, @@ -144,14 +170,13 @@ int git_mempack_reset(git_odb_backend *_backend) { struct memory_packer_db *db = (struct memory_packer_db *)_backend; struct memobject *object = NULL; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; - git_oidmap_foreach_value(db->objects, object, { + while (git_odb_mempack_oidmap_iterate(&iter, NULL, &object, &db->objects) == 0) git__free(object); - }); git_array_clear(db->commits); - - git_oidmap_clear(db->objects); + git_odb_mempack_oidmap_clear(&db->objects); return 0; } @@ -161,7 +186,7 @@ static void impl__free(git_odb_backend *_backend) struct memory_packer_db *db = (struct memory_packer_db *)_backend; git_mempack_reset(_backend); - git_oidmap_free(db->objects); + git_odb_mempack_oidmap_dispose(&db->objects); git__free(db); } @@ -174,9 +199,6 @@ int git_mempack_new(git_odb_backend **out) db = git__calloc(1, sizeof(struct memory_packer_db)); GIT_ERROR_CHECK_ALLOC(db); - if (git_oidmap_new(&db->objects) < 0) - return -1; - db->parent.version = GIT_ODB_BACKEND_VERSION; db->parent.read = &impl__read; db->parent.write = &impl__write; @@ -187,3 +209,13 @@ int git_mempack_new(git_odb_backend **out) *out = (git_odb_backend *)db; return 0; } + +int git_mempack_object_count(size_t *out, git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + + GIT_ASSERT_ARG(_backend); + + *out = (size_t)git_odb_mempack_oidmap_size(&db->objects); + return 0; +} diff --git a/src/libgit2/odb_pack.c b/src/libgit2/odb_pack.c index fc533ae8..ef8d3530 100644 --- a/src/libgit2/odb_pack.c +++ b/src/libgit2/odb_pack.c @@ -743,10 +743,10 @@ static int pack_backend__writepack(struct git_odb_writepack **out, #ifdef GIT_EXPERIMENTAL_SHA256 opts.odb = odb; + opts.oid_type = backend->opts.oid_type; error = git_indexer_new(&writepack->indexer, backend->pack_folder, - backend->opts.oid_type, &opts); #else error = git_indexer_new(&writepack->indexer, @@ -796,13 +796,21 @@ static int pack_backend__writemidx(git_odb_backend *_backend) size_t i; int error = 0; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_midx_writer_options midx_opts = GIT_MIDX_WRITER_OPTIONS_INIT; +#endif + GIT_ASSERT_ARG(_backend); backend = (struct pack_backend *)_backend; +#ifdef GIT_EXPERIMENTAL_SHA256 + midx_opts.oid_type = backend->opts.oid_type; +#endif + error = git_midx_writer_new(&w, backend->pack_folder #ifdef GIT_EXPERIMENTAL_SHA256 - , backend->opts.oid_type + , &midx_opts #endif ); @@ -863,8 +871,8 @@ static void pack_backend__free(git_odb_backend *_backend) git_mwindow_put_pack(p); git_midx_free(backend->midx); - git_vector_free(&backend->midx_packs); - git_vector_free(&backend->packs); + git_vector_dispose(&backend->midx_packs); + git_vector_dispose(&backend->packs); git__free(backend->pack_folder); git__free(backend); } @@ -883,7 +891,7 @@ static int pack_backend__alloc( } if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) { - git_vector_free(&backend->midx_packs); + git_vector_dispose(&backend->midx_packs); git__free(backend); return -1; } diff --git a/src/libgit2/offmap.c b/src/libgit2/offmap.c deleted file mode 100644 index be9eb66d..00000000 --- a/src/libgit2/offmap.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "offmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(off, off64_t, void *) - -__KHASH_IMPL(off, static kh_inline, off64_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) - - -int git_offmap_new(git_offmap **out) -{ - *out = kh_init(off); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_offmap_free(git_offmap *map) -{ - kh_destroy(off, map); -} - -void git_offmap_clear(git_offmap *map) -{ - kh_clear(off, map); -} - -size_t git_offmap_size(git_offmap *map) -{ - return kh_size(map); -} - -void *git_offmap_get(git_offmap *map, const off64_t key) -{ - size_t idx = kh_get(off, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_offmap_set(git_offmap *map, const off64_t key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(off, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_offmap_delete(git_offmap *map, const off64_t key) -{ - khiter_t idx = kh_get(off, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(off, map, idx); - return 0; -} - -int git_offmap_exists(git_offmap *map, const off64_t key) -{ - return kh_get(off, map, key) != kh_end(map); -} - -int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_value(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/libgit2/offmap.h b/src/libgit2/offmap.h deleted file mode 100644 index 81c459b0..00000000 --- a/src/libgit2/offmap.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_offmap_h__ -#define INCLUDE_offmap_h__ - -#include "common.h" - -#include "git2/types.h" - -/** A map with `off64_t`s as key. */ -typedef struct kh_off_s git_offmap; - -/** - * Allocate a new `off64_t` map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_offmap_new(git_offmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_offmap_free(git_offmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_offmap_clear(git_offmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_offmap_size(git_offmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_offmap_get(git_offmap *map, const off64_t key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_offmap_set(git_offmap *map, const off64_t key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_offmap_delete(git_offmap *map, const off64_t key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_offmap_exists(git_offmap *map, const off64_t key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key); - -#define git_offmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ - while (git_offmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ - code; \ - } } - -#define git_offmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_offmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/libgit2/oid.c b/src/libgit2/oid.c index 631a566e..2bb7a6f6 100644 --- a/src/libgit2/oid.c +++ b/src/libgit2/oid.c @@ -9,7 +9,7 @@ #include "git2/oid.h" #include "repository.h" -#include "threadstate.h" +#include "runtime.h" #include #include @@ -153,15 +153,42 @@ int git_oid_pathfmt(char *str, const git_oid *oid) return 0; } +static git_tlsdata_key thread_str_key; + +static void GIT_SYSTEM_CALL thread_str_free(void *s) +{ + char *str = (char *)s; + git__free(str); +} + +static void thread_str_global_shutdown(void) +{ + char *str = git_tlsdata_get(thread_str_key); + git_tlsdata_set(thread_str_key, NULL); + + git__free(str); + git_tlsdata_dispose(thread_str_key); +} + +int git_oid_global_init(void) +{ + if (git_tlsdata_init(&thread_str_key, thread_str_free) != 0) + return -1; + + return git_runtime_shutdown_register(thread_str_global_shutdown); +} + char *git_oid_tostr_s(const git_oid *oid) { - git_threadstate *threadstate = git_threadstate_get(); char *str; - if (!threadstate) - return NULL; + if ((str = git_tlsdata_get(thread_str_key)) == NULL) { + if ((str = git__malloc(GIT_OID_MAX_HEXSIZE + 1)) == NULL) + return NULL; + + git_tlsdata_set(thread_str_key, str); + } - str = threadstate->oid_fmt; git_oid_nfmt(str, git_oid_hexsize(git_oid_type(oid)) + 1, oid); return str; } diff --git a/src/libgit2/oid.h b/src/libgit2/oid.h index 7b6b09d8..9415915f 100644 --- a/src/libgit2/oid.h +++ b/src/libgit2/oid.h @@ -66,6 +66,15 @@ GIT_INLINE(size_t) git_oid_hexsize(git_oid_t type) return 0; } +GIT_INLINE(bool) git_oid_type_is_valid(git_oid_t type) +{ + return (type == GIT_OID_SHA1 +#ifdef GIT_EXPERIMENTAL_SHA256 + || type == GIT_OID_SHA256 +#endif + ); +} + GIT_INLINE(const char *) git_oid_type_name(git_oid_t type) { switch (type) { @@ -270,4 +279,6 @@ int git_oid__fromstrn( int git_oid__fromraw(git_oid *out, const unsigned char *raw, git_oid_t type); +int git_oid_global_init(void); + #endif diff --git a/src/libgit2/oidmap.c b/src/libgit2/oidmap.c deleted file mode 100644 index eaf9fa05..00000000 --- a/src/libgit2/oidmap.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "oidmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(oid, const git_oid *, void *) - -GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) -{ - khint_t h; - memcpy(&h, oid->id, sizeof(khint_t)); - return h; -} - -__KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal) - -int git_oidmap_new(git_oidmap **out) -{ - *out = kh_init(oid); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_oidmap_free(git_oidmap *map) -{ - kh_destroy(oid, map); -} - -void git_oidmap_clear(git_oidmap *map) -{ - kh_clear(oid, map); -} - -size_t git_oidmap_size(git_oidmap *map) -{ - return kh_size(map); -} - -void *git_oidmap_get(git_oidmap *map, const git_oid *key) -{ - size_t idx = kh_get(oid, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(oid, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_oidmap_delete(git_oidmap *map, const git_oid *key) -{ - khiter_t idx = kh_get(oid, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(oid, map, idx); - return 0; -} - -int git_oidmap_exists(git_oidmap *map, const git_oid *key) -{ - return kh_get(oid, map, key) != kh_end(map); -} - -int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_value(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/libgit2/oidmap.h b/src/libgit2/oidmap.h deleted file mode 100644 index b748f727..00000000 --- a/src/libgit2/oidmap.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_oidmap_h__ -#define INCLUDE_oidmap_h__ - -#include "common.h" - -#include "git2/oid.h" - -/** A map with `git_oid`s as key. */ -typedef struct kh_oid_s git_oidmap; - -/** - * Allocate a new OID map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_oidmap_new(git_oidmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_oidmap_free(git_oidmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_oidmap_clear(git_oidmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_oidmap_size(git_oidmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_oidmap_get(git_oidmap *map, const git_oid *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_oidmap_delete(git_oidmap *map, const git_oid *key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_oidmap_exists(git_oidmap *map, const git_oid *key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key); - -#define git_oidmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_oidmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/libgit2/pack-objects.c b/src/libgit2/pack-objects.c index b2d80cba..ced98e8c 100644 --- a/src/libgit2/pack-objects.c +++ b/src/libgit2/pack-objects.c @@ -64,6 +64,9 @@ struct walk_object { /* Size of the buffer to feed to zlib */ #define COMPRESS_BUFLEN (1024 * 1024) +GIT_HASHMAP_OID_FUNCTIONS(git_packbuilder_pobjectmap, GIT_HASHMAP_INLINE, git_pobject *); +GIT_HASHMAP_OID_FUNCTIONS(git_packbuilder_walk_objectmap, GIT_HASHMAP_INLINE, struct walk_object *); + static unsigned name_hash(const char *name) { unsigned c, hash = 0; @@ -139,9 +142,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) hash_algorithm = git_oid_algorithm(pb->oid_type); GIT_ASSERT(hash_algorithm); - if (git_oidmap_new(&pb->object_ix) < 0 || - git_oidmap_new(&pb->walk_objects) < 0 || - git_pool_init(&pb->object_pool, sizeof(struct walk_object)) < 0) + if (git_pool_init(&pb->object_pool, sizeof(struct walk_object)) < 0) goto on_error; pb->repo = repo; @@ -192,10 +193,10 @@ static int rehash(git_packbuilder *pb) git_pobject *po; size_t i; - git_oidmap_clear(pb->object_ix); + git_packbuilder_pobjectmap_clear(&pb->object_ix); for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) { - if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) + if (git_packbuilder_pobjectmap_put(&pb->object_ix, &po->id, po) < 0) return -1; } @@ -214,7 +215,7 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, /* If the object already exists in the hash table, then we don't * have any work to do */ - if (git_oidmap_exists(pb->object_ix, oid)) + if (git_packbuilder_pobjectmap_contains(&pb->object_ix, oid)) return 0; if (pb->nr_objects >= pb->nr_alloc) { @@ -246,7 +247,7 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, git_oid_cpy(&po->id, oid); po->hash = name_hash(name); - if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) { + if (git_packbuilder_pobjectmap_put(&pb->object_ix, &po->id, po) < 0) { git_error_set_oom(); return -1; } @@ -515,7 +516,7 @@ static int cb_tag_foreach(const char *name, git_oid *oid, void *data) GIT_UNUSED(name); - if ((po = git_oidmap_get(pb->object_ix, oid)) == NULL) + if (git_packbuilder_pobjectmap_get(&po, &pb->object_ix, oid) != 0) return 0; po->tagged = 1; @@ -932,6 +933,9 @@ static int report_delta_progress( { int ret; + if (pb->failure) + return pb->failure; + if (pb->progress_cb) { uint64_t current_time = git_time_monotonic(); uint64_t elapsed = current_time - pb->last_progress_report_time; @@ -943,8 +947,10 @@ static int report_delta_progress( GIT_PACKBUILDER_DELTAFICATION, count, pb->nr_objects, pb->progress_cb_payload); - if (ret) + if (ret) { + pb->failure = ret; return git_error_set_after_callback(ret); + } } } @@ -976,7 +982,10 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, } pb->nr_deltified += 1; - report_delta_progress(pb, pb->nr_deltified, false); + if ((error = report_delta_progress(pb, pb->nr_deltified, false)) < 0) { + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + goto on_error; + } po = *list++; (*list_size)--; @@ -1124,6 +1133,10 @@ struct thread_params { size_t depth; size_t working; size_t data_ready; + + /* A pb->progress_cb can stop the packing process by returning an error. + When that happens, all threads observe the error and stop voluntarily. */ + bool stopped; }; static void *threaded_find_deltas(void *arg) @@ -1133,7 +1146,12 @@ static void *threaded_find_deltas(void *arg) while (me->remaining) { if (find_deltas(me->pb, me->list, &me->remaining, me->window, me->depth) < 0) { - ; /* TODO */ + me->stopped = true; + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); + me->working = false; + git_cond_signal(&me->pb->progress_cond); + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_unlock(me->pb) == 0, NULL); + return NULL; } GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); @@ -1175,8 +1193,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, pb->nr_threads = git__online_cpus(); if (pb->nr_threads <= 1) { - find_deltas(pb, list, &list_size, window, depth); - return 0; + return find_deltas(pb, list, &list_size, window, depth); } p = git__mallocarray(pb->nr_threads, sizeof(*p)); @@ -1195,6 +1212,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, p[i].depth = depth; p[i].working = 1; p[i].data_ready = 0; + p[i].stopped = 0; /* try to split chunks on "path" boundaries */ while (sub_size && sub_size < list_size && @@ -1262,7 +1280,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, (!victim || victim->remaining < p[i].remaining)) victim = &p[i]; - if (victim) { + if (victim && !target->stopped) { sub_size = victim->remaining / 2; list = victim->list + victim->list_size - sub_size; while (sub_size && list[0]->hash && @@ -1286,7 +1304,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, } target->list_size = sub_size; target->remaining = sub_size; - target->working = 1; + target->working = 1; /* even when target->stopped, so that we don't process this thread again */ GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); if (git_mutex_lock(&target->mutex)) { @@ -1299,7 +1317,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, git_cond_signal(&target->cond); git_mutex_unlock(&target->mutex); - if (!sub_size) { + if (target->stopped || !sub_size) { git_thread_join(&target->thread, NULL); git_cond_free(&target->cond); git_mutex_free(&target->mutex); @@ -1308,7 +1326,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, } git__free(p); - return 0; + return pb->failure; } #else @@ -1319,6 +1337,7 @@ int git_packbuilder__prepare(git_packbuilder *pb) { git_pobject **delta_list; size_t i, n = 0; + int error; if (pb->nr_objects == 0 || pb->done) return 0; /* nothing to do */ @@ -1327,8 +1346,10 @@ int git_packbuilder__prepare(git_packbuilder *pb) * Although we do not report progress during deltafication, we * at least report that we are in the deltafication stage */ - if (pb->progress_cb) - pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); + if (pb->progress_cb) { + if ((error = pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload)) < 0) + return git_error_set_after_callback(error); + } delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list)); GIT_ERROR_CHECK_ALLOC(delta_list); @@ -1345,31 +1366,33 @@ int git_packbuilder__prepare(git_packbuilder *pb) if (n > 1) { git__tsort((void **)delta_list, n, type_size_sort); - if (ll_find_deltas(pb, delta_list, n, + if ((error = ll_find_deltas(pb, delta_list, n, GIT_PACK_WINDOW + 1, - GIT_PACK_DEPTH) < 0) { + GIT_PACK_DEPTH)) < 0) { git__free(delta_list); - return -1; + return error; } } - report_delta_progress(pb, pb->nr_objects, true); + error = report_delta_progress(pb, pb->nr_objects, true); pb->done = true; git__free(delta_list); - return 0; + return error; } -#define PREPARE_PACK if (git_packbuilder__prepare(pb) < 0) { return -1; } +#define PREPARE_PACK error = git_packbuilder__prepare(pb); if (error < 0) { return error; } int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) { + int error; PREPARE_PACK; return write_pack(pb, cb, payload); } int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb) { + int error; PREPARE_PACK; return write_pack(pb, &write_pack_buf, buf); @@ -1419,8 +1442,9 @@ int git_packbuilder_write( #ifdef GIT_EXPERIMENTAL_SHA256 opts.mode = mode; opts.odb = pb->odb; + opts.oid_type = GIT_OID_SHA1; - error = git_indexer_new(&indexer, path, GIT_OID_SHA1, &opts); + error = git_indexer_new(&indexer, path, &opts); #else error = git_indexer_new(&indexer, path, mode, pb->odb, &opts); #endif @@ -1584,12 +1608,16 @@ static int retrieve_object(struct walk_object **out, git_packbuilder *pb, const struct walk_object *obj; int error; - if ((obj = git_oidmap_get(pb->walk_objects, id)) == NULL) { + error = git_packbuilder_walk_objectmap_get(&obj, &pb->walk_objects, id); + + if (error == GIT_ENOTFOUND) { if ((error = lookup_walk_object(&obj, pb, id)) < 0) return error; - if ((error = git_oidmap_set(pb->walk_objects, &obj->id, obj)) < 0) + if ((error = git_packbuilder_walk_objectmap_put(&pb->walk_objects, &obj->id, obj)) < 0) return error; + } else if (error != 0) { + return error; } *out = obj; @@ -1821,13 +1849,12 @@ void git_packbuilder_free(git_packbuilder *pb) if (pb->odb) git_odb_free(pb->odb); - if (pb->object_ix) - git_oidmap_free(pb->object_ix); + git_packbuilder_pobjectmap_dispose(&pb->object_ix); if (pb->object_list) git__free(pb->object_list); - git_oidmap_free(pb->walk_objects); + git_packbuilder_walk_objectmap_dispose(&pb->walk_objects); git_pool_clear(&pb->object_pool); git_hash_ctx_cleanup(&pb->ctx); diff --git a/src/libgit2/pack-objects.h b/src/libgit2/pack-objects.h index bbc8b943..ad04fb0a 100644 --- a/src/libgit2/pack-objects.h +++ b/src/libgit2/pack-objects.h @@ -12,10 +12,10 @@ #include "str.h" #include "hash.h" -#include "oidmap.h" #include "zstream.h" #include "pool.h" #include "indexer.h" +#include "hashmap_oid.h" #include "git2/oid.h" #include "git2/pack.h" @@ -51,6 +51,11 @@ typedef struct git_pobject { filled:1; } git_pobject; +typedef struct walk_object walk_object; + +GIT_HASHMAP_OID_STRUCT(git_packbuilder_pobjectmap, git_pobject *); +GIT_HASHMAP_OID_STRUCT(git_packbuilder_walk_objectmap, walk_object *); + struct git_packbuilder { git_repository *repo; /* associated repository */ git_odb *odb; /* associated object database */ @@ -69,9 +74,8 @@ struct git_packbuilder { git_pobject *object_list; - git_oidmap *object_ix; - - git_oidmap *walk_objects; + git_packbuilder_pobjectmap object_ix; + git_packbuilder_walk_objectmap walk_objects; git_pool object_pool; #ifndef GIT_DEPRECATE_HARD @@ -100,6 +104,10 @@ struct git_packbuilder { uint64_t last_progress_report_time; bool done; + + /* A non-zero error code in failure causes all threads to shut themselves + down. Some functions will return this error code. */ + volatile int failure; }; int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb); diff --git a/src/libgit2/pack.c b/src/libgit2/pack.c index eff73988..8bdaac3a 100644 --- a/src/libgit2/pack.c +++ b/src/libgit2/pack.c @@ -13,6 +13,7 @@ #include "odb.h" #include "oid.h" #include "oidarray.h" +#include "hashmap_oid.h" /* Option to bypass checking existence of '.keep' files */ bool git_disable_pack_keep_file_checks = false; @@ -41,6 +42,12 @@ static int pack_entry_find_offset( const git_oid *short_oid, size_t len); +#define off64_hash(key) (uint32_t)((key)>>33^(key)^(key)<<11) +#define off64_equal(a, b) ((a) == (b)) + +GIT_HASHMAP_FUNCTIONS(git_pack_offsetmap, GIT_HASHMAP_INLINE, off64_t, git_pack_cache_entry *, off64_hash, off64_equal); +GIT_HASHMAP_OID_FUNCTIONS(git_pack_oidmap, , struct git_pack_entry *); + static int packfile_error(const char *message) { git_error_set(GIT_ERROR_ODB, "invalid pack file - %s", message); @@ -75,31 +82,21 @@ static void free_cache_object(void *o) static void cache_free(git_pack_cache *cache) { + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; git_pack_cache_entry *entry; - if (cache->entries) { - git_offmap_foreach_value(cache->entries, entry, { - free_cache_object(entry); - }); + while (git_pack_offsetmap_iterate(&iter, NULL, &entry, &cache->entries) == 0) + free_cache_object(entry); - git_offmap_free(cache->entries); - cache->entries = NULL; - } + git_pack_offsetmap_dispose(&cache->entries); } static int cache_init(git_pack_cache *cache) { - if (git_offmap_new(&cache->entries) < 0) - return -1; - cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT; if (git_mutex_init(&cache->lock)) { git_error_set(GIT_ERROR_OS, "failed to initialize pack cache mutex"); - - git__free(cache->entries); - cache->entries = NULL; - return -1; } @@ -108,15 +105,16 @@ static int cache_init(git_pack_cache *cache) static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) { - git_pack_cache_entry *entry; + git_pack_cache_entry *entry = NULL; if (git_mutex_lock(&cache->lock) < 0) return NULL; - if ((entry = git_offmap_get(cache->entries, offset)) != NULL) { + if (git_pack_offsetmap_get(&entry, &cache->entries, offset) == 0) { git_atomic32_inc(&entry->refcount); entry->last_usage = cache->use_ctr++; } + git_mutex_unlock(&cache->lock); return entry; @@ -125,16 +123,17 @@ static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) /* Run with the cache lock held */ static void free_lowest_entry(git_pack_cache *cache) { - off64_t offset; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; git_pack_cache_entry *entry; + off64_t offset; - git_offmap_foreach(cache->entries, offset, entry, { + while (git_pack_offsetmap_iterate(&iter, &offset, &entry, &cache->entries) == 0) { if (entry && git_atomic32_get(&entry->refcount) == 0) { cache->memory_used -= entry->raw.len; - git_offmap_delete(cache->entries, offset); + git_pack_offsetmap_remove(&cache->entries, offset); free_cache_object(entry); } - }); + } } static int cache_add( @@ -157,12 +156,12 @@ static int cache_add( return -1; } /* Add it to the cache if nobody else has */ - exists = git_offmap_exists(cache->entries, offset); + exists = git_pack_offsetmap_contains(&cache->entries, offset); if (!exists) { while (cache->memory_used + base->len > cache->memory_limit) free_lowest_entry(cache); - git_offmap_set(cache->entries, offset, entry); + git_pack_offsetmap_put(&cache->entries, offset, entry); cache->memory_used += entry->raw.len; *cached_out = entry; @@ -1009,7 +1008,7 @@ int get_delta_base( if (p->has_cache) { struct git_pack_entry *entry; - if ((entry = git_oidmap_get(p->idx_cache, &base_oid)) != NULL) { + if (git_pack_oidmap_get(&entry, &p->idx_cache, &base_oid) == 0) { if (entry->offset == 0) return packfile_error("delta offset is zero"); @@ -1351,7 +1350,7 @@ int git_pack_foreach_entry( git_vector_insert(&oids, (void*)¤t[4]); } - git_vector_free(&offsets); + git_vector_dispose(&offsets); p->ids = (unsigned char **)git_vector_detach(NULL, NULL, &oids); } @@ -1499,6 +1498,7 @@ static int pack_entry_find_offset( size_t len) { const uint32_t *level1_ofs; + size_t ofs_delta = 0; const unsigned char *index; unsigned hi, lo, stride; int pos, found = 0; @@ -1524,9 +1524,15 @@ static int pack_entry_find_offset( if (p->index_version > 1) { level1_ofs += 2; + ofs_delta = 2; index += 8; } + if ((size_t)short_oid->id[0] + ofs_delta >= p->index_map.len) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->short_oid->[0] out of bounds"); + goto cleanup; + } + index += 4 * 256; hi = ntohl(level1_ofs[(int)short_oid->id[0]]); lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); diff --git a/src/libgit2/pack.h b/src/libgit2/pack.h index 1a9eb14b..4fd09214 100644 --- a/src/libgit2/pack.h +++ b/src/libgit2/pack.h @@ -16,10 +16,9 @@ #include "map.h" #include "mwindow.h" #include "odb.h" -#include "offmap.h" -#include "oidmap.h" #include "zstream.h" #include "oid.h" +#include "hashmap_oid.h" /** * Function type for callbacks from git_pack_foreach_entry_offset. @@ -83,12 +82,23 @@ typedef git_array_t(struct pack_chain_elem) git_dependency_chain; #define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024 #define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */ +struct git_pack_entry { + off64_t offset; + git_oid id; + struct git_pack_file *p; +}; + +GIT_HASHMAP_STRUCT(git_pack_offsetmap, off64_t, git_pack_cache_entry *); + +GIT_HASHMAP_OID_STRUCT(git_pack_oidmap, struct git_pack_entry *); +GIT_HASHMAP_OID_PROTOTYPES(git_pack_oidmap, struct git_pack_entry *); + typedef struct { size_t memory_used; size_t memory_limit; size_t use_ctr; git_mutex lock; - git_offmap *entries; + git_pack_offsetmap entries; } git_pack_cache; struct git_pack_file { @@ -110,7 +120,8 @@ struct git_pack_file { int index_version; git_time_t mtime; - git_oidmap *idx_cache; + + git_pack_oidmap idx_cache; unsigned char **ids; git_pack_cache bases; /* delta base cache */ @@ -139,12 +150,6 @@ int git_pack__lookup_id( const unsigned char *id_prefix, const git_oid_t oid_type); -struct git_pack_entry { - off64_t offset; - git_oid id; - struct git_pack_file *p; -}; - typedef struct git_packfile_stream { off64_t curpos; int done; diff --git a/src/libgit2/patch_parse.c b/src/libgit2/patch_parse.c index c0691553..04f2a582 100644 --- a/src/libgit2/patch_parse.c +++ b/src/libgit2/patch_parse.c @@ -562,9 +562,9 @@ static int parse_hunk_header( static int eof_for_origin(int origin) { if (origin == GIT_DIFF_LINE_ADDITION) - return GIT_DIFF_LINE_ADD_EOFNL; - if (origin == GIT_DIFF_LINE_DELETION) return GIT_DIFF_LINE_DEL_EOFNL; + if (origin == GIT_DIFF_LINE_DELETION) + return GIT_DIFF_LINE_ADD_EOFNL; return GIT_DIFF_LINE_CONTEXT_EOFNL; } diff --git a/src/libgit2/path.c b/src/libgit2/path.c index a19340ef..4b584fb8 100644 --- a/src/libgit2/path.c +++ b/src/libgit2/path.c @@ -202,7 +202,7 @@ GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char * { size_t count = 0; - while (len > 0 && tolower(*str) == tolower(*prefix)) { + while (len > 0 && git__tolower(*str) == git__tolower(*prefix)) { count++; str++; prefix++; diff --git a/src/libgit2/pathspec.c b/src/libgit2/pathspec.c index 3e44643c..26684c08 100644 --- a/src/libgit2/pathspec.c +++ b/src/libgit2/pathspec.c @@ -105,7 +105,7 @@ int git_pathspec__vinit( /* free data from the pathspec vector */ void git_pathspec__vfree(git_vector *vspec) { - git_vector_free_deep(vspec); + git_vector_dispose_deep(vspec); } struct pathspec_match_context { diff --git a/src/libgit2/push.c b/src/libgit2/push.c index 8b47abc2..b0e84173 100644 --- a/src/libgit2/push.c +++ b/src/libgit2/push.c @@ -56,14 +56,22 @@ int git_push_new(git_push **out, git_remote *remote, const git_push_options *opt } if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) { - git_vector_free(&p->specs); + git_vector_dispose(&p->specs); git__free(p); return -1; } if (git_vector_init(&p->updates, 0, NULL) < 0) { - git_vector_free(&p->status); - git_vector_free(&p->specs); + git_vector_dispose(&p->status); + git_vector_dispose(&p->specs); + git__free(p); + return -1; + } + + if (git_vector_init(&p->remote_push_options, 0, git__strcmp_cb) < 0) { + git_vector_dispose(&p->status); + git_vector_dispose(&p->specs); + git_vector_dispose(&p->updates); git__free(p); return -1; } @@ -213,12 +221,25 @@ int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) fire_callback = 0; } - if (fire_callback && callbacks && callbacks->update_tips) { - error = callbacks->update_tips(git_str_cstr(&remote_ref_name), - &push_spec->roid, &push_spec->loid, callbacks->payload); + if (!fire_callback || !callbacks) + continue; - if (error < 0) - goto on_error; + if (callbacks->update_refs) + error = callbacks->update_refs( + git_str_cstr(&remote_ref_name), + &push_spec->roid, &push_spec->loid, + &push_spec->refspec, callbacks->payload); +#ifndef GIT_DEPRECATE_HARD + else if (callbacks->update_tips) + error = callbacks->update_tips( + git_str_cstr(&remote_ref_name), + &push_spec->roid, &push_spec->loid, + callbacks->payload); +#endif + + if (error < 0) { + git_error_set_after_callback_function(error, "git_remote_push"); + goto on_error; } } @@ -275,6 +296,7 @@ static int queue_objects(git_push *push) git_vector_foreach(&push->specs, i, spec) { git_object_t type; + git_oid id; size_t size; if (git_oid_is_zero(&spec->loid)) @@ -296,20 +318,20 @@ static int queue_objects(git_push *push) if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) goto on_error; - if (git_object_type(target) == GIT_OBJECT_COMMIT) { - if ((error = git_revwalk_push(rw, git_object_id(target))) < 0) { - git_object_free(target); - goto on_error; - } - } else { - if ((error = git_packbuilder_insert( - push->pb, git_object_id(target), NULL)) < 0) { - git_object_free(target); - goto on_error; - } - } + type = git_object_type(target); + git_oid_cpy(&id, git_object_id(target)); + git_object_free(target); - } else if ((error = git_revwalk_push(rw, &spec->loid)) < 0) + } else { + git_oid_cpy(&id, &spec->loid); + } + + if (type == GIT_OBJECT_COMMIT) + error = git_revwalk_push(rw, &id); + else + error = git_packbuilder_insert(push->pb, &id, NULL); + + if (error < 0) goto on_error; if (!spec->refspec.force) { @@ -444,10 +466,21 @@ static int do_push(git_push *push) if ((error = calculate_work(push)) < 0) goto on_error; - if (callbacks && callbacks->push_negotiation && - (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents, - push->updates.length, callbacks->payload)) < 0) - goto on_error; + if (callbacks && callbacks->push_negotiation) { + git_error_clear(); + + error = callbacks->push_negotiation( + (const git_push_update **) push->updates.contents, + push->updates.length, callbacks->payload); + + if (error < 0) { + git_error_set_after_callback_function(error, + "push_negotiation"); + goto on_error; + } + + error = 0; + } if ((error = queue_objects(push)) < 0 || (error = transport->push(transport, push)) < 0) @@ -479,12 +512,24 @@ static int filter_refs(git_remote *remote) int git_push_finish(git_push *push) { int error; + unsigned int remote_caps; if (!git_remote_connected(push->remote)) { git_error_set(GIT_ERROR_NET, "remote is disconnected"); return -1; } + if ((error = git_remote_capabilities(&remote_caps, push->remote)) < 0) { + git_error_set(GIT_ERROR_INVALID, "remote capabilities not available"); + return -1; + } + + if (git_vector_length(&push->remote_push_options) > 0 && + !(remote_caps & GIT_REMOTE_CAPABILITY_PUSH_OPTIONS)) { + git_error_set(GIT_ERROR_INVALID, "push-options not supported by remote"); + return -1; + } + if ((error = filter_refs(push->remote)) < 0 || (error = do_push(push)) < 0) return error; @@ -528,6 +573,7 @@ void git_push_free(git_push *push) push_spec *spec; push_status *status; git_push_update *update; + char *option; unsigned int i; if (push == NULL) @@ -536,19 +582,24 @@ void git_push_free(git_push *push) git_vector_foreach(&push->specs, i, spec) { free_refspec(spec); } - git_vector_free(&push->specs); + git_vector_dispose(&push->specs); git_vector_foreach(&push->status, i, status) { git_push_status_free(status); } - git_vector_free(&push->status); + git_vector_dispose(&push->status); git_vector_foreach(&push->updates, i, update) { git__free(update->src_refname); git__free(update->dst_refname); git__free(update); } - git_vector_free(&push->updates); + git_vector_dispose(&push->updates); + + git_vector_foreach(&push->remote_push_options, i, option) { + git__free(option); + } + git_vector_dispose(&push->remote_push_options); git__free(push); } diff --git a/src/libgit2/push.h b/src/libgit2/push.h index fc72e845..40a1823e 100644 --- a/src/libgit2/push.h +++ b/src/libgit2/push.h @@ -34,6 +34,7 @@ struct git_push { git_vector specs; git_vector updates; bool report_status; + git_vector remote_push_options; /* report-status */ bool unpack_ok; diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c index e34a7145..aa427829 100644 --- a/src/libgit2/refdb_fs.c +++ b/src/libgit2/refdb_fs.c @@ -26,7 +26,6 @@ #include #include #include -#include #define DEFAULT_NESTING_LEVEL 5 #define MAX_NESTING_LEVEL 10 @@ -62,8 +61,8 @@ typedef struct refdb_fs_backend { git_oid_t oid_type; - int fsync : 1, - sorted : 1; + unsigned int fsync : 1, + sorted : 1; int peeling_mode; git_iterator_flag_t iterator_flags; uint32_t direach_flags; @@ -410,7 +409,9 @@ static const char *loose_parse_symbolic(git_str *file_content) static bool is_per_worktree_ref(const char *ref_name) { return git__prefixcmp(ref_name, "refs/") != 0 || - git__prefixcmp(ref_name, "refs/bisect/") == 0; + git__prefixcmp(ref_name, "refs/bisect/") == 0 || + git__prefixcmp(ref_name, "refs/worktree/") == 0 || + git__prefixcmp(ref_name, "refs/rewritten/") == 0; } static int loose_lookup( @@ -799,89 +800,155 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) { refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); - git_vector_free(&iter->loose); + git_vector_dispose(&iter->loose); git_pool_clear(&iter->pool); git_sortedcache_free(iter->cache); git__free(iter); } -static int iter_load_loose_paths( - refdb_fs_backend *backend, - refdb_fs_iter *iter) +struct iter_load_context { + refdb_fs_backend *backend; + refdb_fs_iter *iter; + + /* + * If we have a glob with a prefix (eg `refs/heads/ *`) then we can + * optimize our prefix to avoid walking refs that we know won't + * match. This is that prefix. + */ + const char *ref_prefix; + size_t ref_prefix_len; + + /* Temporary variables to avoid unnecessary allocations */ + git_str ref_name; + git_str path; +}; + +static void iter_load_optimize_prefix(struct iter_load_context *ctx) { - int error = 0; - git_str path = GIT_STR_INIT; - git_iterator *fsit = NULL; - git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry = NULL; - const char *ref_prefix = GIT_REFS_DIR; - size_t ref_prefix_len = strlen(ref_prefix); + const char *pos, *last_sep = NULL; - if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ - return 0; + if (!ctx->iter->glob) + return; - fsit_opts.flags = backend->iterator_flags; - fsit_opts.oid_type = backend->oid_type; - - if (iter->glob) { - const char *last_sep = NULL; - const char *pos; - for (pos = iter->glob; *pos; ++pos) { - switch (*pos) { - case '?': - case '*': - case '[': - case '\\': - break; - case '/': - last_sep = pos; - /* FALLTHROUGH */ - default: - continue; - } + for (pos = ctx->iter->glob; *pos; pos++) { + switch (*pos) { + case '?': + case '*': + case '[': + case '\\': break; + case '/': + last_sep = pos; + /* FALLTHROUGH */ + default: + continue; } - if (last_sep) { - ref_prefix = iter->glob; - ref_prefix_len = (last_sep - ref_prefix) + 1; - } + break; } - if ((error = git_str_puts(&path, backend->commonpath)) < 0 || - (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) { - git_str_dispose(&path); - return error; + if (last_sep) { + ctx->ref_prefix = ctx->iter->glob; + ctx->ref_prefix_len = (last_sep - ctx->ref_prefix) + 1; } +} + +static int iter_load_paths( + struct iter_load_context *ctx, + const char *root_path, + bool worktree) +{ + git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error = 0; + + fsit_opts.flags = ctx->backend->iterator_flags; + + git_str_clear(&ctx->path); + git_str_puts(&ctx->path, root_path); + git_str_put(&ctx->path, ctx->ref_prefix, ctx->ref_prefix_len); + + fsit_opts.flags = ctx->backend->iterator_flags; + fsit_opts.oid_type = ctx->backend->oid_type; + + if ((error = git_iterator_for_filesystem(&fsit, ctx->path.ptr, &fsit_opts)) < 0) { + /* + * Subdirectories - either glob provided or per-worktree refs - need + * not exist. + */ + if ((worktree || ctx->iter->glob) && error == GIT_ENOTFOUND) + error = 0; - if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { - git_str_dispose(&path); - return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; + goto done; } - error = git_str_sets(&path, ref_prefix); + git_str_clear(&ctx->ref_name); + git_str_put(&ctx->ref_name, ctx->ref_prefix, ctx->ref_prefix_len); - while (!error && !git_iterator_advance(&entry, fsit)) { - const char *ref_name; + while (git_iterator_advance(&entry, fsit) == 0) { char *ref_dup; - git_str_truncate(&path, ref_prefix_len); - git_str_puts(&path, entry->path); - ref_name = git_str_cstr(&path); + git_str_truncate(&ctx->ref_name, ctx->ref_prefix_len); + git_str_puts(&ctx->ref_name, entry->path); - if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) + if (worktree) { + if (!is_per_worktree_ref(ctx->ref_name.ptr)) + continue; + } else { + if (git_repository_is_worktree(ctx->backend->repo) && + is_per_worktree_ref(ctx->ref_name.ptr)) + continue; + } + + if (git__suffixcmp(ctx->ref_name.ptr, ".lock") == 0) continue; - ref_dup = git_pool_strdup(&iter->pool, ref_name); - if (!ref_dup) - error = -1; - else - error = git_vector_insert(&iter->loose, ref_dup); + if (ctx->iter->glob && wildmatch(ctx->iter->glob, ctx->ref_name.ptr, 0)) + continue; + + ref_dup = git_pool_strdup(&ctx->iter->pool, ctx->ref_name.ptr); + GIT_ERROR_CHECK_ALLOC(ref_dup); + + if ((error = git_vector_insert(&ctx->iter->loose, ref_dup)) < 0) + goto done; } +done: git_iterator_free(fsit); - git_str_dispose(&path); + return error; +} +#define iter_load_context_init(b, i) { b, i, GIT_REFS_DIR, CONST_STRLEN(GIT_REFS_DIR) } +#define iter_load_context_dispose(ctx) do { \ + git_str_dispose(&((ctx)->path)); \ + git_str_dispose(&((ctx)->ref_name)); \ +} while(0) + +static int iter_load_loose_paths( + refdb_fs_backend *backend, + refdb_fs_iter *iter) +{ + struct iter_load_context ctx = iter_load_context_init(backend, iter); + + int error = 0; + + if (!backend->commonpath) + return 0; + + iter_load_optimize_prefix(&ctx); + + if ((error = iter_load_paths(&ctx, + backend->commonpath, false)) < 0) + goto done; + + if (git_repository_is_worktree(backend->repo)) { + if ((error = iter_load_paths(&ctx, + backend->gitpath, true)) < 0) + goto done; + } + +done: + iter_load_context_dispose(&ctx); return error; } diff --git a/src/libgit2/reflog.c b/src/libgit2/reflog.c index 86d4355e..b9981729 100644 --- a/src/libgit2/reflog.c +++ b/src/libgit2/reflog.c @@ -13,7 +13,6 @@ #include "refdb.h" #include "git2/sys/refdb_backend.h" -#include "git2/sys/reflog.h" void git_reflog_entry__free(git_reflog_entry *entry) { @@ -40,7 +39,7 @@ void git_reflog_free(git_reflog *reflog) git_reflog_entry__free(entry); } - git_vector_free(&reflog->entries); + git_vector_dispose(&reflog->entries); git__free(reflog->ref_name); git__free(reflog); } diff --git a/src/libgit2/reflog.h b/src/libgit2/reflog.h index bc987859..ab3afdf1 100644 --- a/src/libgit2/reflog.h +++ b/src/libgit2/reflog.h @@ -37,4 +37,6 @@ GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) return (total - 1) - idx; } +void git_reflog_entry__free(git_reflog_entry *entry); + #endif diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c index 5e2fe97e..12dbd7ac 100644 --- a/src/libgit2/refs.c +++ b/src/libgit2/refs.c @@ -808,7 +808,7 @@ int git_reference_list( if (git_reference_foreach_name( repo, &cb__reflist_add, (void *)&ref_list) < 0) { - git_vector_free(&ref_list); + git_vector_dispose(&ref_list); return -1; } @@ -835,17 +835,20 @@ static int is_valid_ref_char(char ch) } } -static int ensure_segment_validity(const char *name, char may_contain_glob) +static int ensure_segment_validity(const char *name, char may_contain_glob, bool allow_caret_prefix) { const char *current = name; + const char *start = current; char prev = '\0'; const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); int segment_len; if (*current == '.') return -1; /* Refname starts with "." */ + if (allow_caret_prefix && *current == '^') + start++; - for (current = name; ; current++) { + for (current = start; ; current++) { if (*current == '\0' || *current == '/') break; @@ -877,7 +880,7 @@ static int ensure_segment_validity(const char *name, char may_contain_glob) return segment_len; } -static bool is_all_caps_and_underscore(const char *name, size_t len) +static bool is_valid_normalized_name(const char *name, size_t len) { size_t i; char c; @@ -888,6 +891,9 @@ static bool is_all_caps_and_underscore(const char *name, size_t len) for (i = 0; i < len; i++) { c = name[i]; + if (i == 0 && c == '^') + continue; /* The first character is allowed to be "^" for negative refspecs */ + if ((c < 'A' || c > 'Z') && c != '_') return false; } @@ -908,6 +914,7 @@ int git_reference__normalize_name( int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; unsigned int process_flags; bool normalize = (buf != NULL); + bool allow_caret_prefix = true; bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0; #ifdef GIT_USE_ICONV @@ -945,7 +952,7 @@ int git_reference__normalize_name( while (true) { char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; - segment_len = ensure_segment_validity(current, may_contain_glob); + segment_len = ensure_segment_validity(current, may_contain_glob, allow_caret_prefix); if (segment_len < 0) goto cleanup; @@ -981,6 +988,12 @@ int git_reference__normalize_name( break; current += segment_len + 1; + + /* + * A caret prefix is only allowed in the first segment to signify a + * negative refspec. + */ + allow_caret_prefix = false; } /* A refname can not be empty */ @@ -1000,12 +1013,12 @@ int git_reference__normalize_name( if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) && - !(is_all_caps_and_underscore(name, (size_t)segment_len) || + !(is_valid_normalized_name(name, (size_t)segment_len) || ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) goto cleanup; if ((segments_count > 1) - && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) + && (is_valid_normalized_name(name, strchr(name, '/') - name))) goto cleanup; error = 0; @@ -1080,6 +1093,12 @@ int git_reference_cmp( return git_oid__cmp(&ref1->target.oid, &ref2->target.oid); } +int git_reference__cmp_cb(const void *a, const void *b) +{ + return git_reference_cmp( + (const git_reference *)a, (const git_reference *)b); +} + /* * Starting with the reference given by `ref_name`, follows symbolic * references until a direct reference is found and updated the OID diff --git a/src/libgit2/refs.h b/src/libgit2/refs.h index cb888bf8..a06965b6 100644 --- a/src/libgit2/refs.h +++ b/src/libgit2/refs.h @@ -12,7 +12,6 @@ #include "git2/oid.h" #include "git2/refs.h" #include "git2/refdb.h" -#include "strmap.h" #include "str.h" #include "oid.h" @@ -92,6 +91,12 @@ int git_reference__is_tag(const char *ref_name); int git_reference__is_note(const char *ref_name); const char *git_reference__shorthand(const char *name); +/* + * A `git_reference_cmp` wrapper suitable for passing to generic + * comparators, like `vector_cmp` / `tsort` / etc. + */ +int git_reference__cmp_cb(const void *a, const void *b); + /** * Lookup a reference by name and try to resolve to an OID. * diff --git a/src/libgit2/refspec.c b/src/libgit2/refspec.c index f0a0c2bf..7ce8ef4b 100644 --- a/src/libgit2/refspec.c +++ b/src/libgit2/refspec.c @@ -22,6 +22,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) const char *lhs, *rhs; int valid = 0; unsigned int flags; + bool is_neg_refspec = false; GIT_ASSERT_ARG(refspec); GIT_ASSERT_ARG(input); @@ -34,6 +35,9 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) refspec->force = 1; lhs++; } + if (*lhs == '^') { + is_neg_refspec = true; + } rhs = strrchr(lhs, ':'); @@ -62,7 +66,14 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); if (1 <= llen && memchr(lhs, '*', llen)) { - if ((rhs && !is_glob) || (!rhs && is_fetch)) + /* + * If the lefthand side contains a glob, then one of the following must be + * true, otherwise the spec is invalid + * 1) the rhs exists and also contains a glob + * 2) it is a negative refspec (i.e. no rhs) + * 3) the rhs doesn't exist and we're fetching + */ + if ((rhs && !is_glob) || (rhs && is_neg_refspec) || (!rhs && is_fetch && !is_neg_refspec)) goto invalid; is_glob = 1; } else if (rhs && is_glob) @@ -225,6 +236,14 @@ int git_refspec_force(const git_refspec *refspec) return refspec->force; } +int git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->src == NULL || !git_refspec_is_negative(refspec)) + return false; + + return (wildmatch(refspec->src + 1, refname, 0) == 0); +} + int git_refspec_src_matches(const git_refspec *refspec, const char *refname) { if (refspec == NULL || refspec->src == NULL) @@ -340,6 +359,14 @@ int git_refspec_is_wildcard(const git_refspec *spec) return (spec->src[strlen(spec->src) - 1] == '*'); } +int git_refspec_is_negative(const git_refspec *spec) +{ + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(spec->src); + + return (spec->src[0] == '^' && spec->dst == NULL); +} + git_direction git_refspec_direction(const git_refspec *spec) { GIT_ASSERT_ARG(spec); diff --git a/src/libgit2/refspec.h b/src/libgit2/refspec.h index bf4f7fcf..37612216 100644 --- a/src/libgit2/refspec.h +++ b/src/libgit2/refspec.h @@ -45,6 +45,14 @@ int git_refspec__serialize(git_str *out, const git_refspec *refspec); */ int git_refspec_is_wildcard(const git_refspec *spec); +/** + * Determines if a refspec is a negative refspec. + * + * @param spec the refspec + * @return 1 if the refspec is a negative, 0 otherwise + */ +int git_refspec_is_negative(const git_refspec *spec); + /** * DWIM `spec` with `refs` existing on the remote, append the dwim'ed * result in `out`. diff --git a/src/libgit2/remote.c b/src/libgit2/remote.c index fee2a7f3..92c5043b 100644 --- a/src/libgit2/remote.c +++ b/src/libgit2/remote.c @@ -1293,9 +1293,9 @@ static int git_remote__download( free_refspecs(&remote->active_refspecs); error = dwim_refspecs(&remote->active_refspecs, to_active, &refs); - git_vector_free(&refs); + git_vector_dispose(&refs); free_refspecs(&specs); - git_vector_free(&specs); + git_vector_dispose(&specs); if (error < 0) goto on_error; @@ -1311,9 +1311,9 @@ static int git_remote__download( error = git_fetch_download_pack(remote); on_error: - git_vector_free(&refs); + git_vector_dispose(&refs); free_refspecs(&specs); - git_vector_free(&specs); + git_vector_dispose(&specs); return error; } @@ -1339,7 +1339,11 @@ int git_remote_download( if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) return error; - return git_remote__download(remote, refspecs, opts); + error = git_remote__download(remote, refspecs, opts); + + git_remote_connect_options_dispose(&connect_opts); + + return error; } int git_remote_fetch( @@ -1348,13 +1352,14 @@ int git_remote_fetch( const git_fetch_options *opts, const char *reflog_message) { - int error, update_fetchhead = 1; git_remote_autotag_option_t tagopt = remote->download_tags; bool prune = false; git_str reflog_msg_buf = GIT_STR_INIT; git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; unsigned int capabilities; git_oid_t oid_type; + unsigned int update_flags = GIT_REMOTE_UPDATE_FETCHHEAD; + int error; GIT_ASSERT_ARG(remote); @@ -1371,7 +1376,7 @@ int git_remote_fetch( return error; if (opts) { - update_fetchhead = opts->update_fetchhead; + update_flags = opts->update_fetchhead; tagopt = opts->download_tags; } @@ -1398,8 +1403,14 @@ int git_remote_fetch( } /* Create "remote/foo" branches for all remote branches */ - error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); + error = git_remote_update_tips(remote, + &connect_opts.callbacks, + update_flags, + tagopt, + git_str_cstr(&reflog_msg_buf)); + git_str_dispose(&reflog_msg_buf); + if (error < 0) goto done; @@ -1578,7 +1589,7 @@ static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git for (i = 0; i < fetchhead_refs.length; ++i) git_fetchhead_ref_free(fetchhead_refs.contents[i]); - git_vector_free(&fetchhead_refs); + git_vector_dispose(&fetchhead_refs); git_reference_free(head_ref); return error; @@ -1713,19 +1724,28 @@ int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks) git_oid_cpy(&id, git_reference_target(ref)); error = git_reference_delete(ref); git_reference_free(ref); + if (error < 0) goto cleanup; - if (callbacks && callbacks->update_tips) - error = callbacks->update_tips(refname, &id, &zero_id, callbacks->payload); + if (callbacks && callbacks->update_refs) + error = callbacks->update_refs(refname, &id, + &zero_id, NULL, callbacks->payload); +#ifndef GIT_DEPRECATE_HARD + else if (callbacks && callbacks->update_tips) + error = callbacks->update_tips(refname, &id, + &zero_id, callbacks->payload); +#endif - if (error < 0) + if (error < 0) { + git_error_set_after_callback_function(error, "git_remote_fetch"); goto cleanup; + } } cleanup: - git_vector_free(&remote_refs); - git_vector_free_deep(&candidates); + git_vector_dispose(&remote_refs); + git_vector_dispose_deep(&candidates); return error; } @@ -1733,6 +1753,7 @@ static int update_ref( const git_remote *remote, const char *ref_name, git_oid *id, + git_refspec *spec, const char *msg, const git_remote_callbacks *callbacks) { @@ -1761,9 +1782,19 @@ static int update_ref( if (error < 0) return error; - if (callbacks && callbacks->update_tips && - (error = callbacks->update_tips(ref_name, &old_id, id, callbacks->payload)) < 0) + if (callbacks && callbacks->update_refs) + error = callbacks->update_refs(ref_name, &old_id, + id, spec, callbacks->payload); +#ifndef GIT_DEPRECATE_HARD + else if (callbacks && callbacks->update_tips) + error = callbacks->update_tips(ref_name, &old_id, + id, callbacks->payload); +#endif + + if (error < 0) { + git_error_set_after_callback_function(error, "git_remote_fetch"); return error; + } return 0; } @@ -1774,6 +1805,7 @@ static int update_one_tip( git_refspec *spec, git_remote_head *head, git_refspec *tagspec, + unsigned int update_flags, git_remote_autotag_option_t tagopt, const char *log_message, const git_remote_callbacks *callbacks) @@ -1781,7 +1813,7 @@ static int update_one_tip( git_odb *odb; git_str refname = GIT_STR_INIT; git_reference *ref = NULL; - bool autotag = false; + bool autotag = false, updated = false; git_oid old; int valid; int error; @@ -1855,22 +1887,33 @@ static int update_one_tip( goto done; } - if (!git_oid__cmp(&old, &head->oid)) - goto done; + if ((updated = !git_oid_equal(&old, &head->oid))) { + /* In autotag mode, don't overwrite any locally-existing tags */ + error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, + log_message); - /* In autotag mode, don't overwrite any locally-existing tags */ - error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, - log_message); + if (error < 0) { + if (error == GIT_EEXISTS) + error = 0; - if (error < 0) { - if (error == GIT_EEXISTS) - error = 0; + goto done; + } + } + if (!callbacks || + (!updated && (update_flags & GIT_REMOTE_UPDATE_REPORT_UNCHANGED) == 0)) goto done; - } - if (callbacks && callbacks->update_tips != NULL && - (error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0) + if (callbacks && callbacks->update_refs) + error = callbacks->update_refs(refname.ptr, &old, + &head->oid, spec, callbacks->payload); +#ifndef GIT_DEPRECATE_HARD + else if (callbacks && callbacks->update_tips) + error = callbacks->update_tips(refname.ptr, &old, + &head->oid, callbacks->payload); +#endif + + if (error < 0) git_error_set_after_callback_function(error, "git_remote_fetch"); done: @@ -1882,7 +1925,7 @@ static int update_one_tip( static int update_tips_for_spec( git_remote *remote, const git_remote_callbacks *callbacks, - int update_fetchhead, + unsigned int update_flags, git_remote_autotag_option_t tagopt, git_refspec *spec, git_vector *refs, @@ -1905,7 +1948,10 @@ static int update_tips_for_spec( /* Update tips based on the remote heads */ git_vector_foreach(refs, i, head) { - if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0) + if (update_one_tip(&update_heads, + remote, spec, head, &tagspec, + update_flags, tagopt, log_message, + callbacks) < 0) goto on_error; } @@ -1917,7 +1963,7 @@ static int update_tips_for_spec( goto on_error; if (spec->dst && - (error = update_ref(remote, spec->dst, &id, log_message, callbacks)) < 0) + (error = update_ref(remote, spec->dst, &id, spec, log_message, callbacks)) < 0) goto on_error; git_oid_cpy(&oid_head.oid, &id); @@ -1927,17 +1973,17 @@ static int update_tips_for_spec( goto on_error; } - if (update_fetchhead && + if ((update_flags & GIT_REMOTE_UPDATE_FETCHHEAD) && (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) goto on_error; git_refspec__dispose(&tagspec); - git_vector_free(&update_heads); + git_vector_dispose(&update_heads); return 0; on_error: git_refspec__dispose(&tagspec); - git_vector_free(&update_heads); + git_vector_dispose(&update_heads); return -1; } @@ -2029,7 +2075,7 @@ static int opportunistic_updates( git_str_clear(&refname); if ((error = git_refspec__transform(&refname, spec, head->name)) < 0 || - (error = update_ref(remote, refname.ptr, &head->oid, msg, callbacks)) < 0) + (error = update_ref(remote, refname.ptr, &head->oid, spec, msg, callbacks)) < 0) goto cleanup; } @@ -2058,11 +2104,11 @@ static int truncate_fetch_head(const char *gitdir) } int git_remote_update_tips( - git_remote *remote, - const git_remote_callbacks *callbacks, - int update_fetchhead, - git_remote_autotag_option_t download_tags, - const char *reflog_message) + git_remote *remote, + const git_remote_callbacks *callbacks, + unsigned int update_flags, + git_remote_autotag_option_t download_tags, + const char *reflog_message) { git_refspec *spec, tagspec; git_vector refs = GIT_VECTOR_INIT; @@ -2091,7 +2137,7 @@ int git_remote_update_tips( goto out; if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0) + if ((error = update_tips_for_spec(remote, callbacks, update_flags, tagopt, &tagspec, &refs, reflog_message)) < 0) goto out; } @@ -2099,7 +2145,7 @@ int git_remote_update_tips( if (spec->push) continue; - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0) + if ((error = update_tips_for_spec(remote, callbacks, update_flags, tagopt, spec, &refs, reflog_message)) < 0) goto out; } @@ -2108,7 +2154,7 @@ int git_remote_update_tips( error = opportunistic_updates(remote, callbacks, &refs, reflog_message); out: - git_vector_free(&refs); + git_vector_dispose(&refs); git_refspec__dispose(&tagspec); return error; } @@ -2167,19 +2213,19 @@ void git_remote_free(git_remote *remote) remote->transport = NULL; } - git_vector_free(&remote->refs); + git_vector_dispose(&remote->refs); free_refspecs(&remote->refspecs); - git_vector_free(&remote->refspecs); + git_vector_dispose(&remote->refspecs); free_refspecs(&remote->active_refspecs); - git_vector_free(&remote->active_refspecs); + git_vector_dispose(&remote->active_refspecs); free_refspecs(&remote->passive_refspecs); - git_vector_free(&remote->passive_refspecs); + git_vector_dispose(&remote->passive_refspecs); free_heads(&remote->local_heads); - git_vector_free(&remote->local_heads); + git_vector_dispose(&remote->local_heads); git_push_free(remote->push); git__free(remote->url); @@ -2222,7 +2268,7 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list); if (error < 0) { - git_vector_free_deep(&list); + git_vector_dispose_deep(&list); return error; } @@ -2503,7 +2549,7 @@ static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const git_vector_foreach(problems, i, str) git__free(str); - git_vector_free(problems); + git_vector_dispose(problems); } return error; @@ -2543,7 +2589,7 @@ int git_remote_rename(git_strarray *out, git_repository *repo, const char *name, cleanup: if (error < 0) - git_vector_free(&problem_refspecs); + git_vector_dispose(&problem_refspecs); git_remote_free(remote); return error; @@ -2582,17 +2628,21 @@ int git_remote_name_is_valid(int *valid, const char *remote_name) git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) { git_refspec *spec; + git_refspec *match = NULL; size_t i; git_vector_foreach(&remote->active_refspecs, i, spec) { if (spec->push) continue; - if (git_refspec_src_matches(spec, refname)) - return spec; + if (git_refspec_src_matches_negative(spec, refname)) + return NULL; + + if (git_refspec_src_matches(spec, refname) && match == NULL) + match = spec; } - return NULL; + return match; } git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) @@ -2650,7 +2700,7 @@ static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned return 0; on_error: - git_vector_free_deep(&refspecs); + git_vector_dispose_deep(&refspecs); return -1; } @@ -2798,7 +2848,7 @@ static int remove_refs(git_repository *repo, const git_refspec *spec) git_vector_foreach(&refs, i, dup) { git__free(dup); } - git_vector_free(&refs); + git_vector_dispose(&refs); return error; } @@ -2967,12 +3017,22 @@ int git_remote_upload( } } - if ((error = git_push_finish(push)) < 0) - goto cleanup; + if (opts && opts->remote_push_options.count > 0) + for (i = 0; i < opts->remote_push_options.count; ++i) { + char *optstr = git__strdup(opts->remote_push_options.strings[i]); + GIT_ERROR_CHECK_ALLOC(optstr); - if (connect_opts.callbacks.push_update_reference && - (error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload)) < 0) - goto cleanup; + if ((error = git_vector_insert(&push->remote_push_options, optstr)) < 0) + goto cleanup; + } + + error = git_push_finish(push); + + if (connect_opts.callbacks.push_update_reference) { + const int cb_error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload); + if (!error) + error = cb_error; + } cleanup: git_remote_connect_options_dispose(&connect_opts); diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 05ece6ef..73876424 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -34,7 +34,6 @@ #include "submodule.h" #include "worktree.h" #include "path.h" -#include "strmap.h" #ifdef GIT_WIN32 # include "win32/w32_util.h" @@ -62,7 +61,8 @@ static const struct { { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true }, { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true }, { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "modules", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true } + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", false } }; static int check_repositoryformatversion(int *version, git_config *config); @@ -328,19 +328,34 @@ static git_repository *repository_alloc(void) return NULL; } -int git_repository_new(git_repository **out) +int git_repository__new(git_repository **out, git_oid_t oid_type) { git_repository *repo; *out = repo = repository_alloc(); GIT_ERROR_CHECK_ALLOC(repo); + GIT_ASSERT_ARG(git_oid_type_is_valid(oid_type)); + repo->is_bare = 1; repo->is_worktree = 0; + repo->oid_type = oid_type; return 0; } +#ifdef GIT_EXPERIMENTAL_SHA256 +int git_repository_new(git_repository **out, git_repository_new_options *opts) +{ + return git_repository__new(out, opts && opts->oid_type ? opts->oid_type : GIT_OID_DEFAULT); +} +#else +int git_repository_new(git_repository** out) +{ + return git_repository__new(out, GIT_OID_SHA1); +} +#endif + static int load_config_data(git_repository *repo, const git_config *config) { int is_bare; @@ -545,25 +560,39 @@ typedef struct { static int validate_ownership_cb(const git_config_entry *entry, void *payload) { validate_ownership_data *data = payload; + const char *test_path; if (strcmp(entry->value, "") == 0) { *data->is_safe = false; } else if (strcmp(entry->value, "*") == 0) { *data->is_safe = true; } else { - const char *test_path = entry->value; + if (git_str_sets(&data->tmp, entry->value) < 0) + return -1; + + if (!git_fs_path_is_root(data->tmp.ptr)) { + /* Input must not have trailing backslash. */ + if (!data->tmp.size || + data->tmp.ptr[data->tmp.size - 1] == '/') + return 0; + + if (git_fs_path_to_dir(&data->tmp) < 0) + return -1; + } + + test_path = data->tmp.ptr; -#ifdef GIT_WIN32 /* - * Git for Windows does some truly bizarre things with - * paths that start with a forward slash; and expects you - * to escape that with `%(prefix)`. This syntax generally - * means to add the prefix that Git was installed to -- eg - * `/usr/local` -- unless it's an absolute path, in which - * case the leading `%(prefix)/` is just removed. And Git - * for Windows expects you to use this syntax for absolute - * Unix-style paths (in "Git Bash" or Windows Subsystem for - * Linux). + * Git - and especially, Git for Windows - does some + * truly bizarre things with paths that start with a + * forward slash; and expects you to escape that with + * `%(prefix)`. This syntax generally means to add the + * prefix that Git was installed to (eg `/usr/local`) + * unless it's an absolute path, in which case the + * leading `%(prefix)/` is just removed. And Git for + * Windows expects you to use this syntax for absolute + * Unix-style paths (in "Git Bash" or Windows Subsystem + * for Linux). * * Worse, the behavior used to be that a leading `/` was * not absolute. It would indicate that Git for Windows @@ -578,13 +607,8 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) */ if (strncmp(test_path, "%(prefix)//", strlen("%(prefix)//")) == 0) test_path += strlen("%(prefix)/"); - else if (strncmp(test_path, "//", 2) == 0 && - strncmp(test_path, "//wsl.localhost/", strlen("//wsl.localhost/")) != 0) - test_path++; -#endif - if (git_fs_path_prettify_dir(&data->tmp, test_path, NULL) == 0 && - strcmp(data->tmp.ptr, data->repo_path) == 0) + if (strcmp(test_path, data->repo_path) == 0) *data->is_safe = true; } @@ -681,9 +705,12 @@ static int validate_ownership(git_repository *repo) goto done; if (!is_safe) { + size_t path_len = git_fs_path_is_root(path) ? + strlen(path) : git_fs_path_dirlen(path); + git_error_set(GIT_ERROR_CONFIG, - "repository path '%s' is not owned by current user", - path); + "repository path '%.*s' is not owned by current user", + (int)min(path_len, INT_MAX), path); error = GIT_EOWNER; } @@ -852,8 +879,30 @@ static int load_grafts(git_repository *repo) git_str path = GIT_STR_INIT; int error; - if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = git_str_joinpath(&path, path.ptr, "grafts")) < 0 || + /* refresh if they've both been opened previously */ + if (repo->grafts && repo->shallow_grafts) { + if ((error = git_grafts_refresh(repo->grafts)) < 0 || + (error = git_grafts_refresh(repo->shallow_grafts)) < 0) + return error; + } + + /* resolve info path, which may not be found for inmemory repository */ + if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0) { + if (error != GIT_ENOTFOUND) + return error; + + /* create empty/inmemory grafts for inmemory repository */ + if (!repo->grafts && (error = git_grafts_new(&repo->grafts, repo->oid_type)) < 0) + return error; + + if (!repo->shallow_grafts && (error = git_grafts_new(&repo->shallow_grafts, repo->oid_type)) < 0) + return error; + + return 0; + } + + /* load grafts from disk */ + if ((error = git_str_joinpath(&path, path.ptr, "grafts")) < 0 || (error = git_grafts_open_or_refresh(&repo->grafts, path.ptr, repo->oid_type)) < 0) goto error; @@ -1174,17 +1223,15 @@ int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *w return err; } -int git_repository__wrap_odb( - git_repository **out, - git_odb *odb, - git_oid_t oid_type) +int git_repository_wrap_odb(git_repository **out, git_odb *odb) { git_repository *repo; repo = repository_alloc(); GIT_ERROR_CHECK_ALLOC(repo); - repo->oid_type = oid_type; + GIT_ASSERT(git_oid_type_is_valid(odb->options.oid_type)); + repo->oid_type = odb->options.oid_type; git_repository_set_odb(repo, odb); *out = repo; @@ -1192,21 +1239,6 @@ int git_repository__wrap_odb( return 0; } -#ifdef GIT_EXPERIMENTAL_SHA256 -int git_repository_wrap_odb( - git_repository **out, - git_odb *odb, - git_oid_t oid_type) -{ - return git_repository__wrap_odb(out, odb, oid_type); -} -#else -int git_repository_wrap_odb(git_repository **out, git_odb *odb) -{ - return git_repository__wrap_odb(out, odb, GIT_OID_DEFAULT); -} -#endif - int git_repository_discover( git_buf *out, const char *start_path, @@ -1226,6 +1258,24 @@ int git_repository_discover( return error; } +static int has_config_worktree(bool *out, git_config *cfg) +{ + int worktreeconfig = 0, error; + + *out = false; + + error = git_config_get_bool(&worktreeconfig, cfg, "extensions.worktreeconfig"); + + if (error == 0) + *out = worktreeconfig; + else if (error == GIT_ENOTFOUND) + *out = false; + else + return error; + + return 0; +} + static int load_config( git_config **out, git_repository *repo, @@ -1234,9 +1284,11 @@ static int load_config( const char *system_config_path, const char *programdata_path) { - int error; git_str config_path = GIT_STR_INIT; git_config *cfg = NULL; + git_config_level_t write_order; + bool has_worktree; + int error; GIT_ASSERT_ARG(out); @@ -1250,6 +1302,14 @@ static int load_config( if (error && error != GIT_ENOTFOUND) goto on_error; + if ((error = has_config_worktree(&has_worktree, cfg)) == 0 && + has_worktree && + (error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0); + + if (error && error != GIT_ENOTFOUND) + goto on_error; + git_str_dispose(&config_path); } @@ -1279,6 +1339,11 @@ static int load_config( git_error_clear(); /* clear any lingering ENOTFOUND errors */ + write_order = GIT_CONFIG_LEVEL_LOCAL; + + if ((error = git_config_set_writeorder(cfg, &write_order, 1)) < 0) + goto on_error; + *out = cfg; return 0; @@ -1798,7 +1863,9 @@ static int check_repositoryformatversion(int *version, git_config *config) static const char *builtin_extensions[] = { "noop", - "objectformat" + "objectformat", + "worktreeconfig", + "preciousobjects" }; static git_vector user_extensions = { 0, git__strcmp_cb }; @@ -2024,7 +2091,7 @@ int git_repository__set_extensions(const char **extensions, size_t len) void git_repository__free_extensions(void) { - git_vector_free_deep(&user_extensions); + git_vector_dispose_deep(&user_extensions); } int git_repository_create_head(const char *git_dir, const char *ref_name) @@ -2439,7 +2506,7 @@ static int repo_write_gitlink( error = git_fs_path_make_relative(&path_to_repo, in_dir); if (!error) - error = git_str_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); + error = git_str_printf(&buf, "%s %s\n", GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); if (!error) error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); @@ -2471,7 +2538,8 @@ static int repo_init_structure( int error = 0; repo_template_item *tpl; bool external_tpl = - ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); + opts->template_path != NULL || + (opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0; mode_t dmode = pick_dir_mode(opts); bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; @@ -2630,6 +2698,8 @@ static int repo_init_directories( if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) return -1; + git_fs_path_mkposix(repo_path->ptr); + has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); if (has_dotgit) opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; @@ -3197,14 +3267,18 @@ int git_repository_set_workdir( if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) return -1; - if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) { + git_str_dispose(&path); return 0; + } if (update_gitlink) { git_config *config; - if (git_repository_config__weakptr(&config, repo) < 0) + if (git_repository_config__weakptr(&config, repo) < 0) { + git_str_dispose(&path); return -1; + } error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); @@ -3226,6 +3300,7 @@ int git_repository_set_workdir( git__free(old_workdir); } + git_str_dispose(&path); return error; } @@ -3268,6 +3343,25 @@ int git_repository_set_bare(git_repository *repo) return 0; } +int git_repository_head_commit(git_commit **commit, git_repository *repo) +{ + git_reference *head; + git_object *obj; + int error; + + if ((error = git_repository_head(&head, repo)) < 0) + return error; + + if ((error = git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + *commit = (git_commit *)obj; + +cleanup: + git_reference_free(head); + return error; +} + int git_repository_head_tree(git_tree **tree, git_repository *repo) { git_reference *head; @@ -3839,3 +3933,89 @@ git_oid_t git_repository_oid_type(git_repository *repo) { return repo ? repo->oid_type : 0; } + +struct mergehead_data { + git_repository *repo; + git_vector *parents; +}; + +static int insert_mergehead(const git_oid *oid, void *payload) +{ + git_commit *commit; + struct mergehead_data *data = (struct mergehead_data *)payload; + + if (git_commit_lookup(&commit, data->repo, oid) < 0) + return -1; + + return git_vector_insert(data->parents, commit); +} + +int git_repository_commit_parents(git_commitarray *out, git_repository *repo) +{ + git_commit *first_parent = NULL, *commit; + git_reference *head_ref = NULL; + git_vector parents = GIT_VECTOR_INIT; + struct mergehead_data data; + size_t i; + int error; + + GIT_ASSERT_ARG(out && repo); + + out->count = 0; + out->commits = NULL; + + error = git_revparse_ext((git_object **)&first_parent, &head_ref, repo, "HEAD"); + + if (error != 0) { + if (error == GIT_ENOTFOUND) + error = 0; + + goto done; + } + + if ((error = git_vector_insert(&parents, first_parent)) < 0) + goto done; + + data.repo = repo; + data.parents = &parents; + + error = git_repository_mergehead_foreach(repo, insert_mergehead, &data); + + if (error == GIT_ENOTFOUND) + error = 0; + else if (error != 0) + goto done; + + out->commits = (git_commit **)git_vector_detach(&out->count, NULL, &parents); + +done: + git_vector_foreach(&parents, i, commit) + git__free(commit); + + git_reference_free(head_ref); + return error; +} + +int git_repository__abbrev_length(int *out, git_repository *repo) +{ + size_t oid_hexsize; + int len; + int error; + + oid_hexsize = git_oid_hexsize(repo->oid_type); + + if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) + return error; + + if (len < GIT_ABBREV_MINIMUM) { + git_error_set(GIT_ERROR_CONFIG, "invalid oid abbreviation setting: '%d'", len); + return -1; + } + + if (len == GIT_ABBREV_FALSE || (size_t)len > oid_hexsize) + len = (int)oid_hexsize; + + *out = len; + + return error; +} diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h index 6d2b64c0..fbf14389 100644 --- a/src/libgit2/repository.h +++ b/src/libgit2/repository.h @@ -102,6 +102,8 @@ typedef enum { /* core.trustctime */ GIT_TRUSTCTIME_DEFAULT = GIT_CONFIGMAP_TRUE, /* core.abbrev */ + GIT_ABBREV_FALSE = GIT_OID_MAX_HEXSIZE, + GIT_ABBREV_MINIMUM = 4, GIT_ABBREV_DEFAULT = 7, /* core.precomposeunicode */ GIT_PRECOMPOSE_DEFAULT = GIT_CONFIGMAP_FALSE, @@ -165,7 +167,7 @@ struct git_repository { git_atomic32 attr_session_key; intptr_t configmap_cache[GIT_CONFIGMAP_CACHE_MAX]; - git_strmap *submodule_cache; + git_submodule_cache *submodule_cache; }; GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) @@ -173,6 +175,7 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) return repo->attrcache; } +int git_repository_head_commit(git_commit **commit, git_repository *repo); int git_repository_head_tree(git_tree **tree, git_repository *repo); int git_repository_create_head(const char *git_dir, const char *ref_name); @@ -196,11 +199,6 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo); int git_repository_grafts__weakptr(git_grafts **out, git_repository *repo); int git_repository_shallow_grafts__weakptr(git_grafts **out, git_repository *repo); -int git_repository__wrap_odb( - git_repository **out, - git_odb *odb, - git_oid_t oid_type); - /* * Configuration map cache * @@ -210,6 +208,9 @@ int git_repository__wrap_odb( int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item); void git_repository__configmap_lookup_cache_clear(git_repository *repo); +/** Return the length that object names will be abbreviated to. */ +int git_repository__abbrev_length(int *out, git_repository *repo); + int git_repository__item_path(git_str *out, const git_repository *repo, git_repository_item_t item); GIT_INLINE(int) git_repository__ensure_not_bare( @@ -280,4 +281,7 @@ int git_repository__set_objectformat( git_repository *repo, git_oid_t oid_type); +/* SHA256-aware internal functions */ +int git_repository__new(git_repository **out, git_oid_t oid_type); + #endif diff --git a/src/libgit2/revert.c b/src/libgit2/revert.c index 4a31ad40..2fb53f8f 100644 --- a/src/libgit2/revert.c +++ b/src/libgit2/revert.c @@ -74,8 +74,7 @@ static int revert_normalize_opts( const char *their_label) { int error = 0; - unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; + unsigned int default_checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS; GIT_UNUSED(repo); diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c index 06d92f82..9083e7a3 100644 --- a/src/libgit2/revparse.c +++ b/src/libgit2/revparse.c @@ -816,7 +816,7 @@ static int revparse( if (temp_object != NULL) base_rev = temp_object; break; - } else if (spec[pos+1] == '\0') { + } else if (spec[pos + 1] == '\0' && !pos) { spec = "HEAD"; identifier_len = 4; parsed = true; @@ -935,7 +935,7 @@ int git_revparse( * allowed. */ if (!git__strcmp(spec, "..")) { - git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); + git_error_set(GIT_ERROR_INVALID, "invalid pattern '..'"); return GIT_EINVALIDSPEC; } diff --git a/src/libgit2/revwalk.c b/src/libgit2/revwalk.c index 4ea6fae8..a793a8e1 100644 --- a/src/libgit2/revwalk.c +++ b/src/libgit2/revwalk.c @@ -14,6 +14,9 @@ #include "git2/revparse.h" #include "merge.h" #include "vector.h" +#include "hashmap_oid.h" + +GIT_HASHMAP_OID_FUNCTIONS(git_revwalk_oidmap, GIT_HASHMAP_INLINE, git_commit_list_node *); static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list); @@ -23,7 +26,7 @@ git_commit_list_node *git_revwalk__commit_lookup( git_commit_list_node *commit; /* lookup and reserve space if not already present */ - if ((commit = git_oidmap_get(walk->commits, oid)) != NULL) + if (git_revwalk_oidmap_get(&commit, &walk->commits, oid) == 0) return commit; commit = git_commit_list_alloc_node(walk); @@ -32,7 +35,7 @@ git_commit_list_node *git_revwalk__commit_lookup( git_oid_cpy(&commit->oid, oid); - if ((git_oidmap_set(walk->commits, &commit->oid, commit)) < 0) + if (git_revwalk_oidmap_put(&walk->commits, &commit->oid, commit) < 0) return NULL; return commit; @@ -623,7 +626,7 @@ static int prepare_walk(git_revwalk *walk) return GIT_ITEROVER; } - /* + /* * This is a bit convoluted, but necessary to maintain the order of * the commits. This is especially important in situations where * git_revwalk__push_glob is called with a git_revwalk__push_options @@ -643,13 +646,13 @@ static int prepare_walk(git_revwalk *walk) git_error_set_oom(); return -1; } - + commit->seen = 1; if (commits_last == NULL) commits = new_list; else commits_last->next = new_list; - + commits_last = new_list; } } @@ -700,8 +703,7 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) git_revwalk *walk = git__calloc(1, sizeof(git_revwalk)); GIT_ERROR_CHECK_ALLOC(walk); - if (git_oidmap_new(&walk->commits) < 0 || - git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || + if (git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || git_pool_init(&walk->commit_pool, COMMIT_ALLOC) < 0) return -1; @@ -727,7 +729,7 @@ void git_revwalk_free(git_revwalk *walk) git_revwalk_reset(walk); git_odb_free(walk->odb); - git_oidmap_free(walk->commits); + git_revwalk_oidmap_dispose(&walk->commits); git_pool_clear(&walk->commit_pool); git_pqueue_free(&walk->iterator_time); git__free(walk); @@ -799,17 +801,18 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) int git_revwalk_reset(git_revwalk *walk) { git_commit_list_node *commit; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; GIT_ASSERT_ARG(walk); - git_oidmap_foreach_value(walk->commits, commit, { + while (git_revwalk_oidmap_iterate(&iter, NULL, &commit, &walk->commits) == 0) { commit->seen = 0; commit->in_degree = 0; commit->topo_delay = 0; commit->uninteresting = 0; commit->added = 0; commit->flags = 0; - }); + } git_pqueue_clear(&walk->iterator_time); git_commit_list_free(&walk->iterator_topo); diff --git a/src/libgit2/revwalk.h b/src/libgit2/revwalk.h index 94b8a6fb..632b2807 100644 --- a/src/libgit2/revwalk.h +++ b/src/libgit2/revwalk.h @@ -10,19 +10,19 @@ #include "common.h" #include "git2/revwalk.h" -#include "oidmap.h" #include "commit_list.h" #include "pqueue.h" #include "pool.h" #include "vector.h" +#include "hashmap_oid.h" -#include "oidmap.h" +GIT_HASHMAP_OID_STRUCT(git_revwalk_oidmap, git_commit_list_node *); struct git_revwalk { git_repository *repo; git_odb *odb; - git_oidmap *commits; + git_revwalk_oidmap commits; git_pool commit_pool; git_commit_list *iterator_topo; diff --git a/src/libgit2/settings.c b/src/libgit2/settings.c new file mode 100644 index 00000000..f4c2453a --- /dev/null +++ b/src/libgit2/settings.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "settings.h" + +#include +#include "alloc.h" +#include "buf.h" +#include "cache.h" +#include "common.h" +#include "filter.h" +#include "grafts.h" +#include "hash.h" +#include "index.h" +#include "merge_driver.h" +#include "pool.h" +#include "mwindow.h" +#include "object.h" +#include "odb.h" +#include "rand.h" +#include "refs.h" +#include "runtime.h" +#include "sysdir.h" +#include "thread.h" +#include "git2/global.h" +#include "streams/registry.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/socket.h" +#include "transports/smart.h" +#include "transports/http.h" +#include "transports/ssh_libssh2.h" + +#ifdef GIT_WIN32 +# include "win32/w32_leakcheck.h" +#endif + +/* Declarations for tuneable settings */ +extern size_t git_mwindow__window_size; +extern size_t git_mwindow__mapped_limit; +extern size_t git_mwindow__file_limit; +extern size_t git_indexer__max_objects; +extern bool git_disable_pack_keep_file_checks; +extern int git_odb__packed_priority; +extern int git_odb__loose_priority; +extern int git_socket_stream__connect_timeout; +extern int git_socket_stream__timeout; + +char *git__user_agent; +char *git__user_agent_product; +char *git__ssl_ciphers; + +static void settings_global_shutdown(void) +{ + git__free(git__user_agent); + git__free(git__user_agent_product); + + git__free(git__ssl_ciphers); + git_repository__free_extensions(); +} + +int git_settings_global_init(void) +{ + return git_runtime_shutdown_register(settings_global_shutdown); +} + +static int config_level_to_sysdir(int *out, int config_level) +{ + switch (config_level) { + case GIT_CONFIG_LEVEL_SYSTEM: + *out = GIT_SYSDIR_SYSTEM; + return 0; + case GIT_CONFIG_LEVEL_XDG: + *out = GIT_SYSDIR_XDG; + return 0; + case GIT_CONFIG_LEVEL_GLOBAL: + *out = GIT_SYSDIR_GLOBAL; + return 0; + case GIT_CONFIG_LEVEL_PROGRAMDATA: + *out = GIT_SYSDIR_PROGRAMDATA; + return 0; + default: + break; + } + + git_error_set( + GIT_ERROR_INVALID, "invalid config path selector %d", config_level); + return -1; +} + +const char *git_settings__user_agent_product(void) +{ + return git__user_agent_product ? git__user_agent_product : + "git/2.0"; +} + +const char *git_settings__user_agent(void) +{ + return git__user_agent ? git__user_agent : + "libgit2 " LIBGIT2_VERSION; +} + +int git_libgit2_opts(int key, ...) +{ + int error = 0; + va_list ap; + + va_start(ap, key); + + switch (key) { + case GIT_OPT_SET_MWINDOW_SIZE: + git_mwindow__window_size = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_SIZE: + *(va_arg(ap, size_t *)) = git_mwindow__window_size; + break; + + case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: + git_mwindow__mapped_limit = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: + *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; + break; + + case GIT_OPT_SET_MWINDOW_FILE_LIMIT: + git_mwindow__file_limit = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_FILE_LIMIT: + *(va_arg(ap, size_t *)) = git_mwindow__file_limit; + break; + + case GIT_OPT_GET_SEARCH_PATH: + { + int sysdir = va_arg(ap, int); + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + int level; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = config_level_to_sysdir(&level, sysdir)) < 0 || + (error = git_sysdir_get(&tmp, level)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_SEARCH_PATH: + { + int level; + + if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0) + error = git_sysdir_set(level, va_arg(ap, const char *)); + } + break; + + case GIT_OPT_SET_CACHE_OBJECT_LIMIT: + { + git_object_t type = (git_object_t)va_arg(ap, int); + size_t size = va_arg(ap, size_t); + error = git_cache_set_max_object_size(type, size); + break; + } + + case GIT_OPT_SET_CACHE_MAX_SIZE: + git_cache__max_storage = va_arg(ap, ssize_t); + break; + + case GIT_OPT_ENABLE_CACHING: + git_cache__enabled = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_CACHED_MEMORY: + *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; + *(va_arg(ap, ssize_t *)) = git_cache__max_storage; + break; + + case GIT_OPT_GET_TEMPLATE_PATH: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_TEMPLATE_PATH: + error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *)); + break; + + case GIT_OPT_SET_SSL_CERT_LOCATIONS: +#ifdef GIT_OPENSSL + { + const char *file = va_arg(ap, const char *); + const char *path = va_arg(ap, const char *); + error = git_openssl__set_cert_location(file, path); + } +#elif defined(GIT_MBEDTLS) + { + const char *file = va_arg(ap, const char *); + const char *path = va_arg(ap, const char *); + error = git_mbedtls__set_cert_location(file, path); + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations"); + error = -1; +#endif + break; + + case GIT_OPT_ADD_SSL_X509_CERT: +#ifdef GIT_OPENSSL + { + X509 *cert = va_arg(ap, X509 *); + error = git_openssl__add_x509_cert(cert); + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support adding of the raw certs"); + error = -1; +#endif + break; + + case GIT_OPT_SET_USER_AGENT: + { + const char *new_agent = va_arg(ap, const char *); + + git__free(git__user_agent); + + if (new_agent) { + git__user_agent= git__strdup(new_agent); + + if (!git__user_agent) + error = -1; + } else { + git__user_agent = NULL; + } + } + break; + + case GIT_OPT_GET_USER_AGENT: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_str_puts(&str, git_settings__user_agent())) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_USER_AGENT_PRODUCT: + { + const char *new_agent = va_arg(ap, const char *); + + git__free(git__user_agent_product); + + if (new_agent) { + git__user_agent_product = git__strdup(new_agent); + + if (!git__user_agent_product) + error = -1; + } else { + git__user_agent_product = NULL; + } + } + break; + + case GIT_OPT_GET_USER_AGENT_PRODUCT: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_str_puts(&str, git_settings__user_agent_product())) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: + git_object__strict_input_validation = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: + git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_SSL_CIPHERS: +#if (GIT_OPENSSL || GIT_MBEDTLS) + { + git__free(git__ssl_ciphers); + git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); + if (!git__ssl_ciphers) { + git_error_set_oom(); + error = -1; + } + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); + error = -1; +#endif + break; + + case GIT_OPT_ENABLE_OFS_DELTA: + git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_FSYNC_GITDIR: + git_repository__fsync_gitdir = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; +#endif + break; + + case GIT_OPT_SET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + git_win32__createfile_sharemode = va_arg(ap, unsigned long); +#endif + break; + + case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: + git_odb__strict_hash_verification = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_ALLOCATOR: + error = git_allocator_setup(va_arg(ap, git_allocator *)); + break; + + case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: + git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_PACK_MAX_OBJECTS: + git_indexer__max_objects = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_PACK_MAX_OBJECTS: + *(va_arg(ap, size_t *)) = git_indexer__max_objects; + break; + + case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: + git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: + git_http__expect_continue = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_ODB_PACKED_PRIORITY: + git_odb__packed_priority = va_arg(ap, int); + break; + + case GIT_OPT_SET_ODB_LOOSE_PRIORITY: + git_odb__loose_priority = va_arg(ap, int); + break; + + case GIT_OPT_SET_EXTENSIONS: + { + const char **extensions = va_arg(ap, const char **); + size_t len = va_arg(ap, size_t); + error = git_repository__set_extensions(extensions, len); + } + break; + + case GIT_OPT_GET_EXTENSIONS: + { + git_strarray *out = va_arg(ap, git_strarray *); + char **extensions; + size_t len; + + if ((error = git_repository__extensions(&extensions, &len)) < 0) + break; + + out->strings = extensions; + out->count = len; + } + break; + + case GIT_OPT_GET_OWNER_VALIDATION: + *(va_arg(ap, int *)) = git_repository__validate_ownership; + break; + + case GIT_OPT_SET_OWNER_VALIDATION: + git_repository__validate_ownership = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_HOMEDIR: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_sysdir_get(&tmp, GIT_SYSDIR_HOME)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_HOMEDIR: + error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *)); + break; + + case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: + *(va_arg(ap, int *)) = git_socket_stream__connect_timeout; + break; + + case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: + { + int timeout = va_arg(ap, int); + + if (timeout < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); + error = -1; + } else { + git_socket_stream__connect_timeout = timeout; + } + } + break; + + case GIT_OPT_GET_SERVER_TIMEOUT: + *(va_arg(ap, int *)) = git_socket_stream__timeout; + break; + + case GIT_OPT_SET_SERVER_TIMEOUT: + { + int timeout = va_arg(ap, int); + + if (timeout < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid timeout"); + error = -1; + } else { + git_socket_stream__timeout = timeout; + } + } + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid option key"); + error = -1; + } + + va_end(ap); + + return error; +} diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h index dc42ce93..29293667 100644 --- a/src/libgit2/settings.h +++ b/src/libgit2/settings.h @@ -4,8 +4,12 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ +#ifndef INCLUDE_settings_h__ +#define INCLUDE_settings_h__ extern int git_settings_global_init(void); -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); +extern const char *git_settings__user_agent(void); +extern const char *git_settings__user_agent_product(void); + +#endif diff --git a/src/libgit2/signature.c b/src/libgit2/signature.c index 5d6ab572..71da4162 100644 --- a/src/libgit2/signature.c +++ b/src/libgit2/signature.c @@ -10,6 +10,7 @@ #include "repository.h" #include "git2/common.h" #include "posix.h" +#include "date.h" void git_signature_free(git_signature *sig) { @@ -43,7 +44,6 @@ static bool contains_angle_brackets(const char *input) static bool is_crud(unsigned char c) { return c <= 32 || - c == '.' || c == ',' || c == ':' || c == ';' || @@ -153,15 +153,10 @@ int git_signature__pdup(git_signature **dest, const git_signature *source, git_p return 0; } -int git_signature_now(git_signature **sig_out, const char *name, const char *email) +static void current_time(time_t *now_out, int *offset_out) { - time_t now; time_t offset; - struct tm *utc_tm; - git_signature *sig; - struct tm _utc; - - *sig_out = NULL; + struct tm _utc, *utc_tm; /* * Get the current time as seconds since the epoch and @@ -171,18 +166,26 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema * us that time as seconds since the epoch. The difference * between its return value and 'now' is our offset to UTC. */ - time(&now); - utc_tm = p_gmtime_r(&now, &_utc); + time(now_out); + utc_tm = p_gmtime_r(now_out, &_utc); utc_tm->tm_isdst = -1; - offset = (time_t)difftime(now, mktime(utc_tm)); + offset = (time_t)difftime(*now_out, mktime(utc_tm)); offset /= 60; - if (git_signature_new(&sig, name, email, now, (int)offset) < 0) - return -1; + *offset_out = (int)offset; +} + +int git_signature_now( + git_signature **sig_out, + const char *name, + const char *email) +{ + time_t now; + int offset; - *sig_out = sig; + current_time(&now, &offset); - return 0; + return git_signature_new(sig_out, name, email, now, offset); } int git_signature_default(git_signature **out, git_repository *repo) @@ -202,6 +205,120 @@ int git_signature_default(git_signature **out, git_repository *repo) return error; } +static int user_from_env( + git_signature **out, + git_repository *repo, + const char *name_env_var, + const char *email_env_var, + const char *date_env_var, + time_t default_time, + int default_offset) +{ + int error; + git_config *cfg; + const char *name, *email, *date; + git_time_t timestamp; + int offset; + git_str name_env = GIT_STR_INIT; + git_str email_env = GIT_STR_INIT; + git_str date_env = GIT_STR_INIT; + + if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) + return error; + + /* Check if the environment variable for the name is set */ + if (!(git__getenv(&name_env, name_env_var))) { + name = git_str_cstr(&name_env); + } else { + /* or else read the configuration value. */ + if ((error = git_config_get_string(&name, cfg, "user.name")) < 0) + goto done; + } + + /* Check if the environment variable for the email is set. */ + if (!(git__getenv(&email_env, email_env_var))) { + email = git_str_cstr(&email_env); + } else { + if ((error = git_config_get_string(&email, cfg, "user.email")) == GIT_ENOTFOUND) { + git_error *last_error; + + git_error_save(&last_error); + + if ((error = git__getenv(&email_env, "EMAIL")) < 0) { + git_error_restore(last_error); + error = GIT_ENOTFOUND; + goto done; + } + + email = git_str_cstr(&email_env); + git_error_free(last_error); + } else if (error < 0) { + goto done; + } + } + + /* Check if the environment variable for the timestamp is set */ + if (!(git__getenv(&date_env, date_env_var))) { + date = git_str_cstr(&date_env); + + if ((error = git_date_offset_parse(×tamp, &offset, date)) < 0) + goto done; + } else { + timestamp = default_time; + offset = default_offset; + } + + error = git_signature_new(out, name, email, timestamp, offset); + +done: + git_config_free(cfg); + git_str_dispose(&name_env); + git_str_dispose(&email_env); + git_str_dispose(&date_env); + return error; +} + +int git_signature_default_from_env( + git_signature **author_out, + git_signature **committer_out, + git_repository *repo) +{ + git_signature *author = NULL, *committer = NULL; + time_t now; + int offset; + int error; + + GIT_ASSERT_ARG(author_out || committer_out); + GIT_ASSERT_ARG(repo); + + current_time(&now, &offset); + + if (author_out && + (error = user_from_env(&author, repo, "GIT_AUTHOR_NAME", + "GIT_AUTHOR_EMAIL", "GIT_AUTHOR_DATE", + now, offset)) < 0) + goto on_error; + + if (committer_out && + (error = user_from_env(&committer, repo, "GIT_COMMITTER_NAME", + "GIT_COMMITTER_EMAIL", "GIT_COMMITTER_DATE", + now, offset)) < 0) + goto on_error; + + if (author_out) + *author_out = author; + + if (committer_out) + *committer_out = committer; + + return 0; + +on_error: + git__free(author); + git__free(committer); + return error; +} + int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender) { diff --git a/src/libgit2/signature.h b/src/libgit2/signature.h index 5c827095..a5645e9b 100644 --- a/src/libgit2/signature.h +++ b/src/libgit2/signature.h @@ -17,7 +17,6 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig); bool git_signature__equal(const git_signature *one, const git_signature *two); - int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); #endif diff --git a/src/libgit2/status.c b/src/libgit2/status.c index df0f7450..06356ad8 100644 --- a/src/libgit2/status.c +++ b/src/libgit2/status.c @@ -414,7 +414,7 @@ void git_status_list_free(git_status_list *status) git_diff_free(status->head2idx); git_diff_free(status->idx2wd); - git_vector_free_deep(&status->paired); + git_vector_dispose_deep(&status->paired); git__memzero(status, sizeof(*status)); git__free(status); diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c index 49aa76c3..37abcdab 100644 --- a/src/libgit2/streams/mbedtls.c +++ b/src/libgit2/streams/mbedtls.c @@ -32,7 +32,6 @@ # endif #endif -#include #include #include #include @@ -40,12 +39,18 @@ #undef inline -#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" -#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30 +#define GIT_SSL_DEFAULT_CIPHERS "TLS1-3-AES-128-GCM-SHA256:TLS1-3-AES-256-GCM-SHA384:TLS1-3-CHACHA20-POLY1305-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256:TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" +#define GIT_SSL_DEFAULT_CIPHERS_COUNT 28 -static mbedtls_ssl_config *git__ssl_conf; static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT]; -static mbedtls_entropy_context *mbedtls_entropy; + +static bool initialized = false; +static mbedtls_ssl_config mbedtls_config; +static mbedtls_ctr_drbg_context mbedtls_rng; +static mbedtls_entropy_context mbedtls_entropy; + +static bool has_ca_chain = false; +static mbedtls_x509_crt mbedtls_ca_chain; /** * This function aims to clean-up the SSL context which @@ -53,19 +58,16 @@ static mbedtls_entropy_context *mbedtls_entropy; */ static void shutdown_ssl(void) { - if (git__ssl_conf) { - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - mbedtls_ctr_drbg_free(git__ssl_conf->p_rng); - git__free(git__ssl_conf->p_rng); - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; + if (has_ca_chain) { + mbedtls_x509_crt_free(&mbedtls_ca_chain); + has_ca_chain = false; } - if (mbedtls_entropy) { - mbedtls_entropy_free(mbedtls_entropy); - git__free(mbedtls_entropy); - mbedtls_entropy = NULL; + + if (initialized) { + mbedtls_ctr_drbg_free(&mbedtls_rng); + mbedtls_ssl_config_free(&mbedtls_config); + mbedtls_entropy_free(&mbedtls_entropy); + initialized = false; } } @@ -74,32 +76,33 @@ int git_mbedtls_stream_global_init(void) int loaded = 0; char *crtpath = GIT_DEFAULT_CERT_LOCATION; struct stat statbuf; - mbedtls_ctr_drbg_context *ctr_drbg = NULL; size_t ciphers_known = 0; char *cipher_name = NULL; char *cipher_string = NULL; char *cipher_string_tmp = NULL; - git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config)); - GIT_ERROR_CHECK_ALLOC(git__ssl_conf); + mbedtls_ssl_config_init(&mbedtls_config); + mbedtls_entropy_init(&mbedtls_entropy); + mbedtls_ctr_drbg_init(&mbedtls_rng); - mbedtls_ssl_config_init(git__ssl_conf); - if (mbedtls_ssl_config_defaults(git__ssl_conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT) != 0) { + if (mbedtls_ssl_config_defaults(&mbedtls_config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT) != 0) { git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS"); goto cleanup; } - /* configure TLSv1 */ - mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); + /* configure TLSv1.1 or better */ +#ifdef MBEDTLS_SSL_MINOR_VERSION_2 + mbedtls_ssl_conf_min_version(&mbedtls_config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_2); +#endif /* verify_server_cert is responsible for making the check. * OPTIONAL because REQUIRED drops the certificate as soon as the check * is made, so we can never see the certificate and override it. */ - mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_authmode(&mbedtls_config, MBEDTLS_SSL_VERIFY_OPTIONAL); /* set the list of allowed ciphersuites */ ciphers_known = 0; @@ -123,42 +126,33 @@ int git_mbedtls_stream_global_init(void) git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); goto cleanup; } - mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); + mbedtls_ssl_conf_ciphersuites(&mbedtls_config, ciphers_list); /* Seeding the random number generator */ - mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context)); - GIT_ERROR_CHECK_ALLOC(mbedtls_entropy); - - mbedtls_entropy_init(mbedtls_entropy); - - ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context)); - GIT_ERROR_CHECK_ALLOC(ctr_drbg); - mbedtls_ctr_drbg_init(ctr_drbg); - - if (mbedtls_ctr_drbg_seed(ctr_drbg, - mbedtls_entropy_func, - mbedtls_entropy, NULL, 0) != 0) { + if (mbedtls_ctr_drbg_seed(&mbedtls_rng, mbedtls_entropy_func, + &mbedtls_entropy, NULL, 0) != 0) { git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool"); goto cleanup; } - mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); + mbedtls_ssl_conf_rng(&mbedtls_config, mbedtls_ctr_drbg_random, &mbedtls_rng); /* load default certificates */ if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0); + if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0); + initialized = true; + return git_runtime_shutdown_register(shutdown_ssl); cleanup: - mbedtls_ctr_drbg_free(ctr_drbg); - git__free(ctr_drbg); - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; + mbedtls_ctr_drbg_free(&mbedtls_rng); + mbedtls_ssl_config_free(&mbedtls_config); + mbedtls_entropy_free(&mbedtls_entropy); return -1; } @@ -192,7 +186,7 @@ static int ssl_set_error(mbedtls_ssl_context *ssl, int error) break; case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, mbedtls_ssl_get_verify_result(ssl), errbuf); ret = GIT_ECERTIFICATE; break; @@ -374,7 +368,7 @@ static int mbedtls_stream_wrap( st->ssl = git__malloc(sizeof(mbedtls_ssl_context)); GIT_ERROR_CHECK_ALLOC(st->ssl); mbedtls_ssl_init(st->ssl); - if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) { + if (mbedtls_ssl_setup(st->ssl, &mbedtls_config)) { git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); error = -1; goto out_err; @@ -441,30 +435,30 @@ int git_mbedtls__set_cert_location(const char *file, const char *path) { int ret = 0; char errbuf[512]; - mbedtls_x509_crt *cacert; GIT_ASSERT_ARG(file || path); - cacert = git__malloc(sizeof(mbedtls_x509_crt)); - GIT_ERROR_CHECK_ALLOC(cacert); + if (has_ca_chain) + mbedtls_x509_crt_free(&mbedtls_ca_chain); + + mbedtls_x509_crt_init(&mbedtls_ca_chain); - mbedtls_x509_crt_init(cacert); if (file) - ret = mbedtls_x509_crt_parse_file(cacert, file); + ret = mbedtls_x509_crt_parse_file(&mbedtls_ca_chain, file); + if (ret >= 0 && path) - ret = mbedtls_x509_crt_parse_path(cacert, path); + ret = mbedtls_x509_crt_parse_path(&mbedtls_ca_chain, path); + /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */ if (ret < 0) { - mbedtls_x509_crt_free(cacert); - git__free(cacert); + mbedtls_x509_crt_free(&mbedtls_ca_chain); mbedtls_strerror( ret, errbuf, 512 ); git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); return -1; } - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); + mbedtls_ssl_conf_ca_chain(&mbedtls_config, &mbedtls_ca_chain, NULL); + has_ca_chain = true; return 0; } diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c index 9db911e3..e5641e0e 100644 --- a/src/libgit2/streams/openssl.c +++ b/src/libgit2/streams/openssl.c @@ -36,10 +36,11 @@ # include #endif -SSL_CTX *git__ssl_ctx; +extern char *git__ssl_ciphers; -#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" +SSL_CTX *git__ssl_ctx; +#define GIT_SSL_DEFAULT_CIPHERS "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" static BIO_METHOD *git_stream_bio_method; static int init_bio_method(void); @@ -105,7 +106,7 @@ static void git_openssl_free(void *mem) static int openssl_init(void) { long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - const char *ciphers = git_libgit2__ssl_ciphers(); + const char *ciphers = git__ssl_ciphers; #ifdef VALGRIND static bool allocators_initialized = false; #endif @@ -133,10 +134,10 @@ static int openssl_init(void) OPENSSL_init_ssl(0, NULL); /* - * Load SSLv{2,3} and TLSv1 so that we can talk with servers - * which use the SSL hellos, which are often used for - * compatibility. We then disable SSL so we only allow OpenSSL - * to speak TLSv1 to perform the encryption itself. + * Despite the name SSLv23_method, this is actually a version- + * flexible context, which honors the protocol versions + * specified in `ssl_opts`. So we only support TLSv1.0 and + * higher. */ if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method()))) goto error; @@ -720,6 +721,30 @@ int git_openssl__set_cert_location(const char *file, const char *path) return 0; } +int git_openssl__add_x509_cert(X509 *cert) +{ + X509_STORE *cert_store; + + if (openssl_ensure_initialized() < 0) + return -1; + + if (!(cert_store = SSL_CTX_get_cert_store(git__ssl_ctx))) + return -1; + + if (cert && X509_STORE_add_cert(cert_store, cert) == 0) { + git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to add raw X509 certificate"); + return -1; + } + + return 0; +} + +int git_openssl__reset_context(void) +{ + shutdown_ssl(); + return openssl_init(); +} + #else #include "stream.h" diff --git a/src/libgit2/streams/openssl.h b/src/libgit2/streams/openssl.h index 89fb60a8..a3ef1a93 100644 --- a/src/libgit2/streams/openssl.h +++ b/src/libgit2/streams/openssl.h @@ -24,6 +24,8 @@ extern int git_openssl_stream_global_init(void); #ifdef GIT_OPENSSL extern int git_openssl__set_cert_location(const char *file, const char *path); +extern int git_openssl__add_x509_cert(X509 *cert); +extern int git_openssl__reset_context(void); extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host); #endif diff --git a/src/libgit2/streams/openssl_dynamic.c b/src/libgit2/streams/openssl_dynamic.c index 222c1099..fe679526 100644 --- a/src/libgit2/streams/openssl_dynamic.c +++ b/src/libgit2/streams/openssl_dynamic.c @@ -65,6 +65,7 @@ int (*SSL_write)(SSL *ssl, const void *buf, int num); long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); void (*SSL_CTX_free)(SSL_CTX *ctx); SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); +X509_STORE *(*SSL_CTX_get_cert_store)(const SSL_CTX *); int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); @@ -80,6 +81,7 @@ int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); void (*X509_free)(X509 *a); void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); X509_NAME *(*X509_get_subject_name)(const X509 *x); +int (*X509_STORE_add_cert)(X509_STORE *ctx, X509 *x); int (*i2d_X509)(X509 *a, unsigned char **ppout); @@ -194,6 +196,7 @@ int git_openssl_stream_dynamic_init(void) SSL_CTX_ctrl = (long (*)(SSL_CTX *, int, long, void *))openssl_sym(&err, "SSL_CTX_ctrl", true); SSL_CTX_free = (void (*)(SSL_CTX *))openssl_sym(&err, "SSL_CTX_free", true); SSL_CTX_new = (SSL_CTX *(*)(const SSL_METHOD *))openssl_sym(&err, "SSL_CTX_new", true); + SSL_CTX_get_cert_store = (X509_STORE *(*)(const SSL_CTX *))openssl_sym(&err, "SSL_CTX_get_cert_store", true); SSL_CTX_set_cipher_list = (int (*)(SSL_CTX *, const char *))openssl_sym(&err, "SSL_CTX_set_cipher_list", true); SSL_CTX_set_default_verify_paths = (int (*)(SSL_CTX *ctx))openssl_sym(&err, "SSL_CTX_set_default_verify_paths", true); SSL_CTX_set_options = (long (*)(SSL_CTX *, long))openssl_sym(&err, "SSL_CTX_set_options", false); @@ -209,6 +212,7 @@ int git_openssl_stream_dynamic_init(void) X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true); X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true); X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true); + X509_STORE_add_cert = (int (*)(X509_STORE *ctx, X509 *x))openssl_sym(&err, "X509_STORE_add_cert", true); i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true); diff --git a/src/libgit2/streams/openssl_dynamic.h b/src/libgit2/streams/openssl_dynamic.h index a9969191..e59f1f93 100644 --- a/src/libgit2/streams/openssl_dynamic.h +++ b/src/libgit2/streams/openssl_dynamic.h @@ -204,6 +204,7 @@ typedef void SSL_METHOD; typedef void X509; typedef void X509_NAME; typedef void X509_NAME_ENTRY; +typedef void X509_STORE; typedef void X509_STORE_CTX; typedef struct { @@ -309,6 +310,7 @@ extern int (*SSL_write)(SSL *ssl, const void *buf, int num); extern long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); extern void (*SSL_CTX_free)(SSL_CTX *ctx); extern SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); +extern X509_STORE *(*SSL_CTX_get_cert_store)(const SSL_CTX *ctx); extern int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); extern int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); extern long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); @@ -326,6 +328,7 @@ extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); extern void (*X509_free)(X509 *a); extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); extern X509_NAME *(*X509_get_subject_name)(const X509 *x); +extern int (*X509_STORE_add_cert)(X509_STORE *ctx, X509 *x); extern int (*i2d_X509)(X509 *a, unsigned char **ppout); diff --git a/src/libgit2/streams/stransport.c b/src/libgit2/streams/stransport.c index 7a3585e2..863616dc 100644 --- a/src/libgit2/streams/stransport.c +++ b/src/libgit2/streams/stransport.c @@ -13,13 +13,16 @@ #include #include +#include "common.h" +#include "trace.h" #include "git2/transport.h" - #include "streams/socket.h" static int stransport_error(OSStatus ret) { - CFStringRef message; + CFStringRef message_ref = NULL; + const char *message_cstr = NULL; + char *message_ptr = NULL; if (ret == noErr || ret == errSSLClosedGraceful) { git_error_clear(); @@ -27,14 +30,39 @@ static int stransport_error(OSStatus ret) } #if !TARGET_OS_IPHONE - message = SecCopyErrorMessageString(ret, NULL); - GIT_ERROR_CHECK_ALLOC(message); + message_ref = SecCopyErrorMessageString(ret, NULL); + GIT_ERROR_CHECK_ALLOC(message_ref); + + /* + * Attempt the cheap CFString conversion; this can return NULL + * when that would be expensive. In that case, call the more + * expensive function. + */ + message_cstr = CFStringGetCStringPtr(message_ref, kCFStringEncodingUTF8); + + if (!message_cstr) { + /* Provide buffer to convert from UTF16 to UTF8 */ + size_t message_size = CFStringGetLength(message_ref) * 2 + 1; + + message_cstr = message_ptr = git__malloc(message_size); + GIT_ERROR_CHECK_ALLOC(message_ptr); + + if (!CFStringGetCString(message_ref, message_ptr, message_size, kCFStringEncodingUTF8)) { + git_error_set(GIT_ERROR_NET, "SecureTransport error: %d", (unsigned int)ret); + goto done; + } + } - git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8)); - CFRelease(message); + git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", message_cstr); + +done: + git__free(message_ptr); + CFRelease(message_ref); #else git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret); - GIT_UNUSED(message); + GIT_UNUSED(message_ref); + GIT_UNUSED(message_cstr); + GIT_UNUSED(message_ptr); #endif return -1; @@ -236,6 +264,10 @@ static ssize_t stransport_read(git_stream *stream, void *data, size_t len) OSStatus ret; if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) { + /* This specific SecureTransport error is not well described */ + if (ret == -9806) + git_trace(GIT_TRACE_INFO, "SecureTraceport error during SSLRead: returned -9806 (connection closed via error)"); + if (st->error == GIT_TIMEOUT) return GIT_TIMEOUT; diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c index 95ea84fc..a3bfc787 100644 --- a/src/libgit2/submodule.c +++ b/src/libgit2/submodule.c @@ -169,22 +169,27 @@ static int is_path_occupied(bool *occupied, git_repository *repo, const char *pa return error; } +GIT_HASHMAP_STR_SETUP(git_submodule_namemap, char *); + /** * Release the name map returned by 'load_submodule_names'. */ -static void free_submodule_names(git_strmap *names) +static void free_submodule_names(git_submodule_namemap *names) { + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; const char *key; char *value; if (names == NULL) return; - git_strmap_foreach(names, key, value, { - git__free((char *) key); + while (git_submodule_namemap_iterate(&iter, &key, &value, names) == 0) { + git__free((char *)key); git__free(value); - }); - git_strmap_free(names); + } + + git_submodule_namemap_dispose(names); + git__free(names); return; } @@ -194,19 +199,22 @@ static void free_submodule_names(git_strmap *names) * TODO: for some use-cases, this might need case-folding on a * case-insensitive filesystem */ -static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) +static int load_submodule_names(git_submodule_namemap **out, git_repository *repo, git_config *cfg) { - const char *key = "submodule\\..*\\.path"; + const char *key = "^submodule\\..*\\.path$"; + char *value; git_config_iterator *iter = NULL; git_config_entry *entry; git_str buf = GIT_STR_INIT; - git_strmap *names; + git_submodule_namemap *names; int isvalid, error; *out = NULL; - if ((error = git_strmap_new(&names)) < 0) + if ((names = git__calloc(1, sizeof(git_submodule_namemap))) == NULL) { + error = -1; goto out; + } if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) goto out; @@ -216,7 +224,7 @@ static int load_submodule_names(git_strmap **out, git_repository *repo, git_conf fdot = strchr(entry->name, '.'); ldot = strrchr(entry->name, '.'); - if (git_strmap_exists(names, entry->value)) { + if (git_submodule_namemap_contains(names, entry->value)) { git_error_set(GIT_ERROR_SUBMODULE, "duplicated submodule path '%s'", entry->value); error = -1; @@ -233,7 +241,12 @@ static int load_submodule_names(git_strmap **out, git_repository *repo, git_conf if (!isvalid) continue; - if ((error = git_strmap_set(names, git__strdup(entry->value), git_str_detach(&buf))) < 0) { + if ((value = git__strdup(entry->value)) == NULL) { + error = -1; + goto out; + } + + if ((error = git_submodule_namemap_put(names, value, git_str_detach(&buf))) < 0) { git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table"); error = -1; goto out; @@ -252,31 +265,43 @@ static int load_submodule_names(git_strmap **out, git_repository *repo, git_conf return error; } -int git_submodule_cache_init(git_strmap **out, git_repository *repo) +GIT_HASHMAP_STR_FUNCTIONS(git_submodule_cache, GIT_HASHMAP_INLINE, git_submodule *); + +int git_submodule__map(git_submodule_cache *cache, git_repository *repo); + +int git_submodule_cache_init(git_submodule_cache **out, git_repository *repo) { + git_submodule_cache *cache = NULL; int error = 0; - git_strmap *cache = NULL; + GIT_ASSERT_ARG(out); GIT_ASSERT_ARG(repo); - if ((error = git_strmap_new(&cache)) < 0) - return error; - if ((error = git_submodule__map(repo, cache)) < 0) { + + if ((cache = git__calloc(1, sizeof(git_submodule_cache))) == NULL) + return -1; + + if ((error = git_submodule__map(cache, repo)) < 0) { git_submodule_cache_free(cache); return error; } + *out = cache; return error; } -int git_submodule_cache_free(git_strmap *cache) +int git_submodule_cache_free(git_submodule_cache *cache) { git_submodule *sm = NULL; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + if (cache == NULL) return 0; - git_strmap_foreach_value(cache, sm, { + + while (git_submodule_cache_iterate(&iter, NULL, &sm, cache) == 0) git_submodule_free(sm); - }); - git_strmap_free(cache); + + git_submodule_cache_dispose(cache); + git__free(cache); return 0; } @@ -292,7 +317,7 @@ int git_submodule__lookup_with_cache( git_submodule **out, /* NULL if user only wants to test existence */ git_repository *repo, const char *name, /* trailing slash is allowed */ - git_strmap *cache) + git_submodule_cache *cache) { int error; unsigned int location; @@ -307,7 +332,7 @@ int git_submodule__lookup_with_cache( } if (cache != NULL) { - if ((sm = git_strmap_get(cache, name)) != NULL) { + if (git_submodule_cache_get(&sm, cache, name) == 0) { if (out) { *out = sm; GIT_REFCOUNT_INC(*out); @@ -332,7 +357,7 @@ int git_submodule__lookup_with_cache( /* If it's not configured or we're looking by path */ if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { git_config_backend *mods; - const char *pattern = "submodule\\..*\\.path"; + const char *pattern = "^submodule\\..*\\.path$"; git_str path = GIT_STR_INIT; fbp_data data = { NULL, NULL }; @@ -434,19 +459,23 @@ static void submodule_free_dup(void *sm) git_submodule_free(sm); } -static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name) +static int submodule_get_or_create( + git_submodule **out, + git_repository *repo, + git_submodule_cache *cache, + const char *name) { git_submodule *sm = NULL; int error; - if ((sm = git_strmap_get(map, name)) != NULL) + if (git_submodule_cache_get(&sm, cache, name) == 0) goto done; /* if the submodule doesn't exist yet in the map, create it */ if ((error = submodule_alloc(&sm, repo, name)) < 0) return error; - if ((error = git_strmap_set(map, sm->name, sm)) < 0) { + if ((error = git_submodule_cache_put(cache, sm->name, sm)) < 0) { git_submodule_free(sm); return error; } @@ -457,12 +486,15 @@ static int submodule_get_or_create(git_submodule **out, git_repository *repo, gi return 0; } -static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) +static int submodules_from_index( + git_submodule_cache *cache, + git_index *idx, + git_config *cfg) { int error; git_iterator *i = NULL; const git_index_entry *entry; - git_strmap *names; + git_submodule_namemap *names; if ((error = load_submodule_names(&names, git_index_owner(idx), cfg))) goto done; @@ -473,7 +505,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf while (!(error = git_iterator_advance(&entry, i))) { git_submodule *sm; - if ((sm = git_strmap_get(map, entry->path)) != NULL) { + if (git_submodule_cache_get(&sm, cache, entry->path) == 0) { if (S_ISGITLINK(entry->mode)) submodule_update_from_index_entry(sm, entry); else @@ -481,10 +513,10 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf } else if (S_ISGITLINK(entry->mode)) { const char *name; - if ((name = git_strmap_get(names, entry->path)) == NULL) + if (git_submodule_namemap_get((char **)&name, names, entry->path) != 0) name = entry->path; - if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name)) { + if (!submodule_get_or_create(&sm, git_index_owner(idx), cache, name)) { submodule_update_from_index_entry(sm, entry); git_submodule_free(sm); } @@ -501,12 +533,15 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf return error; } -static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) +static int submodules_from_head( + git_submodule_cache *cache, + git_tree *head, + git_config *cfg) { int error; git_iterator *i = NULL; const git_index_entry *entry; - git_strmap *names; + git_submodule_namemap *names; if ((error = load_submodule_names(&names, git_tree_owner(head), cfg))) goto done; @@ -517,7 +552,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg while (!(error = git_iterator_advance(&entry, i))) { git_submodule *sm; - if ((sm = git_strmap_get(map, entry->path)) != NULL) { + if (git_submodule_cache_get(&sm, cache, entry->path) == 0) { if (S_ISGITLINK(entry->mode)) submodule_update_from_head_data(sm, entry->mode, &entry->id); else @@ -525,10 +560,10 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg } else if (S_ISGITLINK(entry->mode)) { const char *name; - if ((name = git_strmap_get(names, entry->path)) == NULL) + if (git_submodule_namemap_get((char **)&name, names, entry->path) != 0) name = entry->path; - if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name)) { + if (!submodule_get_or_create(&sm, git_tree_owner(head), cache, name)) { submodule_update_from_head_data( sm, entry->mode, &entry->id); git_submodule_free(sm); @@ -549,11 +584,11 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg /* If have_sm is true, sm is populated, otherwise map an repo are. */ typedef struct { git_config *mods; - git_strmap *map; + git_submodule_cache *cache; git_repository *repo; } lfc_data; -int git_submodule__map(git_repository *repo, git_strmap *map) +int git_submodule__map(git_submodule_cache *cache, git_repository *repo) { int error = 0; git_index *idx = NULL; @@ -563,8 +598,8 @@ int git_submodule__map(git_repository *repo, git_strmap *map) git_config *mods = NULL; bool has_workdir; + GIT_ASSERT_ARG(cache); GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(map); /* get sources that we will need to check */ if (git_repository_index(&idx, repo) < 0) @@ -581,7 +616,7 @@ int git_submodule__map(git_repository *repo, git_strmap *map) /* add submodule information from .gitmodules */ if (has_workdir) { lfc_data data = { 0 }; - data.map = map; + data.cache = cache; data.repo = repo; if ((error = gitmodules_snapshot(&mods, repo)) < 0) { @@ -597,19 +632,22 @@ int git_submodule__map(git_repository *repo, git_strmap *map) } /* add back submodule information from index */ if (mods && idx) { - if ((error = submodules_from_index(map, idx, mods)) < 0) + if ((error = submodules_from_index(cache, idx, mods)) < 0) goto cleanup; } /* add submodule information from HEAD */ if (mods && head) { - if ((error = submodules_from_head(map, head, mods)) < 0) + if ((error = submodules_from_head(cache, head, mods)) < 0) goto cleanup; } /* shallow scan submodules in work tree as needed */ if (has_workdir) { - git_strmap_foreach_value(map, sm, { - submodule_load_from_wd_lite(sm); - }); + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + + while (git_submodule_cache_iterate(&iter, NULL, &sm, cache) == 0) { + if ((error = submodule_load_from_wd_lite(sm)) < 0) + goto cleanup; + } } cleanup: @@ -627,8 +665,9 @@ int git_submodule_foreach( void *payload) { git_vector snapshot = GIT_VECTOR_INIT; - git_strmap *submodules; + git_submodule_cache *submodules; git_submodule *sm; + git_hashmap_iter_t iter; int error; size_t i; @@ -637,20 +676,22 @@ int git_submodule_foreach( return -1; } - if ((error = git_strmap_new(&submodules)) < 0) - return error; + if ((submodules = git__calloc(1, sizeof(git_submodule_cache))) == NULL) + return -1; - if ((error = git_submodule__map(repo, submodules)) < 0) + if ((error = git_submodule__map(submodules, repo)) < 0) goto done; - if (!(error = git_vector_init( - &snapshot, git_strmap_size(submodules), submodule_cmp))) { - - git_strmap_foreach_value(submodules, sm, { + if (!(error = git_vector_init(&snapshot, + git_submodule_cache_size(submodules), + submodule_cmp))) { + for (iter = GIT_HASHMAP_ITER_INIT; + git_submodule_cache_iterate(&iter, NULL, &sm, submodules) == 0; ) { if ((error = git_vector_insert(&snapshot, sm)) < 0) break; + GIT_REFCOUNT_INC(sm); - }); + } } if (error < 0) @@ -668,12 +709,14 @@ int git_submodule_foreach( done: git_vector_foreach(&snapshot, i, sm) git_submodule_free(sm); - git_vector_free(&snapshot); + git_vector_dispose(&snapshot); - git_strmap_foreach_value(submodules, sm, { + for (iter = GIT_HASHMAP_ITER_INIT; + git_submodule_cache_iterate(&iter, NULL, &sm, submodules) == 0; ) git_submodule_free(sm); - }); - git_strmap_free(submodules); + + git_submodule_cache_dispose(submodules); + git__free(submodules); return error; } @@ -1338,11 +1381,11 @@ int git_submodule_update(git_submodule *sm, int init, git_submodule_update_optio /* Get the status of the submodule to determine if it is already initialized */ if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) goto done; - + /* If the submodule is configured but hasn't been added, skip it */ if (submodule_status == GIT_SUBMODULE_STATUS_IN_CONFIG) goto done; - + /* * If submodule work dir is not already initialized, check to see * what we need to do (initialize, clone, return error...) @@ -2049,7 +2092,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) { lfc_data *data = payload; const char *namestart, *property; - git_strmap *map = data->map; + git_submodule_cache *cache = data->cache; git_str name = GIT_STR_INIT; git_submodule *sm; int error, isvalid; @@ -2080,7 +2123,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) * a new submodule, load the config and insert it. If it's * already inserted, we've already loaded it, so we skip. */ - if (git_strmap_exists(map, name.ptr)) { + if (git_submodule_cache_contains(cache, name.ptr)) { error = 0; goto done; } @@ -2093,7 +2136,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) goto done; } - if ((error = git_strmap_set(map, sm->name, sm)) < 0) + if ((error = git_submodule_cache_put(cache, sm->name, sm)) < 0) goto done; error = 0; diff --git a/src/libgit2/submodule.h b/src/libgit2/submodule.h index 40b7b70f..2690d8dd 100644 --- a/src/libgit2/submodule.h +++ b/src/libgit2/submodule.h @@ -12,6 +12,7 @@ #include "git2/submodule.h" #include "git2/repository.h" #include "futils.h" +#include "hashmap.h" /* Notes: * @@ -116,15 +117,17 @@ enum { #define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ ((S) & ~(0xFFFFFFFFu << 20)) +GIT_HASHMAP_STR_STRUCT(git_submodule_cache, git_submodule *); + /* Initialize an external submodule cache for the provided repo. */ -extern int git_submodule_cache_init(git_strmap **out, git_repository *repo); +extern int git_submodule_cache_init(git_submodule_cache **out, git_repository *repo); /* Release the resources of the submodule cache. */ -extern int git_submodule_cache_free(git_strmap *cache); +extern int git_submodule_cache_free(git_submodule_cache *cache); /* Submodule lookup with an explicit cache */ extern int git_submodule__lookup_with_cache( - git_submodule **out, git_repository *repo, const char *path, git_strmap *cache); + git_submodule **out, git_repository *repo, const char *path, git_submodule_cache *cache); /* Internal status fn returns status and optionally the various OIDs */ extern int git_submodule__status( @@ -145,10 +148,6 @@ extern int git_submodule_parse_ignore( extern int git_submodule_parse_update( git_submodule_update_t *out, const char *value); -extern int git_submodule__map( - git_repository *repo, - git_strmap *map); - /** * Check whether a submodule's name is valid. * diff --git a/src/libgit2/tag.c b/src/libgit2/tag.c index 562ec13e..d12efdb6 100644 --- a/src/libgit2/tag.c +++ b/src/libgit2/tag.c @@ -263,8 +263,10 @@ static bool tag_name_is_valid(const char *tag_name) /* * Discourage tag name starting with dash, * https://github.com/git/git/commit/4f0accd638b8d2 + * and refuse to use HEAD as a tagname, + * https://github.com/git/git/commit/bbd445d5efd415 */ - return tag_name[0] != '-'; + return tag_name[0] != '-' && git__strcmp(tag_name, "HEAD"); } static int git_tag_create__internal( @@ -548,7 +550,7 @@ int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_reposit error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); if (error < 0) - git_vector_free(&taglist); + git_vector_dispose(&taglist); tag_names->strings = (char **)git_vector_detach(&tag_names->count, NULL, &taglist); diff --git a/src/libgit2/threadstate.c b/src/libgit2/threadstate.c deleted file mode 100644 index ed9bb9b9..00000000 --- a/src/libgit2/threadstate.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "threadstate.h" -#include "runtime.h" - -/** - * Handle the thread-local state - * - * `git_threadstate_global_init` will be called as part - * of `git_libgit2_init` (which itself must be called - * before calling any other function in the library). - * - * This function allocates a TLS index to store the per- - * thread state. - * - * Any internal method that requires thread-local state - * will then call `git_threadstate_get()` which returns a - * pointer to the thread-local state structure; this - * structure is lazily allocated on each thread. - * - * This mechanism will register a shutdown handler - * (`git_threadstate_global_shutdown`) which will free the - * TLS index. This shutdown handler will be called by - * `git_libgit2_shutdown`. - */ - -static git_tlsdata_key tls_key; - -static void threadstate_dispose(git_threadstate *threadstate) -{ - if (!threadstate) - return; - - if (threadstate->error_t.message != git_str__initstr) - git__free(threadstate->error_t.message); - threadstate->error_t.message = NULL; -} - -static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) -{ - threadstate_dispose(threadstate); - git__free(threadstate); -} - -static void git_threadstate_global_shutdown(void) -{ - git_threadstate *threadstate; - - threadstate = git_tlsdata_get(tls_key); - git_tlsdata_set(tls_key, NULL); - - threadstate_dispose(threadstate); - git__free(threadstate); - - git_tlsdata_dispose(tls_key); -} - -int git_threadstate_global_init(void) -{ - if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) - return -1; - - return git_runtime_shutdown_register(git_threadstate_global_shutdown); -} - -git_threadstate *git_threadstate_get(void) -{ - git_threadstate *threadstate; - - if ((threadstate = git_tlsdata_get(tls_key)) != NULL) - return threadstate; - - /* - * Avoid git__malloc here, since if it fails, it sets an error - * message, which requires thread state, which would allocate - * here, which would fail, which would set an error message... - */ - - if ((threadstate = git__allocator.gmalloc(sizeof(git_threadstate), - __FILE__, __LINE__)) == NULL) - return NULL; - - memset(threadstate, 0, sizeof(git_threadstate)); - - if (git_str_init(&threadstate->error_buf, 0) < 0) { - git__allocator.gfree(threadstate); - return NULL; - } - - git_tlsdata_set(tls_key, threadstate); - return threadstate; -} diff --git a/src/libgit2/threadstate.h b/src/libgit2/threadstate.h deleted file mode 100644 index 6ef04192..00000000 --- a/src/libgit2/threadstate.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_threadstate_h__ -#define INCLUDE_threadstate_h__ - -#include "common.h" - -typedef struct { - git_error *last_error; - git_error error_t; - git_str error_buf; - char oid_fmt[GIT_OID_MAX_HEXSIZE+1]; -} git_threadstate; - -extern int git_threadstate_global_init(void); -extern git_threadstate *git_threadstate_get(void); - -#endif diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c index 4761c992..c7579fb3 100644 --- a/src/libgit2/trailer.c +++ b/src/libgit2/trailer.c @@ -24,7 +24,7 @@ static const char *const git_generated_prefixes[] = { static int is_blank_line(const char *str) { const char *s = str; - while (*s && *s != '\n' && isspace(*s)) + while (*s && *s != '\n' && git__isspace(*s)) s++; return !*s || *s == '\n'; } @@ -93,7 +93,7 @@ static bool find_separator(size_t *out, const char *line, const char *separators return true; } - if (!whitespace_found && (isalnum(*c) || *c == '-')) + if (!whitespace_found && (git__isalnum(*c) || *c == '-')) continue; if (c != line && (*c == ' ' || *c == '\t')) { whitespace_found = 1; @@ -158,7 +158,7 @@ static size_t find_patch_start(const char *str) const char *s; for (s = str; *s; s = next_line(s)) { - if (git__prefixcmp(s, "---") == 0) + if (git__prefixcmp(s, "---") == 0 && git__isspace(s[3])) return s - str; } @@ -233,12 +233,12 @@ static size_t find_trailer_start(const char *buf, size_t len) } find_separator(&separator_pos, bol, TRAILER_SEPARATORS); - if (separator_pos >= 1 && !isspace(bol[0])) { + if (separator_pos >= 1 && !git__isspace(bol[0])) { trailer_lines++; possible_continuation_lines = 0; if (recognized_prefix) continue; - } else if (isspace(bol[0])) + } else if (git__isspace(bol[0])) possible_continuation_lines++; else { non_trailer_lines++; @@ -323,7 +323,7 @@ int git_message_trailers(git_message_trailer_array *trailer_arr, const char *mes goto ret; } - if (isalnum(*ptr) || *ptr == '-') { + if (git__isalnum(*ptr) || *ptr == '-') { /* legal key character */ NEXT(S_KEY); } diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c index ccffa998..1965498c 100644 --- a/src/libgit2/transaction.c +++ b/src/libgit2/transaction.c @@ -8,7 +8,6 @@ #include "transaction.h" #include "repository.h" -#include "strmap.h" #include "refdb.h" #include "pool.h" #include "reflog.h" @@ -44,17 +43,23 @@ typedef struct { remove :1; } transaction_node; +GIT_HASHMAP_STR_SETUP(git_transaction_nodemap, transaction_node *); + struct git_transaction { transaction_t type; git_repository *repo; git_refdb *db; git_config *cfg; + void *cfg_data; - git_strmap *locks; + git_transaction_nodemap locks; git_pool pool; }; -int git_transaction_config_new(git_transaction **out, git_config *cfg) +int git_transaction_config_new( + git_transaction **out, + git_config *cfg, + void *data) { git_transaction *tx; @@ -66,6 +71,8 @@ int git_transaction_config_new(git_transaction **out, git_config *cfg) tx->type = TRANSACTION_CONFIG; tx->cfg = cfg; + tx->cfg_data = data; + *out = tx; return 0; } @@ -88,11 +95,6 @@ int git_transaction_new(git_transaction **out, git_repository *repo) goto on_error; } - if ((error = git_strmap_new(&tx->locks)) < 0) { - error = -1; - goto on_error; - } - if ((error = git_repository_refdb(&tx->db, repo)) < 0) goto on_error; @@ -124,7 +126,7 @@ int git_transaction_lock_ref(git_transaction *tx, const char *refname) if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) return error; - if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) + if ((error = git_transaction_nodemap_put(&tx->locks, node->name, node)) < 0) goto cleanup; return 0; @@ -138,8 +140,11 @@ int git_transaction_lock_ref(git_transaction *tx, const char *refname) static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) { transaction_node *node; + int error; + + error = git_transaction_nodemap_get(&node, &tx->locks, refname); - if ((node = git_strmap_get(tx->locks, refname)) == NULL) { + if (error != 0) { git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); return GIT_ENOTFOUND; } @@ -328,18 +333,20 @@ static int update_target(git_refdb *db, transaction_node *node) int git_transaction_commit(git_transaction *tx) { transaction_node *node; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; int error = 0; GIT_ASSERT_ARG(tx); if (tx->type == TRANSACTION_CONFIG) { - error = git_config_unlock(tx->cfg, true); + error = git_config_unlock(tx->cfg, tx->cfg_data, true); tx->cfg = NULL; + tx->cfg_data = NULL; return error; } - git_strmap_foreach_value(tx->locks, node, { + while (git_transaction_nodemap_iterate(&iter, NULL, &node, &tx->locks) == 0) { if (node->reflog) { if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) return error; @@ -355,7 +362,7 @@ int git_transaction_commit(git_transaction *tx) if ((error = update_target(tx->db, node)) < 0) return error; } - }); + } return 0; } @@ -364,30 +371,29 @@ void git_transaction_free(git_transaction *tx) { transaction_node *node; git_pool pool; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; if (!tx) return; if (tx->type == TRANSACTION_CONFIG) { - if (tx->cfg) { - git_config_unlock(tx->cfg, false); - git_config_free(tx->cfg); - } + if (tx->cfg) + git_config_unlock(tx->cfg, tx->cfg_data, false); git__free(tx); return; } /* start by unlocking the ones we've left hanging, if any */ - git_strmap_foreach_value(tx->locks, node, { + while (git_transaction_nodemap_iterate(&iter, NULL, &node, &tx->locks) == 0) { if (node->committed) continue; git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); - }); + } git_refdb_free(tx->db); - git_strmap_free(tx->locks); + git_transaction_nodemap_dispose(&tx->locks); /* tx is inside the pool, so we need to extract the data */ memcpy(&pool, &tx->pool, sizeof(git_pool)); diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h index 780c0683..cb26017a 100644 --- a/src/libgit2/transaction.h +++ b/src/libgit2/transaction.h @@ -9,6 +9,9 @@ #include "common.h" -int git_transaction_config_new(git_transaction **out, git_config *cfg); +int git_transaction_config_new( + git_transaction **out, + git_config *cfg, + void *data); #endif diff --git a/src/libgit2/transport.c b/src/libgit2/transport.c index 640ccaca..c31fca3a 100644 --- a/src/libgit2/transport.c +++ b/src/libgit2/transport.c @@ -22,6 +22,7 @@ typedef struct transport_definition { static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL }; static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL }; + #ifdef GIT_SSH static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL }; #endif @@ -33,11 +34,13 @@ static transport_definition transports[] = { { "http://", git_transport_smart, &http_subtransport_definition }, { "https://", git_transport_smart, &http_subtransport_definition }, { "file://", git_transport_local, NULL }, + #ifdef GIT_SSH { "ssh://", git_transport_smart, &ssh_subtransport_definition }, { "ssh+git://", git_transport_smart, &ssh_subtransport_definition }, { "git+ssh://", git_transport_smart, &ssh_subtransport_definition }, #endif + { NULL, 0, 0 } }; @@ -200,7 +203,7 @@ int git_transport_unregister(const char *scheme) git__free(d); if (!custom_transports.length) - git_vector_free(&custom_transports); + git_vector_dispose(&custom_transports); error = 0; goto done; diff --git a/src/libgit2/transports/credential.c b/src/libgit2/transports/credential.c index 6e00b028..7d0eacec 100644 --- a/src/libgit2/transports/credential.c +++ b/src/libgit2/transports/credential.c @@ -161,7 +161,7 @@ static void ssh_custom_free(struct git_credential *cred) if (c->publickey) { /* Zero the memory which previously held the publickey */ - size_t key_len = strlen(c->publickey); + size_t key_len = c->publickey_len; git__memzero(c->publickey, key_len); git__free(c->publickey); } @@ -204,7 +204,7 @@ int git_credential_ssh_key_memory_new( const char *privatekey, const char *passphrase) { -#ifdef GIT_SSH_MEMORY_CREDENTIALS +#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS return git_credential_ssh_key_type_new( cred, username, diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index 8437674f..ea819952 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -9,7 +9,6 @@ #ifndef GIT_WINHTTP -#include "http_parser.h" #include "net.h" #include "remote.h" #include "smart.h" @@ -334,7 +333,7 @@ static int lookup_proxy( return 0; } - if (!proxy || + if (!proxy || !*proxy || (error = git_net_url_parse_http(&transport->proxy.url, proxy)) < 0) goto done; diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h index 8e8e7226..7410202a 100644 --- a/src/libgit2/transports/http.h +++ b/src/libgit2/transports/http.h @@ -15,14 +15,4 @@ extern bool git_http__expect_continue; -GIT_INLINE(int) git_http__user_agent(git_str *buf) -{ - const char *ua = git_libgit2__user_agent(); - - if (!ua) - ua = "libgit2 " LIBGIT2_VERSION; - - return git_str_printf(buf, "git/2.0 (%s)", ua); -} - #endif diff --git a/src/libgit2/transports/httpclient.c b/src/libgit2/transports/httpclient.c index a20b5949..e25e0a73 100644 --- a/src/libgit2/transports/httpclient.c +++ b/src/libgit2/transports/httpclient.c @@ -7,7 +7,7 @@ #include "common.h" #include "git2.h" -#include "http_parser.h" + #include "vector.h" #include "trace.h" #include "httpclient.h" @@ -21,6 +21,7 @@ #include "streams/socket.h" #include "streams/tls.h" #include "auth.h" +#include "httpparser.h" static git_http_auth_scheme auth_schemes[] = { { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate }, @@ -108,7 +109,7 @@ struct git_http_client { git_http_server_t current_server; http_client_state state; - http_parser parser; + git_http_parser parser; git_http_server server; git_http_server proxy; @@ -154,7 +155,7 @@ void git_http_response_dispose(git_http_response *response) memset(response, 0, sizeof(git_http_response)); } -static int on_header_complete(http_parser *parser) +static int on_header_complete(git_http_parser *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; git_http_client *client = ctx->client; @@ -219,7 +220,7 @@ static int on_header_complete(http_parser *parser) return 0; } -static int on_header_field(http_parser *parser, const char *str, size_t len) +static int on_header_field(git_http_parser *parser, const char *str, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -254,7 +255,7 @@ static int on_header_field(http_parser *parser, const char *str, size_t len) return 0; } -static int on_header_value(http_parser *parser, const char *str, size_t len) +static int on_header_value(git_http_parser *parser, const char *str, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -342,7 +343,7 @@ static int resend_needed(git_http_client *client, git_http_response *response) return 0; } -static int on_headers_complete(http_parser *parser) +static int on_headers_complete(git_http_parser *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -364,8 +365,8 @@ static int on_headers_complete(http_parser *parser) return ctx->parse_status = PARSE_STATUS_ERROR; } - ctx->response->status = parser->status_code; - ctx->client->keepalive = http_should_keep_alive(parser); + ctx->response->status = git_http_parser_status_code(parser); + ctx->client->keepalive = git_http_parser_keep_alive(parser); /* Prepare for authentication */ collect_authinfo(&ctx->response->server_auth_schemetypes, @@ -378,18 +379,15 @@ static int on_headers_complete(http_parser *parser) ctx->response->resend_credentials = resend_needed(ctx->client, ctx->response); - /* Stop parsing. */ - http_parser_pause(parser, 1); - if (ctx->response->content_type || ctx->response->chunked) ctx->client->state = READING_BODY; else ctx->client->state = DONE; - return 0; + return git_http_parser_pause(parser); } -static int on_body(http_parser *parser, const char *buf, size_t len) +static int on_body(git_http_parser *parser, const char *buf, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; size_t max_len; @@ -411,7 +409,7 @@ static int on_body(http_parser *parser, const char *buf, size_t len) return 0; } -static int on_message_complete(http_parser *parser) +static int on_message_complete(git_http_parser *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -651,6 +649,30 @@ static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) return git_str_oom(buf) ? -1 : 0; } +static int append_user_agent(git_str *buf) +{ + const char *product = git_settings__user_agent_product(); + const char *comment = git_settings__user_agent(); + + GIT_ASSERT(product && comment); + + if (!*product) + return 0; + + git_str_puts(buf, "User-Agent: "); + git_str_puts(buf, product); + + if (*comment) { + git_str_puts(buf, " ("); + git_str_puts(buf, comment); + git_str_puts(buf, ")"); + } + + git_str_puts(buf, "\r\n"); + + return git_str_oom(buf) ? -1 : 0; +} + static int generate_connect_request( git_http_client *client, git_http_request *request) @@ -665,9 +687,7 @@ static int generate_connect_request( puts_host_and_port(buf, &client->server.url, true); git_str_puts(buf, " HTTP/1.1\r\n"); - git_str_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_str_puts(buf, "\r\n"); + append_user_agent(buf); git_str_puts(buf, "Host: "); puts_host_and_port(buf, &client->server.url, true); @@ -711,9 +731,7 @@ static int generate_request( git_str_puts(buf, " HTTP/1.1\r\n"); - git_str_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_str_puts(buf, "\r\n"); + append_user_agent(buf); git_str_puts(buf, "Host: "); puts_host_and_port(buf, request->url, false); @@ -768,25 +786,37 @@ static int check_certificate( void *cert_cb_payload) { git_cert *cert; - git_error_state last_error = {0}; + git_error *last_error; int error; if ((error = git_stream_certificate(&cert, stream)) < 0) return error; - git_error_state_capture(&last_error, GIT_ECERTIFICATE); + /* + * Allow callers to set an error - but save ours and clear + * it, so that we can detect if they set one and restore it + * if we need to. + */ + git_error_save(&last_error); + git_error_clear(); error = cert_cb(cert, is_valid, url->host, cert_cb_payload); - if (error == GIT_PASSTHROUGH && !is_valid) - return git_error_state_restore(&last_error); - else if (error == GIT_PASSTHROUGH) - error = 0; - else if (error && !git_error_last()) - git_error_set(GIT_ERROR_HTTP, - "user rejected certificate for %s", url->host); + if (error == GIT_PASSTHROUGH) { + error = is_valid ? 0 : -1; - git_error_state_free(&last_error); + if (error) { + git_error_restore(last_error); + last_error = NULL; + } + } else if (error) { + if (!git_error_exists()) + git_error_set(GIT_ERROR_HTTP, + "user rejected certificate for %s", + url->host); + } + + git_error_free(last_error); return error; } @@ -864,9 +894,29 @@ GIT_INLINE(int) server_setup_from_url( return 0; } +static bool parser_settings_initialized; +static git_http_parser_settings parser_settings; + +GIT_INLINE(git_http_parser_settings *) http_client_parser_settings(void) +{ + if (!parser_settings_initialized) { + parser_settings.on_header_field = on_header_field; + parser_settings.on_header_value = on_header_value; + parser_settings.on_headers_complete = on_headers_complete; + parser_settings.on_body = on_body; + parser_settings.on_message_complete = on_message_complete; + + parser_settings_initialized = true; + } + + return &parser_settings; +} + static void reset_parser(git_http_client *client) { - http_parser_init(&client->parser, HTTP_RESPONSE); + git_http_parser_init(&client->parser, + GIT_HTTP_PARSER_RESPONSE, + http_client_parser_settings()); } static int setup_hosts( @@ -1109,27 +1159,9 @@ GIT_INLINE(int) client_read(git_http_client *client) return (int)read_len; } -static bool parser_settings_initialized; -static http_parser_settings parser_settings; - -GIT_INLINE(http_parser_settings *) http_client_parser_settings(void) -{ - if (!parser_settings_initialized) { - parser_settings.on_header_field = on_header_field; - parser_settings.on_header_value = on_header_value; - parser_settings.on_headers_complete = on_headers_complete; - parser_settings.on_body = on_body; - parser_settings.on_message_complete = on_message_complete; - - parser_settings_initialized = true; - } - - return &parser_settings; -} - GIT_INLINE(int) client_read_and_parse(git_http_client *client) { - http_parser *parser = &client->parser; + git_http_parser *parser = &client->parser; http_parser_context *ctx = (http_parser_context *) parser->data; unsigned char http_errno; int read_len; @@ -1143,11 +1175,10 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) if (!client->read_buf.size && (read_len = client_read(client)) < 0) return read_len; - parsed_len = http_parser_execute(parser, - http_client_parser_settings(), + parsed_len = git_http_parser_execute(parser, client->read_buf.ptr, client->read_buf.size); - http_errno = client->parser.http_errno; + http_errno = git_http_parser_errno(parser); if (parsed_len > INT_MAX) { git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse"); @@ -1166,26 +1197,29 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) * (This can happen in response to an expect/continue request, * where the server gives you a 100 and 200 simultaneously.) */ - if (http_errno == HPE_PAUSED) { + if (http_errno == GIT_HTTP_PARSER_PAUSED) { + size_t additional_size; + + git_http_parser_resume(parser); + /* - * http-parser has a "feature" where it will not deliver the - * final byte when paused in a callback. Consume that byte. - * https://github.com/nodejs/http-parser/issues/97 + * http-parser has a "feature" where it will not deliver + * the final byte when paused in a callback. Consume + * that byte. */ - GIT_ASSERT(client->read_buf.size > parsed_len); + if ((additional_size = git_http_parser_remain_after_pause(parser)) > 0) { + GIT_ASSERT((client->read_buf.size - parsed_len) >= additional_size); - http_parser_pause(parser, 0); - - parsed_len += http_parser_execute(parser, - http_client_parser_settings(), - client->read_buf.ptr + parsed_len, - 1); + parsed_len += git_http_parser_execute(parser, + client->read_buf.ptr + parsed_len, + additional_size); + } } /* Most failures will be reported in http_errno */ - else if (parser->http_errno != HPE_OK) { + else if (git_http_parser_errno(parser) != GIT_HTTP_PARSER_OK) { git_error_set(GIT_ERROR_HTTP, "http parser error: %s", - http_errno_description(http_errno)); + git_http_parser_errmsg(parser, http_errno)); return -1; } @@ -1193,7 +1227,7 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) else if (parsed_len != client->read_buf.size) { git_error_set(GIT_ERROR_HTTP, "http parser did not consume entire buffer: %s", - http_errno_description(http_errno)); + git_http_parser_errmsg(parser, http_errno)); return -1; } @@ -1232,7 +1266,7 @@ static void complete_response_body(git_http_client *client) /* If there was an error, just close the connection. */ if (client_read_and_parse(client) < 0 || - parser_context.error != HPE_OK || + parser_context.error != GIT_HTTP_PARSER_OK || (parser_context.parse_status != PARSE_STATUS_OK && parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { git_error_clear(); @@ -1240,6 +1274,7 @@ static void complete_response_body(git_http_client *client) } done: + client->parser.data = NULL; git_str_clear(&client->read_buf); } @@ -1407,9 +1442,9 @@ int git_http_client_read_response( git_http_response_dispose(response); if (client->current_server == PROXY) { - git_vector_free_deep(&client->proxy.auth_challenges); + git_vector_dispose_deep(&client->proxy.auth_challenges); } else if(client->current_server == SERVER) { - git_vector_free_deep(&client->server.auth_challenges); + git_vector_dispose_deep(&client->server.auth_challenges); } client->state = READING_RESPONSE; @@ -1429,6 +1464,7 @@ int git_http_client_read_response( done: git_str_dispose(&parser_context.parse_header_name); git_str_dispose(&parser_context.parse_header_value); + client->parser.data = NULL; return error; } @@ -1484,6 +1520,8 @@ int git_http_client_read_body( if (error < 0) client->connected = 0; + client->parser.data = NULL; + return error; } @@ -1506,7 +1544,7 @@ int git_http_client_skip_body(git_http_client *client) do { error = client_read_and_parse(client); - if (parser_context.error != HPE_OK || + if (parser_context.error != GIT_HTTP_PARSER_OK || (parser_context.parse_status != PARSE_STATUS_OK && parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { git_error_set(GIT_ERROR_HTTP, @@ -1518,6 +1556,8 @@ int git_http_client_skip_body(git_http_client *client) if (error < 0) client->connected = 0; + client->parser.data = NULL; + return error; } @@ -1565,7 +1605,7 @@ GIT_INLINE(void) http_server_close(git_http_server *server) git_net_url_dispose(&server->url); - git_vector_free_deep(&server->auth_challenges); + git_vector_dispose_deep(&server->auth_challenges); free_auth_context(server); } diff --git a/src/libgit2/transports/httpparser.c b/src/libgit2/transports/httpparser.c new file mode 100644 index 00000000..84833e61 --- /dev/null +++ b/src/libgit2/transports/httpparser.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "httpparser.h" + +#include + +#if defined(GIT_HTTPPARSER_HTTPPARSER) + +#include "http_parser.h" + +static int on_message_begin(http_parser *p) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_message_begin(parser); +} + +static int on_url(http_parser *p, const char *str, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_url(parser, str, len); +} + +static int on_header_field(http_parser *p, const char *str, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_header_field(parser, str, len); +} + +static int on_header_value(http_parser *p, const char *str, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_header_value(parser, str, len); +} + +static int on_headers_complete(http_parser *p) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_headers_complete(parser); +} + +static int on_body(http_parser *p, const char *buf, size_t len) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_body(parser, buf, len); +} + +static int on_message_complete(http_parser *p) +{ + git_http_parser *parser = (git_http_parser *)p; + return parser->settings.on_message_complete(parser); +} + +void git_http_parser_init( + git_http_parser *parser, + git_http_parser_t type, + git_http_parser_settings *settings) +{ + http_parser_init(&parser->parser, (enum http_parser_type)type); + memcpy(&parser->settings, settings, sizeof(git_http_parser_settings)); +} + +size_t git_http_parser_execute( + git_http_parser *parser, + const char *data, + size_t len) +{ + struct http_parser_settings settings_proxy; + + memset(&settings_proxy, 0, sizeof(struct http_parser_settings)); + + settings_proxy.on_message_begin = parser->settings.on_message_begin ? on_message_begin : NULL; + settings_proxy.on_url = parser->settings.on_url ? on_url : NULL; + settings_proxy.on_header_field = parser->settings.on_header_field ? on_header_field : NULL; + settings_proxy.on_header_value = parser->settings.on_header_value ? on_header_value : NULL; + settings_proxy.on_headers_complete = parser->settings.on_headers_complete ? on_headers_complete : NULL; + settings_proxy.on_body = parser->settings.on_body ? on_body : NULL; + settings_proxy.on_message_complete = parser->settings.on_message_complete ? on_message_complete : NULL; + + return http_parser_execute(&parser->parser, &settings_proxy, data, len); +} + +#elif defined(GIT_HTTPPARSER_LLHTTP) || defined(GIT_HTTPPARSER_BUILTIN) + +# include + +size_t git_http_parser_execute( + git_http_parser *parser, + const char* data, + size_t len) +{ + llhttp_errno_t error; + size_t parsed_len; + + /* + * Unlike http_parser, which returns the number of parsed + * bytes in the _execute() call, llhttp returns an error + * code. + */ + + if (data == NULL || len == 0) + error = llhttp_finish(parser); + else + error = llhttp_execute(parser, data, len); + + parsed_len = len; + + /* + * Adjust number of parsed bytes in case of error. + */ + if (error != HPE_OK) { + parsed_len = llhttp_get_error_pos(parser) - data; + + /* This isn't a real pause, just a way to stop parsing early. */ + if (error == HPE_PAUSED_UPGRADE) + llhttp_resume_after_upgrade(parser); + } + + return parsed_len; +} + +#else +# error unknown http-parser +#endif diff --git a/src/libgit2/transports/httpparser.h b/src/libgit2/transports/httpparser.h new file mode 100644 index 00000000..1fe0dcf6 --- /dev/null +++ b/src/libgit2/transports/httpparser.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_httpparser_h__ +#define INCLUDE_transports_httpparser_h__ + +#include "git2_util.h" + +#if defined(GIT_HTTPPARSER_HTTPPARSER) + +# include + +typedef enum { + GIT_HTTP_PARSER_OK = HPE_OK, + GIT_HTTP_PARSER_PAUSED = HPE_PAUSED, +} git_http_parser_error_t; + +typedef enum { + GIT_HTTP_PARSER_REQUEST = HTTP_REQUEST, + GIT_HTTP_PARSER_RESPONSE = HTTP_RESPONSE, +} git_http_parser_t; + +typedef struct git_http_parser git_http_parser; + +typedef struct { + int (*on_message_begin)(git_http_parser *); + int (*on_url)(git_http_parser *, const char *, size_t); + int (*on_header_field)(git_http_parser *, const char *, size_t); + int (*on_header_value)(git_http_parser *, const char *, size_t); + int (*on_headers_complete)(git_http_parser *); + int (*on_body)(git_http_parser *, const char *, size_t); + int (*on_message_complete)(git_http_parser *); +} git_http_parser_settings; + +struct git_http_parser { + http_parser parser; + git_http_parser_settings settings; + void *data; +}; + +void git_http_parser_init( + git_http_parser *parser, + git_http_parser_t type, + git_http_parser_settings *settings); + +size_t git_http_parser_execute( + git_http_parser *parser, + const char *data, + size_t len); + +# define git_http_parser_status_code(parser) parser->parser.status_code +# define git_http_parser_keep_alive(parser) http_should_keep_alive(&parser->parser) +# define git_http_parser_pause(parser) (http_parser_pause(&parser->parser, 1), 0) +# define git_http_parser_resume(parser) http_parser_pause(&parser->parser, 0) +# define git_http_parser_remain_after_pause(parser) 1 +# define git_http_parser_errno(parser) parser->parser.http_errno +# define git_http_parser_errmsg(parser, errno) http_errno_description(errno) + +#elif defined(GIT_HTTPPARSER_LLHTTP) || defined(GIT_HTTPPARSER_BUILTIN) + +# include + +typedef enum { + GIT_HTTP_PARSER_OK = HPE_OK, + GIT_HTTP_PARSER_PAUSED = HPE_PAUSED, +} git_http_parser_error_t; + +typedef enum { + GIT_HTTP_PARSER_REQUEST = HTTP_REQUEST, + GIT_HTTP_PARSER_RESPONSE = HTTP_RESPONSE, +} git_http_parser_t; + +typedef llhttp_t git_http_parser; +typedef llhttp_settings_t git_http_parser_settings; + +# define git_http_parser_init(parser, direction, settings) llhttp_init(parser, (llhttp_type_t)direction, settings) + +size_t git_http_parser_execute( + git_http_parser *parser, + const char *data, + size_t len); + +# define git_http_parser_status_code(parser) parser->status_code +# define git_http_parser_keep_alive(parser) llhttp_should_keep_alive(parser) +# define git_http_parser_pause(parser) (llhttp_pause(parser), GIT_HTTP_PARSER_PAUSED) +# define git_http_parser_resume(parser) llhttp_resume(parser) +# define git_http_parser_remain_after_pause(parser) 0 +# define git_http_parser_errno(parser) parser->error +# define git_http_parser_errmsg(parser, errno) llhttp_get_error_reason(parser) + +#else +# error unknown http-parser +#endif + +#endif diff --git a/src/libgit2/transports/local.c b/src/libgit2/transports/local.c index 64c21afb..85439053 100644 --- a/src/libgit2/transports/local.c +++ b/src/libgit2/transports/local.c @@ -58,7 +58,7 @@ static void free_heads(git_vector *heads) git_vector_foreach(heads, i, head) free_head(head); - git_vector_free(heads); + git_vector_dispose(heads); } static int add_ref(transport_local *t, const char *name) @@ -108,10 +108,6 @@ static int add_ref(transport_local *t, const char *name) return error; } - /* If it's not a tag, we don't need to try to peel it */ - if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) - return 0; - if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0) return error; @@ -186,7 +182,7 @@ static int store_refs(transport_local *t) return 0; on_error: - git_vector_free(&t->refs); + git_vector_dispose(&t->refs); git_strarray_dispose(&ref_names); return -1; } @@ -303,6 +299,11 @@ static int local_negotiate_fetch( GIT_UNUSED(wants); + if (wants->depth) { + git_error_set(GIT_ERROR_NET, "shallow fetch is not supported by the local transport"); + return GIT_ENOTSUPPORTED; + } + /* Fill in the loids */ git_vector_foreach(&t->refs, i, rhead) { git_object *obj; @@ -453,7 +454,7 @@ static int local_push( default: last = git_error_last(); - if (last && last->message) + if (last->klass != GIT_ERROR_NONE) status->msg = git__strdup(last->message); else status->msg = git__strdup("Unspecified error encountered"); diff --git a/src/libgit2/transports/smart.c b/src/libgit2/transports/smart.c index 53727282..7bf964ac 100644 --- a/src/libgit2/transports/smart.c +++ b/src/libgit2/transports/smart.c @@ -124,7 +124,7 @@ static void free_symrefs(git_vector *symrefs) git__free(spec); } - git_vector_free(symrefs); + git_vector_dispose(symrefs); } static int git_smart__connect( @@ -249,6 +249,9 @@ static int git_smart__capabilities(unsigned int *capabilities, git_transport *tr *capabilities = 0; + if (t->caps.push_options) + *capabilities |= GIT_REMOTE_CAPABILITY_PUSH_OPTIONS; + if (t->caps.want_tip_sha1) *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID; @@ -370,17 +373,27 @@ static int git_smart__close(git_transport *transport) git_vector *common = &t->common; unsigned int i; git_pkt *p; + git_smart_service_t service; int ret; git_smart_subtransport_stream *stream; const char flush[] = "0000"; + if (t->direction == GIT_DIRECTION_FETCH) { + service = GIT_SERVICE_UPLOADPACK; + } else if (t->direction == GIT_DIRECTION_PUSH) { + service = GIT_SERVICE_RECEIVEPACK; + } else { + git_error_set(GIT_ERROR_NET, "invalid direction"); + return -1; + } + /* * If we're still connected at this point and not using RPC, * we should say goodbye by sending a flush, or git-daemon * will complain that we disconnected unexpectedly. */ if (t->connected && !t->rpc && - !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { + !t->wrapped->action(&stream, t->wrapped, t->url, service)) { t->current_stream->write(t->current_stream, flush, 4); } @@ -389,7 +402,7 @@ static int git_smart__close(git_transport *transport) git_vector_foreach(common, i, p) git_pkt_free(p); - git_vector_free(common); + git_vector_dispose(common); if (t->url) { git__free(t->url); @@ -414,11 +427,11 @@ static void git_smart__free(git_transport *transport) /* Free the subtransport */ t->wrapped->free(t->wrapped); - git_vector_free(&t->heads); + git_vector_dispose(&t->heads); git_vector_foreach(refs, i, p) git_pkt_free(p); - git_vector_free(refs); + git_vector_dispose(refs); git_remote_connect_options_dispose(&t->connect_opts); @@ -511,9 +524,8 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0 || git_vector_init(&t->heads, 16, ref_name_cmp) < 0 || definition->callback(&t->wrapped, &t->parent, definition->param) < 0) { - git_vector_free(&t->refs); - git_vector_free(&t->heads); - t->wrapped->free(t->wrapped); + git_vector_dispose(&t->refs); + git_vector_dispose(&t->heads); git__free(t); return -1; } diff --git a/src/libgit2/transports/smart.h b/src/libgit2/transports/smart.h index 52c7553a..c987d93b 100644 --- a/src/libgit2/transports/smart.h +++ b/src/libgit2/transports/smart.h @@ -38,6 +38,7 @@ #define GIT_CAP_SHALLOW "shallow" #define GIT_CAP_OBJECT_FORMAT "object-format=" #define GIT_CAP_AGENT "agent=" +#define GIT_CAP_PUSH_OPTIONS "push-options" extern bool git_smart__ofs_delta_enabled; @@ -146,7 +147,8 @@ typedef struct transport_smart_caps { thin_pack:1, want_tip_sha1:1, want_reachable_sha1:1, - shallow:1; + shallow:1, + push_options:1; char *object_format; char *agent; } transport_smart_caps; @@ -203,7 +205,7 @@ int git_smart__update_heads(transport_smart *t, git_vector *symrefs); /* smart_pkt.c */ typedef struct { git_oid_t oid_type; - int seen_capabilities: 1; + unsigned int seen_capabilities: 1; } git_pkt_parse_data; int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen, git_pkt_parse_data *data); diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c index 3307acfa..7ea8676e 100644 --- a/src/libgit2/transports/smart_pkt.c +++ b/src/libgit2/transports/smart_pkt.c @@ -536,10 +536,10 @@ static int parse_len(size_t *out, const char *line, size_t linelen) num[PKT_LEN_SIZE] = '\0'; for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit(num[i])) { + if (!git__isxdigit(num[i])) { /* Make sure there are no special characters before passing to error message */ for (k = 0; k < PKT_LEN_SIZE; ++k) { - if(!isprint(num[k])) { + if(!git__isprint(num[k])) { num[k] = '.'; } } diff --git a/src/libgit2/transports/smart_protocol.c b/src/libgit2/transports/smart_protocol.c index c9c422d4..87c19045 100644 --- a/src/libgit2/transports/smart_protocol.c +++ b/src/libgit2/transports/smart_protocol.c @@ -59,7 +59,7 @@ int git_smart__store_refs(transport_smart *t, int flushes) return recvd; if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); + git_error_set(GIT_ERROR_NET, "could not read refs from remote repository"); return GIT_EEOF; } @@ -194,6 +194,12 @@ int git_smart__detect_caps( continue; } + if (!git__prefixcmp(ptr, GIT_CAP_PUSH_OPTIONS)) { + caps->common = caps->push_options = 1; + ptr += strlen(GIT_CAP_PUSH_OPTIONS); + continue; + } + if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { caps->common = caps->thin_pack = 1; ptr += strlen(GIT_CAP_THIN_PACK); @@ -285,7 +291,7 @@ static int recv_pkt( if ((ret = git_smart__recv(t)) < 0) { return ret; } else if (ret == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); + git_error_set(GIT_ERROR_NET, "could not read from remote repository"); return GIT_EEOF; } } while (error); @@ -311,6 +317,13 @@ static int store_common(transport_smart *t) if ((error = recv_pkt(&pkt, NULL, t)) < 0) return error; + if (t->rpc && (pkt->type == GIT_PKT_SHALLOW || + pkt->type == GIT_PKT_UNSHALLOW || + pkt->type == GIT_PKT_FLUSH)) { + git__free(pkt); + continue; + } + if (pkt->type != GIT_PKT_ACK) { git__free(pkt); return 0; @@ -388,6 +401,7 @@ static int setup_shallow_roots( memcpy(out->ptr, wants->shallow_roots, sizeof(git_oid) * wants->shallow_roots_len); + out->size = wants->shallow_roots_len; } return 0; @@ -427,6 +441,9 @@ int git_smart__negotiate_fetch( if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) goto on_error; + if (!t->rpc) + git_str_clear(&data); + while ((error = recv_pkt((git_pkt **)&pkt, NULL, t)) == 0) { bool complete = false; @@ -778,33 +795,54 @@ int git_smart__download_pack( static int gen_pktline(git_str *buf, git_push *push) { push_spec *spec; + char *option; size_t i, len; - char old_id[GIT_OID_SHA1_HEXSIZE+1], new_id[GIT_OID_SHA1_HEXSIZE+1]; - - old_id[GIT_OID_SHA1_HEXSIZE] = '\0'; new_id[GIT_OID_SHA1_HEXSIZE] = '\0'; + char old_id[GIT_OID_MAX_HEXSIZE + 1], new_id[GIT_OID_MAX_HEXSIZE + 1]; + size_t old_id_len, new_id_len; git_vector_foreach(&push->specs, i, spec) { - len = 2*GIT_OID_SHA1_HEXSIZE + 7 + strlen(spec->refspec.dst); + len = strlen(spec->refspec.dst) + 7; if (i == 0) { - ++len; /* '\0' */ + /* Need a leading \0 */ + ++len; + if (push->report_status) len += strlen(GIT_CAP_REPORT_STATUS) + 1; + + if (git_vector_length(&push->remote_push_options) > 0) + len += strlen(GIT_CAP_PUSH_OPTIONS) + 1; + len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; } + old_id_len = git_oid_hexsize(git_oid_type(&spec->roid)); + new_id_len = git_oid_hexsize(git_oid_type(&spec->loid)); + + len += (old_id_len + new_id_len); + git_oid_fmt(old_id, &spec->roid); + old_id[old_id_len] = '\0'; + git_oid_fmt(new_id, &spec->loid); + new_id[new_id_len] = '\0'; - git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst); + git_str_printf(buf, "%04"PRIxZ"%.*s %.*s %s", len, + (int)old_id_len, old_id, (int)new_id_len, new_id, + spec->refspec.dst); if (i == 0) { git_str_putc(buf, '\0'); + /* Core git always starts their capabilities string with a space */ if (push->report_status) { git_str_putc(buf, ' '); git_str_printf(buf, GIT_CAP_REPORT_STATUS); } + if (git_vector_length(&push->remote_push_options) > 0) { + git_str_putc(buf, ' '); + git_str_printf(buf, GIT_CAP_PUSH_OPTIONS); + } git_str_putc(buf, ' '); git_str_printf(buf, GIT_CAP_SIDE_BAND_64K); } @@ -812,6 +850,13 @@ static int gen_pktline(git_str *buf, git_push *push) git_str_putc(buf, '\n'); } + if (git_vector_length(&push->remote_push_options) > 0) { + git_str_printf(buf, "0000"); + git_vector_foreach(&push->remote_push_options, i, option) { + git_str_printf(buf, "%04"PRIxZ"%s", strlen(option) + 4 , option); + } + } + git_str_puts(buf, "0000"); return git_str_oom(buf) ? -1 : 0; } @@ -940,7 +985,7 @@ static int parse_report(transport_smart *transport, git_push *push) } if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); + git_error_set(GIT_ERROR_NET, "could not read report from remote repository"); error = GIT_EEOF; goto done; } @@ -1157,7 +1202,7 @@ int git_smart__push(git_transport *transport, git_push *push) #ifdef PUSH_DEBUG { git_remote_head *head; - char hex[GIT_OID_SHA1_HEXSIZE+1]; hex[GIT_OID_SHA1_HEXSIZE] = '\0'; + char hex[GIT_OID_MAX_HEXSIZE+1], hex[GIT_OID_MAX_HEXSIZE] = '\0'; git_vector_foreach(&push->remote->refs, i, head) { git_oid_fmt(hex, &head->oid); diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c index de63d454..3f3a127f 100644 --- a/src/libgit2/transports/ssh.c +++ b/src/libgit2/transports/ssh.c @@ -5,1090 +5,67 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "ssh.h" +#include "ssh_exec.h" +#include "ssh_libssh2.h" -#ifdef GIT_SSH -#include -#endif - -#include "runtime.h" -#include "net.h" -#include "smart.h" -#include "streams/socket.h" -#include "sysdir.h" - -#include "git2/credential.h" -#include "git2/sys/credential.h" - -#ifdef GIT_SSH - -#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) - -static const char cmd_uploadpack[] = "git-upload-pack"; -static const char cmd_receivepack[] = "git-receive-pack"; - -typedef struct { - git_smart_subtransport_stream parent; - git_stream *io; - LIBSSH2_SESSION *session; - LIBSSH2_CHANNEL *channel; - const char *cmd; - git_net_url url; - unsigned sent_command : 1; -} ssh_stream; - -typedef struct { - git_smart_subtransport parent; - transport_smart *owner; - ssh_stream *current_stream; - git_credential *cred; - char *cmd_uploadpack; - char *cmd_receivepack; -} ssh_subtransport; - -static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); - -static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) -{ - char *ssherr; - libssh2_session_last_error(session, &ssherr, NULL, 0); - - git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); -} - -/* - * Create a git protocol request. - * - * For example: git-upload-pack '/libgit2/libgit2' - */ -static int gen_proto(git_str *request, const char *cmd, git_net_url *url) -{ - const char *repo; - - repo = url->path; - - if (repo && repo[0] == '/' && repo[1] == '~') - repo++; - - if (!repo || !repo[0]) { - git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); - return -1; - } - - git_str_puts(request, cmd); - git_str_puts(request, " '"); - git_str_puts(request, repo); - git_str_puts(request, "'"); - - if (git_str_oom(request)) - return -1; - - return 0; -} - -static int send_command(ssh_stream *s) -{ - int error; - git_str request = GIT_STR_INIT; - - error = gen_proto(&request, s->cmd, &s->url); - if (error < 0) - goto cleanup; - - error = libssh2_channel_exec(s->channel, request.ptr); - if (error < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not execute request"); - goto cleanup; - } - - s->sent_command = 1; - -cleanup: - git_str_dispose(&request); - return error; -} - -static int ssh_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) -{ - int rc; - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - - *bytes_read = 0; - - if (!s->sent_command && send_command(s) < 0) - return -1; - - if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not read data"); - return -1; - } - - /* - * If we can't get anything out of stdout, it's typically a - * not-found error, so read from stderr and signal EOF on - * stderr. - */ - if (rc == 0) { - if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { - git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer); - return GIT_EEOF; - } else if (rc < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not read stderr"); - return -1; - } - } - - - *bytes_read = rc; - - return 0; -} - -static int ssh_stream_write( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - size_t off = 0; - ssize_t ret = 0; - - if (!s->sent_command && send_command(s) < 0) - return -1; - - do { - ret = libssh2_channel_write(s->channel, buffer + off, len - off); - if (ret < 0) - break; - - off += ret; - - } while (off < len); - - if (ret < 0) { - ssh_error(s->session, "SSH could not write data"); - return -1; - } - - return 0; -} - -static void ssh_stream_free(git_smart_subtransport_stream *stream) -{ - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - ssh_subtransport *t; - - if (!stream) - return; - - t = OWNING_SUBTRANSPORT(s); - t->current_stream = NULL; - - if (s->channel) { - libssh2_channel_close(s->channel); - libssh2_channel_free(s->channel); - s->channel = NULL; - } - - if (s->session) { - libssh2_session_disconnect(s->session, "closing transport"); - libssh2_session_free(s->session); - s->session = NULL; - } - - if (s->io) { - git_stream_close(s->io); - git_stream_free(s->io); - s->io = NULL; - } - - git_net_url_dispose(&s->url); - git__free(s); -} - -static int ssh_stream_alloc( - ssh_subtransport *t, - const char *cmd, - git_smart_subtransport_stream **stream) -{ - ssh_stream *s; - - GIT_ASSERT_ARG(stream); - - s = git__calloc(sizeof(ssh_stream), 1); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = ssh_stream_read; - s->parent.write = ssh_stream_write; - s->parent.free = ssh_stream_free; - - s->cmd = cmd; - - *stream = &s->parent; - return 0; -} - -static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) { - int rc = LIBSSH2_ERROR_NONE; - - struct libssh2_agent_publickey *curr, *prev = NULL; - - LIBSSH2_AGENT *agent = libssh2_agent_init(session); - - if (agent == NULL) - return -1; - - rc = libssh2_agent_connect(agent); - - if (rc != LIBSSH2_ERROR_NONE) { - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - goto shutdown; - } - - rc = libssh2_agent_list_identities(agent); - - if (rc != LIBSSH2_ERROR_NONE) - goto shutdown; - - while (1) { - rc = libssh2_agent_get_identity(agent, &curr, prev); - - if (rc < 0) - goto shutdown; +#include "transports/smart.h" - /* rc is set to 1 whenever the ssh agent ran out of keys to check. - * Set the error code to authentication failure rather than erroring - * out with an untranslatable error code. - */ - if (rc == 1) { - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - goto shutdown; - } - - rc = libssh2_agent_userauth(agent, c->username, curr); - - if (rc == 0) - break; - - prev = curr; - } - -shutdown: - - if (rc != LIBSSH2_ERROR_NONE) - ssh_error(session, "error authenticating"); - - libssh2_agent_disconnect(agent); - libssh2_agent_free(agent); - - return rc; -} - -static int _git_ssh_authenticate_session( - LIBSSH2_SESSION *session, - git_credential *cred) -{ - int rc; - - do { - git_error_clear(); - switch (cred->credtype) { - case GIT_CREDENTIAL_USERPASS_PLAINTEXT: { - git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; - rc = libssh2_userauth_password(session, c->username, c->password); - break; - } - case GIT_CREDENTIAL_SSH_KEY: { - git_credential_ssh_key *c = (git_credential_ssh_key *)cred; - - if (c->privatekey) - rc = libssh2_userauth_publickey_fromfile( - session, c->username, c->publickey, - c->privatekey, c->passphrase); - else - rc = ssh_agent_auth(session, c); - - break; - } - case GIT_CREDENTIAL_SSH_CUSTOM: { - git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; - - rc = libssh2_userauth_publickey( - session, c->username, (const unsigned char *)c->publickey, - c->publickey_len, c->sign_callback, &c->payload); - break; - } - case GIT_CREDENTIAL_SSH_INTERACTIVE: { - void **abstract = libssh2_session_abstract(session); - git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; - - /* ideally, we should be able to set this by calling - * libssh2_session_init_ex() instead of libssh2_session_init(). - * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() - * allows you to pass the `abstract` as part of the call, whereas - * libssh2_userauth_keyboard_interactive() does not! - * - * The only way to set the `abstract` pointer is by calling - * libssh2_session_abstract(), which will replace the existing - * pointer as is done below. This is safe for now (at time of writing), - * but may not be valid in future. - */ - *abstract = c->payload; - - rc = libssh2_userauth_keyboard_interactive( - session, c->username, c->prompt_callback); - break; - } -#ifdef GIT_SSH_MEMORY_CREDENTIALS - case GIT_CREDENTIAL_SSH_MEMORY: { - git_credential_ssh_key *c = (git_credential_ssh_key *)cred; - - GIT_ASSERT(c->username); - GIT_ASSERT(c->privatekey); - - rc = libssh2_userauth_publickey_frommemory( - session, - c->username, - strlen(c->username), - c->publickey, - c->publickey ? strlen(c->publickey) : 0, - c->privatekey, - strlen(c->privatekey), - c->passphrase); - break; - } -#endif - default: - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - } - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || - rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || - rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) - return GIT_EAUTH; - - if (rc != LIBSSH2_ERROR_NONE) { - if (!git_error_last()) - ssh_error(session, "Failed to authenticate SSH session"); - return -1; - } - - return 0; -} - -static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) -{ - int error, no_callback = 0; - git_credential *cred = NULL; - - if (!t->owner->connect_opts.callbacks.credentials) { - no_callback = 1; - } else { - error = t->owner->connect_opts.callbacks.credentials( - &cred, - t->owner->url, - user, - auth_methods, - t->owner->connect_opts.callbacks.payload); - - if (error == GIT_PASSTHROUGH) { - no_callback = 1; - } else if (error < 0) { - return error; - } else if (!cred) { - git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); - return -1; - } - } - - if (no_callback) { - git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); - return GIT_EAUTH; - } - - if (!(cred->credtype & auth_methods)) { - cred->free(cred); - git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); - return GIT_EAUTH; - } - - *out = cred; - - return 0; -} - -#define SSH_DIR ".ssh" -#define KNOWN_HOSTS_FILE "known_hosts" - -/* - * Load the known_hosts file. - * - * Returns success but leaves the output NULL if we couldn't find the file. - */ -static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session) -{ - git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT; - LIBSSH2_KNOWNHOSTS *known_hosts = NULL; - int error; - - GIT_ASSERT_ARG(hosts); - - if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 || - (error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0) - goto out; - - if ((known_hosts = libssh2_knownhost_init(session)) == NULL) { - ssh_error(session, "error initializing known hosts"); - error = -1; - goto out; - } - - /* - * Try to read the file and consider not finding it as not trusting the - * host rather than an error. - */ - error = libssh2_knownhost_readfile(known_hosts, git_str_cstr(&path), LIBSSH2_KNOWNHOST_FILE_OPENSSH); - if (error == LIBSSH2_ERROR_FILE) - error = 0; - if (error < 0) - ssh_error(session, "error reading known_hosts"); - -out: - *hosts = known_hosts; - - git_str_dispose(&sshdir); - git_str_dispose(&path); - - return error; -} - -static void add_hostkey_pref_if_avail( - LIBSSH2_KNOWNHOSTS *known_hosts, - const char *hostname, - int port, - git_str *prefs, - int type, - const char *type_name) -{ - struct libssh2_knownhost *host = NULL; - const char key = '\0'; - int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type; - int error; - - error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host); - if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { - if (git_str_len(prefs) > 0) { - git_str_putc(prefs, ','); - } - git_str_puts(prefs, type_name); - } -} - -/* - * We figure out what kind of key we want to ask the remote for by trying to - * look it up with a nonsense key and using that mismatch to figure out what key - * we do have stored for the host. - * - * Populates prefs with the string to pass to libssh2_session_method_pref. - */ -static void find_hostkey_preference( - LIBSSH2_KNOWNHOSTS *known_hosts, - const char *hostname, - int port, - git_str *prefs) -{ - /* - * The order here is important as it indicates the priority of what will - * be preferred. - */ -#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519"); -#endif -#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256 - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256"); - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384"); - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521"); -#endif - add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa"); -} - -static int _git_ssh_session_create( - LIBSSH2_SESSION **session, - LIBSSH2_KNOWNHOSTS **hosts, - const char *hostname, - int port, - git_stream *io) -{ - git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); - LIBSSH2_SESSION *s; - LIBSSH2_KNOWNHOSTS *known_hosts; - git_str prefs = GIT_STR_INIT; - int rc = 0; - - GIT_ASSERT_ARG(session); - GIT_ASSERT_ARG(hosts); - - s = libssh2_session_init(); - if (!s) { - git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); - return -1; - } - - if ((rc = load_known_hosts(&known_hosts, s)) < 0) { - ssh_error(s, "error loading known_hosts"); - libssh2_session_free(s); - return -1; - } - - find_hostkey_preference(known_hosts, hostname, port, &prefs); - if (git_str_len(&prefs) > 0) { - do { - rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs)); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - if (rc != LIBSSH2_ERROR_NONE) { - ssh_error(s, "failed to set hostkey preference"); - goto on_error; - } - } - git_str_dispose(&prefs); - - do { - rc = libssh2_session_handshake(s, socket->s); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (rc != LIBSSH2_ERROR_NONE) { - ssh_error(s, "failed to start SSH session"); - goto on_error; - } - - libssh2_session_set_blocking(s, 1); - - *session = s; - *hosts = known_hosts; - - return 0; - -on_error: - libssh2_knownhost_free(known_hosts); - libssh2_session_free(s); - return -1; -} - - -/* - * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on - * the type of key that libssh2_session_hostkey returns. - */ -static int fingerprint_type_mask(int keytype) -{ - int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW; - return mask; - - switch (keytype) { - case LIBSSH2_HOSTKEY_TYPE_RSA: - mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; - break; - case LIBSSH2_HOSTKEY_TYPE_DSS: - mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; - break; -#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 - case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: - mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: - mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: - mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521; - break; -#endif -#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 - case LIBSSH2_HOSTKEY_TYPE_ED25519: - mask |= LIBSSH2_KNOWNHOST_KEY_ED25519; - break; -#endif - } - - return mask; -} - -/* - * Check the host against the user's known_hosts file. - * - * Returns 1/0 for valid/''not-valid or <0 for an error - */ -static int check_against_known_hosts( - LIBSSH2_SESSION *session, - LIBSSH2_KNOWNHOSTS *known_hosts, - const char *hostname, - int port, - const char *key, - size_t key_len, - int key_type) -{ - int check, typemask, ret = 0; - struct libssh2_knownhost *host = NULL; - - if (known_hosts == NULL) - return 0; - - typemask = fingerprint_type_mask(key_type); - check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host); - if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) { - ssh_error(session, "error checking for known host"); - return -1; - } - - ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0; - - return ret; -} - -/* - * Perform the check for the session's certificate against known hosts if - * possible and then ask the user if they have a callback. - * - * Returns 1/0 for valid/not-valid or <0 for an error - */ -static int check_certificate( - LIBSSH2_SESSION *session, - LIBSSH2_KNOWNHOSTS *known_hosts, - git_transport_certificate_check_cb check_cb, - void *check_cb_payload, - const char *host, - int port) -{ - git_cert_hostkey cert = {{ 0 }}; - const char *key; - size_t cert_len; - int cert_type, cert_valid = 0, error = 0; - - if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) { - ssh_error(session, "failed to retrieve hostkey"); - return -1; - } - - if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0) - return -1; - - cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; - if (key != NULL) { - cert.type |= GIT_CERT_SSH_RAW; - cert.hostkey = key; - cert.hostkey_len = cert_len; - switch (cert_type) { - case LIBSSH2_HOSTKEY_TYPE_RSA: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA; - break; - case LIBSSH2_HOSTKEY_TYPE_DSS: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS; - break; - -#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 - case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384; - break; - case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521; - break; -#endif - -#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 - case LIBSSH2_HOSTKEY_TYPE_ED25519: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519; - break; -#endif - default: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN; - } - } - -#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_SHA256; - memcpy(&cert.hash_sha256, key, 32); - } -#endif - - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_SHA1; - memcpy(&cert.hash_sha1, key, 20); - } - - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_MD5; - memcpy(&cert.hash_md5, key, 16); - } - - if (cert.type == 0) { - git_error_set(GIT_ERROR_SSH, "unable to get the host key"); - return -1; - } - - git_error_clear(); - error = 0; - if (!cert_valid) { - git_error_set(GIT_ERROR_SSH, "invalid or unknown remote ssh hostkey"); - error = GIT_ECERTIFICATE; - } - - if (check_cb != NULL) { - git_cert_hostkey *cert_ptr = &cert; - git_error_state previous_error = {0}; - - git_error_state_capture(&previous_error, error); - error = check_cb((git_cert *) cert_ptr, cert_valid, host, check_cb_payload); - if (error == GIT_PASSTHROUGH) { - error = git_error_state_restore(&previous_error); - } else if (error < 0 && !git_error_last()) { - git_error_set(GIT_ERROR_NET, "unknown remote host key"); - } - - git_error_state_free(&previous_error); - } - - return error; -} - -#define SSH_DEFAULT_PORT "22" - -static int _git_ssh_setup_conn( - ssh_subtransport *t, - const char *url, - const char *cmd, - git_smart_subtransport_stream **stream) -{ - int auth_methods, error = 0, port; - ssh_stream *s; - git_credential *cred = NULL; - LIBSSH2_SESSION *session=NULL; - LIBSSH2_CHANNEL *channel=NULL; - LIBSSH2_KNOWNHOSTS *known_hosts = NULL; - - t->current_stream = NULL; - - *stream = NULL; - if (ssh_stream_alloc(t, cmd, stream) < 0) - return -1; - - s = (ssh_stream *)*stream; - s->session = NULL; - s->channel = NULL; - - if ((error = git_net_url_parse_standard_or_scp(&s->url, url)) < 0 || - (error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || - (error = git_stream_connect(s->io)) < 0) - goto done; - - /* - * Try to parse the port as a number, if we can't then fall back to - * default. It would be nice if we could get the port that was resolved - * as part of the stream connection, but that's not something that's - * exposed. - */ - if (git__strntol32(&port, s->url.port, strlen(s->url.port), NULL, 10) < 0) { - git_error_set(GIT_ERROR_NET, "invalid port to ssh: %s", s->url.port); - error = -1; - goto done; - } - - if ((error = _git_ssh_session_create(&session, &known_hosts, s->url.host, port, s->io)) < 0) - goto done; - - if ((error = check_certificate(session, known_hosts, t->owner->connect_opts.callbacks.certificate_check, t->owner->connect_opts.callbacks.payload, s->url.host, port)) < 0) - goto done; - - /* we need the username to ask for auth methods */ - if (!s->url.username) { - if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) - goto done; - - s->url.username = git__strdup(((git_credential_username *) cred)->username); - cred->free(cred); - cred = NULL; - if (!s->url.username) - goto done; - } else if (s->url.username && s->url.password) { - if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0) - goto done; - } - - if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) - goto done; - - error = GIT_EAUTH; - /* if we already have something to try */ - if (cred && auth_methods & cred->credtype) - error = _git_ssh_authenticate_session(session, cred); - - while (error == GIT_EAUTH) { - if (cred) { - cred->free(cred); - cred = NULL; - } - - if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0) - goto done; - - if (strcmp(s->url.username, git_credential_get_username(cred))) { - git_error_set(GIT_ERROR_SSH, "username does not match previous request"); - error = -1; - goto done; - } - - error = _git_ssh_authenticate_session(session, cred); - - if (error == GIT_EAUTH) { - /* refresh auth methods */ - if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) - goto done; - else - error = GIT_EAUTH; - } - } - - if (error < 0) - goto done; - - channel = libssh2_channel_open_session(session); - if (!channel) { - error = -1; - ssh_error(session, "Failed to open SSH channel"); - goto done; - } - - libssh2_channel_set_blocking(channel, 1); - - s->session = session; - s->channel = channel; - - t->current_stream = s; - -done: - if (known_hosts) - libssh2_knownhost_free(known_hosts); - - if (error < 0) { - ssh_stream_free(*stream); - - if (session) - libssh2_session_free(session); - } - - if (cred) - cred->free(cred); - - return error; -} - -static int ssh_uploadpack_ls( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; - - return _git_ssh_setup_conn(t, url, cmd, stream); -} - -static int ssh_uploadpack( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); - return -1; -} - -static int ssh_receivepack_ls( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; - - - return _git_ssh_setup_conn(t, url, cmd, stream); -} - -static int ssh_receivepack( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); - return -1; -} - -static int _ssh_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return ssh_uploadpack_ls(t, url, stream); - - case GIT_SERVICE_UPLOADPACK: - return ssh_uploadpack(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK_LS: - return ssh_receivepack_ls(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK: - return ssh_receivepack(t, url, stream); - } +int git_smart_subtransport_ssh( + git_smart_subtransport **out, + git_transport *owner, + void *param) +{ +#ifdef GIT_SSH_LIBSSH2 + return git_smart_subtransport_ssh_libssh2(out, owner, param); +#elif GIT_SSH_EXEC + return git_smart_subtransport_ssh_exec(out, owner, param); +#else + GIT_UNUSED(out); + GIT_UNUSED(owner); + GIT_UNUSED(param); - *stream = NULL; + git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport; library was built without SSH support"); return -1; -} - -static int _ssh_close(git_smart_subtransport *subtransport) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - GIT_ASSERT(!t->current_stream); - - GIT_UNUSED(t); - - return 0; -} - -static void _ssh_free(git_smart_subtransport *subtransport) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - git__free(t->cmd_uploadpack); - git__free(t->cmd_receivepack); - git__free(t); -} - -#define SSH_AUTH_PUBLICKEY "publickey" -#define SSH_AUTH_PASSWORD "password" -#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" - -static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) -{ - const char *list, *ptr; - - *out = 0; - - list = libssh2_userauth_list(session, username, strlen(username)); - - /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ - if (list == NULL && !libssh2_userauth_authenticated(session)) { - ssh_error(session, "remote rejected authentication"); - return GIT_EAUTH; - } - - ptr = list; - while (ptr) { - if (*ptr == ',') - ptr++; - - if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { - *out |= GIT_CREDENTIAL_SSH_KEY; - *out |= GIT_CREDENTIAL_SSH_CUSTOM; -#ifdef GIT_SSH_MEMORY_CREDENTIALS - *out |= GIT_CREDENTIAL_SSH_MEMORY; #endif - ptr += strlen(SSH_AUTH_PUBLICKEY); - continue; - } - - if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { - *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - ptr += strlen(SSH_AUTH_PASSWORD); - continue; - } - - if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { - *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; - ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); - continue; - } - - /* Skip it if we don't know it */ - ptr = strchr(ptr, ','); - } - - return 0; } -#endif -int git_smart_subtransport_ssh( - git_smart_subtransport **out, git_transport *owner, void *param) +static int transport_set_paths(git_transport *t, git_strarray *paths) { -#ifdef GIT_SSH - ssh_subtransport *t; + transport_smart *smart = (transport_smart *)t; - GIT_ASSERT_ARG(out); - - GIT_UNUSED(param); - - t = git__calloc(sizeof(ssh_subtransport), 1); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = (transport_smart *)owner; - t->parent.action = _ssh_action; - t->parent.close = _ssh_close; - t->parent.free = _ssh_free; - - *out = (git_smart_subtransport *) t; - return 0; +#ifdef GIT_SSH_LIBSSH2 + return git_smart_subtransport_ssh_libssh2_set_paths( + (git_smart_subtransport *)smart->wrapped, + paths->strings[0], + paths->strings[1]); +#elif GIT_SSH_EXEC + return git_smart_subtransport_ssh_exec_set_paths( + (git_smart_subtransport *)smart->wrapped, + paths->strings[0], + paths->strings[1]); #else - GIT_UNUSED(owner); - GIT_UNUSED(param); - - GIT_ASSERT_ARG(out); - *out = NULL; + GIT_UNUSED(t); + GIT_UNUSED(smart); + GIT_UNUSED(paths); - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); + GIT_ASSERT(!"cannot create SSH library; library was built without SSH support"); return -1; #endif } -int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) +int git_transport_ssh_with_paths( + git_transport **out, + git_remote *owner, + void *payload) { -#ifdef GIT_SSH git_strarray *paths = (git_strarray *) payload; git_transport *transport; - transport_smart *smart; - ssh_subtransport *t; int error; + git_smart_subtransport_definition ssh_definition = { git_smart_subtransport_ssh, 0, /* no RPC */ - NULL, + NULL }; if (paths->count != 2) { @@ -1099,49 +76,10 @@ int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *p if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) return error; - smart = (transport_smart *) transport; - t = (ssh_subtransport *) smart->wrapped; - - t->cmd_uploadpack = git__strdup(paths->strings[0]); - GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); - t->cmd_receivepack = git__strdup(paths->strings[1]); - GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + if ((error = transport_set_paths(transport, paths)) < 0) + return error; *out = transport; return 0; -#else - GIT_UNUSED(owner); - GIT_UNUSED(payload); - - GIT_ASSERT_ARG(out); - *out = NULL; - - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); - return -1; -#endif -} - -#ifdef GIT_SSH -static void shutdown_ssh(void) -{ - libssh2_exit(); } -#endif - -int git_transport_ssh_global_init(void) -{ -#ifdef GIT_SSH - if (libssh2_init(0) < 0) { - git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); - return -1; - } - - return git_runtime_shutdown_register(shutdown_ssh); -#else - - /* Nothing to initialize */ - return 0; - -#endif -} diff --git a/src/libgit2/transports/ssh_exec.c b/src/libgit2/transports/ssh_exec.c new file mode 100644 index 00000000..91d46a38 --- /dev/null +++ b/src/libgit2/transports/ssh_exec.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "ssh_exec.h" + +#ifdef GIT_SSH_EXEC + +#include "common.h" + +#include "config.h" +#include "net.h" +#include "path.h" +#include "futils.h" +#include "process.h" +#include "transports/smart.h" + +typedef struct { + git_smart_subtransport_stream parent; +} ssh_exec_subtransport_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + + ssh_exec_subtransport_stream *current_stream; + + char *cmd_uploadpack; + char *cmd_receivepack; + + git_smart_service_t action; + git_process *process; +} ssh_exec_subtransport; + +static int ssh_exec_subtransport_stream_read( + git_smart_subtransport_stream *s, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + ssh_exec_subtransport *transport; + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + ssize_t ret; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT(stream->parent.subtransport); + + transport = (ssh_exec_subtransport *)stream->parent.subtransport; + + if ((ret = git_process_read(transport->process, buffer, buf_size)) < 0) { + return (int)ret; + } + + *bytes_read = (size_t)ret; + return 0; +} + +static int ssh_exec_subtransport_stream_write( + git_smart_subtransport_stream *s, + const char *buffer, + size_t len) +{ + ssh_exec_subtransport *transport; + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + ssize_t ret; + + GIT_ASSERT(stream && stream->parent.subtransport); + + transport = (ssh_exec_subtransport *)stream->parent.subtransport; + + while (len > 0) { + if ((ret = git_process_write(transport->process, buffer, len)) < 0) + return (int)ret; + + len -= ret; + } + + return 0; +} + +static void ssh_exec_subtransport_stream_free(git_smart_subtransport_stream *s) +{ + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + + git__free(stream); +} + +static int ssh_exec_subtransport_stream_init( + ssh_exec_subtransport_stream **out, + ssh_exec_subtransport *transport) +{ + GIT_ASSERT_ARG(out); + + *out = git__calloc(sizeof(ssh_exec_subtransport_stream), 1); + GIT_ERROR_CHECK_ALLOC(*out); + + (*out)->parent.subtransport = &transport->parent; + (*out)->parent.read = ssh_exec_subtransport_stream_read; + (*out)->parent.write = ssh_exec_subtransport_stream_write; + (*out)->parent.free = ssh_exec_subtransport_stream_free; + + return 0; +} + +GIT_INLINE(int) ensure_transport_state( + ssh_exec_subtransport *transport, + git_smart_service_t expected, + git_smart_service_t next) +{ + if (transport->action != expected && transport->action != next) { + git_error_set(GIT_ERROR_NET, "invalid transport state"); + + return -1; + } + + return 0; +} + +static int get_ssh_cmdline( + git_vector *args, + bool *use_shell, + ssh_exec_subtransport *transport, + git_net_url *url, + const char *command) +{ + git_remote *remote = ((transport_smart *)transport->owner)->owner; + git_repository *repo = remote->repo; + git_config *cfg; + git_str ssh_cmd = GIT_STR_INIT, url_and_host = GIT_STR_INIT, + remote_cmd = GIT_STR_INIT; + const char *default_ssh_cmd = "ssh"; + int error; + + /* + * Safety check: like git, we forbid paths that look like an + * option as that could lead to injection to ssh that can make + * us do unexpected things + */ + if (git_process__is_cmdline_option(url->username)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: username '%s' is ambiguous with command-line option", url->username); + return -1; + } else if (git_process__is_cmdline_option(url->host)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: host '%s' is ambiguous with command-line option", url->host); + return -1; + } else if (git_process__is_cmdline_option(url->path)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: path '%s' is ambiguous with command-line option", url->path); + return -1; + } + + if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) + return error; + + if ((error = git__getenv(&ssh_cmd, "GIT_SSH_COMMAND")) == 0) + *use_shell = true; + else if (error != GIT_ENOTFOUND) + goto done; + else if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0) + *use_shell = false; + else if (error != GIT_ENOTFOUND) + goto done; + else if ((error = git_config__get_string_buf(&ssh_cmd, cfg, "core.sshcommand")) < 0 && error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + + if (!ssh_cmd.size && + git_str_puts(&ssh_cmd, default_ssh_cmd) < 0) + goto done; + + if ((error = git_vector_insert(args, git_str_detach(&ssh_cmd))) < 0) + goto done; + + if (url->port_specified) { + char *p = git__strdup("-p"); + char *port = git__strdup(url->port); + + if (!p || !port || + (error = git_vector_insert(args, p)) < 0 || + (error = git_vector_insert(args, port)) < 0) + goto done; + } + + if (url->username) { + if ((error = git_str_puts(&url_and_host, url->username)) < 0 || + (error = git_str_putc(&url_and_host, '@')) < 0) + goto done; + } + + if ((error = git_str_puts(&url_and_host, url->host)) < 0 || + (error = git_vector_insert(args, git_str_detach(&url_and_host))) < 0) + goto done; + + if ((error = git_str_puts(&remote_cmd, command)) < 0 || + (error = git_str_puts(&remote_cmd, " '")) < 0 || + (error = git_str_puts_escaped(&remote_cmd, url->path, "'!", "'\\", "'")) < 0 || + (error = git_str_puts(&remote_cmd, "'")) < 0 || + (error = git_vector_insert(args, git_str_detach(&remote_cmd))) < 0) + goto done; + +done: + git_str_dispose(&ssh_cmd); + git_str_dispose(&url_and_host); + git_str_dispose(&remote_cmd); + git_config_free(cfg); + return error; +} + +static int start_ssh( + ssh_exec_subtransport *transport, + git_smart_service_t action, + const char *sshpath) +{ + const char *env[] = { "GIT_DIR=" }; + + git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT; + git_net_url url = GIT_NET_URL_INIT; + git_vector args = GIT_VECTOR_INIT; + bool use_shell = false; + const char *command; + int error; + + process_opts.capture_in = 1; + process_opts.capture_out = 1; + process_opts.capture_err = 0; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + command = transport->cmd_uploadpack ? + transport->cmd_uploadpack : "git-upload-pack"; + break; + case GIT_SERVICE_RECEIVEPACK_LS: + command = transport->cmd_receivepack ? + transport->cmd_receivepack : "git-receive-pack"; + break; + default: + git_error_set(GIT_ERROR_NET, "invalid action"); + error = -1; + goto done; + } + + if (git_net_str_is_url(sshpath)) + error = git_net_url_parse(&url, sshpath); + else + error = git_net_url_parse_scp(&url, sshpath); + + if (error < 0) + goto done; + + if ((error = get_ssh_cmdline(&args, &use_shell, + transport, &url, command)) < 0) + goto done; + + process_opts.use_shell = use_shell; + + if ((error = git_process_new(&transport->process, + (const char **)args.contents, args.length, + env, ARRAY_SIZE(env), &process_opts)) < 0 || + (error = git_process_start(transport->process)) < 0) { + git_process_free(transport->process); + transport->process = NULL; + goto done; + } + +done: + git_vector_dispose_deep(&args); + git_net_url_dispose(&url); + return error; +} + +static int ssh_exec_subtransport_action( + git_smart_subtransport_stream **out, + git_smart_subtransport *t, + const char *sshpath, + git_smart_service_t action) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + ssh_exec_subtransport_stream *stream = NULL; + git_smart_service_t expected; + int error; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + case GIT_SERVICE_RECEIVEPACK_LS: + if ((error = ensure_transport_state(transport, 0, 0)) < 0 || + (error = ssh_exec_subtransport_stream_init(&stream, transport)) < 0 || + (error = start_ssh(transport, action, sshpath)) < 0) + goto on_error; + + transport->current_stream = stream; + break; + + case GIT_SERVICE_UPLOADPACK: + case GIT_SERVICE_RECEIVEPACK: + expected = (action == GIT_SERVICE_UPLOADPACK) ? + GIT_SERVICE_UPLOADPACK_LS : GIT_SERVICE_RECEIVEPACK_LS; + + if ((error = ensure_transport_state(transport, expected, action)) < 0) + goto on_error; + + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid service request"); + goto on_error; + } + + transport->action = action; + *out = &transport->current_stream->parent; + + return 0; + +on_error: + if (stream != NULL) + ssh_exec_subtransport_stream_free(&stream->parent); + + return -1; +} + +static int ssh_exec_subtransport_close(git_smart_subtransport *t) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + + if (transport->process) { + git_process_close(transport->process); + git_process_free(transport->process); + transport->process = NULL; + } + + transport->action = 0; + + return 0; +} + +static void ssh_exec_subtransport_free(git_smart_subtransport *t) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + + git__free(transport->cmd_uploadpack); + git__free(transport->cmd_receivepack); + git__free(transport); +} + +int git_smart_subtransport_ssh_exec( + git_smart_subtransport **out, + git_transport *owner, + void *payload) +{ + ssh_exec_subtransport *transport; + + GIT_UNUSED(payload); + + transport = git__calloc(sizeof(ssh_exec_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(transport); + + transport->owner = owner; + transport->parent.action = ssh_exec_subtransport_action; + transport->parent.close = ssh_exec_subtransport_close; + transport->parent.free = ssh_exec_subtransport_free; + + *out = (git_smart_subtransport *) transport; + return 0; +} + +int git_smart_subtransport_ssh_exec_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack) +{ + ssh_exec_subtransport *t = (ssh_exec_subtransport *)subtransport; + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + + t->cmd_uploadpack = git__strdup(cmd_uploadpack); + GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); + + t->cmd_receivepack = git__strdup(cmd_receivepack); + GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + + return 0; +} + +#endif diff --git a/src/libgit2/transports/ssh_exec.h b/src/libgit2/transports/ssh_exec.h new file mode 100644 index 00000000..4bcba06b --- /dev/null +++ b/src/libgit2/transports/ssh_exec.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_transports_ssh_exec_h__ +#define INCLUDE_transports_ssh_exec_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" + +int git_smart_subtransport_ssh_exec( + git_smart_subtransport **out, + git_transport *owner, + void *param); + +int git_smart_subtransport_ssh_exec_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack); + +#endif diff --git a/src/libgit2/transports/ssh_libssh2.c b/src/libgit2/transports/ssh_libssh2.c new file mode 100644 index 00000000..6469c8d6 --- /dev/null +++ b/src/libgit2/transports/ssh_libssh2.c @@ -0,0 +1,1126 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "ssh_libssh2.h" + +#ifdef GIT_SSH_LIBSSH2 + +#include + +#include "runtime.h" +#include "net.h" +#include "smart.h" +#include "process.h" +#include "streams/socket.h" +#include "sysdir.h" + +#include "git2/credential.h" +#include "git2/sys/credential.h" + +#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) + +extern int git_socket_stream__timeout; + +static const char cmd_uploadpack[] = "git-upload-pack"; +static const char cmd_receivepack[] = "git-receive-pack"; + +typedef struct { + git_smart_subtransport_stream parent; + git_stream *io; + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + const char *cmd; + git_net_url url; + unsigned sent_command : 1; +} ssh_stream; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + ssh_stream *current_stream; + git_credential *cred; + char *cmd_uploadpack; + char *cmd_receivepack; +} ssh_subtransport; + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); + +static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) +{ + char *ssherr; + libssh2_session_last_error(session, &ssherr, NULL, 0); + + git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); +} + +/* + * Create a git protocol request. + * + * For example: git-upload-pack '/libgit2/libgit2' + */ +static int gen_proto(git_str *request, const char *cmd, git_net_url *url) +{ + const char *repo; + + repo = url->path; + + if (repo && repo[0] == '/' && repo[1] == '~') + repo++; + + if (!repo || !repo[0]) { + git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); + return -1; + } + + git_str_puts(request, cmd); + git_str_puts(request, " '"); + git_str_puts(request, repo); + git_str_puts(request, "'"); + + if (git_str_oom(request)) + return -1; + + return 0; +} + +static int send_command(ssh_stream *s) +{ + int error; + git_str request = GIT_STR_INIT; + + error = gen_proto(&request, s->cmd, &s->url); + if (error < 0) + goto cleanup; + + error = libssh2_channel_exec(s->channel, request.ptr); + if (error < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not execute request"); + goto cleanup; + } + + s->sent_command = 1; + +cleanup: + git_str_dispose(&request); + return error; +} + +static int ssh_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + ssize_t rc; + + *bytes_read = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read data"); + return -1; + } + + /* + * If we can't get anything out of stdout, it's typically a + * not-found error, so read from stderr and signal EOF on + * stderr. + */ + if (rc == 0) { + if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { + git_error_set(GIT_ERROR_SSH, "%*s", (int)rc, buffer); + return GIT_EEOF; + } else if (rc < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read stderr"); + return -1; + } + } + + *bytes_read = rc; + + return 0; +} + +static int ssh_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + size_t off = 0; + ssize_t ret = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + do { + ret = libssh2_channel_write(s->channel, buffer + off, len - off); + if (ret < 0) + break; + + off += ret; + + } while (off < len); + + if (ret < 0) { + ssh_error(s->session, "SSH could not write data"); + return -1; + } + + return 0; +} + +static void ssh_stream_free(git_smart_subtransport_stream *stream) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + ssh_subtransport *t; + + if (!stream) + return; + + t = OWNING_SUBTRANSPORT(s); + t->current_stream = NULL; + + if (s->channel) { + libssh2_channel_close(s->channel); + libssh2_channel_free(s->channel); + s->channel = NULL; + } + + if (s->session) { + libssh2_session_disconnect(s->session, "closing transport"); + libssh2_session_free(s->session); + s->session = NULL; + } + + if (s->io) { + git_stream_close(s->io); + git_stream_free(s->io); + s->io = NULL; + } + + git_net_url_dispose(&s->url); + git__free(s); +} + +static int ssh_stream_alloc( + ssh_subtransport *t, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + ssh_stream *s; + + GIT_ASSERT_ARG(stream); + + s = git__calloc(sizeof(ssh_stream), 1); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = ssh_stream_read; + s->parent.write = ssh_stream_write; + s->parent.free = ssh_stream_free; + + s->cmd = cmd; + + *stream = &s->parent; + return 0; +} + +static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) { + int rc = LIBSSH2_ERROR_NONE; + + struct libssh2_agent_publickey *curr, *prev = NULL; + + LIBSSH2_AGENT *agent = libssh2_agent_init(session); + + if (agent == NULL) + return -1; + + rc = libssh2_agent_connect(agent); + + if (rc != LIBSSH2_ERROR_NONE) { + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + goto shutdown; + } + + rc = libssh2_agent_list_identities(agent); + + if (rc != LIBSSH2_ERROR_NONE) + goto shutdown; + + while (1) { + rc = libssh2_agent_get_identity(agent, &curr, prev); + + if (rc < 0) + goto shutdown; + + /* rc is set to 1 whenever the ssh agent ran out of keys to check. + * Set the error code to authentication failure rather than erroring + * out with an untranslatable error code. + */ + if (rc == 1) { + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + goto shutdown; + } + + rc = libssh2_agent_userauth(agent, c->username, curr); + + if (rc == 0) + break; + + prev = curr; + } + +shutdown: + + if (rc != LIBSSH2_ERROR_NONE) + ssh_error(session, "error authenticating"); + + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + + return rc; +} + +static int _git_ssh_authenticate_session( + LIBSSH2_SESSION *session, + git_credential *cred) +{ + int rc; + + do { + git_error_clear(); + switch (cred->credtype) { + case GIT_CREDENTIAL_USERPASS_PLAINTEXT: { + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; + rc = libssh2_userauth_password(session, c->username, c->password); + break; + } + case GIT_CREDENTIAL_SSH_KEY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; + + if (c->privatekey) + rc = libssh2_userauth_publickey_fromfile( + session, c->username, c->publickey, + c->privatekey, c->passphrase); + else + rc = ssh_agent_auth(session, c); + + break; + } + case GIT_CREDENTIAL_SSH_CUSTOM: { + git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; + + rc = libssh2_userauth_publickey( + session, c->username, (const unsigned char *)c->publickey, + c->publickey_len, c->sign_callback, &c->payload); + break; + } + case GIT_CREDENTIAL_SSH_INTERACTIVE: { + void **abstract = libssh2_session_abstract(session); + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; + + /* ideally, we should be able to set this by calling + * libssh2_session_init_ex() instead of libssh2_session_init(). + * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() + * allows you to pass the `abstract` as part of the call, whereas + * libssh2_userauth_keyboard_interactive() does not! + * + * The only way to set the `abstract` pointer is by calling + * libssh2_session_abstract(), which will replace the existing + * pointer as is done below. This is safe for now (at time of writing), + * but may not be valid in future. + */ + *abstract = c->payload; + + rc = libssh2_userauth_keyboard_interactive( + session, c->username, c->prompt_callback); + break; + } +#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS + case GIT_CREDENTIAL_SSH_MEMORY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; + + GIT_ASSERT(c->username); + GIT_ASSERT(c->privatekey); + + rc = libssh2_userauth_publickey_frommemory( + session, + c->username, + strlen(c->username), + c->publickey, + c->publickey ? strlen(c->publickey) : 0, + c->privatekey, + strlen(c->privatekey), + c->passphrase); + break; + } +#endif + default: + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + } + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || + rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || + rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) + return GIT_EAUTH; + + if (rc != LIBSSH2_ERROR_NONE) { + if (git_error_last()->klass == GIT_ERROR_NONE) + ssh_error(session, "failed to authenticate SSH session"); + return -1; + } + + return 0; +} + +static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) +{ + int error, no_callback = 0; + git_credential *cred = NULL; + + if (!t->owner->connect_opts.callbacks.credentials) { + no_callback = 1; + } else { + error = t->owner->connect_opts.callbacks.credentials( + &cred, + t->owner->url, + user, + auth_methods, + t->owner->connect_opts.callbacks.payload); + + if (error == GIT_PASSTHROUGH) { + no_callback = 1; + } else if (error < 0) { + return error; + } else if (!cred) { + git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); + return -1; + } + } + + if (no_callback) { + git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); + return GIT_EAUTH; + } + + if (!(cred->credtype & auth_methods)) { + cred->free(cred); + git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); + return GIT_EAUTH; + } + + *out = cred; + + return 0; +} + +#define SSH_DIR ".ssh" +#define KNOWN_HOSTS_FILE "known_hosts" + +/* + * Load the known_hosts file. + * + * Returns success but leaves the output NULL if we couldn't find the file. + */ +static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session) +{ + git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT; + LIBSSH2_KNOWNHOSTS *known_hosts = NULL; + int error; + + GIT_ASSERT_ARG(hosts); + + if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 || + (error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0) + goto out; + + if ((known_hosts = libssh2_knownhost_init(session)) == NULL) { + ssh_error(session, "error initializing known hosts"); + error = -1; + goto out; + } + + /* + * Try to read the file and consider not finding it as not trusting the + * host rather than an error. + */ + error = libssh2_knownhost_readfile(known_hosts, git_str_cstr(&path), LIBSSH2_KNOWNHOST_FILE_OPENSSH); + if (error == LIBSSH2_ERROR_FILE) + error = 0; + if (error < 0) + ssh_error(session, "error reading known_hosts"); + +out: + *hosts = known_hosts; + + git_str_dispose(&sshdir); + git_str_dispose(&path); + + return error; +} + +static void add_hostkey_pref_if_avail( + LIBSSH2_KNOWNHOSTS *known_hosts, + const char *hostname, + int port, + git_str *prefs, + int type, + const char *type_name) +{ + struct libssh2_knownhost *host = NULL; + const char key = '\0'; + int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type; + int error; + + error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host); + if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { + if (git_str_len(prefs) > 0) { + git_str_putc(prefs, ','); + } + git_str_puts(prefs, type_name); + } +} + +/* + * We figure out what kind of key we want to ask the remote for by trying to + * look it up with a nonsense key and using that mismatch to figure out what key + * we do have stored for the host. + * + * Populates prefs with the string to pass to libssh2_session_method_pref. + */ +static void find_hostkey_preference( + LIBSSH2_KNOWNHOSTS *known_hosts, + const char *hostname, + int port, + git_str *prefs) +{ + /* + * The order here is important as it indicates the priority of what will + * be preferred. + */ +#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519"); +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256 + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256"); + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384"); + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521"); +#endif + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "rsa-sha2-512"); + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "rsa-sha2-256"); + add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa"); +} + +static int _git_ssh_session_create( + LIBSSH2_SESSION **session, + LIBSSH2_KNOWNHOSTS **hosts, + const char *hostname, + int port, + git_stream *io) +{ + git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); + LIBSSH2_SESSION *s; + LIBSSH2_KNOWNHOSTS *known_hosts; + git_str prefs = GIT_STR_INIT; + int rc = 0; + + GIT_ASSERT_ARG(session); + GIT_ASSERT_ARG(hosts); + + s = libssh2_session_init(); + if (!s) { + git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); + return -1; + } + + if (git_socket_stream__timeout > 0) { + libssh2_session_set_timeout(s, git_socket_stream__timeout); + } + + if ((rc = load_known_hosts(&known_hosts, s)) < 0) { + ssh_error(s, "error loading known_hosts"); + libssh2_session_free(s); + return -1; + } + + find_hostkey_preference(known_hosts, hostname, port, &prefs); + if (git_str_len(&prefs) > 0) { + do { + rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs)); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(s, "failed to set hostkey preference"); + goto on_error; + } + } + git_str_dispose(&prefs); + + do { + rc = libssh2_session_handshake(s, socket->s); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(s, "failed to start SSH session"); + goto on_error; + } + + libssh2_session_set_blocking(s, 1); + + *session = s; + *hosts = known_hosts; + + return 0; + +on_error: + libssh2_knownhost_free(known_hosts); + libssh2_session_free(s); + return -1; +} + + +/* + * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on + * the type of key that libssh2_session_hostkey returns. + */ +static int fingerprint_type_mask(int keytype) +{ + int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW; + return mask; + + switch (keytype) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; + break; +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: + mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + mask |= LIBSSH2_KNOWNHOST_KEY_ED25519; + break; +#endif + } + + return mask; +} + +/* + * Check the host against the user's known_hosts file. + * + * Returns 1/0 for valid/''not-valid or <0 for an error + */ +static int check_against_known_hosts( + LIBSSH2_SESSION *session, + LIBSSH2_KNOWNHOSTS *known_hosts, + const char *hostname, + int port, + const char *key, + size_t key_len, + int key_type) +{ + int check, typemask, ret = 0; + struct libssh2_knownhost *host = NULL; + + if (known_hosts == NULL) + return 0; + + typemask = fingerprint_type_mask(key_type); + check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host); + if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) { + ssh_error(session, "error checking for known host"); + return -1; + } + + ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0; + + return ret; +} + +/* + * Perform the check for the session's certificate against known hosts if + * possible and then ask the user if they have a callback. + * + * Returns 1/0 for valid/not-valid or <0 for an error + */ +static int check_certificate( + LIBSSH2_SESSION *session, + LIBSSH2_KNOWNHOSTS *known_hosts, + git_transport_certificate_check_cb check_cb, + void *check_cb_payload, + const char *host, + int port) +{ + git_cert_hostkey cert = {{ 0 }}; + const char *key; + size_t cert_len; + int cert_type, cert_valid = 0, error = GIT_ECERTIFICATE; + + if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) { + ssh_error(session, "failed to retrieve hostkey"); + return -1; + } + + if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0) + return -1; + + cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; + if (key != NULL) { + cert.type |= GIT_CERT_SSH_RAW; + cert.hostkey = key; + cert.hostkey_len = cert_len; + switch (cert_type) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS; + break; + +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384; + break; + case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521; + break; +#endif + +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519; + break; +#endif + default: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN; + } + } + +#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_SHA256; + memcpy(&cert.hash_sha256, key, 32); + } +#endif + + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_SHA1; + memcpy(&cert.hash_sha1, key, 20); + } + + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_MD5; + memcpy(&cert.hash_md5, key, 16); + } + + if (cert.type == 0) { + git_error_set(GIT_ERROR_SSH, "unable to get the host key"); + return -1; + } + + if (check_cb != NULL) { + git_cert_hostkey *cert_ptr = &cert; + + error = check_cb((git_cert *)cert_ptr, cert_valid, host, + check_cb_payload); + + if (error == 0) + cert_valid = 1; + else if (error != GIT_PASSTHROUGH) + cert_valid = 0; + } + + if (!cert_valid) { + git_error_set(GIT_ERROR_SSH, "invalid or unknown remote ssh hostkey"); + return (error == GIT_PASSTHROUGH) ? GIT_ECERTIFICATE : error; + } + + return 0; +} + +#define SSH_DEFAULT_PORT "22" + +static int _git_ssh_setup_conn( + ssh_subtransport *t, + const char *url, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + int auth_methods, error = 0, port; + ssh_stream *s; + git_credential *cred = NULL; + LIBSSH2_SESSION *session=NULL; + LIBSSH2_CHANNEL *channel=NULL; + LIBSSH2_KNOWNHOSTS *known_hosts = NULL; + + t->current_stream = NULL; + + *stream = NULL; + if (ssh_stream_alloc(t, cmd, stream) < 0) + return -1; + + s = (ssh_stream *)*stream; + s->session = NULL; + s->channel = NULL; + + if (git_net_str_is_url(url)) + error = git_net_url_parse(&s->url, url); + else + error = git_net_url_parse_scp(&s->url, url); + + if (error < 0) + goto done; + + /* Safety check: like git, we forbid paths that look like an option as + * that could lead to injection on the remote side */ + if (git_process__is_cmdline_option(s->url.path)) { + git_error_set(GIT_ERROR_NET, "cannot ssh: path '%s' is ambiguous with command-line option", s->url.path); + error = -1; + goto done; + } + + + if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || + (error = git_stream_connect(s->io)) < 0) + goto done; + + /* + * Try to parse the port as a number, if we can't then fall back to + * default. It would be nice if we could get the port that was resolved + * as part of the stream connection, but that's not something that's + * exposed. + */ + if (git__strntol32(&port, s->url.port, strlen(s->url.port), NULL, 10) < 0) + port = -1; + + if ((error = _git_ssh_session_create(&session, &known_hosts, s->url.host, port, s->io)) < 0) + goto done; + + if ((error = check_certificate(session, known_hosts, t->owner->connect_opts.callbacks.certificate_check, t->owner->connect_opts.callbacks.payload, s->url.host, port)) < 0) + goto done; + + /* we need the username to ask for auth methods */ + if (!s->url.username) { + if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) + goto done; + + s->url.username = git__strdup(((git_credential_username *) cred)->username); + cred->free(cred); + cred = NULL; + if (!s->url.username) + goto done; + } else if (s->url.username && s->url.password) { + if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0) + goto done; + } + + if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) + goto done; + + error = GIT_EAUTH; + /* if we already have something to try */ + if (cred && auth_methods & cred->credtype) + error = _git_ssh_authenticate_session(session, cred); + + while (error == GIT_EAUTH) { + if (cred) { + cred->free(cred); + cred = NULL; + } + + if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0) + goto done; + + if (strcmp(s->url.username, git_credential_get_username(cred))) { + git_error_set(GIT_ERROR_SSH, "username does not match previous request"); + error = -1; + goto done; + } + + error = _git_ssh_authenticate_session(session, cred); + + if (error == GIT_EAUTH) { + /* refresh auth methods */ + if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) + goto done; + else + error = GIT_EAUTH; + } + } + + if (error < 0) + goto done; + + channel = libssh2_channel_open_session(session); + if (!channel) { + error = -1; + ssh_error(session, "Failed to open SSH channel"); + goto done; + } + + libssh2_channel_set_blocking(channel, 1); + + s->session = session; + s->channel = channel; + + t->current_stream = s; + +done: + if (known_hosts) + libssh2_knownhost_free(known_hosts); + + if (error < 0) { + ssh_stream_free(*stream); + + if (session) + libssh2_session_free(session); + } + + if (cred) + cred->free(cred); + + return error; +} + +static int ssh_uploadpack_ls( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; + + return _git_ssh_setup_conn(t, url, cmd, stream); +} + +static int ssh_uploadpack( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); + return -1; +} + +static int ssh_receivepack_ls( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; + + + return _git_ssh_setup_conn(t, url, cmd, stream); +} + +static int ssh_receivepack( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); + return -1; +} + +static int _ssh_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return ssh_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return ssh_uploadpack(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return ssh_receivepack_ls(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK: + return ssh_receivepack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static int _ssh_close(git_smart_subtransport *subtransport) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + GIT_ASSERT(!t->current_stream); + + GIT_UNUSED(t); + + return 0; +} + +static void _ssh_free(git_smart_subtransport *subtransport) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + git__free(t); +} + +#define SSH_AUTH_PUBLICKEY "publickey" +#define SSH_AUTH_PASSWORD "password" +#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) +{ + const char *list, *ptr; + + *out = 0; + + list = libssh2_userauth_list(session, username, + (unsigned int)strlen(username)); + + /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ + if (list == NULL && !libssh2_userauth_authenticated(session)) { + ssh_error(session, "remote rejected authentication"); + return GIT_EAUTH; + } + + ptr = list; + while (ptr) { + if (*ptr == ',') + ptr++; + + if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { + *out |= GIT_CREDENTIAL_SSH_KEY; + *out |= GIT_CREDENTIAL_SSH_CUSTOM; +#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS + *out |= GIT_CREDENTIAL_SSH_MEMORY; +#endif + ptr += strlen(SSH_AUTH_PUBLICKEY); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { + *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + ptr += strlen(SSH_AUTH_PASSWORD); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { + *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; + ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); + continue; + } + + /* Skip it if we don't know it */ + ptr = strchr(ptr, ','); + } + + return 0; +} + +int git_smart_subtransport_ssh_libssh2( + git_smart_subtransport **out, + git_transport *owner, + void *param) +{ + ssh_subtransport *t; + + GIT_ASSERT_ARG(out); + + GIT_UNUSED(param); + + t = git__calloc(sizeof(ssh_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = (transport_smart *)owner; + t->parent.action = _ssh_action; + t->parent.close = _ssh_close; + t->parent.free = _ssh_free; + + *out = (git_smart_subtransport *) t; + return 0; +} + +int git_smart_subtransport_ssh_libssh2_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack) +{ + ssh_subtransport *t = (ssh_subtransport *)subtransport; + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + + t->cmd_uploadpack = git__strdup(cmd_uploadpack); + GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); + + t->cmd_receivepack = git__strdup(cmd_receivepack); + GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + + return 0; +} + +static void shutdown_libssh2(void) +{ + libssh2_exit(); +} + +int git_transport_ssh_libssh2_global_init(void) +{ + if (libssh2_init(0) < 0) { + git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); + return -1; + } + + return git_runtime_shutdown_register(shutdown_libssh2); +} + +#else /* GIT_SSH */ + +int git_transport_ssh_libssh2_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/libgit2/transports/ssh_libssh2.h b/src/libgit2/transports/ssh_libssh2.h new file mode 100644 index 00000000..3f8cc2a8 --- /dev/null +++ b/src/libgit2/transports/ssh_libssh2.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_transports_libssh2_h__ +#define INCLUDE_transports_libssh2_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" + +int git_transport_ssh_libssh2_global_init(void); + +int git_smart_subtransport_ssh_libssh2( + git_smart_subtransport **out, + git_transport *owner, + void *param); + +int git_smart_subtransport_ssh_libssh2_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack); + +#endif diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index ae572c56..b83ef990 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -293,7 +293,7 @@ static int certificate_check(winhttp_stream *s, int valid) /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { - if (!git_error_last()) + if (git_error_last()->klass == GIT_ERROR_NONE) git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); return GIT_ECERTIFICATE; @@ -317,7 +317,7 @@ static int certificate_check(winhttp_stream *s, int valid) if (error == GIT_PASSTHROUGH) error = valid ? 0 : GIT_ECERTIFICATE; - if (error < 0 && !git_error_last()) + if (error < 0 && git_error_last()->klass == GIT_ERROR_NONE) git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); return error; @@ -436,7 +436,7 @@ static int winhttp_stream_connect(winhttp_stream *s) GIT_ERROR_CHECK_ALLOC(proxy_url); } - if (proxy_url) { + if (proxy_url && *proxy_url) { git_str processed_url = GIT_STR_INIT; WINHTTP_PROXY_INFO proxy_info; wchar_t *proxy_wide; @@ -746,6 +746,33 @@ static void CALLBACK winhttp_status( } } +static int user_agent(bool *exists, git_str *out) +{ + const char *product = git_settings__user_agent_product(); + const char *comment = git_settings__user_agent(); + + GIT_ASSERT(product && comment); + + if (!*product) { + *exists = false; + return 0; + } + + git_str_puts(out, product); + + if (*comment) { + git_str_puts(out, " ("); + git_str_puts(out, comment); + git_str_puts(out, ")"); + } + + if (git_str_oom(out)) + return -1; + + *exists = true; + return 0; +} + static int winhttp_connect( winhttp_subtransport *t) { @@ -757,6 +784,7 @@ static int winhttp_connect( int error = -1; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + bool has_ua = true; DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | @@ -787,11 +815,11 @@ static int winhttp_connect( goto on_error; } - - if (git_http__user_agent(&ua) < 0) + if (user_agent(&has_ua, &ua) < 0) goto on_error; - if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { + if (has_ua && + git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); goto on_error; } @@ -933,7 +961,7 @@ static int send_request(winhttp_stream *s, size_t len, bool chunked) (!request_failed && s->status_sending_request_reached)) { git_error_clear(); if ((error = certificate_check(s, cert_valid)) < 0) { - if (!git_error_last()) + if (git_error_last()->klass == GIT_ERROR_NONE) git_error_set(GIT_ERROR_OS, "user cancelled certificate check"); return error; diff --git a/src/libgit2/tree.c b/src/libgit2/tree.c index 236a87f7..ad9d8af2 100644 --- a/src/libgit2/tree.c +++ b/src/libgit2/tree.c @@ -21,6 +21,8 @@ #define TREE_ENTRY_CHECK_NAMELEN(n) \ if (n > UINT16_MAX) { git_error_set(GIT_ERROR_INVALID, "tree entry path too long"); } +GIT_HASHMAP_STR_FUNCTIONS(git_treebuilder_entrymap, GIT_HASHMAP_INLINE, git_tree_entry *); + static bool valid_filemode(const int filemode) { return (filemode == GIT_FILEMODE_TREE @@ -347,7 +349,7 @@ size_t git_treebuilder_entrycount(git_treebuilder *bld) { GIT_ASSERT_ARG_WITH_RETVAL(bld, 0); - return git_strmap_size(bld->map); + return git_treebuilder_entrymap_size(&bld->map); } GIT_INLINE(void) set_error(const char *str, const char *path) @@ -381,7 +383,7 @@ static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0) return error; - if (mode < 0 || mode > UINT16_MAX) + if (mode < 0 || (uint32_t)mode > UINT16_MAX) return -1; *mode_out = mode; @@ -512,10 +514,12 @@ static int git_treebuilder__write_with_buffer( git_tree_entry *entry; git_vector entries = GIT_VECTOR_INIT; size_t oid_size = git_oid_size(bld->repo->oid_type); + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; git_str_clear(buf); - entrycount = git_strmap_size(bld->map); + entrycount = git_treebuilder_entrymap_size(&bld->map); + if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0) goto out; @@ -523,10 +527,10 @@ static int git_treebuilder__write_with_buffer( (error = git_str_grow(buf, entrycount * 72)) < 0) goto out; - git_strmap_foreach_value(bld->map, entry, { + while (git_treebuilder_entrymap_iterate(&iter, NULL, &entry, &bld->map) == 0) { if ((error = git_vector_insert(&entries, entry)) < 0) goto out; - }); + } git_vector_sort(&entries); @@ -547,7 +551,7 @@ static int git_treebuilder__write_with_buffer( error = git_odb_write(oid, odb, buf->ptr, buf->size, GIT_OBJECT_TREE); out: - git_vector_free(&entries); + git_vector_dispose(&entries); return error; } @@ -570,7 +574,7 @@ static int append_entry( entry->attr = (uint16_t)filemode; - if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { + if ((error = git_treebuilder_entrymap_put(&bld->map, entry->filename, entry)) < 0) { git_tree_entry_free(entry); git_error_set(GIT_ERROR_TREE, "failed to append entry %s to the tree builder", filename); return -1; @@ -753,11 +757,6 @@ int git_treebuilder_new( bld->repo = repo; - if (git_strmap_new(&bld->map) < 0) { - git__free(bld); - return -1; - } - if (source != NULL) { git_tree_entry *entry_src; @@ -796,13 +795,13 @@ int git_treebuilder_insert( if ((error = check_entry(bld->repo, filename, id, filemode)) < 0) return error; - if ((entry = git_strmap_get(bld->map, filename)) != NULL) { + if (git_treebuilder_entrymap_get(&entry, &bld->map, filename) == 0) { git_oid_cpy(&entry->oid, id); } else { entry = alloc_entry(filename, strlen(filename), id); GIT_ERROR_CHECK_ALLOC(entry); - if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { + if (git_treebuilder_entrymap_put(&bld->map, entry->filename, entry) < 0) { git_tree_entry_free(entry); git_error_set(GIT_ERROR_TREE, "failed to insert %s", filename); return -1; @@ -819,10 +818,15 @@ int git_treebuilder_insert( static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) { + git_tree_entry *entry; + GIT_ASSERT_ARG_WITH_RETVAL(bld, NULL); GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); - return git_strmap_get(bld->map, filename); + if (git_treebuilder_entrymap_get(&entry, &bld->map, filename) != 0) + return NULL; + + return entry; } const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) @@ -837,7 +841,7 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename) if (entry == NULL) return tree_error("failed to remove entry: file isn't in the tree", filename); - git_strmap_delete(bld->map, filename); + git_treebuilder_entrymap_remove(&bld->map, filename); git_tree_entry_free(entry); return 0; @@ -858,16 +862,17 @@ int git_treebuilder_filter( { const char *filename; git_tree_entry *entry; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; GIT_ASSERT_ARG(bld); GIT_ASSERT_ARG(filter); - git_strmap_foreach(bld->map, filename, entry, { - if (filter(entry, payload)) { - git_strmap_delete(bld->map, filename); - git_tree_entry_free(entry); - } - }); + while (git_treebuilder_entrymap_iterate(&iter, &filename, &entry, &bld->map) == 0) { + if (filter(entry, payload)) { + git_treebuilder_entrymap_remove(&bld->map, filename); + git_tree_entry_free(entry); + } + } return 0; } @@ -875,11 +880,14 @@ int git_treebuilder_filter( int git_treebuilder_clear(git_treebuilder *bld) { git_tree_entry *e; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; GIT_ASSERT_ARG(bld); - git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e)); - git_strmap_clear(bld->map); + while (git_treebuilder_entrymap_iterate(&iter, NULL, &e, &bld->map) == 0) + git_tree_entry_free(e); + + git_treebuilder_entrymap_clear(&bld->map); return 0; } @@ -891,7 +899,7 @@ void git_treebuilder_free(git_treebuilder *bld) git_str_dispose(&bld->write_cache); git_treebuilder_clear(bld); - git_strmap_free(bld->map); + git_treebuilder_entrymap_dispose(&bld->map); git__free(bld); } @@ -1312,7 +1320,7 @@ int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseli git_str_dispose(&component); git_array_clear(stack); - git_vector_free(&entries); + git_vector_dispose(&entries); return error; } diff --git a/src/libgit2/tree.h b/src/libgit2/tree.h index 5088450a..8deec604 100644 --- a/src/libgit2/tree.h +++ b/src/libgit2/tree.h @@ -13,7 +13,6 @@ #include "repository.h" #include "odb.h" #include "vector.h" -#include "strmap.h" #include "pool.h" struct git_tree_entry { @@ -29,9 +28,11 @@ struct git_tree { git_array_t(git_tree_entry) entries; }; +GIT_HASHMAP_STR_STRUCT(git_treebuilder_entrymap, git_tree_entry *); + struct git_treebuilder { git_repository *repo; - git_strmap *map; + git_treebuilder_entrymap map; git_str write_cache; }; diff --git a/src/libgit2/worktree.c b/src/libgit2/worktree.c index a878634c..00ff9e7d 100644 --- a/src/libgit2/worktree.c +++ b/src/libgit2/worktree.c @@ -335,11 +335,21 @@ int git_worktree_add(git_worktree **out, git_repository *repo, goto out; } - if (git_branch_is_checked_out(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); - err = -1; + if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) goto out; - } + } else if (wtopts.checkout_existing && git_branch_lookup(&ref, repo, name, GIT_BRANCH_LOCAL) == 0) { + /* Do nothing */ + } else if ((err = git_repository_head(&head, repo)) < 0 || + (err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0 || + (err = git_branch_create(&ref, repo, name, commit, false)) < 0) { + goto out; + } + + if (git_branch_is_checked_out(ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference %s is already checked out", + git_reference_name(ref)); + err = -1; + goto out; } /* Create gitdir directory ".git/worktrees/" */ @@ -392,19 +402,6 @@ int git_worktree_add(git_worktree **out, git_repository *repo, || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) goto out; - /* Set up worktree reference */ - if (wtopts.ref) { - if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) - goto out; - } else { - if ((err = git_repository_head(&head, repo)) < 0) - goto out; - if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0) - goto out; - if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0) - goto out; - } - /* Set worktree's HEAD */ if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) goto out; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index ee35eb96..3692dc3d 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,8 +1,6 @@ # util: a shared library for common utility functions for libgit2 projects add_library(util OBJECT) -set_target_properties(util PROPERTIES C_STANDARD 90) -set_target_properties(util PROPERTIES C_EXTENSIONS OFF) configure_file(git2_features.h.in git2_features.h) @@ -36,7 +34,7 @@ if(USE_SHA1 STREQUAL "CollisionDetection") target_compile_definitions(util PRIVATE SHA1DC_NO_STANDARD_INCLUDES=1) target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_SHA1_C=\"git2_util.h\") target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"git2_util.h\") -elseif(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA1 STREQUAL "OpenSSL-Dynamic") +elseif(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA1 STREQUAL "OpenSSL-Dynamic" OR USE_SHA1 STREQUAL "OpenSSL-FIPS") add_definitions(-DOPENSSL_API_COMPAT=0x10100000L) file(GLOB UTIL_SRC_SHA1 hash/openssl.*) elseif(USE_SHA1 STREQUAL "CommonCrypto") @@ -51,9 +49,9 @@ endif() list(SORT UTIL_SRC_SHA1) -if(USE_SHA256 STREQUAL "Builtin") +if(USE_SHA256 STREQUAL "builtin") file(GLOB UTIL_SRC_SHA256 hash/builtin.* hash/rfc6234/*) -elseif(USE_SHA256 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL-Dynamic") +elseif(USE_SHA256 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL-Dynamic" OR USE_SHA256 STREQUAL "OpenSSL-FIPS") add_definitions(-DOPENSSL_API_COMPAT=0x10100000L) file(GLOB UTIL_SRC_SHA256 hash/openssl.*) elseif(USE_SHA256 STREQUAL "CommonCrypto") @@ -63,7 +61,7 @@ elseif(USE_SHA256 STREQUAL "mbedTLS") elseif(USE_SHA256 STREQUAL "Win32") file(GLOB UTIL_SRC_SHA256 hash/win32.*) else() - message(FATAL_ERROR "Asked for unknown SHA256 backend: ${USE_SHA256}") + message(FATAL_ERROR "asked for unknown SHA256 backend: ${USE_SHA256}") endif() list(SORT UTIL_SRC_SHA256) diff --git a/src/util/alloc.c b/src/util/alloc.c index 6ec173d0..998b0aea 100644 --- a/src/util/alloc.c +++ b/src/util/alloc.c @@ -8,8 +8,9 @@ #include "alloc.h" #include "runtime.h" -#include "allocators/failalloc.h" #include "allocators/stdalloc.h" +#include "allocators/debugalloc.h" +#include "allocators/failalloc.h" #include "allocators/win32_leakcheck.h" /* Fail any allocation until git_libgit2_init is called. */ @@ -88,6 +89,8 @@ static int setup_default_allocator(void) { #if defined(GIT_WIN32_LEAKCHECK) return git_win32_leakcheck_init_allocator(&git__allocator); +#elif defined(GIT_DEBUG_STRICT_ALLOC) + return git_debugalloc_init_allocator(&git__allocator); #else return git_stdalloc_init_allocator(&git__allocator); #endif diff --git a/src/util/allocators/debugalloc.c b/src/util/allocators/debugalloc.c new file mode 100644 index 00000000..44022cd7 --- /dev/null +++ b/src/util/allocators/debugalloc.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "debugalloc.h" + +static void *debugalloc__malloc(size_t len, const char *file, int line) +{ + unsigned char *ptr; + size_t total = len + sizeof(size_t); + + GIT_UNUSED(file); + GIT_UNUSED(line); + + if (!len || (ptr = malloc(total)) == NULL) + return NULL; + + memcpy(ptr, &len, sizeof(size_t)); + return ptr + sizeof(size_t); +} + +static void *debugalloc__realloc(void *_ptr, size_t len, const char *file, int line) +{ + unsigned char *ptr = _ptr, *newptr; + size_t original_len; + size_t total = len + sizeof(size_t); + + GIT_UNUSED(file); + GIT_UNUSED(line); + + if (!len && !ptr) + return NULL; + + if (!len) { + free(ptr - sizeof(size_t)); + return NULL; + } + + if ((newptr = malloc(total)) == NULL) + return NULL; + + if (ptr) { + memcpy(&original_len, ptr - sizeof(size_t), sizeof(size_t)); + memcpy(newptr + sizeof(size_t), ptr, min(len, original_len)); + + memset(ptr - sizeof(size_t), 0xfd, original_len + sizeof(size_t)); + free(ptr - sizeof(size_t)); + } + + memcpy(newptr, &len, sizeof(size_t)); + return newptr + sizeof(size_t); +} + +static void debugalloc__free(void *_ptr) +{ + unsigned char *ptr = _ptr; + + if (!ptr) + return; + + free(ptr - sizeof(size_t)); +} + +int git_debugalloc_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = debugalloc__malloc; + allocator->grealloc = debugalloc__realloc; + allocator->gfree = debugalloc__free; + return 0; +} diff --git a/src/cli/cli.h b/src/util/allocators/debugalloc.h similarity index 57% rename from src/cli/cli.h rename to src/util/allocators/debugalloc.h index 7dede678..dea0ca31 100644 --- a/src/cli/cli.h +++ b/src/util/allocators/debugalloc.h @@ -5,16 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef CLI_cli_h__ -#define CLI_cli_h__ - -#define PROGRAM_NAME "git2" +#ifndef INCLUDE_allocators_debugalloc_h__ +#define INCLUDE_allocators_debugalloc_h__ #include "git2_util.h" -#include "error.h" -#include "opt.h" -#include "opt_usage.h" -#include "sighandler.h" +#include "alloc.h" + +int git_debugalloc_init_allocator(git_allocator *allocator); -#endif /* CLI_cli_h__ */ +#endif diff --git a/src/util/allocators/stdalloc.c b/src/util/allocators/stdalloc.c index f2d72a7e..65ec40fb 100644 --- a/src/util/allocators/stdalloc.c +++ b/src/util/allocators/stdalloc.c @@ -12,11 +12,6 @@ static void *stdalloc__malloc(size_t len, const char *file, int line) GIT_UNUSED(file); GIT_UNUSED(line); -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!len) - return NULL; -#endif - return malloc(len); } @@ -25,11 +20,6 @@ static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int lin GIT_UNUSED(file); GIT_UNUSED(line); -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!size) - return NULL; -#endif - return realloc(ptr, size); } diff --git a/src/util/array.h b/src/util/array.h index 633d598e..515e6e3a 100644 --- a/src/util/array.h +++ b/src/util/array.h @@ -41,39 +41,40 @@ #define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) - -typedef git_array_t(char) git_array_generic_t; - -/* use a generic array for growth, return 0 on success */ -GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) +GIT_INLINE(void *) git_array__alloc(void *arr, size_t *size, size_t *asize, size_t item_size) { - volatile git_array_generic_t *a = _a; size_t new_size; - char *new_array; + void *new_array; + + if (*size < *asize) + return arr; - if (a->size < 8) { + if (*size < 8) { new_size = 8; } else { - if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) + if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, *asize, 3)) goto on_oom; + new_size /= 2; } - if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) + if ((new_array = git__reallocarray(arr, new_size, item_size)) == NULL) goto on_oom; - a->ptr = new_array; - a->asize = new_size; - return 0; + *asize = new_size; + + return new_array; on_oom: - git_array_clear(*a); - return -1; + git__free(arr); + *size = 0; + *asize = 0; + return NULL; } #define git_array_alloc(a) \ - (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ - &(a).ptr[(a).size++] : (void *)NULL) + (((a).size < (a).asize || \ + ((a).ptr = git_array__alloc((a).ptr, &(a).size, &(a).asize, sizeof(*(a).ptr))) != NULL) ? &(a).ptr[(a).size++] : (void *)NULL) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) diff --git a/src/util/cc-compat.h b/src/util/cc-compat.h index ede6e9aa..ae81970e 100644 --- a/src/util/cc-compat.h +++ b/src/util/cc-compat.h @@ -44,9 +44,11 @@ _unused = (x); \ } while (0) # define GIT_UNUSED_ARG __attribute__((unused)) +# define GIT_UNUSED_FUNCTION __attribute__((unused)) #else # define GIT_UNUSED(x) ((void)(x)) # define GIT_UNUSED_ARG +# define GIT_UNUSED_FUNCTION #endif /* Define the printf format specifier to use for size_t output */ diff --git a/src/util/ctype_compat.h b/src/util/ctype_compat.h new file mode 100644 index 00000000..462c8a17 --- /dev/null +++ b/src/util/ctype_compat.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_ctype_compat_h__ +#define INCLUDE_ctype_compat_h__ + +/* + * The Microsoft C runtime (MSVCRT) may take a heavy lock on the + * locale in order to figure out how the `ctype` functions work. + * This is deeply slow. Provide our own to avoid that. + */ + +#ifdef GIT_WIN32 + +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} + +GIT_INLINE(int) git__toupper(int c) +{ + return (c >= 'a' && c <= 'z') ? (c - 32) : c; +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +GIT_INLINE(bool) git__isalnum(int c) +{ + return git__isalpha(c) || git__isdigit(c); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +GIT_INLINE(bool) git__isprint(int c) +{ + return (c >= ' ' && c <= '~'); +} + +#else +# define git__tolower(a) tolower((unsigned char)(a)) +# define git__toupper(a) toupper((unsigned char)(a)) + +# define git__isalpha(a) (!!isalpha((unsigned char)(a))) +# define git__isdigit(a) (!!isdigit((unsigned char)(a))) +# define git__isalnum(a) (!!isalnum((unsigned char)(a))) +# define git__isspace(a) (!!isspace((unsigned char)(a))) +# define git__isxdigit(a) (!!isxdigit((unsigned char)(a))) +# define git__isprint(a) (!!isprint((unsigned char)(a))) +#endif + +#endif diff --git a/src/util/date.c b/src/util/date.c index 4d757e21..0ff5287a 100644 --- a/src/util/date.c +++ b/src/util/date.c @@ -129,9 +129,9 @@ static size_t match_string(const char *date, const char *str) for (i = 0; *date; date++, str++, i++) { if (*date == *str) continue; - if (toupper(*date) == toupper(*str)) + if (git__toupper(*date) == git__toupper(*str)) continue; - if (!isalnum(*date)) + if (!git__isalnum(*date)) break; return 0; } @@ -143,7 +143,7 @@ static int skip_alpha(const char *date) int i = 0; do { i++; - } while (isalpha(date[i])); + } while (git__isalpha(date[i])); return i; } @@ -251,7 +251,7 @@ static size_t match_multi_number(unsigned long num, char c, const char *date, ch num2 = strtol(end+1, &end, 10); num3 = -1; - if (*end == c && isdigit(end[1])) + if (*end == c && git__isdigit(end[1])) num3 = strtol(end+1, &end, 10); /* Time? Date? */ @@ -349,7 +349,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ case '.': case '/': case '-': - if (isdigit(end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(num, *end, date, end, tm); if (match) return match; @@ -364,7 +364,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ n = 0; do { n++; - } while (isdigit(date[n])); + } while (git__isdigit(date[n])); /* Four-digit year or a timezone? */ if (n == 4) { @@ -514,11 +514,11 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset if (!c || c == '\n') break; - if (isalpha(c)) + if (git__isalpha(c)) match = match_alpha(date, &tm, offset); - else if (isdigit(c)) + else if (git__isdigit(c)) match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit(date[1])) + else if ((c == '-' || c == '+') && git__isdigit(date[1])) match = match_tz(date, offset); if (!match) { @@ -682,7 +682,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm const char *end = date; int i; - while (isalpha(*++end)) + while (git__isalpha(*++end)) /* scan to non-alpha */; for (i = 0; i < 12; i++) { @@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) case '.': case '/': case '-': - if (isdigit(end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(number, *end, date, end, tm); if (match) return date + match; @@ -843,13 +843,13 @@ static git_time_t approxidate_str(const char *date, if (!c) break; date++; - if (isdigit(c)) { + if (git__isdigit(c)) { pending_number(&tm, &number); date = approxidate_digit(date-1, &tm, &number); touched = 1; continue; } - if (isalpha(c)) + if (git__isalpha(c)) date = approxidate_alpha(date-1, &tm, &now, &number, &touched); } pending_number(&tm, &number); @@ -858,7 +858,7 @@ static git_time_t approxidate_str(const char *date, return update_tm(&tm, &now, 0); } -int git_date_parse(git_time_t *out, const char *date) +int git_date_offset_parse(git_time_t *out, int *out_offset, const char *date) { time_t time_sec; git_time_t timestamp; @@ -866,6 +866,7 @@ int git_date_parse(git_time_t *out, const char *date) if (!parse_date_basic(date, ×tamp, &offset)) { *out = timestamp; + *out_offset = offset; return 0; } @@ -876,6 +877,13 @@ int git_date_parse(git_time_t *out, const char *date) return error_ret; } +int git_date_parse(git_time_t *out, const char *date) +{ + int offset; + + return git_date_offset_parse(out, &offset, date); +} + int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) { time_t t; diff --git a/src/util/date.h b/src/util/date.h index 7ebd3c30..785fc064 100644 --- a/src/util/date.h +++ b/src/util/date.h @@ -10,9 +10,21 @@ #include "util.h" #include "str.h" +/* + * Parse a string into a value as a git_time_t with a timezone offset. + * + * Sample valid input: + * - "yesterday" + * - "July 17, 2003" + * - "2003-7-17 08:23i+03" + */ +extern int git_date_offset_parse(git_time_t *out, int *out_offset, const char *date); + /* * Parse a string into a value as a git_time_t. * + * Timezone offset is ignored. + * * Sample valid input: * - "yesterday" * - "July 17, 2003" diff --git a/src/util/errors.c b/src/util/errors.c new file mode 100644 index 00000000..feed6a83 --- /dev/null +++ b/src/util/errors.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#include "errors.h" +#include "posix.h" +#include "str.h" +#include "runtime.h" + +/* + * Some static error data that is used when we're out of memory, TLS + * has not been setup, or TLS has failed. + */ + +static git_error oom_error = { + "Out of memory", + GIT_ERROR_NOMEMORY +}; + +static git_error uninitialized_error = { + "library has not been initialized", + GIT_ERROR_INVALID +}; + +static git_error tlsdata_error = { + "thread-local data initialization failure", + GIT_ERROR_THREAD +}; + +static git_error no_error = { + "no error", + GIT_ERROR_NONE +}; + +#define IS_STATIC_ERROR(err) \ + ((err) == &oom_error || (err) == &uninitialized_error || \ + (err) == &tlsdata_error || (err) == &no_error) + +/* Per-thread error state (TLS) */ + +static git_tlsdata_key tls_key; + +struct error_threadstate { + /* The error message buffer. */ + git_str message; + + /* Error information, set by `git_error_set` and friends. */ + git_error error; + + /* + * The last error to occur; points to the error member of this + * struct _or_ a static error. + */ + git_error *last; +}; + +static void threadstate_dispose(struct error_threadstate *threadstate) +{ + if (!threadstate) + return; + + git_str_dispose(&threadstate->message); +} + +static struct error_threadstate *threadstate_get(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = git_tlsdata_get(tls_key)) != NULL) + return threadstate; + + /* + * Avoid git__malloc here, since if it fails, it sets an error + * message, which requires thread state, which would allocate + * here, which would fail, which would set an error message... + */ + + if ((threadstate = git__allocator.gmalloc( + sizeof(struct error_threadstate), + __FILE__, __LINE__)) == NULL) + return NULL; + + memset(threadstate, 0, sizeof(struct error_threadstate)); + + if (git_str_init(&threadstate->message, 0) < 0) { + git__allocator.gfree(threadstate); + return NULL; + } + + git_tlsdata_set(tls_key, threadstate); + return threadstate; +} + +static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) +{ + threadstate_dispose(threadstate); + git__free(threadstate); +} + +static void git_error_global_shutdown(void) +{ + struct error_threadstate *threadstate; + + threadstate = git_tlsdata_get(tls_key); + git_tlsdata_set(tls_key, NULL); + + threadstate_dispose(threadstate); + git__free(threadstate); + + git_tlsdata_dispose(tls_key); +} + +int git_error_global_init(void) +{ + if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) + return -1; + + return git_runtime_shutdown_register(git_error_global_shutdown); +} + +static void set_error_from_buffer(int error_class) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error; + git_str *buf; + + if (!threadstate) + return; + + error = &threadstate->error; + buf = &threadstate->message; + + error->message = buf->ptr; + error->klass = error_class; + + threadstate->last = error; +} + +static void set_error(int error_class, char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (string) + git_str_puts(buf, string); + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +void git_error_set_oom(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + threadstate->last = &oom_error; +} + +void git_error_set(int error_class, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(error_class, fmt, ap); + va_end(ap); +} + +void git_error_vset(int error_class, const char *fmt, va_list ap) +{ +#ifdef GIT_WIN32 + DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; +#endif + + struct error_threadstate *threadstate = threadstate_get(); + int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (fmt) { + git_str_vprintf(buf, fmt, ap); + if (error_class == GIT_ERROR_OS) + git_str_PUTS(buf, ": "); + } + + if (error_class == GIT_ERROR_OS) { +#ifdef GIT_WIN32 + char *win32_error = git_win32_get_error_message(win32_error_code); + if (win32_error) { + git_str_puts(buf, win32_error); + git__free(win32_error); + + SetLastError(0); + } + else +#endif + if (error_code) + git_str_puts(buf, strerror(error_code)); + + if (error_code) + errno = 0; + } + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +int git_error_set_str(int error_class, const char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + GIT_ASSERT_ARG(string); + + if (!threadstate) + return -1; + + buf = &threadstate->message; + + git_str_clear(buf); + git_str_puts(buf, string); + + if (git_str_oom(buf)) + return -1; + + set_error_from_buffer(error_class); + return 0; +} + +void git_error_clear(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + if (threadstate->last != NULL) { + set_error(0, NULL); + threadstate->last = NULL; + } + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif +} + +bool git_error_exists(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = threadstate_get()) == NULL) + return true; + + return threadstate->last != NULL; +} + +const git_error *git_error_last(void) +{ + struct error_threadstate *threadstate; + + /* If the library is not initialized, return a static error. */ + if (!git_runtime_init_count()) + return &uninitialized_error; + + if ((threadstate = threadstate_get()) == NULL) + return &tlsdata_error; + + if (!threadstate->last) + return &no_error; + + return threadstate->last; +} + +int git_error_save(git_error **out) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error, *dup; + + if (!threadstate) { + *out = &tlsdata_error; + return -1; + } + + error = threadstate->last; + + if (!error || error == &no_error) { + *out = &no_error; + return 0; + } else if (IS_STATIC_ERROR(error)) { + *out = error; + return 0; + } + + if ((dup = git__malloc(sizeof(git_error))) == NULL) { + *out = &oom_error; + return -1; + } + + dup->klass = error->klass; + dup->message = git__strdup(error->message); + + if (!dup->message) { + *out = &oom_error; + return -1; + } + + *out = dup; + return 0; +} + +int git_error_restore(git_error *error) +{ + struct error_threadstate *threadstate = threadstate_get(); + + GIT_ASSERT_ARG(error); + + if (IS_STATIC_ERROR(error) && threadstate) + threadstate->last = error; + else + set_error(error->klass, error->message); + + git_error_free(error); + return 0; +} + +void git_error_free(git_error *error) +{ + if (!error) + return; + + if (IS_STATIC_ERROR(error)) + return; + + git__free(error->message); + git__free(error); +} + +int git_error_system_last(void) +{ +#ifdef GIT_WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +void git_error_system_set(int code) +{ +#ifdef GIT_WIN32 + SetLastError(code); +#else + errno = code; +#endif +} + +/* Deprecated error values and functions */ + +#ifndef GIT_DEPRECATE_HARD + +#include "git2/deprecated.h" + +const git_error *giterr_last(void) +{ + return git_error_last(); +} + +void giterr_clear(void) +{ + git_error_clear(); +} + +void giterr_set_str(int error_class, const char *string) +{ + git_error_set_str(error_class, string); +} + +void giterr_set_oom(void) +{ + git_error_set_oom(); +} +#endif diff --git a/src/libgit2/errors.h b/src/util/errors.h similarity index 67% rename from src/libgit2/errors.h rename to src/util/errors.h index 772c7bad..8d587755 100644 --- a/src/libgit2/errors.h +++ b/src/util/errors.h @@ -8,13 +8,22 @@ #ifndef INCLUDE_errors_h__ #define INCLUDE_errors_h__ -#include "common.h" +#include "git2_util.h" +#include "git2/sys/errors.h" + +/* Initialize the error thread-state. */ +int git_error_global_init(void); /* * `vprintf`-style formatting for the error message for this thread. */ void git_error_vset(int error_class, const char *fmt, va_list ap); +/** + * Determines whether an error exists. + */ +bool git_error_exists(void); + /** * Set error message for user callback if needed. * @@ -27,9 +36,8 @@ GIT_INLINE(int) git_error_set_after_callback_function( int error_code, const char *action) { if (error_code) { - const git_error *e = git_error_last(); - if (!e || !e->message) - git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, + if (!git_error_exists()) + git_error_set(GIT_ERROR_CALLBACK, "%s callback returned %d", action, error_code); } return error_code; @@ -53,28 +61,24 @@ int git_error_system_last(void); */ void git_error_system_set(int code); -/** - * Structure to preserve libgit2 error state - */ -typedef struct { - int error_code; - unsigned int oom : 1; - git_error error_msg; -} git_error_state; - /** * Capture current error state to restore later, returning error code. * If `error_code` is zero, this does not clear the current error state. * You must either restore this error state, or free it. + * + * This function returns 0 on success, or -1 on failure. If the function + * fails, the `out` structure is set to the failure error message and + * the normal system error message is not updated. */ -extern int git_error_state_capture(git_error_state *state, int error_code); +extern int git_error_save(git_error **out); /** - * Restore error state to a previous value, returning saved error code. + * Restore thread error state to the given value. The given value is + * freed and `git_error_free` need not be called on it. */ -extern int git_error_state_restore(git_error_state *state); +extern int git_error_restore(git_error *error); /** Free an error state. */ -extern void git_error_state_free(git_error_state *state); +extern void git_error_free(git_error *error); #endif diff --git a/src/util/fs_path.c b/src/util/fs_path.c index e03fcf7c..5b00b1b4 100644 --- a/src/util/fs_path.c +++ b/src/util/fs_path.c @@ -419,6 +419,16 @@ int git_fs_path_to_dir(git_str *path) return git_str_oom(path) ? -1 : 0; } +size_t git_fs_path_dirlen(const char *path) +{ + size_t len = strlen(path); + + while (len > 1 && path[len - 1] == '/') + len--; + + return len; +} + void git_fs_path_string_to_dir(char *path, size_t size) { size_t end = strlen(path); @@ -601,6 +611,37 @@ bool git_fs_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +#ifdef GIT_WIN32 + +bool git_fs_path_isexecutable(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + + if (git__suffixcmp_icase(path, ".exe") != 0 && + git__suffixcmp_icase(path, ".cmd") != 0) + return false; + + return (p_stat(path, &st) == 0); +} + +#else + +bool git_fs_path_isexecutable(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0 && + ((st.st_mode & S_IXUSR) != 0); +} + +#endif + bool git_fs_path_islink(const char *path) { struct stat st; @@ -1938,12 +1979,13 @@ static int sudo_uid_lookup(uid_t *out) { git_str uid_str = GIT_STR_INIT; int64_t uid; - int error; + int error = -1; - if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 && - (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 && - uid == (int64_t)((uid_t)uid)) { + if (git__getenv(&uid_str, "SUDO_UID") == 0 && + git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10) == 0 && + uid == (int64_t)((uid_t)uid)) { *out = (uid_t)uid; + error = 0; } git_str_dispose(&uid_str); @@ -2009,9 +2051,10 @@ int git_fs_path_owner_is_system(bool *out, const char *path) return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR); } -int git_fs_path_find_executable(git_str *fullpath, const char *executable) -{ #ifdef GIT_WIN32 + +static int find_executable(git_str *fullpath, const char *executable) +{ git_win32_path fullpath_w, executable_w; int error; @@ -2024,9 +2067,15 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable) error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); return error; +} + #else + +static int find_executable(git_str *fullpath, const char *executable) +{ git_str path = GIT_STR_INIT; const char *current_dir, *term; + size_t current_dirlen; bool found = false; if (git__getenv(&path, "PATH") < 0) @@ -2038,20 +2087,28 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable) if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) term = strchr(current_dir, '\0'); + current_dirlen = term - current_dir; git_str_clear(fullpath); - if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || - git_str_putc(fullpath, '/') < 0 || + + /* An empty path segment is treated as '.' */ + if (current_dirlen == 0 && git_str_putc(fullpath, '.')) + return -1; + else if (current_dirlen != 0 && + git_str_put(fullpath, current_dir, current_dirlen) < 0) + return -1; + + if (git_str_putc(fullpath, '/') < 0 || git_str_puts(fullpath, executable) < 0) return -1; - if (git_fs_path_isfile(fullpath->ptr)) { + if (git_fs_path_isexecutable(fullpath->ptr)) { found = true; break; } current_dir = term; - while (*current_dir == GIT_PATH_LIST_SEPARATOR) + if (*current_dir == GIT_PATH_LIST_SEPARATOR) current_dir++; } @@ -2062,5 +2119,19 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable) git_str_clear(fullpath); return GIT_ENOTFOUND; +} + #endif + +int git_fs_path_find_executable(git_str *fullpath, const char *executable) +{ + /* For qualified paths we do not look in PATH */ + if (strchr(executable, '/') != NULL) { + if (!git_fs_path_isexecutable(executable)) + return GIT_ENOTFOUND; + + return git_str_puts(fullpath, executable); + } + + return find_executable(fullpath, executable); } diff --git a/src/util/fs_path.h b/src/util/fs_path.h index e5ca6737..82baf9bf 100644 --- a/src/util/fs_path.h +++ b/src/util/fs_path.h @@ -86,6 +86,29 @@ extern int git_fs_path_to_dir(git_str *path); */ extern void git_fs_path_string_to_dir(char *path, size_t size); +/** + * Provides the length of the given path string with no trailing + * slashes. + */ +size_t git_fs_path_dirlen(const char *path); + +/** + * Returns nonzero if the given path is a filesystem root; on Windows, this + * means a drive letter (eg `A:/`, `C:\`). On POSIX this is `/`. + */ +GIT_INLINE(int) git_fs_path_is_root(const char *name) +{ +#ifdef GIT_WIN32 + if (((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) && + name[1] == ':' && + (name[2] == '/' || name[2] == '\\') && + name[3] == '\0') + return 1; +#endif + + return (name[0] == '/' && name[1] == '\0'); +} + /** * Taken from git.git; returns nonzero if the given path is "." or "..". */ @@ -181,6 +204,12 @@ extern bool git_fs_path_isdir(const char *path); */ extern bool git_fs_path_isfile(const char *path); +/** + * Check if the given path points to an executable. + * @return true or false + */ +extern bool git_fs_path_isexecutable(const char *path); + /** * Check if the given path points to a symbolic link. * @return true or false diff --git a/src/util/futils.c b/src/util/futils.c index 7b5a24b3..eb32cbb1 100644 --- a/src/util/futils.c +++ b/src/util/futils.c @@ -8,9 +8,9 @@ #include "futils.h" #include "runtime.h" -#include "strmap.h" #include "hash.h" #include "rand.h" +#include "hashmap_str.h" #include @@ -653,7 +653,8 @@ int git_futils_mkdir_relative( *tail = '\0'; st.st_mode = 0; - if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) + if (opts->cache_pathset && + git_hashset_str_contains(opts->cache_pathset, make_path.ptr)) continue; /* See what's going on with this path component */ @@ -688,17 +689,17 @@ int git_futils_mkdir_relative( make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) goto done; - if (opts->dir_map && opts->pool) { + if (opts->cache_pathset && opts->cache_pool) { char *cache_path; size_t alloc_size; GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); - cache_path = git_pool_malloc(opts->pool, alloc_size); + cache_path = git_pool_malloc(opts->cache_pool, alloc_size); GIT_ERROR_CHECK_ALLOC(cache_path); memcpy(cache_path, make_path.ptr, make_path.size + 1); - if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) + if ((error = git_hashset_str_add(opts->cache_pathset, cache_path)) < 0) goto done; } } diff --git a/src/util/futils.h b/src/util/futils.h index 3f207afb..7ae869be 100644 --- a/src/util/futils.h +++ b/src/util/futils.h @@ -13,8 +13,8 @@ #include "posix.h" #include "fs_path.h" #include "pool.h" -#include "strmap.h" #include "hash.h" +#include "hashmap_str.h" /** * Filebuffer methods @@ -25,7 +25,7 @@ extern int git_futils_readbuffer(git_str *obj, const char *path); extern int git_futils_readbuffer_updated( git_str *obj, const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], + unsigned char checksum[GIT_HASH_SHA256_SIZE], int *updated); extern int git_futils_readbuffer_fd_full(git_str *obj, git_file fd); extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); @@ -109,8 +109,17 @@ struct git_futils_mkdir_perfdata struct git_futils_mkdir_options { - git_strmap *dir_map; - git_pool *pool; + /* + * Callers can optionally pass an allocation pool and a + * hashset of strings; mkdir will populate these with the + * path(s) it creates; this can be useful for repeated calls + * to mkdir. This will reduce I/O by avoiding testing for the + * existence of intermediate directories that it knows already + * exist (because it created them). + */ + git_pool *cache_pool; + git_hashset_str *cache_pathset; + struct git_futils_mkdir_perfdata perfdata; }; diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in index a84ea895..cd14cc23 100644 --- a/src/util/git2_features.h.in +++ b/src/util/git2_features.h.in @@ -30,7 +30,9 @@ #cmakedefine GIT_QSORT_MSC #cmakedefine GIT_SSH 1 -#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 +#cmakedefine GIT_SSH_EXEC 1 +#cmakedefine GIT_SSH_LIBSSH2 1 +#cmakedefine GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1 #cmakedefine GIT_NTLM 1 #cmakedefine GIT_GSSAPI 1 @@ -44,10 +46,15 @@ #cmakedefine GIT_MBEDTLS 1 #cmakedefine GIT_SCHANNEL 1 +#cmakedefine GIT_HTTPPARSER_HTTPPARSER 1 +#cmakedefine GIT_HTTPPARSER_LLHTTP 1 +#cmakedefine GIT_HTTPPARSER_BUILTIN 1 + #cmakedefine GIT_SHA1_COLLISIONDETECT 1 #cmakedefine GIT_SHA1_WIN32 1 #cmakedefine GIT_SHA1_COMMON_CRYPTO 1 #cmakedefine GIT_SHA1_OPENSSL 1 +#cmakedefine GIT_SHA1_OPENSSL_FIPS 1 #cmakedefine GIT_SHA1_OPENSSL_DYNAMIC 1 #cmakedefine GIT_SHA1_MBEDTLS 1 @@ -55,9 +62,13 @@ #cmakedefine GIT_SHA256_WIN32 1 #cmakedefine GIT_SHA256_COMMON_CRYPTO 1 #cmakedefine GIT_SHA256_OPENSSL 1 +#cmakedefine GIT_SHA256_OPENSSL_FIPS 1 #cmakedefine GIT_SHA256_OPENSSL_DYNAMIC 1 #cmakedefine GIT_SHA256_MBEDTLS 1 +#cmakedefine GIT_COMPRESSION_BUILTIN 1 +#cmakedefine GIT_COMPRESSION_ZLIB 1 + #cmakedefine GIT_RAND_GETENTROPY 1 #cmakedefine GIT_RAND_GETLOADAVG 1 diff --git a/src/util/git2_util.h b/src/util/git2_util.h index c62dc241..d47ce5f4 100644 --- a/src/util/git2_util.h +++ b/src/util/git2_util.h @@ -12,6 +12,7 @@ #endif #include "git2/common.h" +#include "git2/sys/errors.h" #include "cc-compat.h" typedef struct git_str git_str; @@ -48,6 +49,10 @@ typedef struct git_str git_str; # define GIT_WARN_UNUSED_RESULT #endif +#if (defined(_WIN32)) && !defined(__CYGWIN__) +# define GIT_WIN32 1 +#endif + #include #include #include @@ -164,5 +169,6 @@ typedef struct git_str git_str; if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } #include "util.h" +#include "ctype_compat.h" #endif diff --git a/src/util/hash/builtin.c b/src/util/hash/builtin.c index cc4aa58f..b4736efb 100644 --- a/src/util/hash/builtin.c +++ b/src/util/hash/builtin.c @@ -32,13 +32,23 @@ int git_hash_sha256_init(git_hash_sha256_ctx *ctx) return 0; } -int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len) +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *_data, size_t len) { + const unsigned char *data = _data; GIT_ASSERT_ARG(ctx); - if (SHA256Input(&ctx->c, data, len)) { - git_error_set(GIT_ERROR_SHA, "SHA256 error"); - return -1; + + while (len > 0) { + unsigned int chunk = (len > UINT_MAX) ? UINT_MAX : (unsigned int)len; + + if (SHA256Input(&ctx->c, data, chunk)) { + git_error_set(GIT_ERROR_SHA, "SHA256 error"); + return -1; + } + + data += chunk; + len -= chunk; } + return 0; } diff --git a/src/util/hash/openssl.c b/src/util/hash/openssl.c index eaf91e74..89110731 100644 --- a/src/util/hash/openssl.c +++ b/src/util/hash/openssl.c @@ -120,6 +120,80 @@ int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) #endif +#ifdef GIT_SHA1_OPENSSL_FIPS + +static const EVP_MD *SHA1_ENGINE_DIGEST_TYPE = NULL; + +int git_hash_sha1_global_init(void) +{ + SHA1_ENGINE_DIGEST_TYPE = EVP_sha1(); + return SHA1_ENGINE_DIGEST_TYPE != NULL ? 0 : -1; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_MD_CTX_destroy(ctx->c); +#else + EVP_MD_CTX_free(ctx->c); +#endif +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT(SHA1_ENGINE_DIGEST_TYPE); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + ctx->c = EVP_MD_CTX_create(); +#else + ctx->c = EVP_MD_CTX_new(); +#endif + + GIT_ASSERT(ctx->c); + + if (EVP_DigestInit_ex(ctx->c, SHA1_ENGINE_DIGEST_TYPE, NULL) != 1) { + git_hash_sha1_ctx_cleanup(ctx); + git_error_set(GIT_ERROR_SHA, "failed to initialize sha1 context"); + return -1; + } + + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx && ctx->c); + + if (EVP_DigestUpdate(ctx->c, data, len) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to update sha1"); + return -1; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + unsigned int len = 0; + + GIT_ASSERT_ARG(ctx && ctx->c); + + if (EVP_DigestFinal(ctx->c, out, &len) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to finalize sha1"); + return -1; + } + + return 0; +} + +#endif + #ifdef GIT_SHA256_OPENSSL # ifdef GIT_OPENSSL_DYNAMIC @@ -193,3 +267,77 @@ int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) } #endif + +#ifdef GIT_SHA256_OPENSSL_FIPS + +static const EVP_MD *SHA256_ENGINE_DIGEST_TYPE = NULL; + +int git_hash_sha256_global_init(void) +{ + SHA256_ENGINE_DIGEST_TYPE = EVP_sha256(); + return SHA256_ENGINE_DIGEST_TYPE != NULL ? 0 : -1; +} + +int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx) +{ + return git_hash_sha256_init(ctx); +} + +void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_MD_CTX_destroy(ctx->c); +#else + EVP_MD_CTX_free(ctx->c); +#endif +} + +int git_hash_sha256_init(git_hash_sha256_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT(SHA256_ENGINE_DIGEST_TYPE); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + ctx->c = EVP_MD_CTX_create(); +#else + ctx->c = EVP_MD_CTX_new(); +#endif + + GIT_ASSERT(ctx->c); + + if (EVP_DigestInit_ex(ctx->c, SHA256_ENGINE_DIGEST_TYPE, NULL) != 1) { + git_hash_sha256_ctx_cleanup(ctx); + git_error_set(GIT_ERROR_SHA, "failed to initialize sha256 context"); + return -1; + } + + return 0; +} + +int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx && ctx->c); + + if (EVP_DigestUpdate(ctx->c, data, len) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to update sha256"); + return -1; + } + + return 0; +} + +int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx) +{ + unsigned int len = 0; + + GIT_ASSERT_ARG(ctx && ctx->c); + + if (EVP_DigestFinal(ctx->c, out, &len) != 1) { + git_error_set(GIT_ERROR_SHA, "failed to finalize sha256"); + return -1; + } + + return 0; +} + +#endif diff --git a/src/util/hash/openssl.h b/src/util/hash/openssl.h index 7cb089ab..8be37fd4 100644 --- a/src/util/hash/openssl.h +++ b/src/util/hash/openssl.h @@ -11,7 +11,11 @@ #include "hash/sha.h" #ifndef GIT_OPENSSL_DYNAMIC -# include +# if defined(GIT_SHA1_OPENSSL_FIPS) || defined(GIT_SHA256_OPENSSL_FIPS) +# include +# else +# include +# endif #else typedef struct { @@ -36,10 +40,22 @@ struct git_hash_sha1_ctx { }; #endif +#ifdef GIT_SHA1_OPENSSL_FIPS +struct git_hash_sha1_ctx { + EVP_MD_CTX* c; +}; +#endif + #ifdef GIT_SHA256_OPENSSL struct git_hash_sha256_ctx { SHA256_CTX c; }; #endif +#ifdef GIT_SHA256_OPENSSL_FIPS +struct git_hash_sha256_ctx { + EVP_MD_CTX* c; +}; +#endif + #endif diff --git a/src/util/hash/sha.h b/src/util/hash/sha.h index 4f596234..8e7eeae8 100644 --- a/src/util/hash/sha.h +++ b/src/util/hash/sha.h @@ -17,7 +17,10 @@ typedef struct git_hash_sha256_ctx git_hash_sha256_ctx; # include "common_crypto.h" #endif -#if defined(GIT_SHA1_OPENSSL) || defined(GIT_SHA256_OPENSSL) +#if defined(GIT_SHA1_OPENSSL) || \ + defined(GIT_SHA1_OPENSSL_FIPS) || \ + defined(GIT_SHA256_OPENSSL) || \ + defined(GIT_SHA256_OPENSSL_FIPS) # include "openssl.h" #endif diff --git a/src/util/hashmap.h b/src/util/hashmap.h new file mode 100644 index 00000000..c29844f3 --- /dev/null +++ b/src/util/hashmap.h @@ -0,0 +1,424 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_hashmap_h__ +#define INCLUDE_hashmap_h__ + +/* + * This is a variation on khash.h from khlib 2013-05-02 (0.2.8) + * + * The MIT License + * + * Copyright (c) 2008, 2009, 2011 by Attractive Chaos + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#define GIT_HASHMAP_INIT {0} +#define GIT_HASHSET_INIT {0} + +#define GIT_HASHMAP_EMPTY +#define GIT_HASHMAP_INLINE GIT_INLINE(GIT_HASHMAP_EMPTY) + +#define GIT_HASHMAP_IS_EMPTY(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define GIT_HASHMAP_IS_DELETE(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define GIT_HASHMAP_IS_EITHER(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define GIT_HASHMAP_SET_EMPTY_FALSE(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define GIT_HASHMAP_SET_DELETE_TRUE(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) +#define GIT_HASHMAP_SET_DELETE_FALSE(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define GIT_HASHMAP_SET_BOTH_FALSE(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) + +#define GIT_HASHMAP_FLAGSIZE(m) ((m) < 16? 1 : (m)>>4) +#define GIT_HASHMAP_ROUNDUP(x) (--(x), (x)|=(x)>>1, \ + (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) + +#define GIT_HASHSET_VAL_T void * + +typedef uint32_t git_hashmap_iter_t; +#define GIT_HASHMAP_ITER_INIT 0 + +#define GIT_HASHMAP_STRUCT_MEMBERS(key_t, val_t) \ + uint32_t n_buckets, \ + size, \ + n_occupied, \ + upper_bound; \ + uint32_t *flags; \ + key_t *keys; \ + val_t *vals; + +#define GIT_HASHMAP_STRUCT(name, key_t, val_t) \ + typedef struct { \ + GIT_HASHMAP_STRUCT_MEMBERS(key_t, val_t) \ + } name; +#define GIT_HASHSET_STRUCT(name, key_t) \ + GIT_HASHMAP_STRUCT(name, key_t, void *) + + +#define GIT_HASHMAP__COMMON_PROTOTYPES(name, key_t, val_t) \ + extern uint32_t name##_size(name *h); \ + extern bool name##_contains(name *h, key_t key); \ + extern int name##_remove(name *h, key_t key); \ + extern void name##_clear(name *h); \ + extern void name##_dispose(name *h); + +#define GIT_HASHMAP_PROTOTYPES(name, key_t, val_t) \ + GIT_HASHMAP__COMMON_PROTOTYPES(name, key_t, val_t) \ + extern int name##_get(val_t *out, name *h, key_t key); \ + extern int name##_put(name *h, key_t key, val_t val); \ + extern int name##_iterate(git_hashmap_iter_t *iter, key_t *key, val_t *val, name *h); \ + extern int name##_foreach(name *h, int (*cb)(key_t, val_t)); + +#define GIT_HASHSET_PROTOTYPES(name, key_t) \ + GIT_HASHMAP__COMMON_PROTOTYPES(name, key_t, GIT_HASHSET_VAL_T) \ + extern int name##_add(name *h, key_t key); \ + extern int name##_iterate(git_hashmap_iter_t *iter, key_t *key, name *h); \ + extern int name##_foreach(name *h, int (*cb)(key_t)); \ + + +#define GIT_HASHMAP__COMMON_FUNCTIONS(name, is_map, scope, key_t, val_t, __hash_fn, __equal_fn) \ + GIT_UNUSED_FUNCTION scope uint32_t name##_size(name *h) \ + { \ + return h->size; \ + } \ + GIT_INLINE(int) name##__idx(uint32_t *out, name *h, key_t key) \ + { \ + if (h->n_buckets) { \ + uint32_t k, i, last, mask, step = 0; \ + GIT_ASSERT((h)->flags); \ + mask = h->n_buckets - 1; \ + k = __hash_fn(key); \ + i = k & mask; \ + last = i; \ + while (!GIT_HASHMAP_IS_EMPTY(h->flags, i) && \ + (GIT_HASHMAP_IS_DELETE(h->flags, i) || !__equal_fn(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) \ + return GIT_ENOTFOUND; \ + } \ + if (GIT_HASHMAP_IS_EITHER(h->flags, i)) \ + return GIT_ENOTFOUND; \ + *out = i; \ + return 0; \ + } \ + return GIT_ENOTFOUND; \ + } \ + GIT_UNUSED_FUNCTION scope bool name##_contains(name *h, key_t key) \ + { \ + uint32_t idx; \ + return name##__idx(&idx, h, key) == 0; \ + } \ + GIT_INLINE(int) name##__remove_at_idx(name *h, uint32_t idx) \ + { \ + if (idx < h->n_buckets && !GIT_HASHMAP_IS_EITHER(h->flags, idx)) { \ + GIT_HASHMAP_SET_DELETE_TRUE(h->flags, idx); \ + --h->size; \ + return 0; \ + } \ + return GIT_ENOTFOUND; \ + } \ + GIT_UNUSED_FUNCTION scope int name##_remove(name *h, key_t key) \ + { \ + uint32_t idx; \ + int error; \ + if ((error = name##__idx(&idx, h, key)) == 0) \ + error = name##__remove_at_idx(h, idx); \ + return error; \ + } \ + GIT_INLINE(int) name##__resize(name *h, uint32_t new_n_buckets) \ + { \ + /* This function uses 0.25*n_buckets bytes of working \ + * space instead of [sizeof(key_t+val_t)+.25]*n_buckets. \ + */ \ + double git_hashmap__upper_bound = 0.77; \ + uint32_t *new_flags = 0; \ + uint32_t j = 1; \ + { \ + GIT_HASHMAP_ROUNDUP(new_n_buckets); \ + if (new_n_buckets < 4) \ + new_n_buckets = 4; \ + if (h->size >= (uint32_t)(new_n_buckets * git_hashmap__upper_bound + 0.5)) { \ + /* Requested size is too small */ \ + j = 0; \ + } else { \ + /* Shrink or expand; rehash */ \ + new_flags = git__reallocarray(NULL, GIT_HASHMAP_FLAGSIZE(new_n_buckets), sizeof(uint32_t)); \ + if (!new_flags) \ + return -1; \ + memset(new_flags, 0xaa, GIT_HASHMAP_FLAGSIZE(new_n_buckets) * sizeof(uint32_t)); \ + if (h->n_buckets < new_n_buckets) { \ + /* Expand */ \ + key_t *new_keys = git__reallocarray(h->keys, new_n_buckets, sizeof(key_t)); \ + if (!new_keys) { \ + git__free(new_flags); \ + return -1; \ + } \ + h->keys = new_keys; \ + if (is_map) { \ + val_t *new_vals = git__reallocarray(h->vals, new_n_buckets, sizeof(val_t)); \ + if (!new_vals) { \ + git__free(new_flags); \ + return -1; \ + } \ + h->vals = new_vals; \ + } \ + } \ + } \ + } \ + if (j) { \ + /* Rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (GIT_HASHMAP_IS_EITHER(h->flags, j) == 0) { \ + key_t key = h->keys[j]; \ + val_t val; \ + uint32_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (is_map) \ + val = h->vals[j]; \ + GIT_HASHMAP_SET_DELETE_TRUE(h->flags, j); \ + while (1) { \ + /* Kick-out process; sort of like in Cuckoo hashing */ \ + uint32_t k, i, step = 0; \ + k = __hash_fn(key); \ + i = k & new_mask; \ + while (!GIT_HASHMAP_IS_EMPTY(new_flags, i)) \ + i = (i + (++step)) & new_mask; \ + GIT_HASHMAP_SET_EMPTY_FALSE(new_flags, i); \ + if (i < h->n_buckets && GIT_HASHMAP_IS_EITHER(h->flags, i) == 0) { \ + /* Kick out the existing element */ \ + { \ + key_t tmp = h->keys[i]; \ + h->keys[i] = key; \ + key = tmp; \ + } \ + if (is_map) { \ + val_t tmp = h->vals[i]; \ + h->vals[i] = val; \ + val = tmp; \ + } \ + /* Mark it as deleted in the old hash table */ \ + GIT_HASHMAP_SET_DELETE_TRUE(h->flags, i); \ + } else { \ + /* Write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (is_map) \ + h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { \ + /* Shrink the hash table */ \ + h->keys = git__reallocarray(h->keys, new_n_buckets, sizeof(key_t)); \ + if (is_map) \ + h->vals = git__reallocarray(h->vals, new_n_buckets, sizeof(val_t)); \ + } \ + /* free the working space */ \ + git__free(h->flags); \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (uint32_t)(h->n_buckets * git_hashmap__upper_bound + 0.5); \ + } \ + return 0; \ + } \ + GIT_INLINE(int) name##__put_idx(uint32_t *idx, bool *key_exists, name *h, key_t key) \ + { \ + uint32_t x; \ + if (h->n_occupied >= h->upper_bound) { \ + /* Update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + /* Clear "deleted" elements */ \ + if (name##__resize(h, h->n_buckets - 1) < 0) \ + return -1; \ + } else if (name##__resize(h, h->n_buckets + 1) < 0) { \ + return -1; \ + } \ + } \ + GIT_ASSERT((h)->flags); \ + GIT_ASSERT((h)->keys); \ + /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + uint32_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; \ + k = __hash_fn(key); \ + i = k & mask; \ + if (GIT_HASHMAP_IS_EMPTY(h->flags, i)) { \ + /* for speed up */ \ + x = i; \ + } else { \ + last = i; \ + while (!GIT_HASHMAP_IS_EMPTY(h->flags, i) && (GIT_HASHMAP_IS_DELETE(h->flags, i) || !__equal_fn(h->keys[i], key))) { \ + if (GIT_HASHMAP_IS_DELETE(h->flags, i)) \ + site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { \ + x = site; \ + break; \ + } \ + } \ + if (x == h->n_buckets) { \ + if (GIT_HASHMAP_IS_EMPTY(h->flags, i) && site != h->n_buckets) \ + x = site; \ + else \ + x = i; \ + } \ + } \ + } \ + if (GIT_HASHMAP_IS_EMPTY(h->flags, x)) { \ + /* not present at all */ \ + h->keys[x] = key; \ + GIT_HASHMAP_SET_BOTH_FALSE(h->flags, x); \ + ++h->size; \ + ++h->n_occupied; \ + *key_exists = 1; \ + } else if (GIT_HASHMAP_IS_DELETE(h->flags, x)) { \ + /* deleted */ \ + h->keys[x] = key; \ + GIT_HASHMAP_SET_BOTH_FALSE(h->flags, x); \ + ++h->size; \ + *key_exists = 1; \ + } else { \ + /* Don't touch h->keys[x] if present and not deleted */ \ + *key_exists = 0; \ + } \ + *idx = x; \ + return 0; \ + } \ + GIT_UNUSED_FUNCTION scope void name##_clear(name *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, GIT_HASHMAP_FLAGSIZE(h->n_buckets) * sizeof(uint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + GIT_UNUSED_FUNCTION scope void name##_dispose(name *h) \ + { \ + git__free(h->flags); \ + git__free(h->keys); \ + git__free(h->vals); \ + memset(h, 0, sizeof(name)); \ + } + +#define GIT_HASHMAP_FUNCTIONS(name, scope, key_t, val_t, __hash_fn, __equal_fn) \ + GIT_HASHMAP__COMMON_FUNCTIONS(name, true, scope, key_t, val_t, __hash_fn, __equal_fn) \ + \ + GIT_UNUSED_FUNCTION scope int name##_get(val_t *out, name *h, key_t key) \ + { \ + uint32_t idx; \ + int error; \ + if ((error = name##__idx(&idx, h, key)) == 0) \ + *out = (h)->vals[idx]; \ + return error; \ + } \ + GIT_UNUSED_FUNCTION scope int name##_put(name *h, key_t key, val_t val) \ + { \ + uint32_t idx; \ + bool key_exists; \ + int error = name##__put_idx(&idx, &key_exists, h, key); \ + if (error) \ + return error; \ + GIT_ASSERT((h)->vals); \ + if (!key_exists) \ + (h)->keys[idx] = key; \ + (h)->vals[idx] = val; \ + return 0; \ + } \ + GIT_UNUSED_FUNCTION scope int name##_iterate(git_hashmap_iter_t *iter, key_t *key, val_t *val, name *h) \ + { \ + for (; *iter < h->n_buckets; (*iter)++) { \ + if (GIT_HASHMAP_IS_EITHER(h->flags, *iter)) \ + continue; \ + if (key) \ + *key = h->keys[*iter]; \ + if (val) \ + *val = h->vals[*iter]; \ + (*iter)++; \ + return 0; \ + } \ + return GIT_ITEROVER; \ + } \ + GIT_UNUSED_FUNCTION scope int name##_foreach(name *h, int (*cb)(key_t, val_t)) \ + { \ + uint32_t idx = 0; \ + key_t key; \ + val_t val; \ + int ret; \ + while ((ret = name##_iterate(&idx, &key, &val, h)) == 0) { \ + if ((ret = cb(key, val)) != 0) \ + return ret; \ + } \ + return ret == GIT_ITEROVER ? 0 : ret; \ + } + +#define GIT_HASHSET_FUNCTIONS(name, scope, key_t, __hash_fn, __equal_fn) \ + GIT_HASHMAP__COMMON_FUNCTIONS(name, false, scope, key_t, void *, __hash_fn, __equal_fn) \ + \ + GIT_UNUSED_FUNCTION scope int name##_add(name *h, key_t key) \ + { \ + uint32_t idx; \ + bool key_exists; \ + int error = name##__put_idx(&idx, &key_exists, h, key); \ + if (error) \ + return error; \ + if (!key_exists) { \ + (h)->keys[idx] = key; \ + } \ + return 0; \ + } \ + GIT_UNUSED_FUNCTION scope int name##_iterate(git_hashmap_iter_t *iter, key_t *key, name *h) \ + { \ + for (; *iter < h->n_buckets; (*iter)++) { \ + if (GIT_HASHMAP_IS_EITHER(h->flags, *iter)) \ + continue; \ + *key = h->keys[*iter]; \ + return 0; \ + } \ + return GIT_ITEROVER; \ + } \ + GIT_UNUSED_FUNCTION scope int name##_foreach(name *h, int (*cb)(key_t)) \ + { \ + git_hashmap_iter_t iter = 0; \ + key_t key; \ + int ret; \ + while ((ret = name##_iterate(&iter, &key, h)) == 0) { \ + if ((ret = cb(key)) != 0) \ + return ret; \ + } \ + return ret == GIT_ITEROVER ? 0 : ret; \ + } + + +#define GIT_HASHSET_SETUP(name, key_t, __hash_fn, __equal_fn) \ + GIT_HASHSET_STRUCT(name, key_t) \ + GIT_HASHSET_FUNCTIONS(name, GIT_HASHMAP_INLINE, key_t, __hash_fn, __equal_fn) +#define GIT_HASHMAP_SETUP(name, key_t, val_t, __hash_fn, __equal_fn) \ + GIT_HASHMAP_STRUCT(name, key_t, val_t) \ + GIT_HASHMAP_FUNCTIONS(name, GIT_HASHMAP_INLINE, key_t, val_t, __hash_fn, __equal_fn) + +#endif diff --git a/src/util/hashmap_str.h b/src/util/hashmap_str.h new file mode 100644 index 00000000..099aac5a --- /dev/null +++ b/src/util/hashmap_str.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_hashmap_str_h__ +#define INCLUDE_hashmap_str_h__ + +#include "hashmap.h" + +GIT_INLINE(uint32_t) git_hashmap_str_hash(const char *s) +{ + uint32_t h = (uint32_t)*s; + + if (h) { + for (++s; *s; ++s) + h = (h << 5) - h + (uint32_t)*s; + } + + return h; +} + +GIT_INLINE(bool) git_hashmap_str_equal(const char *one, const char *two) +{ + return strcmp(one, two) == 0; +} + +#define GIT_HASHMAP_STR_STRUCT(name, val_t) \ + GIT_HASHMAP_STRUCT(name, const char *, val_t) +#define GIT_HASHMAP_STR_PROTOTYPES(name, val_t) \ + GIT_HASHMAP_PROTOTYPES(name, const char *, val_t) +#define GIT_HASHMAP_STR_FUNCTIONS(name, scope, val_t) \ + GIT_HASHMAP_FUNCTIONS(name, scope, const char *, val_t, git_hashmap_str_hash, git_hashmap_str_equal) + +#define GIT_HASHMAP_STR_SETUP(name, val_t) \ + GIT_HASHMAP_STR_STRUCT(name, val_t) \ + GIT_HASHMAP_STR_FUNCTIONS(name, GIT_HASHMAP_INLINE, val_t) + +GIT_HASHSET_SETUP(git_hashset_str, const char *, git_hashmap_str_hash, git_hashmap_str_equal); +GIT_HASHMAP_SETUP(git_hashmap_str, const char *, void *, git_hashmap_str_hash, git_hashmap_str_equal); + +#endif diff --git a/src/util/integer.h b/src/util/integer.h index 63277177..a9e416cc 100644 --- a/src/util/integer.h +++ b/src/util/integer.h @@ -89,7 +89,9 @@ GIT_INLINE(int) git__is_int(int64_t p) /* Use Microsoft's safe integer handling functions where available */ #elif defined(_MSC_VER) -# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# if !defined(ENABLE_INTSAFE_SIGNED_FUNCTIONS) +# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# endif # include # define git__add_sizet_overflow(out, one, two) \ diff --git a/src/util/khash.h b/src/util/khash.h deleted file mode 100644 index c9b7f131..00000000 --- a/src/util/khash.h +++ /dev/null @@ -1,615 +0,0 @@ -/* The MIT License - - Copyright (c) 2008, 2009, 2011 by Attractive Chaos - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* - An example: - -#include "khash.h" -KHASH_MAP_INIT_INT(32, char) -int main() { - int ret, is_missing; - khiter_t k; - khash_t(32) *h = kh_init(32); - k = kh_put(32, h, 5, &ret); - kh_value(h, k) = 10; - k = kh_get(32, h, 10); - is_missing = (k == kh_end(h)); - k = kh_get(32, h, 5); - kh_del(32, h, k); - for (k = kh_begin(h); k != kh_end(h); ++k) - if (kh_exist(h, k)) kh_value(h, k) = 1; - kh_destroy(32, h); - return 0; -} -*/ - -/* - 2013-05-02 (0.2.8): - - * Use quadratic probing. When the capacity is power of 2, stepping function - i*(i+1)/2 guarantees to traverse each bucket. It is better than double - hashing on cache performance and is more robust than linear probing. - - In theory, double hashing should be more robust than quadratic probing. - However, my implementation is probably not for large hash tables, because - the second hash function is closely tied to the first hash function, - which reduce the effectiveness of double hashing. - - Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php - - 2011-12-29 (0.2.7): - - * Minor code clean up; no actual effect. - - 2011-09-16 (0.2.6): - - * The capacity is a power of 2. This seems to dramatically improve the - speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - - - http://code.google.com/p/ulib/ - - http://nothings.org/computer/judy/ - - * Allow to optionally use linear probing which usually has better - performance for random input. Double hashing is still the default as it - is more robust to certain non-random input. - - * Added Wang's integer hash function (not used by default). This hash - function is more robust to certain non-random input. - - 2011-02-14 (0.2.5): - - * Allow to declare global functions. - - 2009-09-26 (0.2.4): - - * Improve portability - - 2008-09-19 (0.2.3): - - * Corrected the example - * Improved interfaces - - 2008-09-11 (0.2.2): - - * Improved speed a little in kh_put() - - 2008-09-10 (0.2.1): - - * Added kh_clear() - * Fixed a compiling error - - 2008-09-02 (0.2.0): - - * Changed to token concatenation which increases flexibility. - - 2008-08-31 (0.1.2): - - * Fixed a bug in kh_get(), which has not been tested previously. - - 2008-08-31 (0.1.1): - - * Added destructor -*/ - - -#ifndef __AC_KHASH_H -#define __AC_KHASH_H - -/*! - @header - - Generic hash table library. - */ - -#define AC_VERSION_KHASH_H "0.2.8" - -#include -#include -#include - -/* compiler specific configuration */ - -typedef uint32_t khint32_t; -typedef uint64_t khint64_t; - -#ifndef kh_inline -#ifdef _MSC_VER -#define kh_inline __inline -#elif defined(__GNUC__) -#define kh_inline __inline__ -#else -#define kh_inline -#endif -#endif /* kh_inline */ - -typedef khint32_t khint_t; -typedef khint_t khiter_t; - -#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) -#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) -#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) -#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) -#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) -#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) -#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) - -#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) - -#ifndef kroundup32 -#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -#endif - -#ifndef kcalloc -#define kcalloc(N,Z) calloc(N,Z) -#endif -#ifndef kmalloc -#define kmalloc(Z) malloc(Z) -#endif -#ifndef krealloc -#define krealloc(P,Z) realloc(P,Z) -#endif -#ifndef kreallocarray -#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) -#endif -#ifndef kfree -#define kfree(P) free(P) -#endif - -static const double __ac_HASH_UPPER = 0.77; - -#define __KHASH_TYPE(name, khkey_t, khval_t) \ - typedef struct kh_##name##_s { \ - khint_t n_buckets, size, n_occupied, upper_bound; \ - khint32_t *flags; \ - khkey_t *keys; \ - khval_t *vals; \ - } kh_##name##_t; - -#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ - extern kh_##name##_t *kh_init_##name(void); \ - extern void kh_destroy_##name(kh_##name##_t *h); \ - extern void kh_clear_##name(kh_##name##_t *h); \ - extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ - extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ - extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ - extern void kh_del_##name(kh_##name##_t *h, khint_t x); - -#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - SCOPE kh_##name##_t *kh_init_##name(void) { \ - return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ - } \ - SCOPE void kh_destroy_##name(kh_##name##_t *h) \ - { \ - if (h) { \ - kfree((void *)h->keys); kfree(h->flags); \ - kfree((void *)h->vals); \ - kfree(h); \ - } \ - } \ - SCOPE void kh_clear_##name(kh_##name##_t *h) \ - { \ - if (h && h->flags) { \ - memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ - h->size = h->n_occupied = 0; \ - } \ - } \ - SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ - { \ - if (h->n_buckets) { \ - khint_t k, i, last, mask, step = 0; \ - mask = h->n_buckets - 1; \ - k = __hash_func(key); i = k & mask; \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - i = (i + (++step)) & mask; \ - if (i == last) return h->n_buckets; \ - } \ - return __ac_iseither(h->flags, i)? h->n_buckets : i; \ - } else return 0; \ - } \ - SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ - { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ - khint32_t *new_flags = 0; \ - khint_t j = 1; \ - { \ - kroundup32(new_n_buckets); \ - if (new_n_buckets < 4) new_n_buckets = 4; \ - if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ - else { /* hash table size to be changed (shrink or expand); rehash */ \ - new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ - if (!new_flags) return -1; \ - memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (h->n_buckets < new_n_buckets) { /* expand */ \ - khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (!new_keys) { kfree(new_flags); return -1; } \ - h->keys = new_keys; \ - if (kh_is_map) { \ - khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - if (!new_vals) { kfree(new_flags); return -1; } \ - h->vals = new_vals; \ - } \ - } /* otherwise shrink */ \ - } \ - } \ - if (j) { /* rehashing is needed */ \ - for (j = 0; j != h->n_buckets; ++j) { \ - if (__ac_iseither(h->flags, j) == 0) { \ - khkey_t key = h->keys[j]; \ - khval_t val; \ - khint_t new_mask; \ - new_mask = new_n_buckets - 1; \ - if (kh_is_map) val = h->vals[j]; \ - __ac_set_isdel_true(h->flags, j); \ - while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ - khint_t k, i, step = 0; \ - k = __hash_func(key); \ - i = k & new_mask; \ - while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ - __ac_set_isempty_false(new_flags, i); \ - if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ - { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ - if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ - __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ - } else { /* write the element and jump out of the loop */ \ - h->keys[i] = key; \ - if (kh_is_map) h->vals[i] = val; \ - break; \ - } \ - } \ - } \ - } \ - if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ - h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - } \ - kfree(h->flags); /* free the working space */ \ - h->flags = new_flags; \ - h->n_buckets = new_n_buckets; \ - h->n_occupied = h->size; \ - h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ - } \ - return 0; \ - } \ - SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ - { \ - khint_t x; \ - if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ - if (h->n_buckets > (h->size<<1)) { \ - if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ - *ret = -1; return h->n_buckets; \ - } \ - } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ - *ret = -1; return h->n_buckets; \ - } \ - } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ - { \ - khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ - x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ - if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ - else { \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - if (__ac_isdel(h->flags, i)) site = i; \ - i = (i + (++step)) & mask; \ - if (i == last) { x = site; break; } \ - } \ - if (x == h->n_buckets) { \ - if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ - else x = i; \ - } \ - } \ - } \ - if (__ac_isempty(h->flags, x)) { /* not present at all */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; ++h->n_occupied; \ - *ret = 1; \ - } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; \ - *ret = 2; \ - } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ - return x; \ - } \ - SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ - { \ - if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ - __ac_set_isdel_true(h->flags, x); \ - --h->size; \ - } \ - } - -#define KHASH_DECLARE(name, khkey_t, khval_t) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_PROTOTYPES(name, khkey_t, khval_t) - -#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -/* --- BEGIN OF HASH FUNCTIONS --- */ - -/*! @function - @abstract Integer hash function - @param key The integer [khint32_t] - @return The hash value [khint_t] - */ -#define kh_int_hash_func(key) (khint32_t)(key) -/*! @function - @abstract Integer comparison function - */ -#define kh_int_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract 64-bit integer hash function - @param key The integer [khint64_t] - @return The hash value [khint_t] - */ -#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) -/*! @function - @abstract 64-bit integer comparison function - */ -#define kh_int64_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract const char* hash function - @param s Pointer to a null terminated string - @return The hash value - */ -static kh_inline khint_t __ac_X31_hash_string(const char *s) -{ - khint_t h = (khint_t)*s; - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; - return h; -} -/*! @function - @abstract Another interface to const char* hash function - @param key Pointer to a null terminated string [const char*] - @return The hash value [khint_t] - */ -#define kh_str_hash_func(key) __ac_X31_hash_string(key) -/*! @function - @abstract Const char* comparison function - */ -#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) - -static kh_inline khint_t __ac_Wang_hash(khint_t key) -{ - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} -#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) - -/* --- END OF HASH FUNCTIONS --- */ - -/* Other convenient macros... */ - -/*! - @abstract Type of the hash table. - @param name Name of the hash table [symbol] - */ -#define khash_t(name) kh_##name##_t - -/*! @function - @abstract Initiate a hash table. - @param name Name of the hash table [symbol] - @return Pointer to the hash table [khash_t(name)*] - */ -#define kh_init(name) kh_init_##name() - -/*! @function - @abstract Destroy a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_destroy(name, h) kh_destroy_##name(h) - -/*! @function - @abstract Reset a hash table without deallocating memory. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_clear(name, h) kh_clear_##name(h) - -/*! @function - @abstract Resize a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param s New size [khint_t] - */ -#define kh_resize(name, h, s) kh_resize_##name(h, s) - -/*! @function - @abstract Insert a key to the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @param r Extra return code: -1 if the operation failed; - 0 if the key is present in the hash table; - 1 if the bucket is empty (never used); 2 if the element in - the bucket has been deleted [int*] - @return Iterator to the inserted element [khint_t] - */ -#define kh_put(name, h, k, r) kh_put_##name(h, k, r) - -/*! @function - @abstract Retrieve a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] - */ -#define kh_get(name, h, k) kh_get_##name(h, k) - -/*! @function - @abstract Remove a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Iterator to the element to be deleted [khint_t] - */ -#define kh_del(name, h, k) kh_del_##name(h, k) - -/*! @function - @abstract Test whether a bucket contains data. - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return 1 if containing data; 0 otherwise [int] - */ -#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) - -/*! @function - @abstract Get key given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Key [type of keys] - */ -#define kh_key(h, x) ((h)->keys[x]) - -/*! @function - @abstract Get value given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Value [type of values] - @discussion For hash sets, calling this results in segfault. - */ -#define kh_val(h, x) ((h)->vals[x]) - -/*! @function - @abstract Alias of kh_val() - */ -#define kh_value(h, x) ((h)->vals[x]) - -/*! @function - @abstract Get the start iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The start iterator [khint_t] - */ -#define kh_begin(h) (khint_t)(0) - -/*! @function - @abstract Get the end iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The end iterator [khint_t] - */ -#define kh_end(h) ((h)->n_buckets) - -/*! @function - @abstract Get the number of elements in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of elements in the hash table [khint_t] - */ -#define kh_size(h) ((h)->size) - -/*! @function - @abstract Get the number of buckets in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of buckets in the hash table [khint_t] - */ -#define kh_n_buckets(h) ((h)->n_buckets) - -/*! @function - @abstract Iterate over the entries in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param kvar Variable to which key will be assigned - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (kvar) = kh_key(h,__i); \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/*! @function - @abstract Iterate over the values in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach_value(h, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/* More convenient interfaces */ - -/*! @function - @abstract Instantiate a hash set containing integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT(name) \ - KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT(name, khval_t) \ - KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT64(name) \ - KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT64(name, khval_t) \ - KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) - -typedef const char *kh_cstr_t; -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_STR(name) \ - KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_STR(name, khval_t) \ - KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) - -#endif /* __AC_KHASH_H */ diff --git a/src/util/net.c b/src/util/net.c index afd52ce0..d7ca48a5 100644 --- a/src/util/net.c +++ b/src/util/net.c @@ -11,7 +11,6 @@ #include "posix.h" #include "str.h" -#include "http_parser.h" #include "runtime.h" #define DEFAULT_PORT_HTTP "80" @@ -22,7 +21,7 @@ #define GIT_NET_URL_PARSER_INIT { 0 } typedef struct { - int hierarchical : 1; + unsigned int hierarchical : 1; const char *scheme; const char *user; @@ -388,6 +387,7 @@ static int url_parse_finalize(git_net_url *url, git_net_url_parser *parser) port = GIT_STR_INIT, path = GIT_STR_INIT, query = GIT_STR_INIT, fragment = GIT_STR_INIT; const char *default_port; + int port_specified = 0; int error = 0; if (parser->scheme_len) { @@ -409,10 +409,13 @@ static int url_parse_finalize(git_net_url *url, git_net_url_parser *parser) (error = git_str_decode_percent(&host, parser->host, parser->host_len)) < 0) goto done; - if (parser->port_len) + if (parser->port_len) { + port_specified = 1; error = git_str_put(&port, parser->port, parser->port_len); - else if (parser->scheme_len && (default_port = default_port_for_scheme(scheme.ptr)) != NULL) + } else if (parser->scheme_len && + (default_port = default_port_for_scheme(scheme.ptr)) != NULL) { error = git_str_puts(&port, default_port); + } if (error < 0) goto done; @@ -441,6 +444,7 @@ static int url_parse_finalize(git_net_url *url, git_net_url_parser *parser) url->fragment = git_str_detach(&fragment); url->username = git_str_detach(&user); url->password = git_str_detach(&password); + url->port_specified = port_specified; error = 0; @@ -657,7 +661,7 @@ static bool has_at(const char *str) int git_net_url_parse_scp(git_net_url *url, const char *given) { const char *default_port = default_port_for_scheme("ssh"); - const char *c, *user, *host, *port, *path = NULL; + const char *c, *user, *host, *port = NULL, *path = NULL; size_t user_len = 0, host_len = 0, port_len = 0; unsigned short bracket = 0; @@ -786,10 +790,12 @@ int git_net_url_parse_scp(git_net_url *url, const char *given) GIT_ASSERT(host_len); GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); - if (port_len) + if (port_len) { + url->port_specified = 1; GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); - else + } else { GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); + } GIT_ASSERT(path); GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); diff --git a/src/util/net.h b/src/util/net.h index 8024956a..360a55db 100644 --- a/src/util/net.h +++ b/src/util/net.h @@ -35,6 +35,8 @@ typedef struct git_net_url { char *fragment; char *username; char *password; + + unsigned int port_specified; } git_net_url; #define GIT_NET_URL_INIT { NULL } diff --git a/src/util/pool.c b/src/util/pool.c index 16ffa398..afbae452 100644 --- a/src/util/pool.c +++ b/src/util/pool.c @@ -144,7 +144,7 @@ int git_pool_init(git_pool *pool, size_t item_size) void git_pool_clear(git_pool *pool) { - git_vector_free_deep(&pool->allocations); + git_vector_dispose_deep(&pool->allocations); } static void *pool_alloc(git_pool *pool, size_t size) { diff --git a/src/util/pool.h b/src/util/pool.h index 0238431b..75a28c3b 100644 --- a/src/util/pool.h +++ b/src/util/pool.h @@ -83,6 +83,11 @@ typedef struct { */ extern int git_pool_init(git_pool *pool, size_t item_size); +GIT_INLINE(bool) git_pool_is_initialized(git_pool *pool) +{ + return (pool->item_size > 0); +} + /** * Free all items in pool */ diff --git a/src/util/pqueue.h b/src/util/pqueue.h index 97232b4a..a8ef0184 100644 --- a/src/util/pqueue.h +++ b/src/util/pqueue.h @@ -33,7 +33,7 @@ extern int git_pqueue_init( size_t init_size, git_vector_cmp cmp); -#define git_pqueue_free git_vector_free +#define git_pqueue_free git_vector_dispose #define git_pqueue_clear git_vector_clear #define git_pqueue_size git_vector_length #define git_pqueue_get git_vector_get diff --git a/src/util/process.h b/src/util/process.h new file mode 100644 index 00000000..a3804ec7 --- /dev/null +++ b/src/util/process.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_process_h__ +#define INCLUDE_process_h__ + +typedef struct git_process git_process; + +typedef struct { + unsigned int use_shell : 1, + capture_in : 1, + capture_out : 1, + capture_err : 1, + exclude_env : 1; + + char *cwd; +} git_process_options; + +typedef enum { + GIT_PROCESS_STATUS_NONE, + GIT_PROCESS_STATUS_NORMAL, + GIT_PROCESS_STATUS_ERROR +} git_process_result_status; + +#define GIT_PROCESS_RESULT_INIT { GIT_PROCESS_STATUS_NONE } + +typedef struct { + git_process_result_status status; + int exitcode; + int signal; +} git_process_result; + +#define GIT_PROCESS_OPTIONS_INIT { 0 } + +#ifdef GIT_WIN32 +# define p_pid_t DWORD +#else +# define p_pid_t pid_t +#endif + +/** + * Create a new process. The command to run should be specified as the + * element of the `arg` array, execv-style. This should be the full path + * to the command to run, the PATH is not obeyed. + * + * This function will add the given environment variables (in `env`) + * to the current environment. Operations on environment variables + * are not thread safe, so you may not modify the environment during + * this call. You can avoid this by setting `exclude_env` in the + * options and providing the entire environment yourself. + * + * @param out location to store the process + * @param args the command (with arguments) to run + * @param args_len the length of the args array + * @param env environment variables to add (or NULL) + * @param env_len the length of the env len + * @param opts the options for creating the process + * @return 0 or an error code + */ +extern int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts); + +/** + * Create a new process. The command to run should be specified as the + * `cmdline` option - which is the full text of the command line as it + * would be specified or run by a user. The command to run will be + * looked up in the PATH. + * + * On Unix, this will be executed by the system's shell (`/bin/sh`) + * and may contain _Bourne-style_ shell quoting rules. On Windows, + * this will be passed to `CreateProcess`, and similarly, may + * contain _Windows-style_ shell quoting rules. + * + * This function will add the given environment variables (in `env`) + * to the current environment. Operations on environment variables + * are not thread safe, so you may not modify the environment during + * this call. You can avoid this by setting `exclude_env` in the + * options and providing the entire environment yourself. + */ +extern int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts); + +#ifdef GIT_WIN32 + +extern int git_process__appname( + git_str *out, + const char *cmdline); + +/* Windows path parsing is tricky; this helper function is for testing. */ +extern int git_process__cmdline( + git_str *out, + const char **in, + size_t in_len); + +#endif + +/* + * Whether the given string looks like a command line option (starts + * with a dash). This is useful for examining strings that will become + * cmdline arguments to ensure that they are not erroneously treated + * as an option. For example, arguments to `ssh`. + */ +GIT_INLINE(bool) git_process__is_cmdline_option(const char *str) +{ + return (str && str[0] == '-'); +} + +/** + * Start the process. + * + * @param process the process to start + * @return 0 or an error code + */ +extern int git_process_start(git_process *process); + +/** + * Returns the process id of the process. + * + * @param out pointer to a pid_t to store the process id + * @param process the process to query + * @return 0 or an error code + */ +extern int git_process_id(p_pid_t *out, git_process *process); + +/** + * Read from the process's stdout. The process must have been created with + * `capture_out` set to true. + * + * @param process the process to read from + * @param buf the buf to read into + * @param count maximum number of bytes to read + * @return number of bytes read or an error code + */ +extern ssize_t git_process_read(git_process *process, void *buf, size_t count); + +/** + * Read from the process's stderr. The process must have been created with + * `capture_err` set to true. + * + * @param process the process to read from + * @param buf the buf to read into + * @param count maximum number of bytes to read + * @return number of bytes read or an error code + */ +extern ssize_t git_process_read_err(git_process *process, void *buf, size_t count); + +/** + * Write to the process's stdin. The process must have been created with + * `capture_in` set to true. + * + * @param process the process to write to + * @param buf the buf to write + * @param count maximum number of bytes to write + * @return number of bytes written or an error code + */ +extern ssize_t git_process_write(git_process *process, const void *buf, size_t count); + +/** + * Wait for the process to finish. + * + * @param result the result of the process or NULL + * @param process the process to wait on + */ +extern int git_process_wait(git_process_result *result, git_process *process); + +/** + * Close the input pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_in(git_process *process); + +/** + * Close the output pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_out(git_process *process); + +/** + * Close the error pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_err(git_process *process); + +/** + * Close all resources that are used by the process. This does not + * wait for the process to complete. + * + * @parma process the process to close + */ +extern int git_process_close(git_process *process); + +/** + * Place a human-readable error message in the given git buffer. + * + * @param msg the buffer to store the message + * @param result the process result that produced an error + */ +extern int git_process_result_msg(git_str *msg, git_process_result *result); + +/** + * Free a process structure + * + * @param process the process to free + */ +extern void git_process_free(git_process *process); + +#endif diff --git a/src/util/rand.c b/src/util/rand.c index 2ed06057..2b137a57 100644 --- a/src/util/rand.c +++ b/src/util/rand.c @@ -10,10 +10,6 @@ See . */ #include "rand.h" #include "runtime.h" -#if defined(GIT_RAND_GETENTROPY) -# include -#endif - #if defined(GIT_WIN32) # include #endif @@ -80,10 +76,10 @@ GIT_INLINE(int) getseed(uint64_t *seed) GIT_INLINE(int) getseed(uint64_t *seed) { struct timeval tv; - double loadavg[3]; int fd; # if defined(GIT_RAND_GETLOADAVG) + double loadavg[3]; bits convert; # endif @@ -129,8 +125,6 @@ GIT_INLINE(int) getseed(uint64_t *seed) convert.f = loadavg[0]; *seed ^= (convert.d >> 36); convert.f = loadavg[1]; *seed ^= (convert.d); convert.f = loadavg[2]; *seed ^= (convert.d >> 16); -# else - GIT_UNUSED(loadavg[0]); # endif *seed ^= git_time_monotonic(); diff --git a/src/util/regexp.c b/src/util/regexp.c index 08700882..eb458224 100644 --- a/src/util/regexp.c +++ b/src/util/regexp.c @@ -125,7 +125,7 @@ int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { git_error_set_oom(); - goto out; + return -1; } if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), diff --git a/src/util/sortedcache.c b/src/util/sortedcache.c index 7ff900ef..05b94347 100644 --- a/src/util/sortedcache.c +++ b/src/util/sortedcache.c @@ -6,6 +6,7 @@ */ #include "sortedcache.h" +#include "hashmap.h" int git_sortedcache_new( git_sortedcache **out, @@ -26,8 +27,7 @@ int git_sortedcache_new( GIT_ERROR_CHECK_ALLOC(sc); if (git_pool_init(&sc->pool, 1) < 0 || - git_vector_init(&sc->items, 4, item_cmp) < 0 || - git_strmap_new(&sc->map) < 0) + git_vector_init(&sc->items, 4, item_cmp) < 0) goto fail; if (git_rwlock_init(&sc->lock)) { @@ -46,8 +46,7 @@ int git_sortedcache_new( return 0; fail: - git_strmap_free(sc->map); - git_vector_free(&sc->items); + git_vector_dispose(&sc->items); git_pool_clear(&sc->pool); git__free(sc); return -1; @@ -65,7 +64,7 @@ const char *git_sortedcache_path(git_sortedcache *sc) static void sortedcache_clear(git_sortedcache *sc) { - git_strmap_clear(sc->map); + git_hashmap_str_clear(&sc->map); if (sc->free_item) { size_t i; @@ -88,8 +87,8 @@ static void sortedcache_free(git_sortedcache *sc) return; sortedcache_clear(sc); - git_vector_free(&sc->items); - git_strmap_free(sc->map); + git_vector_dispose(&sc->items); + git_hashmap_str_dispose(&sc->map); git_sortedcache_wunlock(sc); @@ -274,7 +273,7 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) char *item_key; void *item; - if ((item = git_strmap_get(sc->map, key)) != NULL) + if (git_hashmap_str_get(&item, &sc->map, key) == 0) goto done; keylen = strlen(key); @@ -294,11 +293,11 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) item_key = ((char *)item) + sc->item_path_offset; memcpy(item_key, key, keylen); - if ((error = git_strmap_set(sc->map, item_key, item)) < 0) + if ((error = git_hashmap_str_put(&sc->map, item_key, item)) < 0) goto done; if ((error = git_vector_insert(&sc->items, item)) < 0) - git_strmap_delete(sc->map, item_key); + git_hashmap_str_remove(&sc->map, item_key); done: if (out) @@ -307,9 +306,11 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) } /* lookup item by key */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) +void *git_sortedcache_lookup(git_sortedcache *sc, const char *key) { - return git_strmap_get(sc->map, key); + void *value; + + return git_hashmap_str_get(&value, &sc->map, key) == 0 ? value : NULL; } /* find out how many items are in the cache */ @@ -370,7 +371,7 @@ int git_sortedcache_remove(git_sortedcache *sc, size_t pos) (void)git_vector_remove(&sc->items, pos); - git_strmap_delete(sc->map, item + sc->item_path_offset); + git_hashmap_str_remove(&sc->map, item + sc->item_path_offset); if (sc->free_item) sc->free_item(sc->free_item_payload, item); diff --git a/src/util/sortedcache.h b/src/util/sortedcache.h index 3eee4659..d4383e96 100644 --- a/src/util/sortedcache.h +++ b/src/util/sortedcache.h @@ -14,7 +14,7 @@ #include "vector.h" #include "thread.h" #include "pool.h" -#include "strmap.h" +#include "hashmap_str.h" #include @@ -36,7 +36,7 @@ typedef struct { void *free_item_payload; git_pool pool; git_vector items; - git_strmap *map; + git_hashmap_str map; git_futils_filestamp stamp; char path[GIT_FLEX_ARRAY]; } git_sortedcache; @@ -163,7 +163,7 @@ GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); void git_sortedcache_runlock(git_sortedcache *sc); /* Lookup item by key - returns NULL if not found */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); +void *git_sortedcache_lookup(git_sortedcache *sc, const char *key); /* Get how many items are in the cache * diff --git a/src/util/str.c b/src/util/str.c index 0d405bfd..7b8d99bc 100644 --- a/src/util/str.c +++ b/src/util/str.c @@ -485,8 +485,8 @@ int git_str_decode_percent( for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { if (str[str_pos] == '%' && str_len > str_pos + 2 && - isxdigit(str[str_pos + 1]) && - isxdigit(str[str_pos + 2])) { + git__isxdigit(str[str_pos + 1]) && + git__isxdigit(str[str_pos + 2])) { buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + HEX_DECODE(str[str_pos + 2]); str_pos += 2; @@ -1065,10 +1065,13 @@ int git_str_puts_escaped( git_str *buf, const char *string, const char *esc_chars, - const char *esc_with) + const char *esc_prefix, + const char *esc_suffix) { const char *scan; - size_t total = 0, esc_len = strlen(esc_with), count, alloclen; + size_t total = 0, count, alloclen; + size_t esc_prefix_len = esc_prefix ? strlen(esc_prefix) : 0; + size_t esc_suffix_len = esc_suffix ? strlen(esc_suffix) : 0; if (!string) return 0; @@ -1080,7 +1083,7 @@ int git_str_puts_escaped( scan += count; /* count run of escaped characters */ count = strspn(scan, esc_chars); - total += count * (esc_len + 1); + total += count * (esc_prefix_len + esc_suffix_len + 1); scan += count; } @@ -1096,13 +1099,22 @@ int git_str_puts_escaped( buf->size += count; for (count = strspn(scan, esc_chars); count > 0; --count) { - /* copy escape sequence */ - memmove(buf->ptr + buf->size, esc_with, esc_len); - buf->size += esc_len; + /* copy escape prefix sequence */ + if (esc_prefix) { + memmove(buf->ptr + buf->size, esc_prefix, esc_prefix_len); + buf->size += esc_prefix_len; + } + /* copy character to be escaped */ buf->ptr[buf->size] = *scan; buf->size++; scan++; + + /* copy escape suffix sequence */ + if (esc_suffix) { + memmove(buf->ptr + buf->size, esc_suffix, esc_suffix_len); + buf->size += esc_suffix_len; + } } } diff --git a/src/util/str.h b/src/util/str.h index 588e6fc2..ad9b892c 100644 --- a/src/util/str.h +++ b/src/util/str.h @@ -268,21 +268,23 @@ int git_str_splice( * @param str String buffer to append data to * @param string String to escape and append * @param esc_chars Characters to be escaped - * @param esc_with String to insert in from of each found character + * @param esc_prefix String to insert as prefix of each found character + * @param esc_suffix String to insert as suffix of each found character * @return 0 on success, <0 on failure (probably allocation problem) */ extern int git_str_puts_escaped( git_str *str, const char *string, const char *esc_chars, - const char *esc_with); + const char *esc_prefix, + const char *esc_suffix); /** * Append string escaping characters that are regex special */ GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) { - return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); + return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\", NULL); } /** diff --git a/src/util/strlist.c b/src/util/strlist.c new file mode 100644 index 00000000..df5640c2 --- /dev/null +++ b/src/util/strlist.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2_util.h" +#include "vector.h" +#include "strlist.h" + +int git_strlist_copy(char ***out, const char **in, size_t len) +{ + char **dup; + size_t i; + + dup = git__calloc(len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +int git_strlist_copy_with_null(char ***out, const char **in, size_t len) +{ + char **dup; + size_t new_len, i; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_len, len, 1); + + dup = git__calloc(new_len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +bool git_strlist_contains_prefix( + const char **strings, + size_t len, + const char *str, + size_t n) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (strncmp(strings[i], str, n) == 0) + return true; + } + + return false; +} + +bool git_strlist_contains_key( + const char **strings, + size_t len, + const char *key, + char delimiter) +{ + const char *c; + + for (c = key; *c; c++) { + if (*c == delimiter) + break; + } + + return *c ? + git_strlist_contains_prefix(strings, len, key, (c - key)) : + false; +} + +void git_strlist_free(char **strings, size_t len) +{ + size_t i; + + if (!strings) + return; + + for (i = 0; i < len; i++) + git__free(strings[i]); + + git__free(strings); +} + +void git_strlist_free_with_null(char **strings) +{ + char **s; + + if (!strings) + return; + + for (s = strings; *s; s++) + git__free(*s); + + git__free(strings); +} diff --git a/src/util/strlist.h b/src/util/strlist.h new file mode 100644 index 00000000..68fbf8fb --- /dev/null +++ b/src/util/strlist.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "git2_util.h" + +extern int git_strlist_copy(char ***out, const char **in, size_t len); + +extern int git_strlist_copy_with_null( + char ***out, + const char **in, + size_t len); + +extern bool git_strlist_contains_prefix( + const char **strings, + size_t len, + const char *str, + size_t n); + +extern bool git_strlist_contains_key( + const char **strings, + size_t len, + const char *key, + char delimiter); + +extern void git_strlist_free(char **strings, size_t len); + +extern void git_strlist_free_with_null(char **strings); + +#endif diff --git a/src/util/strmap.c b/src/util/strmap.c deleted file mode 100644 index c6e5b6dc..00000000 --- a/src/util/strmap.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "strmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(str, const char *, void *) - -__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) - -int git_strmap_new(git_strmap **out) -{ - *out = kh_init(str); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_strmap_free(git_strmap *map) -{ - kh_destroy(str, map); -} - -void git_strmap_clear(git_strmap *map) -{ - kh_clear(str, map); -} - -size_t git_strmap_size(git_strmap *map) -{ - return kh_size(map); -} - -void *git_strmap_get(git_strmap *map, const char *key) -{ - size_t idx = kh_get(str, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_strmap_set(git_strmap *map, const char *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(str, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_strmap_delete(git_strmap *map, const char *key) -{ - khiter_t idx = kh_get(str, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(str, map, idx); - return 0; -} - -int git_strmap_exists(git_strmap *map, const char *key) -{ - return kh_get(str, map, key) != kh_end(map); -} - -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_val(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/util/strmap.h b/src/util/strmap.h deleted file mode 100644 index b64d3dcb..00000000 --- a/src/util/strmap.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_strmap_h__ -#define INCLUDE_strmap_h__ - -#include "git2_util.h" - -/** A map with C strings as key. */ -typedef struct kh_str_s git_strmap; - -/** - * Allocate a new string map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_strmap_new(git_strmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free keys or values added - * to this map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_strmap_free(git_strmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_strmap_clear(git_strmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_strmap_size(git_strmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_strmap_get(git_strmap *map, const char *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_strmap_set(git_strmap *map, const char *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_strmap_delete(git_strmap *map, const char *key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_strmap_exists(git_strmap *map, const char *key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key); - -#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ - code; \ - } } - -#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h index 778477e8..60f27d3d 100644 --- a/src/util/unix/posix.h +++ b/src/util/unix/posix.h @@ -54,8 +54,6 @@ GIT_INLINE(int) p_fsync(int fd) #define p_send(s,b,l,f) send(s,b,l,f) #define p_inet_pton(a, b, c) inet_pton(a, b, c) -#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) -#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) #define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) #define p_snprintf snprintf #define p_chdir(p) chdir(p) diff --git a/src/util/unix/process.c b/src/util/unix/process.c new file mode 100644 index 00000000..fb0484c7 --- /dev/null +++ b/src/util/unix/process.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include +#include + +#include "git2_util.h" +#include "vector.h" +#include "futils.h" +#include "process.h" +#include "strlist.h" + +#ifdef __APPLE__ + #include + #define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif + +struct git_process { + char **args; + char **env; + + char *cwd; + + unsigned int use_shell : 1, + capture_in : 1, + capture_out : 1, + capture_err : 1; + + pid_t pid; + + int child_in; + int child_out; + int child_err; + git_process_result_status status; +}; + +GIT_INLINE(bool) is_delete_env(const char *env) +{ + char *c = strchr(env, '='); + + if (c == NULL) + return false; + + return *(c+1) == '\0'; +} + +GIT_INLINE(int) insert_dup(git_vector *v, const char *s) +{ + char *dup = git__strdup(s); + + GIT_ERROR_CHECK_ALLOC(dup); + + return git_vector_insert(v, dup); +} + +static int setup_args( + char ***out, + const char **args, + size_t args_len, + bool use_shell) +{ + git_vector prefixed = GIT_VECTOR_INIT; + git_str first = GIT_STR_INIT; + size_t cnt; + + GIT_ASSERT(args && args_len); + + if (use_shell) { + if (git_str_puts(&first, args[0]) < 0 || + git_str_puts(&first, " \"$@\"") < 0 || + insert_dup(&prefixed, "/bin/sh") < 0 || + insert_dup(&prefixed, "-c") < 0 || + git_vector_insert(&prefixed, git_str_detach(&first)) < 0) + goto on_error; + } + + for (cnt = 0; args && cnt < args_len; cnt++) { + if (insert_dup(&prefixed, args[cnt]) < 0) + goto on_error; + } + + git_vector_insert(&prefixed, NULL); + + *out = (char **)prefixed.contents; + + return 0; + +on_error: + git_str_dispose(&first); + git_vector_dispose_deep(&prefixed); + return -1; +} + +static int merge_env( + char ***out, + const char **env, + size_t env_len, + bool exclude_env) +{ + git_vector merged = GIT_VECTOR_INIT; + char **kv; + size_t max, cnt; + int error = 0; + + for (max = env_len, kv = environ; !exclude_env && *kv; kv++) + max++; + + if ((error = git_vector_init(&merged, max, NULL)) < 0) + goto on_error; + + for (cnt = 0; env && cnt < env_len; cnt++) { + if (is_delete_env(env[cnt])) + continue; + + if (insert_dup(&merged, env[cnt]) < 0) + goto on_error; + } + + if (!exclude_env) { + for (kv = environ; *kv; kv++) { + if (env && git_strlist_contains_key(env, env_len, *kv, '=')) + continue; + + if (insert_dup(&merged, *kv) < 0) + goto on_error; + } + } + + if (merged.length == 0) { + *out = NULL; + error = 0; + goto on_error; + } + + git_vector_insert(&merged, NULL); + + *out = (char **)merged.contents; + + return 0; + +on_error: + git_vector_dispose_deep(&merged); + return error; +} + +int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process *process; + + GIT_ASSERT_ARG(out && args && args_len > 0); + + *out = NULL; + + process = git__calloc(sizeof(git_process), 1); + GIT_ERROR_CHECK_ALLOC(process); + + if (setup_args(&process->args, args, args_len, opts ? opts->use_shell : false) < 0 || + merge_env(&process->env, env, env_len, opts ? opts->exclude_env : false) < 0) { + git_process_free(process); + return -1; + } + + if (opts) { + process->use_shell = opts->use_shell; + process->capture_in = opts->capture_in; + process->capture_out = opts->capture_out; + process->capture_err = opts->capture_err; + + if (opts->cwd) { + process->cwd = git__strdup(opts->cwd); + GIT_ERROR_CHECK_ALLOC(process->cwd); + } + } + + process->child_in = -1; + process->child_out = -1; + process->child_err = -1; + process->status = -1; + + *out = process; + return 0; +} + +extern int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process_options merged_opts = {0}; + + memcpy(&merged_opts, opts, sizeof(git_process_options)); + merged_opts.use_shell = 1; + + return git_process_new(out, &cmdline, 1, env, env_len, &merged_opts); +} + +#define CLOSE_FD(fd) \ + if (fd >= 0) { \ + close(fd); \ + fd = -1; \ + } + +static int try_read_status(size_t *out, int fd, void *buf, size_t len) +{ + size_t read_len = 0; + int ret = -1; + + while (ret && read_len < len) { + ret = read(fd, buf + read_len, len - read_len); + + if (ret < 0 && errno != EAGAIN && errno != EINTR) { + git_error_set(GIT_ERROR_OS, "could not read child status"); + return -1; + } + + read_len += ret; + } + + *out = read_len; + return 0; +} + + +static int read_status(int fd) +{ + size_t status_len = sizeof(int) * 3, read_len = 0; + char buffer[status_len], fn[128]; + int error, fn_error, os_error, fn_len = 0; + + if ((error = try_read_status(&read_len, fd, buffer, status_len)) < 0) + return error; + + /* Immediate EOF indicates the exec succeeded. */ + if (read_len == 0) + return 0; + + if (read_len < status_len) { + git_error_set(GIT_ERROR_INVALID, "child status truncated"); + return -1; + } + + memcpy(&fn_error, &buffer[0], sizeof(int)); + memcpy(&os_error, &buffer[sizeof(int)], sizeof(int)); + memcpy(&fn_len, &buffer[sizeof(int) * 2], sizeof(int)); + + if (fn_len > 0) { + fn_len = min(fn_len, (int)(ARRAY_SIZE(fn) - 1)); + + if ((error = try_read_status(&read_len, fd, fn, fn_len)) < 0) + return error; + + fn[fn_len] = '\0'; + } else { + fn[0] = '\0'; + } + + if (fn_error) { + errno = os_error; + git_error_set(GIT_ERROR_OS, "could not %s", fn[0] ? fn : "(unknown)"); + } + + return fn_error; +} + +static bool try_write_status(int fd, const void *buf, size_t len) +{ + size_t write_len; + int ret; + + for (write_len = 0; write_len < len; ) { + ret = write(fd, buf + write_len, len - write_len); + + if (ret <= 0) + break; + + write_len += ret; + } + + return (len == write_len); +} + +static void write_status(int fd, const char *fn, int error, int os_error) +{ + size_t status_len = sizeof(int) * 3, fn_len; + char buffer[status_len]; + + fn_len = strlen(fn); + + if (fn_len > INT_MAX) + fn_len = INT_MAX; + + memcpy(&buffer[0], &error, sizeof(int)); + memcpy(&buffer[sizeof(int)], &os_error, sizeof(int)); + memcpy(&buffer[sizeof(int) * 2], &fn_len, sizeof(int)); + + /* Do our best effort to write all the status. */ + if (!try_write_status(fd, buffer, status_len)) + return; + + if (fn_len) + try_write_status(fd, fn, fn_len); +} + +static int resolve_path(git_process *process) +{ + git_str full_path = GIT_STR_INIT; + int error = 0; + + /* The shell will resolve the path for us */ + if (process->use_shell) + goto done; + + error = git_fs_path_find_executable(&full_path, process->args[0]); + + if (error == GIT_ENOTFOUND) { + git_error_set(GIT_ERROR_SSH, "cannot run %s: No such file or directory", process->args[0]); + error = -1; + } + + if (error) + goto done; + + git__free(process->args[0]); + process->args[0] = git_str_detach(&full_path); + +done: + git_str_dispose(&full_path); + return error; +} + +int git_process_start(git_process *process) +{ + int in[2] = { -1, -1 }, out[2] = { -1, -1 }, + err[2] = { -1, -1 }, status[2] = { -1, -1 }; + int fdflags, state, error; + pid_t pid; + + /* Locate the path (unless we're letting the shell do it for us) */ + if ((error = resolve_path(process)) < 0) + goto on_error; + + /* Set up the pipes to read from/write to the process */ + if ((process->capture_in && pipe(in) < 0) || + (process->capture_out && pipe(out) < 0) || + (process->capture_err && pipe(err) < 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + /* Set up a self-pipe for status from the forked process. */ + if (pipe(status) < 0 || + (fdflags = fcntl(status[1], F_GETFD)) < 0 || + fcntl(status[1], F_SETFD, fdflags | FD_CLOEXEC) < 0) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + switch (pid = fork()) { + case -1: + git_error_set(GIT_ERROR_OS, "could not fork"); + goto on_error; + + /* Child: start the process. */ + case 0: + /* Close the opposing side of the pipes */ + CLOSE_FD(status[0]); + + if (process->capture_in) { + CLOSE_FD(in[1]); + dup2(in[0], STDIN_FILENO); + } + + if (process->capture_out) { + CLOSE_FD(out[0]); + dup2(out[1], STDOUT_FILENO); + } + + if (process->capture_err) { + CLOSE_FD(err[0]); + dup2(err[1], STDERR_FILENO); + } + + if (process->cwd && (error = chdir(process->cwd)) < 0) { + write_status(status[1], "chdir", error, errno); + exit(0); + } + + /* + * Exec the process and write the results back if the + * call fails. If it succeeds, we'll close the status + * pipe (via CLOEXEC) and the parent will know. + */ + error = execve(process->args[0], + process->args, + process->env); + + write_status(status[1], "execve", error, errno); + exit(0); + + /* Parent: make sure the child process exec'd correctly. */ + default: + /* Close the opposing side of the pipes */ + CLOSE_FD(status[1]); + + if (process->capture_in) { + CLOSE_FD(in[0]); + process->child_in = in[1]; + } + + if (process->capture_out) { + CLOSE_FD(out[1]); + process->child_out = out[0]; + } + + if (process->capture_err) { + CLOSE_FD(err[1]); + process->child_err = err[0]; + } + + /* Try to read the status */ + process->status = status[0]; + if ((error = read_status(status[0])) < 0) { + waitpid(process->pid, &state, 0); + goto on_error; + } + + process->pid = pid; + return 0; + } + +on_error: + CLOSE_FD(in[0]); CLOSE_FD(in[1]); + CLOSE_FD(out[0]); CLOSE_FD(out[1]); + CLOSE_FD(err[0]); CLOSE_FD(err[1]); + CLOSE_FD(status[0]); CLOSE_FD(status[1]); + return -1; +} + +int git_process_id(p_pid_t *out, git_process *process) +{ + GIT_ASSERT(out && process); + + if (!process->pid) { + git_error_set(GIT_ERROR_INVALID, "process not running"); + return -1; + } + + *out = process->pid; + return 0; +} + +static ssize_t process_read(int fd, void *buf, size_t count) +{ + ssize_t ret; + + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if ((ret = read(fd, buf, count)) < 0) { + git_error_set(GIT_ERROR_OS, "could not read from child process"); + return -1; + } + + return ret; +} + +ssize_t git_process_read(git_process *process, void *buf, size_t count) +{ + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_out); + + return process_read(process->child_out, buf, count); +} + +ssize_t git_process_read_err(git_process *process, void *buf, size_t count) +{ + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_err); + + return process_read(process->child_err, buf, count); +} + +#ifdef GIT_THREADS + +# define signal_state sigset_t + +/* + * Since signal-handling is process-wide, we cannot simply use + * SIG_IGN to avoid SIGPIPE. Instead: http://www.microhowto.info:80/howto/ignore_sigpipe_without_affecting_other_threads_in_a_process.html + */ + +GIT_INLINE(int) disable_signals(sigset_t *saved_mask) +{ + sigset_t sigpipe_mask; + + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, saved_mask) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal mask"); + return -1; + } + + return 0; +} + +GIT_INLINE(int) restore_signals(sigset_t *saved_mask) +{ + sigset_t sigpipe_mask, pending; + int signal; + + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (sigpending(&pending) < 0) { + git_error_set(GIT_ERROR_OS, "could not examine pending signals"); + return -1; + } + + if (sigismember(&pending, SIGPIPE) == 1 && + sigwait(&sigpipe_mask, &signal) < 0) { + git_error_set(GIT_ERROR_OS, "could not wait for (blocking) signal delivery"); + return -1; + } + + if (pthread_sigmask(SIG_SETMASK, saved_mask, 0) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal mask"); + return -1; + } + + return 0; +} + +#else + +# define signal_state struct sigaction + +GIT_INLINE(int) disable_signals(struct sigaction *saved_handler) +{ + struct sigaction ign_handler = { 0 }; + + ign_handler.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &ign_handler, saved_handler) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal handler"); + return -1; + } + + return 0; +} + +GIT_INLINE(int) restore_signals(struct sigaction *saved_handler) +{ + if (sigaction(SIGPIPE, saved_handler, NULL) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal handler"); + return -1; + } + + return 0; +} + +#endif + +ssize_t git_process_write(git_process *process, const void *buf, size_t count) +{ + signal_state saved_signal; + ssize_t ret; + + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_in); + + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (disable_signals(&saved_signal) < 0) + return -1; + + if ((ret = write(process->child_in, buf, count)) < 0) + git_error_set(GIT_ERROR_OS, "could not write to child process"); + + if (restore_signals(&saved_signal) < 0) + return -1; + + return (ret < 0) ? -1 : ret; +} + +int git_process_close_in(git_process *process) +{ + if (!process->capture_in) { + git_error_set(GIT_ERROR_INVALID, "input is not open"); + return -1; + } + + CLOSE_FD(process->child_in); + return 0; +} + +int git_process_close_out(git_process *process) +{ + if (!process->capture_out) { + git_error_set(GIT_ERROR_INVALID, "output is not open"); + return -1; + } + + CLOSE_FD(process->child_out); + return 0; +} + +int git_process_close_err(git_process *process) +{ + if (!process->capture_err) { + git_error_set(GIT_ERROR_INVALID, "error is not open"); + return -1; + } + + CLOSE_FD(process->child_err); + return 0; +} + +int git_process_close(git_process *process) +{ + CLOSE_FD(process->child_in); + CLOSE_FD(process->child_out); + CLOSE_FD(process->child_err); + + return 0; +} + +int git_process_wait(git_process_result *result, git_process *process) +{ + int state; + + if (result) + memset(result, 0, sizeof(git_process_result)); + + if (!process->pid) { + git_error_set(GIT_ERROR_INVALID, "process is stopped"); + return -1; + } + + if (waitpid(process->pid, &state, 0) < 0) { + git_error_set(GIT_ERROR_OS, "could not wait for child"); + return -1; + } + + process->pid = 0; + + if (result) { + if (WIFEXITED(state)) { + result->status = GIT_PROCESS_STATUS_NORMAL; + result->exitcode = WEXITSTATUS(state); + } else if (WIFSIGNALED(state)) { + result->status = GIT_PROCESS_STATUS_ERROR; + result->signal = WTERMSIG(state); + } else { + result->status = GIT_PROCESS_STATUS_ERROR; + } + } + + return 0; +} + +int git_process_result_msg(git_str *out, git_process_result *result) +{ + if (result->status == GIT_PROCESS_STATUS_NONE) { + return git_str_puts(out, "process not started"); + } else if (result->status == GIT_PROCESS_STATUS_NORMAL) { + return git_str_printf(out, "process exited with code %d", + result->exitcode); + } else if (result->signal) { + return git_str_printf(out, "process exited on signal %d", + result->signal); + } + + return git_str_puts(out, "unknown error"); +} + +void git_process_free(git_process *process) +{ + if (!process) + return; + + if (process->pid) + git_process_close(process); + + git__free(process->cwd); + git_strlist_free_with_null(process->args); + git_strlist_free_with_null(process->env); + git__free(process); +} diff --git a/src/util/unix/realpath.c b/src/util/unix/realpath.c index 9e31a63b..2565e2e8 100644 --- a/src/util/unix/realpath.c +++ b/src/util/unix/realpath.c @@ -16,17 +16,35 @@ char *p_realpath(const char *pathname, char *resolved) { - char *ret; - if ((ret = realpath(pathname, resolved)) == NULL) + char *result; + + if ((result = realpath(pathname, resolved)) == NULL) return NULL; #ifdef __OpenBSD__ /* The OpenBSD realpath function behaves differently, * figure out if the file exists */ - if (access(ret, F_OK) < 0) - ret = NULL; + if (access(result, F_OK) < 0) { + if (!resolved) + free(result); + + return NULL; + } #endif - return ret; + + /* + * If resolved == NULL, the system has allocated the result + * string. We need to strdup this into _our_ allocator pool + * so that callers can free it with git__free. + */ + if (!resolved) { + char *dup = git__strdup(result); + free(result); + + result = dup; + } + + return result; } #endif diff --git a/src/util/util.c b/src/util/util.c index c8e8303a..3e4d9344 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -269,13 +269,26 @@ int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) return prefixcmp(str, str_n, prefix, true); } -int git__suffixcmp(const char *str, const char *suffix) +static int suffixcmp(const char *str, const char *suffix, bool icase) { size_t a = strlen(str); size_t b = strlen(suffix); + if (a < b) return -1; - return strcmp(str + (a - b), suffix); + + return icase ? strcasecmp(str + (a - b), suffix) : + strcmp(str + (a - b), suffix); +} + +int git__suffixcmp(const char *str, const char *suffix) +{ + return suffixcmp(str, suffix, false); +} + +int git__suffixcmp_icase(const char *str, const char *suffix) +{ + return suffixcmp(str, suffix, true); } char *git__strtok(char **end, const char *sep) @@ -623,12 +636,12 @@ int git__bsearch_r( */ int git__strcmp_cb(const void *a, const void *b) { - return strcmp((const char *)a, (const char *)b); + return git__strcmp((const char *)a, (const char *)b); } int git__strcasecmp_cb(const void *a, const void *b) { - return strcasecmp((const char *)a, (const char *)b); + return git__strcasecmp((const char *)a, (const char *)b); } int git__parse_bool(int *out, const char *value) diff --git a/src/util/util.h b/src/util/util.h index 7f178b16..d3cbbf4d 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -7,15 +7,15 @@ #ifndef INCLUDE_util_h__ #define INCLUDE_util_h__ -#ifndef GIT_WIN32 -# include -#endif - #include "str.h" #include "git2_util.h" #include "strnlen.h" #include "thread.h" +#ifndef GIT_WIN32 +# include +#endif + #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define bitsizeof(x) (CHAR_BIT * sizeof(x)) #define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) @@ -57,6 +57,7 @@ extern int git__prefixcmp_icase(const char *str, const char *prefix); extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); +extern int git__suffixcmp_icase(const char *str, const char *suffix); GIT_INLINE(int) git__signum(int val) { @@ -83,15 +84,6 @@ extern char *git__strsep(char **end, const char *sep); extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str); -#ifdef GIT_WIN32 -GIT_INLINE(int) git__tolower(int c) -{ - return (c >= 'A' && c <= 'Z') ? (c + 32) : c; -} -#else -# define git__tolower(a) tolower(a) -#endif - extern size_t git__linenlen(const char *buffer, size_t buffer_len); GIT_INLINE(const char *) git__next_line(const char *s) @@ -249,26 +241,6 @@ GIT_INLINE(size_t) git__size_t_powerof2(size_t v) return git__size_t_bitmask(v) + 1; } -GIT_INLINE(bool) git__isupper(int c) -{ - return (c >= 'A' && c <= 'Z'); -} - -GIT_INLINE(bool) git__isalpha(int c) -{ - return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); -} - -GIT_INLINE(bool) git__isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -GIT_INLINE(bool) git__isspace(int c) -{ - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); -} - GIT_INLINE(bool) git__isspace_nonlf(int c) { return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); @@ -279,11 +251,6 @@ GIT_INLINE(bool) git__iswildcard(int c) return (c == '*' || c == '?' || c == '['); } -GIT_INLINE(bool) git__isxdigit(int c) -{ - return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - /* * Parse a string value as a boolean, just like Core Git does. * diff --git a/src/util/vector.c b/src/util/vector.c index 4a4bc8c0..34ac3bb9 100644 --- a/src/util/vector.c +++ b/src/util/vector.c @@ -76,7 +76,7 @@ int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) return 0; } -void git_vector_free(git_vector *v) +void git_vector_dispose(git_vector *v) { if (!v) return; @@ -88,7 +88,7 @@ void git_vector_free(git_vector *v) v->_alloc_size = 0; } -void git_vector_free_deep(git_vector *v) +void git_vector_dispose_deep(git_vector *v) { size_t i; @@ -100,7 +100,7 @@ void git_vector_free_deep(git_vector *v) v->contents[i] = NULL; } - git_vector_free(v); + git_vector_dispose(v); } int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) diff --git a/src/util/vector.h b/src/util/vector.h index e50cdfef..02f82b30 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -28,8 +28,8 @@ typedef struct git_vector { GIT_WARN_UNUSED_RESULT int git_vector_init( git_vector *v, size_t initial_size, git_vector_cmp cmp); -void git_vector_free(git_vector *v); -void git_vector_free_deep(git_vector *v); /* free each entry and self */ +void git_vector_dispose(git_vector *v); +void git_vector_dispose_deep(git_vector *v); /* free each entry and self */ void git_vector_clear(git_vector *v); GIT_WARN_UNUSED_RESULT int git_vector_dup( git_vector *v, const git_vector *src, git_vector_cmp cmp); diff --git a/src/util/win32/path_w32.c b/src/util/win32/path_w32.c index 7a559e45..2714efbb 100644 --- a/src/util/win32/path_w32.c +++ b/src/util/win32/path_w32.c @@ -153,6 +153,7 @@ int git_win32_path_canonicalize(git_win32_path path) static int git_win32_path_join( git_win32_path dest, + size_t *dest_len, const wchar_t *one, size_t one_len, const wchar_t *two, @@ -176,6 +177,9 @@ static int git_win32_path_join( memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); dest[one_len + backslash + two_len] = L'\0'; + if (dest_len) + *dest_len = one_len + backslash + two_len; + return 0; } @@ -253,21 +257,92 @@ static void win32_path_iter_dispose(struct win32_path_iter *iter) iter->current_dir = NULL; } +struct executable_suffix { + const wchar_t *suffix; + size_t len; +}; + +static struct executable_suffix suffixes[] = { { NULL, 0 }, { L".exe", 4 }, { L".cmd", 4 } }; + +static bool has_executable_suffix(wchar_t *exe, size_t exe_len) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(suffixes); i++) { + struct executable_suffix *suffix = &suffixes[i]; + + if (!suffix->len) + continue; + + if (exe_len < suffix->len) + continue; + + if (memcmp(&exe[exe_len - suffix->len], suffix->suffix, suffix->len) == 0) + return true; + } + + return false; +} + +static int is_executable(git_win32_path path, size_t path_len, bool suffixed) +{ + size_t i; + + /* + * if the given name has an executable suffix, then try looking for it + * directly. in all cases, append executable extensions + * (".exe", ".cmd"...) + */ + for (i = suffixed ? 0 : 1; i < ARRAY_SIZE(suffixes); i++) { + struct executable_suffix *suffix = &suffixes[i]; + + if (suffix->len) { + if (path_len + suffix->len > MAX_PATH) + continue; + + wcscat(path, suffix->suffix); + } + + if (_waccess(path, 0) == 0) + return true; + + path[path_len] = L'\0'; + } + + return false; +} + int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) { struct win32_path_iter path_iter; const wchar_t *dir; - size_t dir_len, exe_len = wcslen(exe); - bool found = false; + size_t dir_len, exe_len, fullpath_len; + bool suffixed = false, found = false; + + if ((exe_len = wcslen(exe)) > MAX_PATH) + goto done; + + /* see if the given executable has an executable suffix; if so we will + * look for the explicit name directly, as well as with added suffixes. + */ + suffixed = has_executable_suffix(exe, exe_len); + + /* For fully-qualified paths we do not look in PATH */ + if (wcschr(exe, L'\\') != NULL || wcschr(exe, L'/') != NULL) { + if ((found = is_executable(exe, exe_len, suffixed))) + wcscpy(fullpath, exe); + + goto done; + } if (win32_path_iter_init(&path_iter) < 0) return -1; - while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { - if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) + while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER && !found) { + if (git_win32_path_join(fullpath, &fullpath_len, dir, dir_len, exe, exe_len) < 0) continue; - if (_waccess(fullpath, 0) == 0) { + if (is_executable(fullpath, fullpath_len, suffixed)) { found = true; break; } @@ -275,6 +350,7 @@ int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) win32_path_iter_dispose(&path_iter); +done: if (found) return 0; diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c index 3fec469a..7ace3b1e 100644 --- a/src/util/win32/posix_w32.c +++ b/src/util/win32/posix_w32.c @@ -770,6 +770,7 @@ int p_rmdir(const char *path) * handle to the directory." This sounds like what everybody else calls * EBUSY. Let's convert appropriate error codes. */ + case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: errno = EBUSY; break; @@ -787,13 +788,19 @@ int p_rmdir(const char *path) char *p_realpath(const char *orig_path, char *buffer) { git_win32_path orig_path_w, buffer_w; + DWORD long_len; if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) return NULL; - /* Note that if the path provided is a relative path, then the current directory + /* + * POSIX realpath performs two functions: first, it turns relative + * paths into absolute paths. For this, we need GetFullPathName. + * + * Note that if the path provided is a relative path, then the current directory * is used to resolve the path -- which is a concurrency issue because the current - * directory is a process-wide variable. */ + * directory is a process-wide variable. + */ if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) errno = ENAMETOOLONG; @@ -803,9 +810,26 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; } - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; + /* + * Then, the path is canonicalized. eg, on macOS, + * "/TMP" -> "/private/tmp". For this, we need GetLongPathName. + */ + if ((long_len = GetLongPathNameW(buffer_w, buffer_w, GIT_WIN_PATH_UTF16)) == 0) { + DWORD error = GetLastError(); + + if (error == ERROR_FILE_NOT_FOUND || + error == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (error == ERROR_ACCESS_DENIED) + errno = EPERM; + else + errno = EINVAL; + + return NULL; + } + + if (long_len > GIT_WIN_PATH_UTF16) { + errno = ENAMETOOLONG; return NULL; } @@ -821,7 +845,6 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; git_fs_path_mkposix(buffer); - return buffer; } diff --git a/src/util/win32/process.c b/src/util/win32/process.c new file mode 100644 index 00000000..ad4ae896 --- /dev/null +++ b/src/util/win32/process.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include + +#include "git2_util.h" +#include "process.h" +#include "strlist.h" +#include "fs_path.h" + +#ifndef DWORD_MAX +# define DWORD_MAX INT32_MAX +#endif + +#define ENV_MAX 32767 + +struct git_process { + char *app_name; + wchar_t *app_path; + wchar_t *cmdline; + wchar_t *env; + + wchar_t *cwd; + + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1; + + PROCESS_INFORMATION process_info; + + HANDLE child_in; + HANDLE child_out; + HANDLE child_err; + + git_process_result_status status; +}; + +/* + * Windows processes have a single command-line that is split by the + * invoked application into arguments (instead of an array of + * command-line arguments). This command-line is split by space or + * tab delimiters, unless that whitespace is within a double quote. + * Literal double-quotes themselves can be escaped by a backslash, + * but only when not within double quotes. Literal backslashes can + * be escaped by a backslash. + * + * Effectively, this means that instead of thinking about quoting + * individual strings, think about double quotes as an escaping + * mechanism for whitespace. + * + * In other words (using ` as a string boundary): + * [ `foo`, `bar` ] => `foo bar` + * [ `foo bar` ] => `foo" "bar` + * [ `foo bar`, `foo bar` ] => `foo" "bar foo" "bar` + * [ `foo "bar" foo` ] => `foo" "\"bar\"" "foo` + */ +int git_process__cmdline( + git_str *out, + const char **in, + size_t in_len) +{ + bool quoted = false; + const char *c; + size_t i; + + for (i = 0; i < in_len; i++) { + /* Arguments are delimited by an unquoted space */ + if (i) + git_str_putc(out, ' '); + + for (c = in[i]; *c; c++) { + /* Start or stop quoting spaces within an argument */ + if ((*c == ' ' || *c == '\t') && !quoted) { + git_str_putc(out, '"'); + quoted = true; + } else if (*c != ' ' && *c != '\t' && quoted) { + git_str_putc(out, '"'); + quoted = false; + } + + /* Escape double-quotes and backslashes */ + if (*c == '"' || *c == '\\') + git_str_putc(out, '\\'); + + git_str_putc(out, *c); + } + } + + return git_str_oom(out) ? -1 : 0; +} + +GIT_INLINE(bool) is_delete_env(const char *env) +{ + char *c = strchr(env, '='); + + if (c == NULL) + return false; + + return *(c+1) == '\0'; +} + +static int merge_env(wchar_t **out, const char **in, size_t in_len, bool exclude_env) +{ + git_str merged = GIT_STR_INIT; + wchar_t *in16 = NULL, *env = NULL, *e; + char *e8 = NULL; + size_t e_len; + int ret = 0; + size_t i; + + *out = NULL; + + in16 = git__malloc(ENV_MAX * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(in16); + + e8 = git__malloc(ENV_MAX); + GIT_ERROR_CHECK_ALLOC(e8); + + for (i = 0; in && i < in_len; i++) { + if (is_delete_env(in[i])) + continue; + + if ((ret = git_utf8_to_16(in16, ENV_MAX, in[i])) < 0) + goto done; + + git_str_put(&merged, (const char *)in16, ret * 2); + git_str_put(&merged, "\0\0", 2); + } + + if (!exclude_env) { + env = GetEnvironmentStringsW(); + + for (e = env; *e; e += (e_len + 1)) { + e_len = wcslen(e); + + if ((ret = git_utf8_from_16(e8, ENV_MAX, e)) < 0) + goto done; + + if (git_strlist_contains_key(in, in_len, e8, '=')) + continue; + + git_str_put(&merged, (const char *)e, e_len * 2); + git_str_put(&merged, "\0\0", 2); + } + } + + git_str_put(&merged, "\0\0", 2); + + *out = (wchar_t *)git_str_detach(&merged); + +done: + if (env) + FreeEnvironmentStringsW(env); + + git_str_dispose(&merged); + git__free(e8); + git__free(in16); + + return ret < 0 ? -1 : 0; +} + +static int process_new( + git_process **out, + const char *appname, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process *process; + int error = 0; + + *out = NULL; + + process = git__calloc(1, sizeof(git_process)); + GIT_ERROR_CHECK_ALLOC(process); + + if (appname && (process->app_name = git__strdup(appname)) == NULL) { + error = -1; + goto done; + } + + if (git_utf8_to_16_alloc(&process->cmdline, cmdline) < 0) { + error = -1; + goto done; + } + + if (opts && opts->cwd && + git_utf8_to_16_alloc(&process->cwd, opts->cwd) < 0) { + error = -1; + goto done; + } + + if (env && (error = merge_env(&process->env, env, env_len, opts && opts->exclude_env) < 0)) + goto done; + + if (opts) { + process->capture_in = opts->capture_in; + process->capture_out = opts->capture_out; + process->capture_err = opts->capture_err; + } + +done: + if (error) + git_process_free(process); + else + *out = process; + + return error; +} + +int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + GIT_ASSERT_ARG(out && cmdline); + + return process_new(out, NULL, cmdline, env, env_len, opts); +} + +int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_str cmd_path = GIT_STR_INIT, cmdline = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out && args && args_len > 0); + + if ((error = git_process__cmdline(&cmdline, args, args_len)) < 0) + goto done; + + error = process_new(out, args[0], cmdline.ptr, env, env_len, opts); + +done: + git_str_dispose(&cmd_path); + git_str_dispose(&cmdline); + return error; +} + +#define CLOSE_HANDLE(h) do { if ((h) != NULL) CloseHandle(h); } while(0) + +int git_process_start(git_process *process) +{ + STARTUPINFOW startup_info; + SECURITY_ATTRIBUTES security_attrs; + DWORD flags = CREATE_UNICODE_ENVIRONMENT; + HANDLE in[2] = { NULL, NULL }, + out[2] = { NULL, NULL }, + err[2] = { NULL, NULL }; + + if (process->app_name) { + git_str cmd_path = GIT_STR_INIT; + int error; + + if ((error = git_fs_path_find_executable(&cmd_path, process->app_name)) == 0) + error = git_utf8_to_16_alloc(&process->app_path, cmd_path.ptr); + + git_str_dispose(&cmd_path); + + if (error < 0) + goto on_error; + } + + memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES)); + security_attrs.bInheritHandle = TRUE; + + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + if (process->capture_in) { + if (!CreatePipe(&in[0], &in[1], &security_attrs, 0) || + !SetHandleInformation(in[1], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdInput = in[0]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + if (process->capture_out) { + if (!CreatePipe(&out[0], &out[1], &security_attrs, 0) || + !SetHandleInformation(out[0], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdOutput = out[1]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + if (process->capture_err) { + if (!CreatePipe(&err[0], &err[1], &security_attrs, 0) || + !SetHandleInformation(err[0], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdError = err[1]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION)); + + if (!CreateProcessW(process->app_path, process->cmdline, + NULL, NULL, TRUE, flags, process->env, + process->cwd, + &startup_info, + &process->process_info)) { + git_error_set(GIT_ERROR_OS, "could not create process"); + goto on_error; + } + + CLOSE_HANDLE(in[0]); process->child_in = in[1]; + CLOSE_HANDLE(out[1]); process->child_out = out[0]; + CLOSE_HANDLE(err[1]); process->child_err = err[0]; + + return 0; + +on_error: + CLOSE_HANDLE(in[0]); CLOSE_HANDLE(in[1]); + CLOSE_HANDLE(out[0]); CLOSE_HANDLE(out[1]); + CLOSE_HANDLE(err[0]); CLOSE_HANDLE(err[1]); + return -1; +} + +int git_process_id(p_pid_t *out, git_process *process) +{ + GIT_ASSERT(out && process); + + if (!process->process_info.dwProcessId) { + git_error_set(GIT_ERROR_INVALID, "process not running"); + return -1; + } + + *out = process->process_info.dwProcessId; + return 0; +} + +ssize_t git_process_read(git_process *process, void *buf, size_t count) +{ + DWORD ret; + + if (count > DWORD_MAX) + count = DWORD_MAX; + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (!ReadFile(process->child_out, buf, (DWORD)count, &ret, NULL)) { + if (GetLastError() == ERROR_BROKEN_PIPE) + return 0; + + git_error_set(GIT_ERROR_OS, "could not read"); + return -1; + } + + return ret; +} + +ssize_t git_process_write(git_process *process, const void *buf, size_t count) +{ + DWORD ret; + + if (count > DWORD_MAX) + count = DWORD_MAX; + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (!WriteFile(process->child_in, buf, (DWORD)count, &ret, NULL)) { + git_error_set(GIT_ERROR_OS, "could not write"); + return -1; + } + + return ret; +} + +int git_process_close_in(git_process *process) +{ + if (!process->capture_in) { + git_error_set(GIT_ERROR_INVALID, "input is not open"); + return -1; + } + + if (process->child_in) { + CloseHandle(process->child_in); + process->child_in = NULL; + } + + return 0; +} + +int git_process_close_out(git_process *process) +{ + if (!process->capture_out) { + git_error_set(GIT_ERROR_INVALID, "output is not open"); + return -1; + } + + if (process->child_out) { + CloseHandle(process->child_out); + process->child_out = NULL; + } + + return 0; +} + +int git_process_close_err(git_process *process) +{ + if (!process->capture_err) { + git_error_set(GIT_ERROR_INVALID, "error is not open"); + return -1; + } + + if (process->child_err) { + CloseHandle(process->child_err); + process->child_err = NULL; + } + + return 0; +} + +int git_process_close(git_process *process) +{ + if (process->child_in) { + CloseHandle(process->child_in); + process->child_in = NULL; + } + + if (process->child_out) { + CloseHandle(process->child_out); + process->child_out = NULL; + } + + if (process->child_err) { + CloseHandle(process->child_err); + process->child_err = NULL; + } + + CloseHandle(process->process_info.hProcess); + process->process_info.hProcess = NULL; + + CloseHandle(process->process_info.hThread); + process->process_info.hThread = NULL; + + return 0; +} + +int git_process_wait(git_process_result *result, git_process *process) +{ + DWORD exitcode; + + if (result) + memset(result, 0, sizeof(git_process_result)); + + if (!process->process_info.dwProcessId) { + git_error_set(GIT_ERROR_INVALID, "process is stopped"); + return -1; + } + + if (WaitForSingleObject(process->process_info.hProcess, INFINITE) == WAIT_FAILED) { + git_error_set(GIT_ERROR_OS, "could not wait for process"); + return -1; + } + + if (!GetExitCodeProcess(process->process_info.hProcess, &exitcode)) { + git_error_set(GIT_ERROR_OS, "could not get process exit code"); + return -1; + } + + result->status = GIT_PROCESS_STATUS_NORMAL; + result->exitcode = exitcode; + + memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION)); + return 0; +} + +int git_process_result_msg(git_str *out, git_process_result *result) +{ + if (result->status == GIT_PROCESS_STATUS_NONE) { + return git_str_puts(out, "process not started"); + } else if (result->status == GIT_PROCESS_STATUS_NORMAL) { + return git_str_printf(out, "process exited with code %d", + result->exitcode); + } else if (result->signal) { + return git_str_printf(out, "process exited on signal %d", + result->signal); + } + + return git_str_puts(out, "unknown error"); +} + +void git_process_free(git_process *process) +{ + if (!process) + return; + + if (process->process_info.hProcess) + git_process_close(process); + + git__free(process->env); + git__free(process->cwd); + git__free(process->cmdline); + git__free(process->app_path); + git__free(process->app_name); + git__free(process); +} diff --git a/tests/benchmarks/benchmark.sh b/tests/benchmarks/benchmark.sh index 4a89807b..944f26bc 100755 --- a/tests/benchmarks/benchmark.sh +++ b/tests/benchmarks/benchmark.sh @@ -6,9 +6,10 @@ set -eo pipefail # parse the command line # -usage() { echo "usage: $(basename "$0") [--cli ] [--baseline-cli ] [--suite ] [--json ] [--zip ] [--verbose] [--debug]"; } +usage() { echo "usage: $(basename "$0") [--cli ] [--name ] [--baseline-cli ] [--suite ] [--json ] [--zip ] [--verbose] [--debug]"; } TEST_CLI="git" +TEST_CLI_NAME= BASELINE_CLI= SUITE= JSON_RESULT= @@ -22,6 +23,9 @@ for a in "$@"; do if [ "${NEXT}" = "cli" ]; then TEST_CLI="${a}" NEXT= + elif [ "${NEXT}" = "name" ]; then + TEST_CLI_NAME="${a}" + NEXT= elif [ "${NEXT}" = "baseline-cli" ]; then BASELINE_CLI="${a}" NEXT= @@ -41,6 +45,10 @@ for a in "$@"; do NEXT="cli" elif [[ "${a}" == "-c"* ]]; then TEST_CLI="${a/-c/}" + elif [ "${a}" = "n" ] || [ "${a}" = "--name" ]; then + NEXT="name" + elif [[ "${a}" == "-n"* ]]; then + TEST_CLI_NAME="${a/-n/}" elif [ "${a}" = "b" ] || [ "${a}" = "--baseline-cli" ]; then NEXT="baseline-cli" elif [[ "${a}" == "-b"* ]]; then @@ -92,11 +100,11 @@ SYSTEM_KERNEL=$(uname -v) fullpath() { if [[ "$(uname -s)" == "MINGW"* && $(cygpath -u "${TEST_CLI}") == "/"* ]]; then - echo "${TEST_CLI}" + echo "$1" elif [[ "${TEST_CLI}" == "/"* ]]; then - echo "${TEST_CLI}" + echo "$1" else - which "${TEST_CLI}" + which "$1" fi } @@ -205,9 +213,19 @@ for TEST_PATH in "${BENCHMARK_DIR}"/*; do ERROR_FILE="${OUTPUT_DIR}/${TEST_FILE}.err" FAILED= - ${TEST_PATH} --cli "${TEST_CLI}" --baseline-cli "${BASELINE_CLI}" --json "${JSON_FILE}" ${SHOW_OUTPUT} >"${OUTPUT_FILE}" 2>"${ERROR_FILE}" || FAILED=1 + { + ${TEST_PATH} --cli "${TEST_CLI}" --baseline-cli "${BASELINE_CLI}" --json "${JSON_FILE}" ${SHOW_OUTPUT} >"${OUTPUT_FILE}" 2>"${ERROR_FILE}"; + FAILED=$? + } || true + + if [ "${FAILED}" = "2" ]; then + if [ "${VERBOSE}" != "1" ]; then + echo "skipped!" + fi - if [ "${FAILED}" = "1" ]; then + indent < "${ERROR_FILE}" + continue + elif [ "${FAILED}" != "0" ]; then if [ "${VERBOSE}" != "1" ]; then echo "failed!" fi diff --git a/tests/benchmarks/benchmark_helpers.sh b/tests/benchmarks/benchmark_helpers.sh index 14dbb43c..5143a01f 100644 --- a/tests/benchmarks/benchmark_helpers.sh +++ b/tests/benchmarks/benchmark_helpers.sh @@ -21,6 +21,9 @@ else OUTPUT_STYLE="auto" fi +HELP_GIT_REMOTE="https://github.com/git/git" +HELP_LINUX_REMOTE="https://github.com/torvalds/linux" + # # parse the arguments to the outer script that's including us; these are arguments that # the `benchmark.sh` passes (or that a user could specify when running an individual test) @@ -218,6 +221,26 @@ create_preparescript() { fi } + clone_repo() { + REPO="\${1}" + + if [ "\${REPO}" = "" ]; then + echo "usage: clone_repo " 1>&2 + exit 1 + fi + + REPO_UPPER=\$(echo "\${1}" | tr '[:lower:]' '[:upper:]') + REPO_URL=\$(eval echo "\\\${BENCHMARK_\${REPO_UPPER}_REPOSITORY}") + + if [ "\${REPO_URL}" = "" ]; then + echo "\$0: unknown repository '\${REPO}'" 1>&2 + exit 1 + fi + + rm -rf "\${SANDBOX_DIR:?}/\${REPO}" + git clone "\${REPO_URL}" "\${SANDBOX_DIR}/\${REPO}" + } + cd "\${SANDBOX_DIR}" EOF @@ -293,6 +316,9 @@ gitbench() { NEXT="prepare" elif [ "${a}" = "--chdir" ]; then NEXT="chdir" + elif [[ "${a}" == "--" ]]; then + shift + break elif [[ "${a}" == "--"* ]]; then echo "unknown argument: \"${a}\"" 1>&2 gitbench_usage 1>&2 @@ -361,3 +387,27 @@ gitbench() { hyperfine "${ARGUMENTS[@]}" rm -rf "${SANDBOX_DIR:?}" } + +# helper script to give useful error messages about configuration +needs_repo() { + REPO="${1}" + + if [ "${REPO}" = "" ]; then + echo "usage: needs_repo " 1>&2 + exit 1 + fi + + REPO_UPPER=$(echo "${1}" | tr '[:lower:]' '[:upper:]') + REPO_URL=$(eval echo "\${BENCHMARK_${REPO_UPPER}_REPOSITORY}") + REPO_REMOTE_URL=$(eval echo "\${HELP_${REPO_UPPER}_REMOTE}") + + if [ "${REPO_URL}" = "" ]; then + echo "$0: '${REPO}' repository not configured" 1>&2 + echo "" 1>&2 + echo "This benchmark needs an on-disk '${REPO}' repository. First, clone the" 1>&2 + echo "remote repository ('${REPO_REMOTE_URL}') locally then set," 1>&2 + echo "the 'BENCHMARK_${REPO_UPPER}_REPOSITORY' environment variable to the path that" 1>&2 + echo "contains the repository locally, then run this benchmark again." 1>&2 + exit 2 + fi +} diff --git a/tests/benchmarks/blame__git_cached b/tests/benchmarks/blame__git_cached new file mode 100755 index 00000000..1664f737 --- /dev/null +++ b/tests/benchmarks/blame__git_cached @@ -0,0 +1,11 @@ +#!/bin/bash -e + +. "$(dirname "$0")/benchmark_helpers.sh" + +needs_repo git + +gitbench --prepare "clone_repo git && cd git && git reset --hard v2.45.0" \ + --warmup 5 \ + --chdir "git" \ + -- \ + --no-pager blame "git.c" diff --git a/tests/benchmarks/blame__linux_cached b/tests/benchmarks/blame__linux_cached new file mode 100755 index 00000000..20c5db5b --- /dev/null +++ b/tests/benchmarks/blame__linux_cached @@ -0,0 +1,11 @@ +#!/bin/bash -e + +. "$(dirname "$0")/benchmark_helpers.sh" + +needs_repo linux + +gitbench --prepare "clone_repo linux && cd linux && git reset --hard v6.9" \ + --warmup 5 \ + --chdir "linux" \ + -- \ + --no-pager blame "Makefile" diff --git a/tests/benchmarks/blame__simple_cached b/tests/benchmarks/blame__simple_cached new file mode 100755 index 00000000..f13e2cb0 --- /dev/null +++ b/tests/benchmarks/blame__simple_cached @@ -0,0 +1,9 @@ +#!/bin/bash -e + +. "$(dirname "$0")/benchmark_helpers.sh" + +gitbench --prepare "sandbox_repo testrepo && cd testrepo && git reset --hard HEAD" \ + --warmup 5 \ + --chdir "testrepo" \ + -- \ + --no-pager blame "branch_file.txt" diff --git a/tests/clar/clar.c b/tests/clar/clar.c index c9c3fde3..f28d6af0 100644 --- a/tests/clar/clar.c +++ b/tests/clar/clar.c @@ -4,7 +4,12 @@ * This file is part of clar, distributed under the ISC license. * For full terms see the included COPYING file. */ -#include + +#define _BSD_SOURCE +#define _DARWIN_C_SOURCE +#define _DEFAULT_SOURCE + +#include #include #include #include @@ -13,21 +18,35 @@ #include #include #include +#include /* required for sandboxing */ #include #include +#if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_WCHAR__) + /* + * uClibc can optionally be built without wchar support, in which case + * the installed is a stub that only defines the `whar_t` + * type but none of the functions typically declared by it. + */ +#else +# define CLAR_HAVE_WCHAR +#endif + #ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN # include # include -# include # include # define _MAIN_CC __cdecl # ifndef stat # define stat(path, st) _stat(path, st) + typedef struct _stat STAT_T; +# else + typedef struct stat STAT_T; # endif # ifndef mkdir # define mkdir(path, mode) _mkdir(path) @@ -60,30 +79,11 @@ # else # define p_snprintf snprintf # endif - -# ifndef PRIuZ -# define PRIuZ "Iu" -# endif -# ifndef PRIxZ -# define PRIxZ "Ix" -# endif - -# if defined(_MSC_VER) || defined(__MINGW32__) - typedef struct stat STAT_T; -# else - typedef struct _stat STAT_T; -# endif #else # include /* waitpid(2) */ # include # define _MAIN_CC # define p_snprintf snprintf -# ifndef PRIuZ -# define PRIuZ "zu" -# endif -# ifndef PRIxZ -# define PRIxZ "zx" -# endif typedef struct stat STAT_T; #endif @@ -94,13 +94,15 @@ static void fs_rm(const char *_source); static void fs_copy(const char *_source, const char *dest); +#ifdef CLAR_FIXTURE_PATH static const char * fixture_path(const char *base, const char *fixture_name); +#endif struct clar_error { const char *file; const char *function; - size_t line_number; + uintmax_t line_number; const char *error_msg; char *description; @@ -162,6 +164,10 @@ static struct { struct clar_report *reports; struct clar_report *last_report; + const char *invoke_file; + const char *invoke_func; + size_t invoke_line; + void (*local_cleanup)(void *); void *local_cleanup_payload; @@ -193,11 +199,14 @@ static void clar_print_shutdown(int test_count, int suite_count, int error_count static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); static void clar_print_ontest(const char *suite_name, 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_onabortv(const char *msg, va_list argp); static void clar_print_onabort(const char *msg, ...); /* From clar_sandbox.c */ -static void clar_unsandbox(void); -static int clar_sandbox(void); +static void clar_tempdir_init(void); +static void clar_tempdir_shutdown(void); +static int clar_sandbox_create(const char *suite_name, const char *test_name); +static int clar_sandbox_cleanup(void); /* From summary.h */ static struct clar_summary *clar_summary_init(const char *filename); @@ -216,6 +225,15 @@ static int clar_summary_shutdown(struct clar_summary *fp); _clar.trace_payload); \ } while (0) +static void clar_abort(const char *msg, ...) +{ + va_list argp; + va_start(argp, msg); + clar_print_onabortv(msg, argp); + va_end(argp); + exit(-1); +} + void cl_trace_register(cl_trace_cb *cb, void *payload) { _clar.pfn_trace_cb = cb; @@ -269,9 +287,7 @@ static double clar_time_diff(clar_time *start, clar_time *end) static void clar_time_now(clar_time *out) { - struct timezone tz; - - gettimeofday(out, &tz); + gettimeofday(out, NULL); } static double clar_time_diff(clar_time *start, clar_time *end) @@ -294,6 +310,8 @@ clar_run_test( CL_TRACE(CL_TRACE__TEST__BEGIN); + clar_sandbox_create(suite->name, test->name); + _clar.last_report->start = time(NULL); clar_time_now(&start); @@ -318,9 +336,13 @@ clar_run_test( if (_clar.local_cleanup != NULL) _clar.local_cleanup(_clar.local_cleanup_payload); + clar__clear_invokepoint(); + if (cleanup->ptr != NULL) cleanup->ptr(); + clar_sandbox_cleanup(); + CL_TRACE(CL_TRACE__TEST__END); _clar.tests_ran++; @@ -384,7 +406,8 @@ clar_run_suite(const struct clar_suite *suite, const char *filter) _clar.active_test = test[i].name; - report = calloc(1, sizeof(struct clar_report)); + if ((report = calloc(1, sizeof(*report))) == NULL) + clar_abort("Failed to allocate report.\n"); report->suite = _clar.active_suite; report->test = _clar.active_test; report->test_number = _clar.tests_ran; @@ -422,7 +445,7 @@ clar_usage(const char *arg) printf(" -t Display results in tap format\n"); printf(" -l Print suite names\n"); printf(" -r[filename] Write summary file (to the optional filename)\n"); - exit(-1); + exit(1); } static void @@ -430,18 +453,11 @@ clar_parse_args(int argc, char **argv) { int i; - /* Verify options before execute */ for (i = 1; i < argc; ++i) { char *argument = argv[i]; - if (argument[0] != '-' || argument[1] == '\0' - || strchr("sixvqQtlr", argument[1]) == NULL) { + if (argument[0] != '-' || argument[1] == '\0') clar_usage(argv[0]); - } - } - - for (i = 1; i < argc; ++i) { - char *argument = argv[i]; switch (argument[1]) { case 's': @@ -454,8 +470,13 @@ clar_parse_args(int argc, char **argv) argument += offset; arglen = strlen(argument); - if (arglen == 0) - clar_usage(argv[0]); + if (arglen == 0) { + if (i + 1 == argc) + clar_usage(argv[0]); + + argument = argv[++i]; + arglen = strlen(argument); + } for (j = 0; j < _clar_suite_count; ++j) { suitelen = strlen(_clar_suites[j].name); @@ -472,14 +493,12 @@ clar_parse_args(int argc, char **argv) ++found; - if (!exact) - _clar.verbosity = MAX(_clar.verbosity, 1); - switch (action) { case 's': { - struct clar_explicit *explicit = - calloc(1, sizeof(struct clar_explicit)); - assert(explicit); + struct clar_explicit *explicit; + + if ((explicit = calloc(1, sizeof(*explicit))) == NULL) + clar_abort("Failed to allocate explicit test.\n"); explicit->suite_idx = j; explicit->filter = argument; @@ -503,27 +522,39 @@ clar_parse_args(int argc, char **argv) } } - if (!found) { - clar_print_onabort("No suite matching '%s' found.\n", argument); - exit(-1); - } + if (!found) + clar_abort("No suite matching '%s' found.\n", argument); + break; } case 'q': + if (argument[2] != '\0') + clar_usage(argv[0]); + _clar.report_errors_only = 1; break; case 'Q': + if (argument[2] != '\0') + clar_usage(argv[0]); + _clar.exit_on_error = 1; break; case 't': + if (argument[2] != '\0') + clar_usage(argv[0]); + _clar.output_format = CL_OUTPUT_TAP; break; case 'l': { size_t j; + + if (argument[2] != '\0') + clar_usage(argv[0]); + printf("Test suites (use -s to run just one):\n"); for (j = 0; j < _clar_suite_count; ++j) printf(" %3d: %s\n", (int)j, _clar_suites[j].name); @@ -532,17 +563,27 @@ clar_parse_args(int argc, char **argv) } case 'v': + if (argument[2] != '\0') + clar_usage(argv[0]); + _clar.verbosity++; break; case 'r': _clar.write_summary = 1; free(_clar.summary_filename); - _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL; + + if (*(argument + 2)) { + if ((_clar.summary_filename = strdup(argument + 2)) == NULL) + clar_abort("Failed to allocate summary filename.\n"); + } else { + _clar.summary_filename = NULL; + } + break; default: - assert(!"Unexpected commandline argument!"); + clar_usage(argv[0]); } } } @@ -564,22 +605,18 @@ clar_test_init(int argc, char **argv) if (!_clar.summary_filename && (summary_env = getenv("CLAR_SUMMARY")) != NULL) { _clar.write_summary = 1; - _clar.summary_filename = strdup(summary_env); + if ((_clar.summary_filename = strdup(summary_env)) == NULL) + clar_abort("Failed to allocate summary filename.\n"); } if (_clar.write_summary && !_clar.summary_filename) - _clar.summary_filename = strdup("summary.xml"); + if ((_clar.summary_filename = strdup("summary.xml")) == NULL) + clar_abort("Failed to allocate summary filename.\n"); - 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.write_summary) + _clar.summary = clar_summary_init(_clar.summary_filename); - if (clar_sandbox() < 0) { - clar_print_onabort("Failed to sandbox the test runner.\n"); - exit(-1); - } + clar_tempdir_init(); } int @@ -611,12 +648,11 @@ clar_test_shutdown(void) _clar.total_errors ); - clar_unsandbox(); + clar_tempdir_shutdown(); - if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { - clar_print_onabort("Failed to write the summary file\n"); - exit(-1); - } + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) + clar_abort("Failed to write the summary file '%s: %s.\n", + _clar.summary_filename, strerror(errno)); for (explicit = _clar.explicit; explicit; explicit = explicit_next) { explicit_next = explicit->next; @@ -624,6 +660,14 @@ clar_test_shutdown(void) } for (report = _clar.reports; report; report = report_next) { + struct clar_error *error, *error_next; + + for (error = report->errors; error; error = error_next) { + free(error->description); + error_next = error->next; + free(error); + } + report_next = report->next; free(report); } @@ -647,9 +691,9 @@ static void abort_test(void) { if (!_clar.trampoline_enabled) { clar_print_onabort( - "Fatal error: a cleanup method raised an exception."); + "Fatal error: a cleanup method raised an exception.\n"); clar_report_errors(_clar.last_report); - exit(-1); + exit(1); } CL_TRACE(CL_TRACE__TEST__LONGJMP); @@ -671,7 +715,10 @@ void clar__fail( const char *description, int should_abort) { - struct clar_error *error = calloc(1, sizeof(struct clar_error)); + struct clar_error *error; + + if ((error = calloc(1, sizeof(*error))) == NULL) + clar_abort("Failed to allocate error.\n"); if (_clar.last_report->errors == NULL) _clar.last_report->errors = error; @@ -681,13 +728,14 @@ void clar__fail( _clar.last_report->last_error = error; - error->file = file; - error->function = function; - error->line_number = line; + error->file = _clar.invoke_file ? _clar.invoke_file : file; + error->function = _clar.invoke_func ? _clar.invoke_func : function; + error->line_number = _clar.invoke_line ? _clar.invoke_line : line; error->error_msg = error_msg; - if (description != NULL) - error->description = strdup(description); + if (description != NULL && + (error->description = strdup(description)) == NULL) + clar_abort("Failed to allocate description.\n"); _clar.total_errors++; _clar.last_report->status = CL_TEST_FAILURE; @@ -761,6 +809,7 @@ void clar__assert_equal( } } } +#ifdef CLAR_HAVE_WCHAR else if (!strcmp("%ls", fmt)) { const wchar_t *wcs1 = va_arg(args, const wchar_t *); const wchar_t *wcs2 = va_arg(args, const wchar_t *); @@ -796,8 +845,9 @@ void clar__assert_equal( } } } - else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { - size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); +#endif /* CLAR_HAVE_WCHAR */ + else if (!strcmp("%"PRIuMAX, fmt) || !strcmp("%"PRIxMAX, fmt)) { + uintmax_t sz1 = va_arg(args, uintmax_t), sz2 = va_arg(args, uintmax_t); is_equal = (sz1 == sz2); if (!is_equal) { int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); @@ -809,7 +859,8 @@ void clar__assert_equal( void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); is_equal = (p1 == p2); if (!is_equal) - p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); + p_snprintf(buf, sizeof(buf), "0x%"PRIxPTR" != 0x%"PRIxPTR, + (uintptr_t)p1, (uintptr_t)p2); } else { int i1 = va_arg(args, int), i2 = va_arg(args, int); @@ -833,6 +884,23 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque) _clar.local_cleanup_payload = opaque; } +void clar__set_invokepoint( + const char *file, + const char *func, + size_t line) +{ + _clar.invoke_file = file; + _clar.invoke_func = func; + _clar.invoke_line = line; +} + +void clar__clear_invokepoint(void) +{ + _clar.invoke_file = NULL; + _clar.invoke_func = NULL; + _clar.invoke_line = 0; +} + #include "clar/sandbox.h" #include "clar/fixtures.h" #include "clar/fs.h" diff --git a/tests/clar/clar.h b/tests/clar/clar.h index 8c22382b..ca72292a 100644 --- a/tests/clar/clar.h +++ b/tests/clar/clar.h @@ -8,6 +8,25 @@ #define __CLAR_TEST_H__ #include +#include + +#if defined(_WIN32) && defined(CLAR_WIN32_LONGPATHS) +# define CLAR_MAX_PATH 4096 +#elif defined(_WIN32) +# define CLAR_MAX_PATH MAX_PATH +#else +# define CLAR_MAX_PATH PATH_MAX +#endif + +#ifndef CLAR_SELFTEST +# define CLAR_CURRENT_FILE __FILE__ +# define CLAR_CURRENT_LINE __LINE__ +# define CLAR_CURRENT_FUNC __func__ +#else +# define CLAR_CURRENT_FILE "file" +# define CLAR_CURRENT_LINE 42 +# define CLAR_CURRENT_FUNC "func" +#endif enum cl_test_status { CL_TEST_OK, @@ -30,6 +49,7 @@ void clar_test_shutdown(void); int clar_test(int argc, char *argv[]); const char *clar_sandbox_path(void); +const char *clar_tempdir_path(void); void cl_set_cleanup(void (*cleanup)(void *), void *opaque); void cl_fs_cleanup(void); @@ -83,19 +103,33 @@ void cl_fixture_cleanup(const char *fixture_name); const char *cl_fixture_basename(const char *fixture_name); #endif +/** + * Invoke a helper function, which itself will use `cl_assert` + * constructs. This will preserve the stack information of the + * current call point, so that function name and line number + * information is shown from the line of the test, instead of + * the helper function. + */ +#define cl_invoke(expr) \ + do { \ + clar__set_invokepoint(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE); \ + expr; \ + clar__clear_invokepoint(); \ + } while(0) + /** * Assertion macros with explicit error message */ -#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1) -#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1) -#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1) +#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Function call failed: " #expr, desc, 1) +#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expected function call to fail: " #expr, desc, 1) +#define cl_assert_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expression is not true: " #expr, desc, 1) /** * Check macros with explicit error message */ -#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0) -#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0) -#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0) +#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Function call failed: " #expr, desc, 0) +#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expected function call to fail: " #expr, desc, 0) +#define cl_check_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expression is not true: " #expr, desc, 0) /** * Assertion macros with no error message @@ -114,33 +148,33 @@ const char *cl_fixture_basename(const char *fixture_name); /** * Forced failure/warning */ -#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1) -#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0) +#define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Test failed.", desc, 1) +#define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Warning during test execution:", desc, 0) #define cl_skip() clar__skip() /** * Typed assertion macros */ -#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) -#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) +#define cl_assert_equal_s(s1,s2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) +#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) -#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) -#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) +#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) +#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) -#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) -#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) +#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) +#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) -#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) -#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) +#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) +#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) -#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) -#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) -#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) +#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) +#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) +#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) -#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) +#define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) -#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) +#define cl_assert_equal_p(p1,p2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) void clar__skip(void); @@ -170,4 +204,11 @@ void clar__assert_equal( const char *fmt, ...); +void clar__set_invokepoint( + const char *file, + const char *func, + size_t line); + +void clar__clear_invokepoint(void); + #endif diff --git a/tests/clar/clar/fixtures.h b/tests/clar/clar/fixtures.h index 77033d36..9f1023df 100644 --- a/tests/clar/clar/fixtures.h +++ b/tests/clar/clar/fixtures.h @@ -1,7 +1,8 @@ +#ifdef CLAR_FIXTURE_PATH static const char * fixture_path(const char *base, const char *fixture_name) { - static char _path[4096]; + static char _path[CLAR_MAX_PATH]; size_t root_len; root_len = strlen(base); @@ -20,7 +21,6 @@ fixture_path(const char *base, const char *fixture_name) return _path; } -#ifdef CLAR_FIXTURE_PATH const char *cl_fixture(const char *fixture_name) { return fixture_path(CLAR_FIXTURE_PATH, fixture_name); @@ -28,7 +28,7 @@ const char *cl_fixture(const char *fixture_name) void cl_fixture_sandbox(const char *fixture_name) { - fs_copy(cl_fixture(fixture_name), _clar_path); + fs_copy(cl_fixture(fixture_name), clar_sandbox_path()); } const char *cl_fixture_basename(const char *fixture_name) @@ -45,6 +45,6 @@ const char *cl_fixture_basename(const char *fixture_name) void cl_fixture_cleanup(const char *fixture_name) { - fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name))); + fs_rm(fixture_path(clar_sandbox_path(), cl_fixture_basename(fixture_name))); } #endif diff --git a/tests/clar/clar/fs.h b/tests/clar/clar/fs.h index 44ede457..42fa4719 100644 --- a/tests/clar/clar/fs.h +++ b/tests/clar/clar/fs.h @@ -8,12 +8,6 @@ #ifdef _WIN32 -#ifdef CLAR_WIN32_LONGPATHS -# define CLAR_MAX_PATH 4096 -#else -# define CLAR_MAX_PATH MAX_PATH -#endif - #define RM_RETRY_COUNT 5 #define RM_RETRY_DELAY 10 @@ -146,7 +140,7 @@ fs_rm_wait(WCHAR *_wpath) ERROR_PATH_NOT_FOUND == last_error) return 0; - Sleep(RM_RETRY_DELAY * retries * retries); + Sleep(RM_RETRY_DELAY * retries * retries); } while (retries++ <= RM_RETRY_COUNT); @@ -295,7 +289,11 @@ fs_copy(const char *_source, const char *_dest) void cl_fs_cleanup(void) { - fs_rm(fixture_path(_clar_path, "*")); +#ifdef CLAR_FIXTURE_PATH + fs_rm(fixture_path(clar_tempdir_path(), "*")); +#else + ((void)fs_copy); /* unused */ +#endif } #else @@ -514,7 +512,7 @@ fs_rm(const char *path) void cl_fs_cleanup(void) { - clar_unsandbox(); - clar_sandbox(); + clar_tempdir_shutdown(); + clar_tempdir_init(); } #endif diff --git a/tests/clar/clar/print.h b/tests/clar/clar/print.h index c17e2f69..f577c01c 100644 --- a/tests/clar/clar/print.h +++ b/tests/clar/clar/print.h @@ -21,7 +21,7 @@ static void clar_print_clap_error(int num, const struct clar_report *report, con { printf(" %d) Failure:\n", num); - printf("%s::%s [%s:%"PRIuZ"]\n", + printf("%s::%s [%s:%"PRIuMAX"]\n", report->suite, report->test, error->file, @@ -47,8 +47,8 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name switch (status) { case CL_TEST_OK: printf("ok\n"); break; case CL_TEST_FAILURE: printf("fail\n"); break; - case CL_TEST_SKIP: printf("skipped"); break; - case CL_TEST_NOTRUN: printf("notrun"); break; + case CL_TEST_SKIP: printf("skipped\n"); break; + case CL_TEST_NOTRUN: printf("notrun\n"); break; } } else { switch (status) { @@ -136,7 +136,7 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name, printf(" at:\n"); printf(" file: '"); print_escaped(error->file); printf("'\n"); - printf(" line: %" PRIuZ "\n", error->line_number); + printf(" line: %" PRIuMAX "\n", error->line_number); printf(" function: '%s'\n", error->function); printf(" ---\n"); @@ -202,10 +202,15 @@ static void clar_print_onsuite(const char *suite_name, int suite_index) PRINT(onsuite, suite_name, suite_index); } +static void clar_print_onabortv(const char *msg, va_list argp) +{ + PRINT(onabort, msg, argp); +} + static void clar_print_onabort(const char *msg, ...) { va_list argp; va_start(argp, msg); - PRINT(onabort, msg, argp); + clar_print_onabortv(msg, argp); va_end(argp); } diff --git a/tests/clar/clar/sandbox.h b/tests/clar/clar/sandbox.h index 0ba14796..ff431599 100644 --- a/tests/clar/clar/sandbox.h +++ b/tests/clar/clar/sandbox.h @@ -2,7 +2,17 @@ #include #endif -static char _clar_path[4096 + 1]; +/* + * The tempdir is the temporary directory for the entirety of the clar + * process execution. The sandbox is an individual temporary directory + * for the execution of an individual test. Sandboxes are deleted + * entirely after test execution to avoid pollution across tests. + */ + +static char _clar_tempdir[CLAR_MAX_PATH]; +static size_t _clar_tempdir_len; + +static char _clar_sandbox[CLAR_MAX_PATH]; static int is_valid_tmp_path(const char *path) @@ -15,7 +25,10 @@ is_valid_tmp_path(const char *path) if (!S_ISDIR(st.st_mode)) return 0; - return (access(path, W_OK) == 0); + if (access(path, W_OK) != 0) + return 0; + + return (strlen(path) < CLAR_MAX_PATH); } static int @@ -25,20 +38,17 @@ find_tmp_path(char *buffer, size_t length) static const size_t var_count = 5; static const char *env_vars[] = { "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" - }; + }; - size_t i; + size_t i; for (i = 0; i < var_count; ++i) { const char *env = getenv(env_vars[i]); + if (!env) continue; if (is_valid_tmp_path(env)) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath(env, buffer) != NULL) - return 0; -#endif strncpy(buffer, env, length - 1); buffer[length - 1] = '\0'; return 0; @@ -47,21 +57,18 @@ find_tmp_path(char *buffer, size_t length) /* If the environment doesn't say anything, try to use /tmp */ if (is_valid_tmp_path("/tmp")) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) - return 0; -#endif strncpy(buffer, "/tmp", length - 1); buffer[length - 1] = '\0'; return 0; } #else - DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); - if (env_len > 0 && env_len < (DWORD)length) + DWORD len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); + if (len > 0 && len < (DWORD)length) return 0; - if (GetTempPath((DWORD)length, buffer)) + len = GetTempPath((DWORD)length, buffer); + if (len > 0 && len < (DWORD)length) return 0; #endif @@ -75,17 +82,53 @@ find_tmp_path(char *buffer, size_t length) return -1; } -static void clar_unsandbox(void) +static int canonicalize_tmp_path(char *buffer) +{ +#ifdef _WIN32 + char tmp[CLAR_MAX_PATH], *p; + DWORD ret; + + ret = GetFullPathName(buffer, CLAR_MAX_PATH, tmp, NULL); + + if (ret == 0 || ret > CLAR_MAX_PATH) + return -1; + + ret = GetLongPathName(tmp, buffer, CLAR_MAX_PATH); + + if (ret == 0 || ret > CLAR_MAX_PATH) + return -1; + + /* normalize path to POSIX forward slashes */ + for (p = buffer; *p; p++) + if (*p == '\\') + *p = '/'; + + return 0; +#elif defined(CLAR_HAS_REALPATH) + char tmp[CLAR_MAX_PATH]; + + if (realpath(buffer, tmp) == NULL) + return -1; + + strcpy(buffer, tmp); + return 0; +#else + (void)buffer; + return 0; +#endif +} + +static void clar_tempdir_shutdown(void) { - if (_clar_path[0] == '\0') + if (_clar_tempdir[0] == '\0') return; cl_must_pass(chdir("..")); - fs_rm(_clar_path); + fs_rm(_clar_tempdir); } -static int build_sandbox_path(void) +static int build_tempdir_path(void) { #ifdef CLAR_TMPDIR const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; @@ -95,60 +138,153 @@ static int build_sandbox_path(void) size_t len; - if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) + if (find_tmp_path(_clar_tempdir, sizeof(_clar_tempdir)) < 0 || + canonicalize_tmp_path(_clar_tempdir) < 0) return -1; - len = strlen(_clar_path); + len = strlen(_clar_tempdir); -#ifdef _WIN32 - { /* normalize path to POSIX forward slashes */ - size_t i; - for (i = 0; i < len; ++i) { - if (_clar_path[i] == '\\') - _clar_path[i] = '/'; - } - } -#endif + if (len + strlen(path_tail) + 2 > CLAR_MAX_PATH) + return -1; - if (_clar_path[len - 1] != '/') { - _clar_path[len++] = '/'; - } + if (_clar_tempdir[len - 1] != '/') + _clar_tempdir[len++] = '/'; - strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); + strncpy(_clar_tempdir + len, path_tail, sizeof(_clar_tempdir) - len); #if defined(__MINGW32__) - if (_mktemp(_clar_path) == NULL) + if (_mktemp(_clar_tempdir) == NULL) return -1; - if (mkdir(_clar_path, 0700) != 0) + if (mkdir(_clar_tempdir, 0700) != 0) return -1; #elif defined(_WIN32) - if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) + if (_mktemp_s(_clar_tempdir, sizeof(_clar_tempdir)) != 0) return -1; - if (mkdir(_clar_path, 0700) != 0) + if (mkdir(_clar_tempdir, 0700) != 0) + return -1; +#elif defined(__sun) || defined(__TANDEM) + if (mktemp(_clar_tempdir) == NULL) + return -1; + + if (mkdir(_clar_tempdir, 0700) != 0) return -1; #else - if (mkdtemp(_clar_path) == NULL) + if (mkdtemp(_clar_tempdir) == NULL) return -1; #endif + _clar_tempdir_len = strlen(_clar_tempdir); return 0; } -static int clar_sandbox(void) +static void clar_tempdir_init(void) +{ + if (_clar_tempdir[0] == '\0' && build_tempdir_path() < 0) + clar_abort("Failed to build tempdir path.\n"); + + if (chdir(_clar_tempdir) != 0) + clar_abort("Failed to change into tempdir '%s': %s.\n", + _clar_tempdir, strerror(errno)); + +#if !defined(CLAR_SANDBOX_TEST_NAMES) && defined(_WIN32) + srand(clock() ^ (unsigned int)time(NULL) ^ GetCurrentProcessId() ^ GetCurrentThreadId()); +#elif !defined(CLAR_SANDBOX_TEST_NAMES) + srand(clock() ^ time(NULL) ^ (getpid() << 16)); +#endif +} + +static void append(char *dst, const char *src) { - if (_clar_path[0] == '\0' && build_sandbox_path() < 0) + char *d; + const char *s; + + for (d = dst; *d; d++) + ; + + for (s = src; *s; d++, s++) + if (*s == ':') + *d = '_'; + else + *d = *s; + + *d = '\0'; +} + +static int clar_sandbox_create(const char *suite_name, const char *test_name) +{ +#ifndef CLAR_SANDBOX_TEST_NAMES + char alpha[] = "0123456789abcdef"; + int num = rand(); +#endif + + cl_assert(_clar_sandbox[0] == '\0'); + + /* + * We may want to use test names as sandbox directory names for + * readability, _however_ on platforms with restrictions for short + * file / folder names (eg, Windows), this may be too long. + */ +#ifdef CLAR_SANDBOX_TEST_NAMES + cl_assert(strlen(_clar_tempdir) + strlen(suite_name) + strlen(test_name) + 3 < CLAR_MAX_PATH); + + strcpy(_clar_sandbox, _clar_tempdir); + _clar_sandbox[_clar_tempdir_len] = '/'; + _clar_sandbox[_clar_tempdir_len + 1] = '\0'; + + append(_clar_sandbox, suite_name); + append(_clar_sandbox, "__"); + append(_clar_sandbox, test_name); +#else + ((void)suite_name); + ((void)test_name); + ((void)append); + + cl_assert(strlen(_clar_tempdir) + 9 < CLAR_MAX_PATH); + + strcpy(_clar_sandbox, _clar_tempdir); + _clar_sandbox[_clar_tempdir_len] = '/'; + + _clar_sandbox[_clar_tempdir_len + 1] = alpha[(num & 0xf0000000) >> 28]; + _clar_sandbox[_clar_tempdir_len + 2] = alpha[(num & 0x0f000000) >> 24]; + _clar_sandbox[_clar_tempdir_len + 3] = alpha[(num & 0x00f00000) >> 20]; + _clar_sandbox[_clar_tempdir_len + 4] = alpha[(num & 0x000f0000) >> 16]; + _clar_sandbox[_clar_tempdir_len + 5] = alpha[(num & 0x0000f000) >> 12]; + _clar_sandbox[_clar_tempdir_len + 6] = alpha[(num & 0x00000f00) >> 8]; + _clar_sandbox[_clar_tempdir_len + 7] = alpha[(num & 0x000000f0) >> 4]; + _clar_sandbox[_clar_tempdir_len + 8] = alpha[(num & 0x0000000f) >> 0]; + _clar_sandbox[_clar_tempdir_len + 9] = '\0'; +#endif + + if (mkdir(_clar_sandbox, 0700) != 0) return -1; - if (chdir(_clar_path) != 0) + if (chdir(_clar_sandbox) != 0) return -1; return 0; } -const char *clar_sandbox_path(void) +static int clar_sandbox_cleanup(void) { - return _clar_path; + cl_assert(_clar_sandbox[0] != '\0'); + + if (chdir(_clar_tempdir) != 0) + return -1; + + fs_rm(_clar_sandbox); + _clar_sandbox[0] = '\0'; + + return 0; +} + +const char *clar_tempdir_path(void) +{ + return _clar_tempdir; } +const char *clar_sandbox_path(void) +{ + return _clar_sandbox; +} diff --git a/tests/clar/clar/summary.h b/tests/clar/clar/summary.h index 4dd352e2..0d0b646f 100644 --- a/tests/clar/clar/summary.h +++ b/tests/clar/clar/summary.h @@ -66,16 +66,12 @@ struct clar_summary *clar_summary_init(const char *filename) struct clar_summary *summary; FILE *fp; - if ((fp = fopen(filename, "w")) == NULL) { - perror("fopen"); - return NULL; - } + if ((fp = fopen(filename, "w")) == NULL) + clar_abort("Failed to open the summary file '%s': %s.\n", + filename, strerror(errno)); - if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { - perror("malloc"); - fclose(fp); - return NULL; - } + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) + clar_abort("Failed to allocate summary.\n"); summary->filename = filename; summary->fp = fp; diff --git a/tests/clar/clar_libgit2.c b/tests/clar/clar_libgit2.c index a1b92fc3..c4a2dfc7 100644 --- a/tests/clar/clar_libgit2.c +++ b/tests/clar/clar_libgit2.c @@ -649,7 +649,7 @@ void cl_sandbox_set_homedir(const char *home) if (home) { git_libgit2_opts(GIT_OPT_SET_HOMEDIR, home); } else { - git_str_joinpath(&path, clar_sandbox_path(), "__home"); + git_str_joinpath(&path, clar_tempdir_path(), "__home"); if (!git_fs_path_exists(path.ptr)) cl_must_pass(p_mkdir(path.ptr, 0777)); @@ -664,7 +664,7 @@ void cl_sandbox_set_search_path_defaults(void) { git_str path = GIT_STR_INIT; - git_str_joinpath(&path, clar_sandbox_path(), "__config"); + git_str_joinpath(&path, clar_tempdir_path(), "__config"); if (!git_fs_path_exists(path.ptr)) cl_must_pass(p_mkdir(path.ptr, 0777)); diff --git a/tests/clar/clar_libgit2.h b/tests/clar/clar_libgit2.h index c33b5d28..d8105c84 100644 --- a/tests/clar/clar_libgit2.h +++ b/tests/clar/clar_libgit2.h @@ -166,10 +166,27 @@ GIT_INLINE(void) clar__assert_equal_oid( } } +GIT_INLINE(void) clar__assert_equal_oidstr( + const char *file, const char *func, int line, const char *desc, + const char *one_str, const git_oid *two) +{ + git_oid one; + + if (git_oid__fromstr(&one, one_str, git_oid_type(two)) < 0) { + clar__fail(file, func, line, desc, "could not parse oid string", 1); + } else { + clar__assert_equal_oid(file, func, line, desc, &one, two); + } +} + #define cl_assert_equal_oid(one, two) \ clar__assert_equal_oid(__FILE__, __func__, __LINE__, \ "OID mismatch: " #one " != " #two, (one), (two)) +#define cl_assert_equal_oidstr(one_str, two) \ + clar__assert_equal_oidstr(__FILE__, __func__, __LINE__, \ + "OID mismatch: " #one_str " != " #two, (one_str), (two)) + /* * Some utility macros for building long strings */ diff --git a/tests/clar/clar_libgit2_alloc.c b/tests/clar/clar_libgit2_alloc.c index e9303792..54eacd54 100644 --- a/tests/clar/clar_libgit2_alloc.c +++ b/tests/clar/clar_libgit2_alloc.c @@ -104,7 +104,5 @@ void cl_alloc_limit(size_t bytes) void cl_alloc_reset(void) { - git_allocator stdalloc; - git_stdalloc_init_allocator(&stdalloc); - git_allocator_setup(&stdalloc); + git_allocator_setup(NULL); } diff --git a/tests/headertest/CMakeLists.txt b/tests/headertest/CMakeLists.txt index c70ce1ae..b352458d 100644 --- a/tests/headertest/CMakeLists.txt +++ b/tests/headertest/CMakeLists.txt @@ -3,8 +3,7 @@ # even when they have aggressive C90 warnings enabled. add_executable(headertest headertest.c) -set_target_properties(headertest PROPERTIES C_STANDARD 90) -set_target_properties(headertest PROPERTIES C_EXTENSIONS OFF) + target_include_directories(headertest PRIVATE ${LIBGIT2_INCLUDES}) if (MSVC) diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt index af70f55a..7a4bb476 100644 --- a/tests/libgit2/CMakeLists.txt +++ b/tests/libgit2/CMakeLists.txt @@ -1,5 +1,9 @@ # tests: the unit and integration tests for libgit2 +if(NOT "${CMAKE_VERSION}" VERSION_LESS 3.27) + cmake_policy(SET CMP0148 OLD) +endif() + set(Python_ADDITIONAL_VERSIONS 3 2.7) find_package(PythonInterp) @@ -14,6 +18,7 @@ set(TEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}") add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") add_definitions(-DCLAR_WIN32_LONGPATHS) +add_definitions(-DCLAR_HAS_REALPATH) add_definitions(-D_FILE_OFFSET_BITS=64) # Ensure that we do not use deprecated functions internally @@ -40,9 +45,7 @@ set_source_files_properties( add_executable(libgit2_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) -set_target_properties(libgit2_tests PROPERTIES C_STANDARD 90) set_target_properties(libgit2_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - target_include_directories(libgit2_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) target_include_directories(libgit2_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) target_link_libraries(libgit2_tests ${LIBGIT2_SYSTEM_LIBS}) diff --git a/tests/libgit2/attr/repo.c b/tests/libgit2/attr/repo.c index abd23815..793c1a7d 100644 --- a/tests/libgit2/attr/repo.c +++ b/tests/libgit2/attr/repo.c @@ -309,6 +309,31 @@ void test_attr_repo__bare_repo_with_index(void) cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); } +void test_attr_repo__inmemory_repo_without_index(void) +{ + const char *names[1] = { "fake" }; + const char *values[1]; + git_repository *inmemory; + git_index *index = NULL; + + /* setup bare in-memory repo without index */ +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&inmemory, NULL)); +#else + cl_git_pass(git_repository_new(&inmemory)); +#endif + cl_assert(git_repository_is_bare(inmemory)); + + /* verify repo isn't given an index upfront in future */ + git_repository_index(&index, inmemory); + cl_assert(!index); + + /* check attributes can be queried without error due to missing index */ + cl_git_pass(git_attr_get_many(values, inmemory, 0, "fake.txt", 1, names)); + + git_repository_free(inmemory); +} + void test_attr_repo__sysdir(void) { git_str sysdir = GIT_STR_INIT; diff --git a/tests/libgit2/blame/blame_helpers.c b/tests/libgit2/blame/blame_helpers.c index 8aeaa588..c8c3d483 100644 --- a/tests/libgit2/blame/blame_helpers.c +++ b/tests/libgit2/blame/blame_helpers.c @@ -18,7 +18,7 @@ void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx, size_t start_line, size_t len, char boundary, const char *commit_id, const char *orig_path) { char expected[GIT_OID_SHA1_HEXSIZE+1] = {0}, actual[GIT_OID_SHA1_HEXSIZE+1] = {0}; - const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx); + const git_blame_hunk *hunk = git_blame_hunk_byindex(blame, idx); cl_assert(hunk); if (!strncmp(commit_id, "0000", 4)) { diff --git a/tests/libgit2/blame/buffer.c b/tests/libgit2/blame/buffer.c index 06d5042d..cf70dbb3 100644 --- a/tests/libgit2/blame/buffer.c +++ b/tests/libgit2/blame/buffer.c @@ -17,32 +17,223 @@ void test_blame_buffer__cleanup(void) git_repository_free(g_repo); } + +void test_blame_buffer__4_edits(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +x\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 2, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 3, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 4, 1, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 5, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 6, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 7, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 8, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 7, 9, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 8, 10, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 9, 11, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__two_added_lines_and_one_modified(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +x\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +x\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 4, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 5, 1, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 6, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 7, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 8, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 9, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 7, 12, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__two_added_lines(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +abc\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +def\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 4, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 5, 1, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 6, 1, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 7, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 12, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__added_blocks(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 4, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 13, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 16, 5, 0, "aa06ecca", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 21, 3, 0, "000000", "b.txt"); + + +} + +void test_blame_buffer__overlapping_blocks(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +abcdefg\n\ +hijlmno\n\ +pqrstuv\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 4, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 7, 4, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 11, 5, 0, "aa06ecca", "b.txt"); + +} + +void test_blame_buffer__2_add_splits_hunk(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +abc\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abc\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 2, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 3, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 4, 2, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 6, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 7, 2, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 5, 9, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 10, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 7, 13, 5, 0, "aa06ecca", "b.txt"); +} + void test_blame_buffer__index(void) { const git_blame_hunk *hunk; const char *buffer = "Hello\nWorld!"; - /* - * We need to open a different file from the ones used in other tests. Close - * the one opened in test_blame_buffer__initialize() to avoid a leak. - */ git_blame_free(g_fileblame); g_fileblame = NULL; cl_git_pass(git_blame_file(&g_fileblame, g_repo, "file.txt", NULL)); cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_bufferblame)); + cl_assert_equal_i(2, git_blame_hunkcount(g_bufferblame)); check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 1, 0, "836bc00b", "file.txt"); - hunk = git_blame_get_hunk_byline(g_bufferblame, 1); + hunk = git_blame_hunk_byline(g_bufferblame, 1); cl_assert(hunk); cl_assert_equal_s("lhchavez", hunk->final_signature->name); check_blame_hunk_index(g_repo, g_bufferblame, 1, 2, 1, 0, "00000000", "file.txt"); - hunk = git_blame_get_hunk_byline(g_bufferblame, 2); + hunk = git_blame_hunk_byline(g_bufferblame, 2); cl_assert(hunk); cl_assert(hunk->final_signature == NULL); } + + void test_blame_buffer__added_line(void) { const git_blame_hunk *hunk; @@ -65,14 +256,51 @@ CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); + cl_assert_equal_i(5, git_blame_hunkcount(g_bufferblame)); check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "000000", "b.txt"); - hunk = git_blame_get_hunk_byline(g_bufferblame, 16); + hunk = git_blame_hunk_byline(g_bufferblame, 16); cl_assert(hunk); cl_assert_equal_s("Ben Straub", hunk->final_signature->name); } +void test_blame_buffer__added_lines(void) +{ + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +\n\ +\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +\n\ +\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +\n\ +\n\ +\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + cl_assert_equal_i(7, git_blame_hunkcount(g_bufferblame)); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 14, 3, 0, "000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 6, 22, 3, 0, "000000", "b.txt"); + +} + void test_blame_buffer__deleted_line(void) { const char *buffer = "\ @@ -183,7 +411,7 @@ abc\n\ def\n"; cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); + cl_assert_equal_i(5, git_blame_hunkcount(g_bufferblame)); check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt"); check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 5, 0, "63d671eb", "b.txt"); diff --git a/tests/libgit2/blame/getters.c b/tests/libgit2/blame/getters.c index c160cd38..fc7e4444 100644 --- a/tests/libgit2/blame/getters.c +++ b/tests/libgit2/blame/getters.c @@ -10,11 +10,11 @@ void test_blame_getters__initialize(void) git_blame_options opts = GIT_BLAME_OPTIONS_INIT; git_blame_hunk hunks[] = { - { 3, GIT_OID_SHA1_ZERO, 1, NULL, GIT_OID_SHA1_ZERO, "a", 0}, - { 3, GIT_OID_SHA1_ZERO, 4, NULL, GIT_OID_SHA1_ZERO, "b", 0}, - { 3, GIT_OID_SHA1_ZERO, 7, NULL, GIT_OID_SHA1_ZERO, "c", 0}, - { 3, GIT_OID_SHA1_ZERO, 10, NULL, GIT_OID_SHA1_ZERO, "d", 0}, - { 3, GIT_OID_SHA1_ZERO, 13, NULL, GIT_OID_SHA1_ZERO, "e", 0}, + { 3, GIT_OID_SHA1_ZERO, 1, NULL, NULL, GIT_OID_SHA1_ZERO, "a", 0}, + { 3, GIT_OID_SHA1_ZERO, 4, NULL, NULL, GIT_OID_SHA1_ZERO, "b", 0}, + { 3, GIT_OID_SHA1_ZERO, 7, NULL, NULL, GIT_OID_SHA1_ZERO, "c", 0}, + { 3, GIT_OID_SHA1_ZERO, 10, NULL, NULL, GIT_OID_SHA1_ZERO, "d", 0}, + { 3, GIT_OID_SHA1_ZERO, 13, NULL, NULL, GIT_OID_SHA1_ZERO, "e", 0}, }; g_blame = git_blame__alloc(NULL, opts, ""); @@ -37,20 +37,20 @@ void test_blame_getters__cleanup(void) void test_blame_getters__byindex(void) { - const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2); + const git_blame_hunk *h = git_blame_hunk_byindex(g_blame, 2); cl_assert(h); cl_assert_equal_s(h->orig_path, "c"); - h = git_blame_get_hunk_byindex(g_blame, 95); + h = git_blame_hunk_byindex(g_blame, 95); cl_assert_equal_p(h, NULL); } void test_blame_getters__byline(void) { - const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5); + const git_blame_hunk *h = git_blame_hunk_byline(g_blame, 5); cl_assert(h); cl_assert_equal_s(h->orig_path, "b"); - h = git_blame_get_hunk_byline(g_blame, 95); + h = git_blame_hunk_byline(g_blame, 95); cl_assert_equal_p(h, NULL); } diff --git a/tests/libgit2/blame/harder.c b/tests/libgit2/blame/harder.c index e7774172..412949a3 100644 --- a/tests/libgit2/blame/harder.c +++ b/tests/libgit2/blame/harder.c @@ -10,7 +10,7 @@ * |\ * | * (B) aa06ecc * * | (C) 63d671e - * |/ + * |/ * * (D) da23739 * * (E) b99f7ac * @@ -71,7 +71,7 @@ void test_blame_harder__ccc(void) git_blame_options opts = GIT_BLAME_OPTIONS_INIT; GIT_UNUSED(opts); - + /* Attribute the third hunk in b.txt to (E). This hunk was deleted from * a.txt in (D), but reintroduced in (B). */ diff --git a/tests/libgit2/blame/simple.c b/tests/libgit2/blame/simple.c index 6b13cccd..ee6d5f86 100644 --- a/tests/libgit2/blame/simple.c +++ b/tests/libgit2/blame/simple.c @@ -27,7 +27,7 @@ void test_blame_simple__trivial_testrepo(void) cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo/.gitted"))); cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", NULL)); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(2, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "c47800c7", "branch_file.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf3", "branch_file.txt"); } @@ -57,7 +57,7 @@ void test_blame_simple__trivial_blamerepo(void) cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", NULL)); - cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(4, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); @@ -233,7 +233,7 @@ void test_blame_simple__can_restrict_lines_min(void) opts.min_line = 8; cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(2, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 8, 3, 0, "63d671eb", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 11, 5, 0, "aa06ecca", "b.txt"); } @@ -252,7 +252,7 @@ void test_blame_simple__can_ignore_whitespace_change(void) opts.flags |= GIT_BLAME_IGNORE_WHITESPACE; cl_git_pass(git_blame_file(&g_blame, g_repo, "c.txt", &opts)); - cl_assert_equal_i(1, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(1, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "702c7aa5", "c.txt"); } @@ -275,7 +275,7 @@ void test_blame_simple__can_restrict_lines_max(void) opts.max_line = 6; cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(3, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 2, 6, 1, 0, "63d671eb", "b.txt"); @@ -301,7 +301,7 @@ void test_blame_simple__can_restrict_lines_both(void) opts.min_line = 2; opts.max_line = 7; cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(3, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 2, 3, 0, "da237394", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt"); @@ -314,7 +314,7 @@ void test_blame_simple__can_blame_huge_file(void) cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); cl_git_pass(git_blame_file(&g_blame, g_repo, "huge.txt", &opts)); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(2, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 65536, 0, "4eecfea", "huge.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 65537, 1, 0, "6653ff4", "huge.txt"); } @@ -341,7 +341,7 @@ void test_blame_simple__can_restrict_to_newish_commits(void) cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", &opts)); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(2, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 1, "be3563a", "branch_file.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf", "branch_file.txt"); } @@ -354,7 +354,7 @@ void test_blame_simple__can_restrict_to_first_parent_commits(void) cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); + cl_assert_equal_i(4, git_blame_hunkcount(g_blame)); check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); diff --git a/tests/libgit2/checkout/binaryunicode.c b/tests/libgit2/checkout/binaryunicode.c index b8c6c079..e4cab66a 100644 --- a/tests/libgit2/checkout/binaryunicode.c +++ b/tests/libgit2/checkout/binaryunicode.c @@ -28,8 +28,6 @@ static void execute_test(void) cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); cl_git_pass(git_commit_tree(&tree, commit)); - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_git_pass(git_checkout_tree(g_repo, (git_object *)tree, &opts)); git_tree_free(tree); diff --git a/tests/libgit2/checkout/conflict.c b/tests/libgit2/checkout/conflict.c index b2eb939d..a40c3fc2 100644 --- a/tests/libgit2/checkout/conflict.c +++ b/tests/libgit2/checkout/conflict.c @@ -308,8 +308,6 @@ void test_checkout_conflict__directory_file(void) { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 12); cl_git_pass(git_index_write(g_index)); @@ -347,7 +345,6 @@ void test_checkout_conflict__directory_file_with_custom_labels(void) { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; opts.our_label = "HEAD"; opts.their_label = "branch"; @@ -388,8 +385,6 @@ void test_checkout_conflict__link_file(void) { 0100644, CONFLICTING_THEIRS_OID, 3, "link-4" }, }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 12); cl_git_pass(git_index_write(g_index)); @@ -415,8 +410,6 @@ void test_checkout_conflict__links(void) { 0120000, LINK_THEIRS_OID, 3, "link-2" }, }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 5); cl_git_pass(git_index_write(g_index)); @@ -436,8 +429,6 @@ void test_checkout_conflict__add_add(void) { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 2); cl_git_pass(git_index_write(g_index)); @@ -477,8 +468,6 @@ void test_checkout_conflict__mode_change(void) { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-6" }, }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 18); cl_git_pass(git_index_write(g_index)); @@ -608,8 +597,6 @@ void test_checkout_conflict__renames(void) } }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 41); create_index_names(checkout_name_entries, 9); cl_git_pass(git_index_write(g_index)); @@ -793,7 +780,7 @@ void test_checkout_conflict__rename_keep_ours(void) } }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + opts.checkout_strategy |= GIT_CHECKOUT_USE_OURS; create_index(checkout_index_entries, 41); create_index_names(checkout_name_entries, 9); @@ -926,8 +913,6 @@ void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) } }; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - create_index(checkout_index_entries, 24); create_index_names(checkout_name_entries, 6); cl_git_pass(git_index_write(g_index)); @@ -980,17 +965,17 @@ void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); /* Name is mangled on case insensitive only */ -#if defined(GIT_WIN32) || defined(__APPLE__) - ensure_workdir("test-three.txt~ours_0", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - ensure_workdir("test-three.txt~theirs_0", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); -#else - ensure_workdir("test-three.txt~ours", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - ensure_workdir("test-three.txt~theirs", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); -#endif + if (cl_repo_get_bool(g_repo, "core.ignorecase")) { + ensure_workdir("test-three.txt~ours_0", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-three.txt~theirs_0", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); + } else { + ensure_workdir("test-three.txt~ours", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-three.txt~theirs", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); + } ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID); ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID); @@ -1095,7 +1080,7 @@ static void collect_progress( if (path == NULL) return; - git_vector_insert(paths, strdup(path)); + git_vector_insert(paths, git__strdup(path)); } void test_checkout_conflict__report_progress(void) @@ -1141,5 +1126,5 @@ void test_checkout_conflict__report_progress(void) git_vector_foreach(&paths, i, path) git__free(path); - git_vector_free(&paths); + git_vector_dispose(&paths); } diff --git a/tests/libgit2/checkout/crlf.c b/tests/libgit2/checkout/crlf.c index 21f8a852..2ab970cd 100644 --- a/tests/libgit2/checkout/crlf.c +++ b/tests/libgit2/checkout/crlf.c @@ -197,7 +197,7 @@ static void empty_workdir(const char *name) if (cmp) cl_git_pass(p_unlink(fn)); } - git_vector_free_deep(&contents); + git_vector_dispose_deep(&contents); } void test_checkout_crlf__matches_core_git(void) diff --git a/tests/libgit2/checkout/head.c b/tests/libgit2/checkout/head.c index 3b0bf472..f67770e1 100644 --- a/tests/libgit2/checkout/head.c +++ b/tests/libgit2/checkout/head.c @@ -228,7 +228,6 @@ void test_checkout_head__workdir_filemode_is_simplified(void) cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); git_object_free(branch); @@ -256,7 +255,6 @@ void test_checkout_head__obeys_filemode_true(void) cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, branch, NULL)); git_object_free(branch); @@ -284,7 +282,6 @@ void test_checkout_head__obeys_filemode_false(void) cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); git_object_free(branch); diff --git a/tests/libgit2/checkout/icase.c b/tests/libgit2/checkout/icase.c index d77c7abd..4ff8fb28 100644 --- a/tests/libgit2/checkout/icase.c +++ b/tests/libgit2/checkout/icase.c @@ -34,7 +34,6 @@ void test_checkout_icase__initialize(void) cl_git_pass(git_object_lookup(&obj, repo, &id, GIT_OBJECT_ANY)); git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); - checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; } void test_checkout_icase__cleanup(void) @@ -89,7 +88,7 @@ static void assert_name_is(const char *expected) if (start) cl_assert_equal_strn("/", actual + (start - 1), 1); - free(actual); + git__free(actual); } static int symlink_or_fake(git_repository *repo, const char *a, const char *b) @@ -106,7 +105,7 @@ static int symlink_or_fake(git_repository *repo, const char *a, const char *b) void test_checkout_icase__refuses_to_overwrite_files_for_files(void) { - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + checkout_opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_write2file("testrepo/BRANCH_FILE.txt", "neue file\n", 10, \ O_WRONLY | O_CREAT | O_TRUNC, 0644); @@ -128,7 +127,7 @@ void test_checkout_icase__overwrites_files_for_files_when_forced(void) void test_checkout_icase__refuses_to_overwrite_links_for_files(void) { - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + checkout_opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/BRANCH_FILE.txt")); @@ -152,7 +151,7 @@ void test_checkout_icase__overwrites_links_for_files_when_forced(void) void test_checkout_icase__overwrites_empty_folders_for_files(void) { - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + checkout_opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); @@ -164,7 +163,7 @@ void test_checkout_icase__overwrites_empty_folders_for_files(void) void test_checkout_icase__refuses_to_overwrite_populated_folders_for_files(void) { - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + checkout_opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_must_pass(p_mkdir("testrepo/BRANCH_FILE.txt", 0777)); cl_git_write2file("testrepo/BRANCH_FILE.txt/foobar", "neue file\n", 10, \ @@ -192,7 +191,7 @@ void test_checkout_icase__overwrites_folders_for_files_when_forced(void) void test_checkout_icase__refuses_to_overwrite_files_for_folders(void) { - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + checkout_opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_write2file("testrepo/A", "neue file\n", 10, \ O_WRONLY | O_CREAT | O_TRUNC, 0644); @@ -216,7 +215,7 @@ void test_checkout_icase__overwrites_files_for_folders_when_forced(void) void test_checkout_icase__refuses_to_overwrite_links_for_folders(void) { - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + checkout_opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A")); @@ -244,8 +243,6 @@ void test_checkout_icase__ignores_unstaged_casechange(void) git_commit *orig, *br2; git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); cl_git_pass(git_commit_lookup(&orig, repo, git_reference_target(orig_ref))); cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); @@ -270,8 +267,6 @@ void test_checkout_icase__conflicts_with_casechanged_subtrees(void) git_oid oid; git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); cl_git_pass(git_object_lookup(&orig, repo, git_reference_target(orig_ref), GIT_OBJECT_COMMIT)); cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); diff --git a/tests/libgit2/checkout/index.c b/tests/libgit2/checkout/index.c index 3dfdaa63..03d5d392 100644 --- a/tests/libgit2/checkout/index.c +++ b/tests/libgit2/checkout/index.c @@ -61,7 +61,7 @@ void test_checkout_index__can_create_missing_files(void) cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -81,7 +81,6 @@ void test_checkout_index__can_remove_untracked_files(void) cl_assert_equal_i(true, git_fs_path_isdir("./testrepo/dir/subdir/subsubdir")); opts.checkout_strategy = - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_REMOVE_UNTRACKED; @@ -157,7 +156,7 @@ void test_checkout_index__honor_the_specified_pathspecs(void) cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -176,7 +175,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void) cl_git_mkfile("./testrepo/.gitattributes", attributes); cl_repo_set_bool(g_repo, "core.autocrlf", false); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -194,7 +193,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) cl_git_pass(p_unlink("./testrepo/.gitattributes")); cl_repo_set_bool(g_repo, "core.autocrlf", true); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -288,7 +287,7 @@ void test_checkout_index__coresymlinks_set_to_true_fails_when_unsupported(void) cl_repo_set_bool(g_repo, "core.symlinks", true); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); } @@ -304,7 +303,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) cl_repo_set_bool(g_repo, "core.symlinks", true); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -321,7 +320,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) cl_repo_set_bool(g_repo, "core.symlinks", false); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -337,7 +336,7 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void) /* set this up to not return an error code on conflicts, but it * still will not have permission to overwrite anything... */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS; + opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -363,7 +362,7 @@ void test_checkout_index__options_disable_filters(void) cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.disable_filters = false; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -394,7 +393,7 @@ void test_checkout_index__options_dir_modes(void) reset_index_to_treeish((git_object *)commit); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.dir_mode = 0701; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -421,7 +420,7 @@ void test_checkout_index__options_override_file_modes(void) if (!cl_is_chmod_supported()) return; - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.file_mode = 0700; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); @@ -486,7 +485,7 @@ void test_checkout_index__can_notify_of_skipped_files(void) data.file = "new.txt"; data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"; - opts.checkout_strategy = GIT_CHECKOUT_SAFE | + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_ALLOW_CONFLICTS; opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; @@ -526,7 +525,6 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) cl_git_mkfile("./testrepo/new.txt", "my new file\r\n"); opts.checkout_strategy = - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_ALLOW_CONFLICTS; opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; @@ -548,7 +546,7 @@ void test_checkout_index__calls_progress_callback(void) git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; int calls = 0; - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.progress_cb = checkout_progress_counter; opts.progress_payload = &calls; @@ -583,7 +581,6 @@ void test_checkout_index__can_overcome_name_clashes(void) cl_assert(git_fs_path_isfile("./testrepo/path0/file0")); opts.checkout_strategy = - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_ALLOW_CONFLICTS; cl_git_pass(git_checkout_index(g_repo, index, &opts)); @@ -635,7 +632,6 @@ void test_checkout_index__can_update_prefixed_files(void) cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777)); opts.checkout_strategy = - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_REMOVE_UNTRACKED; @@ -686,7 +682,7 @@ void test_checkout_index__target_directory(void) checkout_counts cts; memset(&cts, 0, sizeof(cts)); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.target_directory = "alternative"; cl_assert(!git_fs_path_isdir("alternative")); @@ -731,7 +727,7 @@ void test_checkout_index__target_directory_from_bare(void) cl_git_pass(git_index_write(index)); git_index_free(index); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; @@ -770,7 +766,7 @@ void test_checkout_index__can_get_repo_from_index(void) cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; cl_git_pass(git_repository_index(&index, g_repo)); diff --git a/tests/libgit2/checkout/tree.c b/tests/libgit2/checkout/tree.c index 65df00cd..b9f51f7b 100644 --- a/tests/libgit2/checkout/tree.c +++ b/tests/libgit2/checkout/tree.c @@ -186,8 +186,6 @@ void test_checkout_tree__can_switch_branches(void) git_object_free(obj); /* do second checkout safe because we should be clean after first */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); @@ -213,7 +211,7 @@ void test_checkout_tree__can_remove_untracked(void) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED; + opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED; cl_git_mkfile("testrepo/untracked_file", "as you wish"); cl_assert(git_fs_path_isfile("testrepo/untracked_file")); @@ -228,7 +226,7 @@ void test_checkout_tree__can_remove_ignored(void) git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; int ignored = 0; - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED; + opts.checkout_strategy = GIT_CHECKOUT_REMOVE_IGNORED; cl_git_mkfile("testrepo/ignored_file", "as you wish"); @@ -313,7 +311,7 @@ void test_checkout_tree__conflict_on_ignored_when_not_overwriting(void) int error; cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir( - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false)); + GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false)); cl_assert_equal_i(GIT_ECONFLICT, error); } @@ -334,7 +332,7 @@ void test_checkout_tree__conflict_on_ignored_folder_when_not_overwriting(void) int error; cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir( - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true)); + GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true)); cl_assert_equal_i(GIT_ECONFLICT, error); } @@ -370,7 +368,7 @@ void test_checkout_tree__can_update_only(void) /* now checkout branch but with update only */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; + opts.checkout_strategy = GIT_CHECKOUT_UPDATE_ONLY; cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); @@ -417,7 +415,6 @@ void test_checkout_tree__can_checkout_with_pattern(void) /* now to a narrow patterned checkout */ - g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; g_opts.paths.strings = entries; g_opts.paths.count = 1; @@ -489,7 +486,6 @@ void test_checkout_tree__can_disable_pattern_match(void) /* now to a narrow patterned checkout, but disable pattern */ g_opts.checkout_strategy = - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; g_opts.paths.strings = entries; g_opts.paths.count = 1; @@ -606,8 +602,6 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void) git_index *index = NULL; checkout_counts ct; - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - memset(&ct, 0, sizeof(ct)); opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; opts.notify_cb = checkout_count_callback; @@ -883,8 +877,7 @@ void test_checkout_tree__target_directory_from_bare(void) g_repo = cl_git_sandbox_init("testrepo.git"); cl_assert(git_repository_is_bare(g_repo)); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_RECREATE_MISSING; opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; opts.notify_cb = checkout_count_callback; @@ -963,8 +956,6 @@ void test_checkout_tree__fails_when_conflicts_exist_in_index(void) git_oid oid; git_object *obj = NULL; - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); @@ -1622,8 +1613,6 @@ void test_checkout_tree__retains_external_index_changes(void) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - modify_index_and_checkout_tree(&opts); assert_status_entrycount(g_repo, 1); } @@ -1632,7 +1621,7 @@ void test_checkout_tree__no_index_refresh(void) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_NO_REFRESH; + opts.checkout_strategy = GIT_CHECKOUT_NO_REFRESH; modify_index_and_checkout_tree(&opts); assert_status_entrycount(g_repo, 0); @@ -1659,7 +1648,7 @@ void test_checkout_tree__dry_run(void) /* now checkout branch but with dry run enabled */ memset(&ct, 0, sizeof(ct)); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DRY_RUN; + opts.checkout_strategy = GIT_CHECKOUT_DRY_RUN; opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; opts.notify_cb = checkout_count_callback; opts.notify_payload = &ct; diff --git a/tests/libgit2/cherrypick/workdir.c b/tests/libgit2/cherrypick/workdir.c index c16b7814..21e0b144 100644 --- a/tests/libgit2/cherrypick/workdir.c +++ b/tests/libgit2/cherrypick/workdir.c @@ -257,7 +257,7 @@ void test_cherrypick_workdir__conflict_use_ours(void) }; /* leave the index in a conflicted state, but checkout "ours" to the workdir */ - opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_USE_OURS; git_oid__fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8", GIT_OID_SHA1); diff --git a/tests/libgit2/clone/local.c b/tests/libgit2/clone/local.c index e0bd74df..a15e4581 100644 --- a/tests/libgit2/clone/local.c +++ b/tests/libgit2/clone/local.c @@ -54,41 +54,87 @@ static int unc_path(git_str *buf, const char *host, const char *path) void test_clone_local__should_clone_local(void) { git_str buf = GIT_STR_INIT; + bool local; /* we use a fixture path because it needs to exist for us to want to clone */ const char *path = cl_fixture("testrepo.git"); + /* empty string */ cl_git_pass(file_url(&buf, "", path)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(false, local); + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(false, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_NO_LOCAL)); + cl_assert_equal_i(false, local); + + /* localhost is special */ cl_git_pass(file_url(&buf, "localhost", path)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(false, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_NO_LOCAL)); + cl_assert_equal_i(false, local); + /* a remote host */ cl_git_pass(file_url(&buf, "other-host.mycompany.com", path)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(false, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(false, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(false, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_NO_LOCAL)); + cl_assert_equal_i(false, local); /* Ensure that file:/// urls are percent decoded: .git == %2e%67%69%74 */ cl_git_pass(file_url(&buf, "", path)); git_str_shorten(&buf, 4); cl_git_pass(git_str_puts(&buf, "%2e%67%69%74")); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(false, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, buf.ptr, GIT_CLONE_NO_LOCAL)); + cl_assert_equal_i(false, local); + + /* a local path on disk */ + cl_git_pass(git_clone__should_clone_local(&local, path, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, path, GIT_CLONE_LOCAL)); + + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, path, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(true, local); + + cl_git_pass(git_clone__should_clone_local(&local, path, GIT_CLONE_NO_LOCAL)); + cl_assert_equal_i(false, local); git_str_dispose(&buf); } @@ -210,3 +256,13 @@ void test_clone_local__git_style_unc_paths(void) cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); #endif } + +void test_clone_local__shallow_fails(void) +{ + git_repository *repo; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + opts.fetch_opts.depth = 4; + + cl_git_fail_with(GIT_ENOTSUPPORTED, git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts)); +} diff --git a/tests/libgit2/clone/nonetwork.c b/tests/libgit2/clone/nonetwork.c index 5316003f..e784ec20 100644 --- a/tests/libgit2/clone/nonetwork.c +++ b/tests/libgit2/clone/nonetwork.c @@ -24,7 +24,6 @@ void test_clone_nonetwork__initialize(void) memset(&g_options, 0, sizeof(git_clone_options)); g_options.version = GIT_CLONE_OPTIONS_VERSION; g_options.checkout_opts = dummy_opts; - g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; g_options.fetch_opts = dummy_fetch; } diff --git a/tests/libgit2/commit/create.c b/tests/libgit2/commit/create.c new file mode 100644 index 00000000..9f627dc8 --- /dev/null +++ b/tests/libgit2/commit/create.c @@ -0,0 +1,112 @@ +#include "clar_libgit2.h" +#include "repository.h" + +/* Fixture setup */ +static git_repository *g_repo; +static git_signature *g_author, *g_committer; + +void test_commit_create__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo2"); + cl_git_pass(git_signature_new(&g_author, "Edward Thomson", "ethomson@edwardthomson.com", 123456789, 60)); + cl_git_pass(git_signature_new(&g_committer, "libgit2 user", "nobody@noreply.libgit2.org", 987654321, 90)); +} + +void test_commit_create__cleanup(void) +{ + git_signature_free(g_committer); + git_signature_free(g_author); + cl_git_sandbox_cleanup(); +} + + +void test_commit_create__from_stage_simple(void) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_index *index; + git_oid commit_id; + git_tree *tree; + + opts.author = g_author; + opts.committer = g_committer; + + cl_git_rewritefile("testrepo2/newfile.txt", "This is a new file.\n"); + cl_git_rewritefile("testrepo2/newfile2.txt", "This is a new file.\n"); + cl_git_rewritefile("testrepo2/README", "hello, world.\n"); + cl_git_rewritefile("testrepo2/new.txt", "hi there.\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "newfile2.txt")); + cl_git_pass(git_index_add_bypath(index, "README")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "This is the message.", &opts)); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + cl_assert_equal_oidstr("241b5b04e847bc38dd7b4b9f49f21e55da40f3a6", &commit_id); + cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(tree)); + + git_index_free(index); + git_tree_free(tree); +} + +void test_commit_create__from_stage_nochanges(void) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_oid commit_id; + git_tree *tree; + + opts.author = g_author; + opts.committer = g_committer; + + cl_git_fail_with(GIT_EUNCHANGED, git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts)); + + opts.allow_empty_commit = 1; + + cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts)); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + cl_assert_equal_oidstr("f776dc4c7fd8164b7127dc8e4f9b44421cb01b56", &commit_id); + cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree)); + + git_tree_free(tree); +} + +void test_commit_create__from_stage_newrepo(void) +{ + git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; + git_repository *newrepo; + git_index *index; + git_commit *commit; + git_tree *tree; + git_oid commit_id; + + opts.author = g_author; + opts.committer = g_committer; + + git_repository_init(&newrepo, "newrepo", false); + cl_git_pass(git_repository_index(&index, newrepo)); + + cl_git_rewritefile("newrepo/hello.txt", "hello, world.\n"); + cl_git_rewritefile("newrepo/hi.txt", "hi there.\n"); + cl_git_rewritefile("newrepo/foo.txt", "bar.\n"); + + cl_git_pass(git_index_add_bypath(index, "hello.txt")); + cl_git_pass(git_index_add_bypath(index, "foo.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_commit_create_from_stage(&commit_id, newrepo, "Initial commit.", &opts)); + cl_git_pass(git_repository_head_commit(&commit, newrepo)); + cl_git_pass(git_repository_head_tree(&tree, newrepo)); + + cl_assert_equal_oid(&commit_id, git_commit_id(commit)); + cl_assert_equal_oidstr("b2fa96a4f191c76eb172437281c66aa29609dcaa", git_commit_tree_id(commit)); + + git_tree_free(tree); + git_commit_free(commit); + git_index_free(index); + git_repository_free(newrepo); + cl_fixture_cleanup("newrepo"); +} diff --git a/tests/libgit2/commit/signature.c b/tests/libgit2/commit/signature.c index a9155141..3fa6646c 100644 --- a/tests/libgit2/commit/signature.c +++ b/tests/libgit2/commit/signature.c @@ -36,10 +36,17 @@ void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void) assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " \t nulltoken \n", " \n emeric.fermas@gmail.com \n"); } +void test_commit_signature__leading_and_trailing_dots_are_supported(void) +{ + assert_name_and_email(".nulltoken", ".emeric.fermas@gmail.com", ".nulltoken", ".emeric.fermas@gmail.com"); + assert_name_and_email("nulltoken.", "emeric.fermas@gmail.com.", "nulltoken.", "emeric.fermas@gmail.com."); + assert_name_and_email(".nulltoken.", ".emeric.fermas@gmail.com.", ".nulltoken.", ".emeric.fermas@gmail.com."); +} + void test_commit_signature__leading_and_trailing_crud_is_trimmed(void) { assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", "\"nulltoken\"", "\"emeric.fermas@gmail.com\""); - assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w.", "emeric.fermas@gmail.com"); + assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w;", "emeric.fermas@gmail.com"); assert_name_and_email("nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com", "nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com"); } @@ -146,3 +153,83 @@ void test_commit_signature__pos_and_neg_zero_offsets_dont_match(void) git_signature_free((git_signature *)with_neg_zero); git_signature_free((git_signature *)with_pos_zero); } + +static git_repository *g_repo; + +void test_commit_signature__initialize(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); +} + +void test_commit_signature__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_commit_signature__from_env(void) +{ + git_signature *author_sign, *committer_sign; + git_config *cfg, *local; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); + /* No configuration value is set and no environment variable */ + cl_setenv("EMAIL", NULL); + cl_setenv("GIT_AUTHOR_NAME", NULL); + cl_setenv("GIT_AUTHOR_EMAIL", NULL); + cl_setenv("GIT_COMMITTER_NAME", NULL); + cl_setenv("GIT_COMMITTER_EMAIL", NULL); + cl_git_fail(git_signature_default_from_env(&author_sign, &committer_sign, g_repo)); + /* Name is read from configuration and email is read from fallback EMAIL + * environment variable */ + cl_git_pass(git_config_set_string(local, "user.name", "Name (config)")); + cl_setenv("EMAIL", "email-envvar@example.com"); + cl_git_pass(git_signature_default_from_env(&author_sign, &committer_sign, g_repo)); + cl_assert_equal_s("Name (config)", author_sign->name); + cl_assert_equal_s("email-envvar@example.com", author_sign->email); + cl_assert_equal_s("Name (config)", committer_sign->name); + cl_assert_equal_s("email-envvar@example.com", committer_sign->email); + cl_setenv("EMAIL", NULL); + git_signature_free(author_sign); + git_signature_free(committer_sign); + /* Environment variables have precedence over configuration */ + cl_git_pass(git_config_set_string(local, "user.email", "config@example.com")); + cl_setenv("GIT_AUTHOR_NAME", "Author (envvar)"); + cl_setenv("GIT_AUTHOR_EMAIL", "author-envvar@example.com"); + cl_setenv("GIT_COMMITTER_NAME", "Committer (envvar)"); + cl_setenv("GIT_COMMITTER_EMAIL", "committer-envvar@example.com"); + cl_git_pass(git_signature_default_from_env(&author_sign, &committer_sign, g_repo)); + cl_assert_equal_s("Author (envvar)", author_sign->name); + cl_assert_equal_s("author-envvar@example.com", author_sign->email); + cl_assert_equal_s("Committer (envvar)", committer_sign->name); + cl_assert_equal_s("committer-envvar@example.com", committer_sign->email); + git_signature_free(author_sign); + git_signature_free(committer_sign); + /* When environment variables are not set we can still read from + * configuration */ + cl_setenv("GIT_AUTHOR_NAME", NULL); + cl_setenv("GIT_AUTHOR_EMAIL", NULL); + cl_setenv("GIT_COMMITTER_NAME", NULL); + cl_setenv("GIT_COMMITTER_EMAIL", NULL); + cl_git_pass(git_signature_default_from_env(&author_sign, &committer_sign, g_repo)); + cl_assert_equal_s("Name (config)", author_sign->name); + cl_assert_equal_s("config@example.com", author_sign->email); + cl_assert_equal_s("Name (config)", committer_sign->name); + cl_assert_equal_s("config@example.com", committer_sign->email); + git_signature_free(author_sign); + git_signature_free(committer_sign); + /* We can also override the timestamp with an environment variable */ + cl_setenv("GIT_AUTHOR_DATE", "1971-02-03 04:05:06+01"); + cl_setenv("GIT_COMMITTER_DATE", "1988-09-10 11:12:13-01"); + cl_git_pass(git_signature_default_from_env(&author_sign, &committer_sign, g_repo)); + cl_assert_equal_i(34398306, author_sign->when.time); /* 1971-02-03 03:05:06 UTC */ + cl_assert_equal_i(60, author_sign->when.offset); + cl_assert_equal_i(589896733, committer_sign->when.time); /* 1988-09-10 12:12:13 UTC */ + cl_assert_equal_i(-60, committer_sign->when.offset); + git_signature_free(author_sign); + git_signature_free(committer_sign); + cl_setenv("GIT_AUTHOR_DATE", NULL); + cl_setenv("GIT_COMMITTER_DATE", NULL); + git_config_free(local); + git_config_free(cfg); +} diff --git a/tests/libgit2/config/configlevel.c b/tests/libgit2/config/configlevel.c index 8422d32c..0e31268b 100644 --- a/tests/libgit2/config/configlevel.c +++ b/tests/libgit2/config/configlevel.c @@ -71,3 +71,44 @@ void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_ret git_config_free(cfg); } + +void test_config_configlevel__can_fetch_highest_level(void) +{ + git_config *cfg; + git_config *single_level_cfg; + git_buf buf = {0}; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_HIGHEST_LEVEL)); + + git_config_free(cfg); + + cl_git_pass(git_config_get_string_buf(&buf, single_level_cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(single_level_cfg); +} + +void test_config_configlevel__can_override_local_with_worktree(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_WORKTREE, NULL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(cfg); +} diff --git a/tests/libgit2/config/global.c b/tests/libgit2/config/global.c index 5aba4eec..d382f970 100644 --- a/tests/libgit2/config/global.c +++ b/tests/libgit2/config/global.c @@ -20,6 +20,11 @@ void test_config_global__initialize(void) cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + cl_git_pass(git_futils_mkdir_r("programdata", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "programdata", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr)); + git_str_dispose(&path); } diff --git a/tests/libgit2/config/include.c b/tests/libgit2/config/include.c index 1b55fdc8..ba8bcad4 100644 --- a/tests/libgit2/config/include.c +++ b/tests/libgit2/config/include.c @@ -111,7 +111,7 @@ void test_config_include__missing(void) git_error_clear(); cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_assert(git_error_last() == NULL); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", buf.ptr); @@ -126,7 +126,7 @@ void test_config_include__missing_homedir(void) git_error_clear(); cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_assert(git_error_last() == NULL); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", buf.ptr); diff --git a/tests/libgit2/config/memory.c b/tests/libgit2/config/memory.c index ae661899..9f533e28 100644 --- a/tests/libgit2/config/memory.c +++ b/tests/libgit2/config/memory.c @@ -34,8 +34,13 @@ static int contains_all_cb(const git_config_entry *entry, void *payload) int i; for (i = 0; entries[i].name; i++) { - if (strcmp(entries[i].name, entry->name) || - strcmp(entries[i].value , entry->value)) + if (strcmp(entries[i].name, entry->name)) + continue; + + if ((entries[i].value == NULL) ^ (entry->value == NULL)) + continue; + + if (entry->value && strcmp(entries[i].value , entry->value)) continue; if (entries[i].seen) @@ -61,7 +66,23 @@ static void assert_config_contains_all(git_config_backend *backend, static void setup_backend(const char *cfg) { - cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg), &opts)); + cl_git_pass(git_config_backend_open(backend, 0, NULL)); +} + +static void setup_values_backend(const char **values, size_t len) +{ + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_values(&backend, values, len, &opts)); cl_git_pass(git_config_backend_open(backend, 0, NULL)); } @@ -88,7 +109,13 @@ void test_config_memory__malformed_fails_to_open(void) const char *cfg = "[general\n" "foo=bar\n"; - cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); + + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg), &opts)); cl_git_fail(git_config_backend_open(backend, 0, NULL)); } @@ -137,3 +164,43 @@ void test_config_memory__foreach_sees_multivar(void) "foo=bar2\n"); assert_config_contains_all(backend, entries); } + +void test_config_memory__values(void) +{ + const char *values[] = { + "general.foo=bar1", + "general.foo=bar2", + "other.key=value", + "empty.value=", + "no.value", + }; + + struct expected_entry entries[] = { + { "general.foo", "bar1", 0 }, + { "general.foo", "bar2", 0 }, + { "other.key", "value", 0 }, + { "empty.value", "", 0 }, + { "no.value", NULL, 0 }, + { NULL, NULL, 0 } + }; + + setup_values_backend(values, 5); + assert_config_contains_all(backend, entries); +} + +void test_config_memory__valid_values(void) +{ + const char *values[] = { + "general.foo=bar1", + "=bar2", + "other.key=value" + }; + + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_values(&backend, values, 3, &opts)); + cl_git_fail(git_config_backend_open(backend, 0, NULL)); +} diff --git a/tests/libgit2/config/multivar.c b/tests/libgit2/config/multivar.c index 244e3755..3ed84601 100644 --- a/tests/libgit2/config/multivar.c +++ b/tests/libgit2/config/multivar.c @@ -1,4 +1,6 @@ #include "clar_libgit2.h" +#include "config.h" +#include "config/config_helpers.h" static const char *_name = "remote.ab.url"; @@ -286,3 +288,32 @@ void test_config_multivar__delete_notfound(void) git_config_free(cfg); } + +void test_config_multivar__rename_section(void) +{ + git_repository *repo; + git_config *cfg; + int n; + + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_config(&cfg, repo)); + + cl_git_pass(git_config_set_multivar(cfg, "branch.foo.name", "^$", "bar")); + cl_git_pass(git_config_set_multivar(cfg, "branch.foo.name", "^$", "xyzzy")); + n = 0; + cl_git_pass(git_config_get_multivar_foreach( + cfg, "branch.foo.name", NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass( + git_config_rename_section(repo, "branch.foo", "branch.foobar")); + + assert_config_entry_existence(repo, "branch.foo.name", false); + n = 0; + cl_git_pass(git_config_get_multivar_foreach( + cfg, "branch.foobar.name", NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); + cl_git_sandbox_cleanup(); +} diff --git a/tests/libgit2/config/read.c b/tests/libgit2/config/read.c index ac6459b9..25e7b963 100644 --- a/tests/libgit2/config/read.c +++ b/tests/libgit2/config/read.c @@ -495,6 +495,8 @@ void test_config_read__read_git_config_entry(void) cl_assert_equal_s("core.dummy2", entry->name); cl_assert_equal_s("42", entry->value); cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s(cl_fixture("config/config9"), entry->origin_path); git_config_entry_free(entry); git_config_free(cfg); diff --git a/tests/libgit2/config/readonly.c b/tests/libgit2/config/readonly.c index a8901e39..483f83a8 100644 --- a/tests/libgit2/config/readonly.c +++ b/tests/libgit2/config/readonly.c @@ -24,7 +24,7 @@ void test_config_readonly__writing_to_readonly_fails(void) backend->readonly = 1; cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_fail_with(GIT_ENOTFOUND, git_config_set_string(cfg, "foo.bar", "baz")); + cl_git_fail_with(GIT_EREADONLY, git_config_set_string(cfg, "foo.bar", "baz")); cl_assert(!git_fs_path_exists("global")); } diff --git a/tests/libgit2/config/snapshot.c b/tests/libgit2/config/snapshot.c index 5cc08a72..cc877063 100644 --- a/tests/libgit2/config/snapshot.c +++ b/tests/libgit2/config/snapshot.c @@ -79,6 +79,7 @@ void test_config_snapshot__multivar(void) void test_config_snapshot__includes(void) { + git_config_entry *entry; int i; cl_git_mkfile("including", "[include]\npath = included"); @@ -99,6 +100,16 @@ void test_config_snapshot__includes(void) cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); cl_assert_equal_i(i, 1); + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s("./included", entry->origin_path); + + git_config_entry_free(entry); + cl_git_pass(p_unlink("including")); cl_git_pass(p_unlink("included")); } @@ -106,6 +117,7 @@ void test_config_snapshot__includes(void) void test_config_snapshot__snapshot(void) { git_config *snapshot_snapshot; + git_config_entry *entry; int i; cl_git_mkfile("configfile", "[section]\nkey = 1\n"); @@ -118,22 +130,49 @@ void test_config_snapshot__snapshot(void) cl_git_pass(git_config_get_int32(&i, snapshot_snapshot, "section.key")); cl_assert_equal_i(i, 1); + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot_snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s("configfile", entry->origin_path); + + git_config_entry_free(entry); + git_config_free(snapshot_snapshot); cl_git_pass(p_unlink("configfile")); } -void test_config_snapshot__snapshot_from_in_memony(void) +void test_config_snapshot__snapshot_from_in_memory(void) { const char *configuration = "[section]\nkey = 1\n"; git_config_backend *backend; + git_config_entry *entry; int i; + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + opts.origin_path = "hello"; + cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration))); + cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration), &opts)); cl_git_pass(git_config_add_backend(cfg, backend, 0, NULL, 0)); cl_git_pass(git_config_snapshot(&snapshot, cfg)); cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); cl_assert_equal_i(i, 1); + + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("test", entry->backend_type); + cl_assert_equal_s("hello", entry->origin_path); + + git_config_entry_free(entry); } diff --git a/tests/libgit2/config/write.c b/tests/libgit2/config/write.c index 9d8c3fe9..c71d4f6d 100644 --- a/tests/libgit2/config/write.c +++ b/tests/libgit2/config/write.c @@ -696,6 +696,36 @@ void test_config_write__locking(void) git_config_free(cfg); } +void test_config_write__abort_lock(void) +{ + git_config *cfg; + git_config_entry *entry; + git_transaction *tx; + const char *filename = "locked-file"; + + /* Open the config and lock it */ + cl_git_mkfile(filename, "[section]\n\tname = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_lock(&tx, cfg)); + + /* Change entries in the locked backend */ + cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); + cl_git_pass(git_config_set_string(cfg, "section2.name3", "more value")); + + git_transaction_free(tx); + + /* Now that we've unlocked it, we should see no changes */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); + + git_config_free(cfg); +} + void test_config_write__repeated(void) { const char *filename = "config-repeated"; diff --git a/tests/libgit2/core/features.c b/tests/libgit2/core/features.c index 7b28cc0c..b7cd6014 100644 --- a/tests/libgit2/core/features.c +++ b/tests/libgit2/core/features.c @@ -1,15 +1,8 @@ #include "clar_libgit2.h" -void test_core_features__0(void) +void test_core_features__basic(void) { - int major, minor, rev, caps; - - git_libgit2_version(&major, &minor, &rev); - cl_assert_equal_i(LIBGIT2_VER_MAJOR, major); - cl_assert_equal_i(LIBGIT2_VER_MINOR, minor); - cl_assert_equal_i(LIBGIT2_VER_REVISION, rev); - - caps = git_libgit2_features(); + int caps = git_libgit2_features(); #ifdef GIT_THREADS cl_assert((caps & GIT_FEATURE_THREADS) != 0); @@ -32,4 +25,202 @@ void test_core_features__0(void) #else cl_assert((caps & GIT_FEATURE_NSEC) == 0); #endif + + cl_assert((caps & GIT_FEATURE_HTTP_PARSER) != 0); + cl_assert((caps & GIT_FEATURE_REGEX) != 0); + +#if defined(GIT_USE_ICONV) + cl_assert((caps & GIT_FEATURE_I18N) != 0); +#endif + +#if defined(GIT_NTLM) || defined(GIT_WIN32) + cl_assert((caps & GIT_FEATURE_AUTH_NTLM) != 0); +#endif +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) || defined(GIT_WIN32) + cl_assert((caps & GIT_FEATURE_AUTH_NEGOTIATE) != 0); +#endif + + cl_assert((caps & GIT_FEATURE_COMPRESSION) != 0); + cl_assert((caps & GIT_FEATURE_SHA1) != 0); + +#if defined(GIT_EXPERIMENTAL_SHA256) + cl_assert((caps & GIT_FEATURE_SHA256) != 0); +#endif + + /* + * Ensure that our tests understand all the features; + * this test tries to ensure that if there's a new feature + * added that the backends test (below) is updated as well. + */ + cl_assert((caps & ~(GIT_FEATURE_THREADS | + GIT_FEATURE_HTTPS | + GIT_FEATURE_SSH | + GIT_FEATURE_NSEC | + GIT_FEATURE_HTTP_PARSER | + GIT_FEATURE_REGEX | + GIT_FEATURE_I18N | + GIT_FEATURE_AUTH_NTLM | + GIT_FEATURE_AUTH_NEGOTIATE | + GIT_FEATURE_COMPRESSION | + GIT_FEATURE_SHA1 | + GIT_FEATURE_SHA256 + )) == 0); +} + +void test_core_features__backends(void) +{ + const char *threads = git_libgit2_feature_backend(GIT_FEATURE_THREADS); + const char *https = git_libgit2_feature_backend(GIT_FEATURE_HTTPS); + const char *ssh = git_libgit2_feature_backend(GIT_FEATURE_SSH); + const char *nsec = git_libgit2_feature_backend(GIT_FEATURE_NSEC); + const char *http_parser = git_libgit2_feature_backend(GIT_FEATURE_HTTP_PARSER); + const char *regex = git_libgit2_feature_backend(GIT_FEATURE_REGEX); + const char *i18n = git_libgit2_feature_backend(GIT_FEATURE_I18N); + const char *ntlm = git_libgit2_feature_backend(GIT_FEATURE_AUTH_NTLM); + const char *negotiate = git_libgit2_feature_backend(GIT_FEATURE_AUTH_NEGOTIATE); + const char *compression = git_libgit2_feature_backend(GIT_FEATURE_COMPRESSION); + const char *sha1 = git_libgit2_feature_backend(GIT_FEATURE_SHA1); + const char *sha256 = git_libgit2_feature_backend(GIT_FEATURE_SHA256); + +#if defined(GIT_THREADS) && defined(GIT_WIN32) + cl_assert_equal_s("win32", threads); +#elif defined(GIT_THREADS) + cl_assert_equal_s("pthread", threads); +#else + cl_assert(threads == NULL); +#endif + +#if defined(GIT_HTTPS) && defined(GIT_OPENSSL) + cl_assert_equal_s("openssl", https); +#elif defined(GIT_HTTPS) && defined(GIT_OPENSSL_DYNAMIC) + cl_assert_equal_s("openssl-dynamic", https); +#elif defined(GIT_HTTPS) && defined(GIT_MBEDTLS) + cl_assert_equal_s("mbedtls", https); +#elif defined(GIT_HTTPS) && defined(GIT_SECURE_TRANSPORT) + cl_assert_equal_s("securetransport", https); +#elif defined(GIT_HTTPS) && defined(GIT_SCHANNEL) + cl_assert_equal_s("schannel", https); +#elif defined(GIT_HTTPS) && defined(GIT_WINHTTP) + cl_assert_equal_s("winhttp", https); +#elif defined(GIT_HTTPS) + cl_assert(0); +#else + cl_assert(https == NULL); +#endif + +#if defined(GIT_SSH) && defined(GIT_SSH_EXEC) + cl_assert_equal_s("exec", ssh); +#elif defined(GIT_SSH) && defined(GIT_SSH_LIBSSH2) + cl_assert_equal_s("libssh2", ssh); +#elif defined(GIT_SSH) + cl_assert(0); +#else + cl_assert(ssh == NULL); +#endif + +#if defined(GIT_USE_NSEC) && defined(GIT_USE_STAT_MTIMESPEC) + cl_assert_equal_s("mtimespec", nsec); +#elif defined(GIT_USE_NSEC) && defined(GIT_USE_STAT_MTIM) + cl_assert_equal_s("mtim", nsec); +#elif defined(GIT_USE_NSEC) && defined(GIT_USE_STAT_MTIME_NSEC) + cl_assert_equal_s("mtime", nsec); +#elif defined(GIT_USE_NSEC) && defined(GIT_WIN32) + cl_assert_equal_s("win32", nsec); +#elif defined(GIT_USE_NSEC) + cl_assert(0); +#else + cl_assert(nsec == NULL); +#endif + +#if defined(GIT_HTTPPARSER_HTTPPARSER) + cl_assert_equal_s("httpparser", http_parser); +#elif defined(GIT_HTTPPARSER_LLHTTP) + cl_assert_equal_s("llhttp", http_parser); +#elif defined(GIT_HTTPPARSER_BUILTIN) + cl_assert_equal_s("builtin", http_parser); +#else + cl_assert(0); +#endif + +#if defined(GIT_REGEX_REGCOMP_L) + cl_assert_equal_s("regcomp_l", regex); +#elif defined(GIT_REGEX_REGCOMP) + cl_assert_equal_s("regcomp", regex); +#elif defined(GIT_REGEX_PCRE) + cl_assert_equal_s("pcre", regex); +#elif defined(GIT_REGEX_PCRE2) + cl_assert_equal_s("pcre2", regex); +#elif defined(GIT_REGEX_BUILTIN) + cl_assert_equal_s("builtin", regex); +#else + cl_assert(0); +#endif + +#if defined(GIT_USE_ICONV) + cl_assert_equal_s("iconv", i18n); +#else + cl_assert(i18n == NULL); +#endif + +#if defined(GIT_NTLM) + cl_assert_equal_s("ntlmclient", ntlm); +#elif defined(GIT_WIN32) + cl_assert_equal_s("sspi", ntlm); +#else + cl_assert(ntlm == NULL); +#endif + +#if defined(GIT_GSSAPI) + cl_assert_equal_s("gssapi", negotiate); +#elif defined(GIT_WIN32) + cl_assert_equal_s("sspi", negotiate); +#else + cl_assert(negotiate == NULL); +#endif + +#if defined(GIT_COMPRESSION_BUILTIN) + cl_assert_equal_s("builtin", compression); +#elif defined(GIT_COMPRESSION_ZLIB) + cl_assert_equal_s("zlib", compression); +#else + cl_assert(0); +#endif + +#if defined(GIT_SHA1_COLLISIONDETECT) + cl_assert_equal_s("builtin", sha1); +#elif defined(GIT_SHA1_OPENSSL) + cl_assert_equal_s("openssl", sha1); +#elif defined(GIT_SHA1_OPENSSL_FIPS) + cl_assert_equal_s("openssl-fips", sha1); +#elif defined(GIT_SHA1_OPENSSL_DYNAMIC) + cl_assert_equal_s("openssl-dynamic", sha1); +#elif defined(GIT_SHA1_MBEDTLS) + cl_assert_equal_s("mbedtls", sha1); +#elif defined(GIT_SHA1_COMMON_CRYPTO) + cl_assert_equal_s("commoncrypto", sha1); +#elif defined(GIT_SHA1_WIN32) + cl_assert_equal_s("win32", sha1); +#else + cl_assert(0); +#endif + +#if defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_BUILTIN) + cl_assert_equal_s("builtin", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_OPENSSL) + cl_assert_equal_s("openssl", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_OPENSSL_FIPS) + cl_assert_equal_s("openssl-fips", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_OPENSSL_DYNAMIC) + cl_assert_equal_s("openssl-dynamic", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_MBEDTLS) + cl_assert_equal_s("mbedtls", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_COMMON_CRYPTO) + cl_assert_equal_s("commoncrypto", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) && defined(GIT_SHA256_WIN32) + cl_assert_equal_s("win32", sha256); +#elif defined(GIT_EXPERIMENTAL_SHA256) + cl_assert(0); +#else + cl_assert(sha256 == NULL); +#endif } diff --git a/tests/libgit2/core/oidmap.c b/tests/libgit2/core/oidmap.c deleted file mode 100644 index 34374cee..00000000 --- a/tests/libgit2/core/oidmap.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "clar_libgit2.h" -#include "oidmap.h" - -static struct { - git_oid oid; - size_t extra; -} test_oids[0x0FFF]; - -static git_oidmap *g_map; - -void test_core_oidmap__initialize(void) -{ - uint32_t i, j; - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - uint32_t segment = i / 8; - int modi = i - (segment * 8); - - test_oids[i].extra = i; - - for (j = 0; j < GIT_OID_SHA1_SIZE / 4; ++j) { - test_oids[i].oid.id[j * 4 ] = (unsigned char)modi; - test_oids[i].oid.id[j * 4 + 1] = (unsigned char)(modi >> 8); - test_oids[i].oid.id[j * 4 + 2] = (unsigned char)(modi >> 16); - test_oids[i].oid.id[j * 4 + 3] = (unsigned char)(modi >> 24); - } - - test_oids[i].oid.id[ 8] = (unsigned char)i; - test_oids[i].oid.id[ 9] = (unsigned char)(i >> 8); - test_oids[i].oid.id[10] = (unsigned char)(i >> 16); - test_oids[i].oid.id[11] = (unsigned char)(i >> 24); -#ifdef GIT_EXPERIMENTAL_SHA256 - test_oids[i].oid.type = GIT_OID_SHA1; -#endif - } - - cl_git_pass(git_oidmap_new(&g_map)); -} - -void test_core_oidmap__cleanup(void) -{ - git_oidmap_free(g_map); -} - -void test_core_oidmap__basic(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - } - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); - } -} - -void test_core_oidmap__hash_collision(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - } - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); - } -} - -void test_core_oidmap__get_succeeds_with_existing_keys(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); -} - -void test_core_oidmap__get_fails_with_nonexisting_key(void) -{ - size_t i; - - /* Do _not_ add last OID to verify that we cannot look it up */ - for (i = 0; i < ARRAY_SIZE(test_oids) - 1; ++i) - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[ARRAY_SIZE(test_oids) - 1].oid), NULL); -} - -void test_core_oidmap__setting_oid_persists(void) -{ - git_oid oids[] = { - GIT_OID_INIT(GIT_OID_SHA1, { 0x01 }), - GIT_OID_INIT(GIT_OID_SHA1, { 0x02 }), - GIT_OID_INIT(GIT_OID_SHA1, { 0x03 }) - }; - - cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); - cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); - cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); - - cl_assert_equal_s(git_oidmap_get(g_map, &oids[0]), "one"); - cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "two"); - cl_assert_equal_s(git_oidmap_get(g_map, &oids[2]), "three"); -} - -void test_core_oidmap__setting_existing_key_updates(void) -{ - git_oid oids[] = { - GIT_OID_INIT(GIT_OID_SHA1, { 0x01 }), - GIT_OID_INIT(GIT_OID_SHA1, { 0x02 }), - GIT_OID_INIT(GIT_OID_SHA1, { 0x03 }) - }; - - cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); - cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); - cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); - cl_assert_equal_i(git_oidmap_size(g_map), 3); - - cl_git_pass(git_oidmap_set(g_map, &oids[1], "other")); - cl_assert_equal_i(git_oidmap_size(g_map), 3); - - cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "other"); -} diff --git a/tests/libgit2/core/opts.c b/tests/libgit2/core/opts.c index 1aa095ad..1580fb2f 100644 --- a/tests/libgit2/core/opts.c +++ b/tests/libgit2/core/opts.c @@ -34,9 +34,11 @@ void test_core_opts__extensions_query(void) cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 2); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("noop", out.strings[0]); cl_assert_equal_s("objectformat", out.strings[1]); + cl_assert_equal_s("preciousobjects", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -49,10 +51,12 @@ void test_core_opts__extensions_add(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 3); + cl_assert_equal_sz(out.count, 5); cl_assert_equal_s("foo", out.strings[0]); cl_assert_equal_s("noop", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("preciousobjects", out.strings[3]); + cl_assert_equal_s("worktreeconfig", out.strings[4]); git_strarray_dispose(&out); } @@ -65,10 +69,12 @@ void test_core_opts__extensions_remove(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 3); + cl_assert_equal_sz(out.count, 5); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("baz", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("preciousobjects", out.strings[3]); + cl_assert_equal_s("worktreeconfig", out.strings[4]); git_strarray_dispose(&out); } @@ -81,11 +87,13 @@ void test_core_opts__extensions_uniq(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 4); + cl_assert_equal_sz(out.count, 6); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("foo", out.strings[1]); cl_assert_equal_s("noop", out.strings[2]); cl_assert_equal_s("objectformat", out.strings[3]); + cl_assert_equal_s("preciousobjects", out.strings[4]); + cl_assert_equal_s("worktreeconfig", out.strings[5]); git_strarray_dispose(&out); } diff --git a/tests/libgit2/core/useragent.c b/tests/libgit2/core/useragent.c index a4ece902..2e119de4 100644 --- a/tests/libgit2/core/useragent.c +++ b/tests/libgit2/core/useragent.c @@ -1,17 +1,52 @@ #include "clar_libgit2.h" #include "settings.h" -void test_core_useragent__get(void) +static git_buf default_ua = GIT_BUF_INIT; +static git_buf default_product = GIT_BUF_INIT; + +void test_core_useragent__initialize(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &default_ua)); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT_PRODUCT, &default_product)); +} + +void test_core_useragent__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL); + git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL); + + git_buf_dispose(&default_ua); + git_buf_dispose(&default_product); +} + +void test_core_useragent__get_default(void) +{ + cl_assert(default_ua.size); + cl_assert(default_ua.ptr); + cl_assert(git__prefixcmp(default_ua.ptr, "libgit2 ") == 0); + + cl_assert(default_product.size); + cl_assert(default_product.ptr); + cl_assert(git__prefixcmp(default_product.ptr, "git/") == 0); +} + +void test_core_useragent__set(void) { - const char *custom_name = "super duper git"; - git_str buf = GIT_STR_INIT; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "foo bar 4.24")); + cl_assert_equal_s("foo bar 4.24", git_settings__user_agent()); + cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product()); - cl_assert_equal_p(NULL, git_libgit2__user_agent()); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); - cl_assert_equal_s(custom_name, git_libgit2__user_agent()); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "baz/2.2.3")); + cl_assert_equal_s("foo bar 4.24", git_settings__user_agent()); + cl_assert_equal_s("baz/2.2.3", git_settings__user_agent_product()); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); - cl_assert_equal_s(custom_name, buf.ptr); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "")); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "")); + cl_assert_equal_s("", git_settings__user_agent()); + cl_assert_equal_s("", git_settings__user_agent_product()); - git_str_dispose(&buf); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL)); + cl_assert_equal_s(default_ua.ptr, git_settings__user_agent()); + cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product()); } diff --git a/tests/libgit2/core/version.c b/tests/libgit2/core/version.c new file mode 100644 index 00000000..9fd375c6 --- /dev/null +++ b/tests/libgit2/core/version.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" + +void test_core_version__query(void) +{ + int major, minor, rev; + + git_libgit2_version(&major, &minor, &rev); + cl_assert_equal_i(LIBGIT2_VERSION_MAJOR, major); + cl_assert_equal_i(LIBGIT2_VERSION_MINOR, minor); + cl_assert_equal_i(LIBGIT2_VERSION_REVISION, rev); +} + +void test_core_version__check(void) +{ +#if !LIBGIT2_VERSION_CHECK(1,6,3) + cl_fail("version check"); +#endif + +#if LIBGIT2_VERSION_CHECK(99,99,99) + cl_fail("version check"); +#endif +} diff --git a/tests/libgit2/date/date.c b/tests/libgit2/date/date.c index 82b5c672..b5796861 100644 --- a/tests/libgit2/date/date.c +++ b/tests/libgit2/date/date.c @@ -20,3 +20,11 @@ void test_date_date__invalid_date(void) cl_git_fail(git_date_parse(&d, "")); cl_git_fail(git_date_parse(&d, "NEITHER_INTEGER_NOR_DATETIME")); } + +void test_date_date__offset(void) +{ + git_time_t d; + int offset; + cl_git_pass(git_date_offset_parse(&d, &offset, "1970-1-1 01:00:00+03")); + cl_assert_equal_i(offset, 3*60); +} diff --git a/tests/libgit2/diff/drivers.c b/tests/libgit2/diff/drivers.c index 304a54b5..0dc28798 100644 --- a/tests/libgit2/diff/drivers.c +++ b/tests/libgit2/diff/drivers.c @@ -249,7 +249,7 @@ void test_diff_drivers__builtins(void) git_buf_dispose(&actual); git_str_dispose(&file); git_str_dispose(&expected); - git_vector_free(&files); + git_vector_dispose(&files); } void test_diff_drivers__invalid_pattern(void) diff --git a/tests/libgit2/diff/header.c b/tests/libgit2/diff/header.c new file mode 100644 index 00000000..b5b73698 --- /dev/null +++ b/tests/libgit2/diff/header.c @@ -0,0 +1,69 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "diff_helpers.h" +#include "diff.h" +#include "repository.h" + +static git_repository *g_repo = NULL; + +void test_diff_header__initialize(void) +{ +} + +void test_diff_header__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \ + "deleted file mode 100644\n" \ + "index e8ee89e..0000000\n" \ + "--- a/subdir.txt\n" \ + "+++ /dev/null\n" + +static int check_header_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + int *counter = (int *) payload; + + GIT_UNUSED(delta); + + switch (line->origin) { + case GIT_DIFF_LINE_FILE_HDR: + cl_assert(hunk == NULL); + (*counter)++; + break; + default: + /* unexpected code path */ + return -1; + } + + return 0; +} + +void test_diff_header__can_print_just_headers(void) +{ + const char *one_sha = "26a125e"; + git_tree *one; + git_diff *diff; + int counter = 0; + + g_repo = cl_git_sandbox_init("status"); + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, NULL)); + + cl_git_pass(git_diff_print( + diff, GIT_DIFF_FORMAT_PATCH_HEADER, check_header_cb, &counter)); + + cl_assert_equal_i(8, counter); + + git_diff_free(diff); + + git_tree_free(one); +} diff --git a/tests/libgit2/diff/parse.c b/tests/libgit2/diff/parse.c index 79745b99..59fc0280 100644 --- a/tests/libgit2/diff/parse.c +++ b/tests/libgit2/diff/parse.c @@ -279,6 +279,31 @@ static int file_cb(const git_diff_delta *delta, float progress, void *payload) return 0; } +void test_diff_parse__eof_nl_missing(void) +{ + const char patch[] = + "diff --git a/.env b/.env\n" + "index f89e4c0..7c56eb7 100644\n" + "--- a/.env\n" + "+++ b/.env\n" + "@@ -1 +1 @@\n" + "-hello=12345\n" + "+hello=123456\n" + "\\ No newline at end of file\n"; + git_diff *diff; + git_patch *ret_patch; + git_diff_line *line; + + cl_git_pass(diff_from_buffer(&diff, patch, strlen(patch))); + cl_git_pass(git_patch_from_diff(&ret_patch, diff, 0)); + + cl_assert((line = git_array_get(ret_patch->lines, 2)) != NULL); + cl_assert(line->origin == GIT_DIFF_LINE_DEL_EOFNL); + + git_diff_free(diff); + git_patch_free(ret_patch); +} + void test_diff_parse__foreach_works_with_parsed_patch(void) { const char patch[] = diff --git a/tests/libgit2/diff/rename.c b/tests/libgit2/diff/rename.c index 9d443946..61a2f839 100644 --- a/tests/libgit2/diff/rename.c +++ b/tests/libgit2/diff/rename.c @@ -1441,6 +1441,52 @@ void test_diff_rename__can_delete_unmodified_deltas(void) git_str_dispose(&c1); } +void test_diff_rename__can_delete_unmodified_deltas_including_submodule(void) +{ + git_repository *repo; /* "submodules" */ + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_sandbox_cleanup(); /* Don't use "renames" for this test */ + repo = cl_git_sandbox_init("submodules"); + + cl_repo_set_bool(repo, "core.autocrlf", false); + + cl_git_pass( + git_revparse_single((git_object **)&tree, repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read_tree(index, tree)); + + diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_REMOVE_UNMODIFIED; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(0, exp.files); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + cl_git_sandbox_cleanup(); +} + void test_diff_rename__matches_config_behavior(void) { const char *sha0 = INITIAL_COMMIT; diff --git a/tests/libgit2/diff/stats.c b/tests/libgit2/diff/stats.c index 7af89155..71ea7ea0 100644 --- a/tests/libgit2/diff/stats.c +++ b/tests/libgit2/diff/stats.c @@ -229,6 +229,26 @@ void test_diff_stats__rename_in_subdirectory(void) git_buf_dispose(&buf); } +void test_diff_stats__rename_in_subdirectory_aligns(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " dir/{renamed.txt => rerenamed.txt} | 0\n" + " file3.txt | 2 +-\n" + " 2 files changed, 1 insertion(+), 1 deletion(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "7f4c6d9d6ba363e3352f7c21807ca3a7835072b2", true); + + cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(1, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(1, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + void test_diff_stats__rename_no_find(void) { git_buf buf = GIT_BUF_INIT; diff --git a/tests/libgit2/diff/workdir.c b/tests/libgit2/diff/workdir.c index c433b75c..c2a0c792 100644 --- a/tests/libgit2/diff/workdir.c +++ b/tests/libgit2/diff/workdir.c @@ -2136,7 +2136,7 @@ void test_diff_workdir__to_index_pathlist(void) git_diff_free(diff); git_index_free(index); - git_vector_free(&pathlist); + git_vector_dispose(&pathlist); } void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) @@ -2198,7 +2198,7 @@ void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) cl_git_pass(git_futils_rmdir_r("symlink", NULL, GIT_RMDIR_REMOVE_FILES)); git_tree_free(tree); - git_vector_free(&pathlist); + git_vector_dispose(&pathlist); } void test_diff_workdir__order(void) @@ -2286,42 +2286,81 @@ void test_diff_workdir__to_index_reversed_content_loads(void) diff_expects exp; int use_iterator; char *pathspec = "new_file"; - + g_repo = cl_git_sandbox_init("status"); - + opts.context_lines = 3; opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT | GIT_DIFF_REVERSE; opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); - + if (use_iterator) cl_git_pass(diff_foreach_via_iterator( diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); else cl_git_pass(git_diff_foreach( diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - + cl_assert_equal_i(1, exp.files); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - + cl_assert_equal_i(1, exp.hunks); - + cl_assert_equal_i(1, exp.lines); cl_assert_equal_i(0, exp.line_ctxt); cl_assert_equal_i(0, exp.line_adds); cl_assert_equal_i(1, exp.line_dels); } - + + git_diff_free(diff); +} + +void test_diff_workdir__completely_ignored_shows_empty_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + char *pathspec = "subdir.txt"; + + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + g_repo = cl_git_sandbox_init("status"); + cl_git_rewritefile("status/subdir.txt", "Is it a bird?\n\nIs it a plane?\n"); + + /* Perform the diff normally */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s("diff --git a/subdir.txt b/subdir.txt\nindex e8ee89e..53c8db5 100644\n--- a/subdir.txt\n+++ b/subdir.txt\n@@ -1,2 +1,3 @@\n Is it a bird?\n+\n Is it a plane?\n", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + /* Perform the diff ignoring blank lines */ + opts.flags |= GIT_DIFF_IGNORE_BLANK_LINES; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s("", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); git_diff_free(diff); } diff --git a/tests/libgit2/email/create.c b/tests/libgit2/email/create.c index cf40ff55..cd3ccf70 100644 --- a/tests/libgit2/email/create.c +++ b/tests/libgit2/email/create.c @@ -1,5 +1,6 @@ #include "clar.h" #include "clar_libgit2.h" +#include "git2/sys/email.h" #include "diff_generate.h" diff --git a/tests/libgit2/fetchhead/nonetwork.c b/tests/libgit2/fetchhead/nonetwork.c index 2519d895..e92b85fa 100644 --- a/tests/libgit2/fetchhead/nonetwork.c +++ b/tests/libgit2/fetchhead/nonetwork.c @@ -106,7 +106,7 @@ void test_fetchhead_nonetwork__write(void) git_fetchhead_ref_free(fetchhead_ref); } - git_vector_free(&fetchhead_vector); + git_vector_dispose(&fetchhead_vector); cl_assert(equals); } @@ -166,7 +166,7 @@ void test_fetchhead_nonetwork__read(void) git_fetchhead_ref_free(fetchhead_ref); } - git_vector_free(&fetchhead_vector); + git_vector_dispose(&fetchhead_vector); } static int read_old_style_cb(const char *name, const char *url, diff --git a/tests/libgit2/filter/query.c b/tests/libgit2/filter/query.c index 429c1044..d57b808c 100644 --- a/tests/libgit2/filter/query.c +++ b/tests/libgit2/filter/query.c @@ -53,6 +53,18 @@ void test_filter_query__filters(void) cl_assert_equal_i(1, filter_for("id.binident", "ident")); } +void test_filter_query__filters_ignorecase(void) +{ + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + cl_skip(); + + cl_assert_equal_i(1, filter_for("TEXT.TXT", "crlf")); + cl_assert_equal_i(0, filter_for("Binary.bin", "crlf")); + + cl_assert_equal_i(1, filter_for("id.Ident", "crlf")); + cl_assert_equal_i(1, filter_for("ID.IdEnT", "ident")); +} + void test_filter_query__autocrlf_true_implies_crlf(void) { cl_repo_set_bool(g_repo, "core.autocrlf", true); diff --git a/tests/libgit2/filter/systemattrs.c b/tests/libgit2/filter/systemattrs.c index b687b4b4..5f669e35 100644 --- a/tests/libgit2/filter/systemattrs.c +++ b/tests/libgit2/filter/systemattrs.c @@ -1,21 +1,30 @@ #include "clar_libgit2.h" #include "crlf.h" #include "path.h" +#include "futils.h" static git_repository *g_repo = NULL; +static git_str orig_system_path = GIT_STR_INIT; static git_str system_attr_path = GIT_STR_INIT; void test_filter_systemattrs__initialize(void) { - git_buf system_path = GIT_BUF_INIT; + git_str new_system_path = GIT_BUF_INIT; g_repo = cl_git_sandbox_init("crlf"); cl_must_pass(p_unlink("crlf/.gitattributes")); cl_git_pass(git_libgit2_opts( - GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, &system_path)); + GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, &orig_system_path)); + + cl_git_pass(git_str_joinpath(&new_system_path, + clar_sandbox_path(), "_system_path")); + cl_git_pass(git_futils_mkdir_r(new_system_path.ptr, 0777)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, new_system_path.ptr)); + cl_git_pass(git_str_joinpath(&system_attr_path, - system_path.ptr, "gitattributes")); + new_system_path.ptr, "gitattributes")); cl_git_mkfile(system_attr_path.ptr, "*.txt text\n" @@ -23,13 +32,17 @@ void test_filter_systemattrs__initialize(void) "*.crlf text eol=crlf\n" "*.lf text eol=lf\n"); - git_buf_dispose(&system_path); + git_str_dispose(&new_system_path); } void test_filter_systemattrs__cleanup(void) { + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, orig_system_path.ptr)); + cl_must_pass(p_unlink(system_attr_path.ptr)); git_str_dispose(&system_attr_path); + git_str_dispose(&orig_system_path); cl_git_sandbox_cleanup(); } diff --git a/tests/libgit2/grafts/shallow.c b/tests/libgit2/grafts/shallow.c index 5911a26a..349e9e9b 100644 --- a/tests/libgit2/grafts/shallow.c +++ b/tests/libgit2/grafts/shallow.c @@ -40,7 +40,7 @@ void test_grafts_shallow__clears_errors(void) { g_repo = cl_git_sandbox_init("testrepo.git"); cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); } void test_grafts_shallow__shallow_oids(void) @@ -110,8 +110,8 @@ void test_grafts_shallow__revwalk_behavior(void) cl_git_pass(git_revwalk_new(&w, g_repo)); cl_git_pass(git_revwalk_push_head(w)); - cl_git_pass(git_revwalk_next(&oid_1, w)); // a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - cl_git_pass(git_revwalk_next(&oid_2, w)); // be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + cl_git_pass(git_revwalk_next(&oid_1, w)); /* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 */ + cl_git_pass(git_revwalk_next(&oid_2, w)); /* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 */ cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid_3, w)); cl_assert_equal_s(git_oid_tostr_s(&oid_1), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); diff --git a/tests/libgit2/graph/commitgraph.c b/tests/libgit2/graph/commitgraph.c index 53869d61..363806bd 100644 --- a/tests/libgit2/graph/commitgraph.c +++ b/tests/libgit2/graph/commitgraph.c @@ -105,18 +105,19 @@ void test_graph_commitgraph__writer(void) cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/info")); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_commit_graph_writer_new(&w, git_str_cstr(&path), GIT_OID_SHA1)); -#else - cl_git_pass(git_commit_graph_writer_new(&w, git_str_cstr(&path))); + opts.oid_type = GIT_OID_SHA1; #endif + cl_git_pass(git_commit_graph_writer_new(&w, git_str_cstr(&path), &opts)); + /* This is equivalent to `git commit-graph write --reachable`. */ cl_git_pass(git_revwalk_new(&walk, repo)); cl_git_pass(git_revwalk_push_glob(walk, "refs/*")); cl_git_pass(git_commit_graph_writer_add_revwalk(w, walk)); git_revwalk_free(walk); - cl_git_pass(git_commit_graph_writer_dump(&cgraph, w, &opts)); + cl_git_pass(git_commit_graph_writer_dump(&cgraph, w)); + cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/info/commit-graph")); cl_git_pass(git_futils_readbuffer(&expected_cgraph, git_str_cstr(&path))); @@ -136,12 +137,16 @@ void test_graph_commitgraph__validate(void) struct git_commit_graph *cgraph; git_str objects_dir = GIT_STR_INIT; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_commit_graph_open_options opts = GIT_COMMIT_GRAPH_OPEN_OPTIONS_INIT; +#endif + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); cl_git_pass(git_str_joinpath(&objects_dir, git_repository_path(repo), "objects")); /* git_commit_graph_open() calls git_commit_graph_validate() */ #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_commit_graph_open(&cgraph, git_str_cstr(&objects_dir), GIT_OID_SHA1)); + cl_git_pass(git_commit_graph_open(&cgraph, git_str_cstr(&objects_dir), &opts)); #else cl_git_pass(git_commit_graph_open(&cgraph, git_str_cstr(&objects_dir))); #endif @@ -157,6 +162,10 @@ void test_graph_commitgraph__validate_corrupt(void) struct git_commit_graph *cgraph; int fd = -1; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_commit_graph_open_options opts = GIT_COMMIT_GRAPH_OPEN_OPTIONS_INIT; +#endif + cl_fixture_sandbox("testrepo.git"); cl_git_pass(git_repository_open(&repo, cl_git_sandbox_path(1, "testrepo.git", NULL))); @@ -168,7 +177,7 @@ void test_graph_commitgraph__validate_corrupt(void) /* git_commit_graph_open() calls git_commit_graph_validate() */ #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_fail(git_commit_graph_open(&cgraph, cl_git_sandbox_path(1, "testrepo.git", "objects", NULL), GIT_OID_SHA1)); + cl_git_fail(git_commit_graph_open(&cgraph, cl_git_sandbox_path(1, "testrepo.git", "objects", NULL), &opts)); #else cl_git_fail(git_commit_graph_open(&cgraph, cl_git_sandbox_path(1, "testrepo.git", "objects", NULL))); #endif diff --git a/tests/libgit2/graph/reachable_from_any.c b/tests/libgit2/graph/reachable_from_any.c index 8e1c2387..04706fbb 100644 --- a/tests/libgit2/graph/reachable_from_any.c +++ b/tests/libgit2/graph/reachable_from_any.c @@ -231,6 +231,6 @@ void test_graph_reachable_from_any__exhaustive(void) git_vector_foreach (&mc.commits, child_idx, child_commit) git_commit_free(child_commit); git_bitvec_free(&reachable); - git_vector_free(&mc.commits); + git_vector_dispose(&mc.commits); git_odb_free(mc.db); } diff --git a/tests/libgit2/index/crlf.c b/tests/libgit2/index/crlf.c index 666ac1a0..0a7d51ae 100644 --- a/tests/libgit2/index/crlf.c +++ b/tests/libgit2/index/crlf.c @@ -190,7 +190,7 @@ static void set_up_workingdir(const char *name) continue; p_unlink(fn); } - git_vector_free_deep(&contents); + git_vector_dispose_deep(&contents); /* copy input files */ git_fs_path_dirload(&contents, cl_fixture("crlf"), 0, 0); @@ -207,7 +207,7 @@ static void set_up_workingdir(const char *name) git__free(basename); git_str_dispose(&dest_filename); } - git_vector_free_deep(&contents); + git_vector_dispose_deep(&contents); } void test_index_crlf__matches_core_git(void) diff --git a/tests/libgit2/index/tests256.c b/tests/libgit2/index/tests256.c index fed8bfb9..97246ce4 100644 --- a/tests/libgit2/index/tests256.c +++ b/tests/libgit2/index/tests256.c @@ -678,7 +678,7 @@ void test_index_tests256__write_tree_invalid_unowned_index(void) cl_git_pass(git_index__new(&idx, GIT_OID_SHA256)); - // TODO: this one is failing + /* TODO: this one is failing */ cl_git_pass(git_oid__fromstr(&entry.id, "a8c2e0a89a9cbab77c732b6bc39b51a783e3a318a847f46cba7614cac9814291", GIT_OID_SHA256)); entry.path = "foo"; entry.mode = GIT_FILEMODE_BLOB; diff --git a/tests/libgit2/iterator/index.c b/tests/libgit2/iterator/index.c index a0083479..e5ebec60 100644 --- a/tests/libgit2/iterator/index.c +++ b/tests/libgit2/iterator/index.c @@ -627,7 +627,7 @@ void test_iterator_index__pathlist(void) } git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_with_dirs(void) @@ -728,7 +728,7 @@ void test_iterator_index__pathlist_with_dirs(void) } git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_with_dirs_include_trees(void) @@ -759,7 +759,7 @@ void test_iterator_index__pathlist_with_dirs_include_trees(void) git_iterator_free(i); git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_1(void) @@ -799,7 +799,7 @@ void test_iterator_index__pathlist_1(void) git_iterator_free(i); git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_2(void) @@ -841,7 +841,7 @@ void test_iterator_index__pathlist_2(void) git_iterator_free(i); git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_four(void) @@ -883,7 +883,7 @@ void test_iterator_index__pathlist_four(void) git_iterator_free(i); git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_icase(void) @@ -946,7 +946,7 @@ void test_iterator_index__pathlist_icase(void) cl_git_pass(git_index_set_caps(index, caps)); git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__pathlist_with_directory(void) @@ -973,7 +973,7 @@ void test_iterator_index__pathlist_with_directory(void) git_index_free(index); git_tree_free(tree); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } static void create_paths(git_index *index, const char *root, int depth) @@ -1131,7 +1131,7 @@ void test_iterator_index__pathlist_for_deeply_nested_item(void) } git_index_free(index); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_index__advance_over(void) diff --git a/tests/libgit2/iterator/tree.c b/tests/libgit2/iterator/tree.c index 7dfee28b..81035bb8 100644 --- a/tests/libgit2/iterator/tree.c +++ b/tests/libgit2/iterator/tree.c @@ -911,7 +911,7 @@ void test_iterator_tree__pathlist(void) expect_iterator_items(i, expect, NULL, expect, NULL); git_iterator_free(i); - git_vector_free(&filelist); + git_vector_dispose(&filelist); git_tree_free(tree); } @@ -968,7 +968,7 @@ void test_iterator_tree__pathlist_icase(void) expect_iterator_items(i, 2, NULL, 2, NULL); git_iterator_free(i); - git_vector_free(&filelist); + git_vector_dispose(&filelist); git_tree_free(tree); } @@ -1021,7 +1021,7 @@ void test_iterator_tree__pathlist_with_directory(void) git_iterator_free(i); git_tree_free(tree); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_tree__pathlist_with_directory_include_tree_nodes(void) @@ -1050,7 +1050,7 @@ void test_iterator_tree__pathlist_with_directory_include_tree_nodes(void) git_iterator_free(i); git_tree_free(tree); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_tree__pathlist_no_match(void) @@ -1075,6 +1075,6 @@ void test_iterator_tree__pathlist_no_match(void) git_iterator_free(i); git_tree_free(tree); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } diff --git a/tests/libgit2/iterator/workdir.c b/tests/libgit2/iterator/workdir.c index 7634997e..327f1f65 100644 --- a/tests/libgit2/iterator/workdir.c +++ b/tests/libgit2/iterator/workdir.c @@ -585,9 +585,9 @@ void test_iterator_workdir__filesystem(void) expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); git_iterator_free(i); - git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); + git__tsort((void **)expect_base, 8, git__strcasecmp_cb); + git__tsort((void **)expect_trees, 18, git__strcasecmp_cb); + git__tsort((void **)expect_noauto, 5, git__strcasecmp_cb); i_opts.flags = GIT_ITERATOR_IGNORE_CASE; cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); @@ -917,7 +917,7 @@ void test_iterator_workdir__pathlist(void) git_iterator_free(i); } - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_workdir__pathlist_with_dirs(void) @@ -1014,7 +1014,7 @@ void test_iterator_workdir__pathlist_with_dirs(void) git_iterator_free(i); } - git_vector_free(&filelist); + git_vector_dispose(&filelist); } static void create_paths(const char *root, int depth) @@ -1175,7 +1175,7 @@ void test_iterator_workdir__pathlist_for_deeply_nested_item(void) git_iterator_free(i); } - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_workdir__bounded_submodules(void) @@ -1258,7 +1258,7 @@ void test_iterator_workdir__bounded_submodules(void) git_iterator_free(i); } - git_vector_free(&filelist); + git_vector_dispose(&filelist); git_index_free(index); git_tree_free(head); } @@ -1380,7 +1380,7 @@ void test_iterator_workdir__advance_over_with_pathlist(void) cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); git_iterator_free(i); - git_vector_free(&pathlist); + git_vector_dispose(&pathlist); } void test_iterator_workdir__advance_into(void) @@ -1447,7 +1447,7 @@ void test_iterator_workdir__pathlist_with_directory(void) expect_iterator_items(i, expected_len, expected, expected_len, expected); git_iterator_free(i); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_workdir__pathlist_with_directory_include_trees(void) @@ -1473,7 +1473,7 @@ void test_iterator_workdir__pathlist_with_directory_include_trees(void) expect_iterator_items(i, expected_len, expected, expected_len, expected); git_iterator_free(i); - git_vector_free(&filelist); + git_vector_dispose(&filelist); } void test_iterator_workdir__hash_when_requested(void) diff --git a/tests/libgit2/mailmap/blame.c b/tests/libgit2/mailmap/blame.c index e6bc1a41..ce2c9d48 100644 --- a/tests/libgit2/mailmap/blame.c +++ b/tests/libgit2/mailmap/blame.c @@ -33,7 +33,7 @@ void test_mailmap_blame__hunks(void) cl_assert(g_blame); for (idx = 0; idx < ARRAY_SIZE(resolved); ++idx) { - hunk = git_blame_get_hunk_byline(g_blame, idx + 1); + hunk = git_blame_hunk_byline(g_blame, idx + 1); cl_assert(hunk->final_signature != NULL); cl_assert(hunk->orig_signature != NULL); @@ -54,7 +54,7 @@ void test_mailmap_blame__hunks_no_mailmap(void) cl_assert(g_blame); for (idx = 0; idx < ARRAY_SIZE(resolved); ++idx) { - hunk = git_blame_get_hunk_byline(g_blame, idx + 1); + hunk = git_blame_hunk_byline(g_blame, idx + 1); cl_assert(hunk->final_signature != NULL); cl_assert(hunk->orig_signature != NULL); diff --git a/tests/libgit2/merge/trees/renames.c b/tests/libgit2/merge/trees/renames.c index a27945ee..9507b51b 100644 --- a/tests/libgit2/merge/trees/renames.c +++ b/tests/libgit2/merge/trees/renames.c @@ -350,3 +350,22 @@ void test_merge_trees_renames__cache_recomputation(void) git_tree_free(our_tree); git__free(data); } + +void test_merge_trees_renames__emptyfile_renames(void) +{ + git_index *index; + git_merge_options *opts = NULL; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 1, "bar" }, + { 0100644, "60b12be2d2f57977ce83d8dfd32e2394ac1ba1a2", 3, "bar" }, + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 0, "boo" }, + { 0100644, "e50a49f9558d09d4d3bfc108363bb24c127ed263", 0, "foo" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, + "emptyfile_renames", "emptyfile_renames-branch", + opts)); + cl_assert(merge_test_index(index, merge_index_entries, 4)); + git_index_free(index); +} diff --git a/tests/libgit2/merge/workdir/dirty.c b/tests/libgit2/merge/workdir/dirty.c index 36b42a10..570e7c75 100644 --- a/tests/libgit2/merge/workdir/dirty.c +++ b/tests/libgit2/merge/workdir/dirty.c @@ -97,7 +97,6 @@ static int merge_branch(void) cl_git_pass(git_oid__fromstr(&their_oids[0], MERGE_BRANCH_OID, GIT_OID_SHA1)); cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_oids[0])); - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; error = git_merge(repo, (const git_annotated_commit **)&their_head, 1, &merge_opts, &checkout_opts); git_annotated_commit_free(their_head); diff --git a/tests/libgit2/merge/workdir/renames.c b/tests/libgit2/merge/workdir/renames.c index 1b5128cf..90f4b691 100644 --- a/tests/libgit2/merge/workdir/renames.c +++ b/tests/libgit2/merge/workdir/renames.c @@ -100,7 +100,7 @@ void test_merge_workdir_renames__ours(void) merge_opts.flags |= GIT_MERGE_FIND_RENAMES; merge_opts.rename_threshold = 50; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + checkout_opts.checkout_strategy = GIT_CHECKOUT_USE_OURS; cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, &checkout_opts)); cl_git_pass(git_repository_index(&index, repo)); diff --git a/tests/libgit2/merge/workdir/simple.c b/tests/libgit2/merge/workdir/simple.c index e9cffeea..17faabff 100644 --- a/tests/libgit2/merge/workdir/simple.c +++ b/tests/libgit2/merge/workdir/simple.c @@ -103,7 +103,7 @@ static void merge_simple_branch(int merge_file_favor, int addl_checkout_strategy cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); merge_opts.file_favor = merge_file_favor; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS | + checkout_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS | addl_checkout_strategy; cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, &checkout_opts)); @@ -577,7 +577,7 @@ void test_merge_workdir_simple__checkout_ours(void) REMOVED_IN_MASTER_REUC_ENTRY }; - merge_simple_branch(0, GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS); + merge_simple_branch(0, GIT_CHECKOUT_USE_OURS); cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); diff --git a/tests/libgit2/message/trailer.c b/tests/libgit2/message/trailer.c index 919e10a4..09e8f611 100644 --- a/tests/libgit2/message/trailer.c +++ b/tests/libgit2/message/trailer.c @@ -3,19 +3,22 @@ static void assert_trailers(const char *message, git_message_trailer *trailers) { git_message_trailer_array arr; - size_t i; + size_t i, count; int rc = git_message_trailers(&arr, message); cl_assert_equal_i(0, rc); - for(i=0; i 0); + + git_remote_free(remote); + git_repository_free(repo); +#endif +} + +void test_network_fetchlocal__update_refs_is_preferred(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_repository *repo; + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + int callcount = 0; + + cl_git_pass(git_repository_init(&repo, "foo.git", true)); + cl_set_cleanup(cleanup_local_repo, "foo.git"); + + cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"), "+refs/heads/*:refs/remotes/update-tips/*")); + + options.callbacks.update_tips = update_tips; + options.callbacks.update_refs = update_refs; + options.callbacks.payload = &callcount; + + cl_git_pass(git_remote_fetch(remote, NULL, &options, NULL)); + cl_assert_equal_i(0, (callcount & 0x0000ffff)); + cl_assert((callcount & 0xffff0000) > 0); + + git_remote_free(remote); + git_repository_free(repo); +#endif +} diff --git a/tests/libgit2/network/remote/local.c b/tests/libgit2/network/remote/local.c index 2007f377..9f3c8b61 100644 --- a/tests/libgit2/network/remote/local.c +++ b/tests/libgit2/network/remote/local.c @@ -61,7 +61,7 @@ void test_network_remote_local__retrieve_advertised_references(void) cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(refs_len, 30); + cl_assert_equal_i(refs_len, 31); } void test_network_remote_local__retrieve_advertised_before_connect(void) @@ -85,7 +85,7 @@ void test_network_remote_local__retrieve_advertised_references_after_disconnect( cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(refs_len, 30); + cl_assert_equal_i(refs_len, 31); } void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void) @@ -100,7 +100,7 @@ void test_network_remote_local__retrieve_advertised_references_from_spaced_repos cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(refs_len, 30); + cl_assert_equal_i(refs_len, 31); git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ remote = NULL; @@ -471,9 +471,17 @@ void test_network_remote_local__anonymous_remote_inmemory_repo(void) git_repository *inmemory; git_remote *remote; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_repository_new_options repo_opts = GIT_REPOSITORY_NEW_OPTIONS_INIT; +#endif + git_str_sets(&file_path_buf, cl_git_path_url(cl_fixture("testrepo.git"))); +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_repository_new(&inmemory, &repo_opts)); +#else cl_git_pass(git_repository_new(&inmemory)); +#endif cl_git_pass(git_remote_create_anonymous(&remote, inmemory, git_str_cstr(&file_path_buf))); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_assert(git_remote_connected(remote)); diff --git a/tests/libgit2/network/remote/rename.c b/tests/libgit2/network/remote/rename.c index b071f9ce..e02a4ce3 100644 --- a/tests/libgit2/network/remote/rename.c +++ b/tests/libgit2/network/remote/rename.c @@ -216,7 +216,7 @@ void test_network_remote_rename__symref_head(void) cl_assert_equal_i(0, problems.count); git_strarray_dispose(&problems); - cl_git_pass(git_vector_init(&refs, 2, (git_vector_cmp) git_reference_cmp)); + cl_git_pass(git_vector_init(&refs, 2, git_reference__cmp_cb)); cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); while ((error = git_branch_next(&ref, &btype, iter)) == 0) { @@ -238,7 +238,7 @@ void test_network_remote_rename__symref_head(void) cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); git_reference_free(ref); - git_vector_free(&refs); + git_vector_dispose(&refs); cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); git_branch_iterator_free(iter); diff --git a/tests/libgit2/network/remote/tag.c b/tests/libgit2/network/remote/tag.c new file mode 100644 index 00000000..7a166a94 --- /dev/null +++ b/tests/libgit2/network/remote/tag.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "git2/sys/commit.h" + +static git_remote *_remote; +static git_repository *_repo, *_dummy; + +void test_network_remote_tag__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + git_repository_open(&_repo, "testrepo.git"); + + /* We need a repository to have a remote */ + cl_git_pass(git_repository_init(&_dummy, "dummytag.git", true)); + cl_git_pass(git_remote_create(&_remote, _dummy, "origin", cl_git_path_url("testrepo.git"))); +} + +void test_network_remote_tag__cleanup(void) +{ + git_remote_free(_remote); + _remote = NULL; + + git_repository_free(_repo); + _repo = NULL; + + git_repository_free(_dummy); + _dummy = NULL; + + cl_fixture_cleanup("testrepo.git"); + cl_fixture_cleanup("dummytag.git"); +} + +/* + * Create one commit, one tree, one blob. + * Create two tags: one for the commit, one for the blob. + */ +static void create_commit_with_tags(git_reference **out, git_oid *out_commit_tag_id, git_oid *out_blob_tag_id, git_repository *repo) +{ + git_treebuilder *treebuilder; + git_oid blob_id, tree_id, commit_id; + git_signature *sig; + git_object *target; + + cl_git_pass(git_treebuilder_new(&treebuilder, repo, NULL)); + + cl_git_pass(git_blob_create_from_buffer(&blob_id, repo, "", 0)); + cl_git_pass(git_treebuilder_insert(NULL, treebuilder, "README.md", &blob_id, 0100644)); + cl_git_pass(git_treebuilder_write(&tree_id, treebuilder)); + + cl_git_pass(git_signature_now(&sig, "Pusher Joe", "pjoe")); + cl_git_pass(git_commit_create_from_ids(&commit_id, repo, NULL, sig, sig, + NULL, "Tree with tags\n", &tree_id, 0, NULL)); + cl_git_pass(git_reference_create(out, repo, "refs/heads/tree-with-tags", &commit_id, true, "commit yo")); + + cl_git_pass(git_object_lookup(&target, repo, &commit_id, GIT_OBJECT_COMMIT)); + cl_git_pass(git_tag_create_lightweight(out_commit_tag_id, repo, "tagged-commit", target, true)); + git_object_free(target); + + cl_git_pass(git_object_lookup(&target, repo, &blob_id, GIT_OBJECT_BLOB)); + cl_git_pass(git_tag_create_lightweight(out_blob_tag_id, repo, "tagged-blob", target, true)); + git_object_free(target); + + git_treebuilder_free(treebuilder); + git_signature_free(sig); +} + +void test_network_remote_tag__push_different_tag_types(void) +{ + git_push_options opts = GIT_PUSH_OPTIONS_INIT; + git_reference *ref; + git_oid commit_tag_id, blob_tag_id; + char* refspec_tree = "refs/heads/tree-with-tags"; + char* refspec_tagged_commit = "refs/tags/tagged-commit"; + char* refspec_tagged_blob = "refs/tags/tagged-blob"; + const git_strarray refspecs_tree = { &refspec_tree, 1 }; + const git_strarray refspecs_tagged_commit = { &refspec_tagged_commit, 1 }; + const git_strarray refspecs_tagged_blob = { &refspec_tagged_blob, 1 }; + + create_commit_with_tags(&ref, &commit_tag_id, &blob_tag_id, _dummy); + + /* Push tree */ + cl_git_pass(git_remote_push(_remote, &refspecs_tree, &opts)); + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/tree-with-tags")); + git_reference_free(ref); + + /* Push tagged commit */ + cl_git_pass(git_remote_push(_remote, &refspecs_tagged_commit, &opts)); + cl_git_pass(git_reference_name_to_id(&commit_tag_id, _repo, "refs/tags/tagged-commit")); + + /* Push tagged blob */ + cl_git_pass(git_remote_push(_remote, &refspecs_tagged_blob, &opts)); + cl_git_pass(git_reference_name_to_id(&blob_tag_id, _repo, "refs/tags/tagged-blob")); +} diff --git a/tests/libgit2/object/shortid.c b/tests/libgit2/object/shortid.c index 69fceeed..3657a419 100644 --- a/tests/libgit2/object/shortid.c +++ b/tests/libgit2/object/shortid.c @@ -4,13 +4,12 @@ git_repository *_repo; void test_object_shortid__initialize(void) { - cl_git_pass(git_repository_open(&_repo, cl_fixture("duplicate.git"))); + _repo = cl_git_sandbox_init("duplicate.git"); } void test_object_shortid__cleanup(void) { - git_repository_free(_repo); - _repo = NULL; + cl_git_sandbox_cleanup(); } void test_object_shortid__select(void) @@ -49,3 +48,53 @@ void test_object_shortid__select(void) git_buf_dispose(&shorty); } + +void test_object_shortid__core_abbrev(void) +{ + git_oid full; + git_object *obj; + git_buf shorty = {0}; + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, _repo)); + git_oid__fromstr(&full, "ce013625030ba8dba906f756967f9e9ca394464a", GIT_OID_SHA1); + cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); + + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "auto")); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(7, shorty.size); + cl_assert_equal_s("ce01362", shorty.ptr); + + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "off")); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(40, shorty.size); + cl_assert_equal_s("ce013625030ba8dba906f756967f9e9ca394464a", shorty.ptr); + + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "false")); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(40, shorty.size); + cl_assert_equal_s("ce013625030ba8dba906f756967f9e9ca394464a", shorty.ptr); + + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "99")); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(40, shorty.size); + cl_assert_equal_s("ce013625030ba8dba906f756967f9e9ca394464a", shorty.ptr); + + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "4")); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(4, shorty.size); + cl_assert_equal_s("ce01", shorty.ptr); + + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "0")); + cl_git_fail(git_object_short_id(&shorty, obj)); + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "3")); + cl_git_fail(git_object_short_id(&shorty, obj)); + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "invalid")); + cl_git_fail(git_object_short_id(&shorty, obj)); + cl_git_pass(git_config_set_string(cfg, "core.abbrev", "true")); + cl_git_fail(git_object_short_id(&shorty, obj)); + + git_object_free(obj); + git_buf_dispose(&shorty); + git_config_free(cfg); +} diff --git a/tests/libgit2/odb/alternates.c b/tests/libgit2/odb/alternates.c index 4d2da6b1..0278f449 100644 --- a/tests/libgit2/odb/alternates.c +++ b/tests/libgit2/odb/alternates.c @@ -78,3 +78,39 @@ void test_odb_alternates__long_chain(void) cl_git_fail(git_commit_lookup(&commit, repo, &oid)); git_repository_free(repo); } + +void test_odb_alternates__relative(void) +{ + git_commit *commit; + git_oid oid; + + /* Set the alternate A -> testrepo.git */ + init_linked_repo(paths[0], cl_fixture("testrepo.git")); + + /* Set the alternate B -> A */ + init_linked_repo(paths[1], paths[0]); + /* Set the alternate C -> B */ + init_linked_repo(paths[2], paths[1]); + + /* Use a relative alternates path for B -> A */ + cl_git_pass(git_fs_path_prettify(&filepath, paths[1], NULL)); + cl_git_pass(git_str_joinpath(&filepath, filepath.ptr, "objects/info/alternates")); + + cl_git_pass(git_filebuf_open(&file, git_str_cstr(&filepath), 0, 0666)); + git_filebuf_printf(&file, "../../%s/objects\n", paths[0]); + cl_git_pass(git_filebuf_commit(&file)); + + /* Now load B and see if we can find an object from testrepo.git */ + cl_git_pass(git_repository_open(&repo, paths[1])); + git_oid__fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OID_SHA1); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + git_commit_free(commit); + git_repository_free(repo); + + /* Now load C and see if we can find an object from testrepo.git */ + cl_git_pass(git_repository_open(&repo, paths[2])); + git_oid__fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OID_SHA1); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + git_commit_free(commit); + git_repository_free(repo); +} diff --git a/tests/libgit2/odb/backend/loose.c b/tests/libgit2/odb/backend/loose.c index 02227945..f5a0b4f5 100644 --- a/tests/libgit2/odb/backend/loose.c +++ b/tests/libgit2/odb/backend/loose.c @@ -21,7 +21,7 @@ void test_odb_backend_loose__initialize(void) cl_git_pass(git_odb__new(&_odb, NULL)); cl_git_pass(git_odb_add_backend(_odb, backend, 10)); - cl_git_pass(git_repository__wrap_odb(&_repo, _odb, GIT_OID_SHA1)); + cl_git_pass(git_repository_wrap_odb(&_repo, _odb)); } void test_odb_backend_loose__cleanup(void) diff --git a/tests/libgit2/odb/backend/mempack.c b/tests/libgit2/odb/backend/mempack.c index c8a86a2a..462a1d33 100644 --- a/tests/libgit2/odb/backend/mempack.c +++ b/tests/libgit2/odb/backend/mempack.c @@ -8,15 +8,14 @@ static git_odb *_odb; static git_oid _oid; static git_odb_object *_obj; static git_repository *_repo; +static git_odb_backend *_backend; void test_odb_backend_mempack__initialize(void) { - git_odb_backend *backend; - - cl_git_pass(git_mempack_new(&backend)); + cl_git_pass(git_mempack_new(&_backend)); cl_git_pass(git_odb__new(&_odb, NULL)); - cl_git_pass(git_odb_add_backend(_odb, backend, 10)); - cl_git_pass(git_repository__wrap_odb(&_repo, _odb, GIT_OID_SHA1)); + cl_git_pass(git_odb_add_backend(_odb, _backend, 10)); + cl_git_pass(git_repository_wrap_odb(&_repo, _odb)); } void test_odb_backend_mempack__cleanup(void) @@ -59,3 +58,25 @@ void test_odb_backend_mempack__blob_create_from_buffer_succeeds(void) cl_git_pass(git_blob_create_from_buffer(&_oid, _repo, data, strlen(data) + 1)); cl_assert(git_odb_exists(_odb, &_oid) == 1); } + +void test_odb_backend_mempack__empty_object_count_succeeds(void) +{ + size_t count; + cl_git_pass(git_mempack_object_count(&count, _backend)); + cl_assert_equal_sz(0, count); +} + +void test_odb_backend_mempack__object_count_succeeds(void) +{ + const char *data = "data"; + size_t count; + cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJECT_BLOB)); + cl_git_pass(git_mempack_object_count(&count, _backend)); + cl_assert_equal_sz(1, count); +} + +void test_odb_backend_mempack__object_count_fails(void) +{ + size_t count; + cl_git_fail_with(-1, git_mempack_object_count(&count, 0)); +} diff --git a/tests/libgit2/odb/backend/nobackend.c b/tests/libgit2/odb/backend/nobackend.c index 7d9394c6..e8af9a6d 100644 --- a/tests/libgit2/odb/backend/nobackend.c +++ b/tests/libgit2/odb/backend/nobackend.c @@ -11,7 +11,15 @@ void test_odb_backend_nobackend__initialize(void) git_odb *odb; git_refdb *refdb; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_repository_new_options repo_opts = GIT_REPOSITORY_NEW_OPTIONS_INIT; + + repo_opts.oid_type = GIT_OID_SHA1; + + cl_git_pass(git_repository_new(&_repo, &repo_opts)); +#else cl_git_pass(git_repository_new(&_repo)); +#endif cl_git_pass(git_config_new(&config)); cl_git_pass(git_odb__new(&odb, NULL)); cl_git_pass(git_refdb_new(&refdb, _repo)); diff --git a/tests/libgit2/odb/sorting.c b/tests/libgit2/odb/sorting.c index ec4e3696..3fe52e85 100644 --- a/tests/libgit2/odb/sorting.c +++ b/tests/libgit2/odb/sorting.c @@ -7,6 +7,11 @@ typedef struct { size_t position; } fake_backend; +static void odb_backend_free(git_odb_backend *odb) +{ + git__free(odb); +} + static git_odb_backend *new_backend(size_t position) { fake_backend *b; @@ -15,7 +20,7 @@ static git_odb_backend *new_backend(size_t position) if (b == NULL) return NULL; - b->base.free = (void (*)(git_odb_backend *)) git__free; + b->base.free = odb_backend_free; b->base.version = GIT_ODB_BACKEND_VERSION; b->position = position; return (git_odb_backend *)b; diff --git a/tests/libgit2/online/badssl.c b/tests/libgit2/online/badssl.c index 6735e9cd..7169d59f 100644 --- a/tests/libgit2/online/badssl.c +++ b/tests/libgit2/online/badssl.c @@ -73,3 +73,11 @@ void test_online_badssl__old_cipher(void) cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", NULL)); cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", &opts)); } + +void test_online_badssl__sslv3(void) +{ + if (!g_has_ssl) + cl_skip(); + + cl_git_fail(git_clone(&g_repo, "https://mailserv.baehal.com/fake.git", "./fake", NULL)); +} diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c index 5789e965..9e0923aa 100644 --- a/tests/libgit2/online/clone.c +++ b/tests/libgit2/online/clone.c @@ -7,6 +7,7 @@ #include "refs.h" #define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" +#define LIVE_REPO_AS_DIR "http:/github.com/libgit2/TestGitRepository" #define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository" #define BB_REPO_URL "https://libgit2-test@bitbucket.org/libgit2-test/testgitrepository.git" #define BB_REPO_URL_WITH_PASS "https://libgit2-test:YT77Ppm2nq8w4TYjGS8U@bitbucket.org/libgit2-test/testgitrepository.git" @@ -47,6 +48,9 @@ static char *_orig_http_proxy = NULL; static char *_orig_https_proxy = NULL; static char *_orig_no_proxy = NULL; +static char *_ssh_cmd = NULL; +static char *_orig_ssh_cmd = NULL; + static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload) { GIT_UNUSED(cert); @@ -69,7 +73,6 @@ void test_online_clone__initialize(void) memset(&g_options, 0, sizeof(git_clone_options)); g_options.version = GIT_CLONE_OPTIONS_VERSION; g_options.checkout_opts = dummy_opts; - g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; g_options.fetch_opts = dummy_fetch; g_options.fetch_opts.callbacks.certificate_check = ssl_cert; @@ -102,8 +105,26 @@ void test_online_clone__initialize(void) _orig_https_proxy = cl_getenv("HTTPS_PROXY"); _orig_no_proxy = cl_getenv("NO_PROXY"); + _orig_ssh_cmd = cl_getenv("GIT_SSH_COMMAND"); + _ssh_cmd = cl_getenv("GITTEST_SSH_CMD"); + + if (_ssh_cmd) + cl_setenv("GIT_SSH_COMMAND", _ssh_cmd); + else + cl_setenv("GIT_SSH_COMMAND", NULL); + if (_remote_expectcontinue) git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); + +#if !defined(GIT_WIN32) + /* + * On system that allows ':' in filenames "http://path" can be misinterpreted + * as the local path "http:/path". + * Create a local non-repository path that looks like LIVE_REPO_URL to make + * sure we can handle cloning despite this directory being around. + */ + git_futils_mkdir_r(LIVE_REPO_AS_DIR, 0777); +#endif } void test_online_clone__cleanup(void) @@ -116,6 +137,10 @@ void test_online_clone__cleanup(void) cl_fixture_cleanup("./initial"); cl_fixture_cleanup("./subsequent"); +#if !defined(GIT_WIN32) + cl_fixture_cleanup("http:"); +#endif + git__free(_remote_url); git__free(_remote_user); git__free(_remote_pass); @@ -149,6 +174,11 @@ void test_online_clone__cleanup(void) git__free(_orig_https_proxy); git__free(_orig_no_proxy); + cl_setenv("GIT_SSH_COMMAND", _orig_ssh_cmd); + git__free(_orig_ssh_cmd); + + git__free(_ssh_cmd); + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL); git_libgit2_opts(GIT_OPT_SET_SERVER_TIMEOUT, 0); git_libgit2_opts(GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, 0); @@ -218,7 +248,6 @@ void test_online_clone__can_checkout_a_cloned_repo(void) bool checkout_progress_cb_was_called = false, fetch_progress_cb_was_called = false; - g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; g_options.checkout_opts.progress_cb = &checkout_progress; g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called; g_options.fetch_opts.callbacks.transfer_progress = &fetch_progress; @@ -288,10 +317,10 @@ void test_online_clone__clone_mirror(void) cl_fixture_cleanup("./foo.git"); } -static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload) +static int update_refs(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *payload) { int *callcount = (int*)payload; - GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); + GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(spec); *callcount = *callcount + 1; return 0; } @@ -300,7 +329,7 @@ void test_online_clone__custom_remote_callbacks(void) { int callcount = 0; - g_options.fetch_opts.callbacks.update_tips = update_tips; + g_options.fetch_opts.callbacks.update_refs = update_refs; g_options.fetch_opts.callbacks.payload = &callcount; cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); @@ -634,7 +663,7 @@ static int github_credentials( void test_online_clone__ssh_github(void) { -#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS) +#if !defined(GIT_SSH) || !defined(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS) clar__skip(); #endif @@ -649,11 +678,29 @@ void test_online_clone__ssh_github(void) cl_git_pass(git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options)); } +void test_online_clone__ssh_github_shallow(void) +{ +#if !defined(GIT_SSH) || !defined(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS) + clar__skip(); +#endif + + if (!_github_ssh_pubkey || !_github_ssh_privkey) + clar__skip(); + + cl_fake_homedir(NULL); + + g_options.fetch_opts.callbacks.credentials = github_credentials; + g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check; + g_options.fetch_opts.depth = 1; + + cl_git_pass(git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options)); +} + void test_online_clone__ssh_auth_methods(void) { int with_user; -#ifndef GIT_SSH +#ifndef GIT_SSH_LIBSSH2 clar__skip(); #endif g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods; @@ -675,7 +722,7 @@ void test_online_clone__ssh_auth_methods(void) */ void test_online_clone__ssh_certcheck_accepts_unknown(void) { -#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS) +#if !defined(GIT_SSH_LIBSSH2) || !defined(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS) clar__skip(); #endif @@ -704,7 +751,7 @@ void test_online_clone__ssh_certcheck_override_knownhosts(void) { git_str knownhostsfile = GIT_STR_INIT; -#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS) +#if !defined(GIT_SSH) || !defined(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS) clar__skip(); #endif @@ -793,9 +840,10 @@ static int cred_foo_bar(git_credential **cred, const char *url, const char *user void test_online_clone__ssh_cannot_change_username(void) { -#ifndef GIT_SSH +#ifndef GIT_SSH_LIBSSH2 clar__skip(); #endif + g_options.fetch_opts.callbacks.credentials = cred_foo_bar; cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); @@ -837,6 +885,10 @@ static int ssh_certificate_check(git_cert *cert, int valid, const char *host, vo void test_online_clone__ssh_cert(void) { +#ifndef GIT_SSH_LIBSSH2 + cl_skip(); +#endif + g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check; if (!_remote_ssh_fingerprint) @@ -893,7 +945,7 @@ static int ssh_memory_cred_cb(git_credential **cred, const char *url, const char void test_online_clone__ssh_memory_auth(void) { -#ifndef GIT_SSH_MEMORY_CREDENTIALS +#ifndef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS clar__skip(); #endif if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0) @@ -908,12 +960,12 @@ void test_online_clone__certificate_invalid(void) { g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check; - cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options), - GIT_ECERTIFICATE); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); -#ifdef GIT_SSH - cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options), - GIT_ECERTIFICATE); +#ifdef GIT_SSH_LIBSSH2 + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options)); #endif } diff --git a/tests/libgit2/online/customcert.c b/tests/libgit2/online/customcert.c index 7932a9e6..89694b5f 100644 --- a/tests/libgit2/online/customcert.c +++ b/tests/libgit2/online/customcert.c @@ -1,3 +1,4 @@ +#include "clar.h" #include "clar_libgit2.h" #include "path.h" @@ -6,45 +7,52 @@ #include "remote.h" #include "futils.h" #include "refs.h" +#include "str.h" +#include "streams/openssl.h" + +#if (GIT_OPENSSL && !GIT_OPENSSL_DYNAMIC) +# include +# include +# include +#endif /* - * Certificate one is in the `certs` folder; certificate two is in the - * `self-signed.pem` file. + * Certificates for https://test.libgit2.org/ are in the `certs` folder. */ +#define CUSTOM_CERT_DIR "certs" + #define CUSTOM_CERT_ONE_URL "https://test.libgit2.org:1443/anonymous/test.git" -#define CUSTOM_CERT_ONE_PATH "certs" +#define CUSTOM_CERT_ONE_PATH "one" #define CUSTOM_CERT_TWO_URL "https://test.libgit2.org:2443/anonymous/test.git" -#define CUSTOM_CERT_TWO_FILE "self-signed.pem" +#define CUSTOM_CERT_TWO_FILE "two.pem" + +#define CUSTOM_CERT_THREE_URL "https://test.libgit2.org:3443/anonymous/test.git" +#define CUSTOM_CERT_THREE_FILE "three.pem.raw" #if (GIT_OPENSSL || GIT_MBEDTLS) static git_repository *g_repo; -static int initialized = false; #endif void test_online_customcert__initialize(void) { #if (GIT_OPENSSL || GIT_MBEDTLS) - g_repo = NULL; + git_str path = GIT_STR_INIT, file = GIT_STR_INIT; + char cwd[GIT_PATH_MAX]; - if (!initialized) { - git_str path = GIT_STR_INIT, file = GIT_STR_INIT; - char cwd[GIT_PATH_MAX]; + g_repo = NULL; - cl_fixture_sandbox(CUSTOM_CERT_ONE_PATH); - cl_fixture_sandbox(CUSTOM_CERT_TWO_FILE); + cl_fixture_sandbox(CUSTOM_CERT_DIR); - cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); - cl_git_pass(git_str_joinpath(&path, cwd, CUSTOM_CERT_ONE_PATH)); - cl_git_pass(git_str_joinpath(&file, cwd, CUSTOM_CERT_TWO_FILE)); + cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); + cl_git_pass(git_str_join_n(&path, '/', 3, cwd, CUSTOM_CERT_DIR, CUSTOM_CERT_ONE_PATH)); + cl_git_pass(git_str_join_n(&file, '/', 3, cwd, CUSTOM_CERT_DIR, CUSTOM_CERT_TWO_FILE)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, - file.ptr, path.ptr)); - initialized = true; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, + file.ptr, path.ptr)); - git_str_dispose(&file); - git_str_dispose(&path); - } + git_str_dispose(&file); + git_str_dispose(&path); #endif } @@ -57,8 +65,11 @@ void test_online_customcert__cleanup(void) } cl_fixture_cleanup("./cloned"); - cl_fixture_cleanup(CUSTOM_CERT_ONE_PATH); - cl_fixture_cleanup(CUSTOM_CERT_TWO_FILE); + cl_fixture_cleanup(CUSTOM_CERT_DIR); +#endif + +#ifdef GIT_OPENSSL + git_openssl__reset_context(); #endif } @@ -77,3 +88,34 @@ void test_online_customcert__path(void) cl_assert(git_fs_path_exists("./cloned/master.txt")); #endif } + +void test_online_customcert__raw_x509(void) +{ +#if (GIT_OPENSSL && !GIT_OPENSSL_DYNAMIC) + X509* x509_cert = NULL; + char cwd[GIT_PATH_MAX]; + git_str raw_file = GIT_STR_INIT, + raw_file_data = GIT_STR_INIT, + raw_cert = GIT_STR_INIT; + const unsigned char *raw_cert_bytes = NULL; + + cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); + + cl_git_pass(git_str_join_n(&raw_file, '/', 3, cwd, CUSTOM_CERT_DIR, CUSTOM_CERT_THREE_FILE)); + + cl_git_pass(git_futils_readbuffer(&raw_file_data, git_str_cstr(&raw_file))); + cl_git_pass(git_str_decode_base64(&raw_cert, git_str_cstr(&raw_file_data), git_str_len(&raw_file_data))); + + raw_cert_bytes = (const unsigned char *)git_str_cstr(&raw_cert); + x509_cert = d2i_X509(NULL, &raw_cert_bytes, git_str_len(&raw_cert)); + cl_git_pass(git_libgit2_opts(GIT_OPT_ADD_SSL_X509_CERT, x509_cert)); + X509_free(x509_cert); + + cl_git_pass(git_clone(&g_repo, CUSTOM_CERT_THREE_URL, "./cloned", NULL)); + cl_assert(git_fs_path_exists("./cloned/master.txt")); + + git_str_dispose(&raw_cert); + git_str_dispose(&raw_file_data); + git_str_dispose(&raw_file); +#endif +} diff --git a/tests/libgit2/online/fetch.c b/tests/libgit2/online/fetch.c index a557bbf7..75ec0cde 100644 --- a/tests/libgit2/online/fetch.c +++ b/tests/libgit2/online/fetch.c @@ -39,9 +39,13 @@ void test_online_fetch__cleanup(void) git__free(_remote_redirect_subsequent); } -static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) +static int update_refs(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *data) { - GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data); + GIT_UNUSED(refname); + GIT_UNUSED(a); + GIT_UNUSED(b); + GIT_UNUSED(spec); + GIT_UNUSED(data); ++counter; @@ -62,7 +66,7 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) size_t bytes_received = 0; options.callbacks.transfer_progress = progress; - options.callbacks.update_tips = update_tips; + options.callbacks.update_refs = update_refs; options.callbacks.payload = &bytes_received; options.download_tags = flag; counter = 0; @@ -110,6 +114,26 @@ void test_online_fetch__fetch_twice(void) git_remote_free(remote); } +void test_online_fetch__fetch_with_empty_http_proxy(void) +{ + git_remote *remote; + git_config *config; + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; + + opts.proxy_opts.type = GIT_PROXY_AUTO; + + cl_git_pass(git_repository_config(&config, _repo)); + cl_git_pass(git_config_set_string(config, "http.proxy", "")); + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_remote_fetch(remote, NULL, &opts, NULL)); + + git_remote_disconnect(remote); + git_remote_free(remote); + git_config_free(config); +} + static int transferProgressCallback(const git_indexer_progress *stats, void *payload) { bool *invoked = (bool *)payload; @@ -128,6 +152,8 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date git_clone_options opts = GIT_CLONE_OPTIONS_INIT; opts.bare = true; + counter = 0; + cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git", "./fetch/lg2", &opts)); git_repository_free(_repository); @@ -141,11 +167,52 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date options.callbacks.transfer_progress = &transferProgressCallback; options.callbacks.payload = &invoked; + options.callbacks.update_refs = update_refs; cl_git_pass(git_remote_download(remote, NULL, &options)); cl_assert_equal_i(false, invoked); - cl_git_pass(git_remote_update_tips(remote, &options.callbacks, 1, options.download_tags, NULL)); + cl_git_pass(git_remote_update_tips(remote, &options.callbacks, GIT_REMOTE_UPDATE_FETCHHEAD, options.download_tags, NULL)); + cl_assert_equal_i(0, counter); + + git_remote_disconnect(remote); + + git_remote_free(remote); + git_repository_free(_repository); +} + +void test_online_fetch__report_unchanged_tips(void) +{ + git_repository *_repository; + bool invoked = false; + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.bare = true; + + counter = 0; + + cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git", + "./fetch/lg2", &opts)); + git_repository_free(_repository); + + cl_git_pass(git_repository_open(&_repository, "./fetch/lg2")); + + cl_git_pass(git_remote_lookup(&remote, _repository, "origin")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + + cl_assert_equal_i(false, invoked); + + options.callbacks.transfer_progress = &transferProgressCallback; + options.callbacks.payload = &invoked; + options.callbacks.update_refs = update_refs; + cl_git_pass(git_remote_download(remote, NULL, &options)); + + cl_assert_equal_i(false, invoked); + + cl_git_pass(git_remote_update_tips(remote, &options.callbacks, GIT_REMOTE_UPDATE_REPORT_UNCHANGED, options.download_tags, NULL)); + cl_assert(counter > 0); + git_remote_disconnect(remote); git_remote_free(remote); diff --git a/tests/libgit2/online/fetchhead.c b/tests/libgit2/online/fetchhead.c index 1b66c528..60a2b625 100644 --- a/tests/libgit2/online/fetchhead.c +++ b/tests/libgit2/online/fetchhead.c @@ -12,12 +12,9 @@ static git_clone_options g_options; void test_online_fetchhead__initialize(void) { - git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; g_repo = NULL; - memset(&g_options, 0, sizeof(git_clone_options)); - g_options.version = GIT_CLONE_OPTIONS_VERSION; - g_options.fetch_opts = dummy_fetch; + git_clone_options_init(&g_options, GIT_CLONE_OPTIONS_VERSION); } void test_online_fetchhead__cleanup(void) diff --git a/tests/libgit2/online/push.c b/tests/libgit2/online/push.c index 204572cf..eac33eb2 100644 --- a/tests/libgit2/online/push.c +++ b/tests/libgit2/online/push.c @@ -5,6 +5,7 @@ #include "push_util.h" #include "refspec.h" #include "remote.h" +#include "futils.h" static git_repository *_repo; @@ -20,7 +21,12 @@ static char *_remote_ssh_passphrase = NULL; static char *_remote_default = NULL; static char *_remote_expectcontinue = NULL; -static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); +static char *_remote_push_options = NULL; + +static char *_orig_ssh_cmd = NULL; +static char *_ssh_cmd = NULL; + +static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); static git_remote *_remote; static record_callbacks_data _record_cbs_data = {{ 0 }}; @@ -159,7 +165,7 @@ static void do_verify_push_status(record_callbacks_data *data, const push_status git__free(s); } - git_vector_free(actual); + git_vector_dispose(actual); } /** @@ -266,7 +272,7 @@ static void verify_tracking_branches(git_remote *remote, expected_ref expected_r git_vector_foreach(&actual_refs, i, actual_ref) git__free(actual_ref); - git_vector_free(&actual_refs); + git_vector_dispose(&actual_refs); git_str_dispose(&msg); git_buf_dispose(&ref_name); } @@ -367,8 +373,17 @@ void test_online_push__initialize(void) _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); + _remote_push_options = cl_getenv("GITTEST_PUSH_OPTIONS"); _remote = NULL; + _orig_ssh_cmd = cl_getenv("GIT_SSH_COMMAND"); + _ssh_cmd = cl_getenv("GITTEST_SSH_CMD"); + + if (_ssh_cmd) + cl_setenv("GIT_SSH_COMMAND", _ssh_cmd); + else + cl_setenv("GIT_SSH_COMMAND", NULL); + /* Skip the test if we're missing the remote URL */ if (!_remote_url) cl_skip(); @@ -401,7 +416,7 @@ void test_online_push__initialize(void) } git_remote_disconnect(_remote); - git_vector_free_deep(&delete_specs); + git_vector_dispose_deep(&delete_specs); /* Now that we've deleted everything, fetch from the remote */ memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); @@ -422,6 +437,11 @@ void test_online_push__cleanup(void) git__free(_remote_ssh_passphrase); git__free(_remote_default); git__free(_remote_expectcontinue); + git__free(_remote_push_options); + + cl_setenv("GIT_SSH_COMMAND", _orig_ssh_cmd); + git__free(_orig_ssh_cmd); + git__free(_ssh_cmd); /* Freed by cl_git_sandbox_cleanup */ _repo = NULL; @@ -430,6 +450,7 @@ void test_online_push__cleanup(void) record_callbacks_data_clear(&_record_cbs_data); + cl_fixture_cleanup("push-options-result"); cl_fixture_cleanup("testrepo.git"); cl_git_sandbox_cleanup(); } @@ -472,7 +493,8 @@ static void do_push( const char *refspecs[], size_t refspecs_len, push_status expected_statuses[], size_t expected_statuses_len, expected_ref expected_refs[], size_t expected_refs_len, - int expected_ret, int check_progress_cb, int check_update_tips_cb) + int expected_ret, int check_progress_cb, int check_update_tips_cb, + git_strarray *remote_push_options) { git_push_options opts = GIT_PUSH_OPTIONS_INIT; size_t i; @@ -484,6 +506,9 @@ static void do_push( /* Auto-detect the number of threads to use */ opts.pb_parallelism = 0; + if (remote_push_options) + memcpy(&opts.remote_push_options, remote_push_options, sizeof(git_strarray)); + memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); data = opts.callbacks.payload; @@ -527,13 +552,12 @@ static void do_push( verify_update_tips_callback(_remote, expected_refs, expected_refs_len); } - } /* Call push_finish() without ever calling git_push_add_refspec() */ void test_online_push__noop(void) { - do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1); + do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1, NULL); } void test_online_push__b1(void) @@ -543,7 +567,9 @@ void test_online_push__b1(void) expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b2(void) @@ -553,7 +579,9 @@ void test_online_push__b2(void) expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b3(void) @@ -563,7 +591,9 @@ void test_online_push__b3(void) expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b4(void) @@ -573,7 +603,9 @@ void test_online_push__b4(void) expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b5(void) @@ -583,13 +615,15 @@ void test_online_push__b5(void) expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__b5_cancel(void) { const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; - do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1); + do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1, NULL); } void test_online_push__multi(void) @@ -620,7 +654,9 @@ void test_online_push__multi(void) }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1")); entry = git_reflog_entry_byindex(log, 0); @@ -641,16 +677,21 @@ void test_online_push__implicit_tgt(void) const char *specs2[] = { "refs/heads/b2" }; push_status exp_stats2[] = { { "refs/heads/b2", 1 } }; expected_ref exp_refs2[] = { - { "refs/heads/b1", &_oid_b1 }, - { "refs/heads/b2", &_oid_b2 } + { "refs/heads/b1", &_oid_b1 }, + { "refs/heads/b2", &_oid_b2 } }; do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 1, 1, + NULL); + do_push(specs2, ARRAY_SIZE(specs2), exp_stats2, ARRAY_SIZE(exp_stats2), - exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0); + exp_refs2, ARRAY_SIZE(exp_refs2), + 0, 0, 0, + NULL); } void test_online_push__fast_fwd(void) @@ -672,19 +713,27 @@ void test_online_push__fast_fwd(void) do_push(specs_init, ARRAY_SIZE(specs_init), exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1); + exp_refs_init, ARRAY_SIZE(exp_refs_init), + 0, 1, 1, + NULL); do_push(specs_ff, ARRAY_SIZE(specs_ff), exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), + 0, 0, 0, + NULL); do_push(specs_reset, ARRAY_SIZE(specs_reset), exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0); + exp_refs_init, ARRAY_SIZE(exp_refs_init), + 0, 0, 0, + NULL); do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force), exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), + 0, 0, 0, + NULL); } void test_online_push__tag_commit(void) @@ -694,7 +743,9 @@ void test_online_push__tag_commit(void) expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_tree(void) @@ -704,7 +755,9 @@ void test_online_push__tag_tree(void) expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_blob(void) @@ -714,7 +767,9 @@ void test_online_push__tag_blob(void) expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_lightweight(void) @@ -724,7 +779,9 @@ void test_online_push__tag_lightweight(void) expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__tag_to_tag(void) @@ -734,7 +791,9 @@ void test_online_push__tag_to_tag(void) expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 0, 0, + NULL); } void test_online_push__force(void) @@ -751,17 +810,80 @@ void test_online_push__force(void) do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 1, 1, + NULL); do_push(specs2, ARRAY_SIZE(specs2), NULL, 0, - exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + GIT_ENONFASTFORWARD, 0, 0, + NULL); /* Non-fast-forward update with force should pass. */ record_callbacks_data_clear(&_record_cbs_data); do_push(specs2_force, ARRAY_SIZE(specs2_force), exp_stats2_force, ARRAY_SIZE(exp_stats2_force), - exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1); + exp_refs2_force, ARRAY_SIZE(exp_refs2_force), + 0, 1, 1, + NULL); +} + +static void push_option_test(git_strarray given_options, const char *expected_option) +{ + const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; + push_status exp_stats[] = { { "refs/heads/b1", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; + git_str push_options_path = GIT_STR_INIT; + git_str push_options_result = GIT_STR_INIT; + char *options[16]; + git_strarray push_options = { options, given_options.count + 1 }; + size_t i; + + /* Skip the test if we're missing the push options result file */ + if (!_remote_push_options) + cl_skip(); + + cl_assert(given_options.count < 16); + + cl_git_pass(git_str_joinpath(&push_options_path, clar_sandbox_path(), "push-options-result")); + + options[0] = push_options_path.ptr; + for (i = 0; i < given_options.count; i++) + options[i + 1] = given_options.strings[i]; + + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + &push_options); + + cl_assert(git_fs_path_exists(push_options_path.ptr)); + cl_git_pass(git_futils_readbuffer(&push_options_result, push_options_path.ptr)); + + cl_assert_equal_s(expected_option, git_str_cstr(&push_options_result)); + git_str_dispose(&push_options_result); + git_str_dispose(&push_options_path); +} + +void test_online_push__options(void) +{ + char *push_options_string_args_test_1[1] = { "test_string" }; + git_strarray push_options_test_1 = { push_options_string_args_test_1, 1 }; + + char *push_options_string_args_test_2[2] = { "test_string", "another arg?" }; + git_strarray push_options_test_2 = { push_options_string_args_test_2, 2 }; + + char *push_options_string_args_test_3[1] = { "👨🏿‍💻 but can it do unicode? 🇺🇦" }; + git_strarray push_options_test_3 = { push_options_string_args_test_3, 1 }; + + char *push_options_string_args_test_4[3] = { "\0", "\0", "\0" }; + git_strarray push_options_test_4 = { push_options_string_args_test_4, 3 }; + + push_option_test(push_options_test_1, "test_string"); + push_option_test(push_options_test_2, "test_stringanother arg?"); + push_option_test(push_options_test_3, "👨🏿‍💻 but can it do unicode? 🇺🇦"); + push_option_test(push_options_test_4, "\0\0\0"); } void test_online_push__delete(void) @@ -792,7 +914,9 @@ void test_online_push__delete(void) do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 1, 1, + NULL); /* When deleting a non-existent branch, the git client sends zero for both * the old and new commit id. This should succeed on the server with the @@ -802,23 +926,35 @@ void test_online_push__delete(void) */ do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake), exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 0, 0, + NULL); + do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force), exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 0, 0, + NULL); /* Delete one of the pushed branches. */ do_push(specs_delete, ARRAY_SIZE(specs_delete), exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), + 0, 0, 0, + NULL); /* Re-push branches and retry delete with force. */ do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + exp_refs1, ARRAY_SIZE(exp_refs1), + 0, 0, 0, + NULL); + do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force), exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), + 0, 0, 0, + NULL); } void test_online_push__bad_refspecs(void) @@ -862,7 +998,9 @@ void test_online_push__expressions(void) do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); } void test_online_push__notes(void) @@ -884,13 +1022,17 @@ void test_online_push__notes(void) do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); /* And make sure to delete the note */ do_push(specs_del, ARRAY_SIZE(specs_del), exp_stats, 1, - NULL, 0, 0, 0, 0); + NULL, 0, + 0, 0, 0, + NULL); git_signature_free(signature); } @@ -920,13 +1062,17 @@ void test_online_push__configured(void) do_push(NULL, 0, exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + exp_refs, ARRAY_SIZE(exp_refs), + 0, 1, 1, + NULL); /* And make sure to delete the note */ do_push(specs_del, ARRAY_SIZE(specs_del), exp_stats, 1, - NULL, 0, 0, 0, 0); + NULL, 0, + 0, 0, 0, + NULL); git_signature_free(signature); } diff --git a/tests/libgit2/online/push_util.c b/tests/libgit2/online/push_util.c index 94919e9b..88651672 100644 --- a/tests/libgit2/online/push_util.c +++ b/tests/libgit2/online/push_util.c @@ -24,22 +24,24 @@ void record_callbacks_data_clear(record_callbacks_data *data) git_vector_foreach(&data->updated_tips, i, tip) updated_tip_free(tip); - git_vector_free(&data->updated_tips); + git_vector_dispose(&data->updated_tips); git_vector_foreach(&data->statuses, i, status) push_status_free(status); - git_vector_free(&data->statuses); + git_vector_dispose(&data->statuses); data->pack_progress_calls = 0; data->transfer_progress_calls = 0; } -int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) +int record_update_refs_cb(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *data) { updated_tip *t; record_callbacks_data *record_data = (record_callbacks_data *)data; + GIT_UNUSED(spec); + cl_assert(t = git__calloc(1, sizeof(*t))); cl_assert(t->name = git__strdup(refname)); diff --git a/tests/libgit2/online/push_util.h b/tests/libgit2/online/push_util.h index 5f669fea..07b46bf4 100644 --- a/tests/libgit2/online/push_util.h +++ b/tests/libgit2/online/push_util.h @@ -12,7 +12,7 @@ extern const git_oid OID_ZERO; * @param data pointer to a record_callbacks_data instance */ #define RECORD_CALLBACKS_INIT(data) \ - { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, NULL, data, NULL } + { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, data, NULL, record_update_refs_cb } typedef struct { char *name; @@ -50,7 +50,7 @@ void record_callbacks_data_clear(record_callbacks_data *data); * * @param data (git_vector *) of updated_tip instances */ -int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data); +int record_update_refs_cb(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *data); /** * Create a set of refspecs that deletes each of the inputs diff --git a/tests/libgit2/online/shallow.c b/tests/libgit2/online/shallow.c index 5c0e6565..a5508c16 100644 --- a/tests/libgit2/online/shallow.c +++ b/tests/libgit2/online/shallow.c @@ -164,3 +164,290 @@ void test_online_shallow__unshallow(void) git_revwalk_free(walk); git_repository_free(repo); } + +void test_online_shallow__deepen_full(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_revwalk *walk; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_remote *origin = NULL; + git_oid oid; + git_oid *roots; + size_t roots_len; + size_t num_commits = 0; + int error = 0; + + clone_opts.fetch_opts.depth = 7; + clone_opts.remote_cb = remote_single_branch; + + git_str_joinpath(&path, clar_sandbox_path(), "deepen_full"); + cl_git_pass(git_clone(&repo, "https://github.com/libgit2/TestGitRepository", git_str_cstr(&path), &clone_opts)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + fetch_opts.depth = 8; + cl_git_pass(git_remote_lookup(&origin, repo, "origin")); + cl_git_pass(git_remote_fetch(origin, NULL, &fetch_opts, NULL)); + cl_assert_equal_b(false, git_repository_is_shallow(repo)); + + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(0, roots_len); + + git_revwalk_new(&walk, repo); + git_revwalk_push_head(walk); + + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + + cl_assert_equal_i(num_commits, 21); + cl_assert_equal_i(error, GIT_ITEROVER); + + git__free(roots); + git_remote_free(origin); + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} + +void test_online_shallow__deepen_six(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_revwalk *walk; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_remote *origin = NULL; + git_oid oid; + git_oid *roots; + size_t roots_len; + size_t num_commits = 0; + int error = 0; + + clone_opts.fetch_opts.depth = 5; + clone_opts.remote_cb = remote_single_branch; + + git_str_joinpath(&path, clar_sandbox_path(), "deepen_6"); + cl_git_pass(git_clone(&repo, "https://github.com/libgit2/TestGitRepository", git_str_cstr(&path), &clone_opts)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + fetch_opts.depth = 6; + cl_git_pass(git_remote_lookup(&origin, repo, "origin")); + cl_git_pass(git_remote_fetch(origin, NULL, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(4, roots_len); + cl_assert_equal_s("58be4659bb571194ed4562d04b359d26216f526e", git_oid_tostr_s(&roots[0])); + cl_assert_equal_s("d31f5a60d406e831d056b8ac2538d515100c2df2", git_oid_tostr_s(&roots[1])); + cl_assert_equal_s("6462e7d8024396b14d7651e2ec11e2bbf07a05c4", git_oid_tostr_s(&roots[2])); + cl_assert_equal_s("2c349335b7f797072cf729c4f3bb0914ecb6dec9", git_oid_tostr_s(&roots[3])); + + git_revwalk_new(&walk, repo); + git_revwalk_push_head(walk); + + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + + cl_assert_equal_i(num_commits, 17); + cl_assert_equal_i(error, GIT_ITEROVER); + + git__free(roots); + git_remote_free(origin); + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} + +void test_online_shallow__shorten_four(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_revwalk *walk; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_remote *origin = NULL; + git_oid oid; + git_oid *roots; + size_t roots_len; + size_t num_commits = 0; + int error = 0; + + clone_opts.fetch_opts.depth = 5; + clone_opts.remote_cb = remote_single_branch; + + git_str_joinpath(&path, clar_sandbox_path(), "shorten_4"); + cl_git_pass(git_clone(&repo, "https://github.com/libgit2/TestGitRepository", git_str_cstr(&path), &clone_opts)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + fetch_opts.depth = 4; + cl_git_pass(git_remote_lookup(&origin, repo, "origin")); + cl_git_pass(git_remote_fetch(origin, NULL, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(6, roots_len); + /* roots added during initial clone, not removed as not encountered during fetch */ + cl_assert_equal_s("c070ad8c08840c8116da865b2d65593a6bb9cd2a", git_oid_tostr_s(&roots[0])); + cl_assert_equal_s("0966a434eb1a025db6b71485ab63a3bfbea520b6", git_oid_tostr_s(&roots[1])); + cl_assert_equal_s("83834a7afdaa1a1260568567f6ad90020389f664", git_oid_tostr_s(&roots[3])); + /* roots added during fetch */ + cl_assert_equal_s("bab66b48f836ed950c99134ef666436fb07a09a0", git_oid_tostr_s(&roots[2])); + cl_assert_equal_s("59706a11bde2b9899a278838ef20a97e8f8795d2", git_oid_tostr_s(&roots[4])); + cl_assert_equal_s("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864", git_oid_tostr_s(&roots[5])); + + git_revwalk_new(&walk, repo); + git_revwalk_push_head(walk); + + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + + cl_assert_equal_i(num_commits, 10); + cl_assert_equal_i(error, GIT_ITEROVER); + + git__free(roots); + git_remote_free(origin); + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} + +void test_online_shallow__preserve_unrelated_roots(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_revwalk *walk; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_remote *origin = NULL; + git_strarray refspecs; + git_oid oid; + git_oid *roots; + size_t roots_len; + size_t num_commits = 0; + int error = 0; + git_oid first_oid; + git_oid second_oid; + git_oid third_oid; + char *first_commit = "c070ad8c08840c8116da865b2d65593a6bb9cd2a"; + char *second_commit = "6e1475206e57110fcef4b92320436c1e9872a322"; + char *third_commit = "7f822839a2fe9760f386cbbbcb3f92c5fe81def7"; + +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_oid_fromstr(&first_oid, first_commit, GIT_OID_SHA1)); + cl_git_pass(git_oid_fromstr(&second_oid, second_commit, GIT_OID_SHA1)); + cl_git_pass(git_oid_fromstr(&third_oid, third_commit, GIT_OID_SHA1)); +#else + cl_git_pass(git_oid_fromstr(&first_oid, first_commit)); + cl_git_pass(git_oid_fromstr(&second_oid, second_commit)); + cl_git_pass(git_oid_fromstr(&third_oid, third_commit)); +#endif + + /* setup empty repository without cloning */ + git_str_joinpath(&path, clar_sandbox_path(), "preserve_unrelated_roots"); + cl_git_pass(git_repository_init(&repo, git_str_cstr(&path), true)); + cl_git_pass(git_remote_create(&origin, repo, "origin", "https://github.com/libgit2/TestGitRepository")); + cl_assert_equal_b(false, git_repository_is_shallow(repo)); + + /* shallow fetch for first commit */ + fetch_opts.depth = 1; + refspecs.strings = &first_commit; + refspecs.count = 1; + cl_git_pass(git_remote_fetch(origin, &refspecs, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(1, roots_len); + cl_assert_equal_s("c070ad8c08840c8116da865b2d65593a6bb9cd2a", git_oid_tostr_s(&roots[0])); + + cl_git_pass(git_revwalk_new(&walk, repo)); + cl_git_pass(git_revwalk_push(walk, &first_oid)); + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + cl_assert_equal_i(num_commits, 1); + cl_assert_equal_i(error, GIT_ITEROVER); + + /* shallow fetch for second commit */ + fetch_opts.depth = 1; + refspecs.strings = &second_commit; + refspecs.count = 1; + cl_git_pass(git_remote_fetch(origin, &refspecs, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + git__free(roots); + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(2, roots_len); + cl_assert_equal_s("c070ad8c08840c8116da865b2d65593a6bb9cd2a", git_oid_tostr_s(&roots[0])); + cl_assert_equal_s("6e1475206e57110fcef4b92320436c1e9872a322", git_oid_tostr_s(&roots[1])); + + git_revwalk_free(walk); + cl_git_pass(git_revwalk_new(&walk, repo)); + cl_git_pass(git_revwalk_push(walk, &second_oid)); + num_commits = 0; + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + cl_assert_equal_i(error, GIT_ITEROVER); + cl_assert_equal_i(num_commits, 1); + + /* fetch full history for third commit, includes first commit which should be removed from shallow roots */ + fetch_opts.depth = 100; + refspecs.strings = &third_commit; + refspecs.count = 1; + cl_git_pass(git_remote_fetch(origin, &refspecs, &fetch_opts, NULL)); + cl_assert_equal_b(true, git_repository_is_shallow(repo)); + + git__free(roots); + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(1, roots_len); + cl_assert_equal_s("6e1475206e57110fcef4b92320436c1e9872a322", git_oid_tostr_s(&roots[0])); + + git_revwalk_free(walk); + cl_git_pass(git_revwalk_new(&walk, repo)); + cl_git_pass(git_revwalk_push(walk, &third_oid)); + num_commits = 0; + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + cl_assert_equal_i(error, GIT_ITEROVER); + cl_assert_equal_i(num_commits, 12); + + cl_git_pass(git_revwalk_reset(walk)); + cl_git_pass(git_revwalk_push(walk, &second_oid)); + num_commits = 0; + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + cl_assert_equal_i(error, GIT_ITEROVER); + cl_assert_equal_i(num_commits, 1); + + /* unshallow repository without specifying any refspec */ + fetch_opts.depth = GIT_FETCH_DEPTH_UNSHALLOW; + cl_git_pass(git_remote_fetch(origin, NULL, &fetch_opts, NULL)); + cl_assert_equal_b(false, git_repository_is_shallow(repo)); + + git__free(roots); + cl_git_pass(git_repository__shallow_roots(&roots, &roots_len, repo)); + cl_assert_equal_i(0, roots_len); + + git_revwalk_free(walk); + cl_git_pass(git_revwalk_new(&walk, repo)); + cl_git_pass(git_revwalk_push(walk, &first_oid)); + cl_git_pass(git_revwalk_push(walk, &second_oid)); + cl_git_pass(git_revwalk_push(walk, &third_oid)); + num_commits = 0; + while ((error = git_revwalk_next(&oid, walk)) == GIT_OK) { + num_commits++; + } + cl_assert_equal_i(error, GIT_ITEROVER); + cl_assert_equal_i(num_commits, 18); + + git__free(roots); + git_remote_free(origin); + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} diff --git a/tests/libgit2/pack/filelimit.c b/tests/libgit2/pack/filelimit.c index fa08485f..148c8012 100644 --- a/tests/libgit2/pack/filelimit.c +++ b/tests/libgit2/pack/filelimit.c @@ -8,7 +8,7 @@ static size_t expected_open_mwindow_files = 0; static size_t original_mwindow_file_limit = 0; -extern git_mutex git__mwindow_mutex; +extern git_mutex git_mwindow__mutex; extern git_mwindow_ctl git_mwindow__mem_ctl; void test_pack_filelimit__initialize_tiny(void) @@ -120,13 +120,13 @@ void test_pack_filelimit__open_repo_with_multiple_packfiles(void) ++i; cl_assert_equal_i(commit_count, i); - cl_git_pass(git_mutex_lock(&git__mwindow_mutex)); + cl_git_pass(git_mutex_lock(&git_mwindow__mutex)); /* * Adding an assert while holding a lock will cause the whole process to * deadlock. Copy the value and do the assert after releasing the lock. */ open_windows = ctl->open_windows; - cl_git_pass(git_mutex_unlock(&git__mwindow_mutex)); + cl_git_pass(git_mutex_unlock(&git_mwindow__mutex)); cl_assert_equal_i(expected_open_mwindow_files, open_windows); diff --git a/tests/libgit2/pack/indexer.c b/tests/libgit2/pack/indexer.c index 9722deca..023eb5da 100644 --- a/tests/libgit2/pack/indexer.c +++ b/tests/libgit2/pack/indexer.c @@ -101,7 +101,7 @@ void test_pack_indexer__out_of_order(void) git_indexer_progress stats = { 0 }; #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif @@ -123,7 +123,7 @@ void test_pack_indexer__missing_trailer(void) git_indexer_progress stats = { 0 }; #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif @@ -144,7 +144,7 @@ void test_pack_indexer__leaky(void) git_indexer_progress stats = { 0 }; #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif @@ -178,7 +178,7 @@ void test_pack_indexer__fix_thin(void) #ifdef GIT_EXPERIMENTAL_SHA256 opts.odb = odb; - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, &opts)); + cl_git_pass(git_indexer_new(&idx, ".", &opts)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, odb, &opts)); #endif @@ -215,7 +215,7 @@ void test_pack_indexer__fix_thin(void) cl_git_pass(p_stat(name, &st)); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif @@ -255,7 +255,8 @@ void test_pack_indexer__corrupt_length(void) #ifdef GIT_EXPERIMENTAL_SHA256 opts.odb = odb; - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, &opts)); + opts.oid_type = GIT_OID_SHA1; + cl_git_pass(git_indexer_new(&idx, ".", &opts)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, odb, &opts)); #endif @@ -281,7 +282,7 @@ void test_pack_indexer__incomplete_pack_fails_with_strict(void) opts.verify = 1; #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, &opts)); + cl_git_pass(git_indexer_new(&idx, ".", &opts)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, &opts)); #endif @@ -306,7 +307,7 @@ void test_pack_indexer__out_of_order_with_connectivity_checks(void) opts.verify = 1; #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, &opts)); + cl_git_pass(git_indexer_new(&idx, ".", &opts)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, &opts)); #endif @@ -354,7 +355,7 @@ void test_pack_indexer__no_tmp_files(void) cl_assert(git_str_len(&first_tmp_file) == 0); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif diff --git a/tests/libgit2/pack/midx.c b/tests/libgit2/pack/midx.c index 4c4dfc51..d7180c47 100644 --- a/tests/libgit2/pack/midx.c +++ b/tests/libgit2/pack/midx.c @@ -59,7 +59,7 @@ void test_pack_midx__writer(void) cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/pack")); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_midx_writer_new(&w, git_str_cstr(&path), GIT_OID_SHA1)); + cl_git_pass(git_midx_writer_new(&w, git_str_cstr(&path), NULL)); #else cl_git_pass(git_midx_writer_new(&w, git_str_cstr(&path))); #endif diff --git a/tests/libgit2/pack/packbuilder.c b/tests/libgit2/pack/packbuilder.c index ff3dc1f6..2c598b6e 100644 --- a/tests/libgit2/pack/packbuilder.c +++ b/tests/libgit2/pack/packbuilder.c @@ -42,7 +42,7 @@ void test_pack_packbuilder__cleanup(void) git_vector_foreach(&_commits, i, o) { git__free(o); } - git_vector_free(&_commits); + git_vector_dispose(&_commits); } git_packbuilder_free(_packbuilder); @@ -98,14 +98,11 @@ void test_pack_packbuilder__create_pack(void) { git_indexer_progress stats; git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - git_hash_ctx ctx; - unsigned char hash[GIT_HASH_SHA1_SIZE]; - char hex[(GIT_HASH_SHA1_SIZE * 2) + 1]; seed_packbuilder(); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&_indexer, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&_indexer, ".", NULL)); #else cl_git_pass(git_indexer_new(&_indexer, ".", 0, NULL, NULL)); #endif @@ -114,33 +111,13 @@ void test_pack_packbuilder__create_pack(void) cl_git_pass(git_indexer_commit(_indexer, &stats)); git_str_printf(&path, "pack-%s.pack", git_indexer_name(_indexer)); - - /* - * By default, packfiles are created with only one thread. - * Therefore we can predict the object ordering and make sure - * we create exactly the same pack as git.git does when *not* - * reusing existing deltas (as libgit2). - * - * $ cd tests/resources/testrepo.git - * $ git rev-list --objects HEAD | \ - * git pack-objects -q --no-reuse-delta --threads=1 pack - * $ sha1sum pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack - * 5d410bdf97cf896f9007681b92868471d636954b - * - */ + cl_assert(git_fs_path_exists(path.ptr)); cl_git_pass(git_futils_readbuffer(&buf, git_str_cstr(&path))); - - cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); - cl_git_pass(git_hash_update(&ctx, buf.ptr, buf.size)); - cl_git_pass(git_hash_final(hash, &ctx)); - git_hash_ctx_cleanup(&ctx); + cl_assert(buf.size > 256); git_str_dispose(&path); git_str_dispose(&buf); - - git_hash_fmt(hex, hash, GIT_HASH_SHA1_SIZE); - cl_assert_equal_s(hex, "5d410bdf97cf896f9007681b92868471d636954b"); } void test_pack_packbuilder__get_name(void) @@ -148,22 +125,49 @@ void test_pack_packbuilder__get_name(void) seed_packbuilder(); cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL)); - cl_assert_equal_s("7f5fa362c664d68ba7221259be1cbd187434b2f0", git_packbuilder_name(_packbuilder)); + cl_assert(git_packbuilder_name(_packbuilder) != NULL); +} + +static void get_packfile_path(git_str *out, git_packbuilder *pb) +{ + git_str_puts(out, "pack-"); + git_str_puts(out, git_packbuilder_name(pb)); + git_str_puts(out, ".pack"); +} + +static void get_index_path(git_str *out, git_packbuilder *pb) +{ + git_str_puts(out, "pack-"); + git_str_puts(out, git_packbuilder_name(pb)); + git_str_puts(out, ".idx"); } void test_pack_packbuilder__write_default_path(void) { + git_str idx = GIT_STR_INIT, pack = GIT_STR_INIT; + seed_packbuilder(); cl_git_pass(git_packbuilder_write(_packbuilder, NULL, 0, NULL, NULL)); - cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx")); - cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack")); + + git_str_puts(&idx, "objects/pack/"); + get_index_path(&idx, _packbuilder); + + git_str_puts(&pack, "objects/pack/"); + get_packfile_path(&pack, _packbuilder); + + cl_assert(git_fs_path_exists(idx.ptr)); + cl_assert(git_fs_path_exists(pack.ptr)); + + git_str_dispose(&idx); + git_str_dispose(&pack); } static void test_write_pack_permission(mode_t given, mode_t expected) { struct stat statbuf; mode_t mask, os_mask; + git_str idx = GIT_STR_INIT, pack = GIT_STR_INIT; seed_packbuilder(); @@ -181,11 +185,17 @@ static void test_write_pack_permission(mode_t given, mode_t expected) mask = p_umask(0); p_umask(mask); - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx", &statbuf)); + get_index_path(&idx, _packbuilder); + get_packfile_path(&pack, _packbuilder); + + cl_git_pass(p_stat(idx.ptr, &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack", &statbuf)); + cl_git_pass(p_stat(pack.ptr, &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); + + git_str_dispose(&idx); + git_str_dispose(&pack); } void test_pack_packbuilder__permissions_standard(void) @@ -251,7 +261,7 @@ void test_pack_packbuilder__foreach(void) seed_packbuilder(); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif @@ -275,7 +285,7 @@ void test_pack_packbuilder__foreach_with_cancel(void) seed_packbuilder(); #ifdef GIT_EXPERIMENTAL_SHA256 - cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", NULL)); #else cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); #endif diff --git a/tests/libgit2/pack/sharing.c b/tests/libgit2/pack/sharing.c index 2e2042d2..dd7aee8b 100644 --- a/tests/libgit2/pack/sharing.c +++ b/tests/libgit2/pack/sharing.c @@ -1,19 +1,18 @@ #include "clar_libgit2.h" #include -#include "strmap.h" #include "mwindow.h" #include "pack.h" +#include "hashmap.h" -extern git_strmap *git__pack_cache; +extern git_mwindow_packmap git_mwindow__pack_cache; void test_pack_sharing__open_two_repos(void) { git_repository *repo1, *repo2; git_object *obj1, *obj2; git_oid id; - size_t pos; - void *data; - int error; + struct git_pack_file *pack; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; cl_git_pass(git_repository_open(&repo1, cl_fixture("testrepo.git"))); cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); @@ -23,14 +22,10 @@ void test_pack_sharing__open_two_repos(void) cl_git_pass(git_object_lookup(&obj1, repo1, &id, GIT_OBJECT_ANY)); cl_git_pass(git_object_lookup(&obj2, repo2, &id, GIT_OBJECT_ANY)); - pos = 0; - while ((error = git_strmap_iterate(&data, git__pack_cache, &pos, NULL)) == 0) { - struct git_pack_file *pack = (struct git_pack_file *) data; - + while (git_mwindow_packmap_iterate(&iter, NULL, &pack, &git_mwindow__pack_cache) == 0) cl_assert_equal_i(2, pack->refcount.val); - } - cl_assert_equal_i(3, git_strmap_size(git__pack_cache)); + cl_assert_equal_i(3, git_mwindow_packmap_size(&git_mwindow__pack_cache)); git_object_free(obj1); git_object_free(obj2); @@ -38,5 +33,5 @@ void test_pack_sharing__open_two_repos(void) git_repository_free(repo2); /* we don't want to keep the packs open after the repos go away */ - cl_assert_equal_i(0, git_strmap_size(git__pack_cache)); + cl_assert_equal_i(0, git_mwindow_packmap_size(&git_mwindow__pack_cache)); } diff --git a/tests/libgit2/perf/helper__perf__do_merge.c b/tests/libgit2/perf/helper__perf__do_merge.c index eb10524e..6f53af63 100644 --- a/tests/libgit2/perf/helper__perf__do_merge.c +++ b/tests/libgit2/perf/helper__perf__do_merge.c @@ -26,13 +26,12 @@ void perf__do_merge(const char *fixture, perf__timer__start(&t_total); - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; clone_opts.checkout_opts = checkout_opts; perf__timer__start(&t_clone); cl_git_pass(git_clone(&g_repo, fixture, test_name, &clone_opts)); perf__timer__stop(&t_clone); - + git_oid__fromstr(&oid_a, id_a, GIT_OID_SHA1); cl_git_pass(git_commit_lookup(&commit_a, g_repo, &oid_a)); cl_git_pass(git_branch_create(&ref_branch_a, g_repo, diff --git a/tests/libgit2/refs/branches/delete.c b/tests/libgit2/refs/branches/delete.c index 6b3d507a..63f8c5d9 100644 --- a/tests/libgit2/refs/branches/delete.c +++ b/tests/libgit2/refs/branches/delete.c @@ -92,6 +92,21 @@ void test_refs_branches_delete__can_delete_a_local_branch(void) git_reference_free(branch); } +void test_refs_branches_delete__can_delete_a_local_branch_with_multivar(void) +{ + git_reference *branch; + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_multivar( + cfg, "branch.br2.gitpublishto", "^$", "example1@example.com")); + cl_git_pass(git_config_set_multivar( + cfg, "branch.br2.gitpublishto", "^$", "example2@example.com")); + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + void test_refs_branches_delete__can_delete_a_remote_branch(void) { git_reference *branch; diff --git a/tests/libgit2/refs/iterator.c b/tests/libgit2/refs/iterator.c index 706fd1ef..a46db629 100644 --- a/tests/libgit2/refs/iterator.c +++ b/tests/libgit2/refs/iterator.c @@ -96,7 +96,7 @@ static void assert_all_refnames_match(const char **expected, git_vector *names) } cl_assert(expected[i] == NULL); - git_vector_free(names); + git_vector_dispose(names); } void test_refs_iterator__list(void) @@ -129,7 +129,7 @@ void test_refs_iterator__empty(void) git_repository *empty; cl_git_pass(git_odb__new(&odb, NULL)); - cl_git_pass(git_repository__wrap_odb(&empty, odb, GIT_OID_SHA1)); + cl_git_pass(git_repository_wrap_odb(&empty, odb)); cl_git_pass(git_reference_iterator_new(&iter, empty)); cl_assert_equal_i(GIT_ITEROVER, git_reference_next(&ref, iter)); @@ -222,7 +222,7 @@ void test_refs_iterator__foreach_name(void) git__free(name); } - git_vector_free(&output); + git_vector_dispose(&output); } static int refs_foreach_name_cancel_cb(const char *name, void *payload) diff --git a/tests/libgit2/refs/normalize.c b/tests/libgit2/refs/normalize.c index ff815002..c2f5684e 100644 --- a/tests/libgit2/refs/normalize.c +++ b/tests/libgit2/refs/normalize.c @@ -229,8 +229,6 @@ void test_refs_normalize__jgit_suite(void) GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master^"); ensure_refname_invalid( GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/^master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "^refs/heads/master"); ensure_refname_invalid( GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master~"); @@ -399,3 +397,13 @@ void test_refs_normalize__refspec_pattern(void) ensure_refname_invalid( ONE_LEVEL_AND_REFSPEC, "*/*/foo"); } + +void test_refs_normalize__negative_refspec_pattern(void) +{ + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "^foo/bar", "^foo/bar"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "^foo/bar/*", "^foo/bar/*"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/^bar"); +} diff --git a/tests/libgit2/refs/revparse.c b/tests/libgit2/refs/revparse.c index d2f46484..9bc3bd10 100644 --- a/tests/libgit2/refs/revparse.c +++ b/tests/libgit2/refs/revparse.c @@ -747,6 +747,25 @@ void test_refs_revparse__try_to_retrieve_branch_before_abbrev_sha(void) cl_git_sandbox_cleanup(); } +void test_refs_revparse__at_at_end_of_refname(void) +{ + git_repository *repo; + git_reference *branch; + git_object *target; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_revparse_single(&target, repo, "HEAD")); + cl_git_pass(git_branch_create(&branch, repo, "master@", (git_commit *)target, 0)); + git_object_free(target); + + test_id_inrepo("master@", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE, repo); + + cl_git_fail_with(GIT_ENOTFOUND, git_revparse_single(&target, repo, "foo@")); + + git_reference_free(branch); + cl_git_sandbox_cleanup(); +} void test_refs_revparse__range(void) { diff --git a/tests/libgit2/refs/tags/name.c b/tests/libgit2/refs/tags/name.c index 1dd1760b..bc6cbaef 100644 --- a/tests/libgit2/refs/tags/name.c +++ b/tests/libgit2/refs/tags/name.c @@ -11,7 +11,9 @@ void test_refs_tags_name__is_name_valid(void) { cl_assert_equal_i(true, name_is_valid("sometag")); cl_assert_equal_i(true, name_is_valid("test/sometag")); + cl_assert_equal_i(true, name_is_valid("test/HEAD")); cl_assert_equal_i(false, name_is_valid("")); cl_assert_equal_i(false, name_is_valid("-dash")); + cl_assert_equal_i(false, name_is_valid("HEAD")); } diff --git a/tests/libgit2/remote/fetch.c b/tests/libgit2/remote/fetch.c index 85e99206..6b7cec92 100644 --- a/tests/libgit2/remote/fetch.c +++ b/tests/libgit2/remote/fetch.c @@ -10,8 +10,11 @@ static char* repo2_path; static const char *REPO1_REFNAME = "refs/heads/main"; static const char *REPO2_REFNAME = "refs/remotes/repo1/main"; +static const char *REPO1_UNDERSCORE_REFNAME = "refs/heads/_branch"; +static const char *REPO2_UNDERSCORE_REFNAME = "refs/remotes/repo1/_branch"; static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main"; static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main"; +static char *NEGATIVE_SPEC = "^refs/heads/_*"; void test_remote_fetch__initialize(void) { git_config *c; @@ -40,10 +43,10 @@ void test_remote_fetch__cleanup(void) { git_repository_free(repo2); cl_git_pass(git_futils_rmdir_r(repo1_path, NULL, GIT_RMDIR_REMOVE_FILES)); - free(repo1_path); + git__free(repo1_path); cl_git_pass(git_futils_rmdir_r(repo2_path, NULL, GIT_RMDIR_REMOVE_FILES)); - free(repo2_path); + git__free(repo2_path); } @@ -75,6 +78,7 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, /* create two commits in repo 1 and a reference to them */ { git_oid empty_tree_id; + git_commit *commit1; git_tree *empty_tree; git_signature *sig; git_treebuilder *tb; @@ -84,10 +88,12 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, cl_git_pass(git_signature_default(&sig, repo1)); cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, sig, NULL, "one", empty_tree, 0, NULL)); + cl_git_pass(git_commit_lookup(&commit1, repo1, commit1id)); cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, - sig, NULL, "two", empty_tree, 1, commit1id)); + sig, NULL, "two", empty_tree, 1, commit1)); git_tree_free(empty_tree); + git_commit_free(commit1); git_signature_free(sig); git_treebuilder_free(tb); } @@ -167,3 +173,61 @@ void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) { git_reference_free(ref); } + +/** + * This checks that negative refspecs are respected when fetching. We create a + * repository with a '_' prefixed reference. A second repository is configured + * with a negative refspec to ignore any refs prefixed with '_' and fetch the + * first repository into the second. + * + * @param commit1id A pointer to an OID which will be populated with the first + * commit. + */ +static void do_fetch_repo_with_ref_matching_negative_refspec(git_oid *commit1id) { + /* create a commit in repo 1 and a reference to it with '_' prefix */ + { + git_oid empty_tree_id; + git_tree *empty_tree; + git_signature *sig; + git_treebuilder *tb; + cl_git_pass(git_treebuilder_new(&tb, repo1, NULL)); + cl_git_pass(git_treebuilder_write(&empty_tree_id, tb)); + cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id)); + cl_git_pass(git_signature_default(&sig, repo1)); + cl_git_pass(git_commit_create(commit1id, repo1, REPO1_UNDERSCORE_REFNAME, sig, + sig, NULL, "one", empty_tree, 0, NULL)); + + git_tree_free(empty_tree); + git_signature_free(sig); + git_treebuilder_free(tb); + } + + /* fetch the remote with negative refspec for references prefixed with '_' */ + { + char *refspec_strs = { NEGATIVE_SPEC }; + git_strarray refspecs = { &refspec_strs, 1 }; + + git_remote *remote; + + cl_git_pass(git_remote_create_anonymous(&remote, repo2, + git_repository_path(repo1))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message")); + + git_remote_free(remote); + } +} + +void test_remote_fetch__skip_negative_refspec_match(void) { + git_oid commit1id; + git_reference *ref1; + git_reference *ref2; + + do_fetch_repo_with_ref_matching_negative_refspec(&commit1id); + + /* assert that the reference in exists in repo1 but not in repo2 */ + cl_git_pass(git_reference_lookup(&ref1, repo1, REPO1_UNDERSCORE_REFNAME)); + cl_assert_equal_b(git_reference_lookup(&ref2, repo2, REPO2_UNDERSCORE_REFNAME), GIT_ENOTFOUND); + + git_reference_free(ref1); + git_reference_free(ref2); +} diff --git a/tests/libgit2/repo/extensions.c b/tests/libgit2/repo/extensions.c index 105cdae1..cb62c53a 100644 --- a/tests/libgit2/repo/extensions.c +++ b/tests/libgit2/repo/extensions.c @@ -70,3 +70,13 @@ void test_repo_extensions__adds_extension(void) cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0); git_repository_free(extended); } + +void test_repo_extensions__preciousobjects(void) +{ + git_repository *extended = NULL; + + cl_repo_set_string(repo, "extensions.preciousObjects", "true"); + + cl_git_pass(git_repository_open(&extended, "empty_bare.git")); + git_repository_free(extended); +} diff --git a/tests/libgit2/repo/getters.c b/tests/libgit2/repo/getters.c index d401bb83..8e21d35b 100644 --- a/tests/libgit2/repo/getters.c +++ b/tests/libgit2/repo/getters.c @@ -51,3 +51,63 @@ void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) git_odb_free(odb); } + +void test_repo_getters__commit_parents(void) +{ + git_repository *repo; + git_commitarray parents; + git_oid first_parent; + git_oid merge_parents[4]; + + git_oid__fromstr(&first_parent, "099fabac3a9ea935598528c27f866e34089c2eff", GIT_OID_SHA1); + + /* A commit on a new repository has no parents */ + + cl_git_pass(git_repository_init(&repo, "new_repo", false)); + cl_git_pass(git_repository_commit_parents(&parents, repo)); + + cl_assert_equal_sz(0, parents.count); + cl_assert_equal_p(NULL, parents.commits); + + git_commitarray_dispose(&parents); + git_repository_free(repo); + + /* A standard commit has one parent */ + + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_commit_parents(&parents, repo)); + + cl_assert_equal_sz(1, parents.count); + cl_assert_equal_oid(&first_parent, git_commit_id(parents.commits[0])); + + git_commitarray_dispose(&parents); + + /* A merge commit has multiple parents */ + + cl_git_rewritefile("testrepo/.git/MERGE_HEAD", + "8496071c1b46c854b31185ea97743be6a8774479\n" + "5b5b025afb0b4c913b4c338a42934a3863bf3644\n" + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045\n" + "9fd738e8f7967c078dceed8190330fc8648ee56a\n"); + + cl_git_pass(git_repository_commit_parents(&parents, repo)); + + cl_assert_equal_sz(5, parents.count); + + cl_assert_equal_oid(&first_parent, git_commit_id(parents.commits[0])); + + git_oid__fromstr(&merge_parents[0], "8496071c1b46c854b31185ea97743be6a8774479", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[0], git_commit_id(parents.commits[1])); + git_oid__fromstr(&merge_parents[1], "5b5b025afb0b4c913b4c338a42934a3863bf3644", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[1], git_commit_id(parents.commits[2])); + git_oid__fromstr(&merge_parents[2], "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[2], git_commit_id(parents.commits[3])); + git_oid__fromstr(&merge_parents[3], "9fd738e8f7967c078dceed8190330fc8648ee56a", GIT_OID_SHA1); + cl_assert_equal_oid(&merge_parents[3], git_commit_id(parents.commits[4])); + + git_commitarray_dispose(&parents); + + git_repository_free(repo); + + cl_fixture_cleanup("testrepo"); +} diff --git a/tests/libgit2/repo/init.c b/tests/libgit2/repo/init.c index d78ec063..e23271d3 100644 --- a/tests/libgit2/repo/init.c +++ b/tests/libgit2/repo/init.c @@ -489,7 +489,7 @@ void test_repo_init__relative_gitdir(void) /* Verify gitlink */ cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); - cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); + cl_assert_equal_s("gitdir: ../my_repository/\n", dot_git_content.ptr); git_str_dispose(&dot_git_content); cleanup_repository("root"); @@ -526,7 +526,7 @@ void test_repo_init__relative_gitdir_2(void) /* Verify gitlink */ cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); - cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); + cl_assert_equal_s("gitdir: ../my_repository/\n", dot_git_content.ptr); git_str_dispose(&dot_git_content); cleanup_repository("root"); @@ -755,3 +755,28 @@ void test_repo_init__longpath(void) git_str_dispose(&path); #endif } + +void test_repo_init__absolute_path_with_backslashes(void) +{ +#ifdef GIT_WIN32 + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + char *c; + + cl_set_cleanup(&cleanup_repository, "path"); + + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "path/to/newrepo")); + + for (c = path.ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } + + initopts.flags |= GIT_REPOSITORY_INIT_MKDIR | GIT_REPOSITORY_INIT_MKPATH; + + cl_git_pass(git_repository_init_ext(&g_repo, path.ptr, &initopts)); + git_str_dispose(&path); +#else + clar__skip(); +#endif +} diff --git a/tests/libgit2/repo/new.c b/tests/libgit2/repo/new.c index d77e903f..5136e60b 100644 --- a/tests/libgit2/repo/new.c +++ b/tests/libgit2/repo/new.c @@ -5,7 +5,15 @@ void test_repo_new__has_nothing(void) { git_repository *repo; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_repository_new_options repo_opts = GIT_REPOSITORY_NEW_OPTIONS_INIT; + + repo_opts.oid_type = GIT_OID_SHA1; + + cl_git_pass(git_repository_new(&repo, &repo_opts)); +#else cl_git_pass(git_repository_new(&repo)); +#endif cl_assert_equal_b(true, git_repository_is_bare(repo)); cl_assert_equal_p(NULL, git_repository_path(repo)); cl_assert_equal_p(NULL, git_repository_workdir(repo)); @@ -16,7 +24,15 @@ void test_repo_new__is_bare_until_workdir_set(void) { git_repository *repo; +#ifdef GIT_EXPERIMENTAL_SHA256 + git_repository_new_options repo_opts = GIT_REPOSITORY_NEW_OPTIONS_INIT; + + repo_opts.oid_type = GIT_OID_SHA1; + + cl_git_pass(git_repository_new(&repo, &repo_opts)); +#else cl_git_pass(git_repository_new(&repo)); +#endif cl_assert_equal_b(true, git_repository_is_bare(repo)); cl_git_pass(git_repository_set_workdir(repo, clar_sandbox_path(), 0)); @@ -25,3 +41,37 @@ void test_repo_new__is_bare_until_workdir_set(void) git_repository_free(repo); } +void test_repo_new__sha1(void) +{ + git_repository *repo; + +#ifdef GIT_EXPERIMENTAL_SHA256 + git_repository_new_options repo_opts = GIT_REPOSITORY_NEW_OPTIONS_INIT; + + repo_opts.oid_type = GIT_OID_SHA1; + + cl_git_pass(git_repository_new(&repo, &repo_opts)); +#else + cl_git_pass(git_repository_new(&repo)); +#endif + cl_assert_equal_i(GIT_OID_SHA1, git_repository_oid_type(repo)); + + git_repository_free(repo); +} + +void test_repo_new__sha256(void) +{ +#ifndef GIT_EXPERIMENTAL_SHA256 + cl_skip(); +#else + git_repository *repo; + git_repository_new_options repo_opts = GIT_REPOSITORY_NEW_OPTIONS_INIT; + + repo_opts.oid_type = GIT_OID_SHA256; + + cl_git_pass(git_repository_new(&repo, &repo_opts)); + cl_assert_equal_i(GIT_OID_SHA256, git_repository_oid_type(repo)); + + git_repository_free(repo); +#endif +} diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 3d1a0620..d5855134 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -316,7 +316,7 @@ static void unposix_path(git_str *path) src = tgt = path->ptr; /* convert "/d/..." to "d:\..." */ - if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') { + if (src[0] == '/' && git__isalpha(src[1]) && src[2] == '/') { *tgt++ = src[1]; *tgt++ = ':'; *tgt++ = '\\'; @@ -533,15 +533,21 @@ void test_repo_open__validates_bare_repo_ownership(void) cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); } -void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) +static int test_safe_path(const char *path) { git_repository *repo; git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT, config_data = GIT_STR_INIT; + int error; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + /* + * Sandbox the fixture, and ensure that when we fake an owner + * of "other" that the repository cannot be opened (and fails + * with `GIT_EOWNER`). + */ cl_fixture_sandbox("empty_standard_repo"); cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); @@ -555,81 +561,179 @@ void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + git_str_clear(&config_data); git_str_printf(&config_data, "[foo]\n" \ "\tbar = Foobar\n" \ "\tbaz = Baz!\n" \ "[safe]\n" \ - "\tdirectory = /non/existent/path\n" \ - "\tdirectory = /\n" \ - "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s\n" \ - "\tdirectory = /tmp\n" \ + "\tdirectory = %s\n" \ "[bar]\n" \ "\tfoo = barfoo\n", - clar_sandbox_path(), "empty_standard_repo"); + path); cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + error = git_repository_open(&repo, "empty_standard_repo"); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); git_str_dispose(&config_data); + + return error; } -void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) +static int test_bare_safe_path(const char *path) { git_repository *repo; - git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT; + git_str config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; + int error; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_fixture_sandbox("testrepo.git"); git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with( - GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); /* Add safe.directory options to the global configuration */ git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - config_path.ptr); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - cl_git_rewritefile(config_filename.ptr, "[foo]\n" - "\tbar = Foobar\n" - "\tbaz = Baz!\n" - "[safe]\n" - "\tdirectory = *\n" - "[bar]\n" - "\tfoo = barfoo\n"); + git_str_printf(&config_data, + "[foo]\n" \ + "\tbar = Foobar\n" \ + "\tbaz = Baz!\n" \ + "[safe]\n" \ + "\tdirectory = /non/existent/path\n" \ + "\tdirectory = /\n" \ + "\tdirectory = c:\\\\temp\n" \ + "\tdirectory = %s\n" \ + "\tdirectory = /tmp\n" \ + "[bar]\n" \ + "\tfoo = barfoo\n", + path); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + error = git_repository_open(&repo, "testrepo.git"); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); + git_str_dispose(&config_data); + + return error; +} + +void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf(&path, "%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__safe_directory_fails_with_trailing_slash(void) +{ + git_str path = GIT_STR_INIT; + + /* + * "/tmp/foo/" is not permitted; safe path must be specified + * as "/tmp/foo" + */ + cl_git_pass(git_str_printf(&path, "%s/%s/", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) +{ + cl_git_pass(test_safe_path("*")); } void test_repo_open__can_allowlist_bare_gitdir(void) { + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf(&path, "%s/%s", + clar_sandbox_path(), "testrepo.git")); + cl_git_pass(test_bare_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) +{ + cl_git_pass(test_bare_safe_path("*")); +} + +void test_repo_open__can_handle_prefixed_safe_paths(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)/" becomes "%(prefix)//tmp/foo" - so + * "%(prefix)/" is stripped and means the literal path + * follows. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +#endif +} + +void test_repo_open__prefixed_safe_paths_must_have_two_slashes(void) +{ + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)" becomes "%(prefix)/tmp/foo" - so it's + * actually trying to look in the git prefix, for example, + * "/usr/local/tmp/foo", which we don't actually support. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_handle_win32_prefixed_safe_paths(void) +{ +#ifdef GIT_WIN32 git_repository *repo; - git_str config_path = GIT_STR_INIT, + git_str unc_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT, config_data = GIT_STR_INIT; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - cl_fixture_sandbox("testrepo.git"); + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + /* + * On Windows, we can generally map a local drive to a UNC path; + * for example C:\Foo\Bar becomes //localhost/C$/Foo/bar + */ + cl_git_pass(git_str_printf(&unc_path, "//localhost/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + + if (unc_path.ptr[13] != ':' || unc_path.ptr[14] != '/') + cl_skip(); + + unc_path.ptr[13] = '$'; git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, unc_path.ptr)); /* Add safe.directory options to the global configuration */ git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); @@ -638,64 +742,74 @@ void test_repo_open__can_allowlist_bare_gitdir(void) git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + /* The blank resets our sandbox directory and opening fails */ + git_str_printf(&config_data, - "[foo]\n" \ - "\tbar = Foobar\n" \ - "\tbaz = Baz!\n" \ - "[safe]\n" \ - "\tdirectory = /non/existent/path\n" \ - "\tdirectory = /\n" \ - "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s\n" \ - "\tdirectory = /tmp\n" \ - "[bar]\n" \ - "\tfoo = barfoo\n", - clar_sandbox_path(), "testrepo.git"); + "[safe]\n\tdirectory = %%(prefix)/%s\n", + unc_path.ptr); cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + cl_git_pass(git_repository_open(&repo, unc_path.ptr)); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); git_str_dispose(&config_data); + git_str_dispose(&unc_path); +#endif } -void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) +void test_repo_open__can_handle_win32_unc_safe_paths(void) { +#ifdef GIT_WIN32 git_repository *repo; - git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT; + git_str unc_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - cl_fixture_sandbox("testrepo.git"); + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + /* + * On Windows, we can generally map a local drive to a UNC path; + * for example C:\Foo\Bar becomes //localhost/C$/Foo/bar + */ + cl_git_pass(git_str_printf(&unc_path, "//localhost/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + + if (unc_path.ptr[13] != ':' || unc_path.ptr[14] != '/') + cl_skip(); + + unc_path.ptr[13] = '$'; git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with( - GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, unc_path.ptr)); /* Add safe.directory options to the global configuration */ git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - config_path.ptr); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - cl_git_rewritefile(config_filename.ptr, "[foo]\n" - "\tbar = Foobar\n" - "\tbaz = Baz!\n" - "[safe]\n" - "\tdirectory = *\n" - "[bar]\n" - "\tfoo = barfoo\n"); + /* The blank resets our sandbox directory and opening fails */ - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + git_str_printf(&config_data, + "[safe]\n\tdirectory = %s\n", + unc_path.ptr); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); + + cl_git_pass(git_repository_open(&repo, unc_path.ptr)); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); + git_str_dispose(&config_data); + git_str_dispose(&unc_path); +#endif } void test_repo_open__can_reset_safe_directory_list(void) diff --git a/tests/libgit2/repo/pathspec.c b/tests/libgit2/repo/pathspec.c index 5b86662b..0efc2a37 100644 --- a/tests/libgit2/repo/pathspec.c +++ b/tests/libgit2/repo/pathspec.c @@ -383,3 +383,33 @@ void test_repo_pathspec__in_memory(void) git_pathspec_free(ps); } + +void test_repo_pathspec__starstar(void) +{ + static char *strings[] = { "**/foo", "**/bar/baz" }; + git_strarray s = { strings, ARRAY_SIZE(strings) }; + git_pathspec *ps; + + cl_git_pass(git_pathspec_new(&ps, &s)); + + /* "**" "/foo" does *not* match top-level "foo" */ + cl_assert(!git_pathspec_matches_path(ps, 0, "foo")); + cl_assert(!git_pathspec_matches_path(ps, 0, "fooz")); + cl_assert(!git_pathspec_matches_path(ps, 0, "bar")); + + cl_assert(git_pathspec_matches_path(ps, 0, "asdf/foo")); + cl_assert(!git_pathspec_matches_path(ps, 0, "asdf/fooz")); + cl_assert(git_pathspec_matches_path(ps, 0, "a/b/c/foo")); + cl_assert(!git_pathspec_matches_path(ps, 0, "a/b/c/fooz")); + + cl_assert(git_pathspec_matches_path(ps, 0, "bar/foo")); + cl_assert(!git_pathspec_matches_path(ps, 0, "bar/baz")); + cl_assert(!git_pathspec_matches_path(ps, 0, "bar/foo/baz")); + + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "asdf/foo")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "a/b/c/foo")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "bar/foo")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "bar/baz")); + + git_pathspec_free(ps); +} diff --git a/tests/libgit2/repo/setters.c b/tests/libgit2/repo/setters.c index 5c91ed39..1ad38bb8 100644 --- a/tests/libgit2/repo/setters.c +++ b/tests/libgit2/repo/setters.c @@ -56,7 +56,7 @@ void test_repo_setters__setting_a_workdir_creates_a_gitlink(void) cl_git_pass(git_futils_readbuffer(&content, "./new_workdir/.git")); cl_assert(git__prefixcmp(git_str_cstr(&content), "gitdir: ") == 0); - cl_assert(git__suffixcmp(git_str_cstr(&content), "testrepo.git/") == 0); + cl_assert(git__suffixcmp(git_str_cstr(&content), "testrepo.git/\n") == 0); git_str_dispose(&content); cl_git_pass(git_repository_config(&cfg, repo)); diff --git a/tests/libgit2/repo/shallow.c b/tests/libgit2/repo/shallow.c index adb7a9e4..a3e3036c 100644 --- a/tests/libgit2/repo/shallow.c +++ b/tests/libgit2/repo/shallow.c @@ -35,5 +35,5 @@ void test_repo_shallow__clears_errors(void) { g_repo = cl_git_sandbox_init("testrepo.git"); cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); } diff --git a/tests/libgit2/repo/template.c b/tests/libgit2/repo/template.c index e8fe266c..2b1ecf88 100644 --- a/tests/libgit2/repo/template.c +++ b/tests/libgit2/repo/template.c @@ -228,8 +228,7 @@ void test_repo_template__extended_with_template_and_shared_mode(void) const char *repo_path; int filemode; - opts.flags = GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.flags = GIT_REPOSITORY_INIT_MKPATH; opts.template_path = "template"; opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; diff --git a/tests/libgit2/revert/workdir.c b/tests/libgit2/revert/workdir.c index 3e790b77..8d1246ef 100644 --- a/tests/libgit2/revert/workdir.c +++ b/tests/libgit2/revert/workdir.c @@ -374,7 +374,7 @@ void test_revert_workdir__conflict_use_ours(void) { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, }; - opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_USE_OURS; git_oid__fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45", GIT_OID_SHA1); cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); diff --git a/tests/libgit2/status/worktree.c b/tests/libgit2/status/worktree.c index efbf597a..8a2ea9cb 100644 --- a/tests/libgit2/status/worktree.c +++ b/tests/libgit2/status/worktree.c @@ -1360,3 +1360,13 @@ void test_status_worktree__at_head_parent(void) git_tree_free(parent_tree); git_status_list_free(statuslist); } + +void test_status_worktree__skip_hash(void) +{ + git_repository *repo = cl_git_sandbox_init("status_skiphash"); + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read(index, true)); + git_index_free(index); +} diff --git a/tests/libgit2/submodule/add.c b/tests/libgit2/submodule/add.c index a2a66e7f..5208c7fc 100644 --- a/tests/libgit2/submodule/add.c +++ b/tests/libgit2/submodule/add.c @@ -60,7 +60,7 @@ void test_submodule_add__url_absolute(void) /* Verify gitdir path is relative */ cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_libgit2" "/.git")); - cl_assert_equal_s("gitdir: ../.git/modules/sm_libgit2/", dot_git_content.ptr); + cl_assert_equal_s("gitdir: ../.git/modules/sm_libgit2/\n", dot_git_content.ptr); git_repository_free(repo); git_str_dispose(&dot_git_content); diff --git a/tests/libgit2/submodule/lookup.c b/tests/libgit2/submodule/lookup.c index febb7dfa..14a624ba 100644 --- a/tests/libgit2/submodule/lookup.c +++ b/tests/libgit2/submodule/lookup.c @@ -401,6 +401,24 @@ void test_submodule_lookup__prefix_name(void) git_submodule_free(sm); } +/* ".path" in name of submodule */ +void test_submodule_lookup__dotpath_in_name(void) +{ + sm_lookup_data data; + + cl_git_rewritefile( + "submod2/.gitmodules", "[submodule \"kwb.pathdict\"]\n" + " path = kwb.pathdict\n" + " url = ../Test_App\n" + "[submodule \"fakin.path.app\"]\n" + " path = fakin.path.app\n" + " url = ../Test_App\n"); + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(9, data.count); +} + void test_submodule_lookup__renamed(void) { const char *newpath = "sm_actually_changed"; diff --git a/tests/libgit2/submodule/repository_init.c b/tests/libgit2/submodule/repository_init.c index 39b55c40..f24ec1d2 100644 --- a/tests/libgit2/submodule/repository_init.c +++ b/tests/libgit2/submodule/repository_init.c @@ -24,7 +24,7 @@ void test_submodule_repository_init__basic(void) /* Verify gitlink */ cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_gitmodules_only" "/.git")); - cl_assert_equal_s("gitdir: ../.git/modules/sm_gitmodules_only/", dot_git_content.ptr); + cl_assert_equal_s("gitdir: ../.git/modules/sm_gitmodules_only/\n", dot_git_content.ptr); cl_assert(git_fs_path_isfile("submod2/" "sm_gitmodules_only" "/.git")); diff --git a/tests/libgit2/submodule/update.c b/tests/libgit2/submodule/update.c index 052a4a1f..674d1a4f 100644 --- a/tests/libgit2/submodule/update.c +++ b/tests/libgit2/submodule/update.c @@ -30,7 +30,7 @@ void test_submodule_update__uninitialized_submodule_no_init(void) } struct update_submodule_cb_payload { - int update_tips_called; + int update_refs_called; int checkout_progress_called; int checkout_notify_called; }; @@ -71,15 +71,16 @@ static int checkout_notify_cb( return 0; } -static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) +static int update_refs(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *data) { struct update_submodule_cb_payload *update_payload = data; GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); + GIT_UNUSED(spec); - update_payload->update_tips_called = 1; + update_payload->update_refs_called = 1; return 1; } @@ -96,7 +97,7 @@ void test_submodule_update__update_submodule(void) update_options.checkout_opts.progress_cb = checkout_progress_cb; update_options.checkout_opts.progress_payload = &update_payload; - update_options.fetch_opts.callbacks.update_tips = update_tips; + update_options.fetch_opts.callbacks.update_refs = update_refs; update_options.fetch_opts.callbacks.payload = &update_payload; /* get the submodule */ @@ -126,7 +127,7 @@ void test_submodule_update__update_submodule(void) /* verify that the expected callbacks have been called. */ cl_assert_equal_i(1, update_payload.checkout_progress_called); - cl_assert_equal_i(1, update_payload.update_tips_called); + cl_assert_equal_i(1, update_payload.update_refs_called); git_submodule_free(sm); } @@ -143,7 +144,7 @@ void test_submodule_update__update_submodule_with_path(void) update_options.checkout_opts.progress_cb = checkout_progress_cb; update_options.checkout_opts.progress_payload = &update_payload; - update_options.fetch_opts.callbacks.update_tips = update_tips; + update_options.fetch_opts.callbacks.update_refs = update_refs; update_options.fetch_opts.callbacks.payload = &update_payload; /* get the submodule */ @@ -173,7 +174,7 @@ void test_submodule_update__update_submodule_with_path(void) /* verify that the expected callbacks have been called. */ cl_assert_equal_i(1, update_payload.checkout_progress_called); - cl_assert_equal_i(1, update_payload.update_tips_called); + cl_assert_equal_i(1, update_payload.update_refs_called); git_submodule_free(sm); } diff --git a/tests/libgit2/trace/trace.c b/tests/libgit2/trace/trace.c index 097208bf..9fea5766 100644 --- a/tests/libgit2/trace/trace.c +++ b/tests/libgit2/trace/trace.c @@ -32,16 +32,11 @@ void test_trace_trace__cleanup(void) void test_trace_trace__sets(void) { -#ifdef GIT_TRACE cl_assert(git_trace_level() == GIT_TRACE_INFO); -#else - cl_skip(); -#endif } void test_trace_trace__can_reset(void) { -#ifdef GIT_TRACE cl_assert(git_trace_level() == GIT_TRACE_INFO); cl_git_pass(git_trace_set(GIT_TRACE_ERROR, trace_callback)); @@ -51,14 +46,10 @@ void test_trace_trace__can_reset(void) git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); cl_assert(written == 1); -#else - cl_skip(); -#endif } void test_trace_trace__can_unset(void) { -#ifdef GIT_TRACE cl_assert(git_trace_level() == GIT_TRACE_INFO); cl_git_pass(git_trace_set(GIT_TRACE_NONE, NULL)); @@ -67,40 +58,25 @@ void test_trace_trace__can_unset(void) cl_assert(written == 0); git_trace(GIT_TRACE_FATAL, "Hello %s!", "world"); cl_assert(written == 0); -#else - cl_skip(); -#endif } void test_trace_trace__skips_higher_level(void) { -#ifdef GIT_TRACE cl_assert(written == 0); git_trace(GIT_TRACE_DEBUG, "Hello %s!", "world"); cl_assert(written == 0); -#else - cl_skip(); -#endif } void test_trace_trace__writes(void) { -#ifdef GIT_TRACE cl_assert(written == 0); git_trace(GIT_TRACE_INFO, "Hello %s!", "world"); cl_assert(written == 1); -#else - cl_skip(); -#endif } void test_trace_trace__writes_lower_level(void) { -#ifdef GIT_TRACE cl_assert(written == 0); git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); cl_assert(written == 1); -#else - cl_skip(); -#endif } diff --git a/tests/libgit2/transport/register.c b/tests/libgit2/transport/register.c index 4d3c0972..1554afc2 100644 --- a/tests/libgit2/transport/register.c +++ b/tests/libgit2/transport/register.c @@ -144,6 +144,7 @@ static int custom_subtransport__action( git_remote_connect_options_dispose(&opts); *out = git__calloc(1, sizeof(git_smart_subtransport_stream)); + GIT_ERROR_CHECK_ALLOC(*out); (*out)->subtransport = transport; (*out)->read = custom_subtransport_stream__read; (*out)->write = custom_subtransport_stream__write; @@ -169,6 +170,7 @@ static void custom_subtransport__free(git_smart_subtransport *transport) static int custom_transport_callback(git_smart_subtransport **out, git_transport *owner, void *param) { struct custom_subtransport *subtransport = git__calloc(1, sizeof(struct custom_subtransport)); + GIT_ERROR_CHECK_ALLOC(subtransport); subtransport->called = (int *)param; subtransport->owner = owner; subtransport->subtransport.action = custom_subtransport__action; diff --git a/tests/libgit2/transport/ssh_exec.c b/tests/libgit2/transport/ssh_exec.c new file mode 100644 index 00000000..886c10ad --- /dev/null +++ b/tests/libgit2/transport/ssh_exec.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" +#include "git2/sys/remote.h" +#include "git2/sys/transport.h" + + +void test_transport_ssh_exec__reject_injection_username(void) +{ +#ifndef GIT_SSH_EXEC + cl_skip(); +#else + git_remote *remote; + git_repository *repo; + git_transport *transport; + const char *url = "-oProxyCommand=git@somehost:somepath"; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + + cl_git_pass(git_repository_init(&repo, "./transport-username", 0)); + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_transport_new(&transport, remote, url)); + cl_git_fail_with(-1, transport->connect(transport, url, + GIT_SERVICE_UPLOADPACK_LS, &opts)); + + transport->free(transport); + git_remote_free(remote); + git_repository_free(repo); +#endif +} + +void test_transport_ssh_exec__reject_injection_hostname(void) +{ +#ifndef GIT_SSH_EXEC + cl_skip(); +#else + git_remote *remote; + git_repository *repo; + git_transport *transport; + const char *url = "-oProxyCommand=somehost:somepath-hostname"; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + + cl_git_pass(git_repository_init(&repo, "./transport-hostname", 0)); + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_transport_new(&transport, remote, url)); + cl_git_fail_with(-1, transport->connect(transport, url, + GIT_SERVICE_UPLOADPACK_LS, &opts)); + + transport->free(transport); + git_remote_free(remote); + git_repository_free(repo); +#endif +} + +void test_transport_ssh_exec__reject_injection_path(void) +{ +#ifndef GIT_SSH_EXEC + cl_skip(); +#else + git_remote *remote; + git_repository *repo; + git_transport *transport; + const char *url = "git@somehost:-somepath"; + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + + cl_git_pass(git_repository_init(&repo, "./transport-path", 0)); + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_transport_new(&transport, remote, url)); + cl_git_fail_with(-1, transport->connect(transport, url, + GIT_SERVICE_UPLOADPACK_LS, &opts)); + + transport->free(transport); + git_remote_free(remote); + git_repository_free(repo); +#endif +} diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index 81dcfe1f..1fd1f75b 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -6,15 +6,19 @@ static worktree_fixture fixture = WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); +static worktree_fixture submodule = + WORKTREE_FIXTURE_INIT("submodules", "submodules-worktree-parent"); void test_worktree_config__initialize(void) { setup_fixture_worktree(&fixture); + setup_fixture_worktree(&submodule); } void test_worktree_config__cleanup(void) { cleanup_fixture_worktree(&fixture); + cleanup_fixture_worktree(&submodule); } void test_worktree_config__open(void) @@ -27,7 +31,7 @@ void test_worktree_config__open(void) git_config_free(cfg); } -void test_worktree_config__set(void) +void test_worktree_config__set_level_local(void) { git_config *cfg; int32_t val; @@ -45,3 +49,78 @@ void test_worktree_config__set(void) cl_assert_equal_i(val, 5); git_config_free(cfg); } + +void test_worktree_config__requires_extension(void) +{ + git_config *cfg; + git_config *wtcfg; + int extension = 0; + + /* + * the "submodules" repo does not have extensions.worktreeconfig + * set, the worktree configuration should not be available. + */ + cl_git_pass(git_repository_config(&cfg, submodule.repo)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&extension, cfg, "extensions.worktreeconfig")); + cl_assert_equal_i(0, extension); + cl_git_fail_with(GIT_ENOTFOUND, git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + git_config_free(cfg); + + /* the "testrepo" repo does have the configuration set. */ + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_bool(&extension, cfg, "extensions.worktreeconfig")); + cl_assert_equal_i(1, extension); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + git_config_free(wtcfg); + git_config_free(cfg); +} + +void test_worktree_config__exists(void) +{ + git_config *cfg, *wtcfg, *snap; + const char *str; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_repository_config(&wtcfg, fixture.worktree)); + + cl_git_pass(git_config_snapshot(&snap, cfg)); + cl_git_pass(git_config_get_string(&str, snap, "worktreetest.config")); + cl_assert_equal_s("mainrepo", str); + git_config_free(snap); + + cl_git_pass(git_config_snapshot(&snap, wtcfg)); + cl_git_pass(git_config_get_string(&str, snap, "worktreetest.config")); + cl_assert_equal_s("worktreerepo", str); + git_config_free(snap); + + git_config_free(cfg); + git_config_free(wtcfg); +} + +void test_worktree_config__set_level_worktree(void) +{ + git_config *cfg; + git_config *wtcfg; + int32_t val; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + cl_git_pass(git_config_set_int32(wtcfg, "worktree.specific", 42)); + + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + /* reopen to verify config has been set */ + git_config_free(cfg); + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + cl_git_fail_with(GIT_ENOTFOUND, git_config_delete_entry(cfg, "worktree.specific")); + + cl_git_pass(git_config_delete_entry(wtcfg, "worktree.specific")); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_int32(&val, cfg, "worktree.specific")); + + git_config_free(cfg); + git_config_free(wtcfg); +} diff --git a/tests/libgit2/worktree/refs.c b/tests/libgit2/worktree/refs.c index 557726aa..51e7b2b9 100644 --- a/tests/libgit2/worktree/refs.c +++ b/tests/libgit2/worktree/refs.c @@ -20,7 +20,7 @@ void test_worktree_refs__cleanup(void) cleanup_fixture_worktree(&fixture); } -void test_worktree_refs__list(void) +void test_worktree_refs__list_no_difference_in_worktree(void) { git_strarray refs, wtrefs; unsigned i, j; @@ -61,6 +61,66 @@ void test_worktree_refs__list(void) cl_git_pass(error); } +void test_worktree_refs__list_worktree_specific(void) +{ + git_strarray refs, wtrefs; + git_reference *ref, *new_branch; + int error = 0; + git_oid oid; + + cl_git_pass(git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_create( + &new_branch, fixture.worktree, "refs/bisect/a-bisect-ref", &oid, + 0, "test")); + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_lookup(&ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + cl_assert_equal_sz(wtrefs.count, refs.count + 1); + + git_reference_free(ref); + git_reference_free(new_branch); + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + +void test_worktree_refs__list_worktree_specific_hidden_in_main_repo(void) +{ + git_strarray refs, wtrefs; + git_reference *ref, *new_branch; + int error = 0; + git_oid oid; + + cl_git_pass( + git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup( + &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_create( + &new_branch, fixture.repo, "refs/bisect/a-bisect-ref", &oid, + 0, "test")); + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup( + &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_lookup( + &ref, fixture.repo, "refs/bisect/a-bisect-ref")); + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + cl_assert_equal_sz(refs.count, wtrefs.count + 1); + + git_reference_free(ref); + git_reference_free(new_branch); + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + void test_worktree_refs__read_head(void) { git_reference *head; diff --git a/tests/libgit2/worktree/worktree.c b/tests/libgit2/worktree/worktree.c index fed5c925..00e3e3fe 100644 --- a/tests/libgit2/worktree/worktree.c +++ b/tests/libgit2/worktree/worktree.c @@ -217,6 +217,50 @@ void test_worktree_worktree__init(void) git_repository_free(repo); } +void test_worktree_worktree__add_remove_add(void) +{ + git_worktree_add_options add_opts = GIT_WORKTREE_ADD_OPTIONS_INIT; + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_str path = GIT_BUF_INIT; + git_reference *branch; + git_repository *repo; + git_worktree *wt; + + /* Add the worktree */ + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-add-remove-add")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, NULL)); + + /* Open and verify created repo */ + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-add-remove-add/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_reference_free(branch); + git_repository_free(repo); + + /* Prune the worktree */ + opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_WORKING_TREE; + cl_git_pass(git_worktree_prune(wt, &opts)); + cl_assert(!git_fs_path_exists(wt->gitdir_path)); + cl_assert(!git_fs_path_exists(wt->gitlink_path)); + git_worktree_free(wt); + + /* Add the worktree back with default options should fail. */ + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, &add_opts)); + /* If allowing checkout of existing branches, it should succeed. */ + add_opts.checkout_existing = 1; + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, &add_opts)); + + /* Open and verify created repo */ + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-add-remove-add/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_reference_free(branch); + git_repository_free(repo); + + git_str_dispose(&path); + git_worktree_free(wt); +} + void test_worktree_worktree__add_locked(void) { git_worktree *wt; @@ -244,6 +288,7 @@ void test_worktree_worktree__add_locked(void) void test_worktree_worktree__init_existing_branch(void) { + git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; git_reference *head, *branch; git_commit *commit; git_worktree *wt; @@ -251,12 +296,18 @@ void test_worktree_worktree__init_existing_branch(void) cl_git_pass(git_repository_head(&head, fixture.repo)); cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); - cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false)); + cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new-exist", commit, false)); - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); - cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new-exist")); + + /* Add the worktree back with default options should fail. */ + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, NULL)); + /* If allowing checkout of existing branches, it should succeed. */ + opts.checkout_existing = 1; + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, &opts)); git_str_dispose(&path); + git_worktree_free(wt); git_commit_free(commit); git_reference_free(head); git_reference_free(branch); diff --git a/tests/resources/certs/README b/tests/resources/certs/README new file mode 100644 index 00000000..6a4fc4c6 --- /dev/null +++ b/tests/resources/certs/README @@ -0,0 +1,5 @@ +These are self-signed certificates for https://test.libgit2.org/. + +* one/ - contains certificates for https://test.libgit2.org:1443/ +* two.pem - contains a certificate for https://test.libgit2.org:2443/ +* three.pem - contains a certificate for https://test.libgit2.org:3443/ diff --git a/tests/resources/certs/one/61f2ddb6.0 b/tests/resources/certs/one/61f2ddb6.0 new file mode 100644 index 00000000..7d9ef6fc --- /dev/null +++ b/tests/resources/certs/one/61f2ddb6.0 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWzCCA0MCFESY816VkhBPUOsdp7djKW5q4ZVzMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlD +YW1icmlkZ2UxFDASBgNVBAoMC2xpYmdpdDIub3JnMRkwFwYDVQQDDBB0ZXN0Lmxp +YmdpdDIub3JnMB4XDTIxMDgyNTE4NTExMVoXDTMxMDgyMzE4NTExMVowajELMAkG +A1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJy +aWRnZTEUMBIGA1UECgwLbGliZ2l0Mi5vcmcxGTAXBgNVBAMMEHRlc3QubGliZ2l0 +Mi5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvaRUaM3IJh9N +G6Yc7tHioUsIGU0MkzSvy/X6O/vONnuuioiJQyPIvfRSvZR2iQj8THTypDGhWn3r +6h2wk5eOUGwJH2N9FrrlEBdpsMc7SKdiJXwTI30mkK3/qru8NzE71dgCkYp1xhKw +edTkAFK+PkvyVLFL7K35cx8Bxfamyssdb+qGWa7g4P27CWUdvQgmurrzzPIMZiLD +/cI1Kwer/N7nTY/6CSs9dcHTlanyZdf+mQ50+//vI4F6+OduGHJkxRF48jLUz1rz +P3WGRMRbHjCmvWpX/9DLgqGk7XTy0hNgNUCit6kawwcv5y7SP/ii86MkynAHn5i8 +d+zhXjdrSSy8i0IbRJafnxmtrsmjGeIzraJSRqMlv7KKWEBz+alm6vlePnRUbWB7 +0po5uSsRPya6kJJCzMjIfKq1dgXq33m9jCG2wU+L4fEHVlEkFGXYTspMlIBNUjTc +c45+e1EpamF8aHm32PP8gTF8fGZzQjOXmNW5g7t0joWMGZ+Ao2jYc1pG3SOARi36 +azrmB5/XJqbbfVZEzIue01fO/5R8RgabOP1qWUjH2KLb8zTDok+CW0ULNseU+MKf +PHXG2OjxcR0vTqop2V6JlKTXXx3/TOD16/+mSrrPzNDejLrkvAH9oN38YpMBM8eg +vfivHNRm0jjdGbv2OOPEBLEf1cNimQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBZ +znFta24sWoqdgKXKAK5RHAh/HyOvTInwcXi9RU4XjYlbqNVs0ODR74VRZINoyAL2 +bo+x/iUuAp9+b8fjr79fpVof3nSMU7UtMcT1nvzVmaUYSkKQ0f/9vK4yg0kao1bV +WwhIc0slKgOJjEicPVs3kd+duv5vakQeUajLPGM8SiS1F/nF67rIuZLdJn2Qp+im +w5Q3Pjgqw5VrJxyk3AaUcntKHpWy1POLyNV79tXra6BxbtQVlRS0+h1MHELARDFx +1ZtgyAe5YbWM7WrIiFKD4mmKZu4GMnJDXVpfUub5g0U/e7L/gg6Z1UyYZuln6axw +RojuAHo1uAWFUsjhWLYV/7P/l/dC+7gFjvSsUqb1+U7jXObzfKjXo/FwYcy4VsVv +xNbglbhdVjAo/YBTJuf3L0UZjSbxvQIYS+v8u1ECeWE6SH6cHRzryeo5wO4h8NJR +n30xsvocHFbs4LWy5BVfMUo6wGUy0Y+1gSwSqVMv3JPuLwxUsv0HPdeC00Ab9cHq +kYXPNZXg3a6orTDa4hJLdAm2V/fn/2KKJYlNj7iCL664QgoCHl7LFyLMiwFVCu5h +4JjGL3Q+8MondaLZlq5YDmvtj979AyM/7qL4XAE2oofQ4J5dqnKKpMkWdAM/fI/9 +N5DK/4zMXJWgIED0yo2SSZHQmuqZplacOhmfjjZigQ== +-----END CERTIFICATE----- diff --git a/tests/resources/certs/one/db4f60b0.0 b/tests/resources/certs/one/db4f60b0.0 new file mode 100644 index 00000000..7d9ef6fc --- /dev/null +++ b/tests/resources/certs/one/db4f60b0.0 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWzCCA0MCFESY816VkhBPUOsdp7djKW5q4ZVzMA0GCSqGSIb3DQEBCwUAMGox +CzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlD +YW1icmlkZ2UxFDASBgNVBAoMC2xpYmdpdDIub3JnMRkwFwYDVQQDDBB0ZXN0Lmxp +YmdpdDIub3JnMB4XDTIxMDgyNTE4NTExMVoXDTMxMDgyMzE4NTExMVowajELMAkG +A1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJy +aWRnZTEUMBIGA1UECgwLbGliZ2l0Mi5vcmcxGTAXBgNVBAMMEHRlc3QubGliZ2l0 +Mi5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvaRUaM3IJh9N +G6Yc7tHioUsIGU0MkzSvy/X6O/vONnuuioiJQyPIvfRSvZR2iQj8THTypDGhWn3r +6h2wk5eOUGwJH2N9FrrlEBdpsMc7SKdiJXwTI30mkK3/qru8NzE71dgCkYp1xhKw +edTkAFK+PkvyVLFL7K35cx8Bxfamyssdb+qGWa7g4P27CWUdvQgmurrzzPIMZiLD +/cI1Kwer/N7nTY/6CSs9dcHTlanyZdf+mQ50+//vI4F6+OduGHJkxRF48jLUz1rz +P3WGRMRbHjCmvWpX/9DLgqGk7XTy0hNgNUCit6kawwcv5y7SP/ii86MkynAHn5i8 +d+zhXjdrSSy8i0IbRJafnxmtrsmjGeIzraJSRqMlv7KKWEBz+alm6vlePnRUbWB7 +0po5uSsRPya6kJJCzMjIfKq1dgXq33m9jCG2wU+L4fEHVlEkFGXYTspMlIBNUjTc +c45+e1EpamF8aHm32PP8gTF8fGZzQjOXmNW5g7t0joWMGZ+Ao2jYc1pG3SOARi36 +azrmB5/XJqbbfVZEzIue01fO/5R8RgabOP1qWUjH2KLb8zTDok+CW0ULNseU+MKf +PHXG2OjxcR0vTqop2V6JlKTXXx3/TOD16/+mSrrPzNDejLrkvAH9oN38YpMBM8eg +vfivHNRm0jjdGbv2OOPEBLEf1cNimQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBZ +znFta24sWoqdgKXKAK5RHAh/HyOvTInwcXi9RU4XjYlbqNVs0ODR74VRZINoyAL2 +bo+x/iUuAp9+b8fjr79fpVof3nSMU7UtMcT1nvzVmaUYSkKQ0f/9vK4yg0kao1bV +WwhIc0slKgOJjEicPVs3kd+duv5vakQeUajLPGM8SiS1F/nF67rIuZLdJn2Qp+im +w5Q3Pjgqw5VrJxyk3AaUcntKHpWy1POLyNV79tXra6BxbtQVlRS0+h1MHELARDFx +1ZtgyAe5YbWM7WrIiFKD4mmKZu4GMnJDXVpfUub5g0U/e7L/gg6Z1UyYZuln6axw +RojuAHo1uAWFUsjhWLYV/7P/l/dC+7gFjvSsUqb1+U7jXObzfKjXo/FwYcy4VsVv +xNbglbhdVjAo/YBTJuf3L0UZjSbxvQIYS+v8u1ECeWE6SH6cHRzryeo5wO4h8NJR +n30xsvocHFbs4LWy5BVfMUo6wGUy0Y+1gSwSqVMv3JPuLwxUsv0HPdeC00Ab9cHq +kYXPNZXg3a6orTDa4hJLdAm2V/fn/2KKJYlNj7iCL664QgoCHl7LFyLMiwFVCu5h +4JjGL3Q+8MondaLZlq5YDmvtj979AyM/7qL4XAE2oofQ4J5dqnKKpMkWdAM/fI/9 +N5DK/4zMXJWgIED0yo2SSZHQmuqZplacOhmfjjZigQ== +-----END CERTIFICATE----- diff --git a/tests/resources/certs/three.pem b/tests/resources/certs/three.pem new file mode 100644 index 00000000..b63f0930 --- /dev/null +++ b/tests/resources/certs/three.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIUIGLmUoxDx3fh8dGDdrw81kDLWJgwDQYJKoZIhvcNAQEL +BQAwajELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNV +BAcMCUNhbWJyaWRnZTEUMBIGA1UECgwLbGliZ2l0Mi5vcmcxGTAXBgNVBAMMEHRl +c3QubGliZ2l0Mi5vcmcwHhcNMjQxMDAxMDkwODEzWhcNMzQwOTI5MDkwODEzWjBq +MQswCQYDVQQGEwJVUzEWMBQGA1UECAwNTWFzc2FjaHVzZXR0czESMBAGA1UEBwwJ +Q2FtYnJpZGdlMRQwEgYDVQQKDAtsaWJnaXQyLm9yZzEZMBcGA1UEAwwQdGVzdC5s +aWJnaXQyLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL7tLetm +ORd4uklvSeFn9eJfHCrhmNkWHd1VVuMhjcKkjk+N7GNdq1UMsSZU0xnJMICjJcnq +plZ9WnO6VkRibyIf/Xp5dh3sQh9jvuTNhvWAOYv9tjxX1sxA0WeHAZj/5qUMtvH3 +mr0gqNbtIrmEFAeWgcYSJbXhnWit7YEnGReHc17F50hbbLOgl5HLQAzIAEkj4nKX +hR8KKgyQk3dNfoc9orSjUxDp8fq9Z2prcEOYFmkItvC8rtqLB9EBJ1moGjB1BWEd +3Eg28TUXubRGwrTbpMWP5UWQWuS2Adnd/oQE7NVe5xhxzad8qlReEodF+ptSux5G +Nw+qy5l5GbMdNfgmXXp34vUldl8dH1BAtC0QH/fmAg5Ea7Ys0bnM9c0MRHJlX7QC +3fk/pVCSw7ozuceTyJyqhHMRUAcKeGJETJoBz/nmWm2oG5VjZ54k6ktvjjKNGpuL +44dR+xpZljESmHOm7c10AKBiuMjSG5i1o7wzWEDDfCPKo/5Uf5mMcTn78yrO83zt +yhIIYfm1kDTbAMMDo1MYHnsqXROxc0ShyLM5fLf7hWh+p8p0MrLUWbAtdETOOx9Y +qOla6qOGwmKmZaDR82GBCNcLkkqiuWC9oWb2YCtw7UB6cT7BSR7z8mLzfKrzryuJ +2IRawZ9JuuHmBMhvLmDWXoLy6zbxTc8eIaffAgMBAAGjUzBRMB0GA1UdDgQWBBTE +X4I8cLBkgHHuPS6wu1Z2CFVwNjAfBgNVHSMEGDAWgBTEX4I8cLBkgHHuPS6wu1Z2 +CFVwNjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBzgWp1dk0Y +ezID9uD4QRdKarPsRwH4EiXi4sk0udjrOljiPWrUTTvg4Zo/PszaJGmFUtoKGrSD +RqaCHMeSOT3NGuxqvfdSvxT6I7b6mDTRiJGncQ/h6Efj/9M04lHvfPsv33055ZF/ +FZBNznG+FYJM0iOMsgsRq95sM28/Z0eG65YJ/4o4Y9eC+zKUqC+ksVvrEDiinFPP +XPzrqJ5aW6TgUgQ583Q1u/Ijj3Z+LxlV3fe2OVgFKCL82XOsZPRCQq71vnNXA3KN +RpmXw4fNgZYmYDzUuEaeP+0jL38sTIZaHOEOPlghjny9c+UgqAWUEiN9M1HC59Dc +7ViTzRYlWUSbLOzh2vmT7nv7l9eRRNVp5gfWVhI6yu6lZ4tsH1BvMoJ3tgJ+c9pf +WMZEIKuErZl0u58Zs09ozzXaXXnMjx6pnTrqwMqaHJe5SFHh3XvvycGjCXRy4vjh +xs+BpmGqB44DRtYBbW6n4UDc481QVEoWd9DOQ7Y2OQJ1pHFTLLqkWSXw3KmO0cAX +51H9Ab46WfmYhrtmGNrYWUB7fGSsJ1C/4fiuo8Hx5sq+NP11H80q8TOBcPGRyeTj +K8L5y6T907tfZMlfloiBcbBFSbOf4+KCyVOYaM0O/LrroH+zZaNJiEP1By7Qsn/2 +Ekw0zxtlImc0SQ3NqnkfAsp6nfYMJPylZA== +-----END CERTIFICATE----- diff --git a/tests/resources/certs/three.pem.raw b/tests/resources/certs/three.pem.raw new file mode 100644 index 00000000..63e35f85 --- /dev/null +++ b/tests/resources/certs/three.pem.raw @@ -0,0 +1 @@ +MIIFtTCCA52gAwIBAgIUIGLmUoxDx3fh8dGDdrw81kDLWJgwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJyaWRnZTEUMBIGA1UECgwLbGliZ2l0Mi5vcmcxGTAXBgNVBAMMEHRlc3QubGliZ2l0Mi5vcmcwHhcNMjQxMDAxMDkwODEzWhcNMzQwOTI5MDkwODEzWjBqMQswCQYDVQQGEwJVUzEWMBQGA1UECAwNTWFzc2FjaHVzZXR0czESMBAGA1UEBwwJQ2FtYnJpZGdlMRQwEgYDVQQKDAtsaWJnaXQyLm9yZzEZMBcGA1UEAwwQdGVzdC5saWJnaXQyLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL7tLetmORd4uklvSeFn9eJfHCrhmNkWHd1VVuMhjcKkjk+N7GNdq1UMsSZU0xnJMICjJcnqplZ9WnO6VkRibyIf/Xp5dh3sQh9jvuTNhvWAOYv9tjxX1sxA0WeHAZj/5qUMtvH3mr0gqNbtIrmEFAeWgcYSJbXhnWit7YEnGReHc17F50hbbLOgl5HLQAzIAEkj4nKXhR8KKgyQk3dNfoc9orSjUxDp8fq9Z2prcEOYFmkItvC8rtqLB9EBJ1moGjB1BWEd3Eg28TUXubRGwrTbpMWP5UWQWuS2Adnd/oQE7NVe5xhxzad8qlReEodF+ptSux5GNw+qy5l5GbMdNfgmXXp34vUldl8dH1BAtC0QH/fmAg5Ea7Ys0bnM9c0MRHJlX7QC3fk/pVCSw7ozuceTyJyqhHMRUAcKeGJETJoBz/nmWm2oG5VjZ54k6ktvjjKNGpuL44dR+xpZljESmHOm7c10AKBiuMjSG5i1o7wzWEDDfCPKo/5Uf5mMcTn78yrO83ztyhIIYfm1kDTbAMMDo1MYHnsqXROxc0ShyLM5fLf7hWh+p8p0MrLUWbAtdETOOx9YqOla6qOGwmKmZaDR82GBCNcLkkqiuWC9oWb2YCtw7UB6cT7BSR7z8mLzfKrzryuJ2IRawZ9JuuHmBMhvLmDWXoLy6zbxTc8eIaffAgMBAAGjUzBRMB0GA1UdDgQWBBTEX4I8cLBkgHHuPS6wu1Z2CFVwNjAfBgNVHSMEGDAWgBTEX4I8cLBkgHHuPS6wu1Z2CFVwNjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBzgWp1dk0YezID9uD4QRdKarPsRwH4EiXi4sk0udjrOljiPWrUTTvg4Zo/PszaJGmFUtoKGrSDRqaCHMeSOT3NGuxqvfdSvxT6I7b6mDTRiJGncQ/h6Efj/9M04lHvfPsv33055ZF/FZBNznG+FYJM0iOMsgsRq95sM28/Z0eG65YJ/4o4Y9eC+zKUqC+ksVvrEDiinFPPXPzrqJ5aW6TgUgQ583Q1u/Ijj3Z+LxlV3fe2OVgFKCL82XOsZPRCQq71vnNXA3KNRpmXw4fNgZYmYDzUuEaeP+0jL38sTIZaHOEOPlghjny9c+UgqAWUEiN9M1HC59Dc7ViTzRYlWUSbLOzh2vmT7nv7l9eRRNVp5gfWVhI6yu6lZ4tsH1BvMoJ3tgJ+c9pfWMZEIKuErZl0u58Zs09ozzXaXXnMjx6pnTrqwMqaHJe5SFHh3XvvycGjCXRy4vjhxs+BpmGqB44DRtYBbW6n4UDc481QVEoWd9DOQ7Y2OQJ1pHFTLLqkWSXw3KmO0cAX51H9Ab46WfmYhrtmGNrYWUB7fGSsJ1C/4fiuo8Hx5sq+NP11H80q8TOBcPGRyeTjK8L5y6T907tfZMlfloiBcbBFSbOf4+KCyVOYaM0O/LrroH+zZaNJiEP1By7Qsn/2Ekw0zxtlImc0SQ3NqnkfAsp6nfYMJPylZA== \ No newline at end of file diff --git a/tests/resources/certs/two.pem b/tests/resources/certs/two.pem new file mode 100644 index 00000000..e13417e4 --- /dev/null +++ b/tests/resources/certs/two.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjsCFAb11im6DYQyGJ0GNQCIehXtegq6MA0GCSqGSIb3DQEBCwUAMGYx +CzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlD +YW1icmlkZ2UxEDAOBgNVBAoMB2xpYmdpdDIxGTAXBgNVBAMMEHRlc3QubGliZ2l0 +Mi5vcmcwHhcNMjEwODMwMDAyMTQyWhcNMzEwODI4MDAyMTQyWjBmMQswCQYDVQQG +EwJVUzEWMBQGA1UECAwNTWFzc2FjaHVzZXR0czESMBAGA1UEBwwJQ2FtYnJpZGdl +MRAwDgYDVQQKDAdsaWJnaXQyMRkwFwYDVQQDDBB0ZXN0LmxpYmdpdDIub3JnMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtqe6b1vnMni+z8Z+a2bGtykI +ITvBged15rn+0qG6Fz+sn9bYG+ceFupztFfoN3cVpUgQDBTzr3CaAx036BlV0z8i +CrG0Oh/XGL+9TITQLumEe4iGi8NoMSujBAyXPSNgmpzDmCTGrNFfmq3HzUtO8t3x +i8OT7d9qCVjFimLvZbgnfHGQ38xvt1XyPgYIVqDQczmMEZ5BdYWB0A1VmnWuP2dH +BgjwPEC3HwMmm1+PL0VoPTdvE5Su092Qdt8QsiA56466DQyll1d/omnOJfrK7z0N +OnfDmnDpARSTy6vDofEAYUQoc3dyvBUk8IIzv2UDcR7fTVvYqseQReIOTEnXmQID +AQABMA0GCSqGSIb3DQEBCwUAA4IBAQBmUEq+JhwWTbB5ODGOKrMG1fKJ+sf6ZH6M +c4BgLEcdoi/nOTfPuw+ols72LuhH7NKaEcqxWev0jGF0WKqMcM8AGVbywZJ3mBWo +sKdh6rAGFNkikW4TzhjtDfFbMR45Didl28Be7ieHQL4CQ0Lse3RMOxp250WpiEYV +W2hIKMwIqOLKGShVD7lI+eHlv+QSH4yOYKHfRHve8s82Tac5OXinc8CJm9ySOtkO +MfLgfkHtHdFBnV6OVbf4p/596MfMXdwT/bBxT6WPkDGc1AYhoDlmLFTpRgHIDCSK +2wgV+qHppl7Kn+p3mFQ9sW/1IaRd+jNZOrgZ8Uu5tJ00OaqR/LVG +-----END CERTIFICATE----- diff --git a/tests/resources/diff_format_email/.gitted/index b/tests/resources/diff_format_email/.gitted/index index 4514a6b9..7053c215 100644 Binary files a/tests/resources/diff_format_email/.gitted/index and b/tests/resources/diff_format_email/.gitted/index differ diff --git a/tests/resources/diff_format_email/.gitted/objects/7f/4c6d9d6ba363e3352f7c21807ca3a7835072b2 b/tests/resources/diff_format_email/.gitted/objects/7f/4c6d9d6ba363e3352f7c21807ca3a7835072b2 new file mode 100644 index 00000000..1be0e792 --- /dev/null +++ b/tests/resources/diff_format_email/.gitted/objects/7f/4c6d9d6ba363e3352f7c21807ca3a7835072b2 @@ -0,0 +1 @@ +xKj1EьLZ[JǢR@vZ;uB[JrN9z)Vb!.Rj +: +$ŘK:!c \˃Fキ[?Kby{; pA-wS[_i{WOGR \ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames new file mode 100644 index 00000000..89b4eea8 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames @@ -0,0 +1 @@ +ea789495e0a72efadcd0f86a48f4c9ed435bb8a3 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch new file mode 100644 index 00000000..1c6a9f4d --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch @@ -0,0 +1 @@ +ab347abd8cda4a0e3b8bb42bb620c0c72c7df779 diff --git a/tests/resources/process/cat.bat b/tests/resources/process/cat.bat new file mode 100644 index 00000000..af9b573c --- /dev/null +++ b/tests/resources/process/cat.bat @@ -0,0 +1,2 @@ +@ECHO OFF +FOR /F "tokens=*" %%a IN ('more') DO ECHO %%a diff --git a/tests/resources/process/env.cmd b/tests/resources/process/env.cmd new file mode 100644 index 00000000..62675cf9 --- /dev/null +++ b/tests/resources/process/env.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +SET diff --git a/tests/resources/process/helloworld.sh b/tests/resources/process/helloworld.sh new file mode 100755 index 00000000..0c4aefc3 --- /dev/null +++ b/tests/resources/process/helloworld.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Hello, world." diff --git a/tests/resources/process/pwd.bat b/tests/resources/process/pwd.bat new file mode 100644 index 00000000..82e4fb60 --- /dev/null +++ b/tests/resources/process/pwd.bat @@ -0,0 +1,2 @@ +@ECHO OFF +ECHO %CD% diff --git a/tests/resources/push.sh b/tests/resources/push.sh index 3e77fb53..648c2ad2 100644 --- a/tests/resources/push.sh +++ b/tests/resources/push.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash #creates push_src repo for libgit2 push tests. set -eu diff --git a/tests/resources/pushoptions.git/HEAD b/tests/resources/pushoptions.git/HEAD new file mode 100644 index 00000000..b870d826 --- /dev/null +++ b/tests/resources/pushoptions.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/resources/pushoptions.git/branches/.gitignore b/tests/resources/pushoptions.git/branches/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/pushoptions.git/config b/tests/resources/pushoptions.git/config new file mode 100644 index 00000000..23d39788 --- /dev/null +++ b/tests/resources/pushoptions.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[receive] + advertisePushOptions = true diff --git a/tests/resources/pushoptions.git/description b/tests/resources/pushoptions.git/description new file mode 100644 index 00000000..498b267a --- /dev/null +++ b/tests/resources/pushoptions.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/resources/pushoptions.git/hooks/pre-receive b/tests/resources/pushoptions.git/hooks/pre-receive new file mode 100755 index 00000000..24f48d34 --- /dev/null +++ b/tests/resources/pushoptions.git/hooks/pre-receive @@ -0,0 +1,3 @@ +#!/bin/sh +printf "${GIT_PUSH_OPTION_1}${GIT_PUSH_OPTION_2}${GIT_PUSH_OPTION_3}" > "${GIT_PUSH_OPTION_0}" +exit 0 diff --git a/tests/resources/pushoptions.git/info/exclude b/tests/resources/pushoptions.git/info/exclude new file mode 100644 index 00000000..a5196d1b --- /dev/null +++ b/tests/resources/pushoptions.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/resources/pushoptions.git/objects/info/.gitignore b/tests/resources/pushoptions.git/objects/info/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/pushoptions.git/objects/pack/.gitignore b/tests/resources/pushoptions.git/objects/pack/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/pushoptions.git/refs/heads/.gitignore b/tests/resources/pushoptions.git/refs/heads/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/pushoptions.git/refs/tags/.gitignore b/tests/resources/pushoptions.git/refs/tags/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/self-signed.pem.raw b/tests/resources/self-signed.pem.raw new file mode 100644 index 00000000..d16446ba --- /dev/null +++ b/tests/resources/self-signed.pem.raw @@ -0,0 +1 @@ +MIIDUzCCAjsCFAb11im6DYQyGJ0GNQCIehXtegq6MA0GCSqGSIb3DQEBCwUAMGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlDYW1icmlkZ2UxEDAOBgNVBAoMB2xpYmdpdDIxGTAXBgNVBAMMEHRlc3QubGliZ2l0Mi5vcmcwHhcNMjEwODMwMDAyMTQyWhcNMzEwODI4MDAyMTQyWjBmMQswCQYDVQQGEwJVUzEWMBQGA1UECAwNTWFzc2FjaHVzZXR0czESMBAGA1UEBwwJQ2FtYnJpZGdlMRAwDgYDVQQKDAdsaWJnaXQyMRkwFwYDVQQDDBB0ZXN0LmxpYmdpdDIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtqe6b1vnMni+z8Z+a2bGtykIITvBged15rn+0qG6Fz+sn9bYG+ceFupztFfoN3cVpUgQDBTzr3CaAx036BlV0z8iCrG0Oh/XGL+9TITQLumEe4iGi8NoMSujBAyXPSNgmpzDmCTGrNFfmq3HzUtO8t3xi8OT7d9qCVjFimLvZbgnfHGQ38xvt1XyPgYIVqDQczmMEZ5BdYWB0A1VmnWuP2dHBgjwPEC3HwMmm1+PL0VoPTdvE5Su092Qdt8QsiA56466DQyll1d/omnOJfrK7z0NOnfDmnDpARSTy6vDofEAYUQoc3dyvBUk8IIzv2UDcR7fTVvYqseQReIOTEnXmQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBmUEq+JhwWTbB5ODGOKrMG1fKJ+sf6ZH6Mc4BgLEcdoi/nOTfPuw+ols72LuhH7NKaEcqxWev0jGF0WKqMcM8AGVbywZJ3mBWosKdh6rAGFNkikW4TzhjtDfFbMR45Didl28Be7ieHQL4CQ0Lse3RMOxp250WpiEYVW2hIKMwIqOLKGShVD7lI+eHlv+QSH4yOYKHfRHve8s82Tac5OXinc8CJm9ySOtkOMfLgfkHtHdFBnV6OVbf4p/596MfMXdwT/bBxT6WPkDGc1AYhoDlmLFTpRgHIDCSK2wgV+qHppl7Kn+p3mFQ9sW/1IaRd+jNZOrgZ8Uu5tJ00OaqR/LVG \ No newline at end of file diff --git a/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG b/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG new file mode 100644 index 00000000..ea450f95 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +New file diff --git a/tests/resources/status_skiphash/.gitted/HEAD b/tests/resources/status_skiphash/.gitted/HEAD new file mode 100644 index 00000000..b870d826 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/resources/status_skiphash/.gitted/MERGE_RR b/tests/resources/status_skiphash/.gitted/MERGE_RR new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/status_skiphash/.gitted/config b/tests/resources/status_skiphash/.gitted/config new file mode 100644 index 00000000..16aebb68 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true +[index] + skipHash = true diff --git a/tests/resources/status_skiphash/.gitted/description b/tests/resources/status_skiphash/.gitted/description new file mode 100644 index 00000000..498b267a --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/resources/status_skiphash/.gitted/index b/tests/resources/status_skiphash/.gitted/index new file mode 100644 index 00000000..1963fe0d Binary files /dev/null and b/tests/resources/status_skiphash/.gitted/index differ diff --git a/tests/resources/status_skiphash/.gitted/info/exclude b/tests/resources/status_skiphash/.gitted/info/exclude new file mode 100644 index 00000000..a5196d1b --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/resources/status_skiphash/.gitted/logs/HEAD b/tests/resources/status_skiphash/.gitted/logs/HEAD new file mode 100644 index 00000000..35e1a747 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 34f4c90b237fcb4c677772a6093f3cba239c41a5 Parnic 1708097798 -0600 commit (initial): New file diff --git a/tests/resources/status_skiphash/.gitted/logs/refs/heads/main b/tests/resources/status_skiphash/.gitted/logs/refs/heads/main new file mode 100644 index 00000000..35e1a747 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 34f4c90b237fcb4c677772a6093f3cba239c41a5 Parnic 1708097798 -0600 commit (initial): New file diff --git a/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 b/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 new file mode 100644 index 00000000..0513158c Binary files /dev/null and b/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 differ diff --git a/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 b/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 new file mode 100644 index 00000000..7c48fa4f Binary files /dev/null and b/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 differ diff --git a/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c b/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c new file mode 100644 index 00000000..c685321c Binary files /dev/null and b/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c differ diff --git a/tests/resources/status_skiphash/.gitted/refs/heads/main b/tests/resources/status_skiphash/.gitted/refs/heads/main new file mode 100644 index 00000000..693a12b6 --- /dev/null +++ b/tests/resources/status_skiphash/.gitted/refs/heads/main @@ -0,0 +1 @@ +34f4c90b237fcb4c677772a6093f3cba239c41a5 diff --git a/tests/resources/status_skiphash/new_file b/tests/resources/status_skiphash/new_file new file mode 100644 index 00000000..badcfca3 --- /dev/null +++ b/tests/resources/status_skiphash/new_file @@ -0,0 +1 @@ +new_file diff --git a/tests/resources/testrepo/.gitted/config b/tests/resources/testrepo/.gitted/config index d0114012..04d750a9 100644 --- a/tests/resources/testrepo/.gitted/config +++ b/tests/resources/testrepo/.gitted/config @@ -3,6 +3,8 @@ filemode = true bare = false logallrefupdates = true +[extensions] + worktreeconfig = true [remote "test"] url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* diff --git a/tests/resources/testrepo/.gitted/config.worktree b/tests/resources/testrepo/.gitted/config.worktree new file mode 100644 index 00000000..df9f0caf --- /dev/null +++ b/tests/resources/testrepo/.gitted/config.worktree @@ -0,0 +1,2 @@ +[worktreetest] + config = mainrepo diff --git a/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree new file mode 100644 index 00000000..7a130a7a --- /dev/null +++ b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree @@ -0,0 +1,2 @@ +[worktreetest] + config = worktreerepo diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt index 232590ff..425c27dc 100644 --- a/tests/util/CMakeLists.txt +++ b/tests/util/CMakeLists.txt @@ -1,5 +1,9 @@ # util: the unit tests for libgit2's utility library +if(NOT "${CMAKE_VERSION}" VERSION_LESS 3.27) + cmake_policy(SET CMP0148 OLD) +endif() + set(Python_ADDITIONAL_VERSIONS 3 2.7) find_package(PythonInterp) @@ -14,6 +18,7 @@ set(TEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}") add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") add_definitions(-DCLAR_WIN32_LONGPATHS) +add_definitions(-DCLAR_HAS_REALPATH) add_definitions(-D_FILE_OFFSET_BITS=64) # Ensure that we do not use deprecated functions internally @@ -39,7 +44,6 @@ set_source_files_properties( add_executable(util_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) -set_target_properties(util_tests PROPERTIES C_STANDARD 90) set_target_properties(util_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) target_include_directories(util_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) diff --git a/tests/util/assert.c b/tests/util/assert.c index 3babc475..4644f353 100644 --- a/tests/util/assert.c +++ b/tests/util/assert.c @@ -92,7 +92,8 @@ void test_assert__argument_with_void_return_type(void) git_error_clear(); cl_assert_equal_p(foo, fn_returns_string(foo)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert_equal_i(GIT_ERROR_NONE, git_error_last()->klass); + cl_assert_equal_s("no error", git_error_last()->message); } void test_assert__internal(void) diff --git a/tests/util/errors.c b/tests/util/errors.c index 78654a75..f9b85a65 100644 --- a/tests/util/errors.c +++ b/tests/util/errors.c @@ -5,7 +5,10 @@ void test_errors__public_api(void) char *str_in_error; git_error_clear(); - cl_assert(git_error_last() == NULL); + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_set_oom(); @@ -23,7 +26,9 @@ void test_errors__public_api(void) cl_assert(str_in_error != NULL); git_error_clear(); - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); } #include "common.h" @@ -35,7 +40,9 @@ void test_errors__new_school(void) char *str_in_error; git_error_clear(); - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_set_oom(); /* internal fn */ @@ -53,7 +60,9 @@ void test_errors__new_school(void) cl_assert(str_in_error != NULL); git_error_clear(); - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); do { struct stat st; @@ -88,52 +97,32 @@ void test_errors__new_school(void) void test_errors__restore(void) { - git_error_state err_state = {0}; + git_error *last_error; git_error_clear(); - cl_assert(git_error_last() == NULL); - - cl_assert_equal_i(0, git_error_state_capture(&err_state, 0)); - - memset(&err_state, 0x0, sizeof(git_error_state)); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp("no error", git_error_last()->message) == 0); git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - - git_error_set(99, "Bar: %s", "foo"); - - git_error_state_restore(&err_state); - - cl_assert_equal_i(42, git_error_last()->klass); - cl_assert_equal_s("Foo: bar", git_error_last()->message); -} - -void test_errors__free_state(void) -{ - git_error_state err_state = {0}; + cl_assert(git_error_save(&last_error) == 0); git_error_clear(); - - git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp("no error", git_error_last()->message) == 0); git_error_set(99, "Bar: %s", "foo"); - git_error_state_free(&err_state); - - cl_assert_equal_i(99, git_error_last()->klass); - cl_assert_equal_s("Bar: foo", git_error_last()->message); + git_error_restore(last_error); - git_error_state_restore(&err_state); - - cl_assert(git_error_last() == NULL); + cl_assert(git_error_last()->klass == 42); + cl_assert(strcmp("Foo: bar", git_error_last()->message) == 0); } void test_errors__restore_oom(void) { - git_error_state err_state = {0}; + git_error *last_error; const git_error *oom_error = NULL; git_error_clear(); @@ -141,15 +130,18 @@ void test_errors__restore_oom(void) git_error_set_oom(); /* internal fn */ oom_error = git_error_last(); cl_assert(oom_error); + cl_assert(oom_error->klass == GIT_ERROR_NOMEMORY); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass); - cl_assert_equal_s("Out of memory", err_state.error_msg.message); + cl_assert(git_error_save(&last_error) == 0); + cl_assert(last_error->klass == GIT_ERROR_NOMEMORY); + cl_assert(strcmp("Out of memory", last_error->message) == 0); - git_error_state_restore(&err_state); + git_error_clear(); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp("no error", git_error_last()->message) == 0); + git_error_restore(last_error); cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); cl_assert_(git_error_last() == oom_error, "static oom error not restored"); @@ -204,11 +196,15 @@ void test_errors__integer_overflow_sets_oom(void) git_error_clear(); cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_clear(); cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69)); - cl_assert_equal_p(NULL, git_error_last()); + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NONE); + cl_assert(strcmp(git_error_last()->message, "no error") == 0); git_error_clear(); cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); diff --git a/tests/util/gitstr.c b/tests/util/gitstr.c index aea35565..37936b5a 100644 --- a/tests/util/gitstr.c +++ b/tests/util/gitstr.c @@ -691,17 +691,33 @@ void test_gitstr__puts_escaped(void) git_str a = GIT_STR_INIT; git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "")); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "", "")); cl_assert_equal_s("this is a test", a.ptr); git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\")); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", NULL, NULL)); + cl_assert_equal_s("this is a test", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\", "")); + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\", NULL)); cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__")); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__", NULL)); cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__", "!!")); + cl_assert_equal_s("th__i!!s__ !!__i!!s__ !!a__ !!test", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this' is' an' escape! ", "'!", "'\\", "'")); + cl_assert_equal_s("this'\\'' is'\\'' an'\\'' escape'\\!' ", a.ptr); + git_str_clear(&a); cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); diff --git a/tests/util/hashmap.c b/tests/util/hashmap.c new file mode 100644 index 00000000..1bd07208 --- /dev/null +++ b/tests/util/hashmap.c @@ -0,0 +1,227 @@ +#include "clar_libgit2.h" +#include "hashmap.h" +#include "hashmap_str.h" + +GIT_HASHMAP_STR_SETUP(git_hashmap_test, char *); + +static git_hashmap_test g_table; + +void test_hashmap__initialize(void) +{ + memset(&g_table, 0x0, sizeof(git_hashmap_test)); +} + +void test_hashmap__cleanup(void) +{ + git_hashmap_test_dispose(&g_table); +} + +void test_hashmap__0(void) +{ + cl_assert(git_hashmap_test_size(&g_table) == 0); +} + +static void insert_strings(git_hashmap_test *table, size_t count) +{ + size_t i, j, over; + char *str; + + for (i = 0; i < count; ++i) { + str = git__malloc(10); + for (j = 0; j < 10; ++j) + str[j] = 'a' + (i % 26); + str[9] = '\0'; + + /* if > 26, then encode larger value in first letters */ + for (j = 0, over = i / 26; over > 0; j++, over = over / 26) + str[j] = 'A' + (over % 26); + + cl_git_pass(git_hashmap_test_put(table, str, str)); + } + + cl_assert_equal_i(git_hashmap_test_size(table), count); +} + +void test_hashmap__inserted_strings_can_be_retrieved(void) +{ + char *str; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + size_t idx = 0; + + insert_strings(&g_table, 20); + + cl_assert(git_hashmap_test_contains(&g_table, "aaaaaaaaa")); + cl_assert(git_hashmap_test_contains(&g_table, "ggggggggg")); + cl_assert(!git_hashmap_test_contains(&g_table, "aaaaaaaab")); + cl_assert(!git_hashmap_test_contains(&g_table, "abcdefghi")); + + while (git_hashmap_test_iterate(&iter, NULL, &str, &g_table) == 0) { + idx++; + git__free(str); + } + + cl_assert_equal_sz(20, idx); +} + +void test_hashmap__deleted_entry_cannot_be_retrieved(void) +{ + const char *key; + char *str; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + size_t idx = 0; + + insert_strings(&g_table, 20); + + cl_assert(git_hashmap_test_contains(&g_table, "bbbbbbbbb")); + cl_git_pass(git_hashmap_test_get(&str, &g_table, "bbbbbbbbb")); + cl_assert_equal_s(str, "bbbbbbbbb"); + cl_git_pass(git_hashmap_test_remove(&g_table, "bbbbbbbbb")); + git__free(str); + + cl_assert(!git_hashmap_test_contains(&g_table, "bbbbbbbbb")); + + while (git_hashmap_test_iterate(&iter, &key, &str, &g_table) == 0) { + idx++; + git__free(str); + } + + cl_assert_equal_sz(idx, 19); +} + +void test_hashmap__inserting_many_keys_succeeds(void) +{ + char *str; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + size_t idx = 0; + + insert_strings(&g_table, 10000); + + while (git_hashmap_test_iterate(&iter, NULL, &str, &g_table) == 0) { + idx++; + git__free(str); + } + + cl_assert_equal_sz(idx, 10000); +} + +void test_hashmap__get_succeeds_with_existing_entries(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + char *str; + uint32_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_hashmap_test_put(&g_table, keys[i], values[i])); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "foo")); + cl_assert_equal_s(str, "oof"); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "bar")); + cl_assert_equal_s(str, "rab"); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "gobble")); + cl_assert_equal_s(str, "elbbog"); +} + +void test_hashmap__get_returns_notfound_on_nonexisting_key(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + char *str; + uint32_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_hashmap_test_put(&g_table, keys[i], values[i])); + + cl_git_fail_with(GIT_ENOTFOUND, git_hashmap_test_get(&str, &g_table, "other")); +} + +void test_hashmap__put_persists_key(void) +{ + char *str; + + cl_git_pass(git_hashmap_test_put(&g_table, "foo", "oof")); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "foo")); + cl_assert_equal_s(str, "oof"); +} + +void test_hashmap__put_persists_multpile_keys(void) +{ + char *str; + + cl_git_pass(git_hashmap_test_put(&g_table, "foo", "oof")); + cl_git_pass(git_hashmap_test_put(&g_table, "bar", "rab")); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "foo")); + cl_assert_equal_s(str, "oof"); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "bar")); + cl_assert_equal_s(str, "rab"); +} + +void test_hashmap__put_updates_existing_key(void) +{ + char *str; + + cl_git_pass(git_hashmap_test_put(&g_table, "foo", "oof")); + cl_git_pass(git_hashmap_test_put(&g_table, "bar", "rab")); + cl_git_pass(git_hashmap_test_put(&g_table, "gobble", "elbbog")); + cl_assert_equal_i(3, git_hashmap_test_size(&g_table)); + + cl_git_pass(git_hashmap_test_put(&g_table, "foo", "other")); + cl_assert_equal_i(git_hashmap_test_size(&g_table), 3); + + cl_git_pass(git_hashmap_test_get(&str, &g_table, "foo")); + cl_assert_equal_s(str, "other"); +} + +void test_hashmap__iteration(void) +{ + struct { + char *key; + char *value; + int seen; + } entries[] = { + { "foo", "oof" }, + { "bar", "rab" }, + { "gobble", "elbbog" }, + }; + const char *key; + char *value; + uint32_t i, n; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_git_pass(git_hashmap_test_put(&g_table, entries[i].key, entries[i].value)); + + i = 0, n = 0; + while (git_hashmap_test_iterate(&iter, &key, &value, &g_table) == 0) { + size_t j; + + for (j = 0; j < ARRAY_SIZE(entries); j++) { + if (strcmp(entries[j].key, key)) + continue; + + cl_assert_equal_i(entries[j].seen, 0); + cl_assert_equal_s(entries[j].value, value); + entries[j].seen++; + break; + } + + n++; + } + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_assert_equal_i(entries[i].seen, 1); + + cl_assert_equal_i(n, ARRAY_SIZE(entries)); +} + +void test_hashmap__iterating_empty_map_stops_immediately(void) +{ + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + + cl_git_fail_with(GIT_ITEROVER, git_hashmap_test_iterate(&iter, NULL, NULL, &g_table)); +} diff --git a/tests/util/path.c b/tests/util/path.c deleted file mode 100644 index 02ec42fc..00000000 --- a/tests/util/path.c +++ /dev/null @@ -1,768 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "fs_path.h" - -#ifndef GIT_WIN32 -# include -#endif - -static char *path_save; - -void test_path__initialize(void) -{ - path_save = cl_getenv("PATH"); -} - -void test_path__cleanup(void) -{ - cl_setenv("PATH", path_save); - git__free(path_save); - path_save = NULL; -} - -static void -check_dirname(const char *A, const char *B) -{ - git_str dir = GIT_STR_INIT; - char *dir2; - - cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); - cl_assert_equal_s(B, dir.ptr); - git_str_dispose(&dir); - - cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); - cl_assert_equal_s(B, dir2); - git__free(dir2); -} - -static void -check_basename(const char *A, const char *B) -{ - git_str base = GIT_STR_INIT; - char *base2; - - cl_assert(git_fs_path_basename_r(&base, A) >= 0); - cl_assert_equal_s(B, base.ptr); - git_str_dispose(&base); - - cl_assert((base2 = git_fs_path_basename(A)) != NULL); - cl_assert_equal_s(B, base2); - git__free(base2); -} - -static void -check_joinpath(const char *path_a, const char *path_b, const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void -check_joinpath_n( - const char *path_a, - const char *path_b, - const char *path_c, - const char *path_d, - const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&joined_path, '/', 4, - path_a, path_b, path_c, path_d)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void check_setenv(const char* name, const char* value) -{ - char* check; - - cl_git_pass(cl_setenv(name, value)); - check = cl_getenv(name); - - if (value) - cl_assert_equal_s(value, check); - else - cl_assert(check == NULL); - - git__free(check); -} - -/* get the dirname of a path */ -void test_path__00_dirname(void) -{ - check_dirname(NULL, "."); - check_dirname("", "."); - check_dirname("a", "."); - check_dirname("/", "/"); - check_dirname("/usr", "/"); - check_dirname("/usr/", "/"); - check_dirname("/usr/lib", "/usr"); - check_dirname("/usr/lib/", "/usr"); - check_dirname("/usr/lib//", "/usr"); - check_dirname("usr/lib", "usr"); - check_dirname("usr/lib/", "usr"); - check_dirname("usr/lib//", "usr"); - check_dirname(".git/", "."); - - check_dirname(REP16("/abc"), REP15("/abc")); - -#ifdef GIT_WIN32 - check_dirname("C:/", "C:/"); - check_dirname("C:", "C:/"); - check_dirname("C:/path/", "C:/"); - check_dirname("C:/path", "C:/"); - check_dirname("//computername/", "//computername/"); - check_dirname("//computername", "//computername/"); - check_dirname("//computername/path/", "//computername/"); - check_dirname("//computername/path", "//computername/"); - check_dirname("//computername/sub/path/", "//computername/sub"); - check_dirname("//computername/sub/path", "//computername/sub"); -#endif -} - -/* get the base name of a path */ -void test_path__01_basename(void) -{ - check_basename(NULL, "."); - check_basename("", "."); - check_basename("a", "a"); - check_basename("/", "/"); - check_basename("/usr", "usr"); - check_basename("/usr/", "usr"); - check_basename("/usr/lib", "lib"); - check_basename("/usr/lib//", "lib"); - check_basename("usr/lib", "lib"); - - check_basename(REP16("/abc"), "abc"); - check_basename(REP1024("/abc"), "abc"); -} - -/* properly join path components */ -void test_path__05_joins(void) -{ - check_joinpath("", "", ""); - check_joinpath("", "a", "a"); - check_joinpath("", "/a", "/a"); - check_joinpath("a", "", "a/"); - check_joinpath("a", "/", "a/"); - check_joinpath("a", "b", "a/b"); - check_joinpath("/", "a", "/a"); - check_joinpath("/", "", "/"); - check_joinpath("/a", "/b", "/a/b"); - check_joinpath("/a", "/b/", "/a/b/"); - check_joinpath("/a/", "b/", "/a/b/"); - check_joinpath("/a/", "/b/", "/a/b/"); - - check_joinpath("/abcd", "/defg", "/abcd/defg"); - check_joinpath("/abcd", "/defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); - check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); - check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); - - check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); - check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); - check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); - - check_joinpath(REP1024("aaaa"), REP1024("bbbb"), - REP1024("aaaa") "/" REP1024("bbbb")); - check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), - REP1024("/aaaa") REP1024("/bbbb")); -} - -/* properly join path components for more than one path */ -void test_path__06_long_joins(void) -{ - check_joinpath_n("", "", "", "", ""); - check_joinpath_n("", "a", "", "", "a/"); - check_joinpath_n("a", "", "", "", "a/"); - check_joinpath_n("", "", "", "a", "a"); - check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); - check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); - check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); - check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); - check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); - - check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), - REP1024("a") "/" REP1024("b") "/" - REP1024("c") "/" REP1024("d")); - check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), - REP1024("/a") REP1024("/b") - REP1024("/c") REP1024("/d")); -} - - -static void -check_path_to_dir( - const char* path, - const char* expected) -{ - git_str tgt = GIT_STR_INIT; - - git_str_sets(&tgt, path); - cl_git_pass(git_fs_path_to_dir(&tgt)); - cl_assert_equal_s(expected, tgt.ptr); - - git_str_dispose(&tgt); -} - -static void -check_string_to_dir( - const char* path, - size_t maxlen, - const char* expected) -{ - size_t len = strlen(path); - char *buf = git__malloc(len + 2); - cl_assert(buf); - - strncpy(buf, path, len + 2); - - git_fs_path_string_to_dir(buf, maxlen); - - cl_assert_equal_s(expected, buf); - - git__free(buf); -} - -/* convert paths to dirs */ -void test_path__07_path_to_dir(void) -{ - check_path_to_dir("", ""); - check_path_to_dir(".", "./"); - check_path_to_dir("./", "./"); - check_path_to_dir("a/", "a/"); - check_path_to_dir("ab", "ab/"); - /* make sure we try just under and just over an expansion that will - * require a realloc - */ - check_path_to_dir("abcdef", "abcdef/"); - check_path_to_dir("abcdefg", "abcdefg/"); - check_path_to_dir("abcdefgh", "abcdefgh/"); - check_path_to_dir("abcdefghi", "abcdefghi/"); - check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); - check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); - - check_string_to_dir("", 1, ""); - check_string_to_dir(".", 1, "."); - check_string_to_dir(".", 2, "./"); - check_string_to_dir(".", 3, "./"); - check_string_to_dir("abcd", 3, "abcd"); - check_string_to_dir("abcd", 4, "abcd"); - check_string_to_dir("abcd", 5, "abcd/"); - check_string_to_dir("abcd", 6, "abcd/"); -} - -/* join path to itself */ -void test_path__08_self_join(void) -{ - git_str path = GIT_STR_INIT; - size_t asize = 0; - - asize = path.asize; - cl_git_pass(git_str_sets(&path, "/foo")); - cl_assert_equal_s(path.ptr, "/foo"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); - cl_git_pass(git_str_sets(&path, "/foo/bar")); - - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); - cl_assert_equal_s(path.ptr, "/bar/baz"); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); - cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); -} - -static void check_percent_decoding(const char *expected_result, const char *input) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git__percent_decode(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -void test_path__09_percent_decode(void) -{ - check_percent_decoding("abcd", "abcd"); - check_percent_decoding("a2%", "a2%"); - check_percent_decoding("a2%3", "a2%3"); - check_percent_decoding("a2%%3", "a2%%3"); - check_percent_decoding("a2%3z", "a2%3z"); - check_percent_decoding("a,", "a%2c"); - check_percent_decoding("a21", "a2%31"); - check_percent_decoding("a2%1", "a2%%31"); - check_percent_decoding("a bc ", "a%20bc%20"); - check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); -} - -static void check_fromurl(const char *expected_result, const char *input, int should_fail) -{ - git_str buf = GIT_STR_INIT; - - assert(should_fail || expected_result); - - if (!should_fail) { - cl_git_pass(git_fs_path_fromurl(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - } else - cl_git_fail(git_fs_path_fromurl(&buf, input)); - - git_str_dispose(&buf); -} - -#ifdef GIT_WIN32 -#define ABS_PATH_MARKER "" -#else -#define ABS_PATH_MARKER "/" -#endif - -void test_path__10_fromurl(void) -{ - /* Failing cases */ - check_fromurl(NULL, "a", 1); - check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:///", 1); - check_fromurl(NULL, "file:////", 1); - check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); - - /* Passing cases */ - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); -} - -typedef struct { - int expect_idx; - int cancel_after; - char **expect; -} check_walkup_info; - -#define CANCEL_VALUE 1234 - -static int check_one_walkup_step(void *ref, const char *path) -{ - check_walkup_info *info = (check_walkup_info *)ref; - - if (!info->cancel_after) { - cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); - return CANCEL_VALUE; - } - info->cancel_after--; - - cl_assert(info->expect[info->expect_idx] != NULL); - cl_assert_equal_s(info->expect[info->expect_idx], path); - info->expect_idx++; - - return 0; -} - -void test_path__11_walkup(void) -{ - git_str p = GIT_STR_INIT; - - char *expect[] = { - /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 7 */ "this_is_a_path", "", NULL, - /* 8 */ "this_is_a_path/", "", NULL, - /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, - /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, - /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, - /* 12 */ "a/b/c/", "a/b/", "a/", NULL, - /* 13 */ "", NULL, - /* 14 */ "/", NULL, - /* 15 */ NULL - }; - - char *root[] = { - /* 1 */ NULL, - /* 2 */ NULL, - /* 3 */ "/", - /* 4 */ "", - /* 5 */ "/a/b", - /* 6 */ "/a/b/", - /* 7 */ NULL, - /* 8 */ NULL, - /* 9 */ NULL, - /* 10 */ NULL, - /* 11 */ NULL, - /* 12 */ "a/", - /* 13 */ NULL, - /* 14 */ NULL, - }; - - int i, j; - check_walkup_info info; - - info.expect = expect; - info.cancel_after = -1; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.expect_idx = i; - cl_git_pass( - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - cl_assert_equal_s(p.ptr, expect[i]); - cl_assert(expect[info.expect_idx] == NULL); - i = info.expect_idx; - } - - git_str_dispose(&p); -} - -void test_path__11a_walkup_cancel(void) -{ - git_str p = GIT_STR_INIT; - int cancel[] = { 3, 2, 1, 0 }; - char *expect[] = { - "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, - "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, - "/a/b/c/d/e", "[CANCEL]", NULL, - "[CANCEL]", NULL, - NULL - }; - char *root[] = { NULL, NULL, "/", "", NULL }; - int i, j; - check_walkup_info info; - - info.expect = expect; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.cancel_after = cancel[j]; - info.expect_idx = i; - - cl_assert_equal_i( - CANCEL_VALUE, - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - /* skip to next run of expectations */ - while (expect[i] != NULL) i++; - } - - git_str_dispose(&p); -} - -void test_path__12_offset_to_path_root(void) -{ - cl_assert(git_fs_path_root("non/rooted/path") == -1); - cl_assert(git_fs_path_root("/rooted/path") == 0); - -#ifdef GIT_WIN32 - /* Windows specific tests */ - cl_assert(git_fs_path_root("C:non/rooted/path") == -1); - cl_assert(git_fs_path_root("C:/rooted/path") == 2); - cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); - cl_assert(git_fs_path_root("//computername/sharefolder") == 14); - cl_assert(git_fs_path_root("//computername") == -1); -#endif -} - -#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" - -void test_path__13_cannot_prettify_a_non_existing_file(void) -{ - git_str p = GIT_STR_INIT; - - cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); - - git_str_dispose(&p); -} - -void test_path__14_apply_relative(void) -{ - git_str p = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&p, "/this/is/a/base")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../test")); - cl_assert_equal_s("/this/is/a/test", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); - cl_assert_equal_s("/this/is/the/end", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); - cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); - cl_assert_equal_s("/this/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../")); - cl_assert_equal_s("/", p.ptr); - - cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); - - - cl_git_pass(git_str_sets(&p, "d:/another/test")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../..")); - cl_assert_equal_s("d:/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); - cl_assert_equal_s("d:/from/here/and/back/", p.ptr); - - - cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); - cl_assert_equal_s("https://my.url.com/another.git", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); - cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "..")); - cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); - cl_assert_equal_s("https://", p.ptr); - - - cl_git_pass(git_str_sets(&p, "../../this/is/relative")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); - cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); - cl_assert_equal_s("../../that", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../there")); - cl_assert_equal_s("../../there", p.ptr); - git_str_dispose(&p); -} - -static void assert_resolve_relative( - git_str *buf, const char *expected, const char *path) -{ - cl_git_pass(git_str_sets(buf, path)); - cl_git_pass(git_fs_path_resolve_relative(buf, 0)); - cl_assert_equal_s(expected, buf->ptr); -} - -void test_path__15_resolve_relative(void) -{ - git_str buf = GIT_STR_INIT; - - assert_resolve_relative(&buf, "", ""); - assert_resolve_relative(&buf, "", "."); - assert_resolve_relative(&buf, "", "./"); - assert_resolve_relative(&buf, "..", ".."); - assert_resolve_relative(&buf, "../", "../"); - assert_resolve_relative(&buf, "..", "./.."); - assert_resolve_relative(&buf, "../", "./../"); - assert_resolve_relative(&buf, "../", "../."); - assert_resolve_relative(&buf, "../", ".././"); - assert_resolve_relative(&buf, "../..", "../.."); - assert_resolve_relative(&buf, "../../", "../../"); - - assert_resolve_relative(&buf, "/", "/"); - assert_resolve_relative(&buf, "/", "/."); - - assert_resolve_relative(&buf, "", "a/.."); - assert_resolve_relative(&buf, "", "a/../"); - assert_resolve_relative(&buf, "", "a/../."); - - assert_resolve_relative(&buf, "/a", "/a"); - assert_resolve_relative(&buf, "/a/", "/a/."); - assert_resolve_relative(&buf, "/", "/a/../"); - assert_resolve_relative(&buf, "/", "/a/../."); - assert_resolve_relative(&buf, "/", "/a/.././"); - - assert_resolve_relative(&buf, "a", "a"); - assert_resolve_relative(&buf, "a/", "a/"); - assert_resolve_relative(&buf, "a/", "a/."); - assert_resolve_relative(&buf, "a/", "a/./"); - - assert_resolve_relative(&buf, "a/b", "a//b"); - assert_resolve_relative(&buf, "a/b/c", "a/b/c"); - assert_resolve_relative(&buf, "b/c", "./b/c"); - assert_resolve_relative(&buf, "a/c", "a/./c"); - assert_resolve_relative(&buf, "a/b/", "a/b/."); - - assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); - assert_resolve_relative(&buf, "/", "////"); - assert_resolve_relative(&buf, "/a", "///a"); - assert_resolve_relative(&buf, "/", "///."); - assert_resolve_relative(&buf, "/", "///a/.."); - - assert_resolve_relative(&buf, "../../path", "../../test//../././path"); - assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); - - cl_git_pass(git_str_sets(&buf, "/..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/./..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/.//..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.././../a")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "////..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - /* things that start with Windows network paths */ -#ifdef GIT_WIN32 - assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "//a/", "//a/b/.."); - assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); - - cl_git_pass(git_str_sets(&buf, "//a/b/../..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); -#else - assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "/a/", "//a/b/.."); - assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); - assert_resolve_relative(&buf, "/", "//a/b/../.."); -#endif - - git_str_dispose(&buf); -} - -#define assert_common_dirlen(i, p, q) \ - cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); - -void test_path__16_resolve_relative(void) -{ - assert_common_dirlen(0, "", ""); - assert_common_dirlen(0, "", "bar.txt"); - assert_common_dirlen(0, "foo.txt", "bar.txt"); - assert_common_dirlen(0, "foo.txt", ""); - assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); - assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); - - assert_common_dirlen(1, "/one.txt", "/two.txt"); - assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); - assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); - - assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); - assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); -} - -static void fix_path(git_str *s) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(s); -#else - char* c; - - for (c = s->ptr; *c; c++) { - if (*c == '/') - *c = '\\'; - } -#endif -} - -void test_path__find_exe_in_path(void) -{ - char *orig_path; - git_str sandbox_path = GIT_STR_INIT; - git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, - dummy_path = GIT_STR_INIT; - -#ifdef GIT_WIN32 - static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; - static const char *bogus_path_2 = "e:\\non\\existent"; -#else - static const char *bogus_path_1 = "/this/path/does/not/exist/"; - static const char *bogus_path_2 = "/non/existent"; -#endif - - orig_path = cl_getenv("PATH"); - - git_str_puts(&sandbox_path, clar_sandbox_path()); - git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); - cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); - - fix_path(&sandbox_path); - fix_path(&dummy_path); - - cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", - bogus_path_1, GIT_PATH_LIST_SEPARATOR, - orig_path, GIT_PATH_LIST_SEPARATOR, - sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, - bogus_path_2)); - - check_setenv("PATH", new_path.ptr); - - cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); - cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); - - cl_assert_equal_s(full_path.ptr, dummy_path.ptr); - - git_str_dispose(&full_path); - git_str_dispose(&new_path); - git_str_dispose(&dummy_path); - git_str_dispose(&sandbox_path); - git__free(orig_path); -} - -void test_path__validate_current_user_ownership(void) -{ - bool is_cur; - - cl_must_pass(p_mkdir("testdir", 0777)); - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir")); - cl_assert_equal_i(is_cur, 1); - - cl_git_rewritefile("testfile", "This is a test file."); - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile")); - cl_assert_equal_i(is_cur, 1); - -#ifdef GIT_WIN32 - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\")); - cl_assert_equal_i(is_cur, 0); - - cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist")); -#else - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/")); - cl_assert_equal_i(is_cur, (geteuid() == 0)); - - cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); -#endif -} diff --git a/tests/util/path/core.c b/tests/util/path/core.c index f30f6c01..2de709af 100644 --- a/tests/util/path/core.c +++ b/tests/util/path/core.c @@ -1,6 +1,848 @@ #include "clar_libgit2.h" +#include "futils.h" #include "fs_path.h" +#ifndef GIT_WIN32 +# include +#endif + +static char *path_save; + +void test_path_core__initialize(void) +{ + path_save = cl_getenv("PATH"); +} + +void test_path_core__cleanup(void) +{ + cl_setenv("PATH", path_save); + git__free(path_save); + path_save = NULL; +} + +static void +check_dirname(const char *A, const char *B) +{ + git_str dir = GIT_STR_INIT; + char *dir2; + + cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); + cl_assert_equal_s(B, dir.ptr); + git_str_dispose(&dir); + + cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); + cl_assert_equal_s(B, dir2); + git__free(dir2); +} + +static void +check_basename(const char *A, const char *B) +{ + git_str base = GIT_STR_INIT; + char *base2; + + cl_assert(git_fs_path_basename_r(&base, A) >= 0); + cl_assert_equal_s(B, base.ptr); + git_str_dispose(&base); + + cl_assert((base2 = git_fs_path_basename(A)) != NULL); + cl_assert_equal_s(B, base2); + git__free(base2); +} + +static void +check_joinpath(const char *path_a, const char *path_b, const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void +check_joinpath_n( + const char *path_a, + const char *path_b, + const char *path_c, + const char *path_d, + const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&joined_path, '/', 4, + path_a, path_b, path_c, path_d)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void check_setenv(const char* name, const char* value) +{ + char* check; + + cl_git_pass(cl_setenv(name, value)); + check = cl_getenv(name); + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + + git__free(check); +} + +/* get the dirname of a path */ +void test_path_core__00_dirname(void) +{ + check_dirname(NULL, "."); + check_dirname("", "."); + check_dirname("a", "."); + check_dirname("/", "/"); + check_dirname("/usr", "/"); + check_dirname("/usr/", "/"); + check_dirname("/usr/lib", "/usr"); + check_dirname("/usr/lib/", "/usr"); + check_dirname("/usr/lib//", "/usr"); + check_dirname("usr/lib", "usr"); + check_dirname("usr/lib/", "usr"); + check_dirname("usr/lib//", "usr"); + check_dirname(".git/", "."); + + check_dirname(REP16("/abc"), REP15("/abc")); + +#ifdef GIT_WIN32 + check_dirname("C:/", "C:/"); + check_dirname("C:", "C:/"); + check_dirname("C:/path/", "C:/"); + check_dirname("C:/path", "C:/"); + check_dirname("//computername/", "//computername/"); + check_dirname("//computername", "//computername/"); + check_dirname("//computername/path/", "//computername/"); + check_dirname("//computername/path", "//computername/"); + check_dirname("//computername/sub/path/", "//computername/sub"); + check_dirname("//computername/sub/path", "//computername/sub"); +#endif +} + +/* get the base name of a path */ +void test_path_core__01_basename(void) +{ + check_basename(NULL, "."); + check_basename("", "."); + check_basename("a", "a"); + check_basename("/", "/"); + check_basename("/usr", "usr"); + check_basename("/usr/", "usr"); + check_basename("/usr/lib", "lib"); + check_basename("/usr/lib//", "lib"); + check_basename("usr/lib", "lib"); + + check_basename(REP16("/abc"), "abc"); + check_basename(REP1024("/abc"), "abc"); +} + +/* properly join path components */ +void test_path_core__05_joins(void) +{ + check_joinpath("", "", ""); + check_joinpath("", "a", "a"); + check_joinpath("", "/a", "/a"); + check_joinpath("a", "", "a/"); + check_joinpath("a", "/", "a/"); + check_joinpath("a", "b", "a/b"); + check_joinpath("/", "a", "/a"); + check_joinpath("/", "", "/"); + check_joinpath("/a", "/b", "/a/b"); + check_joinpath("/a", "/b/", "/a/b/"); + check_joinpath("/a/", "b/", "/a/b/"); + check_joinpath("/a/", "/b/", "/a/b/"); + + check_joinpath("/abcd", "/defg", "/abcd/defg"); + check_joinpath("/abcd", "/defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); + check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); + check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); + + check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); + check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); + check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); + + check_joinpath(REP1024("aaaa"), REP1024("bbbb"), + REP1024("aaaa") "/" REP1024("bbbb")); + check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), + REP1024("/aaaa") REP1024("/bbbb")); +} + +/* properly join path components for more than one path */ +void test_path_core__06_long_joins(void) +{ + check_joinpath_n("", "", "", "", ""); + check_joinpath_n("", "a", "", "", "a/"); + check_joinpath_n("a", "", "", "", "a/"); + check_joinpath_n("", "", "", "a", "a"); + check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); + check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); + check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); + check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); + check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); + + check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), + REP1024("a") "/" REP1024("b") "/" + REP1024("c") "/" REP1024("d")); + check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), + REP1024("/a") REP1024("/b") + REP1024("/c") REP1024("/d")); +} + + +static void +check_path_to_dir( + const char* path, + const char* expected) +{ + git_str tgt = GIT_STR_INIT; + + git_str_sets(&tgt, path); + cl_git_pass(git_fs_path_to_dir(&tgt)); + cl_assert_equal_s(expected, tgt.ptr); + + git_str_dispose(&tgt); +} + +static void +check_string_to_dir( + const char* path, + size_t maxlen, + const char* expected) +{ + size_t len = strlen(path); + char *buf = git__malloc(len + 2); + cl_assert(buf); + + strncpy(buf, path, len + 2); + + git_fs_path_string_to_dir(buf, maxlen); + + cl_assert_equal_s(expected, buf); + + git__free(buf); +} + +/* convert paths to dirs */ +void test_path_core__07_path_to_dir(void) +{ + check_path_to_dir("", ""); + check_path_to_dir(".", "./"); + check_path_to_dir("./", "./"); + check_path_to_dir("a/", "a/"); + check_path_to_dir("ab", "ab/"); + /* make sure we try just under and just over an expansion that will + * require a realloc + */ + check_path_to_dir("abcdef", "abcdef/"); + check_path_to_dir("abcdefg", "abcdefg/"); + check_path_to_dir("abcdefgh", "abcdefgh/"); + check_path_to_dir("abcdefghi", "abcdefghi/"); + check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); + check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); + + check_string_to_dir("", 1, ""); + check_string_to_dir(".", 1, "."); + check_string_to_dir(".", 2, "./"); + check_string_to_dir(".", 3, "./"); + check_string_to_dir("abcd", 3, "abcd"); + check_string_to_dir("abcd", 4, "abcd"); + check_string_to_dir("abcd", 5, "abcd/"); + check_string_to_dir("abcd", 6, "abcd/"); +} + +/* join path to itself */ +void test_path_core__08_self_join(void) +{ + git_str path = GIT_STR_INIT; + size_t asize = 0; + + asize = path.asize; + cl_git_pass(git_str_sets(&path, "/foo")); + cl_assert_equal_s(path.ptr, "/foo"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); + cl_git_pass(git_str_sets(&path, "/foo/bar")); + + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); + cl_assert_equal_s(path.ptr, "/bar/baz"); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); + cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); +} + +static void check_percent_decoding(const char *expected_result, const char *input) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git__percent_decode(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +void test_path_core__09_percent_decode(void) +{ + check_percent_decoding("abcd", "abcd"); + check_percent_decoding("a2%", "a2%"); + check_percent_decoding("a2%3", "a2%3"); + check_percent_decoding("a2%%3", "a2%%3"); + check_percent_decoding("a2%3z", "a2%3z"); + check_percent_decoding("a,", "a%2c"); + check_percent_decoding("a21", "a2%31"); + check_percent_decoding("a2%1", "a2%%31"); + check_percent_decoding("a bc ", "a%20bc%20"); + check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); +} + +static void check_fromurl(const char *expected_result, const char *input, int should_fail) +{ + git_str buf = GIT_STR_INIT; + + assert(should_fail || expected_result); + + if (!should_fail) { + cl_git_pass(git_fs_path_fromurl(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + } else + cl_git_fail(git_fs_path_fromurl(&buf, input)); + + git_str_dispose(&buf); +} + +#ifdef GIT_WIN32 +#define ABS_PATH_MARKER "" +#else +#define ABS_PATH_MARKER "/" +#endif + +void test_path_core__10_fromurl(void) +{ + /* Failing cases */ + check_fromurl(NULL, "a", 1); + check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:///", 1); + check_fromurl(NULL, "file:////", 1); + check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); + + /* Passing cases */ + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); +} + +typedef struct { + int expect_idx; + int cancel_after; + char **expect; +} check_walkup_info; + +#define CANCEL_VALUE 1234 + +static int check_one_walkup_step(void *ref, const char *path) +{ + check_walkup_info *info = (check_walkup_info *)ref; + + if (!info->cancel_after) { + cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); + return CANCEL_VALUE; + } + info->cancel_after--; + + cl_assert(info->expect[info->expect_idx] != NULL); + cl_assert_equal_s(info->expect[info->expect_idx], path); + info->expect_idx++; + + return 0; +} + +void test_path_core__11_walkup(void) +{ + git_str p = GIT_STR_INIT; + + char *expect[] = { + /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 7 */ "this_is_a_path", "", NULL, + /* 8 */ "this_is_a_path/", "", NULL, + /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, + /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, + /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, + /* 12 */ "a/b/c/", "a/b/", "a/", NULL, + /* 13 */ "", NULL, + /* 14 */ "/", NULL, + /* 15 */ NULL + }; + + char *root[] = { + /* 1 */ NULL, + /* 2 */ NULL, + /* 3 */ "/", + /* 4 */ "", + /* 5 */ "/a/b", + /* 6 */ "/a/b/", + /* 7 */ NULL, + /* 8 */ NULL, + /* 9 */ NULL, + /* 10 */ NULL, + /* 11 */ NULL, + /* 12 */ "a/", + /* 13 */ NULL, + /* 14 */ NULL, + }; + + int i, j; + check_walkup_info info; + + info.expect = expect; + info.cancel_after = -1; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.expect_idx = i; + cl_git_pass( + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + cl_assert_equal_s(p.ptr, expect[i]); + cl_assert(expect[info.expect_idx] == NULL); + i = info.expect_idx; + } + + git_str_dispose(&p); +} + +void test_path_core__11a_walkup_cancel(void) +{ + git_str p = GIT_STR_INIT; + int cancel[] = { 3, 2, 1, 0 }; + char *expect[] = { + "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, + "/a/b/c/d/e", "[CANCEL]", NULL, + "[CANCEL]", NULL, + NULL + }; + char *root[] = { NULL, NULL, "/", "", NULL }; + int i, j; + check_walkup_info info; + + info.expect = expect; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.cancel_after = cancel[j]; + info.expect_idx = i; + + cl_assert_equal_i( + CANCEL_VALUE, + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + /* skip to next run of expectations */ + while (expect[i] != NULL) i++; + } + + git_str_dispose(&p); +} + +void test_path_core__12_offset_to_path_root(void) +{ + cl_assert(git_fs_path_root("non/rooted/path") == -1); + cl_assert(git_fs_path_root("/rooted/path") == 0); + +#ifdef GIT_WIN32 + /* Windows specific tests */ + cl_assert(git_fs_path_root("C:non/rooted/path") == -1); + cl_assert(git_fs_path_root("C:/rooted/path") == 2); + cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); + cl_assert(git_fs_path_root("//computername/sharefolder") == 14); + cl_assert(git_fs_path_root("//computername") == -1); +#endif +} + +#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" + +void test_path_core__13_cannot_prettify_a_non_existing_file(void) +{ + git_str p = GIT_STR_INIT; + + cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); + + git_str_dispose(&p); +} + +void test_path_core__14_apply_relative(void) +{ + git_str p = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&p, "/this/is/a/base")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../test")); + cl_assert_equal_s("/this/is/a/test", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); + cl_assert_equal_s("/this/is/the/end", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); + cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); + cl_assert_equal_s("/this/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../")); + cl_assert_equal_s("/", p.ptr); + + cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); + + + cl_git_pass(git_str_sets(&p, "d:/another/test")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../..")); + cl_assert_equal_s("d:/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); + cl_assert_equal_s("d:/from/here/and/back/", p.ptr); + + + cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); + cl_assert_equal_s("https://my.url.com/another.git", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); + cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "..")); + cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); + cl_assert_equal_s("https://", p.ptr); + + + cl_git_pass(git_str_sets(&p, "../../this/is/relative")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); + cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); + cl_assert_equal_s("../../that", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../there")); + cl_assert_equal_s("../../there", p.ptr); + git_str_dispose(&p); +} + +static void assert_resolve_relative( + git_str *buf, const char *expected, const char *path) +{ + cl_git_pass(git_str_sets(buf, path)); + cl_git_pass(git_fs_path_resolve_relative(buf, 0)); + cl_assert_equal_s(expected, buf->ptr); +} + +void test_path_core__15_resolve_relative(void) +{ + git_str buf = GIT_STR_INIT; + + assert_resolve_relative(&buf, "", ""); + assert_resolve_relative(&buf, "", "."); + assert_resolve_relative(&buf, "", "./"); + assert_resolve_relative(&buf, "..", ".."); + assert_resolve_relative(&buf, "../", "../"); + assert_resolve_relative(&buf, "..", "./.."); + assert_resolve_relative(&buf, "../", "./../"); + assert_resolve_relative(&buf, "../", "../."); + assert_resolve_relative(&buf, "../", ".././"); + assert_resolve_relative(&buf, "../..", "../.."); + assert_resolve_relative(&buf, "../../", "../../"); + + assert_resolve_relative(&buf, "/", "/"); + assert_resolve_relative(&buf, "/", "/."); + + assert_resolve_relative(&buf, "", "a/.."); + assert_resolve_relative(&buf, "", "a/../"); + assert_resolve_relative(&buf, "", "a/../."); + + assert_resolve_relative(&buf, "/a", "/a"); + assert_resolve_relative(&buf, "/a/", "/a/."); + assert_resolve_relative(&buf, "/", "/a/../"); + assert_resolve_relative(&buf, "/", "/a/../."); + assert_resolve_relative(&buf, "/", "/a/.././"); + + assert_resolve_relative(&buf, "a", "a"); + assert_resolve_relative(&buf, "a/", "a/"); + assert_resolve_relative(&buf, "a/", "a/."); + assert_resolve_relative(&buf, "a/", "a/./"); + + assert_resolve_relative(&buf, "a/b", "a//b"); + assert_resolve_relative(&buf, "a/b/c", "a/b/c"); + assert_resolve_relative(&buf, "b/c", "./b/c"); + assert_resolve_relative(&buf, "a/c", "a/./c"); + assert_resolve_relative(&buf, "a/b/", "a/b/."); + + assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); + assert_resolve_relative(&buf, "/", "////"); + assert_resolve_relative(&buf, "/a", "///a"); + assert_resolve_relative(&buf, "/", "///."); + assert_resolve_relative(&buf, "/", "///a/.."); + + assert_resolve_relative(&buf, "../../path", "../../test//../././path"); + assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); + + cl_git_pass(git_str_sets(&buf, "/..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/./..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/.//..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.././../a")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "////..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + /* things that start with Windows network paths */ +#ifdef GIT_WIN32 + assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "//a/", "//a/b/.."); + assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); + + cl_git_pass(git_str_sets(&buf, "//a/b/../..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); +#else + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/a/", "//a/b/.."); + assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); + assert_resolve_relative(&buf, "/", "//a/b/../.."); +#endif + + git_str_dispose(&buf); +} + +#define assert_common_dirlen(i, p, q) \ + cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); + +void test_path_core__16_resolve_relative(void) +{ + assert_common_dirlen(0, "", ""); + assert_common_dirlen(0, "", "bar.txt"); + assert_common_dirlen(0, "foo.txt", "bar.txt"); + assert_common_dirlen(0, "foo.txt", ""); + assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); + assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); + + assert_common_dirlen(1, "/one.txt", "/two.txt"); + assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); + assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); + + assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); + assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); +} + +static void fix_path(git_str *s) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(s); +#else + char* c; + + for (c = s->ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } +#endif +} + +#ifdef GIT_WIN32 +# define PATH_DELIMITER "\\" +#else +# define PATH_DELIMITER "/" +#endif + +void test_path_core__find_exe_in_path(void) +{ + char *orig_path; + git_str sandbox_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, + dummy_path_one = GIT_STR_INIT, + dummy_path_two = GIT_STR_INIT, + dummy_path_three = GIT_STR_INIT; + +#ifdef GIT_WIN32 + static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; + static const char *bogus_path_2 = "e:\\non\\existent"; +#else + static const char *bogus_path_1 = "/this/path/does/not/exist/"; + static const char *bogus_path_2 = "/non/existent"; +#endif + + orig_path = cl_getenv("PATH"); + + git_str_puts(&sandbox_path, clar_sandbox_path()); + + git_str_joinpath(&dummy_path_one, sandbox_path.ptr, "dummmmmmmy_libgit2_file_one"); + cl_git_rewritefile(dummy_path_one.ptr, "this is a dummy file"); + + git_str_joinpath(&dummy_path_two, sandbox_path.ptr, "dummmmmmmy_libgit2_file_two.exe"); + cl_git_rewritefile(dummy_path_two.ptr, "this is a dummy file"); + cl_must_pass(p_chmod("dummmmmmmy_libgit2_file_two.exe", 0777)); + + cl_must_pass(p_mkdir("sub", 0777)); + cl_must_pass(p_mkdir("sub/dir", 0777)); + git_str_joinpath(&dummy_path_three, sandbox_path.ptr, "sub/dir/dummmmmmmy_libgit2_file_three.exe"); + cl_git_rewritefile(dummy_path_three.ptr, "this is a dummy file"); + cl_must_pass(p_chmod("sub/dir/dummmmmmmy_libgit2_file_three.exe", 0777)); + + fix_path(&sandbox_path); + fix_path(&dummy_path_one); + fix_path(&dummy_path_two); + fix_path(&dummy_path_three); + + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + check_setenv("PATH", new_path.ptr); + + /* ensure that we cannot find a nonexistent file */ + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); + + /* ensure that non-executable files are ignored */ + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_one")); + + /* find an executable in the path */ + git_str_clear(&full_path); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe")); + cl_assert_equal_s(full_path.ptr, dummy_path_two.ptr); + + /* find an executable in the current directory */ + git_str_clear(&new_path); + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + ".", GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + check_setenv("PATH", new_path.ptr); + + git_str_clear(&full_path); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe")); + cl_assert_equal_s(full_path.ptr, "." PATH_DELIMITER "dummmmmmmy_libgit2_file_two.exe"); + + /* empty path is the same as current directory on POSIX */ +#ifndef GIT_WIN32 + git_str_clear(&new_path); + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + "", GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + check_setenv("PATH", new_path.ptr); + + git_str_clear(&full_path); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe")); + cl_assert_equal_s(full_path.ptr, "." PATH_DELIMITER "dummmmmmmy_libgit2_file_two.exe"); +#endif + + /* don't look for qualified paths in PATH */ + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "dir/dummmmmmmy_libgit2_file_three.exe")); + + /* but do allow users to use paths relative to current dir */ + git_str_clear(&full_path); + cl_git_pass(git_fs_path_find_executable(&full_path, "sub/dir/dummmmmmmy_libgit2_file_three.exe")); + cl_assert_equal_s(full_path.ptr, "sub/dir/dummmmmmmy_libgit2_file_three.exe"); + + git_str_dispose(&full_path); + git_str_dispose(&new_path); + git_str_dispose(&dummy_path_one); + git_str_dispose(&dummy_path_two); + git_str_dispose(&dummy_path_three); + git_str_dispose(&sandbox_path); + git__free(orig_path); +} + +void test_path_core__validate_current_user_ownership(void) +{ + bool is_cur; + + cl_must_pass(p_mkdir("testdir", 0777)); + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir")); + cl_assert_equal_i(is_cur, 1); + + cl_git_rewritefile("testfile", "This is a test file."); + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile")); + cl_assert_equal_i(is_cur, 1); + +#ifdef GIT_WIN32 + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\")); + cl_assert_equal_i(is_cur, 0); + + cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist")); +#else + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/")); + cl_assert_equal_i(is_cur, (geteuid() == 0)); + + cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); +#endif +} + +void test_path_core__dirlen(void) +{ + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf/")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf//")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo////")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo")); + cl_assert_equal_sz(1, git_fs_path_dirlen("/")); + cl_assert_equal_sz(1, git_fs_path_dirlen("////")); + cl_assert_equal_sz(0, git_fs_path_dirlen("")); +} + static void test_make_relative( const char *expected_path, const char *path, @@ -341,3 +1183,29 @@ void test_path_core__join_unrooted_respects_funny_windows_roots(void) test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); } + +void test_path_core__is_root(void) +{ + cl_assert_equal_b(true, git_fs_path_is_root("/")); + cl_assert_equal_b(false, git_fs_path_is_root("//")); + cl_assert_equal_b(false, git_fs_path_is_root("foo/")); + cl_assert_equal_b(false, git_fs_path_is_root("/foo/")); + cl_assert_equal_b(false, git_fs_path_is_root("/foo")); + cl_assert_equal_b(false, git_fs_path_is_root("\\")); + +#ifdef GIT_WIN32 + cl_assert_equal_b(true, git_fs_path_is_root("A:\\")); + cl_assert_equal_b(false, git_fs_path_is_root("B:\\foo")); + cl_assert_equal_b(false, git_fs_path_is_root("B:\\foo\\")); + cl_assert_equal_b(true, git_fs_path_is_root("C:\\")); + cl_assert_equal_b(true, git_fs_path_is_root("c:\\")); + cl_assert_equal_b(true, git_fs_path_is_root("z:\\")); + cl_assert_equal_b(false, git_fs_path_is_root("z:\\\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\Foo")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\Foo\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\Volume\\12345\\Foo\\Bar.txt")); +#endif +} diff --git a/tests/util/path/win32.c b/tests/util/path/win32.c index 1aaf6867..6f90b447 100644 --- a/tests/util/path/win32.c +++ b/tests/util/path/win32.c @@ -278,5 +278,38 @@ void test_path_win32__8dot3_name(void) cl_must_pass(p_mkdir(".bar", 0777)); cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); git__free(shortname); + + p_rmdir(".foo"); + p_rmdir(".bar"); + p_unlink("bar~1"); +#endif +} + +void test_path_win32__realpath(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT; + char result[GIT_PATH_MAX]; + + /* Ensure relative paths become absolute */ + cl_must_pass(git_str_joinpath(&expected, clar_sandbox_path(), "abcdef")); + cl_must_pass(p_mkdir("abcdef", 0777)); + cl_assert(p_realpath("./abcdef", result) != NULL); + cl_assert_equal_s(expected.ptr, result); + + /* Ensure case is canonicalized */ + git_str_clear(&expected); + cl_must_pass(git_str_joinpath(&expected, clar_sandbox_path(), "FOO")); + cl_must_pass(p_mkdir("FOO", 0777)); + cl_assert(p_realpath("foo", result) != NULL); + cl_assert_equal_s(expected.ptr, result); + + cl_assert(p_realpath("nonexistent", result) == NULL); + cl_assert_equal_i(ENOENT, errno); + + git_str_dispose(&expected); + + p_rmdir("abcdef"); + p_rmdir("FOO"); #endif } diff --git a/tests/util/process/env.c b/tests/util/process/env.c new file mode 100644 index 00000000..a6dc62e7 --- /dev/null +++ b/tests/util/process/env.c @@ -0,0 +1,111 @@ +#include "clar_libgit2.h" +#include "process.h" +#include "vector.h" + +static git_str env_cmd = GIT_STR_INIT; +static git_str accumulator = GIT_STR_INIT; +static git_vector env_result = GIT_VECTOR_INIT; + +void test_process_env__initialize(void) +{ +#ifdef GIT_WIN32 + git_str_printf(&env_cmd, "%s/env.cmd", cl_fixture("process")); +#else + git_str_puts(&env_cmd, "/usr/bin/env"); +#endif + + cl_git_pass(git_vector_init(&env_result, 32, git__strcmp_cb)); +} + +void test_process_env__cleanup(void) +{ + git_vector_dispose(&env_result); + git_str_dispose(&accumulator); + git_str_dispose(&env_cmd); +} + +static void run_env(const char **env_array, size_t env_len, bool exclude_env) +{ + const char *args_array[] = { env_cmd.ptr }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + char buf[1024], *tok; + ssize_t ret; + + opts.capture_out = 1; + opts.exclude_env = exclude_env; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), env_array, env_len, &opts)); + cl_git_pass(git_process_start(process)); + + while ((ret = git_process_read(process, buf, 1024)) > 0) + cl_git_pass(git_str_put(&accumulator, buf, (size_t)ret)); + + cl_assert_equal_i(0, ret); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + for (tok = strtok(accumulator.ptr, "\n"); tok; tok = strtok(NULL, "\n")) { +#ifdef GIT_WIN32 + if (strlen(tok) && tok[strlen(tok) - 1] == '\r') + tok[strlen(tok) - 1] = '\0'; +#endif + + cl_git_pass(git_vector_insert(&env_result, tok)); + } + + git_process_close(process); + git_process_free(process); +} + +void test_process_env__can_add_env(void) +{ + const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" }; + run_env(env_array, 2, false); + + cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=added")); + cl_git_pass(git_vector_search(NULL, &env_result, "TEST_OTHER_ENV=also_added")); +} + +void test_process_env__can_propagate_env(void) +{ + cl_setenv("TEST_NEW_ENV", "propagated"); + run_env(NULL, 0, false); + + cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=propagated")); +} + +void test_process_env__can_remove_env(void) +{ + const char *env_array[] = { "TEST_NEW_ENV=" }; + char *str; + size_t i; + + cl_setenv("TEST_NEW_ENV", "propagated"); + run_env(env_array, 1, false); + + git_vector_foreach(&env_result, i, str) + cl_assert(git__prefixcmp(str, "TEST_NEW_ENV=") != 0); +} + +void test_process_env__can_clear_env(void) +{ + const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" }; + + cl_setenv("SOME_EXISTING_ENV", "propagated"); + run_env(env_array, 2, true); + + /* + * We can't simply test that the environment is precisely what we + * provided. Some systems (eg win32) will add environment variables + * to all processes. + */ + cl_assert_equal_i(GIT_ENOTFOUND, git_vector_search(NULL, &env_result, "SOME_EXISTING_ENV=propagated")); +} diff --git a/tests/util/process/start.c b/tests/util/process/start.c new file mode 100644 index 00000000..febbc70c --- /dev/null +++ b/tests/util/process/start.c @@ -0,0 +1,293 @@ +#include "clar_libgit2.h" +#include "process.h" +#include "vector.h" + +#ifndef GIT_WIN32 +# include +#endif + +#ifndef SIGTERM +# define SIGTERM 42 +#endif + +#ifndef SIGPIPE +# define SIGPIPE 42 +#endif + +static git_str helloworld_cmd = GIT_STR_INIT; +static git_str cat_cmd = GIT_STR_INIT; +static git_str pwd_cmd = GIT_STR_INIT; + +void test_process_start__initialize(void) +{ +#ifdef GIT_WIN32 + git_str_printf(&helloworld_cmd, "%s/helloworld.bat", cl_fixture("process")); + git_str_printf(&cat_cmd, "%s/cat.bat", cl_fixture("process")); + git_str_printf(&pwd_cmd, "%s/pwd.bat", cl_fixture("process")); +#else + git_str_printf(&helloworld_cmd, "%s/helloworld.sh", cl_fixture("process")); +#endif +} + +void test_process_start__cleanup(void) +{ + git_str_dispose(&pwd_cmd); + git_str_dispose(&cat_cmd); + git_str_dispose(&helloworld_cmd); +} + +void test_process_start__returncode(void) +{ +#if defined(GIT_WIN32) + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", "exit", "1" }; +#elif defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__) || \ + defined(__MidnightBSD__) || defined(__DragonFly__) + const char *args_array[] = { "/usr/bin/false" }; +#else + const char *args_array[] = { "/bin/false" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(1, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_process_free(process); +} + +void test_process_start__not_found(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\a\\b\\z\\y\\not_found" }; +#else + const char *args_array[] = { "/a/b/z/y/not_found" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_fail(git_process_start(process)); + git_process_free(process); +} + +void test_process_start__finds_in_path(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "cmd", "/c", "exit", "0" }; +#else + const char *args_array[] = { "true" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_process_free(process); +} + +void test_process_start__adds_suffix(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd", "/c", "exit", "0" }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_process_free(process); +#else + cl_skip(); +#endif +} + +static void write_all(git_process *process, char *buf) +{ + size_t buf_len = strlen(buf); + ssize_t ret; + + while (buf_len) { + ret = git_process_write(process, buf, buf_len); + cl_git_pass(ret < 0 ? (int)ret : 0); + + buf += ret; + buf_len -= ret; + } +} + +static void read_all(git_str *out, git_process *process) +{ + char buf[32]; + size_t buf_len = 32; + ssize_t ret; + + while ((ret = git_process_read(process, buf, buf_len)) > 0) + cl_git_pass(git_str_put(out, buf, ret)); + + cl_git_pass(ret < 0 ? (int)ret : 0); +} + +void test_process_start__redirect_stdio(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", cat_cmd.ptr }; +#else + const char *args_array[] = { "/bin/cat" }; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + git_str buf = GIT_STR_INIT; + + opts.capture_in = 1; + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + + write_all(process, "Hello, world.\r\nHello!\r\n"); + cl_git_pass(git_process_close_in(process)); + + read_all(&buf, process); + cl_assert_equal_s("Hello, world.\r\nHello!\r\n", buf.ptr); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_str_dispose(&buf); + git_process_free(process); +} + +/* +void test_process_start__catch_sigterm(void) +{ + const char *args_array[] = { "/bin/cat" }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + p_pid_t pid; + + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_id(&pid, process)); + + cl_must_pass(kill(pid, SIGTERM)); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(SIGTERM, result.signal); + + git_process_free(process); +} + +void test_process_start__catch_sigpipe(void) +{ + const char *args_array[] = { helloworld_cmd.ptr }; + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + cl_git_pass(git_process_close(process)); + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(SIGPIPE, result.signal); + + git_process_free(process); +} +*/ + +void test_process_start__can_chdir(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr }; + char *startwd = "C:\\"; +#else + const char *args_array[] = { "/bin/pwd" }; + char *startwd = "/"; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + git_process_result result = GIT_PROCESS_RESULT_INIT; + git_str buf = GIT_STR_INIT; + + opts.cwd = startwd; + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_pass(git_process_start(process)); + + read_all(&buf, process); + git_str_rtrim(&buf); + + cl_assert_equal_s(startwd, buf.ptr); + + cl_git_pass(git_process_wait(&result, process)); + + cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status); + cl_assert_equal_i(0, result.exitcode); + cl_assert_equal_i(0, result.signal); + + git_str_dispose(&buf); + git_process_free(process); +} + +void test_process_start__cannot_chdir_to_nonexistent_dir(void) +{ +#ifdef GIT_WIN32 + const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr }; + char *startwd = "C:\\a\\b\\z\\y\\not_found"; +#else + const char *args_array[] = { "/bin/pwd" }; + char *startwd = "/a/b/z/y/not_found"; +#endif + + git_process *process; + git_process_options opts = GIT_PROCESS_OPTIONS_INIT; + + opts.cwd = startwd; + opts.capture_out = 1; + + cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts)); + cl_git_fail(git_process_start(process)); + git_process_free(process); +} diff --git a/tests/util/process/win32.c b/tests/util/process/win32.c new file mode 100644 index 00000000..c04a56ec --- /dev/null +++ b/tests/util/process/win32.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" +#include "process.h" +#include "vector.h" + +#ifdef GIT_WIN32 +static git_str result; + +# define assert_cmdline(expected, given) do { \ + cl_git_pass(git_process__cmdline(&result, given, ARRAY_SIZE(given))); \ + cl_assert_equal_s(expected, result.ptr); \ + git_str_dispose(&result); \ + } while(0) + +#endif + + +void test_process_win32__cmdline_is_whitespace_delimited(void) +{ +#ifdef GIT_WIN32 + const char *one[] = { "one" }; + const char *two[] = { "one", "two" }; + const char *three[] = { "one", "two", "three" }; + const char *four[] = { "one", "two", "three", "four" }; + + assert_cmdline("one", one); + assert_cmdline("one two", two); + assert_cmdline("one two three", three); + assert_cmdline("one two three four", four); +#endif +} + +void test_process_win32__cmdline_escapes_whitespace(void) +{ +#ifdef GIT_WIN32 + const char *spaces[] = { "one with spaces" }; + const char *tabs[] = { "one\twith\ttabs" }; + const char *multiple[] = { "one with many spaces" }; + + assert_cmdline("one\" \"with\" \"spaces", spaces); + assert_cmdline("one\"\t\"with\"\t\"tabs", tabs); + assert_cmdline("one\" \"with\" \"many\" \"spaces", multiple); +#endif +} + +void test_process_win32__cmdline_escapes_quotes(void) +{ +#ifdef GIT_WIN32 + const char *one[] = { "echo", "\"hello world\"" }; + + assert_cmdline("echo \\\"hello\" \"world\\\"", one); +#endif +} + +void test_process_win32__cmdline_escapes_backslash(void) +{ +#ifdef GIT_WIN32 + const char *one[] = { "foo\\bar", "foo\\baz" }; + const char *two[] = { "c:\\program files\\foo bar\\foo bar.exe", "c:\\path\\to\\other\\", "/a", "/b" }; + + assert_cmdline("foo\\\\bar foo\\\\baz", one); + assert_cmdline("c:\\\\program\" \"files\\\\foo\" \"bar\\\\foo\" \"bar.exe c:\\\\path\\\\to\\\\other\\\\ /a /b", two); +#endif +} diff --git a/tests/util/string.c b/tests/util/string.c index de04dea6..051f8c32 100644 --- a/tests/util/string.c +++ b/tests/util/string.c @@ -111,12 +111,6 @@ void test_string__strcasecmp(void) cl_assert(git__strcasecmp("foo", "FOO") == 0); cl_assert(git__strcasecmp("foo", "fOO") == 0); - cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0); - cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0); - cl_assert(strcasecmp("\303\215", "\303\255") < 0); - cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0); cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); diff --git a/tests/util/strmap.c b/tests/util/strmap.c deleted file mode 100644 index c4f5c864..00000000 --- a/tests/util/strmap.c +++ /dev/null @@ -1,190 +0,0 @@ -#include "clar_libgit2.h" -#include "strmap.h" - -static git_strmap *g_table; - -void test_strmap__initialize(void) -{ - cl_git_pass(git_strmap_new(&g_table)); - cl_assert(g_table != NULL); -} - -void test_strmap__cleanup(void) -{ - git_strmap_free(g_table); -} - -void test_strmap__0(void) -{ - cl_assert(git_strmap_size(g_table) == 0); -} - -static void insert_strings(git_strmap *table, size_t count) -{ - size_t i, j, over; - char *str; - - for (i = 0; i < count; ++i) { - str = malloc(10); - for (j = 0; j < 10; ++j) - str[j] = 'a' + (i % 26); - str[9] = '\0'; - - /* if > 26, then encode larger value in first letters */ - for (j = 0, over = i / 26; over > 0; j++, over = over / 26) - str[j] = 'A' + (over % 26); - - cl_git_pass(git_strmap_set(table, str, str)); - } - - cl_assert_equal_i(git_strmap_size(table), count); -} - -void test_strmap__inserted_strings_can_be_retrieved(void) -{ - char *str; - int i; - - insert_strings(g_table, 20); - - cl_assert(git_strmap_exists(g_table, "aaaaaaaaa")); - cl_assert(git_strmap_exists(g_table, "ggggggggg")); - cl_assert(!git_strmap_exists(g_table, "aaaaaaaab")); - cl_assert(!git_strmap_exists(g_table, "abcdefghi")); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert(i == 20); -} - -void test_strmap__deleted_entry_cannot_be_retrieved(void) -{ - char *str; - int i; - - insert_strings(g_table, 20); - - cl_assert(git_strmap_exists(g_table, "bbbbbbbbb")); - str = git_strmap_get(g_table, "bbbbbbbbb"); - cl_assert_equal_s(str, "bbbbbbbbb"); - cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb")); - free(str); - - cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb")); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert_equal_i(i, 19); -} - -void test_strmap__inserting_many_keys_succeeds(void) -{ - char *str; - int i; - - insert_strings(g_table, 10000); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert_equal_i(i, 10000); -} - -void test_strmap__get_succeeds_with_existing_entries(void) -{ - const char *keys[] = { "foo", "bar", "gobble" }; - char *values[] = { "oof", "rab", "elbbog" }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(keys); i++) - cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); - - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); - cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); - cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog"); -} - -void test_strmap__get_returns_null_on_nonexisting_key(void) -{ - const char *keys[] = { "foo", "bar", "gobble" }; - char *values[] = { "oof", "rab", "elbbog" }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(keys); i++) - cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); - - cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL); -} - -void test_strmap__set_persists_key(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); -} - -void test_strmap__set_persists_multpile_keys(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_git_pass(git_strmap_set(g_table, "bar", "rab")); - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); - cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); -} - -void test_strmap__set_updates_existing_key(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_git_pass(git_strmap_set(g_table, "bar", "rab")); - cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog")); - cl_assert_equal_i(git_strmap_size(g_table), 3); - - cl_git_pass(git_strmap_set(g_table, "foo", "other")); - cl_assert_equal_i(git_strmap_size(g_table), 3); - - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other"); -} - -void test_strmap__iteration(void) -{ - struct { - char *key; - char *value; - int seen; - } entries[] = { - { "foo", "oof" }, - { "bar", "rab" }, - { "gobble", "elbbog" }, - }; - const char *key, *value; - size_t i, n; - - for (i = 0; i < ARRAY_SIZE(entries); i++) - cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value)); - - i = 0, n = 0; - while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) { - size_t j; - - for (j = 0; j < ARRAY_SIZE(entries); j++) { - if (strcmp(entries[j].key, key)) - continue; - - cl_assert_equal_i(entries[j].seen, 0); - cl_assert_equal_s(entries[j].value, value); - entries[j].seen++; - break; - } - - n++; - } - - for (i = 0; i < ARRAY_SIZE(entries); i++) - cl_assert_equal_i(entries[i].seen, 1); - - cl_assert_equal_i(n, ARRAY_SIZE(entries)); -} - -void test_strmap__iterating_empty_map_stops_immediately(void) -{ - size_t i = 0; - - cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER); -} diff --git a/tests/util/url/parse.c b/tests/util/url/parse.c index 35486f7b..ece37978 100644 --- a/tests/util/url/parse.c +++ b/tests/util/url/parse.c @@ -27,6 +27,7 @@ void test_url_parse__hostname_trivial(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_root(void) @@ -41,6 +42,7 @@ void test_url_parse__hostname_root(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_implied_root(void) @@ -55,6 +57,7 @@ void test_url_parse__hostname_implied_root(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_numeric(void) @@ -69,6 +72,7 @@ void test_url_parse__hostname_numeric(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_implied_root_custom_port(void) @@ -83,6 +87,22 @@ void test_url_parse__hostname_implied_root_custom_port(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); +} + +void test_url_parse__specified_default_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:80/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_implied_root_empty_port(void) @@ -97,6 +117,7 @@ void test_url_parse__hostname_implied_root_empty_port(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_encoded_password(void) @@ -112,6 +133,7 @@ void test_url_parse__hostname_encoded_password(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_user(void) @@ -127,6 +149,7 @@ void test_url_parse__hostname_user(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_user_pass(void) @@ -143,6 +166,7 @@ void test_url_parse__hostname_user_pass(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_port(void) @@ -159,6 +183,7 @@ void test_url_parse__hostname_port(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_empty_port(void) @@ -173,6 +198,7 @@ void test_url_parse__hostname_empty_port(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__hostname_user_port(void) @@ -189,6 +215,7 @@ void test_url_parse__hostname_user_port(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_user_pass_port(void) @@ -205,6 +232,7 @@ void test_url_parse__hostname_user_pass_port(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_user_pass_port_query(void) @@ -221,6 +249,7 @@ void test_url_parse__hostname_user_pass_port_query(void) cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf"); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_user_pass_port_fragment(void) @@ -237,6 +266,7 @@ void test_url_parse__hostname_user_pass_port_fragment(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_s(conndata.fragment, "fragment"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__hostname_user_pass_port_query_fragment(void) @@ -253,6 +283,7 @@ void test_url_parse__hostname_user_pass_port_query_fragment(void) cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf"); cl_assert_equal_s(conndata.fragment, "fragment"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__fragment_with_question_mark(void) @@ -269,6 +300,7 @@ void test_url_parse__fragment_with_question_mark(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_s(conndata.fragment, "fragment_with?question_mark"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } /* IPv4 addresses */ @@ -283,6 +315,7 @@ void test_url_parse__ipv4_trivial(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_root(void) @@ -295,6 +328,7 @@ void test_url_parse__ipv4_root(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_implied_root(void) @@ -307,6 +341,7 @@ void test_url_parse__ipv4_implied_root(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_implied_root_custom_port(void) @@ -319,6 +354,7 @@ void test_url_parse__ipv4_implied_root_custom_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv4_implied_root_empty_port(void) @@ -331,6 +367,7 @@ void test_url_parse__ipv4_implied_root_empty_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_encoded_password(void) @@ -344,6 +381,7 @@ void test_url_parse__ipv4_encoded_password(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_s(conndata.password, "pass/is@bad"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv4_user(void) @@ -357,6 +395,7 @@ void test_url_parse__ipv4_user(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_user_pass(void) @@ -370,6 +409,7 @@ void test_url_parse__ipv4_user_pass(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_s(conndata.password, "pass"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_port(void) @@ -383,6 +423,7 @@ void test_url_parse__ipv4_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv4_empty_port(void) @@ -395,6 +436,7 @@ void test_url_parse__ipv4_empty_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv4_user_port(void) @@ -408,6 +450,7 @@ void test_url_parse__ipv4_user_port(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv4_user_pass_port(void) @@ -421,6 +464,7 @@ void test_url_parse__ipv4_user_pass_port(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_s(conndata.password, "pass"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } /* IPv6 addresses */ @@ -435,6 +479,7 @@ void test_url_parse__ipv6_trivial(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_root(void) @@ -447,6 +492,7 @@ void test_url_parse__ipv6_root(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_implied_root(void) @@ -459,6 +505,7 @@ void test_url_parse__ipv6_implied_root(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_implied_root_custom_port(void) @@ -471,6 +518,7 @@ void test_url_parse__ipv6_implied_root_custom_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv6_implied_root_empty_port(void) @@ -483,6 +531,7 @@ void test_url_parse__ipv6_implied_root_empty_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_encoded_password(void) @@ -496,6 +545,7 @@ void test_url_parse__ipv6_encoded_password(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_s(conndata.password, "pass/is@bad"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv6_user(void) @@ -509,6 +559,7 @@ void test_url_parse__ipv6_user(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_user_pass(void) @@ -522,6 +573,7 @@ void test_url_parse__ipv6_user_pass(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_s(conndata.password, "pass"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_port(void) @@ -535,6 +587,7 @@ void test_url_parse__ipv6_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv6_empty_port(void) @@ -547,6 +600,7 @@ void test_url_parse__ipv6_empty_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__ipv6_user_port(void) @@ -560,6 +614,7 @@ void test_url_parse__ipv6_user_port(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv6_user_pass_port(void) @@ -573,6 +628,7 @@ void test_url_parse__ipv6_user_pass_port(void) cl_assert_equal_s(conndata.username, "user"); cl_assert_equal_s(conndata.password, "pass"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ipv6_invalid_addresses(void) @@ -681,6 +737,7 @@ void test_url_parse__empty_scheme(void) cl_assert_equal_p(conndata.query, NULL); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__invalid_scheme_is_relative(void) @@ -695,12 +752,14 @@ void test_url_parse__invalid_scheme_is_relative(void) cl_assert_equal_s(conndata.query, "query_string=yes"); cl_assert_equal_p(conndata.fragment, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__scheme_case_is_normalized(void) { cl_git_pass(git_net_url_parse(&conndata, "GIT+SSH://host:42/path/to/project")); cl_assert_equal_s(conndata.scheme, "git+ssh"); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__nonhierarchical_scheme(void) @@ -713,6 +772,7 @@ void test_url_parse__nonhierarchical_scheme(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__no_scheme_relative_path(void) @@ -725,6 +785,7 @@ void test_url_parse__no_scheme_relative_path(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__no_scheme_absolute_path(void) @@ -737,6 +798,7 @@ void test_url_parse__no_scheme_absolute_path(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__empty_path(void) @@ -749,6 +811,7 @@ void test_url_parse__empty_path(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__empty_path_with_empty_authority(void) @@ -761,6 +824,7 @@ void test_url_parse__empty_path_with_empty_authority(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_parse__http_follows_the_rfc(void) @@ -778,6 +842,7 @@ void test_url_parse__ssh_from_terrible_google_rfc_violating_products(void) cl_assert_equal_s(conndata.username, "my.email.address@gmail.com"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__ssh_with_password_from_terrible_google_rfc_violating_products(void) @@ -790,6 +855,7 @@ void test_url_parse__ssh_with_password_from_terrible_google_rfc_violating_produc cl_assert_equal_s(conndata.username, "my.email.address@gmail.com"); cl_assert_equal_s(conndata.password, "seekret"); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_parse__spaces_in_the_name(void) @@ -802,4 +868,5 @@ void test_url_parse__spaces_in_the_name(void) cl_assert_equal_s(conndata.username, "libgit2"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } diff --git a/tests/util/url/scp.c b/tests/util/url/scp.c index 0e0dce17..1fd7c313 100644 --- a/tests/util/url/scp.c +++ b/tests/util/url/scp.c @@ -25,6 +25,7 @@ void test_url_scp__hostname_trivial(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__hostname_bracketed(void) @@ -37,6 +38,7 @@ void test_url_scp__hostname_bracketed(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__hostname_root(void) @@ -49,6 +51,7 @@ void test_url_scp__hostname_root(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__hostname_user(void) @@ -61,6 +64,7 @@ void test_url_scp__hostname_user(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__hostname_user_bracketed(void) @@ -73,6 +77,7 @@ void test_url_scp__hostname_user_bracketed(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__hostname_port(void) @@ -85,6 +90,20 @@ void test_url_scp__hostname_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); +} + +void test_url_scp__hostname_specified_default_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com:22]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__hostname_user_port(void) @@ -97,6 +116,7 @@ void test_url_scp__hostname_user_port(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__ipv4_trivial(void) @@ -109,6 +129,7 @@ void test_url_scp__ipv4_trivial(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__ipv4_bracketed(void) @@ -121,6 +142,7 @@ void test_url_scp__ipv4_bracketed(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__ipv4_user(void) @@ -133,6 +155,7 @@ void test_url_scp__ipv4_user(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__ipv4_port(void) @@ -145,6 +168,7 @@ void test_url_scp__ipv4_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__ipv4_user_port(void) @@ -157,6 +181,7 @@ void test_url_scp__ipv4_user_port(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__ipv6_trivial(void) @@ -169,6 +194,7 @@ void test_url_scp__ipv6_trivial(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__ipv6_user(void) @@ -181,6 +207,7 @@ void test_url_scp__ipv6_user(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__ipv6_port(void) @@ -193,6 +220,7 @@ void test_url_scp__ipv6_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__ipv6_user_port(void) @@ -205,6 +233,7 @@ void test_url_scp__ipv6_user_port(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__hexhost_and_port(void) @@ -217,6 +246,7 @@ void test_url_scp__hexhost_and_port(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 1); } void test_url_scp__malformed_ipv6_one(void) @@ -229,6 +259,7 @@ void test_url_scp__malformed_ipv6_one(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__malformed_ipv6_two(void) @@ -241,6 +272,7 @@ void test_url_scp__malformed_ipv6_two(void) cl_assert_equal_p(conndata.username, NULL); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__malformed_ipv6_with_user(void) @@ -253,6 +285,7 @@ void test_url_scp__malformed_ipv6_with_user(void) cl_assert_equal_s(conndata.username, "git"); cl_assert_equal_p(conndata.password, NULL); cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); + cl_assert_equal_i(conndata.port_specified, 0); } void test_url_scp__invalid_addresses(void) diff --git a/tests/util/vector.c b/tests/util/vector.c index 04afaa49..66f6b383 100644 --- a/tests/util/vector.c +++ b/tests/util/vector.c @@ -12,7 +12,7 @@ void test_vector__0(void) for (i = 0; i < 10; ++i) { git_vector_insert(&x, (void*) 0xabc); } - git_vector_free(&x); + git_vector_dispose(&x); } @@ -27,7 +27,7 @@ void test_vector__1(void) git_vector_insert(&x, (void*) 0x123); git_vector_remove(&x, 0); /* used to read past array bounds. */ - git_vector_free(&x); + git_vector_dispose(&x); } @@ -59,7 +59,7 @@ void test_vector__2(void) git_vector_uniq(&x, NULL); cl_assert(x.length == 2); - git_vector_free(&x); + git_vector_dispose(&x); git__free(ptrs[0]); git__free(ptrs[1]); @@ -91,7 +91,7 @@ void test_vector__3(void) cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); } - git_vector_free(&x); + git_vector_dispose(&x); } /* insert_sorted with duplicates */ @@ -122,7 +122,7 @@ void test_vector__4(void) cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); } - git_vector_free(&x); + git_vector_dispose(&x); } typedef struct { @@ -189,7 +189,7 @@ void test_vector__5(void) _struct_count--; } - git_vector_free(&x); + git_vector_dispose(&x); } static int remove_ones(const git_vector *v, size_t idx, void *p) @@ -274,7 +274,7 @@ void test_vector__remove_matching(void) git_vector_remove_matching(&x, remove_ones, NULL); cl_assert(x.length == 4); - git_vector_free(&x); + git_vector_dispose(&x); } static void assert_vector(git_vector *x, void *expected[], size_t len) @@ -376,7 +376,7 @@ void test_vector__grow_and_shrink(void) git_vector_remove_range(&x, 0, 1); assert_vector(&x, NULL, 0); - git_vector_free(&x); + git_vector_dispose(&x); } void test_vector__reverse(void) @@ -407,7 +407,7 @@ void test_vector__reverse(void) for (i = 0; i < 5; i++) cl_assert_equal_p(out2[i], git_vector_get(&v, i)); - git_vector_free(&v); + git_vector_dispose(&v); } void test_vector__dup_empty_vector(void) @@ -426,5 +426,5 @@ void test_vector__dup_empty_vector(void) cl_assert_equal_i(8, dup._alloc_size); cl_assert_equal_i(1, dup.length); - git_vector_free(&dup); + git_vector_dispose(&dup); }